IJKPLAYER源码分析-主要队列

 1 前言

    对IJKPLAYER播放器内核几个关键的队列的理解,将有助于掌控全局。因此,这里简要介绍所涉及到的几个关键队列实现:

  • PacketQueue:压缩数据队列,是一个带有首尾指针和回收单链表头指针的单链表结构,用来实现了队列,包括video、audio和subtitle均用此队列结构存储;
  • FrameQueue:解压数据队列,是一个由数组表达的环形队列,同样包括video、audio和subtitle均用此队列存储;
  • MessageQueue:其结构如同PacketQueue,不再赘述;

2 PacketQueue

    2.1 队列结构

    PacketQueue结构代码定义: 

typedef struct MyAVPacketList {
    AVPacket pkt;
    struct MyAVPacketList *next;
    int serial;
} MyAVPacketList;

typedef struct PacketQueue {
    MyAVPacketList *first_pkt, *last_pkt;
    int nb_packets;
    int size;
    int64_t duration;
    int abort_request;
    int serial;
    SDL_mutex *mutex;
    SDL_cond *cond;
} PacketQueue;
  • first_pkt:队列头指针,也即单链表的头指针;
  • last_pkt:队列尾指针,也即单链表的尾指针;

  • nb_packets:队列元素个数(AVPacket个数),即first_pkt所指向的单链表元素个数;

  • size:AVPacket *pkt;size += pkt1->pkt.size + sizeof(*pkt1),即该队列所有元素的总字节数,包括AVPacket管理所占空间;

  • duration:#define MIN_PKT_DURATION 15;q->duration += FFMAX(pkt1->pkt.duration, MIN_PKT_DURATION);

  • abort_request:关闭播放器请求,层层传递到队列;

  • serial:主要是针对于点播而言,代表1次不同的seek请求,以此区分seek前后的操作及数据;

2.2 队列大小 

    相关PacketQueue队列大小的外围限制源码:

#define MAX_QUEUE_SIZE (15 * 1024 * 1024)

{ "max-buffer-size",                    "max buffer size should be pre-read",
        OPTION_OFFSET(dcc.max_buffer_size), OPTION_INT(MAX_QUEUE_SIZE, 0, MAX_QUEUE_SIZE) },
{ "min-frames",                         "minimal frames to stop pre-reading",
        OPTION_OFFSET(dcc.min_frames),      OPTION_INT(DEFAULT_MIN_FRAMES, MIN_MIN_FRAMES, MAX_MIN_FRAMES) },

inline static void ffp_reset_demux_cache_control(FFDemuxCacheControl *dcc)
{
    dcc->min_frames                = DEFAULT_MIN_FRAMES;
    dcc->max_buffer_size           = MAX_QUEUE_SIZE;
}

    缺省情况下:

  • audio + video + subtitle队列size大小之和,<= max_buffer_size=15M;
  • audio / video / subtitle各自队列大小都 <= MIN_FRAMES=50000个AVPacket,对于video而言,即50000个帧压缩数据;

    以上2种情形,满足一个条件,队列即满。 

        /* if the queue are full, no need to read more */
        if (ffp->infinite_buffer<1 && !is->seek_req &&
#ifdef FFP_MERGE
              (is->audioq.size + is->videoq.size + is->subtitleq.size > MAX_QUEUE_SIZE
#else
              (is->audioq.size + is->videoq.size + is->subtitleq.size > ffp->dcc.max_buffer_size
#endif
            || (   stream_has_enough_packets(is->audio_st, is->audio_stream, &is->audioq, MIN_FRAMES)
                && stream_has_enough_packets(is->video_st, is->video_stream, &is->videoq, MIN_FRAMES)
                && stream_has_enough_packets(is->subtitle_st, is->subtitle_stream, &is->subtitleq, MIN_FRAMES)))) {
            if (!is->eof) {
                ffp_toggle_buffering(ffp, 0);
            }
            /* wait 10 ms */
            SDL_LockMutex(wait_mutex);
            SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
            SDL_UnlockMutex(wait_mutex);
            continue;
        }

2.3 何时继续读

     由上面分析可知,队列满后,会暂停调用av_read_frame读取AVPacket包,而是条件等待10ms,若is->continue_read_thread信号就绪,会返回以继续调用av_read_frame读AVPacket包;或者,10ms超时该信号仍未就绪,也会立刻返回,下一次loop判断队列是否满,若满则继续10ms等待上述逻辑,不然便会调用av_read_frame方法读取下一个AVPacket包。

    那么,continue_read_thread信号何时就绪呢?我们来继续分析:

static void decoder_init(Decoder *d, AVCodecContext *avctx, PacketQueue *queue, SDL_cond *empty_queue_cond) {
    memset(d, 0, sizeof(Decoder));
    d->avctx = avctx;
    d->queue = queue;
    d->empty_queue_cond = empty_queue_cond;
    d->start_pts = AV_NOPTS_VALUE;

    d->first_frame_decoded_time = SDL_GetTickHR();
    d->first_frame_decoded = 0;

    SDL_ProfilerReset(&d->decode_profiler, -1);
}

decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread);

    在初始化video解码器时,会调用decoder_init方法将 is->continue_read_thread赋值给Decoder的empty_queue_cond信号。Decoder的定义:

