H264分析源码学习之结构体篇——nal_t结构体

最近学习H264的编解码,因此先学习了解H264的结构。我是通过h264分析开源库的源码进行学习的。首先先从数据结构体入手,通过了解重要的数据结构体来认识H264!


我们首先认识几个概念:

VCL:video code layer(视频编码层)   NAL:network abstract layer(网络提取层)

NALU:coded h264 data is stored or transmitted as  a series of packets knowns as Network Abstract Layer Unit 网络提取层单元

RBSP:a NALU contains a Raw Byte Sequence Payload(原始字节序列负载,按字节对齐了的)

SODB:String Of Data Bits(原始数据比特流,长度不一定是8的倍数)


SODB + trailing bits(尾部补齐字节数)   = RBSP

NAL header(1 byte)  + RBSP  = NALU

... + StartCode(3 byte) + NALU + StartCode(3 byte) + NALU + ... = h264 stream




再介绍一下NAL header

NAL Header:占用了一个字节,按照比特自高至低排列可以表示如下:

forbidden_bit(1位)nal_reference_bit(2位)nal_unit_type(5位)

forbidden_bit:为禁止位

nal_reference_bit:优先级,00b 表示可丢弃(比如SEI,B slice),非零表示不可丢弃,如SPS、PPS、I Slice、P Slice等

nal_unit_type:此NAL的类型,也就是RBSP的数据到底是什么。


nal_unit_type不同的值,代表不同NAL类型,如下:

  • 0:未规定
  • 1:非IDR图像中不采用数据划分的片段
  • 2:非IDR图像中A类数据划分片段
  • 3:非IDR图像中B类数据划分片段
  • 4:非IDR图像中C类数据划分片段
  • 5:IDR图像的片段
  • 6:补充增强信息(SEI)
  • 7:序列参数集(SPS)
  • 8:图像参数集(PPS)
  • 9:分割符
  • 10:序列结束符
  • 11:流结束符
  • 12:填充数据
  • 13:序列参数集扩展
  • 14:带前缀的NAL单元
  • 15:子序列参数集
  • 16 – 18:保留
  • 19:不采用数据划分的辅助编码图像片段
  • 20:编码片段扩展
  • 21 – 23:保留
  • 24 – 31:未规定

因此可以发现,SODB的数据不一定都是视频图像数据,也有可能是其他信息(为解码提供相关信息),nal_unit_type取值1-5的NAL,称为VCL的NAL单元;其他类型的NAL,

成为非VCL的NAL单元



这里的nal_t结构体的成员包括nal的头部信息和一个指向rbsp的指针。

/**
   Network Abstraction Layer (NAL) unit
   @see 7.3.1 NAL unit syntax
   @see read_nal_unit
   @see write_nal_unit
   @see debug_nal
*/
/*
		NAL单元数据结构: NALU header  +  NALU payload(这里是一个指向该数据的指针,payload的数据定义在h264_stream_t结构体中)
		
		int read_nal_unit(h264_stream_t* h, uint8_t* buf, int size);函数中实现了本数据结构体的填充
		
*/
typedef struct
{
    int forbidden_zero_bit;   //禁止位			 									 nalu header(从1byte中读取这三个句法元素)
    int nal_ref_idc;					//优先级
    int nal_unit_type;				//nalu类型
    
    void* parsed; // FIXME		//指向nal payload					    	nalu	payload
    int sizeof_parsed;				//nal payload数据结构大小

    //uint8_t* rbsp_buf;
    //int rbsp_size;
} nal_t;



/*
 Read a NAL unit from a byte buffer.
 The buffer must start exactly at the beginning of the nal (after the start prefix).
 The NAL is read into h->nal and into other fields within h depending on its type (check h->nal->nal_unit_type after reading).
 @param[in,out] h          the stream object
 @param[in]     buf        the buffer
 @param[in]     size       the size of the buffer
 @return                   the length of data actually read, or -1 on error
 */
 /*
 此函数从一个字节流buf中读取一个NAL单元,并将数据填充到h264_stream_t结构体中
 这里需要注意:数据buf必须从其实码后开始,因此,buf的开头应该是:NALU header(1 byte)+NALU payload(rbsp)
 
 h264_stream_t* h: stream结构体
 uint8_t* buf:			一个NALU单元的数据内容(不包括开始码)
 int size:					数据长度
 
 */
