初窥门径-ffplay-PacketQueue结构体详细分析

PacketQueue详细介绍

packetQueue结构体说明

PacketQueue结构体在之前的文章中已经有所介绍,这里再重复一遍,已经熟知的可自行跳过。
代码具体实现如下:

typedef struct PacketQueue {
    AVFifo *pkt_list;       //数据存储缓存区域
    int nb_packets;         //包的数量, 即队列的元素数量
    int size;               //队列所有元素的数据大小的综合
    int64_t duration;       //队列所有节点播放时间总和
    int abort_request;      //用户请求退出标志
    int serial;             //播放序列号
    SDL_mutex *mutex;
    SDL_cond *cond;
} PacketQueue;

ffplay使用MyAVPacketList来保存解封装后的数据,而使用PacketQueue来存储MyAVpacketList数据。在函数packet_queue_put_private()会将外部传入的包存储进PacketQueuet中。AVFifo 是一个按字节存储数据的结构体,MyAVPacketList中的数据就存于该结构体中。
MyAVPacketList的serial字段的赋值来⾃PacketQueue的serial,每个PacketQueue的serial是独⽴的。
注意:_⾳频、视频、字幕流都有⾃⼰独⽴的PacketQueue。

PacketQueue相关API说明

以下先给出所有方法的一个总览:

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

代码实现如下:

static int packet_queue_init(PacketQueue *q)
{
    memset(q, 0, sizeof(PacketQueue));
    q->pkt_list = av_fifo_alloc2(1, sizeof(MyAVPacketList), AV_FIFO_FLAG_AUTO_GROW);
    if (!q->pkt_list)
        return AVERROR(ENOMEM);
    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;
}

ffpaly使用该函数来初始化PacketQueue结构体,主要是对PacketQueue的一些字段进行初始化, 创建锁和条件变量等等。

packet_queue_flush

ffpaly中使用这个函数来清空packetQueue的数据,包括释放掉其中的AVpacket数据, 代码实现如下:

static void packet_queue_flush(PacketQueue *q)
{
    MyAVPacketList pkt1;

    SDL_LockMutex(q->mutex);
    while (av_fifo_read(q->pkt_list, &pkt1, 1) >= 0)
        av_packet_free(&pkt1.pkt);                  //释放AVPacket的数据
    q->nb_packets = 0;
    q->size = 0;
    q->duration = 0;
    q->serial++;
    SDL_UnlockMutex(q->mutex);
}

av_fifo_read用于从AvFifo中读取数据到MyAVPacketList中。函数循环取出MyAVPacketList数据后,释放了其AVpacket的数据,并且将队列的属性归于空队列的状态。
该函数在退出播放时,或者进行seek操作时均会使用。

packet_queue_destory

代码实现如下:

static void packet_queue_destroy(PacketQueue *q)
{
    packet_queue_flush(q);      //清空队列的所有元素
    av_fifo_freep2(&q->pkt_list);
    SDL_DestroyMutex(q->mutex);
    SDL_DestroyCond(q->cond);
}

该函数用于销毁整个PacketQueue队列。先是调用了packet_queue_flush函数来清空队列的所有元素,在由函数av_fifo_freep2来释放q->pkt_list的内存,最后销毁锁和条件变量等。
这里看一下av_fifo_freep2函数的实现:

void av_fifo_freep2(AVFifo **f)
{
    if (*f) {
        av_freep(&(*f)->buffer);
        av_freep(f);
    }
}

可以看到其就是一个对AVFifo内存的一个释放,注意av_free和av_freep两个api的不同,av_freep除了会释放这个指针的内存外,还会将这个指针的值置为NULL。

packet_queue_start

ffplay在初始化队列之后,还不能直接使用哦,这里需要在使用packet_queue_start启动队列,实现如下:

static void packet_queue_start(PacketQueue *q)
{
    SDL_LockMutex(q->mutex);
    q->abort_request = 0;
    q->serial++;
    SDL_UnlockMutex(q->mutex);
}

