本文主要介绍 android 多媒体中,音视频同步问题。

以下是详细说明:

先提及一个背景基础知识: 
Stagefright中,audio的数据输出是通过AudioTrack中的callback来不断驱动AudioPlayer::fillBuffer获取数据
video的数据输出,则是由OMX端在解码完毕后,给awesomeplayer发送消息event,在awesomeplayer的onVideoEvent中进行视频的render。
在render之前进行AV同步工作
具体细节不再赘述。读者可自行参考其他文章。
所以本文实际上重点研究的就是AV同步的处理函数:onVideoEvent

一,既然牵扯到AV同步,肯定涉及到一个时间基准的问题。
AV同步目前有两种同步方法: 
1,视频,音频同步到一个第三方系统时钟  
2,以音频时间戳为基准,视频数据同步到音频时间戳上

两种方法各有优缺点, 
第一种方法中,音频视频不能互相感知,如果有一方出现了问题,一样会造成AV时间不同步。尤其是如果AV中一方有一个长时间的累积误差,这种方法就无能为力了。因为他们不能互相修正。
第二种方法中,必须有音频做基准,对于一些没有音频,只有视频的媒体文件,这种方法就无能为力了。

所以android目前对这两种都采用了:
对于音频,视频都健全的文件,把视频同步到音频上。对于没有音频,只有视频的文件,把视频同步到第三方时钟上,即系统时钟

具体来说,就是awesomeplayer选择哪个TimerSoruce为时间基准的问题,是系统时钟?还是audio的时间戳?
那么是怎么选择的呢?

在onVideoEvent中,以下代码做了选择
    TimeSource *ts =  ((mFlags & AUDIO_AT_EOS) || !(mFlags & AUDIOPLAYER_STARTED)) ? &mSystemTimeSource : mTimeSource;
当audio 播放完毕的时候,或者audio没有开始的时候,就选择系统时钟作为TimeSource
否则,就是有audio在播放,就要选择mTimeSource。
这个mTimeSource是啥呢?在AwesomePlayer::createAudioPlayer_l里面,可以看到 mTimeSource = mAudioPlayer;
这样,就把音频选择为TimerSoruce

我们结合这段代码的上下文来研究:

    CHECK(mVideoBuffer->meta_data()->findInt64(kKeyTime, &timeUs));TimeSource *ts =((mFlags & AUDIO_AT_EOS) || !(mFlags & AUDIOPLAYER_STARTED))? &mSystemTimeSource : mTimeSource;if (mFlags & FIRST_FRAME) {modifyFlags(FIRST_FRAME, CLEAR);mSinceLastDropped = 0;mTimeSourceDeltaUs = ts->getRealTimeUs() - timeUs;}int64_t realTimeUs, mediaTimeUs;if (!(mFlags & AUDIO_AT_EOS) && mAudioPlayer != NULL&& mAudioPlayer->getMediaTimeMapping(&realTimeUs, &mediaTimeUs)) {mTimeSourceDeltaUs = realTimeUs - mediaTimeUs;}

这段代码比较绕,我们分成两种情况来分析:
1,如果没有音频数据,如果该视频帧是第一帧,就通过ts->getRealTimeUs()来取系统时钟的值,减去该第一帧在视频文件里的时间戳,这样就能得出视频开始播放的那一霎那,对应的系统时间mTimeSourceDeltaUs,后续的视频帧同步就是用这个播放起始时间作为参考。
这实际上就是我们前面讲到的没有音频的时候,视频数据按照系统时钟来同步
2,如果有音频数据,mTimeSourceDeltaUs会被修改为 realTimeUs - mediaTimeUs;
跟踪getMediaTimeMapping这个函数,你会发现,实际上就是对象AudioPlayer里面的mPositionTimeRealUs-mPositionTimeMediaUs
mPositionTimeRealUs 实际上是在AudioPlayer::fillBuffer里面被赋值   ((mNumFramesPlayed + size_done / mFrameSize) * 1000000)/ mSampleRate;
这实际上就是目前已经播放掉的数据的时间,也就是理想情况下此帧需要被播放的时间。
mPositionTimeMediaUs实际上就是 CHECK(mInputBuffer->meta_data()->findInt64(kKeyTime, &mPositionTimeMediaUs)),即此音频帧在媒体文件中的时间戳。
这样mTimeSourceDeltaUs=realTimeUs - mediaTimeUs;所表示的含义就是该帧音频实际需要播放的时间点,和他所标注的时间戳的一个误差。

