我的目标是在 Node 中渲染画布,并将该画布流式传输到 RTMP 服务器(最终是 Twitch,但现在我正在在本地 RTMP 服务器上测试)。流式传输到 RTMP 的标准方式似乎是ffmpeg
,所以我使用它,从 NodeJS 中作为子进程生成。
我尝试了很多不同的技术组合ffmpeg
params 以获得一致的帧速率和“实时”速度的流,但无法弄清楚。这是我迄今为止走过的路
渲染画布并以连续间隔发送输入
import { createCanvas } from 'canvas';
const canvas = createCanvas(1920, 1080);
const ctx = canvas.getContext('2d');
const fps = 30;
const ffmpeg = spawn('ffmpeg', [
'-re',
'-framerate', String(.fps),
'-r', String(fps),
'-i', '-',
'-vcodec', 'libx264',
'-r', String(fps),
'-s', '1920x1080',
'-g:v', String(2*fps),
'-c:a', 'aac',
'-f', 'flv', 'rtmp://127.0.0.1/live'
]);
ffmpeg.stdout.pipe(process.stdout)
ffmpeg.stderr.pipe(process.stderr)
const send = () => {
ctx.fillStyle = 'red'
ctx.fillRect(0, 0, 1920, 1080);
ctx.font = '100px Arial';
ctx.fillStyle = 'black'
ctx.fillText(new Date().toLocaleString(), 500, 500);
ffmpeg.stdin.write(canvas.toBuffer())
setImmediate(() => send())
}
send()
观察结果
- 流实际启动大约需要 35 秒(我认为是因为 ffmpeg 需要一些时间来分析输入?)
- 帧速率远低于我设置的值,并且“速度”也非常低,尽管我不能 100% 确定这意味着什么。示例日志
Frame= 906 fps=3.9 q=29.0 size= 311kB time=00:00:27.83 bitrate= 91.5kbits/s speed=0.119x
-
Stream behavior
- 在 VLC 中打开后,加载大约需要一分钟
- 流中的计时器开始时比实际时间晚大约 1 分钟,卡在某一秒上 30 秒以上,然后快速增加几秒,然后再次卡住
我有一种预感,这种奇怪行为的至少部分原因是在我发送输入的同一个循环中渲染画布ffmpeg
速度太慢,无法达到 30 FPS。
在与 ffmpeg 输入间隔不同的间隔中渲染画布
每秒仅渲染画布 FPS 次
继续将输入发送至ffmpeg
尽可能快
import { createCanvas } from 'canvas';
const canvas = createCanvas(1920, 1080);
const ctx = canvas.getContext('2d');
let buffer = canvas.toBuffer();
const fps = 30;
const ffmpeg = spawn('ffmpeg', [
...same as before
]);
const render = () => {
ctx.fillStyle = 'red'
ctx.fillRect(0, 0, 1920, 1080);
ctx.font = '100px Arial';
ctx.fillStyle = 'black'
ctx.fillText(new Date().toLocaleString(), 500, 500);
buffer = canvas.toBuffer();
setTimeout(() => render(), 1/fps)
}
render();
const send = () => {
ffmpeg.stdin.write(buffer)
setImmediate(() => send())
}
send()
观察结果
-
ffmpeg
几乎立即开始流式传输
- fps 开始时约为 16 fps,需要几秒钟才能达到 28 fps,然后大约 30 秒才能达到 30 fps。速度更接近 1 倍,但并非完全如此。示例日志
frame=15421 fps= 30 q=29.0 size= 4502kB time=00:08:31.66 bitrate= 72.1kbits/s speed=0.994x
-
Stream behavior
- 在 VLC 中打开后,加载大约需要 5 秒
- 计时器在同一秒停留多分钟
我对流被卡在 1 个时间戳的预感是,当 ffmpeg 发送帧时out以每秒 30 帧的速度,我发送帧的速度比这快得多。所以在流媒体的前一秒
- Canvas 使用时间戳 T 渲染 30 次
-
send
运行 N 次,其中 N 可能远高于 30,发送ffmpeg
具有当前时间戳的 N 帧
- ffmpeg 现在有 N 个带有时间戳 T 的帧,但每秒只能发送 30 个帧,因此屏幕上的时间戳需要 1 秒以上才能改变
仅每 1/FPS 秒发送 ffmpeg 一帧
与以前相同,但不是尽快发送 ffmpeg 帧,而是每秒仅发送 FPS 帧。
import { createCanvas } from 'canvas';
const canvas = createCanvas(1920, 1080);
const ctx = canvas.getContext('2d');
let buffer = canvas.toBuffer();
const fps = 30;
const ffmpeg = spawn('ffmpeg', [
...same as before
]);
const render = () => {
...same as before
}
render();
const send = () => {
ffmpeg.stdin.write(buffer)
setTimeout(() => send(), 1/fps)
}
send()
观察结果
-
ffmpeg
需要几秒钟才能开始流式传输
- fps 一开始很高,大约为 28,在接下来的一分钟左右下降到 16。速度也随之下降。示例日志
frame= 1329 fps= 16 q=29.0 size= 463kB time=00:00:41.93 bitrate= 90.5kbits/s speed= 0.5x
-
Stream behavior
- 在 VLC 中打开后,加载大约需要 10 秒
- 计时器增加的速度大约是预期的两倍,然后挂在一秒上一段时间,然后再次开始以相同的速率增加
我就到此为止,但是 tl;dr 我已经尝试了一大堆不同的组合-re, -framerate, -fps_mode, -r
ffmpeg args,以及代码中的一些其他技术,例如继续使用setImmediate
发送帧,但使用日期比较以 FPS 速率实际发送帧。我确信我可能缺少一些基本的视频流知识,所以我只是在寻找有关如何让我的画布以“实时”速度流式传输的任何指导,或者我在这里可能缺少的任何内容。