typedef struct Decoder {
    AVPacket pkt;
    AVPacket pkt_temp;
    PacketQueue *queue;
    AVCodecContext *avctx;
    int pkt_serial;
    int finished;
    int packet_pending;
    int bfsc_ret;
    uint8_t *bfsc_data;

    SDL_cond *empty_queue_cond;
    int64_t start_pts;
    AVRational start_pts_tb;
    int64_t next_pts;
    AVRational next_pts_tb;
    SDL_Thread *decoder_tid;

    SDL_Profiler decode_profiler;
    Uint64 first_frame_decoded_time;
    int    first_frame_decoded;
} Decoder;

     那么,何时发送is->empty_queue_cond信号呢?其实,是在video的解码线程里,其函数调用栈如下:

ffplay_video_thread => get_video_frame => decoder_decode_frame

    而后,在decoder_decode_frame方法里判断: 

if (d->queue->nb_packets == 0)
    SDL_CondSignal(d->empty_queue_cond);

    即:PacketQueue队列中的压缩数据,如已消费完毕便会发送该信号,继续av_read_frame读取AVPacket包,并同时开始缓冲。

    值得一提的是,由于audio、video和subtitle都会发射continue_read_thread信号,因此,此3者只要1个消费完毕,即会发送此信号,并开始缓冲。

2.4 播放缓冲

    PacketQueue的多少还会涉及到播放状态,若没有数据了,即开始加载,若队列满,则缓冲完毕,开始render播放。

    播放所涉及到的2个缓冲状态:

#define FFP_MSG_BUFFERING_START             500
#define FFP_MSG_BUFFERING_END               501
  • FFP_MSG_BUFFERING_START:无数据了,开始缓冲;
  • FFP_MSG_BUFFERING_END:队列满,缓冲完毕; 

    那么,在哪里通知开始缓冲的呢?有几种情况:

  • 解码时队列消费完毕:
  • seek请求:avformat_seek_file调用前后,开始seek了;

  • 重播:ijkmp_start_l => ffp_start_from_l;

    第1种情况调用栈:

ffplay_video_thread > get_video_frame => decoder_decode_frame => packet_queue_get_or_buffering => ffp_toggle_buffering(ffp, 1);

    第2种情况调用栈:

read_thread => if (is->seek_req) => ffp_toggle_buffering(ffp, 1) =>  avformat_seek_file => ffp_toggle_buffering(ffp, 1)

    第3种情况调用栈:

int ffp_start_from_l(FFPlayer *ffp, long msec)
{
    assert(ffp);
    VideoState *is = ffp->is;
    if (!is)
        return EIJK_NULL_IS_PTR;

    ffp->auto_resume = 1;
    ffp_toggle_buffering(ffp, 1);
    ffp_seek_to_l(ffp, msec);
    return 0;
}

     那么,何时通知缓冲完毕呢?有以下情况:

  • 缓冲队列满
  • 播放完毕
  • av_read_frame返回eof,即读取结束
  • 检查buffer状态发现队列满

3 FrameQueue

3.1 主结构

     FrameQueue结构代码定义: 

#define FRAME_QUEUE_SIZE 16
/* Common struct for handling all types of decoded data and allocated render buffers. */
typedef struct Frame {
    AVFrame *frame;
    AVSubtitle sub;
    int serial;
    double pts;           /* presentation timestamp for the frame */
    double duration;      /* estimated duration of the frame */
    int64_t pos;          /* byte position of the frame in the input file */
    int width;
    int height;
    int format;
    AVRational sar;
    int uploaded;
    int flip_v;
} Frame;

typedef struct FrameQueue {
    Frame queue[FRAME_QUEUE_SIZE];
    int rindex;
    int windex;
    int size;
    int max_size;
    int keep_last;
    int rindex_shown;
    SDL_mutex *mutex;
    SDL_cond *cond;
    PacketQueue *pktq;
} FrameQueue;
  •  queue[FRAME_QUEUE_SIZE]:是一个有FRAME_QUEUE_SIZE个Frame的数组,实际是环形队列,用以存储AVFrame,即解码后的video、audio和subtitle数据存在此处;
  • rindex:指向最近一个可读的Frame;
  • windex:指向下一个可写的Frame;
  • size:可display的帧个数;
  • max_size:queue环形队列最多存储FRAME_QUEUE_SIZE个Frame,但实际用多少个Frame的空间,是可以配置的,video缺省是3个Frame;
  • keep_last:此flag仅用于video,因为video显示时需缓存上次已显示的AVFrame,以计算duration,在队列初始化时设为1;audio和subtitle不需关注,缺省是0;
  • rindex_shown:与keep_last配合使用,仅用于video和audio,rindex + rindex_shown指向即将播放的帧,初始值为0,待显示1帧后,此值更新为1,后续不再变化;

  • pkt:FrameQueue所关联的PacketQueue;

