【问题标题】:File streaming encounters buffer underrun/underflow?文件流遇到缓冲区欠载/下溢?
【发布时间】:2023-04-06 22:22:01
【问题描述】:

背景

我正在使用 Boost 在 C++ 中开发一个渐进式下载媒体流服务器。典型配置是运行 Android 4.2.2 的 Android 渲染设备,使用图库播放器作为媒体播放器,以及在 Windows 桌面上运行的媒体流服务器。 Android 设备通过 HTTP URL 请求媒体文件,媒体服务器使用渐进式下载流式传输文件。

问题

当尝试以 20 Mbps 的内部比特率流式传输视频文件时,渲染器会多次停止。典型的流媒体体验基本上由多次出现的步骤组成:

  1. 平滑渲染 3-5 秒
  2. 视频停顿 5-10 秒
  3. 转到步骤 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


【解决方案1】:

我只提供指点,没有解决方案。

正如您猜想的那样,这个问题涉及多个方面,其中任何一个都可能导致此问题 - 视频是如何编码的(是否有 B 帧,只有 I 帧等...),然后是android 设备用于访问此 HTTP 服务器的网络 - 在此测试期间它的拥塞程度;解码器库和解码器应用程序。不是解决方案,但您可以尝试查看相同测试的行为:

1)使用不同的解码器/渲染应用程序

2) 在一天中的不同时间运行此测试(当网络负载可能较少时)

3) 尝试在不同的操作系统上解码/渲染(Linux 使用 mplayer 或其他东西)或在 Windows 上使用 Windows 媒体播放器)只是为了看看它是否与 Android OS tcp/ip 堆栈实现有任何关系。

【讨论】:

  • 感谢您的指点。我曾考虑过他们,但很高兴从别人那里听到。关于(1),我正在处理选择特定渲染器的渲染软件,因此没有选择其他渲染器的余地。对于 (2),当网络不那么繁忙时(例如,当我的同事回家休息时),我当然会看到改进。至于(3)我在使用DLNA从我的Windows机器渲染到渲染机器时取得了很好的效果。在这种情况下,我可以选择渲染器应用程序,但我没有测试我的服务器软件。
  • 那么根据您在评论中的发现 (3),它可能是您的服务器套接字代码。正如你所指出的,尝试 1)使用非阻塞 write() 2)如果可能,你可以尝试使用 POSIX 套接字而不是 boost::asio 库。 (我知道它的新代码添加,但你可以在一些编译时宏开关下将它添加到现有的),但这是作为一个测试来本地化你的问题,如果它是你的 asio 套接字 api 调用是原因。我有理由确定 TCP_NODELAY 不会在这里解决您的问题或无论如何都不会提供帮助。祝你好运,随时了解你的进展。
猜你喜欢
  • 2015-10-19
  • 1970-01-01
  • 2018-03-08
  • 2019-11-30
  • 1970-01-01
  • 2017-04-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多