H264相关知识学习


H264在网络传输的是NALU,NALU的结构是:NAL头+RBSP, 
其中NAL头占一个字节,其低5个bit位表示NAL type。
RBSP 为原始字节序列载荷
在实际的H264数据帧中,往往帧NAL type前面带有00 00 00 01 或 00 00 01分隔符,
一般来说编码器编出的首帧数据为PPS与SPS,接着为I帧,然后是P帧

 


视频传输原理   

     视频是利用人眼视觉暂留的原理,通过播放一系列的图片,使人眼产生运动的感觉。单纯传输视频画面,视频量非常大,
     对现有的网络和存储来说是不可接受的。为了能够使视频便于传输和存储,人们发现视频有大量重复的信息,如果将重
     复信息在发送端去掉,在接收端恢复出来,这样就大大减少了视频数据的文件,因此有了H.264视频压缩标准。
      在H.264压缩标准中I帧、P帧、B帧用于表示传输的视频画面。


 


1、I帧

I帧又称帧内编码帧,是一种自带全部信息的独立帧,无需参考其他图像便可独立进行解码,可以简单理解为一张静态画面。视频
序列中的第一个帧始终都是I帧,因为它是关键帧。

2、P帧

 P帧又称帧间预测编码帧,需要参考前面的I帧才能进行编码。表示的是当前帧画面与前一帧(前一帧可能是I帧也可能是P帧)的差别。
 解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面。与I帧相比,P帧通常占用更少的数据位,但不足是,由于P帧对
 前面的P和I参考帧有着复杂的依耐性,因此对传输错误非常敏感。


3、B帧

B帧又称双向预测编码帧,也就是B帧记录的是本帧与前后帧的差别。也就是说要解码B帧,不仅要取得之前的缓存画面,还要解码之后的画面,
通过前后画面的与本帧数据的叠加取得最终的画面。B帧压缩率高,但是对解码性能要求较高。

总结:

I帧只需考虑本帧;P帧记录的是与前一帧的差别;B帧记录的是前一帧及后一帧的差别,能节约更多的空间,视频文件小了,但相对来说解码的时候
就比较麻烦。因为在解码时,不仅要用之前缓存的画面,而且要知道下一个I或者P的画面,对于不支持B帧解码的播放器容易卡顿。


视频监控系统中预览的视频画面是实时的,对画面的流畅性要求较高。采用I帧、P帧进行视频传输可以提高网络的适应能力,且能降低解码成本。所以现阶段的视频解码都只采用I帧和P帧进行传输。海康摄像机编码,I帧间隔是50,含49个P帧。


 


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>

#define TAB44 "    "
#define PRINTF_DEBUG

#define PRTNTF_STR_LEN 10