继续在onVideoEvent中分析
        int64_t nowUs = ts->getRealTimeUs() - mTimeSourceDeltaUs;
        int64_t latenessUs = nowUs - timeUs;
我们依旧是分成两种情况来分析这两行代码
1,如果没有音频数据, ts->getRealTimeUs() 获取到当前的系统时钟,mTimeSourceDeltaUs是前面讲到的文件开始播放的系统时钟, 二者相减,表示的是视频播了多久。是一个实际值。this is a real value,
timeUs表示的是这一帧视频在文件中的时间戳。表示的就是当前时刻,视频应该播放到哪一微秒。 this is a supposed value.
二者相减,就表示实际值和应该值之间的误差。
2,如果有音频数据, ts->getRealTimeUs() 获取到的是音频为基准的时间, 实际上是从AudioPlayer::getRealTimeUsLocked()里面得到, 得到的是从整个文件开始播放,到现在的一个累积时间。(当然,getRealTimeUsLocked里面经过了一些修正,主要是修正了buffer长度造成的延时,以及硬件延时。具体参考http://blog.csdn.net/njuitjf/article/details/7637003)
前面交代过, mTimeSourceDeltaUs =realTimeUs - mediaTimeUs=mPositionTimeRealUs-mPositionTimeMediaUs,
这样  nowUs= ts->getRealTimeUs() - mTimeSourceDeltaUs = ts->getRealTimeUs()- ( mPositionTimeRealUs-mPositionTimeMediaUs)=修正过的播放累积时间-(理想情况播放累积时间-此帧音频数据的理论时间戳)=  (audio latency)+此帧音频数据的理论时间戳
这样得到的值,就是对这一帧音频的时间戳,做了一个修正。把硬件时延以及buffer播放带来的时延误差给修正上了。得到的就是预估出来的这个buffer真正应该播放的时间戳
所以,latenessUs = nowUs - timeUs;就是这一帧视频的时间戳,与目前正在播放的音频时间戳的差值。  由于以音频时间戳为基准,所以这里算出来的就是视频帧与时间基准的误差
有了这个误差,就可以对这个视频帧做相应处理

void AwesomePlayer::onVideoEvent()
{if (latenessUs > 500000ll )
{if (mWVMExtractor == NULL) {mVideoBuffer->release();mSeeking = SEEK_VIDEO_ONLY;mSeekTimeUs = mediaTimeUs;postVideoEvent_l();return;} }if (latenessUs > 40000){mVideoBuffer->release();postVideoEvent_l();return;}if (latenessUs < -10000){postVideoEvent_l(10000);return;}mVideoRenderer->render(mVideoBuffer);...
}

这段代码的意思就是:
   1:如果视频数据过慢,慢于音频时间500ms,则丢弃当前帧,并且需要丢弃一段视频帧,直接对video部分(SEEK_VIDEO_ONLY)seek到音频当前帧的时间戳;
   2:如果视频数据过慢,慢于音频时间40ms,则丢弃当前帧,然后再去取数据;
   3:如果视频数据过快,超过音频时间10ms,则显示当前帧后,将读取数据的事件延时(慢的时间-10ms)后放入队列;
   4,如果时间合适,误差不大,直接render

android AV同步详解相关推荐

  1. 《Java和Android开发实战详解》——1.2节Java基础知识

    本节书摘来自异步社区<Java和Android开发实战详解>一书中的第1章,第1.2节Java基础知识,作者 陈会安,更多章节内容可以访问云栖社区"异步社区"公众号查看 ...

  2. Android USB 开发详解

    Android USB 开发详解 先附上 Android USB 官方文档 Android通过两种模式支持各种 USB 外设和 Android USB 附件(实现Android附件协议的硬件):USB ...

  3. Android AIDL使用详解

    一.概述 AIDL 意思即 Android Interface Definition Language,翻译过来就是Android接口定义语言,是用于定义服务器和客户端通信接口的一种描述语言,可以拿来 ...

  4. Android OkHttp 全面详解

    Android OkHttp 全面详解 包的导入 基本使用 异步请求 同步请求 build创建 源码跟踪 newCall RealCall.enqueue Dispatcher.enqueue exe ...

  5. Carson带你学Android:图文详解RxJava背压策略

    前言 Rxjava,由于其基于事件流的链式调用.逻辑简洁 & 使用简单的特点,深受各大 Android开发者的欢迎. 本文主要讲解的是RxJava中的 背压控制策略,希望你们会喜欢. Cars ...

  6. android组件模板,提高效率必备神器 ---- Android Studio模板详解

    原标题:提高效率必备神器 ---- Android Studio模板详解 Android Studio模板大家应该很熟悉,你新建一个project或者module的时候,AS会帮你提供几个选项供你选择 ...

  7. Android 全局大喇叭——详解广播机制

    Android 全局大喇叭--详解广播机制 一.广播机制简介 1. 标准广播(Normal broadcasts) 2. 有序广播(Ordered broadcasts) 二.接收系统广播 1. 动态 ...

  8. android 广播的权限,Android四大组件详解之BroadcastReceiver广播接收者

    Android四大组件详解---BroadcastReceicer广播接收者 广播有两个角色,一个是广播发送者,另一个是广播接收者. 广播按照类型分为两种,一种是全局广播,另一种是本地广播 全局广播: ...

  9. Android进阶——AIDL详解之使用远程服务AIDL实现进程间带远程回调接口和自定义Bean的较复杂通信小结(二)

    文章大纲 引言 一.远程回调AIDL接口的应用 1.封装基本的父类和一些工具类 2. 创建服务端的AIDL 2.1.定义回调AIDL接口 2.2.定义业务AIDL接口 3.实现服务端对应AIDL的带有 ...

最新文章

  1. js filter 用法
  2. 6.2.Scrapy获取图像
  3. Win8上安装天翼宽带运行提示停止运行的问题
  4. Atiti。流量提升软件设计大纲规划 v1 q45
  5. Java Class 文件格式及其简单 Hack
  6. 用C#实现RSS的生成和解析,支持RSS2.0和Atom格式
  7. ssh作业批改系统_如何看待「全国至少十省份叫停家长批改作业」?能否从根源解决问题?...
  8. 圆周率一千万亿位_圆周率是如何计算的?祖冲之的缀术居然失传了
  9. mysql中datetime有带时区_如何在MySQL中存储datetime与时区信息
  10. 全国计算机等级考试一级模拟考,全国计算机等级考试一级模拟试题一
  11. 识别“百度权重”作弊的方法
  12. 如何系统学习知识图谱-胖子哥的实践经验分享
  13. 防风雨密封胶的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  14. DevpTips【golang】安装到测试
  15. Excel按倍率一键调整当前表格的行高
  16. 飞瞳引擎集装箱人工智能API集装箱箱况检测演示,全球No.1集装箱AI企业中集飞瞳,完成全球百万AI集装箱箱况检验上亿集装箱检测
  17. 清华、北大、浙大的计算机课程资源集都在这里了(转载自 -- AI科技大本营)
  18. LIGHT OJ1070 Algebraic Problem
  19. 74HC245_键盘与8段数码管共有一个IO口_控制步进电机【Protues】
  20. 初级学会响应式网页设计-周红川-专题视频课程

热门文章

  1. System.Windows.Forms.Cursors
  2. 基于tomcat的javaweb在线教学网站的开发--完成登录、注册以及考试页面
  3. 麻雀算法SSA优化SVR
  4. Python爬虫入门教程 65-100 爬虫与反爬虫的修罗场,点评网站,字体反爬之三
  5. 赚翻了,快速带你学会Python爬虫接私单
  6. Java101班1组作业完成情况
  7. 各种搜索引擎及其使用技巧 效率翻倍
  8. 空气动力学——第二章 基本原理和基本方程
  9. 史上最全蓝屏代码!电脑蓝屏了查一下什么原因吧!
  10. Loadrunner分析