ffplay---源码分析(十一):同步到外部时钟

        前面我们分析了音视频同步中的两种策略:视频同步到音频,以及音频同步到视频。接下来要分析的是第三种,音频和视频都同步到外部时钟。

 

回顾

先回顾下前面两种同步策略。

视频同步到音频主要由函数compute_target_delay计算出lastvp应显示时长,并通过frame_timer对比系统时间控制输出,最后在video_refresh中更新了video clock(vidclk)。

static double compute_target_delay(double delay, VideoState *is)
{
    //A. 只要主时钟不是video,就需要作同步校正
    if (get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER) {
        diff = get_clock(&is->vidclk) - get_master_clock(is);
    }
    return delay;
}
​
static void video_refresh(void *opaque, double *remaining_time)
{
    delay = compute_target_delay(last_duration, is);
    if (time < is->frame_timer + delay) {
        goto display;
    }
​
    //B. 更新vidclk,同时更新extclk
    update_video_pts(is, vp->pts, vp->pos, vp->serial);
}
​
static void update_video_pts(VideoState *is, double pts, int64_t pos, int serial) {
    set_clock(&is->vidclk, pts, serial);
    sync_clock_to_slave(&is->extclk, &is->vidclk);
}

注意这里的两点:

A. 只要主时钟不是video,就需要作同步校正

B. 更新vidclk,同时更新extclk

再看音频同步到视频。主要由函数synchronize_audio计算校正后应输出的样本数,然后通过libswresample库重采样输出。

