Android平台mp3音乐播放流程分析 ----从AP到audioflinger

来源:互联网 发布:微信导航源码 编辑:程序博客网 时间:2024/05/17 03:07
Mp3播放调用流程

从AudioPreview开始,AudioPreview是一个AP,AudioPreview->OnCreate中除了初始化一些控件外,还会初始化PreviewPlayer,这个player继承了MediaPlayer,然后会调用PreviewPlayer的SetDataSourceAndePrepare(),这样就调用了MediaPlayer的setDataSource设置源,这里的源是声音文件。在AP中的MediaPlayer是BpMediaPlayer,这样通过Binder调用BnMediaPlayer,也就是MediaPlayerService中Client中的SetDataSource设置播放文件。而MediaPlayerService中实际上包括三个内部类,AudioOutput,AudioCache和Client,其中Client继承了BnAudioPlayer,这里谷歌的命名有些歧义,看多了就习惯了,因为CameraService也是这么命名的。可以把这里的Client当成对多媒体播放器(这个播放器是一个面向对象概念)的抽象。AudioOutput和AudioCache都是MediaPlayerBase::AudioSink类型,这个类型是对音频输出的抽象。在MediaPlayerService的设置播放文件SetDataSource函数中,首先会根据播放文件的类型选择播放器,mp3使用的是stagefrightplayer(midi播放时,选择的就是midi播放器),在选择完播放器后,初始化一个AudioOutput,然后对这个播放器调用setAudioSink设置AudioSink,也就是设置AudioOutput,在此之后,再使用播放器的setDataSource进行设置。stagefrightplayer中一般使用Awesomeplayer来进行多媒体文件的播放(NV平台)。但是,在此过程中并不真正设置Awesomeplayer的DataSource,而是在进行prepareAsync时进行设置,这个可能主要是因为还要涉及相应的解码器操作,所以需要异步进行。

在此之后,AudioPreview使用prepareAsync进行准备,prepareAsync最终和setDataSource一样都会调用Awesomeplayer中的prepareAsync,进而逐级调用到prepareAsync_l,在此Awesomeplayer会设置一个异步事件,使得mAsyncPrepareEvent为onPrepareAsyncEvent,这样就可以异步的进行相应的设置了。其实,onPrepareAsyncEvent这个函数非常关键,因为它不仅设置Awesomeplayer的音频文件,而且会初始化相应的解码器,这样看来,使用异步操作会得到比较好的性能,至少不会让AP的界面临时失去响应。onPrepareAsyncEvent首先会调用finishSetDataSource_l设置Awesomeplayer的相应成员变量,finishSetDataSource_l会首先检查多媒体文件的来源是文件系统还是网络,或者网络流媒体,这之后会创建数据源文件的解压器,从而分离出源文件中的meta头和音轨Track部分,这样再使用Awesomeplayer中的setDataSource_l设置相应的音视频源,这里由于播放的是mp3文件只会使用setAudioSource设置音频源,这里要注意设置的音频源为mAudioTrack变量,而不是mAudioSource,mAudioSource用于解码,所以要区分一下。在音频源mAudioTrack设置完成后,此时mAudioTrack不为空,但是mAudioSource为空,所以onPrepareAsyncEvent会调用initAudioDecoder初始化音频解码器,initAudioDecoder会使用OMXCodec::Create构建一个解码器,它会使用OMX的allocateNode函数分配一个OMX节点,这个节点就是OMX.Nvidia.mp3.decoder组件的节点,而后会初始化一个OMXCodec对象。这是个硬件解码器,最终会调用到system/etc/firmware中的解码文件。这之后,initAudioDecoder会调用这个OMXCodec对象的configureCodec函数配置解码器,其间configureCodec会调用initOutputFormat初始化输出格式。initOutputFormat会根据OMX_PARAM_PORTDEFINITIONTYPE类型的eDomain字段,设置输出格式的相应参数,最终将构建好的解码源赋给mAudioSource。initAudioDecoder随后会调用mAudioSource的start函数,开始解码。在构建OMXCodec时,initAudioDecoder会将mAudioTrack设置到OMXCodec中,这样在mAudioSource的start中会调用mp3解压器的start,从而解压得到的多媒体文件数据。从对象构建的角度来看,mp3解压器的构建是一个典型的工厂模式,这样就可以构建不同格式文件的解压器了。而mAudioSource和mAudioTrack都是MetaSource类型,而mAudioSource的start会调用init函数,初始化编码器的输入和输出缓冲,进而完成prepareAsync调用,最后,在finishAsyncPrepare_l中调用通知(Listener),通知异步准备完成,Java层MediaPlayer中的EventHandler会接受和处理这些异步消息,如果AP层实现了相应的Listener,异步消息会通知上层的AP,在这里EventHandler会收到MEDIA_PREPARED消息,从而通知AP层的OnPrepared函数,这样AP(AudioPreview)就可以知道已经准备完成了,随后就可以开始播放音乐了。

