Android音视频【二】 H264码流结构

人间观察
因为穷,人会放弃体面: 个人形象的体面,工作的体面,社交的体面,尊严的体面。

在分析H.264码流前,我们得得先获取一个H.264的码流,两种方法获取:一是自己写个代码编码为h264的码流(后续介绍),二是是直接从视频文件里抽取。我们这里采用方法二。当然也有其它方法。

快手抖音的短视频/直播,毫无疑问采取的编码方式肯定是H.264AAC生成的MP4封装格式的视频,我们下载一个mp4(可以看一下文件的简介中的编解码器是否是H.264,AAC),用如下ffmpeg命令抽取h264和aac:

// ffmpeg命令 抽取aac到文件
ffmpeg -i v0200f7b0000bq9dpgfiv42bsnt20920.MP4 -acodec copy -vn  1.aac
// ffmpeg命令 抽取h264到文件
ffmpeg -i v0200f7b0000bq9dpgfiv42bsnt20920.MP4  -c:v copy -bsf:v h264_mp4toannexb -an  1.h264

抽取的h264和aac可以播放吗?当然可以,我用的是mac,mac上可以用vlc播放。

ffmpeg命令可以从官网直接下载可执行的二进制
关于在Android中如何利用clang交叉编译ffmpeg后续文章介绍

H.264码流格式

h264的有两种码流格式:字节流格式和RTP包格式。

字节流格式

Annex-B Byte stream format,这个是官方h264协议文档中规定的格式,所以它是大多数编码器默认的编码后的输出格式。它的基本数据单位为NAL单元,简称NALU(Network Abstraction Layer Unit)。每个NALU的前面加上起始码:0x000001(3个字节)或0x00000001(4个字节)用于分割,后面会介绍。

RTP包格式

这种格式没有在h264中规定,这种格式不需要起始码分割NALU,而是在NALU开始的几个字节代表NALU的长度。这个我没有过多研究,应该是不常用的。

所以我们这里主要介绍的就是字节流格式的h264裸流。所谓的裸流就是经编码器编码后输出的数据,而没有经过传输协议(比如flv)封装的数据,这样的数据就叫做裸流。

H.264结构

码流分层

如上所说h264码流是由一个接一个的 NALU组成的,但是它按照功能分为

视频编码层:VCL(Video Coding Layer),编码器压缩处理后的压缩视频数据序列。

网络抽象层:NAL(Network Abstraction Layer),负责以网络要求的格式对数据进行打包和传送,是传输层。不管是本地保存还是在网络上传送,都需要通过这一层来传输。

也就是视频编码数据(VCL)在传输或存储(保存到文件)之前,会先被封装进NAL(也就是NALU)单元才可以。

NALU(NAL单元)

h264码流是一系列的NALU组成,用起始码分割每个。所以整体看码流的格式就是:

H264码流 = …Start_Code_Prefix + NALU + Start_Code_Prefix + NALU + …

Start_Code_Prefix 标示的就是起始码,起始码为:0x000001(3个字节)或0x00000001(4个字节),起始码中间的部分就是NALU的部分。

我们看下我们从抖音/快手提取的h264文件的开始部分(因为h264格式开始有SPS,PPS,SEI 分割较多,你可以搜索一下文件后后面的数据流也有):

起始码.png

NALU的主体是:NALU=NALU Header + EBSP

NALU的主体有细分:分别为EBSP、RBSP和SODB。其中EBSP完全等价于NALU主体,而且它们三个的结构关系为:

EBSP包含RBSP,RBSP包含SODB。

EBSP名字叫:扩展字节序列载荷(Encapsulated Byte Sequence Payload)

RBSP名字叫:原始字节序列载荷(Raw Byte Sequence Payload)

SODB(String Of Data Bits)就是最原始的编码数据。

后续介绍,先有个大概的概念区分,真的是概念非常多。

NALU Header

NALU Header 在每个的NALU中,占据一个字节也就是8位。分三部分,如下:

名称 占据位数bit 代表的意义
forbidden_zero_bit 1bit h264文档规定,这个值应该为0,当它不为0时,表示网络传输过程中,当前NALU中可能存在错误,解码器可以考虑不对这个NALU进行解码
nal_ref_idc 2bit 取值0~3,代表当前这个NALU的重要性,取值越大,代表当前NALU越重要
nal_unit_type 5bit NALU的数据类型,比如是sps,pps,sei,idr等

