Android 4.1 Audio系统变化说明(4)

3.4 createTrack和start说明

createTrack中最大的变化就是新增了对MediaSyncEvent同步机制的处理。MediaSyncEvent的目的很简单,其Java API的解释如下:startRecording(MediaSyncEvent) is used to start capture only when the playback on a particular audio session is complete. The audio session ID is retrieved from a player (e.g MediaPlayer, AudioTrack or ToneGenerator) by use of the getAudioSessionId() method. 简单点讲,就是必须等上一个player工作完毕了,才能开始下一个播放或者录制。这个机制解决了Android长久以来的声音经常混着出来的问题(目前一个恶心但却实效的方法就是加一个sleep,以错开多个player不同步的问题。)。注意,iPhone上就没有这个问题。

另外,这个机制的潜在好处就是解放了做AudioPolicy AudioRoute工作的同学们,似乎(个人感觉是可以解决这个问题的)可以不用再去琢磨到底sleep多少时间,在哪加sleep的问题了

在AF中,MediaSyncEvent机制的代表是SyncEvent。大家自己看看就好。

start函数的变化不大,其中加了对SyncEvent的处理。

另外,createTrack中还涉及到FastMixer和TimedTrack处理。核心在PlaybackThread的createTrack_l和Track构造函数中。尤其是和FastMixer的关系。

根据图2,FM(FastMixer简写)内部用得数据结构是FastTrack,而MT用得是Track,所以这里存在一一对应的关系。FM的FastTrack是保存在数组中的,所以

使用FM的Track将通过mFastIndex来指向这个FastTrack。

现在搞清楚FastTrack和Track之间的关系即可,后续的数据流动还需要详细讨论

下面来看看MixerThread的工作流程。这部分是重头戏!

3.5 MixerThread的工作流程

这部分难的还是在FastMixer的工作原理上。不过这里提前和大家说:目前这个功能还没有做完,代码里边一堆的FIXME...。但屌丝们不要happy太早了,

估计马上、很快、必须得下个版本就好了。现在看看这个不成熟的东西,可以缓解以后看到成熟的东西的心理压力。

MT是一个线程,其工作内容主要在threadLoop中完成,而这个函数是由其基类PlaybackThread定义的,大体变化如下:

PlaybackThread的threadLoop定义了整个音频处理的大体流程,具体的细节通过几个虚函数(如prepareTracks_l,threadLoop_mix,threadLoop_write)交给子类去实现了

MT变化大的首先是prepareTracks_l,首先处理的是FastMix类型的Track,判断标准是该Track是否设置了TRACK_FAST标志(爽了,目前JB中还没有哪个地方使用了这个标志)。这部分判断比较复杂。首先FastMixer维护了一个状态机,另外,这个FastMixer运行在自己的线程里,所以线程同步是必须的。这里采用的是状态来控制FastMixer的工作流程。由于涉及到多线程,所以音频的underrun,overrun状态(不知道是什么吗?看前面提到的参考书!)也是一个需要处理的棘手问题。另外,一个MT是带一个AudioMixer对象,这个对象将完成数据的混音,下变换等等超难度,数字音频处理等方面的工作。也就是说,对于混音来说,前期的prepare工作还是由MT线程来完成,因为这样可以做到统一管理(有些Track并不需要使用FastMixer。但仔细一想,谁都希望处理越快越好,在多核CPU上,将混音工作交给多个线程处理是充分利用CPU资源的典范,这应该是未来Android演化的趋势。所以,我估计这个JB还没完全长大....)。对FastMixer感兴趣的屌丝们,请务必认真研究prepareTracks_l函数。

MT下一个重要函数就是threadLoop_mix了,由于存在一个TimedTrack类,那么AudioMixer的process函数就带上了一个时间戳,PTS,presentation timestamp。从编解码角度来说,还有一个DTS,Decode timestamp。这里要闲扯下PTS和DTS的区别了。DTS是解码时间,但编码的时候由于有可能会根据未来帧来编码当前帧。所以,解码的时候会先解未来帧,然后解出当前帧,但是。你播放的时候可不能先播未来帧。只能老老实实得按播放顺序来先播当前帧,然后播未来帧(尽管先解出来的是未来帧)。关于PTS/DTS,请屌丝们研究下IBP相关的知识吧。回到MT,这个PTS是从硬件hal对象取的,应该是HAL内部维护的时间戳。这个时间戳原则上会比较准确。

混音完了,再做特效处理(和以前的版本差不多),然后调用threadLoop_write。MT的threadLoop_write函数的输出端点就是前面那个坑爹的mNormalSink,如果不为空,就调用它的write函数。想着是调用NBAIO_Sink的非阻塞的write函数。根据图2的分析,它有可能是那个MonoPipe,也有可能就是AudioStreamOutputSink,这个sink节点用得就是以前的AudioStreamOutput。而MonoPipe的write其内部就是一个buffer。并没有和真实的AUDIO HAL Output挂上关系。这.....咋整??(大胆假设,小心求证。只能是FastMixer把这个buffer取出来,然后再写到真实的Audio HAL中了。因为在MixerThread构造函数中,曾经为FastTrack保存过mOutputSink,这个就是用来和AudioStreamOutput联系的)

另外,DulicatingThread,DirectOuptutThread没有太大变化。

四 FastMixer工作原理简单说明

我以前想得是:混音工作由FastMixer线程和MixerThread线程共同完成,但输出工作依然在MixerThread做。从上面MonoPipe的分析来看,这个判断可能不准。

既有可能是输出工作也交给FastMixer来做,而MixerThread仅做一部分混音工作,然后把数据通过MonoPipe传给FastMixer线程。FastMixer线程将自己的FastTrack的混音结果和MT的混音结果再做一次混音,然后再由FastMixer输出。

FM定义在FastMixer.cpp中,核心就是一个ThreadLoop。由于AF所有Track的预备工作由MT线程来做,所以FM的threadLoop基本上就是根据状态来做对应处理。

这里的同步使用了LINUX中很底层的futex(Fast Userspace Mutex)。晕,futex是POSIX Mutex的实现基础。不知道写这段代码的人为何不直接用Mutex(估计还是嫌效率的问题,但是 妈的,用了Mutex效率能差多少?代码是写给人看的,太B4我们了...)。玩多线程玩到这种地步,佩服啊!不懂多线程编程的屌丝们,请仔细研究Posix MultiThread Programming吧

FastMixer内部还使用了一个AudioMixer,用于它的混音

然后再write出去.....

这里是FM的简单说明,详细内容,没有拿个真机给我,我也没法整啊....欢迎乐善好施的兄弟们刷个4.1的机器,然后借给我研究下...

(这玩意,个人感觉也不是太难。东西嘛,耐不住琢磨,总能搞透的)。兄弟们今天知道FM和MT的大体工作流程就可以了。

linux

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/pxwsd.html