流媒体服务-传输延时(SEI插帧)

流媒体服务-传输延时(SEI插帧)_第1张图片

什么是延时

很多小伙伴认为,当推流端和拉流端显示的时间不一致,即为延时。

其实这种看法是比较片面的,不同的播放器,对同一路流进行测试,可能会得到不同的结果。

一般来说,延时为以下几个部分的累加组成

  • 采集延时

在采集摄像头或显卡画面时,由于fps的限制和cpu性能、内存拷贝速度等客观限制,采集画面成YUV/RGB等数据时会有一定的延时,一般延时为毫秒级别。由于一般编码器对输入数据格式存在限制,譬如要求统一输入YUV420P,这样在做RGB->YUV420P转换时,也会有转换计算延时(这个可以通过libyuv库来降低)。总而言之,采集延时大概为毫秒级别,如果fps为30,那么一般采集延时会有30毫秒以上的延时,在内存拷贝和颜色转换时,又可能增加若干毫秒的延时。

  • 编码延时

在把原始画面输入到编码器时,并不会立即输出编码后的数据,特别是在开启B帧时,由于需要参考后面的P帧,那么延时会更大,所以延时敏感的情况下一般不开启B帧,这种情况下编码延时应该是毫秒级别,不是很大。

  • 网络上行传输延时

编码后的数据,要经过一定的协议打包才能写入socket,然后传输给推流服务器或拉流代理服务器,协议打包会有一定的内存拷贝和计算量,那么会增加延时,不过这个延时很小,基本忽略不计。数据在上传到服务器时,这个延时可大可小,取决于网络质量。

  • 服务器转协议延时

服务器在收到数据后,要读socket缓存、协议解析、解复用、重新打包等操作,不过总体而言,这个延时比较小,基本没什么影响。有时,服务器为了提高性能,会采取合并写的机制,也就是收到一定量的数据后才会一并转发,这个延时一般为几百毫秒。不过一般服务器会默认不会打开此机制

  • 网络下行延时

流媒体在把视频数据转发给播放器时,会存在网络发送,这个延时大小取决于网络质量。

  • 播放器延时

播放器延时主要有网络接收延时、协议解析解复用延时、解码延时、缓存延时、渲染延时组成,这些延时中缓存延时最大,因为一般的播放器为了保证在网络抖动情况下视频播放的流畅性,会以增加延时为代价,增加播放缓存,这样在网络变差时,不至于播放缓冲卡顿。而且为了音视频同步,也必须确保一定的缓存量。这种延时一般都是秒级别,一般5秒左右。

  • 播放器GOP缓存延时

流媒体服务器为了能让播放器立即出画面,往往会缓存最近的一个I帧,这个I帧往后的所有音视频数据被称作为GOP缓存。如果不缓存GOP,那么播放器要等下一个I帧才能解码成功或不花屏,显然为了提高播放体验,这个GOP缓存是不能去掉的。而一般GOP短则1~3秒,长则10几秒,这个跟采集端编码器设置有关,服务器改变不了。但是由于一般的播放器收到缓存后,并不会丢弃过多的画面来确保低延时。况且播放器还希望有一定的缓存来确保播放的流畅性,所以这个GOP缓存将会增大播放器的延时。

  • 综合延时

以上所有的延时累加,就是你观看到的直观延时。通常大部分延时可能是由播放器造成,如果对播放器缓冲区感兴趣的同学可以参考这篇文章:https://zhuanlan.zhihu.com/p/51582357

如何计算延时

本文所讨论的延时为网络传输延时,也就是经过采集编码后的数据,经由推流端通过网络发送到到流媒体服务器流媒体服务器将数据通过网络推送到到拉流端的延时。

本文推荐使用在码流中混入SEI帧的方式来计算传输延时,具体步骤如下

  • 推流端在I帧之前插入SEI帧,内容为推流端时间戳
  • 拉流端在接收到SEI帧之后,解码出推流端时间戳,与拉流端时间戳对比,计算出延时

流媒体服务-传输延时(SEI插帧)_第2张图片

SEI 帧

先复习一下H264码流结构

  • H.264 原始码流组成结构

H.264 原始码流(裸流)是由一个接一个 NALU 组成。它的功能分为两层,VCL(视频编码层)和 NAL(网络提取层)。

