我正在使用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可以正常工作。在互联网上,带宽是有限的,并且每次达到限制都会导致数据包丢失。 TCP将通过重新发送数据包来处理数据包丢失。每次重新发送都会导致流的延迟延长。
为了更好地理解,请尝试想象一个数据包包含整个帧(JPEG图像)。假设链路的正常延迟为100ms(帧传输的时间)。对于25 FPS,您需要每40ms发送一次帧。如果帧在传输中丢失,则TCP将确保副本将重新发送。 TCP可以检测到这种情况并在2倍的延迟时间内将其固定在理想的情况下-2 * 100ms(实际上会更长,但是为了简单起见,我保留了这种情况)。因此,在图像丢失期间,接收器队列中有5帧正在等待,并等待单个丢失的帧。在此示例中,一帧丢失导致5帧延迟。并且因为TCP创建了数据包队列。延迟永远不会减少,只会增长。在理想情况下,当带宽足够时,延迟将保持不变。
如何修复
为此,我使用了事件drain。该算法背后的思想是ffmpeg生成自己的速度帧。然后node.js读取帧并始终保留最后收到的帧。它还具有单个帧大小的传出缓冲区。因此,如果单帧的发送由于网络条件而延迟,则来自ffmpeg的传入图像将被静默丢弃(这补偿了低带宽),但最后一次接收的除外。因此,当TCP缓冲区发出信号(通过drain
事件)已正确发送某帧时,nodejs将获取最后收到的图像并将其写入流中。
此算法进行自我调节。如果发送带宽足够(发送速度快于ffmpeg生成的帧),则不会丢弃任何数据包,并传递25fps。如果带宽平均只能传输一半的帧,则将丢弃两帧之一,因此接收器将达到12.5fps,但不会增加延迟。
此算法中最复杂的部分可能是正确地将字节流切成帧。
UDP或TCP,尝试通过Internet实时流传输数据非常困难。
HLS会吐出可以通过HTTP下载的文件(在提供大文件时实际上非常有效,并且由于内置了纠错等功能,因此可以确保视频的完整性),然后按顺序重新组合客户端由播放器。
它很容易适应不同的质量流(协议的一部分),这使客户端可以决定其使用数据的速度。
此外,Netflix等使用的是Widevine DRM,它有点类似-基于HTTP的内容交付,尽管Widevine将采用不同的质量编码,将其填充到一个巨大的文件中,并使用HTTP范围请求在质量流之间进行切换。