H.264视频码流NALU分割

H264分层结构

H264的主要目标是为了有高的视频压缩比和良好的网络亲和性,为了达成这两个目标,H264的解决方案是将系统框架分为两个层面,分别是视频编码层面(VCL:Video Coding Layer)和网络抽象层面(NAL:Network Coding Layer)

VLC层

是对核心算法引擎、块、宏块及片的语法级别的定义,负责有效表示视频数据的内容,最终输出编码完的数据SODB;在VCL进⾏数据传输或存储之前,这些编码的VCL数据,被映射或封装进NAL单元。

NAL层

定义了片级以上的语法级别(如序列参数集和图像参数集 等),负责以网络所要求的恰当方式去格式化数据并提供头信息,以保证数据适合各种信道的传输和保存在存储介质上。NAL层将SODB打包成RBSP然后加上NAL头组成一个NALU单元,具体NAL单元的组成也会在后面详细描述。

H.264部分概念

视频由一系列连续图像组成,一张图像也称作一帧。但是H.264对图像做了压缩,所以帧也分为几种:

  • I帧:帧内编码帧 ,I 帧通常是每个 GOP(MPEG 所使⽤的⼀种视频压缩技术) 的第⼀个帧,经过适度地压缩,做为随机访问的参考点,可以当成图象。I帧可以看成是⼀个图像经过压缩后的产物。 ⾃身可以通过视频解压算法解压成⼀张单独的完整的图⽚。
  • P帧:前向预测编码帧,通过充分将低于图像序列中前⾯已编码帧的时间冗余信息来压缩传输数据量的编码图像,也叫预测帧。需要参考其前⾯的⼀个I frame 或者P frame来⽣成⼀张完整 的图⽚。
  • B帧:双向预测帧,既考虑与源图像序列前⾯已编码帧,也顾及源图像序列后⾯ 已编码帧之间的时间冗余信息来压缩传输数据量的编码图像, 也叫双向预测帧。要参考其前⼀个I或者P帧及其后⾯的⼀个P帧来⽣成⼀张完 整的图⽚。

一般是说,I帧的压缩率是7,p帧的压缩率是20,b帧的压缩率是50

IDR

IDR(Instantaneous Decoding Refresh,即时解码刷新) ⼀个序列的第⼀个图像叫做 IDR 图像(⽴即刷新图像),IDR 图像都是 I 帧图像,I和IDR帧都使⽤帧内预测。I帧不⽤参考任何帧,但是之后的P帧和B帧是有可能参考这个I帧之 前的帧的。IDR就不允许这样。

IDR帧核⼼作⽤是:是为了解码的重同步,当解码器解码到 IDR 图像时,⽴即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始⼀个新的序列。这样,如果前⼀ 个序列出现重⼤错误,在这⾥可以获得重新同步的机会。IDR图像之后的图像永远不会使⽤ IDR之前的图像的数据来解码。

GOP

是图像组的意思,表示编码的视频序列分成了一组一组有序的帧的集合进行编码。GOP第一帧永远是I帧。

H.264码流组织形式

H.264有两种封装形式:

  1. ⼀种是annex B模式,传统模式,有startcode。
  2. ⼀种是mp4模式,⼀般mp4 mkv都是mp4模式,没有startcode,SPS和PPS以及其它信息被封装在container中

这里我们只关注annex B模式。H.264的数据以Nal Unit的形式存在,裸码流保存的时候会在Nal Unit之前插入start code。

其中SPS、PPS、IDR和SLICE是NAL单元某一类型的数据。

起始码(start code)

H.264 的基本流由一系列NALU (Network Abstraction Layer Unit )组成,不同的NALU数据量各不相同。H.264 草案指出,当数据流是储存在介质上时,在每个NALU 前添加起始码:0x000001或0x00000001,用来指示一个NALU 的起始和终止位置。在这样的机制下,在码流中检测起始码,作为一个NALU得起始标识,当检测到下一个起始码时,当前NALU结束。

H.264 码流中每个帧的开头的3~4个字节是H.264 的start_code(起始码),0x00000001或0x000001。3字节的0x000001只有一种场合下使用,就是一个完整的帧被编为多个slice(片)的时候,从第二个slice开始,包含这些slice的NALU 使用3字节起始码。也就是说,如果NALU对应的slice为一帧的开始就用0x00000001,否则就用0x000001。

