FFMpeg AVPacket 之理解与掌握

----------------------------------------
author:hjjdebug
date: 2022-01-23
----------------------------------------

本人的描述基本是白话文, 我没有时间和能力去画图,真正的理解需要调试附带的代码. 代码是我整理的.
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";

/* 这是存数据的地方,data 是malloc的返回指针
struct AVBuffer {
    uint8_t *data; 
    buffer_size_t size; 
    atomic_uint refcount;
    void (*free)(void *opaque, uint8_t *data);
    void *opaque; //free 的参数
    int flags;
    int flags_internal;
};

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

// packet 的内存分配问题
int main()
{
	void *p=malloc(10);
	int nLen = strlen(string);
	AVPacket packet;		// 一个局部变量
	AVPacket *pkt= &packet;
// new_packet 会分配nlen的存储数据,指针存到AVBuffer的data,但pkt记录AVBufferRef地址,ref的buf会指向AVBuffer.
//换一种说法,pkt->buf是AVBufferRef 地址,若不为0,pkt->buf->buffer是AVBuffer地址,若不为0,pkt->buf->buffer->data 指向实际的包数据.
//AVBuffer 结构中有一个refcount, 具体看前面的简短注释,源码中还有较详细注释.
	av_new_packet(pkt,nLen);		
	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); //第二个包引用了第一个包,使得AVBuffer 中refcount 加1
	av_packet_unref(pkt2);	//解引用	
	av_packet_unref(pkt2);	//重复调解引用没有关系,因为此时pkt2的buf已经为空了。	
	av_packet_unref(pkt);	//解引用第一个包,AVBuffer中refcount会变成0所以回收内存。
	av_packet_free(&pkt2); //pkt2 是av_packet_alloc 分配的,所以需要free
	free(p);
	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;
};
//释放src, 或者释放dst, AVBuffer->refcount--,为0释放AVBuffer 
static void buffer_replace(AVBufferRef **dst, AVBufferRef **src)
{
    AVBuffer *b;
 
    b = (*dst)->buffer;	//保存AVBuffer 的指针
 
    if (src) {			// 源存在
        **dst = **src;	//源向目标copy
        av_freep(src);  //源被释放, 此处的源是某一个AVBufferRef
    } else
        av_freep(dst); // 源为空,目标释放,这里处理的也只是一个Ref
 
//    if (atomic_fetch_sub_explicit(&b->refcount, 1, memory_order_acq_rel) == 1) 
	b->refcount --;		//处理AVBuffer
    if (b->refcount == 0) {	//buf 参考为0时,
        b->free(b->opaque, b->data);	//AVBuffer 所指的内容被释放
        av_freep(&b);		//释放的是AVBuffer 结构
    }
}
 
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)); //创建一个AVBuffer,名称为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; //create 时 refcount为1
 
    buf->flags = flags;
 
    ref = av_mallocz(sizeof(*ref)); //创建一个AVBufferRef,名称为ref(指针)
    if (!ref) {
        av_freep(&buf);
        return NULL;
    }
 
    ref->buffer = buf;
    ref->data   = data;
    ref->size   = size;
 
    return ref;	//返回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); //创建装数据的内存,即AVBuffer所指的data
        if (!data)
            return AVERROR(ENOMEM);
 
        buf = av_buffer_create(data, size, av_buffer_default_free, NULL, 0); //创建AVBufferRef 及 AVBuffer头
        if (!buf) {
            av_freep(&data);
            return AVERROR(ENOMEM);
        }
 
        buf->buffer->flags_internal |= 1; //BUFFER_FLAG_REALLOCATABLE;
        *pbuf = buf; //返回AVBufferRef
 
        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); //源不为空,释放源(&new),是一个AVBufferRef, AVBuffer 会被释放掉吗?正常不会走到这里
        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); //填充部分填0
 
    return 0;
}
 
 
int av_new_packet(AVPacket *pkt, int size)
{
    AVBufferRef *buf = NULL;
    int ret = packet_alloc(&buf, size); //分配AVBufferRef (包体),及存数的内存size大小
    if (ret < 0)
        return ret;
 
    get_packet_defaults(pkt);	// pkt 包头填充default, 全部为0,pts,dts为最小值
    pkt->buf      = buf;		// 填充pkt的AVBufferRef, data 及size
    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); //源为空,释放buf
}
 
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); // 会使pkt 所指AVBuffer refcount+1
	av_packet_unref(pkt2);	// 会使pkt2 所指AVBuffer refcount-1 (与pkt 所指AVBuffer是同一个AVBuffer)
	av_packet_unref(pkt);	// 此时因所指AVBuffer refcount=0, 会释放AVBuffer
	av_packet_free(&pkt2); //pkt2 是av_packet_alloc 分配的,所以需要free
 
	return 0;
}

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
哇塞电影网址大全,吸取了以往各种导航网址程序的优点,最大程度的完善优化了各项功能和指标,采用谁对我站贡献大,我站也给予他宣传和展示的机会就越多的流量交换模式,只要您在本系统注册登记您的网址,然后在你网站做好我站连接或是挂上流量互换代码,每次您网站有用户访问到放置我站流量互换代码的站,那么你的网站将在最近入站以及你网站所在分类的第一位置!连接双方公正平等。系统前台简介:1.采用ASP+ACCESS架构,安全稳定,防注入功能;2.新闻文章发布功能支持无限级分类,方便自由;3.数据库经过防下载等安全处理,后台可超强命名,随意改动;4.每来访一个IP,来访网站就会自动排到第一,当天来路不同,显示颜色也不同,鼓励点入;5.前台统计数据调用,最新点入网站调用,未审核网站调用等;6.申请加入电影网址大全的网站按最后点进的时间排序首页和分类显示链接;7.分类以昨日点入时间为准,每晚十二点后生成静态;8.每来访一个IP,就会自动排到第一,当天来路次数不同,显示颜色也不同:有1次即显示,10次即套蓝色,30次即套红色加粗;9.首页白天3分钟,晚上5分钟自动更新一次,全站24小时手动更新一次;10.站内搜索功能,方便用户找到自己想要的网址;11.程序全面优化和升级,增强对搜索引擎的收录功能;12.流量互换功能,最大程度互换流量。系统后台功能详细说明:管理目录:admin,管理员用户名:n3n5,密码:n3n5com网站管理系统:1.网站基本信息,说明:里面设置,网站标题,LOGO,关键词,统计代码,版权信息!2.图片广告管理,说明:网站所有图片广告修改的地方,在首页可以看到所有图片广告,其中ads09是在网址内页显示!其它的都在首页和分类页有位置显示!3.顶部文字广告管理,说明:这里的文字,首页,特别推荐里面显,分类首页和分类页,记得,改后要在生成html管理里,生成一下首页!4.添加商家文字广告,说明:这里的文字,首页,中间部分,广告,那里的文字,在图片广告下面,一行七个!5.管理商家文字广告,说明:修改删除商家文字广告!6.管理帐号设置,说明:管理员用户名,密码的修改!9.客户留言管理,说明:留言本的回复,修改和删除!网站分类管理:1.类别添加管理,说明:分类添加删除管理,这里说明一下添加时有首页显示,导航就显示在首页上面,添加时选酷站显示,就在首页下面酷站里调用!2.类别删除管理,说明:删除不想要的分类!3.类别修改管理,说明:分类修改里,有显示,[首][酷]就是上面说明的首页显示,和酷站显示!网址管理系统:1.添加网址链接,说明:用于后台管理员手工添加网址2.添加实用查询|管理实用查询,说明:添加后在首页实用工具里显示!3.添加名站导航|管理名站导航,说明:添加后在首页名站导航里显示!4.添加友情链接|管理友情链接,说明:添加后在首页下部友情链接里显示!5.查看所有的网址,说明:包含站长加的和用户自己加的!6.站长加入的网址,说明:站长加入的网址!7.用户加入已审核,说明:用户提交的网址,并通过审核的,说明一下,本站有自动审核功能,开启关闭,在 网站管理系统-网站基本信息里设置!8.用户加入未审核,说明:用户提交的网址没审的,也就是没有作上本站链接的,或是作上链接没有点击到本站的!9.有来路入未审核,说明:一般用户认为,有来路就应当审核了,这个功能,是为了关闭自动审核而设计的,手工审核的不管有没有来路,都要站长审核的!10.加入黑名单网站,说明:加入黑名单的网址,点击这个导航,进入后,可以删除,和取消黑名单!11.总来路小于五次,说明:本设计用于客户作上本站链接,点入量过小,没有贡献的站,可以多选删除!12.常用维护共三项,说明:(1)开通所有未审的,一般不用这个,如果想要提交的站就收录,可以点击这个功能!(2)删除重复的网站,有一些站长提交过了,又提交了多次或是用二级域名提交,这样可以删除重复的网站!(3)删除所有未审核的站点,(4)清空所有网址,这个点时要注意,点击了,所有网址就都没有了!13.站内报错,说明:用户在网址详提交网址打不开的情况页点击的!14.站内网站搜索,说明:可以按名称,按网址,按分类,按ID号进行搜索! 模版修改管理:首 页 模版修改 分类页模版修改 关于本站页模板 (这里建议会一些HTML知识的站长修改,如果不会不建议修改以免出错,修改时一定要备份)生成html管理:生成分类页面 生成生成主页及其他页 重置统计数据 清除昨天点入数据 清除总点入数据 清除总点出数据 (常用到上面两个,生成分类页和生成主页,也主是首页!每当后台修改了内容时,要马上显示出来就要手动生成,因为前台自动生成要3分钟!)数据库管理:备份数据库 恢复数据库 压缩数据库 (常用到备份数据库,定期备份一下,免费数据库出错找不回来数据!)哇塞电影网址

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值