static int synchronize_audio(VideoState *is, int nb_samples)
{
    //C. 只要主时钟不是audio,就需要作同步校正
    if (get_master_sync_type(is) != AV_SYNC_AUDIO_MASTER) {
        diff = get_clock(&is->audclk) - get_master_clock(is);
    }
    return wanted_nb_samples;
}
​
static int audio_decode_frame(VideoState *is)
{
    wanted_nb_samples = synchronize_audio(is, af->frame->nb_samples);
    return resampled_data_size;
}
​
static void sdl_audio_callback(void *opaque, Uint8 *stream, int len)
{
    audio_size = audio_decode_frame(is);
​
    //D. 更新audclk,同时更新extclk
    set_clock_at(&is->audclk, is->audio_clock - (double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec, is->audio_clock_serial, audio_callback_time / 1000000.0);
    sync_clock_to_slave(&is->extclk, &is->audclk);
}

会找到和“视频同步音频”类似的的两点:

C. 只要主时钟不是audio,就需要作同步校正

D. 更新audclk,同时更新extclk

 

分析

我们知道通过sync选项可以选择同步策略,分别可以选择audio/video/ext,选择不同选项的效果是:

  • audio:视频同步到音频。上一节中的A被触发,video输出需要作同步,同步的参考(get_master_clock)是audclk.

  • video:音频同步到视频。上一节中的C被触发,audio输出需要作同步,同步的参考是vidclk。

  • ext:视频和音频都同步到外部时钟,上一节中的A和C都被触发,同步的参考是extclk

不论选择的是哪一个选项,B和D始终都有执行。

 

所以外部时钟为主的同步策略是这样的:video输出和audio输出时都需要作校正,校正的方法是参考extclk计算diff值。其余部分参考“视频同步到音频”和“音频同步到视频”这两节的分析即可。

 

另一个问题是外部时钟(extclk)是如何对时的?在音视频同步基础概念中我们分析过Clock是需要一直对时以保持pts_drift估算出来的pts不会偏差太远,并且get_clock的返回值实际是这一Clock对应的流的pts。这两点对于extclk来说都是问题。

答案就在前面的B和D步骤中。

对于audclk和vidclk,都是每次在“显示”时用显示的那一帧的pts去对时set_clock_at/set_clock.顺带地,会执行sync_clock_to_slave(&is->extclk, &is->audclk);//&is->vidclk

static void sync_clock_to_slave(Clock *c, Clock *slave)
{
    double clock = get_clock(c);
    double slave_clock = get_clock(slave);
    if (!isnan(slave_clock) && (isnan(clock) || fabs(clock - slave_clock) > AV_NOSYNC_THRESHOLD))
        set_clock(c, slave_clock, slave->serial);
}

sync_clock_to_slave的意思是用从时钟的pts和serial对主时钟对时。

而之所以可以这样做的原因是,在更新audclk和vidclk的时候,音频或视频已经同步到了外部时钟,此时取它们的值来反过来对外部时钟对时可以认为是准确的。

 

也许你会发现,不对,被兜了一圈!这是一个先有鸡还是先有蛋的问题。既然要把video和audio同步到extclk,我们用的extclk校正video和audio,得到更新后的audclk和vidclk,却又反过来用audclk和vidclk去对时extclk。分明就是蛋要鸡来生,鸡要蛋来敷嘛。

幸运的是,这个问题对于开天辟地,扮演上帝角色的代码而言并不难,ffplay说先有蛋。如果有仔细阅读过compute_target_delaysynchronize_audio,就会发现进行校正的必要条件之一是!isnan(diff),也就是diff值是合法数值,这在第一帧的音频或视频显示前是不成立的,也就无需做同步校正。在第一帧视频或音频显示后,此时extclk得到对时,接下来就可以进入正常的同步“循环”了。

 

至此,同步到外部时钟的同步策略分析完了,简单总结下:

  1. 该策略“复用”了前两种策略的代码,代码上几乎等效于前两种策略的叠加

  2. extclk的对时依赖于已同步的audio或video的Clock

 

PS:外部时钟同步策略中其实还有一个小分支没分析,即这段代码:

static void video_refresh(void *opaque, double *remaining_time)
{
    if (!is->paused && get_master_sync_type(is) == AV_SYNC_EXTERNAL_CLOCK && is->realtime)
        check_external_clock_speed(is);
}

有空再来分析。

  •                     <li class="tool-item tool-active is-like "><a href="javascript:;"><svg class="icon" aria-hidden="true">
                            <use xlink:href="#csdnc-thumbsup"></use>
                        </svg><span class="name">点赞</span>
                        <span class="count"></span>
                        </a></li>
                        <li class="tool-item tool-active is-collection "><a href="javascript:;" data-report-click="{&quot;mod&quot;:&quot;popu_824&quot;}"><svg class="icon" aria-hidden="true">
                            <use xlink:href="#icon-csdnc-Collection-G"></use>
                        </svg><span class="name">收藏</span></a></li>
                        <li class="tool-item tool-active is-share"><a href="javascript:;" data-report-click="{&quot;mod&quot;:&quot;1582594662_002&quot;}"><svg class="icon" aria-hidden="true">
                            <use xlink:href="#icon-csdnc-fenxiang"></use>
                        </svg>分享</a></li>
                        <!--打赏开始-->
                                                <!--打赏结束-->
                                                <li class="tool-item tool-more">
                            <a>
                            <svg t="1575545411852" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M179.176 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5718"></path><path d="M509.684 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5719"></path><path d="M846.175 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5720"></path></svg>
                            </a>
                            <ul class="more-box">
                                <li class="item"><a class="article-report">文章举报</a></li>
                            </ul>
                        </li>
                                            </ul>
                </div>
                            </div>
            <div class="person-messagebox">
                <div class="left-message"><a href="https://blog.csdn.net/yao_hou">
                    <img src="https://profile.csdnimg.cn/E/C/8/3_yao_hou" class="avatar_pic" username="yao_hou">
                                            <img src="https://g.csdnimg.cn/static/user-reg-year/1x/4.png" class="user-years">
                                    </a></div>
                <div class="middle-message">
                                        <div class="title"><span class="tit"><a href="https://blog.csdn.net/yao_hou" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;}" target="_blank">令狐掌门</a></span>
                                            </div>
                    <div class="text"><span>发布了180 篇原创文章</span> · <span>获赞 89</span> · <span>访问量 18万+</span></div>
                </div>
                                <div class="right-message">
                                            <a href="https://im.csdn.net/im/main.html?userName=yao_hou" target="_blank" class="btn btn-sm btn-red-hollow bt-button personal-letter">私信
                        </a>
                                                            <a class="btn btn-sm  bt-button personal-watch" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;}">关注</a>
                                    </div>
                            </div>
                    </div>
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值