FFmpeg从入门到精通——进阶篇,SEI那些事儿
前言
在直播应用的开发过程中,如果把主播端消息事件传递到观众端,一般会以Instant Messaging(即时通讯)的方式传递过去,但因为消息分发通道和直播通道是分开的,因此消息与直播音视频数据的同步性就会出现很多问题。那么有没有在音视频内部传递消息的方法呢?答案是SEI。
金山云目前推出的直播问答解决方案中,就用到了SEI,阿曾作为一名视频云架构资深开发工程师,对于与视频相关的技术有着深刻的实践经验,今天给大家分享一下关于SEI的技术细节。
流媒体是采用流式传输方式在网络上播放的媒体格式,视频网站内容、短视频、在线直播这些视频形态,均属于流媒体的不同分支。流媒体大致包含三个层级:码流、封装和协议。从音视频编码器输出的码流,经过某种封装格式后,经过特定的协议传输、保存,构成了流媒体世界的基础功能。
SEI即补充增强信息(Supplemental Enhancement Information),属于码流范畴,它提供了向视频码流中加入额外信息的方法,是H.264/H.265这些视频压缩标准的特性之一。SEI的基本特征如下:
1. 并非解码过程的必须选项
2. 可能对解码过程(容错、纠错)有帮助
3. 集成在视频码流中
也就是说,视频编码器在输出视频码流的时候,可以不提供SEI信息。虽然在视频的传输过程、解封装、解码这些环节,都可能因为某种原因丢弃SEI内容,但在视频内容的生成端和传输过程中,是可以插入SEI信息的。这些插入的信息,和其他视频内容一同经过传输链路到达消费端。举例来说,当前火爆的直播问答模式,就是通过SEI传递较多和答题业务相关的信息,通过SEI承载的信息,极大地优化了题目显示和观众音视频观看的同步性。
那么在SEI中可以添加哪些信息呢?以下是一些用户场景可任意扩展的例子:
1. 传递编码器参数
2. 传递视频版权信息
3. 传递摄像头参数
4. 传递内容生成过程中的剪辑事件(引发场景切换)
对于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内容。比较常见的类型如下表所示:
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中:
1. 第0位是禁止位0,值为1时表示语法出错
2. 第1~2位是参考级别(NRI,NAL ref idc)
3. 第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中:
1. 第0位是禁止位0,值为1时表示语法出错
2. 第1~6位是NAL unit type
3. 第7~12位是NUH layer id
4. 第13~15位是temporal_id
SEI 类型
在H.264/AVC视频编码标准中,并没有规定SEI payload type的范围,所以表征payload type的字节数是浮动的。
语法分析如下所示,当开始解析类型为SEI的NAL时,持续读取8bit,直到非0xff为止,然后把读取的数值累加,累加值即为SEI payload type。
读取SEI payload size和payload type逻辑类似,仍然是读取到0xff为止,这样可以支持任意长度的SEI payload添加。
当获取了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时,具体的语法解析流程如下:
其中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时,注意事项如下:
1. payload size应该大于16;
2. uuid可能出现0x000000/0x000001/0x000002,需要插入0x03做防竞争处理;
构成RBSP时,都需要做RBSP拖尾处理。拖尾处理对所有SODB方式都一致。rbsp_trailing_bits()语法逻辑如下:
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为
SEI payload content
由于payload size是47个字节,除去16字节的uuid,剩下31个字节的content。由于content是字符串,所以有结束符"0x00",有效的30个字符内容是:
rbsp trailing bits
47个payload字节后的"0x80"(灰底色)即是rbsp trailing bits,在user_data_unregistered()里面都是按字节写入的,所以此时的NAL unit结尾写入的字节一定是0x80。
SEI的生成
生成SEI的方式很多,大致可以有:
1.对已有码流做filter,插入SEI NAL
2.视频编码时生成SEI
3.容器层写入时插入SEI
以下代码示例来自于FFmpeg origin/master 分支。
bsf
BitStream Filter(码流过滤)的缩写为bsf,它的作用是,在不做码流解码的前提下,对已经编码后的比特流做特定的修改、调整。
bsf h264_metadata的调用
使用ffmpeg工具时,可以使用比特流过滤器。基本的filter调用格式如下:
从上文提到的mp4文件中提取出h.264码流oceans.h264,可以使用 h264_metadata比特流过滤器添加SEI。下面示例命令添加了类型为未注册的用户数据的SEI,其中uuid为"086f3693-b7b3-4f2c-9653-21492feee5b8",payload内容为"hello":
其中oceans.h264已经有一个SEI和28个SPS。输出的oceans.sei.h264码流中,共有28个SEI,其中第一个与输入保持一致,剩下27个为新插入的SEI。
bsf h264_metadata的代码分析
具体代码位于:libavcodec/h264_metadata_bsf.c中。
以上代码是h264_metadata添加SEI的判断逻辑,当指定了sei_user_data时,满足以下条件之一即可以处理:
·读取的access units是第一个au;
·当前au包含sps;
满足插入SEI逻辑后,具体处理过程中:
·如果发现第一个NAL已经是SEI,则该au不做插入SEI处理;
·如果au包含了IDR帧或者非IDR未分区的帧,则在其前面插入SEI信息。
基于以上代码,oceans.sei.h264码流中新插入27个新的SEI 符合处理逻辑。
具体构造SEI NAL Unit代码如下:
代码完整解释了上文提到的SEI规范,其中"H264_SEI_TYPE_USER_DATA_UNREGISTERED"值为5,对应的即是未注册的用户信息。在解析"ffmpeg"工具输入过程中,将"+"号前面的字符串转换成二进制写入uuid,"+"后内容使用字符串写入payload。
x264
libx264支持多种SEI类型数据写入,常用的仍然是SEI_USER_DATA_UNREGISTERED,具体的写入函数x264_sei_version_write()位于libx264/encoder/set.c中。
libx264提供的uuid和上文举例的uuid一致,payload中主要记录了相关参数和版权信息。以上函数完成了SEI参数的构造,下面的函数x264_sei_write完成了具体语法的写入:
以上写入的代码逻辑和标准语法说明保持一致。
解析SEI
FFmpeg在读取和解码NAL unit,都有相同的逻辑处理SEI。
读取或者解码数据时,会调用下面函数进行码流的解码,其中buf包含具体的二进制流,buf_size是当前码流长度。函数内部会解析码流并实例出具体的NAL对象:
如果NAL对象类型是SEI 时,将调用以下函数解码:
可以看到,根据SEI语法标准,在解析了SEI payload type和length后,对未注册用户数据的提取,跳过了uuid的分析,只尝试提取了x264的build信息。总体上,并未利用SEI_USER_DATA_UNREGISTERED传递过来的其他相关参数信息。
从解码器逻辑看,H264SEIUnregistered结构体只有一个x264_build属性,并未返回实质有效数据。上层业务如果需要提取SEI_USER_DATA_UNREGISTERED,仍然需要自己提取。提取逻辑,请参考下一小节(ffplay)。
ffplay
ffplay是一个简单、常用的FFmpeg接口示例工具,常用于测试解码、播放效果。如果在ffplay中示例跑通SEI提取功能,可以很方便的移植到其他平台。
在ffplay中通过函数av_read_frame(ic, pkt)返回后,读取pkt->data可以快速拿到当前读到的NAL unit。从data数据中取出NAL unit type,如果是SEI且是用户未注册数据类型(payload type值为5),则可以参考SEI语法继续读取UUID和其后传递的字符串。
本文主要对H.264码流中涉及用户未注册数据的SEI进行了分析。总体而言,SEI只是视频标准里面很小的一部分,但在应用过程中,比如直播问答项目中SEI承载的信息,就极大提升了直播观看和答题操作的整体用户体验。所以说,从SEI的例子中,我们就会发现,视频标准里面还有很多金矿等待着大家的挖掘,这就是多媒体技术的魅力,也是金山视频云努力的方向。
原文地址:https://mp.weixin.qq.com/s/BrSOPtDIOj5Lv1h1MLKCSA
FFmpeg从入门到精通——进阶篇,SEI那些事儿的更多相关文章
- Jmeter(三十四) - 从入门到精通进阶篇 - 参数化(详解教程)
1.简介 前边三十多篇文章主要介绍的是Jmeter的一些操作和基础知识,算是一些初级入门的知识点,从这一篇开始我们就来学习Jmeter比较高级的操作和深入的知识点了.今天这一篇主要是讲参数化,其实前边 ...
- Jmeter(三十五) - 从入门到精通进阶篇 - 关联(详解教程)
1.简介 上一篇中介绍了如果想要同时发送多条请求,那么怎样才能让每条数据某些请求参数改变呢.这就用到了jMeter参数化.在实际测试场景中,我们往往还有这样的需求,登录后服务器响应的token作为下次 ...
- Jmeter(三十八) - 从入门到精通进阶篇 - 命令行运行JMeter详解(详解教程)
1.简介 前边一篇文章介绍了如何生成测试报告,细心地小伙伴或者同学们可以看到宏哥启动Jmeter生成测试报告不是在gui页面操作的,而是在gui页面设置好保存以后,用命令行来生成测试报告的.这一篇宏哥 ...
- Jmeter(四十) - 从入门到精通进阶篇 - Jmeter配置文件的刨根问底 - 中篇(详解教程)
1.简介 为什么宏哥要对Jmeter的配置文件进行一下讲解了,因为有的童鞋或者小伙伴在测试中遇到一些需要修改配置文件的问题不是很清楚也不是很懂,就算修改了也是模模糊糊的.更有甚者觉得那是禁地神圣不可轻 ...
- Jmeter(四十一) - 从入门到精通进阶篇 - Jmeter配置文件的刨根问底 - 下篇(详解教程)
1.简介 为什么宏哥要对Jmeter的配置文件进行一下讲解了,因为有的童鞋或者小伙伴在测试中遇到一些需要修改配置文件的问题不是很清楚也不是很懂,就算修改了也是模模糊糊的.更有甚者觉得那是禁地神圣不可轻 ...
- Jmeter(四十二) - 从入门到精通进阶篇 - Jmeter配置文件的刨根问底 -番外篇(详解教程)
1.简介 为什么宏哥要对Jmeter的配置文件进行一下讲解了,因为有的童鞋或者小伙伴在测试中遇到一些需要修改配置文件的问题不是很清楚也不是很懂,就算修改了也是模模糊糊的.更有甚者觉得那是禁地神圣不可轻 ...
- Jmeter(三十六) - 从入门到精通进阶篇 - 设置负载阶梯式压测场景(详解教程)
1.简介 在性能测试中,有时需要模拟一种实际生产中经常出现的情况,即:从某个值开始不断增加压力,直至达到某个值,然后持续运行一段时间,然后继续加压达到某个值持续运行,如此循环直到达到预期的峰值,运行一 ...
- Jmeter(三十七) - 从入门到精通进阶篇 - 输出HTML格式的性能测试报告(详解教程)
1.简介 相对于Loadrunner,Jmeter其实也是可以有测试报告产出的,虽然一般都不用(没有Loadrunner的报告那么强大是一方面),但是有小伙伴们私下问,那宏哥还是顺手写一下吧,今天我们 ...
- Java入门到精通——基础篇之多线程实现简单的PV操作的进程同步
Java入门到精通——基础篇之多线程实现简单的PV操作的进程同步 一.概述 PV操作是对信号量进行的操作. 进程同步是指在并发进程之间存在一种制约关系,一个进程的执行依赖另一个进程的消 ...
随机推荐
- Android 解决手机unauthorized错误
转自:http://blog.csdn.net/quicksand201/article/details/19190821 手机开发者选项中USB调试已经打开,在电脑命令行下输入adb devices ...
- 微软发布新版 Skype Linux 客户端
导读 前两天,微软说要给Linux 用户带来一个令人兴奋的新闻,今天,这个新闻来了.它刚刚为 Linux 发布了一个新的 Skype 客户端.此次发布,微软为 Linux 带来的 Skype 客户端与 ...
- A Great List of Windows Tools
Windows is an extremely effective and a an efficient operating system. Like any other operating syst ...
- 五款超实用的开源 SVG 工具
英文链接:Idrsolutions SVG(Scalable Vector Graphics)是基于 XML 的矢量图像格式,用户可灵活运用图像进行搜索.索引.脚本以及压缩(来自:湖北教育考试网).S ...
- Linux下查看nginx的安装路径
输入:nginx -V 输出:configure arguments: --prefix=/usr/local/nginx
- Maven的安装配置及初次创建项目与java单元测试工具JUnit
Maven 安装 1.把maven安装包解压到某个位置 2.配置M2_HOME环境变量指向这个位置 3.在path环境变量中添加;%M2_HOME%\bin 配置镜像 国内的阿里云镜 ...
- JavaMath方法、服务器与Tomcat安装与配置步骤
一.Math Math.PI 记录的圆周率 Math.E 记录e的常量 Math中还有一些类似的常量,都是一些工程数学常用量. Math.abs 求绝对值 Math.sin 正弦函数 Math. ...
- Oracle Java Mission Control MBean 服务器 飞行记录器
Oracle Java Mission ControlMBean 服务器飞行记录器
- opencv 角点检测+相机标定+去畸变+重投影误差计算
https://blog.csdn.net/u010128736/article/details/52875137 https://blog.csdn.net/h532600610/article/d ...
- Linux命令(基础1)
一 命令的基本构成 (PS:Linux发行版本命令大概有200多个,熟练掌握个百八的就行了,其余的有个大概了解) 命令体 选项 参数(对象) ls -l /var 1.1参数:文件 文件类型: d ...