ITPub博客

首页 > 应用开发 > IT综合 > 直播带货平台开发,实现音视频同步算法

直播带货平台开发,实现音视频同步算法

IT综合 作者:云豹科技晓彤 时间:2021-07-05 17:16:57 0 删除 编辑

      本文是对音视频同步算法的总结,以阅读 ffplay.c 源码为基础,结合各位博主的分析, 逐渐深入理解同步算法原理, 并根据自身理解, 编写一套简易的视频播放器,用于验证直播带货平台开发的音视频同步算法。

ffplay简介

    ffplay 是 FFmpeg 提供的开源播放器,基于 FFmpeg 和 SDL 进行视频播放, 是研究视频播放器,音视频同步算法的很好的示例。 ffplay 源码涉及到很多音视频的基本概念, 在基础理论缺乏的情况下分析起来并不容易,在分析 ffplay 源码之前,要对音视频的相关概念有所了解,关于音视频的基本知识,在网络上有很多,也可以参考我的其他文章,这些也是我在学习中的经验总结。

     在 ffmpeg4.1.3 中, ffplay 源码约 3700 行,非常的小巧,网上关于 ffplay 原理分析的文章也有很多,这里不再详细的分析 ffplay 的源码, 仅按照自己的理解对音视频同步算法进行总结, 并基于 ffplay ,自己动手编写一个简易视频播放器, 对音视频同步算法进行验证。

为什么要做音视频同步

     直播带货平台开发中,如果仅仅是视频按帧率播放,音频按采样率播放,二者没有同步机制,即使一开始音视频是同步的,随着时间的流逝,音视频会渐渐失去同步,并且不同步的现象会随着时间会越来越严重。这是因为:

      一、播放时间难以精确控制

      二、异常、误差会随时间累积。

所以,必须直播带货平台开发要采用一定的同步策略,不断对音视频的时间差作校正,使图像显示与声音播放总体保持一致。

音视频同步算法

音视频同步算法的核心在于准确计算出音频与视频播放时间的偏差, 再根据这个偏差对双方进行调整,确保双方在你追我赶的过程中保持同步。

1. 音视频同步介绍

         视频同步到音频:即以音频为主时间轴作为同步源

         音频同步到视频:即以视频为主时间轴作为同步源

         音频和视频同步到系统时钟:即以系统时钟为主时间轴作为同步源

    ffplay 默认采用第一种同步方式,本节主要阐述视频同步到音频方式。为什么大多播放器要采用视频同步到音频呢,因为音频的采样率是固定的,若音频稍有卡顿,都会很明显的听出来,反则视频则不如此,虽然表面上说的是 25P (每秒 25 帧),不一定每一帧的间隔就必须精确到 40ms (所以每帧间隔大约 40ms ,事实上,也很难做到精确的 40ms ),即便偶尔视频间隔延时大了点或小了点,人眼也是察觉不出来的,所以视频的帧率可以是动态的,并不是严格标准的!

     视频同步到音频,即以音频作为主时间轴, 尽量不去干扰音频的播放,音频采用独立的线程独自解码播放 ( 音频播放的速度在参数设置完毕后是固定的,因此我们也很容易计算音频播放的时间 ), 在整个过程中,根据视频与音频时间差,来决策如何改变视频的播放速度,来确保视频与音频时间差控制在一定范围内, 当偏移在 -90ms (音频滞后于视频)到 +20ms (音频超前视频)之间时,人感觉不到视听质量的变化,这个区域可以认为是同步区域;当偏移在 -185 到 +90 之外时,音频和视频会出现严重的不同步现象,此区域认为是不同步区域。这里我们认为偏移 diff 在 ‘± 一个视频帧间隔 ’ 范围内即认为是同步的,如下图所示:

2. 音视频时间偏差计算

