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

WebSocket+FFmpeg录屏视频提前结束问题(EC2部署后)

录屏应用部署EC2后视频保存时长不稳定问题

我开发了一款录屏应用:前端用MediaRecorder生成video/webm;codecs=H264格式的流,通过WebSocket每2秒把字节数据发送到FastAPI后端;后端调用FFmpeg把接收的字节写入MP4文件。本地运行完全正常,但部署到us-east-1区域的EC2实例后,录制30秒的视频只能保存3秒左右,有时能保存90%,时长极不稳定。调试多日没解决,相关代码及FFmpeg日志如下:

前端代码

const recorder = new MediaRecorder(stream, {
            mimeType: 'video/webm;codecs=H264',
            videoBitsPerSecond: 8000000
        });

recorder.ondataavailable = (e: BlobEvent) => {
    socketRef.socket.send(e.data)
}

后端代码

@router.websocket("/stream")
async def websocket_endpoint(websocket: WebSocket, token: str = Query(...), videoId: str = Query(...), authorize: AuthJWT = Depends()):
    await manager.connect(websocket)
    dataNumber = 1

    recordingFile = os.path.join(temp_dir, f"recording_{videoId}.mp4")

    command = [
        'ffmpeg', 
        '-y',
        '-i', 
        '-', 
        '-codec:v', 
        'copy', 
        '-f', 'mp4',
        recordingFile,
    ]

    process = subprocess.Popen(command, stdin=subprocess.PIPE)
    try:
        while True:
            try:
                data = await websocket.receive_bytes()
                if not data:
                    break
                process.stdin.write(data)
                await websocket.send_json({"chunkNumber": dataNumber, "status": 200})
                dataNumber = dataNumber + 1
            except RuntimeError:
                break      
    except WebSocketDisconnect:
        print(f"Client disconnected: {websocket.client.host}")
    finally:
        manager.disconnect(websocket)

        process.stdin.close()
        process.wait()
        process.terminate()  

FFmpeg日志

libpostproc 57. 3.100/57. 3.100
Input #6, matroska, webm, from 'fd:':
Metadata:
encoder: Chrome
Duration: N/A, start: 0.000000, bitrate: N/A
Stream #0:0(eng): Video: h264 (Constrained Baseline), yuv420p(tv, bt789, progressive), 1920x1080, SAR 1:1 DAR 16:9, 30.30 fps, 56 tbr, 1k tbn (default)

Output #8, mp4, to /tmp/recording_d367d8d3-60de-483e-bf29-1849841b82f3.mp4':
Metadata:
encoder: Lavf60.16.100
Stream #0:0(eng): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 1920x1080 [SAR 1:1 DAR 16:9], q=2-31, 38.30 fps, 56 tbr, 16k tbn (default)
Stream mapping:
Stream #0:0> #0:6 (copy)
Client disconnected: 127.0.0.189 bitrate=3374.0kbits/s speed=0.999x
[matroska, webm @ 8x61e88988f3c0]
9493864 (0x98dd68)
[out#0/mp4 @ 0x61e8898cc488] video:9263kB audio:0kB subtitle:0kB other streams: 9kB global headers: 8kB muxing overhead: 0.143342%
size= 9276kB time=00:00:21.85 bitrate=3476.8kbits/s speed=1.08x
INFO:
connection closed
INFO: 127.0.0.1:52546 "POST /api/process HTTP/1.1" 200 ок

问题分析与解决方案

核心问题点

  1. WebSocket数据传输与FFmpeg写入异步不匹配:后端同步写入process.stdin.write(data)在EC2网络环境下,可能因为缓冲区未及时刷新导致数据滞留丢失;同时FastAPI异步WebSocket接收逻辑与同步subprocess写入存在线程安全隐患。
  2. MP4容器特性限制:MP4依赖全局索引(moov原子),直接流拷贝-codec:v copy时,若WebM流不完整或FFmpeg未正确收尾,会导致MP4文件无法完整解析时长。
  3. EC2网络与进程收尾逻辑缺陷:网络带宽波动可能导致WebSocket数据接收不完整,且客户端断开时FFmpeg未收到EOF信号,无法正确完成文件写入。

具体修复步骤

1. 修复后端写入逻辑,强制刷新缓冲区

每次写入数据后强制刷新FFmpeg输入缓冲区,避免数据滞留在内存:

# 替换原写入代码
process.stdin.write(data)
process.stdin.flush()  # 新增:强制刷新缓冲区

2. 调整FFmpeg命令,适配流式MP4生成

添加-movflags frag_keyframe+empty_moov+default_base_moof参数,让FFmpeg生成支持流式处理的MP4片段,无需等待完整moov原子:

command = [
    'ffmpeg', 
    '-y',
    '-i', '-',
    '-codec:v', 'copy',
    '-movflags', 'frag_keyframe+empty_moov+default_base_moof',  # 新增流式MP4参数
    '-f', 'mp4',
    recordingFile,
]

3. 优化WebSocket接收与进程收尾逻辑

  • 添加接收超时处理,避免无限等待;
  • 客户端断开时确保FFmpeg收到EOF信号,完成文件写入:
try:
    while True:
        try:
            # 新增:30秒超时,防止进程卡住
            data = await asyncio.wait_for(websocket.receive_bytes(), timeout=30)
            if not data:
                break
            process.stdin.write(data)
            process.stdin.flush()
            await websocket.send_json({"chunkNumber": dataNumber, "status": 200})
            dataNumber += 1
        except asyncio.TimeoutError:
            print("WebSocket接收超时,关闭连接...")
            break
except WebSocketDisconnect:
    print(f"客户端断开连接: {websocket.client.host}")
finally:
    manager.disconnect(websocket)
    # 确保FFmpeg收到EOF并完成写入
    process.stdin.close()
    process.wait(timeout=10)
    process.terminate()

4. 前端优化MediaRecorder分片策略

明确设置timeslice参数,确保分片间隔稳定,且只发送非空数据:

const recorder = new MediaRecorder(stream, {
    mimeType: 'video/webm;codecs=H264',
    videoBitsPerSecond: 8000000,
    timeslice: 2000  // 明确设置2秒分片间隔
});

recorder.ondataavailable = (e: BlobEvent) => {
    if (e.data.size > 0) {  // 只发送非空数据块
        socketRef.socket.send(e.data)
    }
}

验证方法

部署修复后的代码到EC2,多次录制30秒视频,检查MP4文件时长是否稳定;查看FFmpeg日志,确认无truncated或invalid frame等错误信息。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 18:45:06