start code并不是NALU的一部分,只是用来分割NALU的一种手段。
 

NAL单元 - NALU

 H.264 最基本的数据存储单元。

NAL Unit 包含 [ Header (1 Byte) + Payload ] 

NALU Header

首先,NALU Header只占 1 个字节,即 8 位,其组成如下图所示:

  • forbidden_zero_bit
    禁止位,初始为0,当网络发现NAL单元有比特错误时可设置该比特为1,以便接收方纠错或丢掉该单元。
  • nal_ref_idc
    用于表示当前NALU的重要性,值越大,越重要。
    解码器在解码处理不过来的时候,可以丢掉重要性为 0 的 NALU。
  • nal_unit_type
    表示 NALU 数据的类型,有以下几种:

其中比较注意的应该是以下几个:

  • 1-4:I/P/B帧,如果 nal_ref_idc 为 0,则表示 I 帧,不为 0 则为 P/B 帧。
  • 5:IDR帧,I 帧的一种,告诉解码器,之前依赖的解码参数集合(接下来要出现的 SPS\PPS 等)可以被刷新了。
  • 6:SEI,英文全称 Supplemental Enhancement Information,翻译为“补充增强信息”,提供了向视频码流中加入额外信息的方法。
  • 7:SPS,全称 Sequence Paramater Set,翻译为“序列参数集”。SPS 中保存了一组编码视频序列(Coded Video Sequence)的全局参数。因此该类型保存的是和编码序列相关的参数。
  • 8: PPS,全称 Picture Paramater Set,翻译为“图像参数集”。该类型保存了整体图像相关的参数。
  • 9:AU 分隔符,AU 全称 Access Unit,它是一个或者多个 NALU 的集合,代表了一个完整的帧,有时候用于解码中的帧边界识别。

SPS 和 PPS 存储了编解码需要一些图像参数,SPS,PPS 需要在 I 帧前出现,不然解码器没法解码。而 SPS,PPS 出现的频率也跟不同应用场景有关,对于一个本地 h264 流,可能只要在第一个 I 帧前面出现一次就可以,但对于直播流,每个 I 帧前面都应该插入 sps 或 pps,因为直播时客户端进入的时间是不确定的。

分割NALU

基于以上的基本信息,我们就可以对NALU根据start code来做切割了,NALU内部信息先不做分析。

nal_unit_type不同值对应不同NALU类型。

雷神的代码,做了些修改,动态可扩展buffer。

执行结果如下:

源代码,h264_nalu.cpp

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

typedef enum {
    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,
} H264NaluType;

typedef enum {
    NALU_PRIORITY_DISPOSABLE = 0,
    NALU_PRIRITY_LOW = 1,
    NALU_PRIORITY_HIGH = 2,
    NALU_PRIORITY_HIGHEST = 3
} H264NaluPriority;

typedef struct {
    int statcode_length;   //! 4 for parameter sets and first slice in
                           //! picture, 3 for everything else (suggested)
    int forbidden_bit;     //! should be always FALSE
    int nal_reference_idc; //! NALU_PRIORITY_xxxx
    int nal_unit_type;     //! NALU_TYPE_xxxx

    size_t len;      //! Length of the NAL unit (Excluding the start code, which
                     //! does not belong to the NALU)
    size_t max_size; //! Nal Unit Buffer size
    unsigned char *buf; //! contains the first byte followed by the EBSP

    void AppendData(unsigned char *data_buf, size_t data_len) {
        if (max_size - len >= data_len) { // space is not enough
            memcpy(this->buf + this->len, data_buf, data_len);
            this->len += data_len;
        } else {
            this->max_size = this->max_size << 1;
            unsigned char *new_buf = new unsigned char[this->max_size];
            memcpy(new_buf, this->buf, this->len);
            delete[] this->buf;
            this->buf = new_buf;
            this->AppendData(data_buf, data_len);
        }
    }
} H264Nalu;

FILE *h264bitstream = NULL; //!< the bit stream file

static bool MatchStartCode3Bytes(unsigned char *Buf) {
    if (Buf[0] != 0 || Buf[1] != 0 || Buf[2] != 1)
        return false; // 0x000001?
    else
        return true;
}

