AVPacket

// AVPacket侧数据
typedef struct AVPacketSideData 
{
	// 数据
    uint8_t *data;
	// 数据大小
    int      size;
	// 数据类型
	// AV_PKT_DATA_* 类型宏
    enum AVPacketSideDataType type;
 
} AVPacketSideData;

AVPacket是FFmpeg中很重要的一个数据结构,它保存了解复用之后,解码之前的数据(仍然是压缩后的数据)和关于这些数据的一些附加信息,如显示时间戳(pts)、解码时间戳(dts)、数据时长,所在媒体流的索引等。对于视频来说,AVPacket通常包含一个压缩的Frame,而音频(Audio)则有可能包含多个压缩的Frame。并且,一个Packet有可能是空的,不包含任何压缩数据,只含有side data(side data,容器提供的关于Packet的一些附加信息。例如,在编码结束的时候更新一些流的参数)。AVPacket的大小是公共的ABI(public ABI)一部分,这样的结构体在FFmpeg很少,由此也可见AVPacket的重要性。它可以被分配在栈空间上(可以使用语句AVPacket packet; 在栈空间定义一个Packet ),并且除非libavcodec 和 libavformat有很大的改动,不然不会在AVPacket中添加新的字段。

/**
 * 该结构用来存储压缩数据. 这是典型的解复合器的输出然后塞入解码器,或者是接收编码器的输出然后塞入复合器
 *
 * 解复合器 --> AVPacket --> 解码器 --> YUV/RGB --> 编码器 --> AVPacket -->复合器
 *
 * 对视频而言, 它通常包含一个压缩帧. 对音频而言,则它可能包含多个压缩帧. 
 * 编码器允许输出一个空包, 没有压缩数据, 仅包含侧数据。
 * (例如在编码结束时更新一些流参数)
 *
 * AVPacket是FFmpeg少数几个结构之一, 其大小是公共ABI的一部分.
 *
 * 数据所有权的语义取决于buf域.
 * 如果被设置, 分组数据动态分配,且永远有效,直到一个叫av_packet_unref()减少引用计数为0时才被释放
 *
 * 如果buf域没有被设置,那么av_packet_ref()将做一个复制而不会增加引用计数
 *
 * 附加数据始终由av_malloc()分配内存, 由av_packet_ref()进行拷贝,由av_packet_unref()执行释放.
 *
 * @浏览 av_packet_ref()
 * @浏览 av_packet_unref()
 */
typedef struct AVPacket 
{
	// 用来管理data指针引用的数据缓存的
	// 为NULL时,那么数据包是不计数的
    AVBufferRef *buf;
 
	// 显示时间戳, 对应时间戳AVStream->time_base单元; 这个时间点, 解压缩的数据包将被提交给用户
    // 如果时间不被存储在文件里, 则可以写成AV_NOPTS_VALUE
    // pts必须大于或等于dts, 因为显示不能在解压缩之前被发生, 除非有人想查看十六进制存储。
	// 某些格式误用了这个名词dts或者是pts/cts那是意味着别的意思, 所以时间戳必须在被存储到AVPacket之前转换成真正的PTS/DTS。
    int64_t pts;
 
	// 解码时间戳, 对应时间戳AVStream->time_base单元; 这个时间点, 数据包被解码
	// 如果时间不被存储在文件里, 则可以写成AV_NOPTS_VALUE
    int64_t dts;
 
	// 存储的数据,指向一个缓存,这是AVPacket实际的数据
    uint8_t *data;
 
	// 数据的大小
    int size;
	
	// 标识该AVPacket所属的音频/视频流的索引
    int stream_index;
    
	// 一个AV_PKT_FLAG标识值, 最低为置1表示关键帧
    int flags;
 
	// 容器可以提供的附加数据
	// 包可以包含几种AVPacketSideDataType类型的侧信息
    AVPacketSideData *side_data;
 
	// 附加信息元素
    int side_data_elems;
 
    // 数据的时长,以所属媒体流的时间基准为单位
    int64_t duration;
 
	// 该数据在媒体流中的字节偏移量
    int64_t pos;
 
	// 该字段不再使用
#if FF_API_CONVERGENCE_DURATION
    attribute_deprecated
    int64_t convergence_duration;
#endif
 
} AVPacket;

