ijkplayer源码分析 视频向音频同步

前言

本文是流程分析的第六篇,分析ijkPlayer中的音视频同步,在video_refresh_thread中。

音视频同步基本概念

因为音视频是独立线程解码和输出的,如果不进行音视频同步输出的话,则播放时会各播各的,会出现音画不同步的现象,所以需要进行音视频同步输出。

  • 有三种同步策略
    视频同步到音频,即音频为主时钟
    音频同步到视频,即视频为主时钟
    视频、音频同步到外部时钟,即外部时钟(系统时间)为主时钟

本文分析视频同步到音频的,这也是业界普遍采用的一种方式。因为人们对声音的敏感度比视觉高,优先保证音频流畅播放,视频向音频同步,即对视频帧进行适当的丢帧或重复渲染以追赶或等待音频。

音视频同步用到的计量

通过音视频帧里的pts在播放进行校准,让视频帧重复渲染以等待音频、或让丢弃视频帧以追赶音频。

  • 时间单位
    ijk里面的时间单位是s,全都转换成了s,所以pts相关的类型都是double

  • AVRational
    ffmpeg里的一时间的刻度,标准基。
    如(1,25),那么时间的可短就是1/25,每一份是0.04s。
    那么pts=25时,即当前帧时刻为1s。

typedef struct AVRational{
    
    
    int num; ///< Numerator ,分子
    int den; ///< Denominator,分母
} AVRational;

typedef struct AVStream {
    
    
    AVRational time_base;
}
  • pts
    frame_queue中Frame帧,音视频解码线程里解码Packet后入队前赋值pts:
    frame->pts = avframe->pts * av_q2d(tb)
// Frame
typedef struct Frame {
    
    
    AVFrame *frame;
    double pts;
}
// ffmpeg
typedef struct AVFrame {
    
    
    int64_t pts;
}
  • frame_timer
    VideoState是在prepare阶段,stream_open中创建的,是全局的一个大杂烩的结构体,封装了各种运行阶段需要的东西。
    frame_timer指当前帧时刻,用于和系统标准时间比较,推进播放流程。
struct VideoState {
    
    
    double frame_timer;
}
  • master clock,主时钟。
    视频向音频对齐,在speed为0的情况下,该函数返回的就是pts。
static double get_master_clock(VideoState *is) {
    
    
    return is->audclk.pts;
}

音视频同步代码分析

在音视频同步过程中,有三个地方进行了同步:

  1. 渲染时若当前时刻小于当前帧截止时刻(当前帧时刻+当前帧显示时长),则修改remainingtime,让渲染线程睡一会,让这帧多渲染会
  2. 渲染时如果队列里多帧等待渲染,若当前系统时刻时间大于当前帧截止时刻(当前帧时刻+当前帧显示时长),则直接丢弃当前帧,再重新走渲染逻辑
  3. 解码时落后时间大于AV_NOSYNC_THRESHOLD,进入丢帧逻辑

渲染时音视频同步核心代码

  1. 渲染时若当前时刻小于当前帧截止时刻(当前帧时刻+当前帧显示时长),则修改remainingtime,让渲染线程睡一会,让这帧多渲染会
  2. 渲染时如果队列里多帧等待渲染,若当前系统时刻时间大于当前帧截止时刻(当前帧时刻+当前帧显示时长),则直接丢弃当前帧,再重新走渲染逻辑
    static void video_refresh(FFPlayer *opaque, double *remaining_time) {
    
    
            double last_duration, duration, delay;
            Frame *vp, *lastvp;

            /*
             * lastvp:上一帧,也就是当前正在显示的帧
             * vp: 要显示的帧
             *
             * vp这次将要显示的目标帧,lastvp是已经显示了的帧(也是当前屏幕上看到的帧),nextvp是下一次要显示的帧。
             */
            lastvp = frame_queue_peek_last(&is->pictq);
            vp = frame_queue_peek(&is->pictq);

            // 当前视频帧应该播放多长时间,根据pts计算
            last_duration = vp_duration(is, lastvp, vp); 

            /*
             * 计算当前帧还应该显示多长时间,即真正要显示多长时间
             */
            delay = compute_target_delay(ffp, last_duration, is);

            // 系统时刻
            time = av_gettime_relative() / 1000000.0;

            // 当前时刻小于当前帧截止时刻,则修改remainingtime,让渲染线程睡一会,让这帧多渲染会
            if (time < is->frame_timer + delay) {
    
    
                *remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);
                goto display; // 去渲染
            }

            // 队列里有多帧等待渲染,若当前系统时刻时间大于当前帧截止时刻,则直接丢弃当前帧,重新开始
            if (frame_queue_nb_remaining(&is->pictq) > 1)  {
    
     // 队列里有多帧
                Frame *nextvp = frame_queue_peek_next(&is->pictq);
                duration = vp_duration(is, vp, nextvp);

                if (!is->step &&
                    (ffp->framedrop > 0 ||
                     (ffp->framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER))
                    && time > is->frame_timer + duration // 系统时刻 > 该帧截止时刻
                        ) {
    
    
                    frame_queue_next(&is->pictq); // 丢弃,指针向下移一个
                    goto retry; // 重新开始
                }
            }
    }