在AudioPreview中实现了OnPrepared函数从而可以接受到异步准备完成的消息,在这个函数中调用Activity中的OnPrepared函数,在这个函数中主要是开始播放音乐。播放音乐时,调用MediaPlayer的start,MediaPlayer调用MediaPlayerService的Client中的Start,这之前需要将播放器状态设置为LOOPING,MediaPlayerService的Client会获取在prepare阶段获取的播放器,也就是stagefrightplayer,stagefrightplayer会调用AwesomePlayer的play函数,进而调用play_l函数。在play_l中,首先会检查准备情况,若准备完成则会根据AudioSink构建一个AudioPlayer,也就是将输出设置到AudioPlayer中,然后调用setSource设置mAudioSource,这样就将刚才解码后的源设置到播放器中了,mAudioPlayer继承了一个TimeSource,这是因为Audio具有时间行,换句话说就是能够知道播放到什么地方了。这个AudioSink是MediaService中的AudioOutput。设置完AudioPlayer及其源之后,play_l会调用startAudioPlayer_l函数,在这个函数中调用AudioPlayer的start函数。AudioPlayer的start是个这个阶段非常重要的函数,它真正开始AudioSink播放。正是这个函数的调用建立了AudioTrack,这样就和AudioFlinger联系上了。AudioPlayer的start调用流程是,首先读取mFirstBuffer,这样就会有一部分数据被保存在缓冲中了,之后通过INFO_FORMAT_CHANGED的检查使得OMXCodec的 mSource.getFormat可以读到当前的格式信息(metaData),在读取相应的采样率、声道等信息后通过mAudioSink的open函数建立AudioTrack,关于AudioTrack实际上最后会通过AudioFlinger的createTrack建立,并且建立相应的Control block结构,这个结构用于控制解码和音频输出的速率,是一个同步控制结构。在open函数调用之后,使用AudioSink的start开始播放音乐,这里的start就是AudioTrack的start。另外,音频的输出工作是有AudioFlinger来完成的,而AudioSink则是一个对AudioTrack封装,至少Mp3是这样。