3.2 keep_last & rindex_shown

    此2值仅用于video和audio,subtitle不用关注。

    keep_last在此处初始化:

static int frame_queue_init(FrameQueue *f, PacketQueue *pktq, int max_size, int keep_last)
{
    int i;
    memset(f, 0, sizeof(FrameQueue));
    if (!(f->mutex = SDL_CreateMutex())) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }
    if (!(f->cond = SDL_CreateCond())) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }
    f->pktq = pktq;
    f->max_size = FFMIN(max_size, FRAME_QUEUE_SIZE);
    f->keep_last = !!keep_last;
    for (i = 0; i < f->max_size; i++)
        if (!(f->queue[i].frame = av_frame_alloc()))
            return AVERROR(ENOMEM);
    return 0;
}

    在FrameQueue队列初始化,最后一个参数keep_last,video和audio传入1,而subtitle传入0:

    /* start video display */
    if (frame_queue_init(&is->pictq, &is->videoq, ffp->pictq_size, 1) < 0)
        goto fail;
    if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
        goto fail;
    if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0)
        goto fail;

    而后,仅在此方法里使用,其中rindex_shown在frame_queue_init初始化时设置为0:

static void frame_queue_next(FrameQueue *f)
{
    if (f->keep_last && !f->rindex_shown) {
        f->rindex_shown = 1;
        return;
    }
    frame_queue_unref_item(&f->queue[f->rindex]);
    if (++f->rindex == f->max_size)
        f->rindex = 0;
    SDL_LockMutex(f->mutex);
    f->size--;
    SDL_CondSignal(f->cond);
    SDL_UnlockMutex(f->mutex);
}

    而frame_queue_next方法在video或audio播放1帧之后调用,指向FrameQueue的下1个待播放的帧。 

    所以,frame_queue_peek方法实际返回的是下一个待播放的video或audio帧:

static Frame *frame_queue_peek(FrameQueue *f)
{
    return &f->queue[(f->rindex + f->rindex_shown) % f->max_size];
}

    而frame_queue_peek_next方法返回的是下下一个待播放的video或audio帧:    

static Frame *frame_queue_peek_next(FrameQueue *f)
{
    return &f->queue[(f->rindex + f->rindex_shown + 1) % f->max_size];
}

    而frame_queue_peek_last方法返回的是上一个已播放的video或audio帧:

static Frame *frame_queue_peek_last(FrameQueue *f)
{
    return &f->queue[f->rindex];
}

    那么,加入FrameQueue队列写满了,会怎么样?

static Frame *frame_queue_peek_writable(FrameQueue *f)
{
    /* wait until we have space to put a new frame */
    SDL_LockMutex(f->mutex);
    while (f->size >= f->max_size &&
           !f->pktq->abort_request) {
        SDL_CondWait(f->cond, f->mutex);
    }
    SDL_UnlockMutex(f->mutex);

    if (f->pktq->abort_request)
        return NULL;

    return &f->queue[f->windex];
}

    可以看到,如FrameQueue队列写满了,将条件等待该队列非满,方能写入1个帧。

    假如FrameQueue没有可读Frame,是空的,会怎么样呢?

static Frame *frame_queue_peek_readable(FrameQueue *f)
{
    /* wait until we have a readable a new frame */
    SDL_LockMutex(f->mutex);
    while (f->size - f->rindex_shown <= 0 &&
           !f->pktq->abort_request) {
        SDL_CondWait(f->cond, f->mutex);
    }
    SDL_UnlockMutex(f->mutex);

    if (f->pktq->abort_request)
        return NULL;

    return &f->queue[(f->rindex + f->rindex_shown) % f->max_size];
}

     可以看到,若FrameQueue队列空,则条件等待非空,才能读取数据。

4 MessageQueue

    其源码结构定义:

// based on PacketQueue in ffplay.c

typedef struct AVMessage {
    int what;
    int arg1;
    int arg2;
    void *obj;
    size_t len;
    void (*free_l)(void *obj);
    struct AVMessage *next;
} AVMessage;

typedef struct MessageQueue {
    AVMessage *first_msg, *last_msg;
    int nb_messages;
    int abort_request;
    SDL_mutex *mutex;
    SDL_cond *cond;

    AVMessage *recycle_msg;
    int recycle_count;
    int alloc_count;
} MessageQueue;

    可以看到,此队列是IJKPLAYER参照FFPLAY的PacketQueue而来,因此,不再赘述。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值