相关文章推荐
神勇威武的小蝌蚪  ·  金角大王——《西游记》中的妖怪_孙悟空_宝物_金炉·  4 月前    · 
含蓄的人字拖  ·  抚顺纪检监察网·  7 月前    · 
帅气的小摩托  ·  创业失败后的自我救赎:为什么选择再次出发|陈 ...·  1 年前    · 
谈吐大方的铁链  ·  加勒比离岸金融业基本情况和特点·  1 年前    · 
拉风的电池  ·  office ...·  1 年前    · 
Code  ›  Android FFmpeg系列07--音画同步开发者社区
时钟同步 ffmpeg
https://cloud.tencent.com/developer/article/2168936
爱笑的卡布奇诺
2 年前
作者头像
雪月清
0 篇文章

Android FFmpeg系列07--音画同步

前往专栏
腾讯云
开发者社区
文档 意见反馈 控制台
首页
学习
活动
专区
工具
TVP
文章/答案/技术大牛
发布
首页
学习
活动
专区
工具
TVP
返回腾讯云官网
社区首页 > 专栏 > 雪月清的随笔 > Android FFmpeg系列07--音画同步

Android FFmpeg系列07--音画同步

作者头像
雪月清
发布 于 2022-11-19 09:56:11
792 0
发布 于 2022-11-19 09:56:11
举报

引言

在前面的Demo中,我们已经分别在独立的线程中实现了对视频的解码渲染和音频的解码播放功能

Android FFmpeg系列03--视频解码与渲染

Android FFmpeg系列05--音频解码与播放

(oceans.mp4)

不过随着播放的持续进行,可以发现播放的画面和声音会逐渐的对不上,存在严重的音画不同步问题,而精确的音频和视频同步,是媒体播放的关键性能衡量指标之一,所以这篇文章我们就来简单的聊聊音画同步的那些事

Demo中一直使用的oceans.mp4可能不是很容易区分音画不同步问题,除非是真的特别严重的时候,在网上找了一个可以用来测试音画是否同步的视频,也上传到工程中的assets目录中了,感兴趣的小伙伴可以自己在MainActivity中改下播放的file

(av_sync_test.mp4)

音画同步定义

音画同步是指播放器正在渲染的每一帧画面和正在播放的每一段声音都能严格对应起来,不存在视觉和听觉可以分辨出来的差异

视觉和听觉可以分辨的差异标准可以参考ITU-R BT.1359标准

从上图可以看到,我们并不是真的需要音频、视频帧的时间严格匹配,只需要在合理的区间内相互追赶就行,所以说音视频的同步是动态的、是暂时的,不同步则是常态

  • 无感知区间:音频帧和视频帧显示的时间戳差值在-100ms~+25ms之间
  • 能感知区间:音频滞后在-100ms以上或者超前了25ms+
  • 无法接受的区间:音频滞后在-185ms以上或者超前了90ms+

为什么要做音画同步

音视频文件在解复用阶段后,音频/视频独立解码、独立播放,理论上来说按照视频的帧率、音频采样率进行播放的话音画是同步的

这里以Demo工程中的av_sync_test.mp4为例

一个视频帧的播放时长为1000ms / 25 = 40ms,一个AAC音频帧的播放时长为1024 / 44100 * 1000ms ≈ 23.22ms,理想情况下音视频完全同步,播放过程如下:

不过实际上受限于各种原因,音画总是不同步的,可能的原因如下:

  • 一帧的播放时间难以精确控制;比如视频帧受限于解码性能、渲染性能等导致一帧耗时大于1 / fps
  • 异常、误差会随时间逐渐积累;比如一帧音频帧播放耗时约等于23.22ms,当累积播放几万帧的时候误差就达到秒级别了

音画同步的三种策略

音视频编码的时候引入了显示时间戳pts的概念:

  • 选择参考时钟(要求时钟是线性递增);
  • 编码时依据参考时钟给每个音频、视频数据帧打上显示时间戳pts;
  • 解码播放时,根据音频、视频时间戳及参考时钟来调整播放(如果数据帧的pts大于当前参考时钟上的时间,则sleep直到参考时钟到达数据帧的时间;如果数据帧的pts小于当前参考时钟上的时间,则尽快消费数据或者直接丢弃数据,以使播放进度追上参考时钟);

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

视频同步到音频:以音频的播放速度为基准来同步视频

  • 优点:音频播放连续;
  • 缺点:视频画面会出现丢帧、跳帧

音频同步到视频:以视频的播放速度为基准来同步音频

  • 优点:视频播放流畅;
  • 缺点:音频根据对齐策略可能会出现静音、卡顿、加速播放等情况

音视频同步到外部时钟:以外部时钟为基准,视频和音频的播放速度都以该时钟为标准

  • 优点:最大限度的保证音视频都不发生跳帧行为;
  • 缺点:如果控制不好外部时钟,极有可能引发音频和视频都跳帧的情况

这三种是最基本的同步策略, 考虑到人对声音的敏感度要强于画面,频繁调节音频会带来较差的感官体验,另一方面是音频数据在确定采样率、采样位数、声道数等参数时播放时间就很容易计算且能准确计算,而视频数据不行 ,所以一般播放器都会默认以音频时钟为参考时钟,视频同步到音频上。ffplay,exoplayer都是如此

音画同步的关键在于计算视频和音频时间的diff和计算最终的delay,在ffplay.c源码中通过如下函数计算

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的计算参考网上总结的一张图:

回到Android端,要实现音画同步一个可参考源码的例子是exoplayer

这里说说AudioTrack来播放音频pcm数据,要计算audio playback position主要有的两种api:

AudioTrack#getTimestamp() (api level 19+)

返回的AudioTimestamp实例中将填入一个以帧为单位,以及呈现该帧的估计时间

该接口的注意事项:

  • 该接口不一定都支持,不支持的时候会返回0;
  • 在音频管道初始预热阶段,可能无法连续更新时间戳;
  • 该接口不应该太频繁调用,频繁调用会导致CPU负担,电量损耗过大;exoplayer中是每500ms查询一次

AudioTrack#getPlaybackHeadPosition() (api level 3+)

返回当前播放的头位置(以帧为单位)

计算最新的音频时间戳

/** The number of microseconds in one second. */
public static final long MICROS_PER_SECOND = 1000000L;
private long framesToDurationUs(long frameCount) {
    return (frameCount * C.MICROS_PER_SECOND) / sampleRate;
long timestamp = framesToDurationUs(audioTrack.getPlaybackHeadPosition());

考虑底层的音频延迟(包括混音器的延迟、音频硬件驱动程序的延迟等)和AudioTrack缓冲区引入的延迟

Method getLatencyMethod;
if (Util.SDK_INT >= 18) {
  try {
    getLatencyMethod =
     android.media.AudioTrack.class.getMethod("getLatency", (Class < ? > []) null);
   } catch (NoSuchMethodException e) {
 
推荐文章
神勇威武的小蝌蚪  ·  金角大王——《西游记》中的妖怪_孙悟空_宝物_金炉
4 月前
含蓄的人字拖  ·  抚顺纪检监察网
7 月前
帅气的小摩托  ·  创业失败后的自我救赎:为什么选择再次出发|陈磊|张伟_网易订阅
1 年前
谈吐大方的铁链  ·  加勒比离岸金融业基本情况和特点
1 年前
拉风的电池  ·  office 365在国内买不到,可以代理上网,用绑定国内银行卡的paypal订阅吗? - Microsoft Community
1 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号