您的位置:首页 > 移动开发 > Android开发

深入剖析Android音频之AudioTrack

2015-06-30 13:27 447 查看
深入剖析Android音频之AudioTrack
播放声音可以用MediaPlayer和AudioTrack,两者都提供了java API供应用开发者使用。虽然都可以播放声音,但两者还是有很大的区别的。其中最大的区别是MediaPlayer可以播放多种格式的声音文件,例如MP3,AAC,WAV,OGG,MIDI等。MediaPlayer会在framework层创建对应的音频解码器。而AudioTrack只能播放已经解码的PCM流,如果是文件的话只支持wav格式的音频文件,因为wav格式的音频文件大部分都是PCM流。AudioTrack不创建解码器,所以只能播放不需要解码的wav文件。当然两者之间还是有紧密的联系,MediaPlayer在framework层还是会创建AudioTrack,把解码后的PCM数流传递给AudioTrack,AudioTrack再传递给AudioFlinger进行混音,然后才传递给硬件播放,所以是MediaPlayer包含了AudioTrack。使用AudioTrack播放音乐示例:

?


AudioTrack构造过程

每一个音频流对应着一个AudioTrack类的一个实例,每个AudioTrack会在创建时注册到 AudioFlinger中,由AudioFlinger把所有的AudioTrack进行混合(Mixer),然后输送到AudioHardware中进行播放,目前Android同时最多可以创建32个音频流,也就是说,Mixer最多会同时处理32个AudioTrack的数据流。



frameworks\base\media\java\android\media\AudioTrack.java

?
with audio session. Use this constructor when the AudioTrack must be attached to a particular audio session. The primary use of the audio session ID is to associate audio effects to a particular instance of AudioTrack: if an audio session ID is provided
when creating an AudioEffect, this effect will be applied only to audio tracks and media players in the same session and not to the output mix. When an AudioTrack is created without specifying a session, it will create its own session which can be retreived
by calling the
getAudioSessionId()
method. If a non-zero session ID is provided, this AudioTrack will share effects attached to this session with all other media players or audio tracks in the same session, otherwise a new session will be created
for this track if none is supplied.

streamType
the type of the audio stream. See STREAM_VOICE_CALL,STREAM_SYSTEM,STREAM_RING,STREAM_MUSIC,STREAM_ALARM, andSTREAM_NOTIFICATION.
sampleRateInHz
the sample rate expressed in Hertz.
channelConfig
describes the configuration of the audio channels. SeeCHANNEL_OUT_MONO andCHANNEL_OUT_STEREO
audioFormat
the format in which the audio data is represented. SeeENCODING_PCM_16BIT andENCODING_PCM_8BIT
bufferSizeInBytes
the total size (in bytes) of the buffer where audio data is read from for playback. If using the AudioTrack in streaming mode, you can write data into this buffer in smaller chunks than this size. If using the AudioTrack in static mode, this
is the maximum size of the sound that will be played for this instance. SeegetMinBufferSize(int, int, int) to determine the minimum required buffer size for the successful creation of an AudioTrack instance in streaming mode. Using values smaller than getMinBufferSize()
will result in an initialization failure.
mode
streaming or static buffer. See MODE_STATIC andMODE_STREAM
sessionId
Id of audio session the AudioTrack must be attached to
AudioTrack有两种数据加载模式:

MODE_STREAM
在这种模式下,应用程序持续地write音频数据流到AudioTrack中,并且write动作将阻塞直到数据流从Java层传输到native层,同时加入到播放队列中。这种模式适用于播放大音频数据,但该模式也造成了一定的延时;

MODE_STATIC
在播放之前,先把所有数据一次性write到AudioTrack的内部缓冲区中。适用于播放内存占用小、延时要求较高的音频数据。

frameworks\base\core\jni\android_media_AudioTrack.cpp

?
1. 检查音频参数;

2. 创建一个AudioTrack(native)对象;

3. 创建一个AudioTrackJniStorage对象;

4. 调用set函数初始化AudioTrack;

buffersize = frameCount * 每帧数据量 = frameCount * (Channel数 * 每个Channel数据量)

构造native AudioTrack

frameworks\av\media\libmedia\AudioTrack.cpp

