【ijkplayer】解码流程梳理(三)

37 篇文章 9 订阅
37 篇文章 2 订阅

ijkplayer

【ijkplayer】整体结构总结(一)

【ijkplayer】read_thread 流程梳理(二)

【ijkplayer】解码流程梳理(三)

【ijkplayer】渲染流程梳理(四)


【ijkplayer】解码流程梳理(三)

视频解码——video_thead

简化代码如下(删除滤镜相关代码,先不做分析)

static int video_thread(void *arg)
{
    AVRational tb = is->video_st->time_base;
    AVRational frame_rate = av_guess_frame_rate(is->ic, is->video_st, NULL);for (;;) {
        ret = get_video_frame(is, frame); // 解码获取一帧视频画面
        if (ret < 0)// 解码结束
            goto the_end;
        if (!ret)// 没有解码得到画面
            continue;
​
        duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);// 用帧率估计帧时长
        pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);// 将pts转化为秒为单位
        ret = queue_picture(is, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial);// 将解码后的帧存入FrameQueue
        av_frame_unref(frame);if (ret < 0)
            goto the_end;
    }
 the_end:av_frame_free(&frame);
    return 0;
}

流程

  • 调用get_video_frame方法,获取一帧解码的视频画面,赋值到frame中

  • 计算duration和pts

  • 调用queue_picture将解码后帧存入FrameQueue

get_video_frame

static int get_video_frame(VideoState *is, AVFrame *frame)
{
    int got_picture;

    if ((got_picture = decoder_decode_frame(&is->viddec, frame, NULL)) < 0)
        return -1;

    if (got_picture) {
        frame->sample_aspect_ratio = av_guess_sample_aspect_ratio(is->ic, is->video_st, frame);
    }
    // 丢帧处理
    if (got_picture) {
        double dpts = NAN;

        if (frame->pts != AV_NOPTS_VALUE)
            dpts = av_q2d(is->video_st->time_base) * frame->pts;

        frame->sample_aspect_ratio = av_guess_sample_aspect_ratio(is->ic, is->video_st, frame);

        if (ffp->framedrop>0 || (ffp->framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) {
            ffp->stat.decode_frame_count++;
            if (frame->pts != AV_NOPTS_VALUE) {
                double diff = dpts - get_master_clock(is);
                if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD &&
                    diff - is->frame_last_filter_delay < 0 &&
                    is->viddec.pkt_serial == is->vidclk.serial &&
                    is->videoq.nb_packets) {
                    is->frame_drops_early++;
                    is->continuous_frame_drops_early++;
                    if (is->continuous_frame_drops_early > ffp->framedrop) {
                        is->continuous_frame_drops_early = 0;
                    } else {
                        ffp->stat.drop_frame_count++;
                        ffp->stat.drop_frame_rate = (float)(ffp->stat.drop_frame_count) / (float)(ffp->stat.decode_frame_count);
                        av_frame_unref(frame);
                        got_picture = 0;
                    }
                }
            }
        }
    }

    return got_picture;
}

流程

  • 调用decoder_decode_frame进行解码操作,获取解码后的数据

  • 如果解码成功,在一定条件下,进入丢帧策略

if (ffp->framedrop>0 || (ffp->framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)){ "framedrop",                      "drop frames when cpu is too slow",
        OPTION_OFFSET(framedrop),       OPTION_INT(0, -1, 120) },

条件满足二者其一即可

  • framedrop>0

  • ffp->framedrop <0并且当前的同步时钟类型不是video的时候,在ffplay_options中,默认framedrop为-1

decoder_decode_frame

static int decoder_decode_frame(Decoder *d, AVFrame *frame, AVSubtitle *sub) {
    for (;;) {
        // 1. 流连续的情况下,不断调用avcodec_receive_frame获取解码后的frame
        if (d->queue->serial == d->pkt_serial) {
            do {
                ret = avcodec_receive_frame(d->avctx, frame);
                if (ret == AVERROR_EOF) {
                    return 0;
                }
                if (ret >= 0)
                    return 1;
            } while (ret != AVERROR(EAGAIN));
        }// 2. 取一个packet
        do {
            if (d->queue->nb_packets == 0)
                SDL_CondSignal(d->empty_queue_cond);
             if (d->packet_pending) {
                av_packet_move_ref(&pkt, &d->pkt);
                d->packet_pending = 0;
            }else{
                 if (packet_queue_get_or_buffering(d->queue, &pkt, 1, &d->pkt_serial) < 0)
                return -1;
             }
        } while (d->queue->serial != d->pkt_serial);// 3. 将packet送入解码器
        avcodec_send_packet(d->avctx, &pkt);
    }
}
  • 序列号相同的情况下,调用avcodec_receive_frame获取解码后的帧,失败跳出循环

  • 如果d->queue->nb_packets为空,则发送SDL_CondSignal(d->empty_queue_cond)信号,表示队列为空,通知读线程继续读数据,否则,调用packet_queue_get_or_buffering获取一个packet,此方法是一个阻塞方法
    将packet送入解码器

  • 这里有一个d->packet_pending的概念,表示在send_packet的时候,失败的packet.

if (avcodec_send_packet(d->avctx, &pkt) == AVERROR(EAGAIN)) {
    av_log(d->avctx, AV_LOG_ERROR, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
    d->packet_pending = 1;
    av_packet_move_ref(&d->pkt, &pkt);
}

省略代码中还有针对flush_pkt的处理

if (pkt.data == flush_pkt.data) {
    avcodec_flush_buffers(d->avctx);
    d->finished = 0;
    d->next_pts = d->start_pts;
    d->next_pts_tb = d->start_pts_tb;
}

了解过PacketQueue的代码,我们知道在往PacketQueue送入一个flush_pkt后,PacketQueueserial值会加1,而送入的flush_pktPacketQueueserial值保持一致。所以如果有“过时”Packet,过滤后,取到的第一个pkt将是flush_pkt。
根据分析的代码,已经可以获取到一帧解码好的数据了,接下来就需要将frame放入队列之中

duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);
pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
ret = queue_picture(is, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial);
av_frame_unref(frame);

queue_picture

static int queue_picture(VideoState *is, AVFrame *src_frame, double pts, double duration, int64_t pos, int serial)
{
    Frame *vp;if (!(vp = frame_queue_peek_writable(&is->pictq)))
        return -1;
​
    vp->sar = src_frame->sample_aspect_ratio;
    vp->uploaded = 0;
​
    vp->width = src_frame->width;
    vp->height = src_frame->height;
    vp->format = src_frame->format;
​
    vp->pts = pts;
    vp->duration = duration;
    vp->pos = pos;
    vp->serial = serial;set_default_window_size(vp->width, vp->height, vp->sar);av_frame_move_ref(vp->frame, src_frame);
    frame_queue_push(&is->pictq);
    return 0;
}
  • frame_queue_peek_writable取出当前队列的写节点

  • 简单的赋值操作,然后把该拷贝的拷贝给节点(struct Frame)保存,然后frame_queue_push,push节点到队列中。

音频/字幕 解码流程大同小异,就不重复分析了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后端码匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值