/************************************************************************************************************
**                                        nalu header: 负责将VCL产生的比特字符串适配到各种各样的网络和多元环境中, 
                                                       覆盖了所有片级以上的语法级别(NALU的作用, 方便网络传输)
**
-------------------------------------------------------------------------------------------------------------
**        字段名称               |    长度(bits)    |        有关描述
-------------------------------------------------------------------------------------------------------------
**        forbidden_bit          |    1             |        编码中默认值为0, 当网络识别此单元中存在比特错误时, 可将其设为1, 以便接收方丢掉该单元
**        nal_reference_idc      |    2             |        0~3标识这个NALU的重要级别
**        nal_unit_type          |    5             |          NALU的类型(类型1~12是H.264定义的, 类型24~31是用于H.264以外的, 
                                                             RTP负荷规范使用这其中的一些值来定义包聚合和分裂, 其他值为H.264保留)

** nal_unit_type:
    0                未使用
    1                未使用Data Partitioning, 非IDR图像的Slice
    2                使用Data Partitioning且为Slice A
    3                使用Data Partitioning且为Slice B
    4                使用Data Partitioning且为Slice C
    5                IDR图像的Slice(立即刷新)
    6                补充增强信息(SEI)
    7                序列参数集(sequence parameter set, SPS)
    8                图像参数集(picture parameter set, PPS)
    9                分界符
    10               序列结束
    11               码流结束
    12               填充
    13...23          保留
    24...31          未使用
    
** SPS, PPS. SLICE等信息就不解析了. 为了减少bits, 用了哥伦布编码(自己解析比较麻烦, 但是网上有很多).

** SPS信息说明:
        1. 视频宽高, 帧率等信息;
        2. seq_parameter_set_id, 指明本序列参数集的id号, 这个id号将被picture参数集引用;
        3. pic_width_in_mbs_minus1, 加1指定以宏块(16*16)为单位的每个解码图像的宽度, 即width = (pic_width_in_mbs_minus1 + 1) * 16
        4. pic_height_in_map_units_minus1;
        5. pic_order_cnt_type, 视频的播放顺序序号叫做POC(picture order count), 取值0,1,2;
        6. time_scale, fixed_frame_rate_flag, 计算帧率(fps).
           视频帧率信息在SPS的VUI parameters syntax中, 需要根据time_scale, fixed_frame_rate_flag计算得到: fps = time_scale / num_units_in_tick.
           但是需要判断参数timing_info_present_flag是否存在, 若不存在表示FPS在信息流中无法获取.
           同时还存在另外一种情况: fixed_frame_rate_flag为1时, 两个连续图像的HDR输出时间频率为单位, 获取的fps是实际的2倍.

** PPS信息说明:    
        1. pic_parameter_set_id, 用以指定本参数集的序号, 该序号在各片的片头被引用;
        2. seq_parameter_set_id, 指明本图像参数集所引用的序列参数集的序号;
        3. 其他高深的暂时还不理解, 指明参考帧队列等.
        
** SLICE信息说明:
        1. slice_type, 片的类型;
        2. pic_parameter_set_id, 引用的图像索引;
        3. frame_num, 每个参考帧都有一个连续的frame_num作为它们的标识, 它指明了各图像的解码顺序. 非参考帧也有,但没有意义;
        4. least significant bits;
        5. 综合三种poc(pic_order_cnt_type), 类型2应该是最省bit的, 因为直接从frame_num获得, 但是序列方式限制最大;
           类型1, 只需要一定的bit量在sps标志出一些信息还在slice header中表示poc的变化, 但是比类型0要节省bit, 但是其序列并不是随意的, 要周期变化;
           对于类型0因为要对poc的lsb(pic_order_cnt_lsb, last bit)进行编码所以用到的bit最多, 优点是序列可以随意.
           ** 自我理解, 不一定准确(这边算显示顺序, 要根据SPS中的pic_order_cnt_type, 为2, 意味着码流中没有B帧, frame_num即为显示顺序;
              为1, 依赖frame_num求解POC; 为0, 把POC的低位编进码流内, 但这只是低位, 而POC的高位PicOrderCntMsb则要求解码器自行计数,
              计数方式依赖于前一编码帧(PrevPicOrderCntMsb与PrevPicOrderCntLsb.
              
           ** 一般的码流分析所见(未仔细证实): pic_order_cnt_type=2, 只有frame_num(无B帧);
              pic_order_cnt_type=1, 暂未分析到;
              pic_order_cnt_type=0, pic_order_cnt_lsb指示显示顺序, 一般为偶数增长(0, 2, 4, 6, 据说是什么场方式和帧方式, 场时其实是0 0 2 2 4 4).
              
           ** 编码与显示的原因: 视频编码顺序与视频的播放顺序, 并不完全相同, 视频编码时, 如果采用了B帧编码, 由于B帧很多时候都是双向预测得来的,
              这时会先编码B帧的后向预测图像(P帧), 然后再进行B帧编码, 因此会把视频原来的播放顺序打乱, 以新的编码顺序输出码流,
              而在解码断接收到码流后, 需要把顺序还原成原本的播放顺序, 以输出正确的视频. 在编解码中, 视频的播放顺序序号叫做POC(picture order count).
              
** 总结: 1. 码流中有很多SPS(序列), 一个序列中有多个图像, 一个图像中有多个片, 一个片中有多个块;
         2. SPS中有seq_parameter_set_id. PPS中有pic_parameter_set_id, 并通过seq_parameter_set_id指明关联的序列.
            SLICE中有pic_parameter_set_id, 指明关联的图像;
         3. SPS中可计算宽高以及帧率, pic_order_cnt_type(显示顺序的类型);
            SLICE HEADER中可算出解码的顺序, 以及根据pic_order_cnt_type算出显示顺序.            
************************************************************************************************************/
typedef enum e_h264_nalu_priority
{
    NALU_PRIORITY_DISPOSABLE     = 0,
    NALU_PRIORITY_LOW             = 1,
    NALU_PRIORITY_HIGH           = 2,
    NALU_PRIORITY_HIGHEST        = 3,
} E_H264_NALU_PRIORITY;

