热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ffmpegAVPacket解读

在工作中用到ffmpeg推流,使用过程中发现AVPacket这个结构体比较特殊,现在记录下来,备忘录。该结构体如下:该结构

在工作中用到ffmpeg推流,使用过程中发现AVPacket这个结构体比较特殊,现在记录下来,备忘录。
该结构体如下:

//该结构在libavcodec中
typedef struct AVPacket {/*** A reference to the reference-counted buffer where the packet data is* stored.* May be NULL, then the packet data is not reference-counted.*/AVBufferRef *buf;/*** Presentation timestamp in AVStream->time_base units; the time at which* the decompressed packet will be presented to the user.* Can be AV_NOPTS_VALUE if it is not stored in the file.* pts MUST be larger or equal to dts as presentation cannot happen before* decompression, unless one wants to view hex dumps. Some formats misuse* the terms dts and pts/cts to mean something different. Such timestamps* must be converted to true pts/dts before they are stored in AVPacket.*/int64_t pts;/*** Decompression timestamp in AVStream->time_base units; the time at which* the packet is decompressed.* Can be AV_NOPTS_VALUE if it is not stored in the file.*/int64_t dts;uint8_t *data;int size;int stream_index;/*** A combination of AV_PKT_FLAG values*/int flags;/*** Additional packet data that can be provided by the container.* Packet can contain several types of side information.*/AVPacketSideData *side_data;int side_data_elems;/*** Duration of this packet in AVStream->time_base units, 0 if unknown.* Equals next_pts - this_pts in presentation order.*/int64_t duration;int64_t pos; ///#if FF_API_CONVERGENCE_DURATION/*** @deprecated Same as the duration field, but as int64_t. This was required* for Matroska subtitles, whose duration values could overflow when the* duration field was still an int.*/attribute_deprecatedint64_t convergence_duration;
#endif
} AVPacket;/*** A reference to a data buffer.** The size of this struct is not a part of the public ABI and it is not meant* to be allocated directly.*/
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 *av_packet_alloc(void);
void av_init_packet(AVPacket *pkt);
void av_packet_free(AVPacket **pkt)

一般来讲,该结构体我们都是定义为指针,然后通过如下定义:

AVPacket *pkt=av_packet_alloc();
av_init_packet(pkt);
//use pkt read(pkt)
av_read_frame(avformatCtx, pkt);
av_packet_free(&pkt);

以前一直以为av_packet_alloc()是为其内部的指针分配内存,不然直接用AVPacket pkt这样多好,后来看了源码发现自己错了,请看源码:

AVPacket *av_packet_alloc(void)
{//这里仅仅是给结构体本身分配了空间,并没有给其内部存储数据的指针分配空间AVPacket *pkt = av_mallocz(sizeof(AVPacket));if (!pkt)return pkt;//可以看到这里它也调用了初始化,所以我们调用了alloc就不要init了av_init_packet(pkt);return pkt;
}
//这个函数跟上面的区别就在这里要传入size大小,这个size就是pkt内部的size大小,
//这个函数可以用来深拷贝另外一个pkt,特别强调的一点,其实所有的包数据都是存储在
//buf中的,data指针指向了buff的data。
int av_new_packet(AVPacket *pkt, int size)
{AVBufferRef *buf &#61; NULL;int ret &#61; packet_alloc(&buf, size);if (ret < 0)return ret;av_init_packet(pkt);pkt->buf &#61; buf;pkt->data &#61; buf->data;//指向了bufpkt->size &#61; size;return 0;
}
void av_init_packet(AVPacket *pkt)
{pkt->pts &#61; AV_NOPTS_VALUE;pkt->dts &#61; AV_NOPTS_VALUE;pkt->pos &#61; -1;pkt->duration &#61; 0;
#if FF_API_CONVERGENCE_DURATION
FF_DISABLE_DEPRECATION_WARNINGSpkt->convergence_duration &#61; 0;
FF_ENABLE_DEPRECATION_WARNINGS
#endifpkt->flags &#61; 0;pkt->stream_index &#61; 0;pkt->buf &#61; NULL;pkt->side_data &#61; NULL;pkt->side_data_elems &#61; 0;
}

然后我们来看av_packet_free&#xff1a;

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

源码如上&#xff0c;我们注意上面传入了二级指针&#xff0c;并且用到了

av_packet_unref(*pkt);
av_freep(pkt);

这里才是今天我们的重点&#xff0c;我们知道av_read_frame(avformatCtx, pkt)&#xff0c;读到数据包的时候就为其内部数据指针分配了空间并且存储了数据&#xff0c;我的问题是&#xff1a;我们如何拷贝一份avpacket&#xff0c;怎么样才算深拷贝&#xff1f;拷贝了怎么释放&#xff1f;

int av_copy_packet(AVPacket *dst, const AVPacket *src);
int av_packet_ref(AVPacket *dst, const AVPacket *src);
//void av_packet_unref(AVPacket *pkt);
AVPacket *av_packet_clone(const AVPacket *src);