解码时音视频同步核心代码

解码时落后时间大于AV_NOSYNC_THRESHOLD,进入丢帧逻辑,即不把该帧入队。
是解码的时候满足条件就丢弃该帧,不是一直丢,必须要满足条件才能丢。

软解的实现是,ffpipenode_ffplay_vdec.c,
硬解的实现是,ffpipenode_android_mediacodec_vdec.c,

均有如下逻辑:

        // 获取当前帧时刻
        dpts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);

        // frame_drop:允许丢帧么,同时也是丢帧的最大数据,即设置为120帧的话丢到120帧时就放过一帧,让这第121帧显示一下,后面的继续丢
        if (ffp->framedrop > 0 
            || (ffp->framedrop && ffp_get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) {
    
    

            ffp->stat.decode_frame_count++;

            if (frame->pts != AV_NOPTS_VALUE) {
    
    
                // 当前播放时刻与音频差值
                double diff = dpts - ffp_get_master_clock(is);

                if (!isnan(diff) 
                    && fabs(diff) < AV_NOSYNC_THRESHOLD 
                    // frame_last_filter_delay一般为0,即diff < 0,即当于落后时间>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);
                        if (frame->opaque) {
    
    
                            SDL_VoutAndroid_releaseBufferProxyP(opaque->weak_vout, (SDL_AMediaCodecBufferProxy **)&frame->opaque, false);
                        }
                        av_frame_unref(frame);
                        return ret;
                    }
                }
            }
        }

compute_target_delay函数解析


/*
 * @param: delay:当前播放帧的持续时长
 * @return: 当前帧真正应该持续多长时间
 */
static double compute_target_delay(FFPlayer *ffp, double delay, VideoState *is) {
    
    
    double sync_threshold, diff = 0;

    if (get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER) {
    
    

        // 当前视频帧与音频时钟的差,视频 - 音频
        diff = get_clock(&is->vidclk) - get_master_clock(is); 

        /*
         * 限制在[AV_SYNC_THRESHOLD_MIN,AV_SYNC_THRESHOLD_MAX]之间
         * delay < AV_SYNC_THRESHOLD_MIN,sync_threshold取AV_SYNC_THRESHOLD_MIN
         * delay > AV_SYNC_THRESHOLD_MAX,sync_threshold取AV_SYNC_THRESHOLD_MAX
         * 在中间的取delay
         */
        sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN, FFMIN(AV_SYNC_THRESHOLD_MAX, delay));

        if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD) {
    
    
            if (diff <= -sync_threshold) {
    
    
                // 视频落后音频,让当前帧快快结束,播放下一阵
                delay = FFMAX(0, delay + diff);
            } else if (diff >= sync_threshold) {
    
    
                if (delay > AV_SYNC_FRAMEDUP_THRESHOLD) {
    
    
                    // 当前帧已经显示了很多秒了,该动动了
                    delay = delay + diff;
                } else {
    
    
                    // 视频超前,超太前了,让当前帧多播放一会
                    delay = 2 * delay;
                }
            } else {
    
    
                // do nothing,在合理区间,返回去继续判断
            }
        }
    }

    if (ffp) {
    
    
        ffp->stat.avdelay = delay;
        ffp->stat.avdiff = diff;
    }

    return delay; // 返回的是delay
}

总代码

把代码贴一下,主要看注释,前面已经讲过核心了。

  • 渲染时总代码:
#define REFRESH_RATE 0.01

// 默认情况每10ms轮询一次
static int video_refresh_thread(void *arg) {
    
    
    FFPlayer *ffp = arg;
    VideoState *is = ffp->is;
    double remaining_time = 0.0;
    while (!is->abort_request) {
    
    
        if (remaining_time > 0.0)
            av_usleep((int) (int64_t) (remaining_time * 1000000.0));
        remaining_time = REFRESH_RATE;
        
        if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))
            video_refresh(ffp, &remaining_time); // 动态修改睡眠时间remaining_time
        }

    return 0;
}

