FFMPEG 之 AVPacket

前言:

    本文介绍FFMPEG的AVPacket相关内容:

  1. AVPacket在FFMPEG中用来保存Demux分离出来的压缩的ES流信息。

  2. AVBufferRef表示AVBuffer的引用,和AVBuffer的关系是多个AVBufferRef对一个AVBuffer。

  3. AVBuffer 实现引用计数,其参数refcount表示正在使用这个AVBuffer的AVBufferRef个数。

使用AVPacket时,通过调用av_buffer_alloc获得表示AVBufferRef的buf和AVBuffer,数据操作可在获得buf后读写pkt->data结构体实现,本文FFMPEG使用版本ffmpeg-4.2.2。

AVPacket,AVBufferRef,AVBuffer关系总结

关系图

引用层次关系可以简述为:AVPacket ->AVBufferRef->AVBuffer->refcount

总结

API解读

AVPacket *av_packet_alloc(void)
{
    AVPacket *pkt = av_mallocz(sizeof(AVPacket));
    if (!pkt)
        return pkt;
    /* 
     *1. 释放side_data,side_data类似extradata,比如保存ADTS的采样率,声道等信息。
         在FFMPEG中用AVPacketSideDataType这个枚举类型来描述,Android的MetaData就是一种side_data。
         封装在MP4,FLV,MKV中的H264,H265的SPS,PPS信息也存在于extradata中,push给解码器的时候,需要事先把这些Header加上。
         此处av_freep(&pkt->side_data)时,用free或者_aligned_free函数释放了0地址。
          GCC手册中描述,free的参数可以是0,也就是说free 0地址多次是可以的,什么动作也不会发生。但如果free的参数非calloc,malloc,realloc,则结果未定义。
    *2. 如果AVBufferRef存在,则消灭他。
    *3. 给AVPacket的成员赋初值
    */
    av_packet_unref(pkt);

    return pkt;
}

/* 带了size参数意味着可以生成AVBufferRef和AVBuffer */
int av_new_packet(AVPacket *pkt, int size)
{
    AVBufferRef *buf = NULL;
    /* 
    * 1. AVBufferRef为空时,流程同av_buffer_alloc,先创建data空间,再创建AVBuffer,最后创建AVBufferRef并返回。此时设置BUFFER_FLAG_REALLOCATABLE标记。
    * 2. AVBufferRef不空时,调用av_buffer_realloc:
        a.AVBuffer的flag不带有BUFFER_FLAG_REALLOCATABLE标记,或不能写(READONLY或者引用计数不是1),或者data指向与AVBufferRef不一致,则重新分配AVBufferRef并释放之前的AVBufferRef。
        b.否则不用重新分配AVBufferRef,直接av_realloc(buf->buffer->data, size)。
    */
    int ret = packet_alloc(&buf, size);
    if (ret < 0)
        return ret;

    av_init_packet(pkt);
    pkt->buf      = buf;
    pkt->data     = buf->data;
    pkt->size     = size;

    return 0;
}

/* 两个packet共享AVBuffer,但side data不共享 */
int av_packet_ref(AVPacket *dst, const AVPacket *src)
{
    int ret;
    /* 复制src的内容到dst,整这一出是为了深拷贝。
    * 把src中side data全部拷贝到dst中去,而不是仅仅传个指针到dst。
    */
    ret = av_packet_copy_props(dst, src);
    if (ret < 0)
        return ret;
    if (!src->buf) {
       /* 根据情况使用现存的AVBufferRef, 亦或使用新分配的AVBufferRef,此时无AVBufferRef,就会生成新的AVBufferRef */
        ret = packet_alloc(&dst->buf, src->size);
        ……
    } else {

         /* src指向的数据引用计数加1。为dst产生一个新的AVBufferRef,然后AVBufferRef指向与src同一个AVBuffer。
          * 注意dst和src的 AVBufferRef是不相同的,但AVBuffer是相同的。真正计数是在 AVBuffer中进行
          */
        dst->buf = av_buffer_ref(src->buf);
        if (!dst->buf) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }

      /* src指向的数据使用了引用计数, dst->data数据此时不需要复制,只需要把数据指针指向src->data即可 */
        dst->data = src->data;
    }
    dst->size = src->size;
    return 0;
fail:
    av_packet_free_side_data(dst);
    return ret;
}

