Android 4.1,英文代号简称JB。在国人眼里,JB这个词还和动物有点关系。Google如此频繁修改Android,终于推出了一个能够被你们成天JB JB挂在嘴上的版本。之后个人文章也能够一面用JB表示版本号,一面用JB表示毛主席常说的”战略上的鄙视了“。请你们根据上下文揣摩我写下JB一词的心情。
今天将稍深刻得介绍一下JB 4.1在Audio系统作的翻天覆地的改动。这里先啰嗦几句:就像80后常常抱怨本身晚生了几年同样,立刻就会有不少码农抱怨接触Android太晚了。为什么?JB Audio系统的难度相对4.0, 2.3, 2.2已经很是很是大了。99%的状况下,在你没有看到这个NB(这不是脏话,4.1 Audio系统中就有一个类叫NBAIO,篮球控不要搞错成NBA了,原意是Non-block Audio I/O。看到了吧,
非阻塞I/O,各位自问下,有多少人对这个东西有深入理解?)东西演化的基础上,不太可能能看懂JB Audio系统。因此,建议这99%中的没有见识过Audio演化历史的屌丝同窗们,先仔细研究(之前我仅仅建议你们看看,如今提升要求为仔细研究)《深刻理解Android 卷I》Audio系统。
BTW,此书在某个章节里特地提醒过你们要去研究下各类I/O模型,不知道有几我的屌过我了。
本文将分几个部分,事前没有打草稿,因此会有点乱。
先从Java层AudioTrack类提及
一 AudioTrack Java类变化说明
- 声道数上,之前只有单声道(MONO)和立体声(STEREO),如今拓展到最NB八声道(7.1 HiFi啊)。参数名为CHANNEL_OUT_7POINT1_SURROUND。看到这个参数,我下巴咣当就掉下来了。这玩意,一时半会我还弄不明白是个什么道理。有知晓的屌丝码农们不妨告诉你们。 固然,最终的输出仍是双声道。多声道(大于2)的时候会使用downmixer处理(下变换处理,同窗们可搜索之)
- 其余的变化也有,但不大了。我这里先挑一些吸引眼球的。BTW,放心,不会像那个泷泽萝拉首秀片子同样只让你们看见大鼻孔的。
二 AudioTrack JNI层变化说明
这一层包括JNI层和AudioTrack自己
- JNI层变化不大。
- Audio Native核心代码移到了framework/av下。对,你没看错。真的是av。这就是JB Audio一个比较大的变化。Audio Native核心代码所有移到了frameworks/AV目录下。
- AudioTrack增长了一个变量,用于控制使用它的进程的调度优先级(前文说错了,这里确实设置的是nicer值)。若是处于播放状态的话,将设置进程调度优先级为ANDROID_PRIORITY_AUDIO。就像大家看到马赛克时必定会嘟喃同样。我这里也要特别啰嗦几句。在单核CPU的状况下,设置优先级是比较愚蠢的(ANDROID_PRIORITY_AUDIO的值为-16,优先级极高,单核设置个这么高的怪物,不知道其余app还怎么玩。若是你不知道我在说什么,先看看这篇文章吧,http://blog.csdn.net/innost/article/details/6940136)。但如今2核,4核已经比较常见了,这里就能够来玩玩调度方面的事情。对屌丝码农的真正考验是:多核并行编程,linux os的原理,须要各位屌丝同窗努力掌握。Audio已经不那么能轻易被大家任意蹂躏了。另外,低端手机,求求大家别移植4.1了,这个真的不是低端能玩的。
- AudioTrack升级为父亲了。JB为它定义了一个莫名其妙的的TimedAudioTrack子类。这个类在编解码的aah_rtp(我如今还不知道aah是什么)里边用到了。从注释上看,该类是一个带时间戳(有时间戳,就能够作同步了)的音频输出接口。详细理解的话,就须要结合具体应用场景去分析了(主要是rtp这一块)。搞编解码的同窗们,抓紧了!
- 另一个超级复杂的变化,是Audio定义了几个输出flag(见audio.h的audio_output_flags_t枚举定义)。根据注释,该值有两个做用,一个是AT的使用者能够指明本身想使用怎样的outputDevice。另一个是设备厂商能够经过它声明本身支持的输出设备(看来,设备初始化的时候,又增添了参数读取和配置这方面的工做)。不过,从该枚举的定义来看,我还看不出它和硬件有什么关系。它定义的值以下:
typedef enum {
AUDIO_OUTPUT_FLAG_NONE = 0x0, // no attributes
AUDIO_OUTPUT_FLAG_DIRECT = 0x1, // this output directly connects a track
// to one output stream: no software mixer
AUDIO_OUTPUT_FLAG_PRIMARY = 0x2, // this output is the primary output of
// the device. It is unique and must be
// present. It is opened by default and
// receives routing, audio mode and volume
// controls related to voice calls.
AUDIO_OUTPUT_FLAG_FAST = 0x4, // output supports "fast tracks",
《==什么叫fast track?太难理解了!目前,java层的audiotrack只会使用第一个标志。
// defined elsewhere
AUDIO_OUTPUT_FLAG_DEEP_BUFFER = 0x8 // use deep audio buffers
《==deep buffer是个什么玩意?这个马赛克是否是太大了点?如今彻底看不清楚啊??!
} audio_output_flags_t;
- AudioTrack其余变化不大。AudioTrack.cpp总共才1600多行,so easy!
OK,上面有好几个马赛克,日常看看日本大片的时候也就撸过去了,但分析Audio可不行。把去马赛克的但愿寄托在下一步AudioFlinger的分析上吧!
三 AudioFlinger变化说明
咱们将根据AF工做的主要流程来介绍下变化状况:
- AF建立,包括其onFirstRef函数
- openOutput函数及MixerThread对象的建立
- AudioTrack调用createTrack函数
- AudioTrack调用start函数
- AF混音,而后输出
3.1 AF建立和onFirstRef
恩,没什么太大变化。有三个点:
- 如今对Primary设备的音量有了更为细致的控制,例若有些设备能设音量,有些不能设置音量,因此定义了一个master_volume_support(AudioFlinger.h)枚举,用来判断Primary设备的音量控制能力。
- 之前播放过程的standby时间(就是为了节电而用)是写死的,如今可由ro.audio.flinger_standbytime_ms控制,若是没有这个属性,则默认是3秒。AF还增长了其余变量控制,例若有一个gScreenState变量,用来表示屏幕是开仍是关。可经过AudioSystem::setParameters来控制。另外还定义了一个和蓝牙SCO相关的mBtNrecIsOff变量,是用于控制蓝牙SCO(录音时用,蓝牙上的一个专业术语叫,NREC。不知道是什么,用懂的人告诉我一下)时禁止AEC和NS特效的。请参考AudioParameter.cpp
3.2 openOutput函数
openOutput函数比较关键,其中会见到之前的老朋友MixerThread,AudioStreamOutput等。整个流程包括加载Audio相关的硬件so。这部分工做在4.0的时候就有了,谈不上太多的变化。但物是人非,老朋友已经发生巨大变化了。先来看MixerThread家族。
图1 PlaybackThread家族
图1稍加解释:
-
ThreadBase从Thread派生,因此它会运行在一个单独的线程中(啰嗦一句,线程和对象其实没有关系的,不懂多线程编程的码农们请务必认真学习多线程)。它定义了一个枚举type_t,用来表示子类的类型,这几个类型包括MIXER,DIRECT,RECORD,DUPLICATING等。这个应该比较好懂吧?
-
ThreadBase的内部类TrackBase从ExtendedAudioBufferProvider派生,这个应该是新增长的。TrackBase嘛,你们把它理解成一个Buffer Container就行了。
-
ThreadBase的内部类PMDeathRecipient用来监听PowerManagerService的死亡消息。这个设计有点搞,由于PMS运行在SystemService中,只有SS挂了,PMS才会挂。而SS挂了,mediaserver也会被init.rc的规则给弄死,因此AudioFlinger也会死。既然你们都一块儿死,速度很快。故,设置这个PMDeathRecipient有何大的意义呢?
再来看ThreadBase的一个重要子类PlaybackThread,这个类应该是作过大整容了。
-
其定义了一个枚举mixer_state,用来反映当前混音工做的状态,有MIXER_IDLE,MIXER_READY和MIXER_ENABLED
-
定义了几个虚函数,须要子类实现,包括threadLoop_mix,prepareTracks_l等。这几个函数的抽象工做作得仍是能够。但变化之大让人防不胜防啊。
-
Track类增长了从VolumeProvider派生,这个VP是用来控制音量的。根据前面的介绍,在JB中,音量管理比之前来得细致
-
新增定义了TimedTrack。这个类的做用和前面提到的rtp aah有关。等同窗们学完本篇,便可开展相应研究,打响歼灭战!
接下来看图2。
图2 MixerThread和它的弟兄们
图2,简单介绍一下:
-
MixerThread从PlaybackThread派生,这个关系至始至终不会变化,相信之后也不会。
-
MT最大的变化是其中几个重要的成员变量。你们确定认识其中的AudioMixer,它是用来混音的。
-
新增一个Soaker对象(由编译宏控制),它是一个线程。这个单词的前缀soak在webster词典(相信经历过,那些年,咱们一块儿GRE的日子 的人知道什么是webster)中最贴切的一条解释是to cause to pay an exorbitant amount。仍是不很明白是干吗的?再一看代码。原来,soaker就是一个专职玩弄CPU的线程。它的工做就是不断得作运算,拉高CPU使用率。它的存在应该是为了测试新AF框架在多核CPU上的效率等等等的问题。因此,低端智能机们,大家不要玩JB了。
-
另一条证实低端智能机不能随便玩JB的铁证就是:咱们看到MT中新增了一个FastMixer,它也是一个线程。明白了?在JB中,多核智能机上,混音工做能够放到FastMixer所在的线程来作,固然速度,效率会高了。
-
FastMixer工做流程比较复杂,又牵扯到多线程同步。因此,这里定义了一个FastMixerStateQueue,它由typedef StateQueue<FastMixerState>获得。首先它是一个StateQueue(简单把它当作数组吧)。其数组元素的类型为FastMixerState。一个StateQueue经过mStats变量保存4个FasetMixerState成员。
-
FasetMixerState相似状态机,有一个enum Command,用来控制状态的。FastMixerState中含有一个八元组的FastTracks数组。FastTrack是用来完成FastMixer的一个功能类。
-
每一个FastTrack都有一个mBufferProvider,该成员类型为SourceAudioBufferProvider。
以上的内容已经比较复杂了,下面来介绍下MixerThread对象建立中碰到的其余内容:
3.3 MixerThread建立
经过图1和图2,应该对AF的几个主要成员有了认识。惋惜啊,上面MixerThread中还有一个mOutputSink成员,没看到吧?它就和咱们前面提到的NBAIO(Non-block Audio I/O )有重大关系。NBAIO的存在,是为了想实现非阻塞的音频输入输出操做。下面是这个类的注释:
NBAIO注释:
// This header file has the abstract interfaces only. Concrete implementation classes are declared
// elsewhere. Implementations _should_ be non-blocking for all methods, especially read() and
// write(), but this is not enforced. In general, implementations do not need to be multi-thread
// safe, and any exceptions are noted in the particular implementation.
NBAIO只是定义了一个接口,须要去实现具体的实现类。固然,它要求read/write函数是非阻塞的,真实实现究竟是不是阻塞,由实现者去控制。
我的感受这部分框架尚未彻底成熟,但NBIO的引入,须要同窗们当心,相对而言,难度也比较大。下面咱们经过图3来看看NBAIO的一些内容。
图3 NBAIO相关内容
图3解释以下:
-
NBAIO包括三个主要类,一个是NBAIO_Port,表明I/O端点,其中定义了一个negotiate函数,用于调用者和I/O端点进行参数协调。注意,并非为I/O端点设置参数。由于I/O端点每每和硬件相关,而硬件有些参数是不能像软件通常随意变化的。例如硬件只支持最多44.1KHZ的采样率,而调用者传递48KHz的采样率,这直接就须要一个协商和匹配的过程。这个函数的比较难用,主要是规则较多。同窗们能够参考其注释说明。
-
NBAIO_Sink对应output端点,其定义了write和writeVia函数,writeVia函数须要传递一个回调函数via,其内部将调用这个via函数获取数据。相似数据的推/拉两种模式。
-
NBAIO_Source对应input端点,其定义了read和readVia函数。意义同NBAIO_Sink。
-
定义一个MonoPipe和MonoPipeReader。Pipe即管道,MonoPipe和LINUX中的IPC通讯Pipe没毛关系,只不过借用了这个管道概念和思路。MonoPipe即只支持单个读者的Pipe(AF中,它是MonoPipeReader)。这两个Pipe,表明了Audio的Output和Input端点。
-
MT中由mOutputSink指向AudioStreamOutSink,此类用NBAIO_Sink派生,用于普通的mixer的输出。mPipeSink指向MonoPipe,本意是用于FastMixer的。另外,还有一个变量mNormalSink,它将根据FastMixer的状况,指向mPipeSink,或者是mOutputSink。这段控制的逻辑以下:
switch (kUseFastMixer) { //kUseFastMixer用于控制FastMixer的使用状况,一共4种:
case FastMixer_Never: //永远不使用FastMixer,这个选项用于调试,即关闭FastMixer的状况
case FastMixer_Dynamic: //根据状况,动态使用。根据注释,这个功能彷佛尚未彻底实现好
mNormalSink = mOutputSink;
break;
case FastMixer_Always: //永远使用FastMixer,调试用
mNormalSink = mPipeSink;
break;
case FastMixer_Static://静态。默认就是这个。但具体是否使用mPipeSink,将收到initFastMixer的控制
mNormalSink = initFastMixer ? mPipeSink : mOutputSink;
break;
}
由上所述,kUseFastMixer默认是FastMixer_Static,但mNormalSink是否指向mPipeSink,还由initFastMixer控制。这个变量自己又有mFrameCount和
mNormalFrameCount的大小决定,只有mFrameCount小于mNormalFrameCount时,initFastMixer才为真。晕了....这两个frameCount由PlaybackThread的
readOutputParameters获得。请同窗们本身研究这段代码吧,就是一些简单的计算。想要搞明白的话,最好带着参数进去,把值都算出来。
好了,MixerThread的建立就分析到此,最好仍是把这段代码多研究研究。了解几个兄弟对象是作什么的....
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吧
这里是FM的简单说明,详细内容,没有拿个真机给我,我也无法整啊....欢迎乐善好施的兄弟们刷个4.1的机器,而后借给我研究下...
(这玩意,我的感受也不是太难。东西嘛,耐不住琢磨,总能搞透的)。兄弟们今天知道FM和MT的大致工做流程就能够了。
五 其余变化
其余变化包括:
- 很是注重调试了,加了大量的XXXDump类。看来,Google本身开发的时候也碰到很多问题。简单的功能,谁会想着去dump呢?
- 增长AudioWatchdog类,用来监控AF性能的,如CPU使用状况等。
六 总结
我记得在研究2.2 AF的时候,AudioFlinger才3k多行,而JB已经有9K多行了。还没算其余的辅助类。从总体上看,JB变化趋势为:
- 要充分利用多核资源,因此FastMixer的出现是必然。还包括NBAIO接口。感受对HAL编写会有大的挑战。
- 增长TimedTrack和SyncEvent,对于RTP或者多个player间的同步会带来比较好的用户体验。
- 增长native层往java层通知的接口。
还有其余的东西.....今天先到这了。
对屌丝的考验:
- LINUX OS编程和POSIX编程必须熟练掌握。
- 复杂代码分析能力必须尽快提升。不然,后面根本看不懂。