node.js - TCP还是UDP?视频流的制作延迟
问题描述
我正在使用 FFMPEG 和 nodejs 流Duplex
类从相机创建视频流。
this.ffmpegProcess = spawn('"ffmpeg"', [
'-i', '-',
'-loglevel', 'info',
/**
* MJPEG Stream
*/
'-map', '0:v',
'-c:v', 'mjpeg',
'-thread_type', 'frame', // suggested for performance on StackOverflow.
'-q:v', '20', // force quality of image. 2-31 when 2=best, 31=worst
'-r', '25', // force framerate
'-f', 'mjpeg',
`-`,
], {
shell: true,
detached: false,
});
在本地网络上,我们正在使用几台计算机对其进行测试,并且每件事都非常稳定,延迟时间最长为 2 秒。
但是,我们已经将该服务导入 AWS 生产环境,当我们连接第一台计算机时,我们有大约 2 秒的延迟,但是如果另一个客户端连接到流,它们都开始延迟很多,延迟累积到 10+秒,此外视频非常慢,就像帧之间的运动一样。
现在我问 TCP 还是 UDP 是因为我们使用 TCP 作为流,这意味着发送的每个数据包都实现了 TCP syn-ack-synack 协议和序列。
我的问题是,TCP 真的会导致延迟达到 10 秒且动作非常慢的问题吗?因为在本地网络上它工作得很好。
解决方案
是的,TCP 绝对不是正确的协议。可以使用,但在源端进行了一些修改。遗憾的是,UDP 不是灵丹妙药,如果没有额外的逻辑,UDP 也无法解决问题(如果您不关心会看到从其他帧随机构建的损坏帧)。
解释
TCP 的主要特点是数据包以正确的顺序传递,并且所有数据包都被传递。这两个功能非常有用,但对视频流非常有害。
在本地网络上,带宽非常大,而且丢包率非常低,因此 TCP 工作正常。在互联网上,带宽是有限的,每次达到限制都会导致数据包丢失。TCP 将通过重新发送数据包来处理数据包丢失。每次重新发送都会导致流的延长延迟。
为了更好地理解,试着想象一个数据包包含整个帧(JPEG 图像)。假设链路的正常延迟为 100 毫秒(帧传输的时间)。对于 25 FPS,您需要每 40 毫秒传送一帧。如果帧在传输过程中丢失,TCP 将确保重新发送副本。TCP 可以检测到这种情况并在 2 倍延迟 - 2*100ms 中修复理想情况(实际上它会更多,但为了简单起见,我保留了这个)。因此,在图像丢失期间,接收器队列中等待 5 帧并等待单个丢失帧。在此示例中,丢失一帧会导致 5 帧延迟。并且因为 TCP 创建了数据包队列。延迟永远不会减少,只会增长。在带宽足够的理想情况下,延迟仍然相同。
如何修复它
我在 nodejs 中所做的是修复源端。TCP中的数据包只有在源会做的情况下才能被跳过,TCP自己没有办法。
为此,我使用了 event drain。该算法背后的想法是ffmpeg以自己的速度生成帧。node.js 读取帧并始终保留最后收到的帧。它还具有单帧大小的传出缓冲区。因此,如果由于网络条件导致单帧的发送延迟,则来自 ffmpeg 的传入图像将被静默丢弃(这补偿了低带宽),除了最后接收的图像。因此,当 TCP 缓冲区(通过drain
事件)发出某些帧已正确发送的信号时,nodejs 将获取最后接收到的图像并将其写入流中。
该算法自我调节。如果发送带宽足够(发送速度比 ffmpeg 生成的帧快),则不会丢弃任何数据包,并且将传送 25fps。如果带宽平均只能传输一半帧,则两个帧中的一个将被丢弃,因此接收器将保持 12.5fps 但不会增加延迟。
该算法中最复杂的部分可能是将字节流正确地分割成帧。
推荐阅读
- java - Android 应用程序中的 SQLite 与 ArrayList
- r - RShiny:DT选择行并分配给组
- google-cloud-sql - Google Cloud SQL 数据库删除保护
- python - 在类的其他地方访问这些函数和变量(Python)
- javascript - 处理快速异步中间件中的错误
- python - 为什么我的keras模型有这么多参数?
- java - 查询已经存在的数据库返回空游标
- javascript - TypeScript 没有 window.eval()
- javascript - jQuery .ajax 到 Vanilla JavaScript
- javascript - DroidEdit Termux 集成