1. SEI的简介
什么是SEI
SEI(Supplemental Enhancement Information),是定义在视频码流里面,提供在视频码流中添加信息的方法。在当前的流媒体中,可以用来添加一些视频无关的信息,例如歌词等,来做到同步显示。SEI可以在编码时候进行添加,也可以在传输时候进行添加。
NAL unit
在继续介绍SEI之前,我们先来了解一下NAL unit这个概念。在H.264/H.265的标准中,都使用了视频编码层(VCL)和网络适配层(NAL)的双层架构。VCL就是实际有效的视频数据,而NAL就是给出了一个标准化的方式保证数据的传输,NAL unit就是NAL的基本语法。以H.264为例,原始码流就是由一个一个的NALU组成的,其中每个NALU是由NAL header和来自VCL的原始数据字节流(RBSP)组成,如下图所示:
每个NALU之间通过起始码进行分隔,起始码分成两种:0x000001(3Byte)或者0x00000001(4Byte)。只要找到0x000001或者0x00000001即可认为是一个NALU的起始。下面我们就分H.264和H.265来给大家介绍一下。
H.264中的SEI
NAL unit type储存在NAL header中,在H.264标准中,有以下的一些定义:
从上图中,我们可以看到nal_unit_type为6即为我们今天的主题SEI。那么当我们拿到一个视频序列时候,该如何去找到SEI呢,我们接着看标准中是如何读取的。
我们找到标准中对于此部分的定义,仅看header部分,我们可以从上图中知道:头部由一个字节组成,其中分为三个部分。第一部分为forbidden_zero_bit,占1bit,0为正确;第二部分为nal_ref_idc,占2bit,表示当前NAL的优先级;第三部分为nal_unit_type,占5bit。所以我们取到NALU的第一个字节后 & 0x1f 就可得到nal_unit_type。即 int type = (code & 0x1F)
H.265中的SEI
同理,我们通过阅读标准可知:
nal_unit_type 39和40代表的为SEI。根据下图我们可以知道,nal_unit_type为6bit,所以我们将第一个字节 & 0x7e 后右移一位即可得到nal_unit_type。即int type = (code & 0x7E)>>1
SEI详情
SEI解析
通过上述说明,我们知道了如何去读取到SEI,那我们现在来讲解一下SEI的一些解析规则:
如图所示,当我们读到NAL unit type为SEI的时候,我们就开始先读取SEI payloadType。我们一次读取8bit,只要是非0xFF,那么这8bit的值即为SEI payloadType;如果是0xFF,那么我们SEI payloadType + 255继续去读,直到有8bit不是0xFF为止。同理,SEI payloadSize也是相同的读法。H.264和H.265对于SEI payloadType和payloadSize的定义都是相同的,全都是浮动的值,给予了我们足够的灵活性。
SEI类型
标准中定义的一种常见的SEI Type就是5,对于type为5的指定处理方法为user_data_unregistered(),常用于存储编码器的编码参数信息等。
user_data_unregistered( payloadSize ) {
uuid_iso_iec_11578
for( i = 16; i < payloadSize; i++ )
user_data_payload_byte
}
通过上面的代码我们可以知道,我们需要传入16个字节的UUID,剩下的自定义数据部分的大小就为payloadSize-16,所以payloadSize一定大于16。上面我们也提到了SEI type是一个可以浮动的值,所以在标准定义之外,常见的自定义的SEI类型还有:
typedef enum
{
SEI_H264_RECOVERY_POINT = 6,
// it is recommended to use range between 70 - 100
SEI_H264_SLICE_INFO = 98,
SEI_H264_REF_IDX = 99,
SEI_H264_LAYOUT = 100,
} SEI_H264_Type;
除此之外,还有一些可以利用SEI传递信息的场景,例如:
- 传递编码器参数
- 传递视频版权信息
- 传递摄像头参数
- 传递内容生成过程中的剪辑事件(引发场景切换)
防竞争处理
为了防止字节竞争,我们遇到0x000000/0x000001/0x000002的时候,需要插入0x03防竞争处理,即变为0x00000300/0x00000301/0x00000302。所以需要我们客户的解码端能够在解码时去掉0x03这个字节。
例子
上图是一个H.264序列,我们可以看到蓝色标注区域就是一个SEI,0x64即为SEI type,是自定义的type 100;后面0x0d即为payloadSize,也就是后面13字节为数据部分,我们可以看到是一个unix时间戳。
上图是一个H.265序列,我们可以看到蓝色标注区域就是一个SEI,4E 01即代表了nal_unit_header,0x64就是旁路推流定义的type 100,后续解析同264。
上图即为一个H.264的SEI type为5的一个示例,按照上面的解析规则,0x05代表了type为5,0x18代表payloadSize为24,减去16位UUID,即用户数据部分为00 00 03 01 73 E1 45 7C 78,中间多出一个03即为防竞争位,将03去掉我们整体解析会发现这是一个unix时间戳:1597212294264。
2. SEI的应用场景
基于SEI的阿里云直播答题架构
直播问答的一核心需求是“画题同步”,这也是决定用户体验的关键。阿里云解决方案:
- 主持人提出问题,此时准备推送题目。为了能快速让用户看到题目,题库都存在持久型缓存数据库Redis上。
- 现场人员发出信息,通过接入方的AppServer,调用云的OpenAPI,在直播视频流当前位置中插入若干SEI帧,帧内容可由业务自定义。
- 播放SDK接收到视频流后,解析出SEI帧,并回调给APP。此时APP立即向AppServer请求问题信息,然后显示在APP上,完成整个出题过程。
- 收到用户答题后,用户答题结果实时写入Redis进行判断答题是否正确返回给现场人员。完成整个答题流程。
以上方案环环相扣,实现了从主持人信号与音视频通过同一传输通道同时传输,可实现高精度同步。
声网 SEI 规范
在默认情况下,声网进行服务端转码推流时,会在转码后的 H264/H265 的 SEI 帧中,增加当前视频的编码信息。该信息为 Json 格式的字符串,具体示例如下:
{
"canvas": {
"w": 640,
"h": 360,
"bgnd": "#000000"
},
"regions": [{
"uid": 1,
"alpha": 1.0,
"zorder": 1,
"volume": 50,
"x": 0,
"y": 0,
"w": 320,
"h": 360
}, {
"uid": 2,
"alpha": 1.0,
"zorder": 1,
"volume": 89,
"x": 320,
"y": 0,
"w": 320,
"h": 360
}],
"ver": "20180828",
"ts": 1535385600000,
"app_data": ""
}
各项参数定义如下:
- canvas:画布信息,画布的参数信息如下;
- w:画布的宽度,单位为像素。主播在 APP 设置的 LiveTranscoding 中的 width 信息;
- h:画布的高度,单位为像素。主播在 APP 设置的 LiveTranscoding 中的 height 信息;
- bgnd:画布的背景颜色,RGB 格式,为 16 进制代码表示的字符串。主播在 APP 设置的 LiveTranscoding 中的 backgroundColor 信息;
- regions:主播信息及主播布局信息,为 region 的列表。主播在 APP 设置的 LiveTranscoding 中的 transcodingUsers 信息。region 的参数信息如下;
- suid:(可选)该区域对应主播的 String 型 User account。该参数适用于启用了 String 型 User account 的主播;
- uid:该区域对应主播的 ID。主播在 APP 设置的 TranscodingUser 中的 uid 信息;
- alpha:该区域的透明度,取值范围 [0.0, 1.0]。主播在 APP 设置的 TranscodingUser 中的 alpha 信息;
- zorder:该区域的层级,取值范围 [1, 100]。主播在 APP 设置的 TranscodingUser 中的 zOrder 信息;
- volume:该区域对应主播的音量大小,取值范围 [0, 255];
- x:该区域在画布中对应的 x 坐标。主播在 APP 设置的 TranscodingUser 中的 x 信息;
- y:该区域在画布中对应的 y 坐标。主播在 APP 设置的 TranscodingUser 中的 y 信息;
- ver:版本信息,当前版本为 20190611;
- ts:生成该信息时的时间戳,单位 ms;
- app_data:自定义信息。主播在 APP 设置的 LiveTranscoding 中的 transcodingExtraInfo 信息;
ZEGO即构 SEI 应用场景
在音视频流媒体应用中,除了可以流媒体通道推拉音视频内容外,还可以使用流 SEI(Supplemental Enhancement Information,媒体补充增强信息)通过流媒体通道将文本信息与音视频内容打包在一起,从主播端(推流端)推出,并从观众端(拉流端)接收,以此实现文本数据与音视频内容的精准同步的目的。详见:媒体补充增强信息(SEI)
当开发者对消息发送有较高频率和实时性要求,且消息丢失不会影响业务逻辑时,推荐使用 SEI(Supplemental Enhancement Information,媒体补充增强信息)。主要应用于如下场景:
- 单向发送大并发 IM 的场景
- 需要文本信息跟媒体流实时同步的场景,例如:
- 直播答题
- 歌词同步
- 单流自定义音浪
- 混流视频画面布局更换的精准控制
3. ffmpeg插入SEI实践
下面介绍讲述如何修改ffmpeg源码,实现在每个关键帧中插入SEI。
在使用ffmpeg命令行工具时,可以使用 h264_metadata bitstream filter添加SEI。下面示例命令就添加了类型为未注册的用户数据的SEI,其中uuid为”086f3693-b7b3-4f2c-9653-21492feee5b8”,payload内容为”hello”:
./ffmpeg -I oceans.h264 -c:v copy -bsf:v h264_metadata=sei_user_data='086f3693-b7b3-4f2c-9653-21492feee5b8+hello' oceans.sei.h264
所以为了实现我们的目标,我们可以修改h264_metadata_bsf.c。
查找关键帧
from
has_sps = 0;
for (i = 0; i < au->nb_units; i++) {
if (au->units[i].type == H264_NAL_SPS) {
err = h264_metadata_update_sps(bsf, au->units[i].content);
if (err < 0)
goto fail;
has_sps = 1;
}
}
to
int has_idr = 0;
has_sps = 0;
for (i = 0; i < au->nb_units; i++) {
if (au->units[i].type == H264_NAL_SPS) {
err = h264_metadata_update_sps(bsf, au->units[i].content);
if (err < 0)
goto fail;
has_sps = 1;
}
if (au->units[i].type == H264_NAL_IDR_SLICE) {
has_idr = 1;
}
}
判断是关键帧
from
// Only insert the SEI in access units containing SPSs, and also
// unconditionally in the first access unit we ever see.
if (ctx->sei_user_data && (has_sps || !ctx->done_first_au)) {
to
// Only insert the SEI in access units containing IDRs, and also
// unconditionally in the first access unit we ever see.
if (ctx->sei_user_data && (has_idr || !ctx->done_first_au)) {
在关键帧后插入一帧SEI
我们的设计是,当参数为-bsf:v h264_metadata=sei_user_data=’086f3693-b7b3-4f2c-9653-21492feee5b8+{timestamp}’
时插入当前时间戳。
解析出的SEI信息为”ts:timestamp”精确到毫秒。
from
udu->data = udu->data_ref->data;
udu->data_length = len + 1;
memcpy(udu->data, ctx->sei_user_data + i + 1, len + 1);
to
udu->data = udu->data_ref->data;
udu->data_length = len + 1;
memcpy(udu->data, ctx->sei_user_data + i + 1, len + 1);
//convert to Timestamp
if(strcmp(udu->data, "{timestamp}") == 0){
char mark[] = "ts:";
long timestamp = av_gettime() / 1000;
char result[20];
sprintf(result, "%s%ld", mark, timestamp);
udu->data = result;
udu->data_length = strlen(result) + 1;
}
使用方法
最后的使用方法如下:
-bsf:v h264_metadata=sei_user_data='086f3693-b7b3-4f2c-9653-21492feee5b8+{timestamp}'
栗子:
-stream_loop -1 -i video.mp4 -c:a aac -c:v libx264 -bsf:v h264_metadata=sei_user_data='086f3693-b7b3-4f2c-9653-21492feee5b8+{timestamp}' -f flv rtmp://demo-push.cn/live/11
4. SEI在测试中的使用
SEI随着视频帧一起到达,可以让我们更加精准的测试视频延时,下面就来分享一下是如何进行的。
整体结构如图:
上图中,老师端和学生端为同一台机器,可以消除时间误差,这样就可以很好的估算出端到端延时。同时我们可以将CDN和Cloud Player部署在同一台机器,可以通过diff来算出各环节耗时,为我们的延时优化提供数据指导。
通过与传统的秒表截图估算误差作比较,使用SEI来观测误差有以下几个好处:
- 数据更加精准更加可信
- 可以在系统的各个流程/环节进行监测,为我们的延时优化提供数据指导
- 可以进行一个长时间多采样的数据对比
refs:
- https://rtcdeveloper.agora.io/t/topic/18970
- https://wangtaot.github.io/2020/11/09/ffmpeg%E6%8F%92%E5%85%A5sei%E5%AE%9E%E8%B7%B5/
- https://blog.csdn.net/feeltouch/article/details/103333174
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。