static void video_refresh(FFPlayer *opaque, double *remaining_time) {
    
    
static void video_refresh(FFPlayer *opaque, double *remaining_time) {
    
    
    FFPlayer *ffp = opaque;
    VideoState *is = ffp->is;
    double time;

    Frame *sp, *sp2;

    if (!is->paused && get_master_sync_type(is) == AV_SYNC_EXTERNAL_CLOCK && is->realtime)
        check_external_clock_speed(is);

    if (!ffp->display_disable && is->show_mode != SHOW_MODE_VIDEO && is->audio_st) {
    
    
        time = av_gettime_relative() / 1000000.0;
        if (is->force_refresh || is->last_vis_time + ffp->rdftspeed < time) {
    
    
            video_display2(ffp);
            is->last_vis_time = time;
        }
        *remaining_time = FFMIN(*remaining_time, is->last_vis_time + ffp->rdftspeed - time);
    }

    if (is->video_st) {
    
    
        retry:
        if (frame_queue_nb_remaining(&is->pictq) == 0) {
    
    
            // nothing to do, no picture to display in the queue
            if (!ffp->first_video_frame_rendered) {
    
    
                live_log(ffp->inject_opaque, "[MTLive] video refresh, no picture to display\n");
                av_log(ffp, AV_LOG_DEBUG, "[MTLive] video refresh, no picture to display\n");
            }
        } else {
    
    
            av_log(ffp, AV_LOG_DEBUG,
                   "音视频同步:start===========================================================\n");

            if (!ffp->first_video_frame_rendered) {
    
    
                live_log(ffp->inject_opaque, "[MTLive] video refresh start\n");
                av_log(ffp, AV_LOG_DEBUG, "[MTLive] video refresh start\n");
            }
            double last_duration, duration, delay;
            Frame *vp, *lastvp;
            /* dequeue the picture */
            /*
             * lastvp:上一帧,也就是当前正在显示的帧
             * vp: 要显示的帧
             *
             * vp这次将要显示的目标帧,lastvp是已经显示了的帧(也是当前屏幕上看到的帧),nextvp是下一次要显示的帧。
             */
            lastvp = frame_queue_peek_last(&is->pictq);
            vp = frame_queue_peek(&is->pictq);

            if (vp->serial != is->videoq.serial) {
    
    
                // 不同序列号直接跳过
                frame_queue_next(&is->pictq);
                goto retry;
            }

            if (lastvp->serial != vp->serial) {
    
    
                av_log(ffp, AV_LOG_DEBUG, "音视频同步:frame_timer赋值1111111,%d, %d", lastvp->serial,
                       vp->serial);
                is->frame_timer = av_gettime_relative() / 1000000.0;
            }

            if (is->paused && ffp->first_video_frame_rendered)
                goto display;



            /* compute nominal last_duration */
            last_duration = vp_duration(is, lastvp, vp); // 当前视频帧应该播放多长时间,根据pts计算

            /*
             * 计算当前帧还应该显示多长时间,即真正要显示多长时间
             */
            delay = compute_target_delay(ffp, last_duration, is);

            // 系统时刻
            time = av_gettime_relative() / 1000000.0;

            av_log(ffp, AV_LOG_DEBUG,
                   "音视频同步:last_duration: %f, delay: %f, time: %f, frame_timer: %f\n",
                   last_duration, delay, time, is->frame_timer);

            // frame_timer > time,修正成time,跟系统时刻走
            if (isnan(is->frame_timer) || time < is->frame_timer) {
    
    
                av_log(ffp, AV_LOG_DEBUG, "音视频同步:frame_timer赋值22222222");
                is->frame_timer = time;
            }

            av_log(ffp, AV_LOG_DEBUG, "音视频同步:time: %f, frame_timer: %f\n", time,
                   is->frame_timer);

            // 当前时刻小于当前帧截止时刻,则修改remainingtime,让渲染线程睡一会,让这帧多渲染会
            if (time < is->frame_timer + delay) {
    
    
                *remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);

                av_log(ffp, AV_LOG_DEBUG, "音视频同步:继续显示remaining_time: goto display: %f",
                       *remaining_time);
                goto display; // 去渲染
            }

            // 推进播放时刻
            is->frame_timer += delay;

            // 播放时刻和系统时间的偏离太大 > AV_SYNC_THRESHOLD_MAX,则修正为系统时间
            if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX) {
    
    
                av_log(ffp, AV_LOG_DEBUG, "音视频同步:frame_timer赋值33333333");
                is->frame_timer = time;
            }

            SDL_LockMutex(is->pictq.mutex);
            if (!isnan(vp->pts))
                update_video_pts(is, vp->pts, vp->pos, vp->serial);
            SDL_UnlockMutex(is->pictq.mutex);

            av_log(ffp, AV_LOG_DEBUG, "音视频同步:frame_queue_nb_remaining %d\n",
                   frame_queue_nb_remaining(&is->pictq));

            // 队列里有多帧等待渲染,若当前系统时刻时间大于当前帧截止时刻,则直接丢弃当前帧,重新开始
            if (frame_queue_nb_remaining(&is->pictq) > 1) {
    
    
                Frame *nextvp = frame_queue_peek_next(&is->pictq);
                duration = vp_duration(is, vp, nextvp);
                if (!is->step &&
                    (ffp->framedrop > 0 ||
                     (ffp->framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER))
                    && time > is->frame_timer + duration // 系统时刻 > 该帧截止时刻
                        ) {
    
    
                    frame_queue_next(&is->pictq); // 丢弃,指针向下移一个

                    av_log(ffp, AV_LOG_DEBUG, "音视频同步:frame_queue_nb_remaining丢弃,goto retry\n");
                    av_log(ffp, AV_LOG_DEBUG,
                           "音视频同步:===========================================================end\n\n");
                    goto retry; // 重新开始
                }
            }

            frame_queue_next(&is->pictq); // 移动到下一帧
            is->force_refresh = 1;

            SDL_LockMutex(ffp->is->play_mutex);
            if (is->step) {
    
    
                is->step = 0;
                if (!is->paused)
                    stream_update_pause_l(ffp);
            }
            SDL_UnlockMutex(ffp->is->play_mutex);
        }
        display:
        /* display picture */
        av_log(ffp, AV_LOG_DEBUG, "音视频同步:display: %d, %d\n",
               is->force_refresh,
               is->pictq.rindex_shown);

        if (!ffp->display_disable
            && is->force_refresh
            && is->show_mode == SHOW_MODE_VIDEO
            && is->pictq.rindex_shown) {
    
    

            av_log(ffp, AV_LOG_DEBUG, "音视频同步:video_display2\n");
            av_log(ffp, AV_LOG_DEBUG,
                   "音视频同步:===========================================================end\n\n");

            video_display2(ffp); // 显示
        }
    }
    is->force_refresh = 0;
}