static bool MatchStartCode4Bytes(unsigned char *Buf) {
    if (Buf[0] != 0 || Buf[1] != 0 || Buf[2] != 0 || Buf[3] != 1)
        return false; // 0x00000001?
    else
        return true;
}

int GetAnnexbNALU(H264Nalu *nalu) {
    nalu->len = 0; // reset immediately
    nalu->statcode_length = 0;

    size_t data_buf_len = 10000;
    unsigned char *data_buf = new unsigned char[data_buf_len];

    size_t read_len = fread(data_buf, 1, data_buf_len, h264bitstream);
    if (read_len < 3) {
        delete[] data_buf;
        return 0;
    }

    // data cursor
    unsigned char *pdata = data_buf;
    // pdata must be smaller than this
    unsigned char *pdata_end = data_buf + read_len;

    if (!MatchStartCode3Bytes(pdata)) {
        if (read_len < 4) {
            delete[] data_buf;
            return 0;
        }
        if (!MatchStartCode4Bytes(pdata)) {
            delete[] data_buf;
            return -1;
        } else {
            nalu->statcode_length = 4;
            pdata += 4;
        }
    } else {
        nalu->statcode_length = 3;
        pdata += 3;
    }

    int next_startcode_length = 0;

    ssize_t rewind = 0;

    while (pdata != pdata_end) {
        if (!MatchStartCode4Bytes(pdata)) {
            if (MatchStartCode3Bytes(pdata)) {
                next_startcode_length = 3;
                break;
            }
        } else {
            next_startcode_length = 4;
            break;
        }

        ++pdata;

        if (pdata == pdata_end) {
            if (feof(h264bitstream)) {
                break;
            }

            rewind = -3;
            if (0 != fseek(h264bitstream, rewind, SEEK_CUR)) {
                delete[] data_buf;
                printf("GetAnnexbNALU: Cannot fseek in the bit stream file");
                return -1;
            }

            pdata -= 3;

            ssize_t cur_data_len = pdata - data_buf;
            if (nalu->len == 0) { // first buf
                nalu->AppendData(data_buf + nalu->statcode_length,
                                 cur_data_len - nalu->statcode_length);
            } else {
                nalu->AppendData(data_buf, cur_data_len);
            }

            read_len = fread(data_buf, 1, data_buf_len, h264bitstream);
            if (read_len <= 0) {
                delete[] data_buf;
                return -1;
            }
            pdata_end = data_buf + read_len; // update
            pdata = data_buf;
        }
    }

    // Here, we have found another start code (and read length of startcode
    // bytes more than we should have.  Hence, go back in the file
    rewind = pdata - pdata_end;

    if (0 != fseek(h264bitstream, rewind, SEEK_CUR)) {
        delete[] data_buf;
        printf("GetAnnexbNALU: Cannot fseek in the bit stream file");
        return -1;
    }

    ssize_t cur_data_len = pdata - data_buf;
    if (nalu->len) {
        nalu->AppendData(data_buf, cur_data_len);
    } else {
        nalu->AppendData(data_buf + nalu->statcode_length,
                         cur_data_len - nalu->statcode_length);
    }
    delete[] data_buf;

    nalu->forbidden_bit = nalu->buf[0] & 0x80;     // 1 bit
    nalu->nal_reference_idc = nalu->buf[0] & 0x60; // 2 bit
    nalu->nal_unit_type = (nalu->buf[0]) & 0x1f;   // 5 bit

    return nalu->len + nalu->statcode_length;
}

/**
 * Analysis H.264 Bitstream
 * @param url    Location of input H.264 bitstream file.
 */
