ff_h2645_packet_split 代码注释

该代码段主要描述了`ff_h2645_packet_split`函数,用于将H264或H265原始数据流分割成NAL单元。它使用`GetByteContext`作为数据源,`H2645Packet`结构体来存储分析结果。NAL单元的数据存储在`rbsp`缓冲区中,通过动态分配的`H2645NAL`结构体数组管理。函数遍历输入数据,查找NAL头并提取NAL单元,处理跳过字节,并更新NAL数组。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ff_h2645_packet_split 代码注释
----------------------------------------
author: hjjdebug
date:  2023年 07月 20日 星期四 15:21:45 CST
----------------------------------------
int ff_h2645_packet_split(H2645Packet *pkt, const uint8_t *buf, int length,
                          void *logctx, int is_nalff, int nal_length_size,
                          enum AVCodecID codec_id, int small_padding, int use_ref)
包含多个数据结构的复杂结构及各对象相互交互的典型代码
目的: 对于buf中存储原始数据流,长度为len, 分割,分析出各个包到NAL(网络抽象层),但这里的目标用pkt来管理.
就是说:
数据源, buf,length
数据汇, pkt
其它的参数是不重要的,也容易理解.
logctx: 不重要,只是打印log时可以把打印信息打印的更详细一点,说明从哪里打印的.
is_nalff: 不重要, h264都为0
nal_length_size:
codec_id: 不重要,不是264就是265,决定如何分析nal_header
small_padding:不重要, 一般为0.
use_ref: 不重要,一般为0.

1. 数据源结构
type = struct GetByteContext {
    const uint8_t *buffer;        //数据指针
    const uint8_t *buffer_end;    //缓冲尾部
    const uint8_t *buffer_start;//缓冲开始
}
它的结构变量是bc, 可见bc 有一个start,一个end指针指示缓存,还有一个buffer指针指示当前位置.

2. 数据汇结构

所谓网络抽象单元,就是SPS,PPS,AUD,IDR,非IDR那些元素
type = struct H2645Packet {
    H2645NAL *nals;  // nals 数组.  就是由网络抽象单元组成的数组
    H2645RBSP rbsp; // raw bit stream, 实际存数的地方,它也只是一个头部,实际存数是动态分配的内存
    int nb_nals;  // 多重意义
    int nals_allocated; //分配了多少个nals
    unsigned int nal_buffer_size; //分配的nals内存大小
}
它的结构变量是pkt,
pkt 中的概念是H264NALs, 这可以通过2个方面来理解.
第1是H264NALs 数组, 其中nals 指向一大块内存用来储藏NALs, nal_buffer_size, 记录的是内存的大小
nals_allocated 代表已经分配的NALS单元个数. 显然nal_buffer_size = nals_allocated * sizeof(H2645NAL)
nb_nals. 个数(起索引的功能,或其它功能),我们要操作第几个NAL
这里我们补充一下H2645NAL 结构 ,它的大小是112字节, 这样我们就对这个储存分析结果的结构有了一个清晰的认识,
是一个动态分配的NALs 数组. 
type = struct H2645NAL {
    uint8_t *rbsp_buffer; //指向rbsp的指针,实际存数的地方
    int size;  //byte 大小,rbsp大小
    const uint8_t *data;
    int size_bits; //bit 大小
    int raw_size; //原始byte大小,含03byte, bsp大小
    const uint8_t *raw_data;
    GetBitContext gb; //运用这个对象,可以分析bit
    int type; //nal 头类型
    int temporal_id;
    int nuh_layer_id;
    int skipped_bytes; //跳过了多少字节
    int skipped_bytes_pos_size; //为跳过字节分配了多少内存
    int *skipped_bytes_pos; //记录在哪里遇到了跳过byte(03)
    int ref_idc;
}
我们看到,每一个nals 是一个大小112字节的结构, 但它包含若干指针,填充这些指针及type,size等属性值就是我们分析的任务.
各个指针及属性在代码中还会有说明

第2: rbsp 是干什么的? 我们copy一下它的结构定义
type = struct H2645RBSP {
    uint8_t *rbsp_buffer;         //内存分配开始指针
    AVBufferRef *rbsp_buffer_ref; //是否是可参考的
    int rbsp_buffer_alloc_size; //内存分配的大小
    int rbsp_buffer_size;        //实际的数据大小
}
这个定义看起来似曾相识了,首先有一个缓存rbsp_buffer,其分配的大小rbsp_buffer_alloc_size, 目前使用的大小rbsp_buffer_size
还有一个变量rbsp_buffer_ref , 是有关内存参考的变量,多数据引用时才会用到,目前可以不用考虑

可见数据源进来,被分析成nals数组, 每个nals 的数据指针,指向的是 rbsp 数据中的某个位置, rbsp 才是真正存储分析数据的地方
有了以上认识,下面可以分析代码了.
能真正理解其中含义,需要反复调试跟踪代码!