AVPacket实际上可用看作一个容器,它本身并不包含压缩的媒体数据,而是通过data指针引用数据的缓存空间。
所以将一个Packet作为参数传递的时候,就要根据具体的需要,对data引用的这部分数据缓存空间进行特殊的处理。

当从一个Packet去创建另一个Packet的时候,有两种情况:

①两个Packet的data引用的是同一数据缓存空间,这时候要注意数据缓存空间的释放问题;

②两个Packet的data引用不同的数据缓存空间,每个Packet都有数据缓存空间的copy;

第二种情况,数据空间的管理比较简单,但是数据实际上有多个copy造成内存空间的浪费。

所以要根据具体的需要,来选择到底是两个Packet共享一个数据缓存空间,还是每个Packet拥有自己独自的缓存空间。

对于多个Packet共享同一个缓存空间,FFmpeg使用的引用计数的机制(reference-count):

当有新的Packet引用共享的缓存空间时,就将引用计数+1;

当释放了引用共享空间的Packet,就将引用计数-1;引用计数为0时,就释放掉引用的缓存空间。

而类AVBufferRef就是用来管理引用机制的:

typedef struct AVBufferRef {
    AVBuffer *buffer;
 
    /**
     * The data buffer. It is considered writable if and only if
     * this is the only reference to the buffer, in which case
     * av_buffer_is_writable() returns 1.
     */
    uint8_t *data;
    /**
     * Size of data in bytes.
     */
    int      size;
} AVBufferRef;

其中,AVPacket会使用两个函数:
①int av_packet_ref(AVPacket *dst, const AVPacket *src):
可以理解为使用引用计数的浅拷贝。

该函数会先拷贝所有非缓存类数据,然后创建一个src->data的新的引用计数。如果src已经设置了引用计数发(src->buffer不为空),则直接将其引用计数+1;如果src没有设置引用计数(src->buffer为空),则为dst创建一个新的引用计数buf,并复制src->data到buf->buffer中。最后,复制src的其他字段到dst中。所以av_packet_ref()是将2个AVPacket共用一个缓存的。
②void av_packet_unref(AVPacket *pkt):
可以理解为使用引用计数的数据清理。

将缓存空间的引用计数-1,并将Packet中的其他字段设为初始值。如果引用计数为0,自动的释放缓存空间。所以,有两个Packet共享同一个数据缓存空间的时候可用这么做。
函数说明:

1. int av_read_frame(AVFormatContext *s, AVPacket *pkt):

#include “avformat.h”

该接口的功能是从流数据文件(AVFormatContext)的媒体流中读取帧填充到填充到AVPacket的数据缓存空间。

如果Packet->buf为空,则Packet的数据缓存空间会在下次调用av_read_frame的时候失效。
但问题在于,每次调用av_read_frame后,都会将上次读取的帧(AVPacket->data)进行重写,这样就可能导致上一次的读取的data释放掉。没法做缓存队列了。

所以假如每次读取之后都需要保留上一次的帧数据,那么就需要在下一次读取前给AVPacket->data增加一个引用计数,或者就是手工创建buff然后拷贝过去然后再将data指向它。

这个增加引用计数的接口就是上面说到的:av_packet_ref(dst, src),将data作为src塞入参数,再用得到的dst指针丢入到缓存队列里,OK,可继续进行下一次av_read_frame()了。

2. AVPacket *av_packet_alloc(void):

简单的创建一个AVPacket,将其字段设为默认值(data为空,没有数据缓存空间),data的指针需要另外去赋值。

AVPacket *av_packet_alloc(void)
{
    AVPacket *pkt = av_mallocz(sizeof(AVPacket));
    if (!pkt)
        return pkt;
 
    av_packet_unref(pkt);
 
    return pkt;
}
3. void av_packet_free(AVPacket **pkt):

释放使用av_packet_alloc创建的AVPacket,如果该Packet有引用计数(packet->buf不为空),则先调用av_packet_unref(&packet)。

只有当引用计数为0时,才会在调用av_packet_free()时释放data的缓存。

4. AVPacket *av_packet_clone(AVPacket *src):

其功能是 av_packet_alloc() + av_packet_ref();

先创建一个新的AVPacket,然后再进行计数引用+数据拷贝,使得新的AVPacket指向老的AVPacket同一个data。

