ffplay源码分析1

ffplay源码分析

-------------------------------------------------------------------音视频同步部分

1、音视频同步 main-> event_loop-> refresh_loop_wait_event-> video_refresh

主函数里面,event_loop阻塞等待GUI操作发来的事件,在阻塞里面会有wait方法,再调用video_refresh进行音视频同步,并显示视频帧。

static void video_refresh(void *opaque, double *remaining_time)
{
    VideoState *is = opaque;
    double time;
    
    Frame *sp, *sp2;
    
    if (is->video_st) {
    retry:
        if (frame_queue_nb_remaining(&is->pictq) == 0) {
            // nothing to do, no picture to display in the queue
        } else {
            double last_duration, duration, delay;
            Frame *vp, *lastvp;
            
            //取得上一已播放的帧
            lastvp = frame_queue_peek_last(&is->pictq);
            //取得本帧(待播放的第一帧)
            vp = frame_queue_peek(&is->pictq);
            
            /*如果本帧的serial和视频packet队列的serial不想等,继续找下一帧,直到相等。
            也就是如果有seek操作,要显示seek操作后的帧
             */
            if (vp->serial != is->videoq.serial) {
                frame_queue_next(&is->pictq);
                goto retry;
            }
            
            /*
             如果本帧和上一帧的serial不想等,则使is->frame_timer等于当前系统时间。
             is->frame_timer是上一帧的播放时刻。
             */
            if (lastvp->serial != vp->serial)
                is->frame_timer = av_gettime_relative() / 1000000.0;
            
            //如果暂停,去显示上一帧
            if (is->paused)
                goto display;
            
            //计算vp.pts - lastvp.pts,也就是上一帧的播放时长
            last_duration = vp_duration(is, lastvp, vp);
            /*
             计算delay值,通过调节delay值来达到提前或延后播放本帧。
             如果视频落后于音频进度,则通过减小delay值,提前播放本帧(没等到last_duration的间隔,就播放),具体还要结合下面代码来判断。
             如果视频超前音频进度,则增加delay值,延后播放本帧(也就是要超过last_duration),先暂时显示上一帧。
             */
            delay = compute_target_delay(last_duration, is);
            
            time= av_gettime_relative()/1000000.0;
            /*
             当前时间 < (上一帧播放时刻 + delay),也就是当前时间小于本帧播放时刻,意味着本帧的播放时刻未到,
             则修改remaining_time,先显示上一帧
             */
            if (time < is->frame_timer + delay) {
                *remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);
                goto display;
            }
            //更新本帧播放时刻,更新后变为上一帧播放时刻
            is->frame_timer += delay;
            
            //如果当前时间和上一帧播放时刻差值超过阈值,则更新frame_timer为当前时间
            if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX)
                is->frame_timer = time;
            
            //更新视频时钟,目的是用于compute_target_delay函数中比较视频时钟和音频时钟上一帧的差值。
            SDL_LockMutex(is->pictq.mutex);
            if (!isnan(vp->pts))
                update_video_pts(is, vp->pts, vp->pos, vp->serial);
            SDL_UnlockMutex(is->pictq.mutex);
            
            //如果丢帧策略生效(framedrop),并且视频落后(下一帧的播放时刻 < 当前时间),则丢弃本帧
            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 && (framedrop>0 || (framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) && time > is->frame_timer + duration){
                    is->frame_drops_late++;
                    frame_queue_next(&is->pictq);
                    goto retry;
                }
            }
            //移动读索引rindex,使本帧变为上一帧
            frame_queue_next(&is->pictq);
            is->force_refresh = 1;
            
            if (is->step && !is->paused)
                stream_toggle_pause(is);
        }
    display:
        /* 显示视频帧*/
        if (!display_disable && is->force_refresh && is->show_mode == SHOW_MODE_VIDEO && is->pictq.rindex_shown)
            video_display(is);
    }
    is->force_refresh = 0;
    
}


static double compute_target_delay(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) {
        
        //时钟差值 = 最后播放的视频帧的pts - 主时钟最后播放的帧的pts(这里仅分析音频时钟)
        diff = get_clock(&is->vidclk) - get_master_clock(is);
        
        //阈值
        sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN, FFMIN(AV_SYNC_THRESHOLD_MAX, delay));
        if (!isnan(diff) && fabs(diff) < is->max_frame_duration) {
            if (diff <= -sync_threshold)    //视频落后于音频,并且超过阈值
                /*
                 diff是小于0的,delay是大于0的。
                 如果delay + diff = 本帧的pts,小于0(仍未追上音频),则delay=0,加速追赶音频。
                 否则 delay = delay + diff,表示经过delay时间,正好视频赶上音频
                 */
                delay = FFMAX(0, delay + diff);
            else if (diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD)
                delay = delay + diff;
            else if (diff >= sync_threshold)
                //视频超前音频,大于阈值,简单扩大2倍delay,等待音频
                delay = 2 * delay;
        }
    }
    
    av_log(NULL, AV_LOG_TRACE, "video: delay=%0.3f A-V=%f\n",
           delay, -diff);
    
    //如果diff未超过sync_threshold阈值,可以认为音视频仍然处于同步状态,直接返回delay值。
    return delay;
}

能力有限,前有大神分析到位,可以详看https://www.cnblogs.com/leisure_chn/p/10307089.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值