为了方便从字节流中提取出 NALU,协议规定,在每个 NALU 的前面加上起始码(StartCode): 0x000001 或 0x00000001。

流媒体服务-传输延时(SEI插帧)_第3张图片

  • NALU 组成结构

NALU(NAL Unit)= 一组对应于视频编码的 NALU 头部信息(NAL header)+ 一个 RBSP(Raw Byte Sequence Payload,原始字节序列负荷)

流媒体服务-传输延时(SEI插帧)_第4张图片

NAL Unit Type 常⻅类型如下:

NAL Unit Type NAL Unit Content
1 非 IDR 图像,且不采用数据划分的片段。
5 IDR 图像。
6 补充增强信息(SEI)。
7 序列参数集(SPS)。
8 图像参数集(PPS)。
11 流结束符。

那么NAL Unit Type6时,即为SEI帧。

SEI payload type 计算方式

当开始解析类型为 SEI 的 NAL 时,在 RBSP 中持续读取 8 bit,直到非 0xff 为止,然后把读取的数值累加,累加值即为 SEI payload type。

SEI RBSP 结构图如下:

流媒体服务-传输延时(SEI插帧)_第5张图片

SEI payload size 计算方式

读取 SEI payload size 的逻辑与 SEI payload type 类似,即读取到非 0xff 为止,这样可以支持任意⻓度的 SEI payload 添加。假设 SEI payload type 后面的字符序列是 FF FF AA BB …,则 FF FF AA 将会解析成 SEI payload size,为 255 + 255 + 170 = 680。

实例代码

// @brief: 将时间戳写入sei frame,将sei frame写入文件
#include 
#include 
#include 
#include 
#include 
#include 

std::vector<uint8_t> MakeSei(const std::string& data)
{
    // 使用1个字节存储payload
    assert(data.size() + 16 < 255);

    std::vector<uint8_t> seiFrame;
    std::vector<uint8_t> uuid(16, 0x41);
    uint8_t              payloadSize = 16 + data.size();
    seiFrame.insert(seiFrame.end(), {0x00, 0x00, 0x00, 0x01}); // start code
    seiFrame.insert(seiFrame.end(), {0x06});                   // nalu type
    seiFrame.insert(seiFrame.end(), {0x05});                   // sei unregister data type
    seiFrame.push_back(payloadSize);                           // sei payload size
    seiFrame.insert(seiFrame.end(), uuid.begin(), uuid.end()); // uuid,这里可以替换成你自己的
    for (auto& ch : data)
        seiFrame.push_back(ch); // custom message
    seiFrame.push_back(0x80);   // rbsp trailing bits

    return seiFrame;
}

int main()
{
    std::ofstream file("sei.h264", std::ios::binary);
    std::string   data =
        "ts:" + std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
    auto sei = MakeSei(data);
    file.write(reinterpret_cast<char*>(sei.data()), sei.size());
    file.close();
    return 0;
}

这里在SEI中写入的数据格式为ts:{timestamp},你也可以定义为你希望的数据格式,如json,注意不要超过255 - 16个字节。

生成的帧信息如下:

流媒体服务-传输延时(SEI插帧)_第6张图片

推流端和拉流端如何进行时钟对齐

在拉流端拿到SEI frame之后,解码出推流端时间戳,计算delay = {拉流端时间戳} - {推流端时间戳}

这里存在一个问题是,拉流端系统时钟可能与推流端系统时钟不一致(如人为调整过系统时间),导致延时计算不准确,甚至是拉流端时间戳早于推流端时间戳。那么这时候就需要将推流端和拉流端的时间戳进行对齐

一般选择流媒体服务器时间戳进行对齐。

计算方法如下:

流媒体服务-传输延时(SEI插帧)_第7张图片

  • 参考文章

https://github.com/ZLMediaKit/ZLMediaKit/wiki/%E6%80%8E%E4%B9%88%E6%B5%8B%E8%AF%95ZLMediaKit%E7%9A%84%E5%BB%B6%E6%97%B6%EF%BC%9F

https://doc-zh.zego.im/faq/sei?product=ExpressAudio&platform=macos

你可能感兴趣的:(音视频)