H264浅析

H264编码格式

H.264的功能分为两层,视频编码层VCL和网络提取层NAL。VCL数据是编码处理之后的输出,表示被压缩编码后的视频数据序列。当VCL数据进行传输或者存储时,这些编码的VCL数据,会被映射或封装到NAL单元中。

  • VCL,Video Coding Layer,视频编码层,编码后纯视频数据内容信息,没有任何冗余header信息;
  • NAL,Network Abstraction Layer,网络提取层,网络传输时,负责以恰当的方式进行打包和发送

简单来说,视频数据传输过程中,VCL数据会被打包到NAL基本单元中,传输NAL单元数据

在这里插入图片描述

NAL

H.264的主要目标:

  • 高的视频压缩比;
  • 良好的网络亲和性,即可适用于各种传输网络;

NALNetwork Abstraction Layer,定义如下:
:::info
The Network Abstraction Layer is a part of the H.264/ANC and HEVC video coding standards.(视频编码标准)
The main goal of the NAL is the provision(供应) of a “network-friendly” video representation addressing “conversational(双向的)” (video telephony: 视频电话) and “non conversational” (storage, broadcast, or streaming) applications.
NAL has achieved a significant improvement in application flexibility relative to prior video coding standards.
NAL的主要目标是提供network-friendly网络友好的视频表示,解决双向(视频电话)和单向(非视频电话-存储、广播或视频播放)应用。
NAL相对于之前的视频编码标准在应用灵活性方面取得了显着的提升。
:::
因此,NAL就是为了打包VCL数据,在网络上,进行更好的传输。

NAL units

编码后的视频数据被打包到NAL units中,每个NAL units就是一个包含整数字节的数据包。每一个NAL unit的第一个字节是header,其中包含数据类型信息。NAL unit的结构定义是面向packetbitstream设计的,具有良好的网络传输性能。

在实际的H264数据帧中,往往帧与帧(NAL unit)之间前面带有00 00 00 01 00 00 01分隔符,称为StartCode,是一个NALU的开始,因此,一个原始的NALU结构包含三部分:

  • StartCode

  • NALU Header

  • NALU Payload(字节序列负荷,RBSP,Raw Byte Sequence Payload)
    在这里插入图片描述

    NALU Header
    NALU Header,就是紧跟着StartCode后面的一个字节,其每个bit的含义如下所示:

    在这里插入图片描述

  • F:forbiden,禁止位,占用NAL头的第一个位,当禁止位值为1是表示语法错误;

  • NRI:参考级别,占用NAL头的第二和三位,值越大,表示该NAL单元越重要;

  • Type:NAL单元类型,占用NAL头的第四位到第八位,常见NAL单元类型如下:
    在这里插入图片描述

    NAL unit可以分类为 VCLnon-VCL NAL units。其中VCL NAL unit中包含视频图像的数据帧,non-VCL NAL unit中包含的是附加信息,例如SPSPPS参数集信息(important header data that can apply to a large number of VCL NAL units)SEI补充增强信息(timing information and other supplemental data that may enhance usability of the decoded video signal but are not necessary for decoding the values of the samples in the video pictures)

  • IDR帧。IDR帧就是I帧,但是I帧不一定是IDR帧。在一个完整的视频流单元中第一个图像帧是IDR帧,IDR帧是强制刷新帧,在解码过程中,当出现了IDR帧时,要更新sps、pps,原因是防止前面I帧错误,导致sps,pps参考I帧导致无法纠正。

  • SEI,Supplemental Enhancement Information,提供向视频流中加入额外信息的方法;

  • SPS,Sequence Parameter Set,SPS中保存了一组编码视频序列(Coded Video Sequence)的全局参数。因此该类型保存的是和编码序列相关的参数。

  • PPS,Picture Paramater Set,该类型保存了整体图像相关的参数。

  • AU分隔符,AU全称Access Unit,它是一个或者多个NALU的集合,代表了一个完整的帧。

解析H264数据

根据上述H264的编码格式分析,可以根据startcode,区分每个NAL unit,然后分析每个NAL unit的第一个字节,得到NAL unit的重要程度、NAL Type和长度信息。具体代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef enum {
    
    
    NALU_TYPE_SLICE = 1,    // 不分区,非IDR图像
    NALU_TYPE_DPA,          // 片分区A
    NALU_TYPE_DPB,          // 片分区B
    NALU_TYPE_DPC,          // 片分区C
    NALU_TYPE_IDR,          // IDR图像中的片
    NALU_TYPE_SEI,         // 补充增强信息单元
    NALU_TYPE_SPS,          // 序列参数集
    NALU_TYPE_PPS,          // 图像参数集
    NALU_TYPE_AUD,          // 分界符
    NALU_TYPE_EOSEQ,        // 序列结束符
    NALU_TYPE_EOSTREAM,     // 码流结束符
    NALU_TYPE_FILL          // 填充
} NaluType;