这三个函数之间有上面区别&#xff0c;我们使用的时候到底用那个才合适&#xff0c;下面我们意义探讨&#xff1a;

int av_copy_packet(AVPacket *dst, const AVPacket *src)
{//这里我们注意&#xff0c;内容浅拷贝&#xff0c;dst在外部一定要分配好外壳内存*dst &#61; *src;return copy_packet_data(dst, src, 0);
}
/* Makes duplicates of data, side_data, but does not copy any other fields */
static int copy_packet_data(AVPacket *pkt, const AVPacket *src, int dup)
{pkt->data &#61; NULL;pkt->side_data &#61; NULL;pkt->side_data_elems &#61; 0;if (pkt->buf) {//仅仅是buf的引用&#xff0c;难道这里用到了类似智能指针的技术引用&#xff1f;AVBufferRef *ref &#61; av_buffer_ref(src->buf);if (!ref)return AVERROR(ENOMEM);pkt->buf &#61; ref;pkt->data &#61; ref->data;} else {DUP_DATA(pkt->data, src->data, pkt->size, 1, ALLOC_BUF);}if (src->side_data_elems && dup) {pkt->side_data &#61; src->side_data;pkt->side_data_elems &#61; 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);
}AVBufferRef *av_buffer_ref(AVBufferRef *buf)
{AVBufferRef *ret &#61; av_mallocz(sizeof(*ret));if (!ret)return NULL;*ret &#61; *buf;//这里也是结构体//这里进行了原子操作&#43;1atomic_fetch_add_explicit(&buf->buffer->refcount, 1, memory_order_relaxed);return ret;
}
看到这里我们大致清楚了&#xff0c;av_copy_packet&#xff1a;
1、首先dst要有一个外壳内存&#xff08;这个指针一定调用了av_packet_alloc&#xff08;&#xff09;&#xff09;&#xff1b;
2、拷贝后&#xff0c;在堆区分配了AVBufferRef *ret &#61; av_mallocz(sizeof(*ret));字节大小的空间&#xff1b;
3、直接将src的ref浅拷贝给它 *ret &#61; *buf;
综上所述&#xff0c;也就是拷贝过程中我们并没有真真的区拷贝数据&#xff0c;而是只分配了一个AVBufferRef大小的结构体&#xff0c;然后让它去浅拷贝了
src的buf&#xff0c;然后将src->buf中的引用计数增加了1&#xff0c;妥妥的智能指针啊&#xff0c;与时俱进

关于怎么释放&#xff0c;我们最后看&#xff0c;接下来我们看下一个函数&#xff1a;int av_packet_ref(AVPacket *dst, const AVPacket *src);

//
int av_packet_ref(AVPacket *dst, const AVPacket *src)
{int ret;dst->buf &#61; NULL;//该函数源码见下面&#xff0c;除了side_data分配了空间外&#xff0c;其余的全部是标准拷贝//从这里我们也可以看出dst一定的先调用av_packet_alloc&#xff08;&#xff09;分配外壳内存ret &#61; av_packet_copy_props(dst, src);if (ret < 0)goto fail;//这里比较特殊&#xff0c;一般来说我们av_read_frame(avformatCtx, pkt);后数据内存//看下面的这张图&#xff0c;一般这个用不到//只要读到包&#xff0c;里面的数据就存在buf中if (!src->buf) {ret &#61; packet_alloc(&dst->buf, src->size);if (ret < 0)goto fail;av_assert1(!src->size || src->data);if (src->size)memcpy(dst->buf->data, src->data, src->size);dst->data &#61; dst->buf->data;} //直接考虑这个//这里也仅仅是用了一次引用计数&#xff0c;具体看上面分析&#xff0c;看到这里恍然大悟&#xff0c;AVPacket的拷贝是通过类似智能指针的方式去//引用的。else {dst->buf &#61; av_buffer_ref(src->buf);if (!dst->buf) {ret &#61; AVERROR(ENOMEM);goto fail;}dst->data &#61; src->data;}dst->size &#61; src->size;return 0;
fail:av_packet_unref(dst);return ret;
}int av_packet_copy_props(AVPacket *dst, const AVPacket *src)
{int i;dst->pts &#61; src->pts;dst->dts &#61; src->dts;dst->pos &#61; src->pos;dst->duration &#61; src->duration;
#if FF_API_CONVERGENCE_DURATION
FF_DISABLE_DEPRECATION_WARNINGSdst->convergence_duration &#61; src->convergence_duration;
FF_ENABLE_DEPRECATION_WARNINGS
#endifdst->flags &#61; src->flags;dst->stream_index &#61; src->stream_index;dst->side_data &#61; NULL;dst->side_data_elems &#61; 0;for (i &#61; 0; i < src->side_data_elems; i&#43;&#43;) {enum AVPacketSideDataType type &#61; src->side_data[i].type;int size &#61; src->side_data[i].size;uint8_t *src_data &#61; src->side_data[i].data;uint8_t *dst_data &#61; av_packet_new_side_data(dst, type, size);if (!dst_data) {av_packet_free_side_data(dst);return AVERROR(ENOMEM);}memcpy(dst_data, src_data, size);}return 0;
}

