You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

全屏幕录屏切换标签时FFmpeg流中断问题排查与解决问询

录屏推流至Twitch时切换标签导致FFmpeg流中断问题解决

问题现象

开发的录屏推流系统初始运行正常,但在录屏过程中切换浏览器标签时,后端FFmpeg流处理会中断,抛出以下错误:

FFmpeg STDERR: [matroska,webm @ 0x7f9dcb904580] EBML header parsing failed
[in#0 @ 0x7f9dcb904380] Error opening input: Invalid data found when processing input
Error opening input file -.
Error opening input files: Invalid data found when processing input

相关代码

前端录屏与WebSocket传输代码

const startRecording = async () => {
    try {
        const screenStream = await navigator.mediaDevices.getDisplayMedia({
            preferCurrentTab: true,
            systemAudio: 'include',
            surfaceSwitching: 'include',
            monitorTypeSurfaces: 'include',
            video: {
                displaySurface: 'browser',
                height: 720,
                width: 1280,
                frameRate: { ideal: 24, max: 30 },
            },
        });

        screenStream.getVideoTracks()[0].onended = () => {
            console.log('Screen sharing ended. Stopping the recorder.');
            stopRecording();
        };

        const micStream = await navigator.mediaDevices.getUserMedia({
            audio: true,
        });

        const combinedStream = new MediaStream([
            ...screenStream.getVideoTracks(),
            ...micStream.getAudioTracks(),
        ]);

        const recorder = new MediaRecorder(combinedStream, {
            mimeType: 'video/webm; codecs=vp8,opus',
            videoBitsPerSecond: 3 * 1024 * 1024,
        });

        const timeslice = 1000;

        recorder.ondataavailable = async (event) => {
            if (socket?.current?.connected && event.data.size > 0) {
                console.log('Sending chunk data:', socket.current.id);
                socket?.current.send(event.data);
                recordedChunks.current.push(event.data);
            } else if (!socket?.current?.connected) {
                handleSocketDisconnection();
            }
        };

        mediaRecorder.current = recorder;
        recorder.start(timeslice);
        setIsRecording(true);
    } catch (error) {
        console.log('Error starting screen recording:', error);
        toast.error('Failed to start screen recording: ' + error);
    }
};

const stopRecording = () => {
    if (socket?.current && mediaRecorder) {
        mediaRecorder?.current?.stop();
        socket.current.close();
        setIsRecording(false);
        downloadRecordedVideo();
    }
};

后端FFmpeg推流配置

const inputSettings = [
    '-f', 'webm', '-i', '-', '-v', 'error', '-analyzeduration', '1000000', '-probesize', '5000000',
];

const twitchSettings = (twitch) => {
    return [
        '-c:v', 'libx264', '-preset', 'veryfast', '-tune', 'zerolatency',
        '-g', '60', '-b:v', '2500k', '-maxrate', '3000k', '-bufsize', '8000k',
        '-r', '30', '-vf', 'tpad=stop_mode=clone:stop_duration=2',
        '-c:a', 'aac', '-ar', '44100', '-b:a', '96k',
        '-use_wallclock_as_timestamps', '1', '-async', '1',
        '-err_detect', 'ignore_err', '-reconnect', '1',
        '-reconnect_streamed', '1', '-reconnect_delay_max', '5',
        '-y', '-f', 'flv', twitch,
    ];
};

用户疑问

  • 录屏时切换标签是否会通过干扰帧率或丢帧引发该问题?
  • 有无办法确保FFmpeg不会因这类中断崩溃?
  • 针对切换标签场景,如何更可靠地处理流或优化FFmpeg配置?

问题根源

问题核心是浏览器对非活跃标签的资源限制:当标签切换为非活跃状态时,浏览器会大幅降低该标签的CPU优先级,限制媒体捕获的帧率(甚至临时暂停流),导致MediaRecorder生成的WebM片段出现EBML头损坏、数据不完整的情况,这类无效数据包被发送到FFmpeg后,触发解析错误并中断处理。

解决方案

1. 确认切换标签的影响

是的,切换标签直接引发问题:

  • 浏览器会将非活跃标签的媒体捕获帧率限制到1-5fps,甚至暂停流
  • MediaRecorder按固定timeslice生成的片段会因流中断出现结构损坏,缺失WebM格式必需的EBML头信息

2. 让FFmpeg容错不崩溃

调整FFmpeg输入参数,增强对损坏流的容忍度:

const inputSettings = [
    '-f', 'webm', '-i', '-', '-v', 'error',
    '-analyzeduration', '2000000', '-probesize', '10485760', // 增大探测范围
    '-fflags', '+genpts+igndts', // 生成缺失时间戳,忽略无效时间戳
    '-err_detect', 'ignore_err', '-ignore_unknown', // 忽略错误与未知流信息
];

3. 前端+后端协同优化切换标签场景

前端侧优化

  • 取消固定timeslice切片:去掉recorder.start(timeslice)中的timeslice参数,让MediaRecorder自然生成完整的WebM片段,避免强制切割不完整数据
  • 监听标签活跃状态动态调整参数:
document.addEventListener('visibilitychange', () => {
    if (mediaRecorder.current) {
        mediaRecorder.current.videoBitsPerSecond = document.hidden 
            ? 1 * 1024 * 1024 // 非活跃时降低比特率,减少资源占用
            : 3 * 1024 * 1024; // 恢复活跃后还原参数
    }
});
  • 验证片段完整性再发送:在WebSocket发送前检查WebM头有效性,丢弃损坏片段:
recorder.ondataavailable = async (event) => {
    if (socket?.current?.connected && event.data.size > 0) {
        const reader = new FileReader();
        reader.onload = () => {
            const buffer = new Uint8Array(reader.result);
            // 检查WebM文件签名(0x1A45DFA3)
            if (buffer.length >= 4 && buffer[0] === 0x1A && buffer[1] === 0x45 && buffer[2] === 0xDF && buffer[3] === 0xA3) {
                socket.current.send(event.data);
                recordedChunks.current.push(event.data);
            } else {
                console.warn('Discarding invalid WebM chunk');
            }
        };
        reader.readAsArrayBuffer(event.data);
    } else if (!socket?.current?.connected) {
        handleSocketDisconnection();
    }
};
  • 添加WebSocket心跳:定期发送小数据包维持连接,避免因流中断触发连接断开

后端侧优化

  • FFmpeg进程自动重启:监听FFmpeg退出事件,若检测到是输入错误导致崩溃,自动重启进程并重新接收流
  • 添加中间缓冲层:在WebSocket和FFmpeg之间增加内存缓冲区,收集一定量的有效数据后再喂给FFmpeg,避免零散损坏数据包直接进入处理流程

内容的提问来源于stack exchange,提问作者Ibad Ahmad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 17:49:52