ffplay数据结构分析(一)
本文为相关课程的学习记录,相关分析均来源于课程的讲解,主要学习音视频相关的操作,对字幕的处理不做分析
下面我们对ffplay的相关数据结构进行分析,本章主要是对PacketQueue的讲解
struct MyAVPacketList和PacketQueue队列
ffplay⽤PacketQueue保存解封装后的数据,即保存AVPacket。 ffplay⾸先定义了⼀个结构体 MyAVPacketList:
typedef struct MyAVPacketList {
AVPacket pkt; //解封装后的数据
struct MyAVPacketList *next; //下⼀个节点
int serial; //播放序列
} MyAVPacketList;
可以理解为是队列的⼀个节点。可以通过其 next 字段访问下⼀个节点。
serial字段主要⽤于标记当前节点的播放序列号,ffplay中多处⽤到serial的概念,主要⽤来区分是否连续数据,每做⼀次seek,该serial都会做+1的递增,以区分不同的播放序列。
接着定义另⼀个结构体PacketQueue:
typedef struct PacketQueue { MyAVPacketList *first_pkt, *last_pkt; // 队⾸,队尾指针 3int nb_packets; // 包数量,也就是队列元素数量 int size; // 队列所有元素的数据⼤⼩总和 int64_t duration; // 队列所有元素的数据播放持续时间 int abort_request; // ⽤户退出请求标志 int serial; // 播放序列号,和MyAVPacketList的serial作⽤相同 SDL_mutex *mutex; // ⽤于维持PacketQueue的多线程安全(SDL_mutex可以按pthread_mutex_t理解) SDL_cond *cond; // ⽤于读、写线程相互通知(SDL_cond可以按pthread_cond_t理解)} PacketQueue;
该结构体内定义了“队列”⾃身的属性。上⾯的注释对每个字段作了简单的介绍,这⾥也看到了serial字段, MyAVPacketList的serial字段的赋值来⾃PacketQueue的serial,每个PacketQueue的serial是独⽴的。
⾳频、视频、字幕流都有⾃⼰独⽴的PacketQueue。
接下来我们也从队列的操作函数具体分析各个字段的含义。 PacketQueue 操作提供以下⽅法:
- packet_queue_init:初始化
- packet_queue_destroy:销毁
- packet_queue_start:启⽤
- packet_queue_abort:中⽌
- packet_queue_get:获取⼀个节点
- packet_queue_put:存⼊⼀个节点
- packet_queue_put_nullpacket:存⼊⼀个空节
- packet_queue_flush:清除队列内所有的节点
packet_queue_init()
初始化⽤于初始各个字段的值,并创建mutex和cond:
/* packet queue handling */
static int packet_queue_init(PacketQueue *q)
{memset(q, 0, sizeof(PacketQueue));q->mutex = SDL_CreateMutex();if (!q->mutex) {av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());return AVERROR(ENOMEM);}q->cond = SDL_CreateCond();if (!q->cond) {av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());return AVERROR(ENOMEM);}q->abort_request = 1; // 在packet_queue_start和packet_queue_abort 时修改到该值return 0;
}
packet_queue_destroy()
相应的,packet_queue_destroy()销毁过程负责清理mutex和cond:
static void packet_queue_destroy(PacketQueue *q)
{packet_queue_flush(q); //先清除所有的节点SDL_DestroyMutex(q->mutex);SDL_DestroyCond(q->cond);
}
packet_queue_start()
启动队列
static void packet_queue_start(PacketQueue *q)
{SDL_LockMutex(q->mutex);q->abort_request = 0;packet_queue_put_private(q, &flush_pkt); //这里放入了一个flush_pktSDL_UnlockMutex(q->mutex);
}
flush_pkt定义是 static AVPacket flush_pkt;,是⼀个特殊的packet,主要⽤来作为⾮连续的两段数据的“分界”标记:
- 插⼊ flush_pkt 触发PacketQueue其对应的serial,加1操作
- 触发解码器清空⾃身缓存 avcodec_flush_buffers(),以备新序列的数据进⾏新解码
packet_queue_abort()
中止队列
static void packet_queue_abort(PacketQueue *q)
{SDL_LockMutex(q->mutex);q->abort_request = 1; // 请求退出SDL_CondSignal(q->cond); //释放一个条件信号SDL_UnlockMutex(q->mutex);
}
这⾥SDL_CondSignal的作⽤在于确保当前等待该条件的线程能被激活并继续执⾏退出流程,并唤醒者会检测abort_request标志确定⾃⼰的退出流程。
packet_queue_put()
向队列中放入一个节点
static int packet_queue_put(PacketQueue *q, AVPacket *pkt)
{int ret;SDL_LockMutex(q->mutex);ret = packet_queue_put_private(q, pkt);//主要实现SDL_UnlockMutex(q->mutex);if (pkt != &flush_pkt && ret < 0)av_packet_unref(pkt); //放入失败,释放AVPacketreturn ret;
}
下面再来看看packet_queue_put_private的实现
static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt)
{MyAVPacketList *pkt1;if (q->abort_request) //如果已中止,则放入失败return -1;pkt1 = av_malloc(sizeof(MyAVPacketList)); //分配节点内存if (!pkt1) //内存不足,则放入失败return -1;// 没有做引用计数,那这里也说明av_read_frame不会释放替用户释放buffer。pkt1->pkt = *pkt; //拷贝AVPacket(浅拷贝,AVPacket.data等内存并没有拷贝)pkt1->next = NULL;if (pkt == &flush_pkt)//如果放入的是flush_pkt,需要增加队列的播放序列号,以区分不连续的两段数据{q->serial++;printf("q->serial = %d\n", q->serial++);}pkt1->serial = q->serial; //用队列序列号标记节点/* 队列操作:如果last_pkt为空,说明队列是空的,新增节点为队头;* 否则,队列有数据,则让原队尾的next为新增节点。 最后将队尾指向新增节点*/if (!q->last_pkt)q->first_pkt = pkt1;elseq->last_pkt->next = pkt1;q->last_pkt = pkt1;//队列属性操作:增加节点数、cache大小、cache总时长, 用来控制队列的大小q->nb_packets++;q->size += pkt1->pkt.size + sizeof(*pkt1);q->duration += pkt1->pkt.duration;/* XXX: should duplicate packet data in DV case *///发出信号,表明当前队列中有数据了,通知等待中的读线程可以取数据了SDL_CondSignal(q->cond);return 0;
}
对于packet_queue_put_private主要完成3件事:
- 计算serial。serial标记了这个节点内的数据是何时的。⼀般情况下新增节点与上⼀个节点的serial是⼀ 样的,但当队列中加⼊⼀个flush_pkt后,后续节点的serial会⽐之前⼤1,⽤来区别不同播放序列的 packet.
- 节点⼊队列操作。
- 队列属性操作。更新队列中节点的数⽬、占⽤字节数(含AVPacket.data的⼤⼩)及其时⻓。主要⽤来控制Packet队列的⼤⼩,我们PacketQueue链表式的队列,在内存充⾜的条件下我们可以⽆限put⼊packet,如果我们要控制队列⼤⼩,则需要通过其变量size、duration、nb_packets三者单⼀或者综 合去约束队列的节点的数量,具体在read_thread进⾏分析。
packet_queue_get()
从队列中获取一个数据
/*** @brief packet_queue_get* @param q 队列* @param pkt 输出参数,即MyAVPacketList.pkt* @param block 调用者是否需要在没节点可取的情况下阻塞等待* @param serial 输出参数,即MyAVPacketList.serial* @return <0: aborted; =0: no packet; >0: has packet*/
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial)
{MyAVPacketList *pkt1;int ret;SDL_LockMutex(q->mutex); // 加锁for (;;) {if (q->abort_request) {ret = -1;break;}pkt1 = q->first_pkt; //MyAVPacketList *pkt1; 从队头拿数据if (pkt1) { //队列中有数据q->first_pkt = pkt1->next; //队头移到第二个节点if (!q->first_pkt)q->last_pkt = NULL;q->nb_packets--; //节点数减1q->size -= pkt1->pkt.size + sizeof(*pkt1); //cache大小扣除一个节点q->duration -= pkt1->pkt.duration; //总时长扣除一个节点//返回AVPacket,这里发生一次AVPacket结构体拷贝,AVPacket的data只拷贝了指针*pkt = pkt1->pkt;if (serial) //如果需要输出serial,把serial输出*serial = pkt1->serial;av_free(pkt1); //释放节点内存,只是释放节点,而不是释放AVPacketret = 1;break;} else if (!block) { //队列中没有数据,且非阻塞调用ret = 0;break;} else { //队列中没有数据,且阻塞调用//这里没有break。for循环的另一个作用是在条件变量满足后重复上述代码取出节点SDL_CondWait(q->cond, q->mutex);}}SDL_UnlockMutex(q->mutex); // 释放锁return ret;
}
该函数整体流程:
- 加锁进⼊for循环,如果需要退出for循环,则break;
- 当没有数据可读且block为1时则等待
- ret = -1 终⽌获取packet
- ret = 0 没有读取到packet
- ret = 1 获取到了packet
- 释放锁
如果有取到数据,主要分3个步骤:- 队列操作:出队列操作;
nb_packets数目相应-1;duration的也相应减少,size也相应占⽤的字节⼤⼩(pkt1->pkt.size + sizeof(*pkt1)) - 给输出参数赋值:就是MyAVPacketList的成员传递给输出参数pkt和serial
- 释放节点内存:释放放⼊队列时申请的节点内存(注意是节点内存⽽不是AVPacket的数据的内存)
- 队列操作:出队列操作;
packet_queue_put_nullpacket()
放⼊“空包”(nullpacket)。放⼊空包意味着流的结束,⼀般在媒体数据读取完成的时候放⼊空包。放⼊空包,⽬的是为了冲刷解码器,将编码器⾥⾯所有frame都读取出来:
static int packet_queue_put_nullpacket(PacketQueue *q, int stream_index)
{AVPacket pkt1, *pkt = &pkt1;av_init_packet(pkt);pkt->data = NULL;pkt->size = 0;pkt->stream_index = stream_index;return packet_queue_put(q, pkt);
}
⽂件数据读取完毕后刷⼊空包。
packet_queue_flush()
packet_queue_flush⽤于将packet队列中的所有节点清除,包括节点对应的AVPacket。
- ⽐如⽤于退出播放和seek播放:
- 退出播放,则要清空packet queue的节点
- seek播放,要清空seek之前缓存的节点数据,以便插⼊新节点数据
static void packet_queue_flush(PacketQueue *q)
{MyAVPacketList *pkt, *pkt1;SDL_LockMutex(q->mutex);for (pkt = q->first_pkt; pkt; pkt = pkt1) {pkt1 = pkt->next;av_packet_unref(&pkt->pkt);av_freep(&pkt);}q->last_pkt = NULL;q->first_pkt = NULL;q->nb_packets = 0;q->size = 0;q->duration = 0;SDL_UnlockMutex(q->mutex);
}
函数主体的for循环是队列遍历,遍历过程释放节点和AVPacket(AVpacket对应的数据也被释放掉)。最后将PacketQueue的属性恢复为空队列状态。
PacketQueue总结 前⾯我们分析了PacketQueue的实现和主要的操作⽅法,现在总结下两个关键的点:
第⼀,PacketQueue的内存管理:

MyAVPacketList的内存是完全由PacketQueue维护的,在put的时候malloc,在get的时候free。
AVPacket分两块:
- ⼀部分是AVPacket结构体的内存,这部分从MyAVPacketList的定义可以看出是和MyAVPacketList 共存亡的。
- 另⼀部分是AVPacket字段指向的内存,这部分⼀般通过 av_packet_unref 函数释放。⼀般情况下,是在get后由调⽤者负责⽤ av_packet_unref 函数释放。特殊的情况是当碰到 packet_queue_flush 或put失败时,这时需要队列⾃⼰处理。
第⼆,serial的变化过程:

如上图所示,左边是队头,右边是队尾,从左往右标注了4个节点的serial,以及放⼊对应节点时queue的 serial。
可以看到放⼊flush_pkt的时候后,serial增加了1.
假设,现在要从队头取出⼀个节点,那么取出的节点是serial 1,⽽PacketQueue⾃身的queue已经增⻓到了2。
PacketQueue设计思路: 1. 设计⼀个多线程安全的队列,保存AVPacket,同时统计队列内已缓存的数据⼤⼩。(这个统计数据会 ⽤来后续设置要缓存的数据量)
2. 引⼊serial的概念,区别前后数据包是否连续,主要应⽤于seek操作。
3. 设计了两类特殊的packet——flush_pkt和nullpkt(类似⽤于多线程编程的事件模型——往队列中放⼊ flush事件、放⼊null事件),我们在⾳频输出、视频输出、播放控制等模块时也会继续对flush_pkt和 nullpkt的作⽤展开分析。
相关文章:
ffplay数据结构分析(一)
本文为相关课程的学习记录,相关分析均来源于课程的讲解,主要学习音视频相关的操作,对字幕的处理不做分析 下面我们对ffplay的相关数据结构进行分析,本章主要是对PacketQueue的讲解 struct MyAVPacketList和PacketQueue队列 ffp…...
JavaWeb学习|JSP相关内容
1.什么是JSP Java Server Pages: Java服务器端页面,也和Servlet一样,用于动态Web技术! 最大的特点: 。写JSP就像在写HTML 。区别: 。HTML只给用户提供静态的数据 。JSP页面中可以嵌入JAVA代码,为用户提供动态数据 JSP最终也会被转换成为一…...
Springboot后端通过路径映射获取本机图片资源
项目场景: 项目中对图片的处理与查看是必不可少的,本文将讲解如何通过项目路径来获取到本机电脑的图片资源 如图所示,在我的本机D盘的图片测试文件夹(文件夹名字不要有中文)下有一些图片, 我们要在浏览器上访问到这些图片&#…...
【IDEA + Spark 3.4.1 + sbt 1.9.3 + Spark MLlib 构建鸢尾花决策树分类预测模型】
决策树进行鸢尾花分类的案例 背景说明: 通过IDEA Spark 3.4.1 sbt 1.9.3 Spark MLlib 构建鸢尾花决策树分类预测模型,这是一个分类模型案例,通过该案例,可以快速了解Spark MLlib分类预测模型的使用方法。 依赖 ThisBuild /…...
亚马逊 EC2服务器下部署java环境
1. jdk 1.8 安装 1.1 下载jdk包 官网 Java Downloads | Oracle tar.gz 包 下载下来 1.2 本地连接 服务器 我用的是亚马逊的ec2 系统是 ubuntu 的 ssh工具是 Mobaxterm , 公有dns 创建实例时的秘钥 链接 Mobaxterm 因为使用的 ubuntu 所以登录的 名称 就是 ubuntu 然后 …...
CTF流量题解http1.pcapng
使用Wireshark工具打开流量文件http1.pcapng,如下图所示。 在过滤检索栏输入http,wireshark自动进行过滤。...
若依vue前端有全局用户信息变量吗
"若依"是一个基于SpringBoot和Vue的前后端分离的开源项目。在前端Vue部分,全局用户信息通常保存在Vuex中,Vuex是Vue.js的状态管理模式。它提供了一个集中式存储来管理所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生…...
什么是Milvus
原文出处:https://www.yii666.com/blog/393941.html 什么是Milvus Milvus 是一款云原生向量数据库,它具备高可用、高性能、易拓展的特点,用于海量向量数据的实时召回。 Milvus 基于 FAISS、Annoy、HNSW 等向量搜索库构建,核心是…...
如何快速实现三菱FX3U程序的无线下载?
1.系统概述 三菱PLC FX3u可以使用专用下载线通过计算机串口下载程序,同样也可以使用自制下载线缆,连接无线模块 DTD435M进行远程无线下载程序,计算机端采用RS232或者RS485 将计算机端与无线模块连接,PLC端同样使用RS232转RS485将…...
Flink源码之RPC
Flink是一个典型的Master/Slave分布式实时处理系统,分布式系统组件之间必然涉及通信,也即RPC,以下图展示Flink组件之间的关系: RPCGateWay 一般RPC框架可根据用户业务类生成客户端和服务器端通信底层代码,此时只需定…...
【LeetCode 75】第二十四题(2390)从字符串中移除星号
目录 题目: 示例: 分析: 代码运行结果: 题目: 示例: 分析: 题目给我们一个字符串,然后字符串中包含星号*,要求每个星号消除一个从星号左边起最近的一个字符…...
通向架构师的道路之weblogic的集群与配置
一、Weblogic的集群 还记得我们在第五天教程中讲到的关于Tomcat的集群吗? 两个tomcat做node即tomcat1, tomcat2,使用Apache HttpServer做请求派发。 现在看看WebLogic的集群吧,其实也差不多。 区别在于: Tomcat的集群的实现为两个物理上…...
SpringBoot 项目创建与运行
一、Spring Boot 1、什么是Spring Boot?为什么要学 Spring Boot Spring 的诞生是为了简化 Java 程序的开发的,而 Spring Boot 的诞生是为了简化 Spring 程序开发的。 Spring Boot 翻译一下就是 Spring 脚手架 盖房子的这个架子就是脚手架,…...
FOHEART H1数据手套:连接虚拟与现实,塑造智能交互新未来
在全新交互时代背景中,数据手套无疑是一种重要的科技产物。它不仅彻底改变了我们与虚拟世界的互动方式,更为我们提供了一种全新、更为直观的交互形式。 FOHEART H1数据手套结合了虚拟现实、手势识别等高新技术,用先进的传感技术和精准的数据…...
MyBatis学习笔记3
日志 1.日志工厂 如果一个数据库的操作,出现了异常,我们需要排错。日志就是最好的工具。 日志工厂:SLF4JLOG4J(掌握)LOG4J2JDK_LOGGINGCOMMONS_LOGGINGSTDOUT_LOGGING(掌握)NO_LOGGING 2.分页 减少数据…...
ES6学习-Symbol
Symbol 数据类型Symbol,表示独一无二的值。 对象的属性名可有两种类型,一种是原来的字符串,另一种是新增的 Symbol 类型 可以保证不与其他属性名产生冲突。 let s1 Symbol() let s2 Symbol() console.log(s1, s2, s1 s2)//Symbol() Sy…...
【Redis】使用Docker镜像配置集群时的Operation timed out问题
不知道有没有小伙伴跟我一样是使用的Docker镜像进行Redis集群案例模拟的(三台虚拟机确实带不动 ),然后我遇到了一个问题:Could not connect to Redis at 172.17.0.2:6379: Operation timed out 172.17.0.2是我其中一个Redis实例的…...
Java 生产初学常用注解
目录 0. 基础语法逻辑运算符继承抛出异常获取数据方式泛型 1. 接收前端数据(controller)mybatis1. QueryWrapper获取和赋值 2. service 层注解 3. Dao 层(与数据库交互)3.1 mybatis-plus中BaseMapper 4. ELK框架es配置sql参数logs…...
mousedown拖拽功能(vue3+ts)
因为项目有rem适配,使用第三方插件无法处理适配问题,所有只能自己写拖拽功能了 拖拽一般都会想到按下,移动,放开,但是本人亲测,就在div绑定一个按下事件就行了(在事件里面写另外两个事件&#x…...
【论文阅读】基于深度学习的时序异常检测——TransAD
系列文章链接 数据基础:多维时序数据集简介 论文一:2022 Anomaly Transformer:异常分数预测 论文二:2022 TransAD:异常分数预测 论文链接:TransAD.pdf 代码库链接:https://github.com/imperial…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用
文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么?1.1.2 感知机的工作原理 1.2 感知机的简单应用:基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...
从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...