typedef enum e_h264_nalu_type
{
    NALU_TYPE_SLICE        = 1,
    NALU_TYPE_DPA          = 2,
    NALU_TYPE_DPB          = 3,
    NALU_TYPE_DPC          = 4,
    NALU_TYPE_IDR          = 5,
    NALU_TYPE_SEI          = 6,
    NALU_TYPE_SPS          = 7,
    NALU_TYPE_PPS          = 8,
    NALU_TYPE_AUD          = 9,
    NALU_TYPE_EOSEQ        = 10,
    NALU_TYPE_EOSTREAM     = 11,
    NALU_TYPE_FILL         = 12,
} E_H264_NALU_TYPE;

typedef struct t_h264_nalu_header
{
    unsigned char forbidden_bit:1, nal_reference_idc:2, nal_unit_type:5;
} T_H264_NALU_HEADER;

typedef struct t_h264_nalu
{
    int startCodeLen;
    
    T_H264_NALU_HEADER h264NaluHeader;
    
    unsigned int bodyLen;
    
    unsigned char *bodyData;
} T_H264_NALU;

/**********************************************************************************
 1. h264的起始码: 0x000001(3 Bytes)或0x00000001(4 Bytes);
 2. 文件流中用起始码来区分NALU.
***********************************************************************************/
static int FindStartCode3Bytes(unsigned char *scData)
{
    int isFind = 0;

    if ((0==scData[0]) && (0==scData[1]) && (1==scData[2]))
    {
        isFind = 1;
    }
    
    return isFind;
}

static int FindStartCode4Bytes(unsigned char *scData)
{
    int isFind = 0;

    if ((0==scData[0]) && (0==scData[1]) && (0==scData[2]) && (1 == scData[3]))
    {
        isFind = 1;
    }
    
    return isFind;
}

static int GetNaluDataLen(int startPos, int h264BitsSize, unsigned char *h264Bits)
{
    int parsePos = 0;
    
    parsePos = startPos;
    
    while (parsePos < h264BitsSize)
    {
        if (FindStartCode3Bytes(&h264Bits[parsePos]))
        {
            return parsePos - startPos;
        }
        else if (FindStartCode4Bytes(&h264Bits[parsePos]))
        {
            return parsePos - startPos;
        }
        else
        {
            parsePos++;
        }
    }
    
    return parsePos - startPos; // if file is end
}