实际上AudioTrack是BpAudioTrack,而BnAudioTrack在AudioFlinger中,AudioFlinger中的createTrack会首先通过checkPlaybackThread_l获取一个工作线程索引,这个索引对播放音频来说是PlayBackThread,这个线程是在AudioFlinger服务开始时创建的,之后createTrack会调用PlaybackThread线程的createTrack_l函数构建一个Track,这个Track是一个PlaybackThread::Track,这个Track的基类是TrackBase,在这个基类中构建了Control block结构,也就是一个共享内存。这个共享内存中的前一部分是audio_track_cblk_t控制结构,audioFlinger的createTrack最终会返回一个TrackHandle的类型,这个类型实际上就是BnAudioTrack。之所以这么做是因为Track用于描述音频或者音轨,而TrackHandle用于交互,这样分工比较明确。而PlaybackThread只是播放的一个基类,真正进行播放的是MixerThread和DirectOutputThread类,这两个类继承了PlaybackThread,主要是实现了其threadLoop函数,从而使线程运转起来。当然,在录音时,还有RecordThread负责录音工作。PlaybackThread的start开始播放音乐,这个函数并没有直接打开设备进行播放,而是只将当前Track添加到mActiveTracks中(使用PlaybackThread函数的addTrack_l函数),这就相当与将播放的音轨放到要播放的音轨数组中,之所以有一个mActiveTracks数组,是因为音频设备从上层函数的角度看不是一个独占设备,这就使得很多程序能够同时播放声音,而音频设备确实在同一时刻只能由一个进程访问,这样就有了AudioFlinger,从而实现对要播放的音轨进行统一管理混合输出。在addTrack_l函数中会触发一个广播事件,mWaitWorkCV.broadcast(),这样MixerThread就会得到通知,有一个Track被激活了。MixerThread会在自己的threadLoop函数中等待(线程中的等待,代表线程睡去。)mWaitWorkCV条件,当mWaitWorkCV唤醒后,MixerThread就开始工作了,但是这个线程的唤醒时机并不是Track被激活的时候,而是在AudioPreview启动后就激活了这个线程函数。在这个线程中,声音才真正被传到硬件上,开始播放。这个线程Loop还是非常复杂的,但是基本流程就是三个函数的调用比较关键,第一个是prepareTracks_l,用于准备缓冲,第二个函数是mAudioMixer的process进行混音,第三个是mOutput的write,将音频缓存中的数据写入到设备中,这样声音就会从音频硬件中播出了。

首先,prepareTracks_l用于检查被激活的音轨,对被激活的音轨做一些设置,这些设置包括音量大小,混音参数等,因为下一步要用到这些参数。这个函数的参数是两个向量(Vector),一个是激活音轨,一个是被删除的音轨,对于那些被终止播放的音轨,就要把这些音轨从激活音轨中去除。在这个准备之后,就可以进入到混音阶段了。在混音阶段,混音器(AudioMixer)会根据音轨的情况判断使用的混音方式,一般的MP3方式都会使用process__OneTrack16BitsStereoNoResampling函数,但有时由于alsa的采样率是固定的所以会使用重采样方式。不过其中的原理都是先统计需要混音的帧,然后使用Track中的getNextBuffer,这个函数在AudioFlinger的PlaybackThread中,这个函数从共享内存的控制结构中获得一块缓冲。混音是逐帧进行的,混音后要使用releaseBuffer释放数据缓冲,这里的释放缓冲,只是更新控制结构中可用缓冲的位置。经过混音后,就可以将其输出到硬件抽象层进行播放了。

这里还要注意一个概念,就是硬件延迟。硬件延迟是音乐由数字数据输出到硬件抽象层后,转为模拟输出的一个时间过程,这个过程一般是一个定值。有了这个时间过程就可以计算出数字数据可以有多大的缓冲,这个缓冲是保证音乐播出连贯性的一个重要参数。在编解码过程中,当数据缓存使用超过警戒值后,就可以读取源文件进行解码并输出到mSource中,供AudioTrack进行读取了。所以在音频输出中重点是对于共享内存Control Block结构的理解,这个结构控制着播放的步进,从结构上说是一个线性结构,使用上是一个环形结构。且这个结构后面是播放端(客户端或者AP)AudioTrack同AudioFlinger交互的共享内存,因此这个结构的构建使用的是就地创建的方式进行初始化。也就是在一块固定的内存区域上进行结构的构建,这样,另一个进程读取时,也能够读出同样的数据。

还有解码是一个异步过程,当源缓冲不足时进行读取,然后,将解码后的数据放到输出缓冲中,以供使用。这里的缓冲是解码器的缓冲,和共享内存的缓冲不是一个概念,这个对应的是MediaBuffer。

以上是音频播放的基本流程,非常曲折。
原创粉丝点击