?

构造AudioTrackJniStorage

AudioTrackJniStorage是音频数据存储的容器,是对匿名共享内存的封装。

?

初始化AudioTrack

为AudioTrack设置音频参数信息,在Android4.4中,增加了一个参数transfer_type用于指定音频数据的传输方式,Android4.4定义了4种音频数据传输方式:

enum transfer_type {

TRANSFER_DEFAULT, // not specified explicitly; determine from the other parameters

TRANSFER_CALLBACK, // callback EVENT_MORE_DATA

TRANSFER_OBTAIN, // FIXME deprecated: call obtainBuffer() and releaseBuffer()

TRANSFER_SYNC, // synchronous write()

TRANSFER_SHARED, // shared memory

};

?
我们知道,AudioPolicyService启动时加载了系统支持的所有音频接口,并且打开了默认的音频输出,打开音频输出时,调用AudioFlinger::openOutput()函数为当前打开的音频输出接口创建一个PlaybackThread线程,同时为该线程分配一个全局唯一的audio_io_handle_t值,并以键值对的形式保存在AudioFlinger的成员变量mPlaybackThreads中。在这里首先根据音频参数通过调用AudioSystem::getOutput()函数得到当前音频输出接口的PlaybackThread线程id号,同时传递给createTrack函数用于创建Track。AudioTrack在AudioFlinger中是以Track来管理的。不过因为它们之间是跨进程的关系,因此需要一个“桥梁”来维护,这个沟通的媒介是IAudioTrack。函数createTrack_l除了为AudioTrack在AudioFlinger中申请一个Track外,还会建立两者间IAudioTrack桥梁。

获取音频输出

获取音频输出就是根据音频参数如采样率、声道、格式等从已经打开的音频输出描述符列表中查找合适的音频输出AudioOutputDescriptor,并返回该音频输出在AudioFlinger中创建的播放线程id号,如果没有合适当前音频输出参数的AudioOutputDescriptor,则请求AudioFlinger打开一个新的音频输出通道,并为当前音频输出创建对应的播放线程,返回该播放线程的id号。具体过程请参考Android AudioPolicyService服务启动过程中的打开输出小节。

创建AudioTrackThread线程

初始化AudioTrack时,如果audioCallback为Null,就会创建AudioTrackThread线程。

AudioTrack支持两种数据输入方式:

1) Push方式:用户主动write,MediaPlayerService通常采用此方式;

2) Pull方式: AudioTrackThread线程通过audioCallback回调函数主动从用户那里获取数据,ToneGenerator就是采用这种方式;

?

申请Track

音频播放需要AudioTrack写入音频数据,同时需要AudioFlinger完成混音,因此需要在AudioTrack与AudioFlinger之间建立数据通道,而AudioTrack与AudioFlinger又分属不同的进程空间,Android系统采用Binder通信方式来搭建它们之间的桥梁。

?
IAudioTrack建立了AudioTrack与AudioFlinger之间的关系,在static模式下,用于存放音频数据的匿名共享内存在AudioTrack这边创建,在stream播放模式下,匿名共享内存却是在AudioFlinger这边创建。这两种播放模式下创建的匿名共享内存是有区别的,stream模式下的匿名共享内存头部会创建一个audio_track_cblk_t对象,用于协调生产者AudioTrack和消费者AudioFlinger之间的步调。createTrack就是在AudioFlinger中创建一个Track对象。

frameworks\av\services\audioflinger\ AudioFlinger.cpp

?
该函数首先以单例模式为应用程序进程创建一个Client对象,直接对话某个客户进程。然后根据播放线程ID找出对应的PlaybackThread,并将创建Track的任务转交给它,PlaybackThread完成Track创建后,由于Track没有通信功能,因此还需要为其创建一个代理通信业务的TrackHandle对象。



构造Client对象
根据进程pid,为请求播放音频的客户端创建一个Client对象。

?
AudioFlinger的成员变量mClients以键值对的形式保存pid和Client对象,这里首先取出pid对应的Client对象,如果该对象为空,则为客户端进程创建一个新的Client对象。

?
构造Client对象时,创建了一个MemoryDealer对象,该对象用于分配共享内存。

