超简单做网站软件,wine wordpress theme,wordpress添加子主题,wordpress 被写入文件本文为相关课程的学习记录#xff0c;相关分析均来源于课程的讲解#xff0c;主要学习音视频相关的操作#xff0c;对字幕的处理不做分析
下面我们对ffplay的相关数据结构进行分析#xff0c;本章主要是对PacketQueue的讲解
struct MyAVPacketList和PacketQueue队列
ffp…本文为相关课程的学习记录相关分析均来源于课程的讲解主要学习音视频相关的操作对字幕的处理不做分析
下面我们对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 终⽌获取packetret 0 没有读取到packetret 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的作⽤展开分析。