FFmpeg从入门到精通——进阶篇,SEI那些事儿
作者:阿曾
前言在直播应用的开发过程中 , 如果把主播端消息事件传递到观众端 , 一般会以InstantMessaging(即时通讯)的方式传递过去 , 但因为消息分发通道和直播通道是分开的 , 因此消息与直播音视频数据的同步性就会出现很多问题 。 那么有没有在音视频内部传递消息的方法呢?答案是SEI 。
金山云目前推出的直播问答解决方案中 , 就用到了SEI , 阿曾作为一名视频云架构资深开发工程师 , 对于与视频相关的技术有着深刻的实践经验 , 今天给大家分享一下关于SEI的技术细节 。
流媒体是采用流式传输方式在网络上播放的媒体格式 , 视频网站内容、短视频、在线直播这些视频形态 , 均属于流媒体的不同分支 。 流媒体大致包含三个层级:码流、封装和协议 。 从音视频编码器输出的码流 , 经过某种封装格式后 , 经过特定的协议传输、保存 , 构成了流媒体世界的基础功能 。
SEI即补充增强信息(SupplementalEnhancementInformation) , 属于码流范畴 , 它提供了向视频码流中加入额外信息的方法 , 是H.264/H.265这些视频压缩标准的特性之一 。 SEI的基本特征如下:
1.并非解码过程的必须选项
2.可能对解码过程(容错、纠错)有帮助
3.集成在视频码流中
也就是说 , 视频编码器在输出视频码流的时候 , 可以不提供SEI信息 。 虽然在视频的传输过程、解封装、解码这些环节 , 都可能因为某种原因丢弃SEI内容 , 但在视频内容的生成端和传输过程中 , 是可以插入SEI信息的 。 这些插入的信息 , 和其他视频内容一同经过传输链路到达消费端 。 举例来说 , 当前火爆的直播问答模式 , 就是通过SEI传递较多和答题业务相关的信息 , 通过SEI承载的信息 , 极大地优化了题目显示和观众音视频观看的同步性 。
那么在SEI中可以添加哪些信息呢?以下是一些用户场景可任意扩展的例子:
1.传递编码器参数
2.传递视频版权信息
3.传递摄像头参数
4.传递内容生成过程中的剪辑事件(引发场景切换)
对于SEI如何应用 , 我们先以H.264/AVC这一视频编码标准为例 。 在这一标准中 , 整个系统框架分为两层:视频编码层面(VideoCodingLayer , 简称VCL)和网络抽象层面(NetworkAbstractionLayer , 简称NAL) 。 VCL负责表示有效视频数据的内容 , NAL负责格式化数据并提供头信息 , 以保证数据适合各种信道和存储介质上的传输 。 NALunit是NAL的基本语法结构 , 它包含一个字节的头信息(NALheader)和一系列来自VCL的原始数据字节流(RBSP) 。
H.264/AVC中的情况NALunittype储存在NALheader中 , 在H.264/AVC标准中 , 可用的NALunittype一共有17种 , 作用是告诉解码器 , 承载的数据是视频关键帧 , 还是视频解码器的配置参数信息 。 其中值为6时表征SEI内容 。 比较常见的类型如下表所示:
1.第0位是禁止位0 , 值为1时表示语法出错
2.第1~2位是参考级别(NRI , NALrefidc)
3.第3~7位是NALunittype
需要注意的是 , 当NRI取值为"00"(二进制)时 , 表征NALunit不参与重建参考图像 , 这时的NALunit是可以丢弃的 。 大于"00"(二进制)时 , NALunit不能被丢弃 。
H.265/HEVC中的情况《ISO/IEC23008-2:2015》是MPEG专家组为HEVC编解码器制定的标准 , H.265/HEVC中NALunit类型完整定义都在该标准的7-1表中 , 可用的NALunittype一共有40种之多 , 其中39和40都表征SEI内容 。 因为标准一共预留64种类型 , 所以在NALheader里面 , 用6bits表征NALunittype 。
1.第0位是禁止位0 , 值为1时表示语法出错
2.第1~6位是NALunittype
3.第7~12位是NUHlayerid
4.第13~15位是temporal_id
SEI类型在H.264/AVC视频编码标准中 , 并没有规定SEIpayloadtype的范围 , 所以表征payloadtype的字节数是浮动的 。
语法分析如下所示 , 当开始解析类型为SEI的NAL时 , 持续读取8bit , 直到非0xff为止 , 然后把读取的数值累加 , 累加值即为SEIpayloadtype 。
其中SEIpayload类型值为5时 , 指定的处理方法叫user_data_unregistered() , 字面含义为未注册的用户数据 , 常用于存储编码器的编码参数信息 , 是比较常见的payload类型 。
读取payloadtype为5时 , 具体的语法解析流程如下:
1.payloadsize应该大于16;
2.uuid可能出现0x000000/0x000001/0x000002 , 需要插入0x03做防竞争处理;
构成RBSP时 , 都需要做RBSP拖尾处理 。 拖尾处理对所有SODB方式都一致 。 rbsp_trailing_bits()语法逻辑如下:
起始码(暗红底色)"0x00000001"分割出来的比特流即是NALunit , 起始码紧跟的第一个字节(墨绿底色)是NALheader 。 上图“NALheader”一共出现了四个数值:
·"0x06" , 此时NRI为"00B" , NALunittype为SEI类型 。
·“0x67” , 此时NRI为“11B” , NALunittype为SPS类型 。
·“0x68” , 此时NRI为“11B” , NALunittype为PPS类型 。
·“0x65” , 此时NRI为“11B” , NALunittype为IDR图像 。
SEIpayloadtype
"0x06"后一个字节为“0x05”(淡黄底色)是SEIpayloadtype , 即表征SEIpayload分析遵循user_data_unregistered()语法 。
SEIpayloadsize
“0x05”后一个字节为“0x2F”(淡蓝底色)是SEIpayloadsize , 此时整个payload是47个字节 。
SEIpayloaduuid
"0x2F"随后的16个字节即为uuid , 此时uuid为
SEI的生成生成SEI的方式很多 , 大致可以有:
1.对已有码流做filter , 插入SEINAL
2.视频编码时生成SEI
3.容器层写入时插入SEI
以下代码示例来自于FFmpegorigin/master分支 。
bsfBitStreamFilter(码流过滤)的缩写为bsf , 它的作用是 , 在不做码流解码的前提下 , 对已经编码后的比特流做特定的修改、调整 。
bsfh264_metadata的调用
使用ffmpeg工具时 , 可以使用比特流过滤器 。 基本的filter调用格式如下:
具体代码位于:libavcodec/h264_metadata_bsf.c中 。
·当前au包含sps;满足插入SEI逻辑后 , 具体处理过程中:
·如果发现第一个NAL已经是SEI , 则该au不做插入SEI处理;
·如果au包含了IDR帧或者非IDR未分区的帧 , 则在其前面插入SEI信息 。
基于以上代码 , oceans.sei.h264码流中新插入27个新的SEI符合处理逻辑 。 具体构造SEINALUnit代码如下:
读取或者解码数据时 , 会调用下面函数进行码流的解码 , 其中buf包含具体的二进制流 , buf_size是当前码流长度 。 函数内部会解析码流并实例出具体的NAL对象:
ffplayffplay是一个简单、常用的FFmpeg接口示例工具 , 常用于测试解码、播放效果 。 如果在ffplay中示例跑通SEI提取功能 , 可以很方便的移植到其他平台 。
在ffplay中通过函数av_read_frame(ic,pkt)返回后 , 读取pkt->data可以快速拿到当前读到的NALunit 。 从data数据中取出NALunittype , 如果是SEI且是用户未注册数据类型(payloadtype值为5) , 则可以参考SEI语法继续读取UUID和其后传递的字符串 。
本文主要对H.264码流中涉及用户未注册数据的SEI进行了分析 。 总体而言 , SEI只是视频标准里面很小的一部分 , 但在应用过程中 , 比如直播问答项目中SEI承载的信息 , 就极大提升了直播观看和答题操作的整体用户体验 。 所以说 , 从SEI的例子中 , 我们就会发现 , 视频标准里面还有很多金矿等待着大家的挖掘 , 这就是多媒体技术的魅力 , 也是金山视频云努力的方向 。
最后文末附上这些年收录整理的资料 , 内容包含:Android学习PDF+架构视频+面试文档+源码笔记 , 高级架构技术进阶脑图、Android开发面试专题资料 , 高级进阶架构资料这几块的内容
分享给大家 , 非常适合近期有面试和想在技术道路上继续精进的朋友 。 也是希望可以帮助到大家提升进阶
【FFmpeg从入门到精通——进阶篇,SEI那些事儿】如果你有需要的话 , 可以私信我【进阶】我发给你
推荐阅读
- 从小就馋此口,比肉香多了,几块钱做一大盘,咋吃都不腻
- 这早餐我从3岁开始吃,三十多年了,从没吃腻过,晶莹剔透很好吃
- 自从学会做这个,我家隔三差五吃,拌一拌上锅一蒸,蘸着料吃,老香了
- 听力障碍|预防耳聋从身边小事做起
- 自从学会这道肉卷,我家隔三差五吃,一卷一蒸,不用蘸酱也好吃
- 我家零食从来不用买,切一切锅里一炸,比油条薯片好吃,越嚼越香
- 馄饨的家常做法,从擀皮到调馅全教你,汤鲜肉香,太好吃了
- 我家茄子从不炒着吃,淋入3个鸡蛋,比吃肉还香,上桌瞬间被扫光
- 引力波|从暗物质到暗物质引力波
- 有两种谷物被称为“长寿食材”,却很少人当主食吃,从今天要改