直播带货平台开发同步系统的关键,就在于计算视频与音频时间偏差diff , 在 ffplay.c 源码中,是通过函数 compute_target_delay 实现的,函数源码如下:

 

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) {

        /* 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);

 

        /* 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 */

        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)

                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;

        }

    }

 

    av_log(NULL, AV_LOG_TRACE, "video: delay=%0.3f A-V=%f\n",

            delay, -diff);

 

    return delay;

}

 

根据自身的理解,结合实际测试,得出diff 的计算方法:

当前视频帧pts : frame->pts * av_q2d(video_st->time_base)

当前直播带货平台视频帧至今流逝的时间: 代表当前视频帧从开始显示到现在的时间, 在ffplay 中函数 get_clock(&is->vidclk) 给出了具体实现,在本次实验中, 通过 nowtime - last_showtime 来表示流逝的时间, 由于我们是在视频显示后立即计算 diff , 这个流逝的时间几乎可以忽略不计,可以使用 0 表示。

音频帧播放时间 = 音频长度 / 采样率

当前音频帧播放完毕时间= 当前音频帧的 pts + 当前音频帧长度 / 采样率

                                           = af->pts + (double) af->frame->nb_samples / af->frame->sample_rate;

       (在计算音频帧长度时需要考虑采样率, 通道数, 样本格式)

  音频缓冲区中未播放数据的时间: 在 ffplay.c 中,采用如下公式来获取:   

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);

 

          缓冲区数据总长度 =SDL 的 A , B 缓冲区总长度  +   当前音频帧尚未拷贝到 SDL 缓冲区的剩余长度 aduio_write_buf_size

到这里,我们就可以计算得到音视频的播放时间偏差diff , 结合上面的偏差图,我们很容易判断出是视频落后于音频,还是音频落后于视频。

3. 量化视频播放的时间延时

通过第2 步我们已经计算出音视频的时间偏差, 接下来我们就要根据这个偏差来量化视频延时的时间, 来控制下一个视频帧显示的时间。

我们参考ffplay.c 中的代码片段:

            last_duration = vp_duration(is, lastvp, vp);

            delay = compute_target_delay(last_duration, is);

 

            time= av_gettime_relative()/1000000.0;

            if (time < is->frame_timer + delay) {

                *remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);

                goto display;

            }

 

            is->frame_timer += delay;

            if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX)

                is->frame_timer = time;

 

remaining_time 为下一帧播放的延时时间, ffplay.c 借助 frame_timer += delay 来记录当前视频累计播放的时间。

     frame_timer + delay - av_gettime_relative()/1000000.0 :代表下一视频帧需要延时的时间,这里需要减去当前时间,是为了得到定时器或 delay 的时间。

      另外, 我们约定任意两个视频帧的间隔至少为 10ms, 所以才有了:

            *remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);

4. 编写简易的视频播放器

    ffplay.c 中的同步算法对于初学者而言理解起来还是有些难度的, 结合自身对 ffplay.c 源码的阅读,以及音视频同步算法的理解, 对上述同步代码进行精简, 亦能达到音视频同步的效果代码片段如下。

 

        if( pm->av_sync_type == AV_SYNC_AUDIO_MASTER){//

 

            master_clock = get_master_clock(pm);

 

            diff = vp->pts - master_clock;

 

            printf("vps:%lf, audioclock:%lf, diff:%lf\n", vp->pts, master_clock, diff);

 

            sync_threshold = (delay > AV_SYNC_THRESHOLD)?delay:AV_SYNC_THRESHOLD;

 

  

 

            if( diff <= -sync_threshold){

 

                delay = 0;

 

            }else if( diff >= sync_threshold){

 

                delay *= 2;

 

            }

 

            

 

        }

 

我们直接根据直播带货平台开发的diff 的值来决策下一帧要延时的时间。

————————————————

声明:本文由云豹科技转发自 挥剑踏苍穹 博客,如有侵权请联系作者删除

原文链接:https://blog.csdn.net/u011734326/article/details/97137998

 


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70002045/viewspace-2779779/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
音视频软件开发相关知识科普账号

注册时间:2021-06-11

  • 博文量
    163
  • 访问量
    60335