void av_packet_unref(AVPacket *pkt)
{
    /* 如果side data 存在,就释放掉。*/
    av_packet_free_side_data(pkt);
    /* 如果AVBufferRef存在,就将它释放掉,并且,将它指向的AVBuffer的引用计数减1。如果减1之后,AVBuffer无人引用,那么需要将AVBuffer也释放掉*/
    av_buffer_unref(&pkt->buf);

    /* 初始化 */
    av_init_packet(pkt);
    pkt->data = NULL;
    pkt->size = 0;
}

/* 实际上是使用引用计数功能将新生成的packet指向同一个AVBuffer */

AVPacket *av_packet_clone(const AVPacket *src)
{
    AVPacket *ret = av_packet_alloc();

    if (!ret)
        return ret;

    /* 将ret的data指向src的data,并且使用AVBuffer引用计数。*/  
    if (av_packet_ref(ret, src))
       av_packet_free(&ret);
    /* AVBufferRef分配不成功可能返回有问题的pkt */
    return ret;
}
void av_packet_move_ref(AVPacket *dst, AVPacket *src)
{

    /* 将src的全部信息放到dst中,并且将 src中信息擦除 */
    *dst = *src;
    av_init_packet(src);
    src->data = NULL;
    src->size = 0;
}

/* 让pkt具有计数功能 */
int av_packet_make_refcounted(AVPacket *pkt)
{
    int ret;
    /* 如果pkt已经使用引用计数,则返回 */
    if (pkt->buf)
        return 0;

    ret = packet_alloc(&pkt->buf, pkt->size);
    if (ret < 0)
        return ret;
    av_assert1(!pkt->size || pkt->data);
    if (pkt->size) /* 将原始数据复制到新生成的data buffer中 */
        memcpy(pkt->buf->data, pkt->data, pkt->size);

   /* data指向新位置,如果需要释放data,此处未进行释放,注意风险 */ 
   pkt->data = pkt->buf->data;

    return 0;
}

/* 如果pkt指向的data部分不可以写,那么就新生成一个AVBufferRef和AVBuffer,复制之前data指向的数据,让新生成的data默认可写。 */
int av_packet_make_writable(AVPacket *pkt)
{
    AVBufferRef *buf = NULL;
    int ret;

    if (pkt->buf && av_buffer_is_writable(pkt->buf))
        return 0;

    ret = packet_alloc(&buf, pkt->size);
    if (ret < 0)
        return ret;
    av_assert1(!pkt->size || pkt->data);
    if (pkt->size)
        memcpy(buf->data, pkt->data, pkt->size);

    av_buffer_unref(&pkt->buf);
    pkt->buf  = buf;
    pkt->data = buf->data;

    return 0;
}

int av_get_packet(AVIOContext *s, AVPacket *pkt, int size)
{
    av_init_packet(pkt);
    pkt->data = NULL;
    pkt->size = 0;
    pkt->pos  = avio_tell(s);

    /* 1. 用av_grow_packet来给pkt分配AVBufferRef,如果AVBufferRef存在,调用av_buffer_realloc;否则调用av_buffer_alloc
    *  2. 用AVIO从流中读size个数据到pkt指向的data中,pkt有数据存在时,从有效data的末尾开始写数据。
    */
    return append_packet_chunked(s, pkt, size);
}


