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


    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    流程

    • 调用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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    流程

    • 调用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) },
    
    • 1
    • 2
    • 3
    • 4

    条件满足二者其一即可

    • 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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 序列号相同的情况下,调用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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    省略代码中还有针对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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    了解过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);
    
    • 1
    • 2
    • 3
    • 4

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • frame_queue_peek_writable取出当前队列的写节点

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

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

  • 相关阅读:
    CSP-J-2004-花生采摘
    软件测试基本常识
    革命带来的:机械化、电气化、自动化、智能化
    Java的垃圾回收机制详解——从入门到出土,学不会接着来砍我!
    LeetCode每日一题——1704. 判断字符串的两半是否相似
    语音识别技术paddlespeech的安装和使用
    【Matlab算法】G-N法求解非线性最小二乘优化问题(附G-N法MATLAB代码)
    三角定位是什么
    10.Python_结构型模式_代理模式
    极米科技H6 Pro 4K、H6 4K高亮定焦版——开启家用投影4K普及时代
  • 原文地址:https://blog.csdn.net/weixin_43874301/article/details/126482052