PacketQueue 总结
PacketQueue设计思路:
1 设计⼀个多线程安全的队列,保存AVPacket,同时统计队列内已缓存的数据⼤⼩。(这个统计数据会 ⽤来后续设置要缓存的数据量)
2 引⼊serial的概念,区别前后数据包是否连续,主要应⽤于seek操作。
3 设计了两类特殊的packet——flush_pkt和nullpkt(类似⽤于多线程编程的事件模型——往队列中放⼊ flush事件、放⼊null事件),其在⾳频输出、视频输出、播放控制等模块时也会继续使用到其来队列控制
1 内存管理
MyAVPacketList的内存是完全由PacketQueue维护的,在put的时候malloc,在get的时候free。
AVPacket分两块:
⼀部分是AVPacket结构体的内存,这部分从MyAVPacketList的定义可以看出是和MyAVPacketList 共存亡的。
另⼀部分是AVPacket字段指向的内存,这部分⼀般通过 av_packet_unref 函数释放。⼀般情况 下,是在get后由调⽤者负责⽤ av_packet_unref 函数释放。特殊的情况是当碰到 packet_queue_flush 或put失败时,这时需要队列⾃⼰处理。
2 serial的变化过程:
可以看到放⼊flush_pkt的时候后,serial增加了1. 假设,现在要从队头取出⼀个节点,那么取出的节点是serial 1,⽽PacketQueue⾃身的queue已经增⻓到了2
3源码函数剖析
packet_queue_init:初始化
packet_queue_destroy:销毁
packet_queue_start:启⽤
packet_queue_abort:中⽌
packet_queue_get:获取⼀个节点
packet_queue_put:存⼊⼀个节点
packet_queue_put_private:执行存入节点的真正函数
packet_queue_put_nullpacket:存⼊⼀个空节点
packet_queue_flush:清除队列内所有的节点
函数解析
packet_queue_init(初始化)
注意 一开始 q->abort_request = 1;
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;
return 0;
}
packet_queue_destroy(销毁)
packet_queue_flush 清楚所有节点 这个后面有说
static void packet_queue_destroy(PacketQueue *q)
{
packet_queue_flush(q); //先清除所有的节点
SDL_DestroyMutex(q->mutex);
SDL_DestroyCond(q->cond);
}
packet_queue_start(启动)
注意这里
q->abort_request = 0;
packet_queue_put_private(q, &flush_pkt); //这里放入了一个flush_pkt
当发现队列有flush_pkt 就会packet_queue_flush的
static void packet_queue_start(PacketQueue *q)
{
SDL_LockMutex(q->mutex);
q->abort_request = 0;
packet_queue_put_private(q, &flush_pkt); //这里放入了一个flush_pkt
SDL_UnlockMutex(q->mutex);
}
packet_queue_abort(中止)
q->abort_request = 1;
static void packet_queue_abort(PacketQueue *q)
{
SDL_LockMutex(q->mutex);
q->abort_request = 1; // 请求退出
SDL_CondSignal(q->cond); //释放一个条件信号
SDL_UnlockMutex(q->mutex);
}
packet_queue_get(获取一个节点)
逻辑和之前的播放器大差不差
该函数整体流程:
加锁进⼊for循环,如果需要退出for循环,则break;当没有数据可读且block为1时则等待
ret = -1 终⽌获取packet
ret = 0 没有读取到packet
ret = 1 获取到了packet 释放锁
注意这 三块代码
MyAVPacketList *pkt1;
*pkt = pkt1->pkt;
if (serial) //如果需要输出serial,把serial输出
*serial = pkt1->serial;
else if (!block) { //队列中没有数据,且非阻塞调用
ret = 0;
break;
} else { //队列中没有数据,且阻塞调用
//这里没有break。for循环的另一个作用是在条件变量满足后重复上述代码取出节点
SDL_CondWait(q->cond, q->mutex);
}
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--; //节点数减1
q->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); //释放节点内存,只是释放节点,而不是释放AVPacket
ret = 1;
break;
} else if (!block) {
//队列中没有数据,且非阻塞调用
ret = 0;
break;
} else {
//队列中没有数据,且阻塞调用
//这里没有break。for循环的另一个作用是在条件变量满足后重复上述代码取出节点
SDL_CondWait(q->cond, q->mutex);
}
}
SDL_UnlockMutex(q->mutex); // 释放锁
return ret;
}
packet_queue_put(存入一个节点)
主要2点
1 packet_queue_put_private
2 if (pkt != &flush_pkt && ret < 0)
av_packet_unref(pkt); //放入失败,释放AVPacket
这里是!=flush_pkt && ret<0
需要增加队列的播放序列号,以区分不连续的两段数据
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); //放入失败,释放AVPacket
return ret;
}
packet_queue_put_private(实现)
对于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进⾏分析。
重点
if (pkt == &flush_pkt)//如果放入的是flush_pkt,需要增加队列的播放序列号,以区分不连续的两段数据
{
q->serial++;
printf("q->serial = %d\n", q->serial);
}
pkt1->serial = q->serial;
是队列不是循环队列
q->nb_packets++;
q->size += pkt1->pkt.size + sizeof(*pkt1);
q->duration += pkt1->pkt.duration;
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;
else
q->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_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。
函数主体的for循环是队列遍历
遍历过程释放节点和AVPacket(AVpacket对应的数据也被释放掉)。
最后将PacketQueue的属性恢复为空队列状态
⽤于
退出播放和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);
}