typedef enum {
    
    
    NALU_PRIORITY_DISPOSABLE = 0,
    NALU_PRIORITY_LOW,
    NALU_PRIORITY_HIGH,
    NALU_PRIORITY_HIGHEST
} NaluPriority;


typedef struct {
    
    
    int pos;        // NALU对应起始码,在文件中的位置
    int codelen;    // 起始码大小
    int header;     // (0) + (priority)2bits + (types)5bits
} Nalu_t;

void fill_typestr(char *type_str, int type) {
    
    
    switch(type) {
    
    
        case NALU_TYPE_SLICE:sprintf(type_str,"SLICE");break;
        case NALU_TYPE_DPA:sprintf(type_str,"DPA");break;
        case NALU_TYPE_DPB:sprintf(type_str,"DPB");break;
        case NALU_TYPE_DPC:sprintf(type_str,"DPC");break;
        case NALU_TYPE_IDR:sprintf(type_str,"IDR");break;
        case NALU_TYPE_SEI:sprintf(type_str,"SEI");break;
        case NALU_TYPE_SPS:sprintf(type_str,"SPS");break;
        case NALU_TYPE_PPS:sprintf(type_str,"PPS");break;
        case NALU_TYPE_AUD:sprintf(type_str,"AUD");break;
        case NALU_TYPE_EOSEQ:sprintf(type_str,"EOSEQ");break;
        case NALU_TYPE_EOSTREAM:sprintf(type_str,"EOSTREAM");break;
        case NALU_TYPE_FILL:sprintf(type_str,"FILL");break;
    }
}

void fill_priostr(char *priostr, int prio) {
    
    
    switch(prio) {
    
    
        case NALU_PRIORITY_DISPOSABLE:sprintf(priostr,"DISPOS");break;
        case NALU_PRIORITY_LOW:sprintf(priostr,"LOW");break;
        case NALU_PRIORITY_HIGH:sprintf(priostr,"HIGH");break;
        case NALU_PRIORITY_HIGHEST:sprintf(priostr,"HIGHEST");break;
    }
}


int startcode_len(unsigned char *buf) {
    
    
    if (buf[0] == 0 && buf[1] == 0) {
    
    
        if (buf[2] == 1) return 3;
        else if(buf[3] == 1) return 4;
    }    
    return -1;
}

void h264_analyse(const char *file) {
    
    
    FILE *fp = fopen(file, "r");
    unsigned char *buf = malloc(30000000); // 根据视频大小设
    Nalu_t *nalu = malloc(sizeof(*nalu));
    memset(nalu, 0, sizeof(Nalu_t));
    
    // 获取文件大小
    fseek(fp, 0, SEEK_END);
    int filesize = ftell(fp);
    rewind(fp);

    // 先读部分数据,解析第一个NALU
    fread(buf, 1, 100, fp);
    nalu->pos = 0;
    nalu->codelen = startcode_len(&buf[nalu->pos]);

    printf("| NUM | CODE |   PRIO |   TYPE  | LEN |\n");

    int cnt = 0;                // nalu个数
    int left = 0, right = 100;  // left表示当前搜索位置,right表示当前文件偏移
    int found, len;             
    while (1) {
    
    
        int headidx = nalu->pos + nalu->codelen;  // NALU header
        nalu->header = buf[headidx];
        // int nForbiddenBit = (nFrameType >> 7) & 0x1;//第1位禁止位,值为1表示语法出错
        // int nReference_idc = (nFrameType >> 5) & 0x03;//第2~3位为参考级别
        // int nType = nFrameType & 0x1f;//第4~8为是nal单元类型
        int type = nalu->header & 0x1F;             // NALU Type
        char type_str[10] = {
    
    0};
        fill_typestr(type_str, type);

        // int prio = (nalu->header & 0x60)>>5;
        int prio = (nalu->header >> 5) & 0x03;
        char prio_str[10] = {
    
    0};
        fill_priostr(prio_str, prio);
        
        // 找到下一个起始码
        found = 0; len = 0;
        left += nalu->codelen + 1;  // 跳过startcode和header
        while (!found) {
    
    
            if (left < right) {
    
    
                if (left + 4 >= filesize) // 防止在函数内数组越界
                    goto clean;
                len = startcode_len(&buf[left++]);
                if (len > 0) found = 1;
            } else {
    
    
                if (right + 100 < filesize) {
    
    
                    fread(&buf[right], 1, 100, fp);
                    right += 100;
                } else {
    
        // 剩余的数据不到100B字节
                    fread(&buf[right], 1, filesize - right, fp);
                    right = filesize;
                }
            }
        }
        int nalulen = left - nalu->pos - nalu->codelen;  // NALU大小,不包括起始码

        printf("|%5d|%6d|%8s|%9s|%5d|\n", cnt, nalu->codelen, prio_str, type_str, nalulen);
        cnt++;
        nalu->codelen = len;
        nalu->pos = left - 1;
    }

clean:
    free(nalu);
    free(buf);
    fclose(fp);
}