//7.3.1 NAL unit syntax
int read_nal_unit(h264_stream_t* h, uint8_t* buf, int size)
{
    nal_t* nal = h->nal;

    bs_t* b = bs_new(buf, size);

    nal->forbidden_zero_bit = bs_read_f(b,1);	//读取第1个位:禁止位
    nal->nal_ref_idc = bs_read_u(b,2);			  //读取第2,3位:优先级
    nal->nal_unit_type = bs_read_u(b,5);			//读取第4-8位:NALU类型
    nal->parsed = NULL;
    nal->sizeof_parsed = 0;

    bs_free(b);

    int nal_size = size;
    int rbsp_size = size;
    uint8_t* rbsp_buf = (uint8_t*)malloc(rbsp_size); //申请一个rbsp数据空间,用来存储rbsp
 
    int rc = nal_to_rbsp(buf, &nal_size, rbsp_buf, &rbsp_size);	//从一个nal单元读取rbsp

    if (rc < 0) { free(rbsp_buf); return -1; } // handle conversion error

    b = bs_new(rbsp_buf, rbsp_size);

    switch ( nal->nal_unit_type )	//NALU类型判断,采取不同处理
    {
        case NAL_UNIT_TYPE_CODED_SLICE_IDR:				
        case NAL_UNIT_TYPE_CODED_SLICE_NON_IDR:  
        case NAL_UNIT_TYPE_CODED_SLICE_AUX:
            read_slice_layer_rbsp(h, b);
            nal->parsed = h->sh;
            nal->sizeof_parsed = sizeof(slice_header_t);
            break;

        case NAL_UNIT_TYPE_SEI:
            read_sei_rbsp(h, b);
            nal->parsed = h->sei;
            nal->sizeof_parsed = sizeof(sei_t);
            break;

        case NAL_UNIT_TYPE_SPS: 
            read_seq_parameter_set_rbsp(h, b); 
            nal->parsed = h->sps;
            nal->sizeof_parsed = sizeof(sps_t);
            break;

        case NAL_UNIT_TYPE_PPS:   
            read_pic_parameter_set_rbsp(h, b);
            nal->parsed = h->pps;
            nal->sizeof_parsed = sizeof(pps_t);
            break;

        case NAL_UNIT_TYPE_AUD:     
            read_access_unit_delimiter_rbsp(h, b); 
            nal->parsed = h->aud;
            nal->sizeof_parsed = sizeof(aud_t);
            break;

        case NAL_UNIT_TYPE_END_OF_SEQUENCE: 
            read_end_of_seq_rbsp(h, b);
            break;

        case NAL_UNIT_TYPE_END_OF_STREAM: 
            read_end_of_stream_rbsp(h, b);
            break;
        //case NAL_UNIT_TYPE_FILLER:
        //case NAL_UNIT_TYPE_SPS_EXT:
        //case NAL_UNIT_TYPE_UNSPECIFIED:
        //case NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_A:  
        //case NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_B: 
        //case NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_C:
        default:
            // here comes the reserved/unspecified/ignored stuff
            nal->parsed = NULL;
            nal->sizeof_parsed = 0;
            return 0;
    }

    if (bs_overrun(b)) { bs_free(b); free(rbsp_buf); return -1; }

    bs_free(b); 
    free(rbsp_buf);

    return nal_size;
}
/*
  IDR(Instantaneous Decoding Refresh)--即时解码刷新。
  
  I和IDR帧都是使用帧内预测的。
  它们都是同一个东西而已,在编码和解码中为了方便,要首个I帧和其他I帧区别开,所以才把第一个首个I帧叫IDR,这样就方便控制编码和解码流程。
  
  IDR帧的作用是立刻刷新,使错误不致传播,从IDR帧开始,重新算一个新的序列开始编码。
  而I帧不具有随机访问的能力,这个功能是由IDR承担。
  IDR会导致DPB(DecodedPictureBuffer 参考帧列表——这是关键所在)清空,而I不会。
  IDR图像一定是I图像,但I图像不一定是IDR图像。
  一个序列中可以有很多的I图像,I图像之后的图像可以引用I图像之间的图像做运动参考。
 
 IDR图像是很重要的,一旦丢失怎个序列的所有帧都没有办法解码!
*/
//Table 7-1 NAL unit type codes
#define NAL_UNIT_TYPE_UNSPECIFIED                    0    // 未使用
#define NAL_UNIT_TYPE_CODED_SLICE_NON_IDR            1    // 非IDR图像中不采用数据划分的片
#define NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_A   2    // 非IDR图像中A类数据划分的片
#define NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_B   3    // 非IDR图像中B类数据划分的片
#define NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_C   4    // 非IDR图像中C类数据划分的片
#define NAL_UNIT_TYPE_CODED_SLICE_IDR                5    // IDR图像的片
#define NAL_UNIT_TYPE_SEI                            6    // 补充增强信息单元
#define NAL_UNIT_TYPE_SPS                            7    // 序列参数集
#define NAL_UNIT_TYPE_PPS                            8    // 图片参数集
#define NAL_UNIT_TYPE_AUD                            9    // 分界符
#define NAL_UNIT_TYPE_END_OF_SEQUENCE               10    // 序列结束
#define NAL_UNIT_TYPE_END_OF_STREAM                 11    // 码流结束
#define NAL_UNIT_TYPE_FILLER                        12    // 填充数据
#define NAL_UNIT_TYPE_SPS_EXT                       13    // Sequence parameter set extension
                                             // 14..18    // Reserved
#define NAL_UNIT_TYPE_CODED_SLICE_AUX               19    // Coded slice of an auxiliary coded picture without partitioning




 






  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值