int ff_h2645_packet_split(H2645Packet *pkt, const uint8_t *buf, int length,
                          void *logctx, int is_nalff, int nal_length_size,
                          enum AVCodecID codec_id, int small_padding, int use_ref)
{
    GetByteContext bc;
    int consumed, ret = 0;
    int next_avc = is_nalff ? 0 : length; //由于is_nalff 为假,所以next_avc=length
    int64_t padding = small_padding ? 0 : MAX_MBPAIR_SIZE; //由于small_padding为假,所以padding 赋值256K

    bytestream2_init(&bc, buf, length); //初始化bc , 这是源, GetByteContext 结构如上述.
    alloc_rbsp_buffer(&pkt->rbsp, length + padding, use_ref); //为pkt->rbsp 分配内存,这是汇, pkt结构如上述. 分配的比原始数据还大padding的空间

    if (!pkt->rbsp.rbsp_buffer)
        return AVERROR(ENOMEM);

    pkt->rbsp.rbsp_buffer_size = 0; //继续初始化pkt的rbsp.rbsp_buffer_size=0 及nb_nal=0
    pkt->nb_nals = 0;
    while (bytestream2_get_bytes_left(&bc) >= 4) { //剩余的源数据比4字节长,我们就分析
        H2645NAL *nal;
        int extract_length = 0;
        int skip_trailing_zeros = 1;

        if (bytestream2_tell(&bc) == next_avc) { //剩余数据足够,可是碰到了下一个avc,需要处理一下,正常不会出这种情况
            int i = 0;
            extract_length = get_nalsize(nal_length_size,
                                         bc.buffer, bytestream2_get_bytes_left(&bc), &i, logctx);
            if (extract_length < 0)
                return extract_length;

            bytestream2_skip(&bc, nal_length_size);

            next_avc = bytestream2_tell(&bc) + extract_length;
        } else {
            int buf_index;

            if (bytestream2_tell(&bc) > next_avc) //源指针>next_avc ? 是则打印警告信息
                av_log(logctx, AV_LOG_WARNING, "Exceeded next NALFF position, re-syncing.\n");

            /* search start code */ //查找下一个开始代码00 00 01, 返回到它的距离(字节数)
            buf_index = find_next_start_code(bc.buffer, buf + next_avc);

            bytestream2_skip(&bc, buf_index); //源指针向前移动buf_index字节数

            if (!bytestream2_get_bytes_left(&bc)) { //源剩下的字节数为0? 返回或打印警告
                if (pkt->nb_nals > 0) {
                    // No more start codes: we discarded some irrelevant
                    // bytes at the end of the packet.
                    return 0;
                } else {
                    av_log(logctx, AV_LOG_ERROR, "No start code is found.\n");
                    return AVERROR_INVALIDDATA;
                }
            }
            //计算剩下的(待分析的)字节数
            extract_length = FFMIN(bytestream2_get_bytes_left(&bc), next_avc - bytestream2_tell(&bc));

            if (bytestream2_tell(&bc) >= next_avc) { // 检查,如果出现异常,跳过一定的字节数
                /* skip to the start of the next NAL */
                bytestream2_skip(&bc, next_avc - bytestream2_tell(&bc));
                continue;
            }
        }
        //准备增加一个NALS 单元,看看要不要分配内存
        if (pkt->nals_allocated < pkt->nb_nals + 1) {
            int new_size = pkt->nals_allocated + 1;    // 在原来分配的基础上加1
            void *tmp;

            if (new_size >= INT_MAX / sizeof(*pkt->nals))
                return AVERROR(ENOMEM);

            tmp = av_fast_realloc(pkt->nals, &pkt->nal_buffer_size, new_size * sizeof(*pkt->nals)); //重新分配内存
            if (!tmp)
                return AVERROR(ENOMEM);

            pkt->nals = tmp; //保留新指针
            memset(pkt->nals + pkt->nals_allocated, 0, sizeof(*pkt->nals)); //把最新的项清0

            nal = &pkt->nals[pkt->nb_nals]; //指向最新的项
            nal->skipped_bytes_pos_size = FFMIN(1024, extract_length/3+1); // 计算一个数值,1024或更小
            nal->skipped_bytes_pos = av_malloc_array(nal->skipped_bytes_pos_size, sizeof(*nal->skipped_bytes_pos));
            if (!nal->skipped_bytes_pos) //分配一块内存用来保留NALS块中跳过的字节(当遇到00 00 03 时,要跳过03 字节)就是BSP->RBSP
                return AVERROR(ENOMEM);

            pkt->nals_allocated = new_size; //更新pkg->nals_allocated
        }
        nal = &pkt->nals[pkt->nb_nals]; //指向新分配的单元
        //函数没有在继续展开,那样分析代码就太长了,需要了解每一个细节可以调试跟入, 但核心框架已在,可帮助理解.
        //把源数据bc.buffer,extract_length(这个数可以很大)抽取到目标nal, 存数在pkt->rbsp, 返回消耗的字节数,consumed<=extract_length
        consumed = ff_h2645_extract_rbsp(bc.buffer, extract_length, &pkt->rbsp, nal, small_padding);
        if (consumed < 0)
            return consumed;

        if (is_nalff && (extract_length != consumed) && extract_length) //出错警告
            av_log(logctx, AV_LOG_DEBUG,
                   "NALFF: Consumed only %d bytes instead of %d\n",
                   consumed, extract_length);

        bytestream2_skip(&bc, consumed); //源跳过消耗的字节数

        /* see commit 3566042a0 */
        if (bytestream2_get_bytes_left(&bc) >= 4 &&
            bytestream2_peek_be32(&bc) == 0x000001E0)
            skip_trailing_zeros = 0;  //skip_trailing_zeros 的判别

        nal->size_bits = get_bit_length(nal, skip_trailing_zeros); //获取nal单元的bit数, 前面抽取已经填充了bytes数及指针,这里计算bit数

        if (nal->size <= 0 || nal->size_bits <= 0)
            continue;

        ret = init_get_bits(&nal->gb, nal->data, nal->size_bits); //初始化nal-gb GetBitContext, 这样就能都NALS单元实行bit操作
        if (ret < 0)
            return ret;

        /* Reset type in case it contains a stale value from a previously parsed NAL */
        nal->type = 0;

        if (codec_id == AV_CODEC_ID_HEVC)
            ret = hevc_parse_nal_header(nal, logctx);
        else
            ret = h264_parse_nal_header(nal, logctx); //分析nal单元的头步,可以填充nal type 信息
        if (ret < 0) {
            av_log(logctx, AV_LOG_WARNING, "Invalid NAL unit %d, skipping.\n",
                   nal->type);
            continue;
        }

        pkt->nb_nals++;  //把pkt->nb_nals 增加一个
    }

