【发布时间】:2023-04-06 22:22:01
【问题描述】:
背景
我正在使用 Boost 在 C++ 中开发一个渐进式下载媒体流服务器。典型配置是运行 Android 4.2.2 的 Android 渲染设备,使用图库播放器作为媒体播放器,以及在 Windows 桌面上运行的媒体流服务器。 Android 设备通过 HTTP URL 请求媒体文件,媒体服务器使用渐进式下载流式传输文件。
问题
当尝试以 20 Mbps 的内部比特率流式传输视频文件时,渲染器会多次停止。典型的流媒体体验基本上由多次出现的步骤组成:
- 平滑渲染 3-5 秒
- 视频停顿 5-10 秒
- 转到步骤 1
最合乎逻辑的解释是渲染器遇到了“buffer underrun”或“buffer underflow”
问题
有什么办法可以解决缓冲区欠载问题、提高媒体流输出速率并防止视觉停滞/阻塞?
技术信息
服务器代码如下所示:
void StreamFile (boost::asio::ip::tcp::socket *socket, const wchar_t *path)
{
. . .
for (long offset=startOffset; offset <= endOffset; offset+=streamingBlockSize)
{
long numBytesToRead = (std::min<long>) (endOffset - offset + 1, streamingBlockSize);
fread (buffer, 1, numBytesToRead, f);
if (RawSocketWrite (socket, buffer, numBytesToRead) == 0)
{
// RawSocketWrite() encountered a serious error, exit
break;
}
}
. . .
}
size_t RawSocketWrite (boost::asio::ip::tcp::socket *socket, const char *data, size_t len)
{
size_t numCharsWritten = 0;
try
{
numCharsWritten = boost::asio::write (socket, boost::asio::buffer (data, len));
}
catch (boost::system::system_error& e)
{
LOG_ERROR (("error", "write() failed in RawSocketWrite (socket %d) %s", socket->native (), e.what()));
numCharsWritten = 0;
}
return numCharsWritten;
}
我正在尝试使用以下文件数据流式传输一个 39 MB、16 秒的视频文件(由 MediaInfo 提供):
Video information
General
Complete name : TestVideo.mp4
Format : MPEG-4
Format profile : Base Media
Codec ID : isom
File size : 38.6 MiB
Duration : 16s 102ms
Overall bit rate : 20.1 Mbps
Video
ID : 1
Format : AVC
Format/Info : Advanced Video Codec
Format profile : High@L4.0
Format settings, CABAC : Yes
Format settings, ReFrames : 1 frame
Format settings, GOP : M=1, N=61
Codec ID : avc1
Codec ID/Info : Advanced Video Coding
Duration : 15s 701ms
Bit rate : 20.0 Mbps
Width : 1 920 pixels
Height : 1 080 pixels
Display aspect ratio : 16:9
Frame rate mode : Variable
Frame rate : 30.000 fps
Minimum frame rate : 29.732 fps
Maximum frame rate : 30.313 fps
Color space : YUV
Chroma subsampling : 4:2:0
Bit depth : 8 bits
Scan type : Progressive
Bits/(Pixel*Frame) : 0.322
Stream size : 37.5 MiB (97%)
Title : VideoHandle
Language : English
mdhd_Duration : 15701
Audio
ID : 2
Format : AAC
Format/Info : Advanced Audio Codec
Format profile : LC
Codec ID : 40
Duration : 16s 102ms
Source duration : 16s 131ms
Bit rate mode : Constant
Bit rate : 192 Kbps
Nominal bit rate : 96.0 Kbps
Channel(s) : 2 channels
Channel positions : Front: L R
Sampling rate : 48.0 KHz
Compression mode : Lossy
Stream size : 374 KiB (1%)
Source stream size : 375 KiB (1%)
Title : SoundHandle
Language : English
mdhd_Duration : 16102
StreamFile() 函数将'streamingBlockSize' 字节块以紧密循环的形式流式传输到输出套接字('streamingBlockSize' 是通过配置文件设置的,并在研究和调试当前缓冲区欠载问题时引入)。
使用 Wireshark 跟踪数据包会显示包含 1448 字节流数据的数据包以均匀的速度发送:
|Time | 192.168.0.197 |
| | | 192.168.0.199 |
|14.420722000| SYN, ACK | |Seq = 0 Ack = 1| |(10243) ------------------> (58358) |
|14.437750000| PSH, ACK - Len: 266 |Seq = 1 Ack = 188| |(10243) ------------------> (58358) |
|14.437924000| ACK - Len: 1448 |Seq = 267 Ack = 188| |(10243) ------------------> (58358) |
|14.437939000| ACK - Len: 1448 |Seq = 1715 Ack = 188| |(10243) ------------------> (58358) |
|14.437950000| ACK - Len: 1448 |Seq = 3163 Ack = 188| |(10243) ------------------> (58358) |
|14.442016000| ACK - Len: 1448 |Seq = 4611 Ack = 188| |(10243) ------------------> (58358) |
|14.444269000| ACK - Len: 1448 |Seq = 6059 Ack = 188| |(10243) ------------------> (58358) |
|14.444293000| ACK - Len: 1448 |Seq = 7507 Ack = 188| |(10243) ------------------> (58358) |
|14.444358000| ACK - Len: 1448 |Seq = 8955 Ack = 188| |(10243) ------------------> (58358) |
|14.444373000| ACK - Len: 1448 |Seq = 10403 Ack = 188| |(10243) ------------------> (58358) |
|14.444389000| ACK - Len: 1448 |Seq = 11851 Ack = 188| |(10243) ------------------> (58358) |
. . .
|72.768739000| ACK - Len: 1448 |Seq = 40488067 Ack = 188| |(10243) ------------------> (58358) |
|72.768766000| ACK - Len: 1448 |Seq = 40489515 Ack = 188| |(10243) ------------------> (58358) |
|72.772484000| ACK - Len: 1448 |Seq = 40490963 Ack = 188| |(10243) ------------------> (58358) |
|72.772521000| PSH, ACK - Len: 895 |Seq = 40492411 Ack = 188| |(10243) ------------------> (58358)
Wireshark 通过 Statistics>Summary 菜单项提供了有关上述数据包的非常有用的摘要信息:
Packets 27997
Between first and last packet 58.352 sec
Avg. packets/sec 479.797
Avg. packet size 1513.586 bytes
Bytes 42375867
Avg. bytes/sec 726213.548
Avg. MBit/sec 5.810
这告诉我们传输一个播放时间为 16.102 秒且渲染器经常遇到停顿的 39 MB 视频需要 58.352 秒。这听起来像是一个典型的缓冲区欠载案例。
此外,Wireshark 检测到的平均 Mbps 速率为 5.81 Mbps。根据定义,这永远无法满足需要以 20.1 Mbps 比特率渲染视频的渲染器。
可能的修复
在研究该问题时,我遇到了许多可能导致该问题的技术问题,并希望您的想法。
增加传递给 write() 的缓冲区大小
我尝试改变传递给 write() 函数的字节数(例如,4096、8192、16384),看看增加数据大小是否可以加快传输速度。它似乎没有什么区别(请参阅 MTU 和 MSS 的讨论以了解可能的解释)。
增加以太网 MTU(最大传输单元)和/或 TCP MSS(最大分段大小)
Wireshark 显示每个 TCP 数据包携带 1448 个视频原始数据。增加 MTU 或 MSS 会提高流传输吞吐量吗? http://www.stratus.com/blog/openvos/?p=1459 对 MTU 和 MSS 进行了有趣的比较。
TCP_NODELAY
有几个页面讨论了套接字设置 TCP_NODELAY(请参阅http://en.wikipedia.org/wiki/Transmission_Control_Protocol)。我的理解是它会改善多文件传输,这通常会导致文件的最后一个字节没有填满输出缓冲区。默认情况下,TCP 将等待 200 毫秒等待缓冲区填满。使用 TCP_NODELAY 不会有延迟。在单个视频文件流式传输的情况下,我预计不会有改进。这是正确的吗?
网络负载变化
正在使用的网络是否会导致数据流过慢?
boost::asio::write() 是一个阻塞写——非阻塞写有帮助吗?
最底部的 boost::asio::write() 是阻塞写入:
try
{
numCharsWritten = boost::asio::write (socket, boost::asio::buffer (data, len));
}
与非阻塞 write() 相比,使用阻塞 write() 时是否可能存在固有延迟?使用非阻塞写入会提高吞吐量吗?
非常感谢您的帮助。
【问题讨论】:
-
你如何测试它?通过WiFi?你测试过下载带宽吗?
-
抱歉这么久才回复——我需要下载局域网测速软件。当通过 Wi-Fi 连接到路由器时,我测量了 24 Mbps 的服务器上传速度,结果不稳定。当通过以太网电缆连接到路由器时,我测得服务器上传速度约为 600 Mbps,并且渲染始终完美。我相信这表明我的服务器软件正在以良好的速度提供视频。假设网络是不稳定的可能来源,是否可以安全地假设该软件是好的?
-
这就是为什么你有缓冲区,以减少波动。如果你有足够大的缓冲区和足够的平均带宽,你就不应该有任何波动。
-
回复您后,我在以太网电缆带宽为 65 Mbps 的网络上进行了测试,但渲染器仍然出现断断续续的问题。令我困扰的是,潜在用户并不总是通过以太网电缆连接。假设渲染器的缓冲区(我无法控制)足够大,为什么 65 Mbps 的带宽不足以让 20.1 Mbps 比特率的视频流畅渲染?
-
收集一些统计数据,例如数据发送速度等,并注销它们。 TCP_NODELAY 无法帮助您,它仅在延迟很重要的双向密集通信的情况下才有用。 TCP 本身会产生很大的延迟,但同样大的缓冲区应该会掩盖这个问题。 MTU 大小也对您没有帮助,默认值应该没问题。 TCP 窗口大小有助于避免等待接收方过于频繁的 ACK。但首先要收集更多信息。
标签: sockets boost tcp video-streaming