SEI(Supplemental Enhancement Information),是定义在视频码流里面,提供在视频码流中添加信息的方法。在当前的流媒体中,可以用来添加一些视频无关的信息,例如歌词等,来做到同步显示。SEI可以在编码时候进行添加,也可以在传输时候进行添加。
在继续介绍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来给大家介绍一下。
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)
同理,我们通过阅读标准可知:
nal_unit_type 39和40代表的为SEI。根据下图我们可以知道,nal_unit_type为6bit,所以我们将第一个字节 & 0x7e 后右移一位即可得到nal_unit_type。即int type = (code & 0x7E)>>1
通过上述说明,我们知道了如何去读取到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 Type就是5,对于type为5的指定处理方法为user_data_unregistered(),常用于存储编码器的编码参数信息等。
通过上面的代码我们可以知道,我们需要传入16个字节的UUID,剩下的自定义数据部分的大小就为payloadSize-16,所以payloadSize一定大于16。上面我们也提到了SEI type是一个可以浮动的值,所以在标准定义之外,常见的自定义的SEI类型还有:
除此之外,还有一些可以利用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。
直播问答的一核心需求是“画题同步”,这也是决定用户体验的关键。阿里云解决方案:
- 主持人提出问题,此时准备推送题目。为了能快速让用户看到题目,题库都存在持久型缓存数据库Redis上。
- 现场人员发出信息,通过接入方的AppServer,调用云的OpenAPI,在直播视频流当前位置中插入若干SEI帧,帧内容可由业务自定义。
- 播放SDK接收到视频流后,解析出SEI帧,并回调给APP。此时APP立即向AppServer请求问题信息,然后显示在APP上,完成整个出题过程。
- 收到用户答题后,用户答题结果实时写入Redis进行判断答题是否正确返回给现场人员。完成整个答题流程。
以上方案环环相扣,实现了从主持人信号与音视频通过同一传输通道同时传输,可实现高精度同步。
在默认情况下,声网进行服务端转码推流时,会在转码后的 H264/H265 的 SEI 帧中,增加当前视频的编码信息。该信息为 Json 格式的字符串,具体示例如下:
各项参数定义如下:
- 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 信息;
在音视频流媒体应用中,除了可以流媒体通道推拉音视频内容外,还可以使用流 SEI(Supplemental Enhancement Information,媒体补充增强信息)通过流媒体通道将文本信息与音视频内容打包在一起,从主播端(推流端)推出,并从观众端(拉流端)接收,以此实现文本数据与音视频内容的精准同步的目的。详见:媒体补充增强信息(SEI)
当开发者对消息发送有较高频率和实时性要求,且消息丢失不会影响业务逻辑时,推荐使用 SEI(Supplemental Enhancement Information,媒体补充增强信息)。主要应用于如下场景:
- 单向发送大并发 IM 的场景
- 需要文本信息跟媒体流实时同步的场景,例如:
- 直播答题
- 歌词同步
- 单流自定义音浪
- 混流视频画面布局更换的精准控制
下面介绍讲述如何修改ffmpeg源码,实现在每个关键帧中插入SEI。
在使用ffmpeg命令行工具时,可以使用 h264_metadata bitstream filter添加SEI。下面示例命令就添加了类型为未注册的用户数据的SEI,其中uuid为”086f3693-b7b3-4f2c-9653-21492feee5b8”,payload内容为”hello”:
所以为了实现我们的目标,我们可以修改h264_metadata_bsf.c。
from
to
from
to
我们的设计是,当参数为时插入当前时间戳。
解析出的SEI信息为”ts:timestamp”精确到毫秒。
from
to
最后的使用方法如下:
栗子:
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