本文是“FFmpeg从入门到精通”系列的第三篇,由金山云供稿,并授权LiveVideoStack发布。此前两篇为FFmpeg代码导读——基础篇和FFmpeg代码导读——HEVC在RTMP中的扩展。FFmpeg广泛应用与音视频领域,被誉为音视频开发的“瑞士军刀”。“FFmpeg从入门到精通”系列将由浅入深,解读FFmpeg的基础功能与使用技巧。
流媒体是采用流式传输方式在网络上播放的媒体格式,视频网站内容、短视频、在线直播这些视频形态,均属于流媒体的不同分支。流媒体大致包含三个层级:码流、封装和协议。从音视频编码器输出的码流,经过某种封装格式后,经过特定的协议传输、保存,构成了流媒体世界的基础功能。
在直播应用的开发过程中,如果把主播端消息事件传递到观众端,一般会以Instant Messaging(即时通讯)的方式传递过去,但因为消息分发通道和直播通道是分开的,因此消息与直播音视频数据的同步性就会出现很多问题。那么有没有在音视频内部传递消息的方法呢?答案是SEI。金山云目前推出的直播问答解决方案中,就用到了SEI,作为一名视频云架构资深开发工程师,今天就来和大家分享一下SEI的技术细节。
SEI即补充增强信息(Supplemental Enhancement Information),属于码流范畴,它提供了向视频码流中加入额外信息的方法,是H.264/H.265这些视频压缩标准的特性之一。SEI的基本特征如下:
并非解码过程的必须选项
可能对解码过程(容错、纠错)有帮助
集成在视频码流中
也就是说,视频编码器在输出视频码流的时候,可以不提供SEI信息。虽然在视频的传输过程、解封装、解码这些环节,都可能因为某种原因丢弃SEI内容,但在视频内容的生成端和传输过程中,是可以插入SEI信息的。这些插入的信息,和其他视频内容一同经过传输链路到达消费端。举例来说,当前火爆的直播问答模式,就是通过SEI传递较多和答题业务相关的信息,通过SEI承载的信息,极大地优化了题目显示和观众音视频观看的同步性。
那么在SEI中可以添加哪些信息呢?以下是一些用户场景可任意扩展的例子:
传递编码器参数
传递视频版权信息
传递摄像头参数
传递内容生成过程中的剪辑事件(引发场景切换)
对于SEI如何应用,我们先以H.264/AVC这一视频编码标准为例。在这一标准中,整个系统框架分为两层:视频编码层面(Video Coding Layer,简称VCL)和网络抽象层面(Network Abstraction Layer,简称NAL)。VCL负责表示有效视频数据的内容,NAL负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。NAL unit是NAL的基本语法结构,它包含一个字节的头信息(NAL header)和一系列来自VCL的原始数据字节流(RBSP)。
H.264/AVC中的情况
NAL unit type储存在NAL header中,在H.264/AVC标准中,可用的NAL unit type一共有17种,作用是告诉解码器,承载的数据是视频关键帧,还是视频解码器的配置参数信息。其中值为6时表征SEI内容。比较常见的类型如下表所示:
NAL unit type NAL unit content
H.264/AVC中完整的NAL unit类型定义请参考《ISO/IEC 14496-10:2014》,这是MPEG专家组为AVC编解码器制定的标准,H.264/AVC中NAL unit类型完整定义都在该标准的7-1表中,标准一共预留了32种类型,在NAL header里面,用5 bits表征NAL unit type。
H.264/AVC中的NAL unit type
如上图所示,在8 bits的NAL header中:
第0位是禁止位0,值为1时表示语法出错
第1~2位是参考级别(NRI,NAL ref idc)
第3~7位是NAL unit type
需要注意的是,当NRI取值为"00"(二进制)时,表征NAL unit不参与重建参考图像,这时的NAL unit是可以丢弃的。大于"00"(二进制)时,NAL unit不能被丢弃。
H.265/HEVC中的情况
《ISO/IEC 23008-2:2015》是MPEG专家组为HEVC编解码器制定的标准,H.265/HEVC中NAL unit类型完整定义都在该标准的7-1表中,可用的NAL unit type一共有40种之多,其中39和40都表征SEI内容。因为标准一共预留64种类型,所以在NAL header里面,用6 bits表征NAL unit type。
H.265/HEVC中的NAL unit type
如上图所示,在16 bits的NAL header中:
第0位是禁止位0,值为1时表示语法出错
第1~6位是NAL unit type
第7~12位是NUH layer id
第13~15位是temporal_id
SEI 类型
在H.264/AVC视频编码标准中,并没有规定SEI payload type的范围,所以表征payload type的字节数是浮动的。
语法分析如下所示,当开始解析类型为SEI的NAL时,持续读取8bit,直到非0xff为止,然后把读取的数值累加,累加值即为SEI payload type。
sei_message(){undefined
payloadType = 0
while( next_bits(8) == 0xFF){undefined
ff_byte
payloadType += 255
}
last_payload_type_byte
payloadType += last_payload_type_byte
}//代码效果参考:http://www.ezhiqi.com/zx/art_2236.html
读取SEI payload size和payload type逻辑类似,仍然是读取到0xff为止,这样可以支持任意长度的SEI payload添加。
sei_message(){undefined
payloadSize = 0
while( next_bits(8) == 0xFF){undefined
ff_byte
payloadSize += 255
}//代码效果参考:http://www.ezhiqi.com/bx/art_123.html
last_payload_size_byte
payloadSize += last_payload_size_byte
}
当获取了SEI payload类型和大小后,就进入了实际的SEI内容读取。
当前《ISO/IEC 14496-10:2014》Annex D.1.1提供了最大到181的payload类型处理规范,由于类型可以指定任意大小,给SEI的添加、处理创造了很大的自由空间。
其中SEI payload类型值为5时,指定的处理方法叫user_data_unregistered(),字面含义为未注册的用户数据,常用于存储编码器的编码参数信息,是比较常见的payload类型。
读取payload type为5时,具体的语法解析流程如下:
function DisplayWindowSize(){undefined
var w=window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;
}//代码效果参考:http://www.ezhiqi.com/bx/art_3703.html
其中uuid_iso_iec_11578的详细定义在《ISO/IEC 11578:1996》 Annex A中,大致规定了使用128 bits(16个字节)来指定UUID。此处UUID可以表征写入SEI payload的角色ID,或者表征其他业务用途。剩下的payloadSize -16字节,即是业务层传递的具体内容了。
通过user_data_unregistered()语法解析可以看出,当使用SEI payload type为5时,注意事项如下:
payload size应该大于16;
uuid可能出现0x000000/0x000001/0x000002,需要插入0x03做防竞争处理;
构成RBSP时,都需要做RBSP拖尾处理。拖尾处理对所有SODB方式都一致。rbsp_trailing_bits()语法逻辑如下:
rbsp_trailing_bits( ){undefined
rbsp_stop_one_bit
while( !byte_aligned( ) )
rbsp_alignment_zero_bit
}
SEI例子
从video.js的示例中下载oceans.mp4并提取出H.264码流如下:
bitstream from oceans.mp4
NAL header
起始码(暗红底色)"0x00000001"分割出来的比特流即是NAL unit,起始码紧跟的第一个字节(墨绿底色)是NAL header。上图“NAL header”一共出现了四个数值:
"0x06",此时NRI为"00B",NAL unit type为SEI类型。
“0x67”,此时NRI为“11B”,NAL unit type为SPS类型。
“0x68”,此时NRI为“11B”,NAL unit type为PPS类型。
“0x65”,此时NRI为“11B”,NAL unit type为IDR图像。
SEI payload type
"0x06"后一个字节为“0x05”(淡黄底色)是SEI payload type,即表征SEI payload分析遵循user_data_unregistered()语法。
SEI payload size
“0x05”后一个字节为“0x2F”(淡蓝底色)是SEI payload size,此时整个payload是47个字节。
SEI payload uuid
"0x2F"随后的16个字节即为uuid,此时uuid为
<p style="margin-left: 16