android AV同步详解
本文主要介绍 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同步详解相关推荐
- 《Java和Android开发实战详解》——1.2节Java基础知识
本节书摘来自异步社区<Java和Android开发实战详解>一书中的第1章,第1.2节Java基础知识,作者 陈会安,更多章节内容可以访问云栖社区"异步社区"公众号查看 ...
- Android USB 开发详解
Android USB 开发详解 先附上 Android USB 官方文档 Android通过两种模式支持各种 USB 外设和 Android USB 附件(实现Android附件协议的硬件):USB ...
- Android AIDL使用详解
一.概述 AIDL 意思即 Android Interface Definition Language,翻译过来就是Android接口定义语言,是用于定义服务器和客户端通信接口的一种描述语言,可以拿来 ...
- Android OkHttp 全面详解
Android OkHttp 全面详解 包的导入 基本使用 异步请求 同步请求 build创建 源码跟踪 newCall RealCall.enqueue Dispatcher.enqueue exe ...
- Carson带你学Android:图文详解RxJava背压策略
前言 Rxjava,由于其基于事件流的链式调用.逻辑简洁 & 使用简单的特点,深受各大 Android开发者的欢迎. 本文主要讲解的是RxJava中的 背压控制策略,希望你们会喜欢. Cars ...
- android组件模板,提高效率必备神器 ---- Android Studio模板详解
原标题:提高效率必备神器 ---- Android Studio模板详解 Android Studio模板大家应该很熟悉,你新建一个project或者module的时候,AS会帮你提供几个选项供你选择 ...
- Android 全局大喇叭——详解广播机制
Android 全局大喇叭--详解广播机制 一.广播机制简介 1. 标准广播(Normal broadcasts) 2. 有序广播(Ordered broadcasts) 二.接收系统广播 1. 动态 ...
- android 广播的权限,Android四大组件详解之BroadcastReceiver广播接收者
Android四大组件详解---BroadcastReceicer广播接收者 广播有两个角色,一个是广播发送者,另一个是广播接收者. 广播按照类型分为两种,一种是全局广播,另一种是本地广播 全局广播: ...
- Android进阶——AIDL详解之使用远程服务AIDL实现进程间带远程回调接口和自定义Bean的较复杂通信小结(二)
文章大纲 引言 一.远程回调AIDL接口的应用 1.封装基本的父类和一些工具类 2. 创建服务端的AIDL 2.1.定义回调AIDL接口 2.2.定义业务AIDL接口 3.实现服务端对应AIDL的带有 ...
最新文章
- js filter 用法
- 6.2.Scrapy获取图像
- Win8上安装天翼宽带运行提示停止运行的问题
- Atiti。流量提升软件设计大纲规划 v1 q45
- Java Class 文件格式及其简单 Hack
- 用C#实现RSS的生成和解析,支持RSS2.0和Atom格式
- ssh作业批改系统_如何看待「全国至少十省份叫停家长批改作业」?能否从根源解决问题?...
- 圆周率一千万亿位_圆周率是如何计算的?祖冲之的缀术居然失传了
- mysql中datetime有带时区_如何在MySQL中存储datetime与时区信息
- 全国计算机等级考试一级模拟考,全国计算机等级考试一级模拟试题一
- 识别“百度权重”作弊的方法
- 如何系统学习知识图谱-胖子哥的实践经验分享
- 防风雨密封胶的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
- DevpTips【golang】安装到测试
- Excel按倍率一键调整当前表格的行高
- 飞瞳引擎集装箱人工智能API集装箱箱况检测演示,全球No.1集装箱AI企业中集飞瞳,完成全球百万AI集装箱箱况检验上亿集装箱检测
- 清华、北大、浙大的计算机课程资源集都在这里了(转载自 -- AI科技大本营)
- LIGHT OJ1070 Algebraic Problem
- 74HC245_键盘与8段数码管共有一个IO口_控制步进电机【Protues】
- 初级学会响应式网页设计-周红川-专题视频课程