AVPacket *av_packet_clone(AVPacket *src)
{
    AVPacket *ret = av_packet_alloc();
    if (!ret)
        return ret;
 
    if (av_packet_ref(ret, src))
        av_packet_free(&ret);
 
    return ret;
}
5. void av_init_packet(AVPacket *pkt():

初始化packet的值为默认值,该函数不会影响data引用的数据缓存空间和size,需要单独处理。AVPacket中的buf为空。

void av_init_packet(AVPacket *pkt)
{
    pkt->pts                  = AV_NOPTS_VALUE;
    pkt->dts                  = AV_NOPTS_VALUE;
    pkt->pos                  = -1;
    pkt->duration             = 0;
#if FF_API_CONVERGENCE_DURATION
FF_DISABLE_DEPRECATION_WARNINGS
    pkt->convergence_duration = 0;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
    pkt->flags                = 0;
    pkt->stream_index         = 0;
    pkt->buf                  = NULL;
    pkt->side_data            = NULL;
    pkt->side_data_elems      = 0;
}
6. int av_new_packet(AVPacket *pkt, int size):

是av_init_packet的增强版,不但会初始化字段,还为data分配了存储空间。

只不过这块空间是空的,长度依照size来定义。AVPacket中的buf不再为空。

int av_new_packet(AVPacket *pkt, int size)
{
    AVBufferRef *buf = NULL;
    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;
}
7. int av_copy_packet(AVPacket *dst, const AVPacket *src):

复制一个新的packet,包括数据缓存。

这和av_packet_clone()的区别在于,clone是利用引用计数指向同一块内存,而coye则是不使用引用计数,2个AVPacket使用2个缓存。

int av_copy_packet(AVPacket *dst, const AVPacket *src)
{
    *dst = *src;
    return copy_packet_data(dst, src, 0);
}
static int copy_packet_data(AVPacket *pkt, const AVPacket *src, int dup)
{
    pkt->data      = NULL;
    pkt->side_data = NULL;
    pkt->side_data_elems = 0;
    if (pkt->buf) {
        AVBufferRef *ref = av_buffer_ref(src->buf);
        if (!ref)
            return AVERROR(ENOMEM);
        pkt->buf  = ref;
        pkt->data = ref->data;
    } else {
        DUP_DATA(pkt->data, src->data, pkt->size, 1, ALLOC_BUF);
    }
    if (src->side_data_elems && dup) {
        pkt->side_data = src->side_data;
        pkt->side_data_elems = src->side_data_elems;
    }
    if (src->side_data_elems && !dup) {
        return av_copy_packet_side_data(pkt, src);
    }
    return 0;
 
failed_alloc:
    av_packet_unref(pkt);
    return AVERROR(ENOMEM);
}
8. int av_packet_from_data(AVPacket *pkt, uint8_t *data, int size):

初始化一个引用计数的packet,并指定了其数据缓存。缓存需要手工创建,AVPacket自动创建引用计数机制,参数pkt需要事先被av_packet_alloc()。

int av_packet_from_data(AVPacket *pkt, uint8_t *data, int size)
{
    if (size >= INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE)
        return AVERROR(EINVAL);
 
    pkt->buf = av_buffer_create(data, size + AV_INPUT_BUFFER_PADDING_SIZE,
                                av_buffer_default_free, NULL, 0);
    if (!pkt->buf)
        return AVERROR(ENOMEM);
 
    pkt->data = data;
    pkt->size = size;
 
    return 0;
}
9. int av_grow_packet(AVPacket *pkt, int grow_by):

增大Packet->data指向的数据缓存。

int av_grow_packet(AVPacket *pkt, int grow_by)
{
    int new_size;
    av_assert0((unsigned)pkt->size <= INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE);
    if ((unsigned)grow_by >
        INT_MAX - (pkt->size + AV_INPUT_BUFFER_PADDING_SIZE))
        return -1;
 
    new_size = pkt->size + grow_by + AV_INPUT_BUFFER_PADDING_SIZE;
    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 {
            data_offset = pkt->data - pkt->buf->data;
            if (data_offset > INT_MAX - new_size)
                return -1;
        }
 
        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 = pkt->buf->data + data_offset;
        }
    } else {
        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;
}
10. void av_shrink_packet(AVPacket *pkt, int size):

减小Packet->data指向的数据缓存。

void av_shrink_packet(AVPacket *pkt, int size)
{
    if (pkt->size <= size)
        return;
    pkt->size = size;
    memset(pkt->data + size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值