int main(int argc, char const* argv[])
{
    
    
    h264_analyse("/opt/nvidia/deepstream/deepstream-6.1/samples/streams/sample_720p.h264"); 
    return 0;
}

文件解析每个NAL unit的信息如下:

| NUM | CODE |   PRIO |   TYPE  | LEN |
|    0|     4|  DISPOS|      AUD|    3|
|    1|     4| HIGHEST|      SPS|   26|
|    2|     4| HIGHEST|      PPS|    6|
|    3|     4|  DISPOS|      SEI|  704|
|    4|     4| HIGHEST|      IDR|47583|
|    5|     4|  DISPOS|      AUD|    3|
|    6|     4|    HIGH|    SLICE|25063|
|    7|     4|  DISPOS|      AUD|    3|
|    8|     4|  DISPOS|    SLICE| 8783|
|    9|     4|  DISPOS|      AUD|    3|
|   10|     4|    HIGH|    SLICE| 3004|
|   11|     4|     LOW|         |23386|
|   12|     4|  DISPOS|      AUD|    3|
|   13|     4|  DISPOS|    SLICE| 7633|
...

H264分析工具Elecard StreamEye Tools

The Elecard StreamEye Tools is a set of powerful tools designed for professionals and prosumers in the video compression field. Elecard StreamEye Tools enables the user to perform an effective in-depth analysis of video sequences.
Elecard StreamEye Tools 是一套功能强大的工具,专为视频压缩领域的专业人士和专业消费者而设计。 Elecard StreamEye Tools帮助用户能够对视频序列进行有效的深入分析。

链接:https://pan.baidu.com/s/1MUy6rs3MuDW6PUGkhDHy6g?pwd=of07 提取码:of07

Elecard StreamEye Tools工具包中包含如下五个应用程序:
· Elecard StreamEye
· Elecard YUV Viewer
· Elecard Video QuEst (Quality Estimator)
· Elecard Stream Analyzer
· Elecard Buffer Analyzer

在这里插入图片描述

Elecard Stream Analyzer

Elecard Stream Analyzer is a powerful tool designed for syntax analysis(语法分析) of encoded media streams(编码的媒体数据) and presentation of the analysis log in a human readable form. Stream Analyzer operates with MPEG-1 Video/Audio, MPEG-2 Video/Audio, AAC, AC-3 and AVC/H.264 files(支持的数据类型).

  1. H264NAL unit分析
    在这里插入图片描述


通过与上面的代码分析的结果对比,可知上面的分析代码是正确的。

  1. H264文件的HEX查看器
    在这里插入图片描述

Elecard StreamEye

The StreamEye provides the user with a visual representation of the encoded video features(编码视频特征的可视化表示) and a stream structure analysis(媒体流的结构分析) of MPEG-1/2/4 or AVC/Н.264 Video Elementary Streams (VES), MPEG-1 System Streams (SS), MPEG-2 Program Streams (PS) and MPEG-2 Transport Streams (TS).

在这里插入图片描述

部分功能总览:

在这里插入图片描述

  1. bitrate分析

选择bitrate的状态,显示相应的bitrate曲线

  1. 颜色空间
    在这里插入图片描述

  2. 文件信息
    在这里插入图片描述

  3. 运动向量
    在这里插入图片描述

    StreamEye工具的功能十分强大,可以根据自己的需求进行相应的分析。

猜你喜欢

转载自blog.csdn.net/hello_dear_you/article/details/128599328