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

Node.js如何分块读取流数据 解决ffmpeg编码中途冻结问题

问题核心原因

你的代码存在4个关键逻辑错误,直接导致编码提前冻结、尾部内容丢失:

  1. 事件绑定逻辑完全错误:Node.js流的pause事件仅在流状态从「流动」切换为「暂停」时触发,不是调用pause()方法就会100%同步执行回调,你把帧切分逻辑完全绑定在这个事件上,会出现凑够整帧但逻辑不触发、帧处理时序混乱的问题。
  2. 缓冲区偏移计算错误:提取完一帧数据后,你计算剩余数据起始偏移的公式buffer.length - bufferData.length是错的——正确的剩余数据起始位置应该是已提取的帧长度colorsLength,你写的公式仅在缓冲区总长度恰好为2倍单帧大小时生效,只要缓冲区长度超过2倍帧大小,就会丢失中间的帧数据,累计下来就会出现总时长少2秒的问题。
  3. 未处理流背压:直接调用encode.stdin.write()不判断返回值,当编码端ffmpeg的输入缓冲区占满时,写入的数据会被丢弃或者造成流阻塞,是进程无报错冻结的核心诱因。
  4. 结尾逻辑不完整:解码流结束后,你直接把所有剩余缓冲区全部写入编码端,既不判断是否为完整帧,也没有主动调用encode.stdin.end()通知编码端输入完成,ffmpeg会一直等待后续输入导致永久挂起。
修复后可运行代码
const { spawn } = require('child_process');
const path = require('path');

const decArgs = [
    '-i', path.join(__dirname, '/public/future.mkv'),
    '-an',
    '-pix_fmt', 'rgb32',
    '-f', 'rawvideo',
    '-'
];

const decode = spawn('ffmpeg', decArgs, {
    stdio: [
        'ignore',
        'pipe',
        'ignore'
    ] 
});

// 编码参数配置
const width = 854;
const height = 480;
const channels = 4;
const fps = '29.97002997002997';
const frameSize = width * height * channels; // 单帧字节大小

const encArgs = [
    '-pix_fmt', 'rgb32',
    '-f', 'rawvideo',
    '-s', `${width}x${height}`,
    '-r', fps,
    '-i', '-',
    '-c:v', 'libx264',
    '-preset', 'ultrafast',
    '-crf', '30',
    '-pix_fmt', 'yuv420p',
    '-r', fps,
    '-y',
    path.join(__dirname, '/public/output.mp4')
];

const encode = spawn('ffmpeg', encArgs, {
    stdio: [
        'pipe',
        'ignore',
        'pipe'
    ]
});

let buffer = Buffer.alloc(0);

// 处理编码端背压:缓冲区满时暂停解码,排空后恢复
encode.stdin.on('drain', () => {
    decode.stdout.resume();
});

decode.stdout.on('data', data => {
    buffer = Buffer.concat([buffer, data]);
    
    // 循环切分完整帧,不依赖pause事件
    while (buffer.length >= frameSize) {
        // 提取一帧完整数据
        const frameData = buffer.subarray(0, frameSize);
        // 保留剩余未处理数据,修正偏移计算
        buffer = buffer.subarray(frameSize);
        
        // 写入编码端,若缓冲区满则暂停解码流
        if (!encode.stdin.write(frameData)) {
            decode.stdout.pause();
        }
    }
});

decode.stdout.on('end', () => {
    // 末尾剩余不足一帧的冗余数据直接丢弃,写完主动关闭编码输入流
    encode.stdin.end();
});

// 打印编码端日志方便排查问题
encode.stderr.on('data', data => {
    console.log(data.toString());
});

// 监听进程退出,捕获异常
encode.on('exit', code => {
    console.log(`编码进程退出,退出码:${code}`);
});
decode.on('exit', code => {
    console.log(`解码进程退出,退出码:${code}`);
});
关键修改说明
  • 帧切分逻辑直接放在data事件回调中,用循环持续切分所有完整帧,pause()/resume()仅用于流控,不再承载业务逻辑,避免事件时序问题
  • 用Buffer.subarray()替代手动拷贝+偏移计算,直接截取缓冲区片段,从根源避免偏移计算错误,性能也比手动copy更高
  • 新增drain事件监听,处理流背压:编码端输入缓冲区满时自动暂停解码,等缓冲区排空后再恢复读取,避免数据丢失和进程阻塞
  • 解码流结束后主动调用encode.stdin.end()通知ffmpeg输入完成,避免进程永久等待挂起;末尾不足一帧的冗余数据是rawvideo流的正常填充数据,丢弃不影响实际视频内容
  • 新增进程退出监听,方便后续排查运行异常

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 11:57:19