int simplest_h264_parser(char *url) {

    H264Nalu *n;
    int buffersize = 100000;

    // FILE *myout=fopen("output_log.txt","wb+");
    FILE *myout = stdout;

    h264bitstream = fopen(url, "rb+");
    if (h264bitstream == NULL) {
        printf("Open file error\n");
        return 0;
    }

    n = new H264Nalu;

    n->max_size = buffersize;
    n->buf = new unsigned char[buffersize];
    if (n->buf == NULL) {
        delete n;
        printf("AllocNALU: n->buf");
        return 0;
    }

    int data_offset = 0;
    int nal_num = 0;
    printf("-----+---------------- NALU Table ------+---------+\n");
    printf(" NUM |    POS    |   SCL  |    IDC |  TYPE |   LEN   |\n");
    printf("-----+-----------+--------+--------+-------+---------+\n");

    while (!feof(h264bitstream)) {
        int data_lenth;
        data_lenth = GetAnnexbNALU(n);
        if (data_lenth == 0)
            break;

        char type_str[20] = {0};
        switch (n->nal_unit_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;
        }
        char idc_str[20] = {0};
        switch (n->nal_reference_idc >> 5) {
        case NALU_PRIORITY_DISPOSABLE:
            sprintf(idc_str, "DISPOS");
            break;
        case NALU_PRIRITY_LOW:
            sprintf(idc_str, "LOW");
            break;
        case NALU_PRIORITY_HIGH:
            sprintf(idc_str, "HIGH");
            break;
        case NALU_PRIORITY_HIGHEST:
            sprintf(idc_str, "HIGHEST");
            break;
        }

        // fprintf(myout,"%5d| %8d| %7s| %6s|
        // %8d|\n",nal_num,data_offset,idc_str,type_str,n->len);
        fprintf(myout, "%5d| 0x%08X| %7d| %7s| %6s| %8ld|\n", nal_num,
                data_offset, n->statcode_length, idc_str, type_str, n->len);

        data_offset = data_offset + data_lenth;

        nal_num++;
    }

    // Free
    if (n) {
        if (n->buf) {
            delete[] n->buf;
            n->buf = NULL;
        }
        delete n;
    }
    return 0;
}