/* 增加av pkt的size,增加grow_by字节,注意返回的pkt一定是使用AVBufferRef引用计数的 */
int av_grow_packet(AVPacket *pkt, int grow_by)
{
    /* 检查增加量,与pkt->size+grow_by+AV_INPUT_BUFFER_PADDING_SIZE这种写法比,可以避免溢出。 */
    if ((unsigned)grow_by >
        INT_MAX - (pkt->size + AV_INPUT_BUFFER_PADDING_SIZE))
        return AVERROR(ENOMEM);
    /* 整块新的buf size */
    new_size = pkt->size + grow_by + AV_INPUT_BUFFER_PADDING_SIZE;
    /* 如果pkt使用AVBufferRef */
    if (pkt->buf) {
        size_t data_offset;
        uint8_t *old_data = pkt->data;
        if (pkt->data == NULL) {
            data_offset = 0;
            pkt->data = pkt->buf->data;
        } else {
            /* 已经使用的数据长度,目前也只有在此函数中发现,pkt->data与pkt->buf->data指向可能不同
             * 可以看出,完整的pkt data和size应该是要从AVBufferRef中拿
             * pkt结构体中指向的data和size是当前可以使用的。像av_shrink_packet这个函数,也是减少了pkt的size。
             */
            data_offset = pkt->data - pkt->buf->data;
            if (data_offset > INT_MAX - new_size)
                return AVERROR(ENOMEM);
        }

        if (new_size + data_offset > pkt->buf->size) {
            int ret = av_buffer_realloc(&pkt->buf, new_size + data_offset);
            if (ret < 0) {
                pkt->data = old_data;
                return ret;
            }
            /* pkt的data仍然需要比AVBufferRef的data超前data_offset个。*/
            pkt->data = pkt->buf->data + data_offset;
        }
    } else {
        /* 没有使用AVBufferRef,则创建一个,所以,av_grow_packet成功之后,pkt的data一定使用了引用计数 */
        pkt->buf = av_buffer_alloc(new_size);
        if (!pkt->buf)
            return AVERROR(ENOMEM);
        if (pkt->size > 0)
            memcpy(pkt->buf->data, pkt->data, pkt->size);
        pkt->data = pkt->buf->data;
    }
    pkt->size += grow_by;
    memset(pkt->data + pkt->size, 0, AV_INPUT_BUFFER_PADDING_SIZE);

    return 0;
}

 AVPacket中的data不总是等于其成员buf中data成员

        先说明一下情况,如果数据包可能包含多个Frame帧,每个Frame帧都有自己的数据和大小,那么AVPacket中的data就可能和成员buf中data成员不一致。总的说来,pkt.buf->data指向整个数据包,而pkt->data指向的是当前正在使用的数据部分。
        A. av_grow_packet的时候,pkt->data = pkt->buf->data + data_offset; 如果data_offset不为0,这个时候,二者不一致。
        B. mkv demux中,pkt->buf->data指向block,pkt->data指向lace.

static int matroska_parse_frame(...)
{
    MatroskaTrackEncoding *encodings = track->encodings.elem;
    uint8_t *pkt_data = data;
......处理pkt_data
    av_init_packet(pkt);
    if (pkt_data != data)
        pkt->buf = av_buffer_create(pkt_data, pkt_size + AV_INPUT_BUFFER_PADDING_SIZE,
                                    NULL, NULL, 0);
    else
        pkt->buf = av_buffer_ref(buf);

    if (!pkt->buf) {
        res = AVERROR(ENOMEM);
        goto fail;
    }

    pkt->data         = pkt_data;
    pkt->size         = pkt_size;
    pkt->flags        = is_keyframe;
    pkt->stream_index = st->index;

......
    res = ff_packet_list_put(&matroska->queue, &matroska->queue_end, pkt, 0);
}

static int matroska_parse_block(AVBufferRef *buf, uint8_t *data...)
{
    block_time = sign_extend(AV_RB16(data), 16);
    data      += 2;
    res = matroska_parse_laces(matroska, &data, &size, (flags & 0x06) >> 1,
                               &lace_size, &laces);
......

    for (n = 0; n < laces; n++) {
......
        res = matroska_parse_frame(matroska, track, st, buf, data, lace_size[n],
                                   timecode, lace_duration, pos,
                                   !n ? is_keyframe : 0,
                                   additional, additional_id, additional_size,
                                   discard_padding);
......
        /*  buf不动,data继续向前推进 */
        data += lace_size[n];
        size -= lace_size[n];
    }
}

static int matroska_parse_cluster(MatroskaDemuxContext *matroska)
{
    MatroskaBlock     *block = &cluster->block;
......
    res = matroska_parse_block(matroska, block->bin.buf, block->bin.data,
                               block->bin.size, block->bin.pos,
                               cluster->timecode, block->duration,
                               is_keyframe, additional, block->additional_id,
                               block->additional.size, cluster->pos,
                               block->discard_padding);
......
}

说明

1. 关于side data
    ffmpeg中mpegts.c的side data比较特别,调用顺序是:
handle_packets->handle_packet->mpegts_push_data->new_pes_packet->av_packet_new_side_data->av_packet_add_side_data
也就是说每一个pes包都要生成side data,这个side data用于保存当前pkt的ID信息:
    sd = av_packet_new_side_data(pkt, AV_PKT_DATA_MPEGTS_STREAM_ID, 1);
    if (!sd)
        return AVERROR(ENOMEM);
    *sd = pes->stream_id;
 ts demux里面填入了当前pes的stream_id,比如0xe0这种。

2. side data不单单可以是codec data,还可以是AVPacketSideDataType的各种类型,比如DRM系统需要的初始化信息,存储IV信息等等。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值