分享

音视频同步

 开花结果 2023-03-20 发布于北京

视频:帧率,表示视频一秒显示的帧数。

音频:采样率,表示音频一秒播放的sample数。

声卡和显卡均是以一帧数据来作为播放单位,如果单纯依赖帧率及采样率来进行播放,在理想条件下,应该是同步的,不会出现偏差。

一个AAC音频frame每声道1024个sample,一帧播放时长duration=(1024/44100)×1000ms = 23.22ms;一个视频frame播放时长duration=1000ms/24 = 41.67ms。声卡虽然是以音频采样点为播放单位,但通常我们每次往声卡缓冲区送一个音频frame,每送一个音频frame更新一下音频的播放时刻,即每隔一个音频frame时长更新一下音频时钟,实际上ffplay就是这么做的。理想情况下,音视频完全同步,音视频播放过程如下图所示:

但实际情况,往往不同步,原因如下:

1一帧的播放时间,难以精准控制。音视频解码及渲染的耗时不同,可能造成每一帧输出有一点细微差距,长久累计,不同步便越来越明显。(例如受限于性能,42ms才能输出一帧)

2音频输出是线性的,而视频输出可能是非线性,从而导致有偏差。

3媒体流本身音视频有差距。(特别是TS实时流,音视频能播放的第一个帧起点不同)

所以,解决音视频同步问题,引入了时间戳:

首先选择一个参考时钟,时间是线性递增的;编码时依据参考时钟给每个音视频数据块都打上时间戳;播放时,根据音视频时间戳PTS及参考时钟,来调整播放。视频和音频的同步实际上是一个动态的过程,以参考时钟为标准,放快了就减慢播放速度;播放快了就加快播放的速度。

没有B帧的情况下,DTS=PTS。从流分析工具elecard eye查看,流中P帧在B帧之前,但显示在B帧之后。

参考时钟的选择一般来说有以下三种:

以音频为基准,将视频同步到音频。人对音频更敏感,音频播放时钟线性增长,常用做法

将音频同步到视频

选择一个外部时钟为基准,视频和音频的播放速度都以该时钟为标准。

ffplay音视频同步方式,以audio为参考时钟,video同步到音频:

获取当前要显示的video PTS,减去上一帧视频PTS,则得出上一帧视频应该显示的时长delay;

当前video PTS与参考时钟当前audio PTS比较,得出音视频差距diff;

获取同步阈值sync_threshold,为一帧视频差距,范围为10ms-100ms;

diff小于sync_threshold,则认为不需要同步;否则delay+diff值,则是正确纠正delay;

如果超过sync_threshold,且视频落后于音频,那么需要减小delay(FFMAX(0, delay + diff)),让当前帧尽快显示。

如果视频落后超过1秒,且之前10次都快速输出视频帧,那么需要反馈给音频源减慢,同时反馈视频源进行丢帧处理,让视频尽快追上音频。因为这很可能是视频解码跟不上了,再怎么调整delay也没用。

如果超过sync_threshold,且视频快于音频,那么需要加大delay,让当前帧延迟显示。

将delay*2慢慢调整差距,这是为了平缓调整差距,因为直接delay+diff,会让画面画面迟滞。

如果视频前一帧本身显示时间很长,那么直接delay+diff一步调整到位,因为这种情况再慢慢调整也没太大意义。

考虑到渲染的耗时,还需进行调整。frame_timer为一帧显示的系统时间,frame_timer+delay- curr_time,则得出正在需要延迟显示当前帧的时间。

小黑圆圈是代表帧的实际播放时刻,小红圆圈代表帧的理论播放时刻,小绿方块表示当前系统时间(当前时刻),小红方块表示位于不同区间的时间点,则当前时刻处于不同区间时,视频同步策略为:

[1] 当前时刻在T0位置,则重复播放上一帧,延时remaining_time后再播放当前帧

[2] 当前时刻在T1位置,则立即播放当前帧

[3] 当前时刻在T2位置,则忽略当前帧,立即显示下一帧,加速视频追赶

上述内容是为了方便理解进行的简单而形象的描述。实际过程要计算相关值,根据compute_target_delay()和video_refresh()中的策略来控制播放过程。

{

    video->frameq.deQueue(&video->frame);

    //获取上一帧需要显示的时长delay

    double current_pts = *(double *)video->frame->opaque;

    double delay = current_pts - video->frame_last_pts;

    if (delay <= 0 || delay >= 1.0)

    {

        delay = video->frame_last_delay;

    }

    // 根据视频PTS和参考时钟调整delay

    double ref_clock = audio->get_audio_clock();

    double diff = current_pts - ref_clock;// diff < 0 :video slow,diff > 0 :video fast

    //一帧视频时间或10ms,10ms音视频差距无法察觉

    double sync_threshold = FFMAX(MIN_SYNC_THRESHOLD, FFMIN(MAX_SYNC_THRESHOLD, delay)) ;

    audio->audio_wait_video(current_pts,false);

    video->video_drop_frame(ref_clock,false);

    if (!isnan(diff) && fabs(diff) < NOSYNC_THRESHOLD) // 不同步

    {

        if (diff <= -sync_threshold)//视频比音频慢,加快

        {

            delay = FFMAX(0,  delay + diff);

            static int last_delay_zero_counts = 0;

            if(video->frame_last_delay <= 0)

            {

                last_delay_zero_counts++;

            }

            else

            {

                last_delay_zero_counts = 0;

            }

            if(diff < -1.0 && last_delay_zero_counts >= 10)

            {

                printf("maybe video codec too slow, adjust video&audio\n");

                #ifndef DORP_PACK

                audio->audio_wait_video(current_pts,true);//差距较大,需要反馈音频等待视频

                #endif          

                video->video_drop_frame(ref_clock,true);//差距较大,需要视频丢帧追上

            }

        }    

        //视频比音频快,减慢

        else if (diff >= sync_threshold && delay > SYNC_FRAMEDUP_THRESHOLD)

            delay = delay + diff;//音视频差距较大,且一帧的超过帧最常时间,一步到位

        else if (diff >= sync_threshold)

            delay = 2 * delay;//音视频差距较小,加倍延迟,逐渐缩小

    }

    video->frame_last_delay = delay;

    video->frame_last_pts = current_pts;

    double curr_time = static_cast<double>(av_gettime()) / 1000000.0;

    if(video->frame_timer == 0)

    {

        video->frame_timer = curr_time;//show first frame ,set frame timer

    }

    double actual_delay = video->frame_timer + delay - curr_time;

    if (actual_delay <= MIN_REFRSH_S)

    {

        actual_delay = MIN_REFRSH_S;

    }

    usleep(static_cast<int>(actual_delay * 1000 * 1000));

    //printf("actual_delay[%lf] delay[%lf] diff[%lf]\n",actual_delay,delay,diff);

    // Display

    SDL_UpdateTexture(video->texture, &(video->rect), video->frame->data[0], video->frame->linesize[0]);

    SDL_RenderClear(video->renderer);

    SDL_RenderCopy(video->renderer, video->texture, &video->rect, &video->rect);

    SDL_RenderPresent(video->renderer);

    video->frame_timer = static_cast<double>(av_gettime()) / 1000000.0 ;

    av_frame_unref(video->frame);

    //update next frame

    schedule_refresh(media, 1);

}


原文链接:https://blog.csdn.net/wangbuji/article/details/122502560

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多