/*
 * @param: delay:当前播放帧的持续时长
 * @return: 当前帧真正应该持续多长时间
 */
static double compute_target_delay(FFPlayer *ffp, double delay, VideoState *is) {
    
    
    double sync_threshold, diff = 0;

    /* update delay to follow master synchronisation source */
    if (get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER) {
    
    
        /* if video is slave, we try to correct big delays by
           duplicating or deleting a frame */
        diff = get_clock(&is->vidclk) - get_master_clock(is); // 要播放的音视频帧pts的diff,视频 - 音频

        /* skip or repeat frame. We take into account the
           delay to compute the threshold. I still don't know
           if it is the best guess */
        /*
         * 限制在[AV_SYNC_THRESHOLD_MIN,AV_SYNC_THRESHOLD_MAX]之间
         * delay < AV_SYNC_THRESHOLD_MIN,sync_threshold取AV_SYNC_THRESHOLD_MIN
         * delay > AV_SYNC_THRESHOLD_MAX,sync_threshold取AV_SYNC_THRESHOLD_MAX
         * 在中间的取delay
         */
        sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN, FFMIN(AV_SYNC_THRESHOLD_MAX, delay));
        av_log(ffp, AV_LOG_DEBUG,
               "音视频同步:compute_target_delay#diff: %f, delay: %f, sync_threshold: %f\n", diff, delay,
               sync_threshold);

        /* -- by bbcallen: replace is->max_frame_duration with AV_NOSYNC_THRESHOLD */
//        if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD) {
    
    
//            if (diff <= -sync_threshold) {
    
    
//                // 视频落后音频,让当前帧快快结束,播放下一阵
//                delay = FFMAX(0, delay + diff);
//            } else if (diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD) {
    
    
//                //
//                delay = delay + diff;
//            } else if (diff >= sync_threshold) {
    
    
//                // 视频超前,超太前了,让当前帧多播放一会
//                delay = 2 * delay;
//            }
//        }

        if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD) {
    
    
            if (diff <= -sync_threshold) {
    
    
                // 视频落后音频,让当前帧快快结束,播放下一阵
                delay = FFMAX(0, delay + diff);
            } else if (diff >= sync_threshold) {
    
    
                if (delay > AV_SYNC_FRAMEDUP_THRESHOLD) {
    
    
                    // 当前帧已经显示了很多秒了,该动动了
                    delay = delay + diff;
                } else {
    
    
                    // 视频超前,超太前了,让当前帧多播放一会
                    delay = 2 * delay;
                }
            } else {
    
    
                // do nothing,在合理区间,返回去继续判断
            }
        }
    }

    if (ffp) {
    
    
        ffp->stat.avdelay = delay;
        ffp->stat.avdiff = diff;
    }