我们主要看一下nal_unit_type在h264协议中定义如下:

nal_unit_type.png

nal_unit_type =1-5是VCL(视频编码层)单元。

6-代表当前NALU为辅助增强信息(SEI)。一般会埋入视频版权等信息。

7-代表当前NALU为序列参数集SPS,包括一个图像序列的所有信息,即两个 IDR 图像间的所有图像信息,如图像尺寸、视频格式等

8-代表当前NALU为图像参数集PPS,包括一个图像的所有分片的所有相关信息, 包括图像类型、序列号等

一般h264的码流最开始都是SEI,SPS,PPS,IDR(I帧)...,SPS,PPS,IDR(I帧). 一般在IDR(I帧)前有SPS,PPS,也就是每一组图像(GOP序列,图片组)都给予了图像参数集(PPS)和这个序列参数集SPS(SPS)。我们看下最开始提取的抖音的h264文件(也就是上面启始码的后一字节)。

// 这里只贴了关键字节,省略其它的
// 16进制打开,每2位数是一个字节byte=8位(bit)
// 1F的二进制位的后五位为:11111
0000 0001 0605 ffff e1dc 45e9 bde6 d948  SEI  06&1F取该字节的后五位=6

3d31 3a31 2e30 3000 8000 0000 0167 6400  SPS  67&1F取该字节的后五位=7

0303 c0f1 8319 a000 0000 0168 e978 b2c8  PPS  68&1F取该字节的后五位=8

b000 0001 6588 8400 4ffe 841f c0a5 9f35  IDR  65&1F取该字节的后五位=5

71b9 4cd3 13c1 0000 0001 419a 246c 47ff  slice(片)  41&1F取该字节的后五位=1

视频的宽高就是在SPS中取出来的。

EBSP和RBSP

NALU的起始码为0x0000010x00000001,但是有一种在NALU的内部也有0x0000010x00000001的数据怎么办?H264采用了一种方法如果NALU内部出现了编码器就在最后一个字节前,插入一个新的字节:0x03。做了如下4种情况的处理:

0x000000  插入x03  0x00000300
0x000001  插入x03  0x00000301
0x000002  插入x03  0x00000302
0x000003  插入x03  0x00000303

0x000003是为了防止NALU内部本来就有0x000003这样的数据。
所以说EBSP相较于RBSP,多了防止冲突的一个字节:0x03。当使用EBSP时,就需要检测EBSP内是否有序列:0x000003,如果有,则去掉其中的0x03。这样一来,我们就能得到原始字节序列载荷:RBSP。

我们用提取的抖音的h264文件找下:

3d31 3a31 2e30 3000 8000 0000 0167 6400
1fac d980 b40a 1b01 1000 0003 0010 0000
// 比如67=SPS 的NALU就有一个0303
0303 c0f1 8319 a000 0000 0168 e978 b2c8
b000 0001 6588 8400 4ffe 841f c0a5 9f35
11fe 06cb d3bf 26e6 9d1f ff2c e1b1 aaf2

RBSP和SODB

原始编码数据SODB(String Of Data Bits)他们2个的关系是:

RBSP = SODB + RBSP尾部

RBSP尾部

H264协议文档中有两种尾部表示,如下:

RBSP尾部.png

尾部特RBSP语法

RBSP最后一个字节的最后一个比特为rbsp_stop_one_bit,其值为1,并且当rbsp_stop_one_bit不是最后一个比特时,用一个或多个rbsp_alignment_zero_bit,其值为0,补齐以形成一个字节对齐。

条带RBSP尾部

nal_unit_type等于1~5时采用这种尾部。在尾部特RBSP语法的基础上,如果当entropy_coding_mode_flag值为1,也即当前采用的熵编码为CABAC,而且more_rbsp_trailing_data返回为true,也即RBSP中有更多数据时,添加一个或多个0x0000

H264的码流结构

所以整体H.264的Annex-B码流格式从概念上来看就是,SODB里就是原始的编码数据。

H.264 Annex-B 码流格式.png

如有描述不准确欢迎指正。

H.264的协议文档

http://www.itu.int/rec/T-REC-H.264

http://www.itu.int/rec/T-REC-H.264-200503-S/en

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,873评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,483评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,525评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,595评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,018评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,958评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,118评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,873评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,643评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,813评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,293评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,615评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,306评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,170评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,968评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,107评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,894评论 2 278

推荐阅读更多精彩内容