【FFLAY】 MyAVPacketList和PacketQueue队列

ffplay⽤PacketQueue保存解封装后的数据,即保存AVPacket。

typedef struct PacketQueue {
MyAVPacketList *first_pkt, *last_pkt;     // 队⾸,队尾指针
int 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;

MyAVPacketList

1 是队列的⼀个节点。可以通过其 next 字段访问下⼀个节点。
2 serial字段主要⽤于标记当前节点的播放序列号,ffplay中多处⽤到serial的概念,主要⽤来区分是否连续数据,每做⼀次seek,该serial都会做+1的递增,以区分不同的播放序列。

typedef struct MyAVPacketList {
    AVPacket pkt;     //解封装后的数据
    struct MyAVPacketList *next; //下⼀个节点
    int serial;       //播放序列
} MyAVPacketList;

3 MyAVPacketList的serial字段的赋值来⾃PacketQueue的serial

static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt)
// AVPacket  要转成 MyAVPacketList才能插入
{
    MyAVPacketList *pkt1;

    if (q->abort_request)
       return -1;

    pkt1 = av_malloc(sizeof(MyAVPacketList));
    if (!pkt1)
        return -1;
    pkt1->pkt = *pkt;
    pkt1->next = NULL;
    if (pkt == &flush_pkt)
        q->serial++;
    pkt1->serial = q->serial;

    if (!q->last_pkt)
        q->first_pkt = pkt1;
    else
        q->last_pkt->next = pkt1;
    q->last_pkt = pkt1;
    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;
}

⾳频、视频、字幕流都有⾃⼰独⽴的PacketQueue。
PacketQueue audioq;
PacketQueue videoq;
PacketQueue subtitleq;
每个PacketQueue的serial是独⽴的
(可以看出 更新serial 要给每个PacketQueue进行刷新)

        if (is->seek_req) {
            int64_t seek_target = is->seek_pos;
            int64_t seek_min    = is->seek_rel > 0 ? seek_target - is->seek_rel + 2: INT64_MIN;
            int64_t seek_max    = is->seek_rel < 0 ? seek_target - is->seek_rel - 2: INT64_MAX;
// FIXME the +-2 is due to rounding being not done in the correct direction in generation
//      of the seek_pos/seek_rel variables

            ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags);
            if (ret < 0) {
                av_log(NULL, AV_LOG_ERROR,
                       "%s: error while seeking\n", is->ic->filename);
            } else {
                if (is->audio_stream >= 0) {
                    packet_queue_flush(&is->audioq);
                    packet_queue_put(&is->audioq, &flush_pkt);
                }
                if (is->subtitle_stream >= 0) {
                    packet_queue_flush(&is->subtitleq);
                    packet_queue_put(&is->subtitleq, &flush_pkt);
                }
                if (is->video_stream >= 0) {
                    packet_queue_flush(&is->videoq);
                    packet_queue_put(&is->videoq, &flush_pkt);
                }
                if (is->seek_flags & AVSEEK_FLAG_BYTE) {
                   set_clock(&is->extclk, NAN, 0);
                } else {
                   set_clock(&is->extclk, seek_target / (double)AV_TIME_BASE, 0);
                }
            }
            is->seek_req = 0;
            is->queue_attachments_req = 1;
            is->eof = 0;
            if (is->paused)
                step_to_next_frame(is);
        }

一 初始化与销毁

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

static void packet_queue_destroy(PacketQueue *q)
{
    packet_queue_flush(q);//先清除所有的节点
    SDL_DestroyMutex(q->mutex);
    SDL_DestroyCond(q->cond);
}

问题
1 audio video 字幕 的 PacketQueue 都是唯一的,为啥还需要锁跟信号量?
2 abort_request 为啥在 destroy 中没有搞?
3 还有更好的函数可以做清除嘛? packet_queue_flush

二 插入数据 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++;
    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_private主要完成3件事:
1 计算serial。serial标记了这个节点内的数据是何时的。⼀般情况下新增节点与上⼀个节点的serial是⼀
样的,但当队列中加⼊⼀个flush_pkt后,后续节点的serial会⽐之前⼤1,⽤来区别不同播放序列的
packet.
2 节点⼊队列操作。
3 队列属性操作。更新队列中节点的数⽬、占⽤字节数(含AVPacket.data的⼤⼩)及其时⻓。主要⽤来
控制Packet队列的⼤⼩,我们PacketQueue链表式的队列,在内存充⾜的条件下我们可以⽆限put⼊
packet,如果我们要控制队列⼤⼩,则需要通过其变量size、duration、nb_packets三者单⼀或者综
合去约束队列的节点的数量,具体在read_thread进⾏分析。

二 问题
1 SDL_CondSignal(q->cond); 通知机制是什么样的?
2 pkt.duration 怎么来的?
3 pkt1->pkt.size 怎么来的?

三 取出数据 packet_queue_get

static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial)
//param block 调⽤者是否需要在没节点可取的情况下阻塞等待
{
    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--;   //put 的反操做
            q->size -= pkt1->pkt.size + sizeof(*pkt1);
            q->duration -= pkt1->pkt.duration;
            *pkt = pkt1->pkt;
            if (serial)   //如果需要输出serial,把serial输出
                *serial = pkt1->serial;
            av_free(pkt1);
            ret = 1;
            break;
        } else if (!block) {   //队列中没有数据,且⾮阻塞调⽤
            ret = 0;
            break;
        } else {   //队列中没有数据,且阻塞调⽤
        //这⾥没有break。for循环的另⼀个作⽤是在条件变量满⾜后重复上述代码取出节点
            SDL_CondWait(q->cond, q->mutex);
        }
    }
    SDL_UnlockMutex(q->mutex);
    return ret;
}

四 不同类型 AVPacket 策略

五 限制PacketQueue队列里面的packet个数

        if (infinite_buffer<1 &&
              (is->audioq.size + is->videoq.size + is->subtitleq.size > MAX_QUEUE_SIZE
            || (stream_has_enough_packets(is->audio_st, is->audio_stream, &is->audioq) &&
                stream_has_enough_packets(is->video_st, is->video_stream, &is->videoq) &&
                stream_has_enough_packets(is->subtitle_st, is->subtitle_stream, &is->subtitleq)))) {
            /* wait 10 ms */
            SDL_LockMutex(wait_mutex);
            SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
            SDL_UnlockMutex(wait_mutex);
            continue;
        }
  1. audioq,videoq,subtitleq三个PacketQueue的总字节数达到了MAX_QUEUE_SIZE(15M,为什么
    是15M?这⾥只是⼀个经验计算值,⽐如4K视频的码率以50Mbps计算,则15MB可以缓存2.4秒,从
    这么计算实际上如果我们真的是播放4K⽚源,15MB是偏⼩的数值,有些⽚源⽐较坑 同⼀个⽂件位置
    附近的pts差值超过5秒,此时如果视频要缓存5秒才能做同步,那15MB的缓存⼤⼩就不够了)
  2. ⾳频、视频、字幕流都已有够⽤的包(stream_has_enough_packets)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值