无法成功关闭节点 .js 中的 ffmpeg 流

unable to successfully close ffmpeg stream in node.js

提问人:K. Russell Smith 提问时间:9/25/2022 更新时间:9/25/2022 访问量:241

问:

我正在尝试编写一个使用 canvas api(通过 node-canvas,该项目目前唯一的 npm 依赖项)生成帧的 Node 视频应用程序,并通过流将其写入 ffmpeg 以生成视频:

const { createCanvas } = require('canvas');
const { spawn } = require('child_process');
const fs = require('fs');
const canvas = createCanvas(1280, 720);

const ffmpeg = spawn('ffmpeg', [
    '-y',
    '-f', 'rawVideo',
    '-vcodec', 'rawVideo',
    '-pix_fmt', 'rgb24',
    '-s', `${ canvas.width }x${ canvas.height }`,
    '-r', '40',
    '-i', '-', '-f', 'mp4',
    '-q:v', '5',
    '-an', '-vcodec', 'mpeg4', 'output.mp4',
]);

const ctx = canvas.getContext('2d');
ctx.font = '30px Prime';
ctx.fillStyle = 'blue';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Hello Canvas', canvas.width / 2, canvas.height / 2);

for (let i = 0; i < 250; ++i)
{
    console.log(i);
    ffmpeg.stdin.write(Buffer.from(ctx.getImageData(0, 0, canvas.width, canvas.height).data));
}
ffmpeg.stdin.end();

不幸的是,当我运行它时,程序在写入帧后抛出这个:

node:events:368
      throw er; // Unhandled 'error' event
      ^

Error: write EPIPE
    at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:98:16)
Emitted 'error' event on Socket instance at:
    at emitErrorNT (node:internal/streams/destroy:164:8)
    at emitErrorCloseNT (node:internal/streams/destroy:129:3)
    at processTicksAndRejections (node:internal/process/task_queues:83:21) {
  errno: -32,
  code: 'EPIPE',
  syscall: 'write'
}

Node.js v17.1.0

我做错了什么?

javascript 节点.js ffmpeg 节点.js-stream

评论

1赞 kesh 9/25/2022
你检查过ffmpeg的日志吗?''-f','rawVideo','-vcodec','rawVideo''跳出来,既不是一种格式,也不是一种编解码器。尝试。stderrrawVideorawvideo
1赞 jfriend00 9/25/2022
对流的写入需要适当的流控制。 如果流缓冲区已满,则返回,并且在获取事件之前不应再写入任何内容。在 nodejs 流文档中有很多关于如何编写此代码的示例。ffmpeg.stdin.write()falsedrain

答:

0赞 K. Russell Smith 9/25/2022 #1

因此,通过我得到的响应为我指明了正确的方向,我能够纠正我的 ffmpeg 生成中的语法错误,然后使画布数据重新编码为 24 位 RGB(因为 MP4 不支持 Alpha 通道);这些解决了我最初的问题。然后我正确地将写入过程设置为 drain:

const { createCanvas } = require('canvas');
const { spawn } = require('child_process');

const video = {
    title:    'canvas',
    width:    1280,
    height:   720,
    fps:      25,
    duration: 10000,
}
const ffmpeg = spawn('ffmpeg', [
    '-y',
    '-f', 'rawvideo',
    '-vcodec', 'rawvideo',
    '-pix_fmt', 'rgb24',
    '-s', `${ video.width }x${ video.height }`,
    '-r', `${ video.fps }`,
    '-i', '-', '-f', 'mp4',
    '-q:v', '5',
    '-an', '-vcodec', 'mpeg4', `${ video.title }.mp4`,
    '-report',
]);
const canvas = createCanvas(video.width, video.height);
const ctx = canvas.getContext('2d');

const draw = delta =>
{
    ctx.fillStyle = 'white';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.font = '60px Prime';
    ctx.fillStyle = 'blue';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText('Hello Canvas', canvas.width / 2, canvas.height / 2);
}
// mp4 does not support transparency:
const canvas_to_raw_rgb24 = ctx =>
{
    const data = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height).data;
    const rgb24 = new Uint8Array(data.length * 0.75);
    for (let i = 0, j = 0; i < data.length; ++i)
    {
        rgb24[j++] = data[i++];
        rgb24[j++] = data[i++];
        rgb24[j++] = data[i++];
    }
    return rgb24;
}

// Write the video
(() =>
{
    const frames = Math.floor(video.duration / 1000 * video.fps);
    let frame = frames;
    write();
    function write()
    {
        let ok = true;
        do
        {
            const delta = video.duration - (video.duration / frames * frame);
            draw(delta);
            --frame;
            const data = canvas_to_raw_rgb24(ctx);
            if (frame === 0)
            {
                ffmpeg.stdin.write(data);
            }
            else
            {
                ok = ffmpeg.stdin.write(data);
            }
        } while (frame > 0 && ok)
        if (frame > 0)
        {
            ffmpeg.stdin.once('drain', write);
        }
        else
        {
            ffmpeg.stdin.end();
        }
    }
})();

所以最后,我有一个强大的基础,可以生成一个可播放的视频