Node.js下FFmpeg间管道逐帧传输及缓冲区报错排查
FFmpeg管道逐帧传输报错解决方案
问题根因
触发24帧后无效缓冲区大小报错和写入速度过快无关,核心是解码端参数配置错误:
- 解码命令中加了
-c:v copy参数,该参数代表直接拷贝压缩后的视频流,不会执行解码操作,输出的是H.264/HEVC等格式的压缩二进制数据,完全不是你需要的rawvideo原始帧。 - 编码端是按照固定大小的原始YUV帧格式读取输入,读到长度、格式都不匹配的压缩流数据,解析到第24帧时数据长度凑不齐一帧要求的大小,自然触发缓冲区无效报错。
- 额外问题:当前直接将
decode.stdout绑定为编码端stdin的写法,没有预留中间帧自定义处理的接入位置,且路径拼接写法存在跨平台兼容隐患。
关键参数说明
你当前使用yuv420p像素格式、854x480分辨率输出,单帧原始数据的固定大小为:854 * 480 * 3 / 2 = 614880 字节
后续做中间帧处理时,必须按这个固定长度从解码输出流中切分完整帧,处理完成后再按固定帧长度写入编码端,否则依然会触发缓冲区错误。
可运行实现代码
版本1:无中间处理的直连管道版本(修正参数即可正常运行)
const { spawn } = require('child_process'); const path = require('path'); // 修正解码参数:去掉-c:v copy,明确指定输出像素格式和编码端对齐 const decArgs = [ '-i', path.join(__dirname, 'public/blackpink.mp4'), '-an', // 丢弃音频 '-f', 'rawvideo', '-pix_fmt', 'yuv420p', // 和编码端输入格式严格对齐 '-' // 输出到标准输出 ]; const decode = spawn('ffmpeg', decArgs); const encArgs = [ '-f', 'rawvideo', '-video_size', '854x480', '-pix_fmt', 'yuv420p', // 和解码端输出格式严格对齐 '-i', '-', '-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '30', '-y', path.join(__dirname, 'public/output.mp4') ]; const encode = spawn('ffmpeg', encArgs); // 管道连接,同时监听两边的错误日志 decode.stdout.pipe(encode.stdin); decode.stderr.on('data', data => console.log('[解码端日志]', data.toString())); encode.stderr.on('data', data => console.log('[编码端日志]', data.toString())); // 监听进程退出事件 encode.on('close', code => { console.log(`编码完成,退出码:${code}`); decode.kill(); });
版本2:带中间帧自定义处理的版本
如果需要在解码、编码中间插入自定义帧处理逻辑,不能直接做stdio直连,需要手动按单帧大小切分流数据,处理后再写入编码端,同时做好背压避免内存溢出:
const { spawn } = require('child_process'); const path = require('path'); const FRAME_WIDTH = 854; const FRAME_HEIGHT = 480; const FRAME_SIZE = FRAME_WIDTH * FRAME_HEIGHT * 3 / 2; // yuv420p单帧大小 const decArgs = [ '-i', path.join(__dirname, 'public/blackpink.mp4'), '-an', '-f', 'rawvideo', '-pix_fmt', 'yuv420p', '-' ]; const decode = spawn('ffmpeg', decArgs); const encArgs = [ '-f', 'rawvideo', '-video_size', `${FRAME_WIDTH}x${FRAME_HEIGHT}`, '-pix_fmt', 'yuv420p', '-i', '-', '-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '30', '-y', path.join(__dirname, 'public/output.mp4') ]; const encode = spawn('ffmpeg', encArgs); let frameBuffer = Buffer.alloc(0); // 监听解码输出,逐帧切分处理 decode.stdout.on('data', chunk => { frameBuffer = Buffer.concat([frameBuffer, chunk]); // 循环切分出所有完整帧 while (frameBuffer.length >= FRAME_SIZE) { const frame = frameBuffer.subarray(0, FRAME_SIZE); frameBuffer = frameBuffer.subarray(FRAME_SIZE); // 此处插入你的自定义帧处理逻辑,直接修改frame缓冲区即可 // 示例:processFrame(frame); // 处理完写入编码端,处理背压 if (!encode.stdin.writableNeedDrain) { encode.stdin.write(frame); } else { decode.stdout.pause(); encode.stdin.once('drain', () => decode.stdout.resume()); } } }); decode.stdout.on('end', () => { // 剩余数据写完后关闭编码端输入 if (frameBuffer.length > 0) encode.stdin.write(frameBuffer); encode.stdin.end(); }); decode.stderr.on('data', data => console.log('[解码端日志]', data.toString())); encode.stderr.on('data', data => console.log('[编码端日志]', data.toString())); encode.on('close', code => { console.log(`编码完成,退出码:${code}`); decode.kill(); });
注意事项
- 解码端、编码端的
-pix_fmt、-video_size参数必须严格对齐,否则会出现花屏、帧率错乱、缓冲区报错问题。 - 不要在rawvideo输出场景下使用
-c:v copy,copy模式不会做格式转换,输出的不是原始帧数据。 - 手动处理流数据时必须按固定单帧大小切分,不能拿到任意chunk就直接写入编码端。
- 写入编码端时要监听
drain事件做背压处理,避免缓冲区积压导致内存暴涨。
内容的提问来源于stack exchange,提问作者imagesck
相关产品推荐
相关产品推荐