#ifdef FFP_SHOW_AUDIO_DELAY
    av_log(NULL, AV_LOG_TRACE, "video: delay=%0.3f A-V=%f\n",
            delay, -diff);
#endif

    return delay;
}

// 就是两帧pts之差
static double vp_duration(VideoState *is, Frame *vp, Frame *nextvp) {
    
    
    if (vp->serial == nextvp->serial) {
    
    
        double duration = nextvp->pts - vp->pts;
        if (isnan(duration) || duration <= 0 || duration > is->max_frame_duration)
            return vp->duration;
        else
            return duration;
    } else {
    
    
        return 0.0;
    }
}

static double get_master_clock(VideoState *is) {
    
    
    double val;

    switch (get_master_sync_type(is)) {
    
    
        case AV_SYNC_VIDEO_MASTER:
            val = get_clock(&is->vidclk);
            break;
        case AV_SYNC_AUDIO_MASTER:
            val = get_clock(&is->audclk);
            break;
        default:
            val = get_clock(&is->extclk);
            break;
    }
    return val;
}

// 在speed为0的情况下,return的就是pts
static double get_clock(Clock *c) {
    
    
    if (*c->queue_serial != c->serial)
        return NAN;
    if (c->paused) {
    
    
        return c->pts;
    } else {
    
    
        double time = av_gettime_relative() / 1000000.0;
        return c->pts_drift + time - (time - c->last_updated) * (1.0 - c->speed);
    }
}

static void set_clock_at(Clock *c, double pts, int serial, double time) {
    
    
    c->pts = pts;
    c->last_updated = time;
    c->pts_drift = c->pts - time;
    c->serial = serial;
}
  • 软解码时总代码
static int get_video_frame(FFPlayer *ffp, AVFrame *frame) {
    
    
    VideoState *is = ffp->is;
    int got_picture;

    ffp_video_statistic_l(ffp);
    if ((got_picture = decoder_decode_frame(ffp, &is->viddec, frame, NULL)) < 0) {
    
    
        error_log(ffp->inject_opaque, __func__, -1, "decoder_decode_frame() failed");
        live_log(ffp->inject_opaque, "decoder_decode_frame() failed");
        return -1;
    }
    // 针对丢帧的处理
    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); // 当前播放时刻与音频差值

                av_log(NULL, AV_LOG_INFO,
                       "音视频同步:get_video_frame, diff:%f, frame_last_filter_delay: %f, nb_packets: %d, continuous_frame_drops_early: %d\n",
                       diff,
                       is->frame_last_filter_delay,
                       is->videoq.nb_packets,
                       is->continuous_frame_drops_early);

                if (!isnan(diff)
                    && fabs(diff) < AV_NOSYNC_THRESHOLD
                    // frame_last_filter_delay一般为0,即diff < 0,即当于落后时间>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++;

                    // 丢帧丢到framedrop个
                    if (is->continuous_frame_drops_early > ffp->framedrop) {
    
    
                        av_log(NULL, AV_LOG_INFO,
                               "get_video_frame, continuous_frame_drops_early:%d\n",
                               is->continuous_frame_drops_early);
                        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;

                        av_log(NULL, AV_LOG_INFO,
                               "get_video_frame, av_frame_unref");

                    }
                }
            }
        }
    }

    return got_picture;
}

参考:
ffplay音视频同步分析——基础概念
ffplay音视频同步分析——视频同步音频

猜你喜欢

转载自blog.csdn.net/u014099894/article/details/112970720