int main(int argc, char *argv[]) {
    char *url = argv[1];
    std::cout << "url: " << url << std::endl;

    simplest_h264_parser(url);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
h.264视频编解码源代码.rar 详细说明:h.264标准代码,用于视频编码!可以实现各种视频的编码和解码,可以在这个代码的基础上进行各种开发,比如算法的优化,转码技术,实现各种分辨了的转码-h.264 standard code, uses in the video frequency code! May realize each kind of video frequency code and the decoding, may carry on each kind of development in this code foundation, for instance the algorithm optimization, transfers the code technology, realizes each kind has distinguished extension code 文件列表: jm73 ....\JM ....\..\bin ....\..\...\decoder.cfg ....\..\...\encoder.cfg ....\..\...\lencod.exe ....\..\...\lencod.map ....\..\...\lencod.pdb ....\..\CHANGES.TXT ....\..\Changes_detail.txt ....\..\copyright.txt ....\..\disclaimer.txt ....\..\doc ....\..\...\coding_style.doc ....\..\...\doxygen.txt ....\..\...\h26l.css ....\..\...\ldecod.dox ....\..\...\lencod.dox ....\..\encoder.cfg ....\..\foreman_part_qcif.yuv ....\..\ldecod ....\..\......\inc ....\..\......\...\annexb.h ....\..\......\...\biaridecod.h ....\..\......\...\block.h ....\..\......\...\cabac.h ....\..\......\...\context_ini.h ....\..\......\...\contributors.h ....\..\......\...\ctx_tables.h ....\..\......\...\defines.h ....\..\......\...\elements.h ....\..\......\...\erc_api.h ....\..\......\...\erc_do.h ....\..\......\...\erc_globals.h ....\..\......\...\errorconcealment.h ....\..\......\...\fmo.h ....\..\......\...\global.h ....\..\......\...\header.h ....\..\......\...\image.h ....\..\......\...\leaky_bucket.h ....\..\......\...\macroblock.h ....\..\......\...\mbuffer.h ....\..\......\...\mb_access.h ....\..\......\...\memalloc.h ....\..\......\...\nalu.h ....\..\......\...\nalucommon.h ....\..\......\...\output.h ....\..\......\...\parset.h ....\..\......\...\parsetcommon.h ....\..\......\...\rtp.h ....\..\......\...\sei.h ....\..\......\...\vlc.h ....\..\......\Makefile ....\..\......\src ....\..\......\...\annexb.c ....\..\......\...\biaridecod.c ....\..\......\...\block.c ....\..\......\...\cabac.c ....\..\......\...\context_ini.c ....\..\......\...\erc_api.c ....\..\......\...\erc_do_i.c ....\..\......\...\erc_do_p.c ....\..\......\...\errorconcealment.c ....\..\......\...\filehandle.c ....\..\......\...\fmo.c ....\..\......\...\header.c ....\..\......\...\image.c ....\..\......\...\ldecod.c ....\..\......\...\leaky_bucket.c ....\..\......\...\loopFilter.c ....\..\......\...\macroblock.c ....\..\......\...\mbuffer.c ....\..\......\...\mb_access.c ....\..\......\...\memalloc.c ....\..\......\...\nal.c ....\..\......\...\nalu.c ....\..\......\...\nalucommon.c ....\..\......\...\nal_part.c ....\..\......\...\output.c ....\..\......\...\parset.c ....\..\......\...\parsetcommon.c ....\..\......\...\rtp.c ....\..\......\...\sei.c ....\..\......\...\vlc.c ....\..\ldecod.dsp ....\..\ldecod.dsw ... ...
H.264是一种视频压缩标准,它的解码原理是将压缩后的视频帧进行解码,恢复成原始的视频帧。具体的解码过程如下: 1. 读取H.264视频码流数据。 2. 解析码流数据,提取出视频帧的数据。H.264码流数据由一系列NALU(网络抽象层单元)组成,其中包含SPS(序列参数集)、PPS(图像参数集)和视频帧的数据。 3. 解码SPS和PPS,并初始化解码器。SPS和PPS包含了视频帧的一些参数,如分辨率、帧率、色彩空间等。 4. 解码视频帧数据。H.264视频帧数据由多个宏块(Macroblock)组成,每个宏块包含多个亚宏块(Sub-macroblock),亚宏块包含多个像素。解码器会对每个宏块进行解码,重建出原始的视频帧。 5. 输出解码后的视频帧。 下面是一个用C语言实现H.264视频解码的简单示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <stdbool.h> #define MAX_FRAME_SIZE 65536 // H.264 NALU类型 typedef enum { NALU_TYPE_UNDEFINED = 0, NALU_TYPE_NON_IDR = 1, NALU_TYPE_IDR = 5, NALU_TYPE_SEI = 6, NALU_TYPE_SPS = 7, NALU_TYPE_PPS = 8 } NaluType; // H.264 NALU结构体 typedef struct { uint8_t *data; // NALU数据指针 int length; // NALU数据长度 NaluType type; // NALU类型 } Nalu; // H.264解码器结构体 typedef struct { void *codec; // 解码器句柄 uint8_t *frameBuffer; // 解码后的视频帧数据 int frameSize; // 解码后的视频帧数据长度 } H264Decoder; // 初始化H.264解码器 bool H264Decoder_Init(H264Decoder *decoder) { // 初始化解码器句柄 decoder->codec = NULL; decoder->frameBuffer = NULL; decoder->frameSize = 0; // TODO: 实现解码器初始化 return true; } // 释放H.264解码器 void H264Decoder_Free(H264Decoder *decoder) { // 释放解码器句柄 if (decoder->codec) { // TODO: 实现解码器释放 } // 释放视频帧数据 if (decoder->frameBuffer) { free(decoder->frameBuffer); decoder->frameBuffer = NULL; decoder->frameSize = 0; } } // H.264视频帧解码 bool H264Decoder_Decode(H264Decoder *decoder, const uint8_t *data, int length) { // 读取NALU头部 uint8_t naluType = (data[0] & 0x1f); uint8_t naluRefIdc = (data[0] >> 5); // 如果NALU类型为SPS或PPS,直接忽略 if (naluType == NALU_TYPE_SPS || naluType == NALU_TYPE_PPS) { return true; } // 如果NALU类型为非IDR帧或IDR帧,解码视频帧数据 if (naluType == NALU_TYPE_NON_IDR || naluType == NALU_TYPE_IDR) { // TODO: 实现视频帧解码 // 将解码后的视频帧数据保存到解码器结构体中 if (decoder->frameBuffer) { free(decoder->frameBuffer); } decoder->frameBuffer = (uint8_t*)malloc(MAX_FRAME_SIZE); memcpy(decoder->frameBuffer, decodedFrameData, decodedFrameSize); decoder->frameSize = decodedFrameSize; return true; } return false; } // 主函数 int main() { // TODO: 实现H.264视频解码器的测试代码 return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值