Advertisement

H264码流结构分析

阅读量:

1、码流总体结构:

h₂⁶₄是一种先进的视频压缩标准,在其架构中分为两个主要功能层次:视频编码层(VCL)和网络传输相关的辅助层(NAL)。在采用H.₂₆₄协议时,默认情况下会生成一系列的Network Abstraction Layer Units (NALUs),每个这样的单位都包含一个相关联的数据块(RBSP)。通常情况下,在H.₂₆₄编码中生成的一个完整NAL单元由三个部分构成:起始码、头部信息以及承载的数据内容。需要注意的是,在某些特定场景下可能会省略头部信息或数据内容中的某些字段。为了确保可靠传输,在整个链路的不同阶段都会对这些NAL单元进行完整性校验,并根据检测到的问题采取相应的纠正措施。

其中RBPS有分为几种类型:

NAL的解码单元的流程如下:

2、 NAL Header:

占用一个比特单元,分为三个组成部分:forbidden_bit(1-bit bit),nal_reference_bit(2-bit bits)(优先级),nal_unit_type(5-bit bits)(类型)。

forbidden_bit:禁止位。

nal_reference_bit:当前NAL的优先级,值越大,该NAL越重要。

nal_unit_type :NAL类型。参见下表

几个例子:

3、 ffmpeg解析H264流程分析

这是一段实际的码流

在上面的图片中,共有三个起始码:0x00 0000 01

其中用于分析的过程如下:
const uint8_tff_h264_decode_nal(H264Contexth, 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;

}

上述分析:

若src[i]不等于零,则执行下一次循环;否则程序将跳转至当前起始码对应的第二个零位置(即图中所示相关的位置)。在图中可以看到 src 地址为 [...] 的情况:[...] = 419(十进制);此处 i 值已调整至 [...] 处(从初始设置 [...] 开始)。由于 src[... - 1] 等于零,则 i 值减一至 [...]

2)执行宏定义STARTCODE_TEST,进行起始码检测:src[i+ 1] =src[414]=0,src[i+2]=src[415]=0;

继续执行下去,在src[i+2]不等于3的情况下,在这里我们正在进行竞争检测(之前已经提到过,在编码过程中遇到连续三个' '的情况时,在第三个' '的位置插入一个' '以避免与起始码冲突)。如果此时没有发生竞争现象,则说明这个确实是下一个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;

当前source register中的第一个数据位是decimal 67(其地址位于hexadecimal 0x00 00 01a6h),将其表示为binary number即得到binary value 1111_111(即binary number of 2^8 - binary number of decimal 3);则根据计算结果可得出h->nal_unit_type参数赋值为7(decimal),同时h->nal_ref_idc参数赋值为3(decimal)

则这一个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 ;

运行宏定义STARTCODE_TEST:当i值为10时,在检查以下条件后进行判断:i+2小于lengthn、 src[i+1]等于0以及 src[i+2]小于等于3。其中在i=9的情况下(因为i从零开始计数), src[10]的值为零,并且 src[11]的值为三。由于当前的条件不符合预期的结果(即 src[i+2]不等于三),触发break语句导致循环中断。

此时尚无法确定下一个nalu单元的starting address所在位置,则当前nalu单元的length值也是未知的。

bufidx =h->nal_unit_type == NAL_DPC ? 1: 0;bufidx=0

si =h->rbsp_buffer_size[bufidx];

av_fast_padded_malloc(&C, h->rbsp_buffer->__access[bufidx], &C, h->rbsp_buffer->__buffer_size[bufidx], long length_value = 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变量多出一位。
这是因为,在竞争检测过程中...编码时增加的一个字节被过滤掉了。

全部评论 (0)

还没有任何评论哟~