FFMpeg AVPacket 之理解与掌握

本人的描述基本是白话文, 我没有时间和能力去画图,真正的理解需要调试附带的代码. 代码是我整理的.
ffmpeg 中引入了很多概念,今天,
介绍一个重要的概念AVPacket  
c 的时代还没有引入类class的概念, 放在一起的数据叫结构struct, 用结构声明一个变量结构变量.
文件中的各个函数,操作的就是这个结构变量.
这里有几个问题需要关注.
1. 为什么要引入这样一个结构?
答. 当然是为了方便使用. 否则写这么多代码干什么.
2. 关注变量的生成和释放, 相当于类的构造和析构.

当你声明了一个包, AVPacket packet;
你也可以从堆中声明一个包, AVPacket *pkt = av_packet_alloc();
堆中声明的,需要释放, 函数 av_packet_free(pkt);

包,当然是用来装东西的. 可以理解为包头加包体构成一个整体包
嗯,现在只是声明或创造了一个包头, 真正装数据的地方是包体.
继续执行函数调用 av_new_packet((pkt,nLen);
创建了包体,可以装nLen 长度的数据. 你可以用memcpy 向pkt->data 中copy 数据.

为啥要搞这么复杂,申请一块内存,copy数据,只要malloc 就可以了,为什么要用packet?
那是因为packet在处理av数据时有很多特殊需要, 例如,包的copy, 经常是只copy包头,不copy包体,
这样,包的copy 就非常有效率. 这就是包这个概念存在的价值了.

那包怎么copy呢?  千万不要这样写 packet = *pkt, 这样只是copy了包头,使得packet的包头与pkt
所指的包头完全一样, 好多问题都是这么引入的.
为什么呢? 究其原因还是c语言没有赋值构造函数, 只是简单的copy了数据和指针, 指针所指向的数据它就管不了了.
你应该调用函数av_packet_ref(&packet,pkt)
这样,packet 就copy 了pkt, 其实它真正的实现是把pkt所指向的数据引用计数增加了一次.
当不用这个packet了,你需要释放av_packet_unref(&packet), 此时包头就会恢复成默认值.
但是,包体却未必会释放, 只有当包体的引用计数减为0时,包体会被释放.

包体对应着一个结构,叫AVBuffer, 可以理解为malloc 的一个实现. 这个结构并没有对用户开放, 不过开源的代码
我们还是能看到它的实现.里面包含了一个refcount

包头中包含一个AVBufferRef 结构, 主要是一个AVBuffer 的指针.
如果比较两个packet, AVBufferRef 地址不同, data指针相同, 那这两个包都指向了同一个AVBuffer.
其实, 包的使用还是很简单的,主要是
av_packet_ref
av_packet_unref
同时还要掌握av_new_packet, av_packet_alloc,av_packet_free
认真掌握下面的程序:

#include <stdio.h>
#include <libavcodec/packet.h>
#include <libavcodec/avcodec.h>

char *string="hello";

// packet 的内存分配问题
int main()
{
	int nLen = strlen(string);
	AVPacket packet;		// 一个局部变量
	AVPacket *pkt= &packet;
	av_new_packet(pkt,nLen);		// new_packet 会分配packet 的存储数据,并由AVBufferRef 指向AVBuffer, AVBuffer是实际装数据的地方
	memcpy(pkt->data,string,nLen);

	AVPacket *pkt2 = av_packet_alloc();
//	av_new_packet(pkt2,nLen);
//	memcpy(pkt2->data,string,nLen);
	av_packet_ref(pkt2,pkt);
	av_packet_unref(pkt2);	
	av_packet_unref(pkt2);	
	av_packet_unref(pkt);	
	av_packet_free(&pkt2); //pkt2 是av_packet_alloc 分配的,所以需要free

	return 0;
}

想进一步理解后面到底发生了什么,可以把玩下面这个程序, 一株小草,下面带了一大块泥巴才活的健康.

#include <stdio.h>
#include <libavcodec/packet.h>
#include <libavcodec/avcodec.h>

struct AVBuffer {
    uint8_t *data; /**< data described by this buffer */
    int size; /**< size of data in bytes */

    /**
     *  number of existing AVBufferRef instances referring to this buffer
     */
    int refcount;

    /**
     * a callback for freeing the data
     */
    void (*free)(void *opaque, uint8_t *data);

    /**
     * an opaque pointer, to be used by the freeing callback
     */
    void *opaque;

    /**
     * A combination of AV_BUFFER_FLAG_*
     */
    int flags;

    /**
     * A combination of BUFFER_FLAG_*
     */
    int flags_internal;
};

static void buffer_replace(AVBufferRef **dst, AVBufferRef **src)
{
    AVBuffer *b;

    b = (*dst)->buffer;

    if (src) {			// 源存在
        **dst = **src;	//源向目标copy
        av_freep(src);  //源被释放
    } else
        av_freep(dst); // 源为空,目标释放

//    if (atomic_fetch_sub_explicit(&b->refcount, 1, memory_order_acq_rel) == 1) 
	b->refcount --;
    if (b->refcount == 0) {	//buf 参考为0时,
        b->free(b->opaque, b->data);	//buf 内容被释放
        av_freep(&b);		//buf 被释放
    }
}

void av_buffer_default_free(void *opaque, uint8_t *data)
{
	(void) opaque;
    av_free(data);
}
AVBufferRef *av_buffer_create(uint8_t *data, int size,
                              void (*free)(void *opaque, uint8_t *data),
                              void *opaque, int flags)
{
    AVBufferRef *ref = NULL;
    AVBuffer    *buf = NULL;

    buf = av_mallocz(sizeof(*buf));
    if (!buf)
        return NULL;

    buf->data     = data;
    buf->size     = size;
    buf->free     = free ? free : av_buffer_default_free;
    buf->opaque   = opaque;

    buf->refcount = 1;

    buf->flags = flags;

    ref = av_mallocz(sizeof(*ref));
    if (!ref) {
        av_freep(&buf);
        return NULL;
    }

    ref->buffer = buf;
    ref->data   = data;
    ref->size   = size;

    return ref;
}

int av_buffer_realloc(AVBufferRef **pbuf, int size)
{
    AVBufferRef *buf = *pbuf;
    uint8_t *tmp;
    int ret;

    if (!buf) 
	{
        /* allocate a new buffer with av_realloc(), so it will be reallocatable
         * later */
        uint8_t *data = av_realloc(NULL, size);
        if (!data)
            return AVERROR(ENOMEM);

        buf = av_buffer_create(data, size, av_buffer_default_free, NULL, 0);
        if (!buf) {
            av_freep(&data);
            return AVERROR(ENOMEM);
        }

        buf->buffer->flags_internal |= 1; //BUFFER_FLAG_REALLOCATABLE;
        *pbuf = buf;

        return 0;
    } 
	else if (buf->size == size) return 0;

    if (!(buf->buffer->flags_internal & 1/*BUFFER_FLAG_REALLOCATABLE*/) ||
        !av_buffer_is_writable(buf) || buf->data != buf->buffer->data) 
	{
        /* cannot realloc, allocate a new reallocable buffer and copy data */
        AVBufferRef *new = NULL;

        ret = av_buffer_realloc(&new, size);
        if (ret < 0)
            return ret;

        memcpy(new->data, buf->data, FFMIN(size, buf->size));

        buffer_replace(pbuf, &new);
        return 0;
    }

    tmp = av_realloc(buf->buffer->data, size);
    if (!tmp)
        return AVERROR(ENOMEM);

    buf->buffer->data = buf->data = tmp;
    buf->buffer->size = buf->size = size;
    return 0;
}

static void get_packet_defaults(AVPacket *pkt)
{
    memset(pkt, 0, sizeof(*pkt));

    pkt->pts             = AV_NOPTS_VALUE;
    pkt->dts             = AV_NOPTS_VALUE;
    pkt->pos             = -1;
}

static int packet_alloc(AVBufferRef **buf, int size)
{
    int ret;
    if (size < 0 || size >= INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE)
        return AVERROR(EINVAL);

    ret = av_buffer_realloc(buf, size + AV_INPUT_BUFFER_PADDING_SIZE);
    if (ret < 0)
        return ret;

    memset((*buf)->data + size, 0, AV_INPUT_BUFFER_PADDING_SIZE);

    return 0;
}


int av_new_packet(AVPacket *pkt, int size)
{
    AVBufferRef *buf = NULL;
    int ret = packet_alloc(&buf, size);
    if (ret < 0)
        return ret;

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

    return 0;
}

void av_free(void *ptr)
{
#if HAVE_ALIGNED_MALLOC
    _aligned_free(ptr);
#else
    free(ptr);
#endif
}

void av_freep(void *arg)
{
    void *val;

    memcpy(&val, arg, sizeof(val));
    memcpy(arg, &(void *){ NULL }, sizeof(val));
    av_free(val);
}

void av_packet_free_side_data(AVPacket *pkt)
{
    int i;
    for (i = 0; i < pkt->side_data_elems; i++)
        av_freep(&pkt->side_data[i].data);
    av_freep(&pkt->side_data);
    pkt->side_data_elems = 0;
}

void av_buffer_unref(AVBufferRef **buf)
{
    if (!buf || !*buf)
        return;

    buffer_replace(buf, NULL);
}

void av_packet_unref(AVPacket *pkt)
{
    av_packet_free_side_data(pkt);
    av_buffer_unref(&pkt->buf);
    get_packet_defaults(pkt);
}

void av_packet_free(AVPacket **pkt)
{
    if (!pkt || !*pkt)
        return;

    av_packet_unref(*pkt);
    av_freep(pkt);
}
char *string="hello";

// packet 的内存分配问题
int main()
{
	int nLen = strlen(string);
	AVPacket packet;		// 一个局部变量
	AVPacket *pkt= &packet;
	av_new_packet(pkt,nLen);		// new_packet 会分配packet 的存储数据,并由AVBufferRef 指向AVBuffer, AVBuffer是实际装数据的地方
	memcpy(pkt->data,string,nLen);

	AVPacket *pkt2 = av_packet_alloc();
//	av_new_packet(pkt2,nLen);
//	memcpy(pkt2->data,string,nLen);
	av_packet_ref(pkt2,pkt);
	av_packet_unref(pkt2);	
	av_packet_unref(pkt);	
	av_packet_free(&pkt2); //pkt2 是av_packet_alloc 分配的,所以需要free

	return 0;
}

上一篇:Cisco Packet Tracer 实验


下一篇:计算机网络 Cisco Packet Tracer 实验