av_read_frame(avformatCtx, pkt);后数据内存结构如下&#xff0c;data里面其实什么都没有&#xff0c;空空如也
在这里插入图片描述

关于AVPacket *av_packet_clone(const AVPacket *src)如下&#xff1a;

//与其余两个区别仅仅是dst不用分配外壳
AVPacket *av_packet_clone(const AVPacket *src)
{AVPacket *ret &#61; av_packet_alloc();if (!ret)return ret;if (av_packet_ref(ret, src))av_packet_free(&ret);return ret;
}

我们先总结下这三个区别&#xff1a;

int av_copy_packet(AVPacket *dst, const AVPacket *src);
//1、分配外壳
//2、不拷贝side_data数据&#xff0c;分配buf在堆区&#xff0c;具体内容引用
int av_packet_ref(AVPacket *dst, const AVPacket *src);
//1、分配外壳
//2、拷贝side_data数据&#xff0c;分配buf在堆区&#xff0c;具体内容引用
//void av_packet_unref(AVPacket *pkt);
AVPacket *av_packet_clone(const AVPacket *src);
//1、不分配外壳
//2、其余跟av_packet_ref一致。

最后我们来看void av_packet_unref(AVPacket *pkt)&#xff1a;

void av_packet_unref(AVPacket *pkt)
{//释放side_data内存av_packet_free_side_data(pkt);//释放前面讲的堆区buf&#xff0c;当计数为0的时候释放真正的数据内存av_buffer_unref(&pkt->buf);av_init_packet(pkt);pkt->data &#61; NULL;pkt->size &#61; 0;
}
void av_packet_free_side_data(AVPacket *pkt)
{int i;for (i &#61; 0; i < pkt->side_data_elems; i&#43;&#43;)av_freep(&pkt->side_data[i].data);av_freep(&pkt->side_data);pkt->side_data_elems &#61; 0;
}
void av_buffer_unref(AVBufferRef **buf)
{if (!buf || !*buf)return;buffer_replace(buf, NULL);
}
static void buffer_replace(AVBufferRef **dst, AVBufferRef **src)
{AVBuffer *b;b &#61; (*dst)->buffer;if (src) {**dst &#61; **src;av_freep(src);} elseav_freep(dst);if (atomic_fetch_sub_explicit(&b->refcount, 1, memory_order_acq_rel) &#61;&#61; 1) {b->free(b->opaque, b->data);av_freep(&b);}
}

讲到这里&#xff0c;就结束了&#xff0c;对AVPacket的拷贝赋值就不会一脸懵逼了吧。


推荐阅读
  • 本文讨论了如何使用GStreamer来删除H264格式视频文件中的中间部分,而不需要进行重编码。作者提出了使用gst_element_seek(...)函数来实现这个目标的思路,并提到遇到了一个解决不了的BUG。文章还列举了8个解决方案,希望能够得到更好的思路。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • 流数据流和IO流的使用及应用
    本文介绍了流数据流和IO流的基本概念和用法,包括输入流、输出流、字节流、字符流、缓冲区等。同时还介绍了异常处理和常用的流类,如FileReader、FileWriter、FileInputStream、FileOutputStream、OutputStreamWriter、InputStreamReader、BufferedReader、BufferedWriter等。此外,还介绍了系统流和标准流的使用。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 怎么在PHP项目中实现一个HTTP断点续传功能发布时间:2021-01-1916:26:06来源:亿速云阅读:96作者:Le ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
  • ***byte(字节)根据长度转成kb(千字节)和mb(兆字节)**parambytes*return*publicstaticStringbytes2kb(longbytes){ ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 本文整理了Java中java.lang.NoSuchMethodError.getMessage()方法的一些代码示例,展示了NoSuchMethodErr ... [详细]
  • 使用freemaker生成Java代码的步骤及示例代码
    本文介绍了使用freemaker这个jar包生成Java代码的步骤,通过提前编辑好的模板,可以避免写重复代码。首先需要在springboot的pom.xml文件中加入freemaker的依赖包。然后编写模板,定义要生成的Java类的属性和方法。最后编写生成代码的类,通过加载模板文件和数据模型,生成Java代码文件。本文提供了示例代码,并展示了文件目录结构。 ... [详细]
  • 本文介绍了使用C++Builder实现获取USB优盘序列号的方法,包括相关的代码和说明。通过该方法,可以获取指定盘符的USB优盘序列号,并将其存放在缓冲中。该方法可以在Windows系统中有效地获取USB优盘序列号,并且适用于C++Builder开发环境。 ... [详细]
  • Question该提问来源于开源项目:react-native-device-info/react-native-device-info ... [详细]
author-avatar
手机用户2602907485
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有