背景
我正在使用 Boost 在 C++ 中开发一个渐进式下载媒体流服务器。典型配置是运行 Android 4.2.2 的 Android 渲染设备,使用图库播放器作为媒体播放器,以及在 Windows 桌面上运行的媒体流服务器。Android 设备通过 HTTP URL 请求媒体文件,媒体服务器使用渐进式下载流式传输文件。
问题
当尝试以 20 Mbps 的内部比特率流式传输视频文件时,渲染器会多次停止。典型的流媒体体验基本上由多次出现的步骤组成:
- 平滑渲染 3-5 秒
- 视频停顿 5-10 秒
- 转到步骤 1
最合乎逻辑的解释是渲染器正在经历“缓冲区欠载”或“缓冲区下溢”
问题
有什么方法可以解决缓冲区欠载问题、提高媒体流输出速率并防止视觉停滞/阻塞?
技术信息
服务器代码如下所示:
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() 是否可能存在固有延迟?使用非阻塞写入会提高吞吐量吗?
非常感谢您的帮助。