frameworks\native\libs\binder\ MemoryDealer.cpp

?
MemoryDealer是个工具类,用于分配共享内存,每一个Client都拥有一个MemoryDealer对象,这就意味着每个客户端进程都是在自己独有的内存空间中分配共享内存。MemoryDealer构造时创建了一个大小为2*1024*1024的匿名共享内存,该客户进程所有的AudioTrack在AudioFlinger中创建的Track都是在这块共享内存中分配buffer。

?
由此可知,当应用程序进程中的AudioTrack请求AudioFlinger在某个PlaybackThread中创建Track对象时,AudioFlinger首先会为应用程序进程创建一个Client对象,同时创建一块大小为2M的共享内存。在创建Track时,Track将在2M共享内存中分配buffer用于音频播放。



创建Track对象

?
这里就为AudioTrack创建了一个Track对象。Track继承于TrackBase,因此构造Track时,首先执行TrackBase的构造函数。



?
TrackBase构造过程主要是为音频播放分配共享内存,在static模式下,共享内存由应用进程自身分配,但在stream模式,共享内存由AudioFlinger分配,static和stream模式下,都会创建audio_track_cblk_t对象,唯一的区别在于,在stream模式下,audio_track_cblk_t对象创建在共享内存的头部。

static模式:



stream模式:



接下来继续分析Track的构造过程:

?
在TrackBase的构造过程中根据是否创建Client对象来采取不同方式分配audio_track_cblk_t对象内存空间,并且创建audio_track_cblk_t对象。在Track构造中,根据不同的播放模式,创建不同的代理对象:

Stream模式下,创建AudioTrackServerProxy代理对象;Static模式下,创建StaticAudioTrackServerProxy代理对象;



在stream模式下,同时分配指定大小的音频数据buffer ,该buffer的结构如下所示:



我们知道在构造Client对象时,创建了一个内存分配工具对象MemoryDealer,同时创建了一块大小为2M的匿名共享内存,这里就是使用MemoryDealer对象在这块匿名共享内存上分配指定大小的buffer。

frameworks\native\libs\binder\MemoryDealer.cpp

?
?
?
audio_track_cblk_t对象用于协调生产者AudioTrack和消费者AudioFlinger之间的步调。



在createTrack时由AudioFlinger申请相应的内存,然后通过IMemory接口返回AudioTrack,这样AudioTrack和AudioFlinger管理着同一个audio_track_cblk_t,通过它实现了环形FIFO,AudioTrack向FIFO中写入音频数据,AudioFlinger从FIFO中读取音频数据,经Mixer后送给AudioHardware进行播放。

1) AudioTrack是FIFO的数据生产者;

2) AudioFlinger是FIFO的数据消费者;

构造TrackHandle
Track对象只负责音频相关业务,对外并没有提供夸进程的Binder调用接口,因此需要将通信业务委托给另外一个对象来完成,这就是TrackHandle存在的意义,TrackHandle负责代理Track的通信业务,它是Track与AudioTrack之间的跨进程通道。

?


AudioFlinger拥有多个工作线程,每个线程拥有多个Track。播放线程实际上是MixerThread的实例,MixerThread的threadLoop()中,会把该线程中的各个Track进行混合,必要时还要进行ReSample(重采样)的动作,转换为统一的采样率(44.1K),然后通过音频系统的AudioHardware层输出音频数据。



? Framework或者Java层通过JNI创建AudioTrack对象;

? 根据StreamType等参数,查找已打开的音频输出设备,如果查找不到匹配的音频输出设备,则请求AudioFlinger打开新的音频输出设备;

? AudioFlinger为该输出设备创建混音线程MixerThread,并把该线程的id作为getOutput()的返回值返回给AudioTrack;

? AudioTrack通过binder机制调用AudioFlinger的createTrack()创建Track,并且创建TrackHandle Binder本地对象,同时返回IAudioTrack的代理对象。

? AudioFlinger注册该Track到MixerThread中;

? AudioTrack通过IAudioTrack接口,得到在AudioFlinger中创建的FIFO(audio_track_cblk_t);

AudioTrack启动过程

AudioTrack数据写入过程

AudioTrack停止过程

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: