1、码流总体结构:
H264码流打包分析
SODB 数据比特串-->最原始的编码数据
RBSP 原始字节序列载荷-->在SODB的后面填加了结尾比特(RBSP trailing bits 一个bit“1”)若干比特“0”,以便字节对齐。
EBSP 扩展字节序列载荷-- >在RBSP基础上填加了仿校验字节(0X03)它的原因是: 在NALU加到Annexb上时,需要填加每组NALU之前的开始码 StartCodePrefix,如果该NALU对应的slice为一帧的开始则用4位字节表示,ox00000001,否则用3位字节表示ox000001.为了使NALU主体中不包括与开始码相冲突的,在编码时,每遇到两个字节连续为0,就插入一个字节的0x03。解码时将0x03去掉。也称为脱壳操作。
h264的功能分为两层,视频编码层(VCL)和网络提取层(NAL)
VCL数据即被压缩编码后的视频数据序列。在VCL数据要封装到NAL单元中之后,才可以用来传输或存储。NAL单元格式如下图:
Nal头 | EBSP | Nal头 | EBSP | Nal头 | EBSP |
h264的功能分为两层,视频编码层(VCL)和网络提取层(NAL)。H.264 的编码视频序列包括一系列的NAL 单元,每个NAL 单元包含一个RBSP。一个原始的H.264 NALU 单元常由 [StartCode] [NALU Header] [NALU Payload] 三部分组成,其中 Start Code 用于标示这是一个NALU 单元的开始,必须是"00 00 00 01" 或"00 00 01"。
其中RBPS有分为几种类型:
NAL的解码单元的流程如下:
2、 NAL Header:
占一个字节,由三部分组成forbidden_bit(1bit),nal_reference_bit(2bits)(优先级),nal_unit_type(5bits)(类型)。
forbidden_bit:禁止位。
nal_reference_bit:当前NAL的优先级,值越大,该NAL越重要。
nal_unit_type :NAL类型。参见下表
几个例子:
3、 ffmpeg解析H264流程分析
这是一段实际的码流
在上面的图片中,共有三个起始码:0x00 0000 01
const uint8_t*ff_h264_decode_nal(H264Context*h, const uint8_t *src,int *dst_length, int*consumed, int length)中分析过程为:
h->nal_ref_idc= src[0] >> 5;
h->nal_unit_type= src[0] & 0x1F;
此处src[0]即为06,写成二进制位0000 0110,则h->nal_ref_idc = 0,h->nal_unit_type = 6
可以判断这个NALU类型为SEI,重要性优先级为0。
src++;src向后移动一个位置,此时src指向图中第一行第五列的数据05
length--;未处理数据长度减1
#defineSTARTCODE_TEST \
if(i + 2 < length && src[i + 1] == 0 && src[i + 2]<= 3){ \
if(src[i + 2] != 3){ \
/* startcode, so we must bepast the end*/ \
length =i; \
} \
break; \
}
for(i = 0; i + 1 < length; i += 2) {
if(src[i])
continue;
if(i > 0 && src[i - 1] == 0)
i--;
STARTCODE_TEST;
}
上述分析:
1)如果src[i] !=0 ,则继续下一次循环,直到src[i] == 0,即等于下一个起始码的第二个00为止,即图中地址为000001a0h行的第3列,地址为0x00 00 01 a3(十进制为419),此时i为414,因为是从0x00 00 00 05地址开始的,此时则第一次执行continue下面的if语句,而且src[i-1]==0,则i--,此时i=413
2)执行宏定义STARTCODE_TEST,进行起始码检测:src[i+ 1] =src[414]=0,src[i+2]=src[415]=0;
继续执行,src[i+2] != 3,这里是进行竞争检测(前面有讲述,如果在编码的过程中出现连续00 00 00时,将会在第三个00前插入一个03,避免和起始码造成冲突),既然没有发生竞争现象,则表明这个确实是下一个NALU的起始码
3)length是指当前NALU单元长度,这里不包括nalu头信息长度,即一个字节,
length = i =413
后面的SEI解析不再赘述
(2)第二个 0x00 00 00 01:
h->nal_ref_idc= src[0] >> 5;
h->nal_unit_type= src[0] & 0x1F;
此时src[0]是指起始码后面第一个数据67(地址为0x00 00 01a6h),写成二进制为0110 0111,则h->nal_unit_type = 7,h->nal_ref_idc =3
则这一个NALU单元类型为SPS
下面开始分析SPS的长度,这一段数据很巧,包含了竞争检测
依然是下面这段代码:
src++;src向后移动一个位置,此时src指向图中地址为00 0001 a7h的数据
length--;未处理数据长度减1
#defineSTARTCODE_TEST \
if(i + 2 < length && src[i + 1] == 0 && src[i + 2]<= 3){ \
if(src[i + 2] != 3){ \
/* startcode, so we must bepast the end*/ \
length =i; \
} \
break; \
}
for(i = 0; i + 1 < length; i += 2) {
if(src[i])
continue;
if(i > 0 && src[i - 1] == 0)
i--;
STARTCODE_TEST;
}
分析:
1)自地址0x00 00 01 a7h开始查找src[i] ==0,此时src[i]的地址为0x00 00 01 b1h,此时src[i]==0,而src[i-1]=80 != 0,此时i = 10 ;
2)执行宏定义STARTCODE_TEST:if(i+2 <lengthn&&src[i+1]==0&&src[i+2]<=3)其中src[i+1]=src[11]=0,且src[i+2]=src[12]=3,继续执行,if(src[i+2] !=3),显然不满足,执行break,跳出循环,此时 i=10
此时尚不能确定下一个nalu单元的起始码在何处,因此当前nalu单元的长度也是未定的,
bufidx =h->nal_unit_type == NAL_DPC ? 1: 0;bufidx=0
si =h->rbsp_buffer_size[bufidx];
av_fast_padded_malloc(&h->rbsp_buffer[bufidx],&h->rbsp_buffer_size[bufidx],length+MAX_MBPAIR_SIZE);
dst =h->rbsp_buffer[bufidx];
以上是为当前NALU分配缓存,并清零
memcpy(dst,src, i);将当前确定的nalu内容拷贝到dst缓存中
si = di = i;目的缓存长度和源缓存长度=i=10
while (si + 2< length) {
//remove escapes (very rare 1:2^22)
if(src[si + 2] > 3) {
dst[di++]= src[si++];
dst[di++]= src[si++];
}else if (src[si] == 0 && src[si + 1] == 0) {
if(src[si + 2] == 3) { // escape
dst[di++] = 0;
dst[di++] = 0;
si += 3;
continue;
}else // next start code
goto nsc;
}
dst[di++]= src[si++];
}
nsc:
memset(dst+ di, 0, FF_INPUT_BUFFER_PADDING_SIZE);
*dst_length= di;
*consumed = si + 1; // +1 forthe header
/*FIXME store exact number of bits in the getbitcontext
*(it is needed for decoding) */
returndst;
分析:src[0]=4D位于0x00 00 01a7h,src[10]=0位于0x00 00 01b1h后面的地址不再赘述,依次为起始位置
从图中可以看出:
src[10]=00,src[11]=00,src[12]=03,src[13]=00,src[14]=80,src[15]=00,src[16]=00,src[17]=19,src[18]=47,src[19]=8C,src[20]=19,src[21]=50,src[22]=00,src[23]=00,src[24]=00
1)进入while循环,
此时si=10:
src[si+2] =src[12]=03且src[11]=00,src[10]=00,则执行:
dst[di++]=0:即dst[10]=0,di=11,dst[11]=0,di=12;
si+=3:si=13,然后continue,开始下一次循环
注:此处的03即为竞争检测,最后将03跳过了
此时si=13:
src[15]=00,src[13]=00,src[14]=80,则执行
dst[di++]=src[si++];
即:dst[12]=src[13]=00,di=13,si=14;
此时si=14:
src[16]=00,src[15]=00,src[14]=80
则执行dst[di++]=src[si++];
即:dst[13]=src[14]=00,di=14,si=15
此时si=15:
src[15]=00,src[16]=00,src[17]=19
则执行:dst[di++] = src[si++];
dst[di++]= src[si++];
dst[di++]= src[si++]
即:dst[14]=src[15]=00,di=15,si=16;
dst[15]=src[16]=00,di=16,si=17;
dst[16]=src[17]=19,di=17,si=18;
此时si=18:
src[18]=47,src[19]=8C,src[20]=19
执行:
dst[di++] = src[si++];
dst[di++]= src[si++];
dst[di++] =src[si++]
即:dst[17]=src[18]=47,di=18,si=19
dst[18]=src[19]=8C,di=19,si=20
dst[19]=src[20]=19,di=20,si=21
此时si=21:
src[21]=50,src[22]=00,src[23]=00
执行:
dst[di++]= src[si++];
即dst[20]=src[21]=50,di=21,si=22
此时si=22:
src[22]=00,src[23]=00,src[24]=00
执行:
goto nsc;
那就看一下nsc:
nsc:
memset(dst+ di, 0, FF_INPUT_BUFFER_PADDING_SIZE);
*dst_length= di;
*consumed = si + 1; // +1 forthe header
/*FIXME store exact number of bits in the getbitcontext
*(it is needed for decoding) */
returndst;
此时di=21,si=22
memset()函数是向SPS单元尾部补零
dst_length=di=21,;即SPS单元的RBSP长度为21,不包括起始码
consumed=si+1=23;表示SPS单元共消耗的字节数,加1表示SPS单元信息头占一个字节,注意consumed比dst_length大1,这是由于竞争检测时,将编码时添加的一个字节的03过滤掉造成的。