剖析AVPacket

AVPacket

AVPacket 中存储的是经过编码的压缩数据。在解码中,AVPacket 由解复用器输出到解码器;在编码中,AVPacket 由编码器输出到复用器。

对于视频而言,一个 AVPacket 通常只包含一个压缩视频帧。而对于音频而言,一个 AVPacket 可能包含多个完整的音频压缩帧。AVPacket 也可以不包含压缩编码数据,而只包含 SideData,这种包可以称为空 Packet。例如,编码结束后只需要更新一些参数时就可以发空 Packet。

AVPacket 对象可以在栈上分配,注意此处指的是AVPacket对象本身。而 AVPacket 中包含的数据缓冲区是通过av_malloc() 在堆上分配的。

 _______              ______________
|       |            |              |
| input |  demuxer   | encoded data |   decoder
| file  | ---------> | packets      | -----+
|_______|            |______________|      |
                                           v
                                       _________
                                      |         |
                                      | decoded |
                                      | frames  |
                                      |_________|
 ________             ______________       |
|        |           |              |      |
| output | <-------- | encoded data | <----+
| file   |   muxer   | packets      |   encoder
|________|           |______________|
    

剖析重要成员

buf、data、size

buf为数据缓冲区引用,也可叫引用计数缓冲区。对另一字段 uint8_t *data 指向的内存区域提供引用计数等管理机制。AVBufferRef 对数据缓冲区提供了管理机制,用户不应直接访问数据缓冲区。

  • 如果 buf == NULL:则data指向的数据缓冲区不使用引用计数机制,av_packet_ref 执行数据缓冲区的拷贝,而非仅仅增加缓冲区引用计数;
  • 如果 buf != NULL:则data指向的数据缓冲区使用引用计数机制,av_packet_ref 将不拷贝缓冲区,而仅增加缓冲区引用计数。av_packet_unref 将数据缓冲区引用计数减 1,当缓冲区引用计数为 0 时,缓冲区内存被ffmpeg回收;

如果 pkt.buf != NULL,则有 pkt.data == pkt.buf->data == pkt.buf->buffer.data

typedef struct AVPacket {
    /**
     * 对存储包数据的引用计数缓冲器的引用
     * 可能为NULL,则该AVPacket包非引用计数模式
     */
    AVBufferRef *buf;
    /**
     * 数据缓冲区地址与大小
     * 音视频编码压缩数据存储于此片内存区域
     */
    uint8_t *data;
    int size;
} AVPacket;

side_data、side_data_elems

AVPacket->side_data 是AVPacket携带的 side数据数组,AVPacket->side_data_elems 是数组的长度

av_packet_new_side_data()av_packet_add_side_data() 函数都提供了向AVPacket添加指定类型side data的能力,只是参数略有差异,每次都会把新side data添加到数组的尾部。

typedef struct AVPacket {
    // 辅助数据组,包含多种类型的辅助信息
    AVPacketSideData *side_data;
    // 数据组数量
    int side_data_elems;
}

pts、dts

以AVStream->time_base为单位的时间戳

typedef struct AVPacket {
    /**
     * 以AVStream->time_base单位表示的时间戳
     * pts必须大于或等于dts
     */
    int64_t pts;
    /**
     * 以AVStream->time_base为单位的解压缩时间戳,即数据包解压缩的时间
     */
    int64_t dts;                         
}

stream_index、flags

当前包(Packet)所有流(Stream)的索引(index)

typedef struct AVPacket {
    // 当前包所有流的索引
    int stream_index;
    // packet标志位,比如是否关键帧等
    int flags;
}

duration、pos

typedef struct AVPacket {
    // 当前包解码后的帧播放持续的时长,单位timebase,值等于下一帧pts减当前帧pts
    int64_t duration;
    // 当前包在流中的位置,单位字节
    int64_t pos;
}

缓存引用机制

typedef struct AVBufferRef {
    AVBuffer *buffer;
    uint8_t *data;
    int size;
} AVBufferRef;

struct AVBuffer {
    uint8_t *data; /**< data described by this buffer */
    int size; /**< size of data in bytes */
    atomic_uint refcount;
    void (*free)(void *opaque, uint8_t *data);
    void *opaque;
    int flags;
};

AVBufferRef引用AVBuffer,每增加一个指向同一个AVBuffer的AVBufferRef,AVBuffer中的引用计数就加1;相反,每减少一个指向AVBuffer的AVBufferRef,AVBuffer中的引用计数就减1,当引用计数等于0时,就会释放AVBuffer.data以及AVBuffer本身,类似于C++的智能指针。

av_packet_ref

av_packet_ref() 作了处理如下:

  • 如果 src->buf == NULL,则将 src 缓冲区拷贝到新创建的 dst 缓冲区,注意 src 缓冲区不支持引用计数,但新建的 dst 缓冲区是支持引用计数的,因为 dst->buf != NULL
  • 如果 src->buf != NULL,则 dst 与 src 共用缓冲区,调用 av_buffer_ref() 增加缓冲区引用计数即可
int av_packet_ref(AVPacket *dst, const AVPacket *src)
{
    // ....
    if (!src->buf) {
        // 为buf分配空间
        ret = packet_alloc(&dst->buf, src->size);
        if (ret < 0) goto fail;
        av_assert1(!src->size || src->data);
        // 数据从data拷贝到buf->data
        if (src->size)
            memcpy(dst->buf->data, src->data, src->size);
    	// data指针也指向buf->data
        dst->data = dst->buf->data;
    } else {
        // 引用计数+1
        dst->buf = av_buffer_ref(src->buf);
        if (!dst->buf) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        // data共用一个缓冲区
        dst->data = src->data;
    }
    dst->size = src->size;
    // ....
    return ret;
}

av_packet_unref

注销 AVPacket *pkt 对象,并调用 av_buffer_unref(&pkt->buf) 将缓冲区引用计数减 1,将缓冲区引用计数减 1后,若缓冲区引用计数变成 0,则回收缓冲区内存。

void av_packet_unref(AVPacket *pkt)
{
    // 释放 side_data
    av_packet_free_side_data(pkt);
    // 释放引用计数
    av_buffer_unref(&pkt->buf);
    // 初始化Packet属性
    av_init_packet(pkt);
    // 释放缓存区指针
    pkt->data = NULL;
    pkt->size = 0;
}

猜你喜欢

转载自blog.csdn.net/davidsguo008/article/details/129353273