这里会对serial加一,重置播放序列。同时将abort_request的值置为0,只有其值置为0后,视频才能播放。

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_private

ffplay使用这个函数实际向队列中插入元素,并处理相关的一些操作,代码实现如下:

static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt)
{
    MyAVPacketList pkt1;
    int ret;

    if (q->abort_request)
       return -1;

    pkt1.pkt = pkt;
    pkt1.serial = q->serial;        //用队列序列号标记节点

    ret = av_fifo_write(q->pkt_list, &pkt1, 1);
    if (ret < 0)
        return ret;

    //更新队列的相关属性
    q->nb_packets++;
    q->size += pkt1.pkt->size + sizeof(pkt1);
    q->duration += pkt1.pkt->duration;
    //发出信号,表明当前队列中有数据了,通知等待中的读线程可以取数据了
    SDL_CondSignal(q->cond);
    return 0;
}

该函数主要做了以下几件事:

  1. 确定MyPacketList的serial序列号,从此可以看出,MyPacketList的serial值其实是由PacketQueue来确定的。
  2. 调用av_fifo_write函数,将pkt1数据实际写入到队列中。
  3. 更新队列的一些属性,如大小,长度等。
  4. 发出信号,通知阻塞的读线程可以来读数据了。

packet_queue_put

往队列中插入一个AVPacket数据包,实现如下:

static int packet_queue_put(PacketQueue *q, AVPacket *pkt)
{
    AVPacket *pkt1;
    int ret;

    pkt1 = av_packet_alloc();
    if (!pkt1) {
        av_packet_unref(pkt);
        return -1;
    }
    av_packet_move_ref(pkt1, pkt);

    SDL_LockMutex(q->mutex);
    ret = packet_queue_put_private(q, pkt1);    //核心实现
    SDL_UnlockMutex(q->mutex);

    if (ret < 0)
        av_packet_free(&pkt1);                  //加入失败,释放packet

    return ret;
}

这里将pkt的数据移动到pkt1中,再调用packet_queue_put_private来向队列中插入数据,最后如果插入失败则销毁释放packet内存。

packet_queue_get

从队列中读取一个节点,代码实现如下:

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;
        }

        if (av_fifo_read(q->pkt_list, &pkt1, 1) >= 0) { //从队列中尝试拿数据
            //成功拿到数据
            q->nb_packets--;
            q->size -= pkt1.pkt->size + sizeof(pkt1);
            q->duration -= pkt1.pkt->duration;
            av_packet_move_ref(pkt, pkt1.pkt);  //将数据拷贝到pkt中
            if (serial)
                *serial = pkt1.serial;
            av_packet_free(&pkt1.pkt);
            ret = 1;
            break;
        } else if (!block) {    //没有读到数据,并且用户指定了非阻塞调用
            ret = 0;
            break;
        } else {                //没有读到数据,并且进行阻塞调用
            SDL_CondWait(q->cond, q->mutex);
        }
    }
    SDL_UnlockMutex(q->mutex);
    return ret;
}

该函数的总体流程:

  1. 加锁
  2. 进⼊for循环,如果需要退出for循环,则break;当没有数据可读且block为1时则等待
    ret = -1 终⽌获取packet
    ret = 0 没有读取到packet
    ret = 1 获取到了packet
  3. 释放锁

可以注意到这里等待的信号会在packet_queue_private_put中发出。

packet_queue_put_nullpacket

这个api在旧版本的ffplay中,是会实际插入一个空包的,表示流的结束和冲刷解码器。但新版的函数中,变成了接受一个包,并插入队列,实现如下:

static int packet_queue_put_nullpacket(PacketQueue *q, AVPacket *pkt, int stream_index)
{
    pkt->stream_index = stream_index;
    return packet_queue_put(q, pkt);
}

这里也给出旧标准的相同函数,以作对比

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);
}

新版如此的修改貌似使得该函数的名字和功能无关联了,我也不太理解做如此修改的意图是什么。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值