static void ParseNaluData(const unsigned int naluLen, unsigned char* const nuluData)
{
    static int naluNum = 0;
    
    unsigned char *data = NULL;
    unsigned char priorityStr[PRTNTF_STR_LEN+1] = {0};
    unsigned char typeStr[PRTNTF_STR_LEN+1] = {0};
    
    T_H264_NALU_HEADER h264NaluHeader = {0};
    
    data = nuluData;
    
    memset(&h264NaluHeader, 0x0, sizeof(T_H264_NALU_HEADER));
    
    h264NaluHeader.nal_reference_idc = data[0]>>5 & 0x3;
    h264NaluHeader.nal_unit_type = data[0] & 0x1f;
    
    naluNum++;
    
#ifdef PRINTF_DEBUG
    switch (h264NaluHeader.nal_reference_idc)
    {
        case NALU_PRIORITY_DISPOSABLE:
            sprintf(priorityStr, "DISPOS");
            break;
            
        case NALU_PRIORITY_LOW:
            sprintf(priorityStr, "LOW");
            break;

        case NALU_PRIORITY_HIGH:
            sprintf(priorityStr, "HIGH");
            break;

        case NALU_PRIORITY_HIGHEST:
            sprintf(priorityStr, "HIGHEST");
            break;

        default:
            break;
    }
    
    switch (h264NaluHeader.nal_unit_type)
    {
        case NALU_TYPE_SLICE:
            sprintf(typeStr, "SLICE");
            break;
            
        case NALU_TYPE_DPA:
            sprintf(typeStr, "DPA");
            break;
            
        case NALU_TYPE_DPB:
            sprintf(typeStr, "DPB");
            break;
            
        case NALU_TYPE_DPC:
            sprintf(typeStr, "DPC");
            break;
            
        case NALU_TYPE_IDR:
            sprintf(typeStr, "IDR");
            break;
            
        case NALU_TYPE_SEI:
            sprintf(typeStr, "SEI");
            break;
            
        case NALU_TYPE_SPS:
            sprintf(typeStr, "SPS");
            break;
            
        case NALU_TYPE_PPS:
            sprintf(typeStr, "PPS");
            break;
            
        case NALU_TYPE_AUD:
            sprintf(typeStr, "AUD");
            break;
            
        case NALU_TYPE_EOSEQ:
            sprintf(typeStr, "EOSEQ");
            break;
            
        case NALU_TYPE_EOSTREAM:
            sprintf(typeStr, "EOSTREAM");
            break;
            
        case NALU_TYPE_FILL:
            sprintf(typeStr, "FILL");
            break;
        
        default:
            break;
    }
    
    printf("%5d| %7s| %6s| %8d|\n", naluNum, priorityStr, typeStr, naluLen);
#endif
}

int main(int argc, char *argv[])
{
    int fileLen = 0;
    int naluLen = 0;
    int h264BitsPos = 0;

    unsigned char *h264Bits = NULL;
    unsigned char *naluData = NULL;
    
    FILE *fp = NULL;
    
    if (2 != argc)
    {
        printf("Usage: flvparse **.flv\n");

        return -1;
    }

    fp = fopen(argv[1], "rb");
    if (!fp)
    {
        printf("open file[%s] error!\n", argv[1]);

        return -1;
    }
    
    fseek(fp, 0, SEEK_END);
    
    fileLen = ftell(fp);
    
    fseek(fp, 0, SEEK_SET);
    
    h264Bits = (unsigned char*)malloc(fileLen);
    if (!h264Bits)
    {
        printf("maybe file is too long, or memery is not enough!\n");
        
        fclose(fp);
    
        return -1;
    }
    
    memset(h264Bits, 0x0, fileLen);
    
    if (fread(h264Bits, 1, fileLen, fp) < 0)
    {
        printf("read file data to h264Bits error!\n");
        
        fclose(fp);
        free(h264Bits);
        
        h264Bits = NULL;
        
        return -1;
    }
    
    fclose(fp);
    
    printf("-----+-------- NALU Table ------+\n");
    printf(" NUM |    IDC |  TYPE |   LEN   |\n");
    printf("-----+--------+-------+---------+\n");

    while (h264BitsPos < (fileLen-4))
    {
        if (FindStartCode3Bytes(&h264Bits[h264BitsPos]))
        {
            naluLen = GetNaluDataLen(h264BitsPos+3, fileLen, h264Bits);

            naluData = (unsigned char*)malloc(naluLen);
            if (naluData)
            {
                memset(naluData, 0x0, naluLen);
                
                memcpy(naluData, h264Bits+h264BitsPos+3, naluLen);
                
                ParseNaluData(naluLen, naluData);
                
                free(naluData);
                naluData = NULL;
            }
            
            h264BitsPos += (naluLen+3);
        }
        else if (FindStartCode4Bytes(&h264Bits[h264BitsPos]))
        {
            naluLen = GetNaluDataLen(h264BitsPos+4, fileLen, h264Bits);

            naluData = (unsigned char*)malloc(naluLen);
            if (naluData)
            {
                memset(naluData, 0x0, naluLen);

                memcpy(naluData, h264Bits+h264BitsPos+4, naluLen);

                ParseNaluData(naluLen, naluData);
                
                free(naluData);
                naluData = NULL;
            }
            
            h264BitsPos += (naluLen+4);
        }
        else
        {
            h264BitsPos++;
        }
    }

    return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值