    return 0;
}

有感于它解析数据时采用了NAL数组,nal单元的数据指针指向rbsp,用一个pkt变量把它们管理起来,这样实现分割数据的过程.

(gdb) p h->pkt.nals[0]
$42 = {
  rbsp_buffer = 0x7ffff56cc040 <incomplete sequence \360>,
  size = 3,        //数据大小(汇)为3,不包含头部00 00 01始, 例如此为AUD(access unit delimiter), 3数据为
(gdb) x/3bx pkt->nals[0].data
0x7ffff56cc040:    0x09    0xf0    0x00
  data = 0x7ffff56cc040 <incomplete sequence \360>, //数据指针,与rbsp_buffer 数值想等
  size_bits = 11, //bit数,去掉尾部0后的长度
  raw_size = 3, //原始数据(源)大小为3
  raw_data = 0x7ffff57e0004 <incomplete sequence \360>, //指向源数据指针
  gb = { //GetBitContext 结构, 已经读走了8个bits, 因而知道nal的type 为9
    buffer = 0x7ffff56cc040 <incomplete sequence \360>,
    buffer_end = 0x7ffff56cc042 "",
    index = 8,
    size_in_bits = 11,
    size_in_bits_plus8 = 19
  },
  type = 9,
  temporal_id = 0,
  nuh_layer_id = 0,
  skipped_bytes = 0, //无skip byte
  skipped_bytes_pos_size = 1024,
  skipped_bytes_pos = 0x555555575a00,
  ref_idc = 0
}
(gdb) p h->pkt.nals[1]
$43 = {
  rbsp_buffer = 0x7ffff56cc043 "gd",
  size = 27, //27个数据,其类型为7, PPS 数据, PPS数据分析在其它函数中进行!!
  data = 0x7ffff56cc043 "gd", //指针与rbsp_buffer 相同
  size_bits = 201, // bits 201(去掉bytes尾部0)
  raw_size = 28, //raw_size比size大1,说明有一个skip byte
  raw_data = 0x7ffff57e000a "gd",
  gb = { //读了8bits, 得到 nal type=7 SPS(sequence parameter set)
    buffer = 0x7ffff56cc043 "gd",
    buffer_end = 0x7ffff56cc05d "",
    index = 8,
    size_in_bits = 201,
    size_in_bits_plus8 = 209
  },
  type = 7,
  temporal_id = 0,
  nuh_layer_id = 0,
  skipped_bytes = 1, //记录skip bytes1
  skipped_bytes_pos_size = 1024,
  skipped_bytes_pos = 0x555555576b80,
  ref_idc = 3 //重要性3
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值