7.2 AudioTrack的破解

AudioTrack属于Audio系统对外提供的API类,所以它在Java层和Native层均有对应的类,先从Java层的用例开始。

7.2.1 用例介绍

这个用例很简单,但其中会有一些重要概念,应注意理解。

注意 要了解AudioTrack Java API的具体信息,需要仔细阅读Android API中的相关文档。阅读API文档,是一个快速掌握相关知识的好方法。


[—>AudioTrack API使用例子(Java层)]

//①根据音频数据的特性来确定所要分配的缓冲区的最小size。

int bufsize=

AudioTrack.getMinBufferSize(8000,//采样率:每秒8000个点。

AudioFormat.CHANNEL_CONFIGURATION_STEREO,//声道数:双声道。

AudioFormat.ENCODING_PCM_16BIT//采样精度:一个采样点16比特,相当于2个字节。

);

//②创建AudioTrack。

AudioTrack trackplayer=new AudioTrack(

AudioManager.STREAM_MUSIC,//音频流类型

8000,AudioFormat.CHANNEL_CONFIGURATION_STEREO,

AudioFormat.ENCODING_PCM_16BIT,bufsize,

AudioTrack.MODE_STREAM//数据加载模式);

//③开始播放。

trackplayer.play();

……

//④调用write写数据。

trackplayer.write(bytes_pkg,0,bytes_pkg.length);//往track中写数据。

……

//⑤停止播放和释放资源。

trackplayer.stop();//停止播放。

trackplayer.release();//释放底层资源。


上面的用例引入了两个新的概念,一个是数据加载模式,另一个是音频流类型。下面进行详细介绍。

1.AudioTrack的数据加载模式

AudioTrack有两种数据加载模式:MODE_STREAM和MODE_STATIC,它们对应着两种完全不同的使用场景。

MODE_STREAM:在这种模式下,通过write一次次把音频数据写到AudioTrack中。这和平时通过write系统调用往文件中写数据类似,但这种工作方式每次都需要把数据从用户提供的Buffer中拷贝到AudioTrack内部的Buffer中,这在一定程度上会使引起延时。为了解决这一问题,AudioTrack就引入了第二种模式。

MODE_STATIC:这种模式下,在play之前只需要把所有的数据通过一次write调用传递到AudioTrack的内部缓冲区中,后续就不必再传递数据了。这种模式适用于像铃声这种内存占用量较小,延时要求较高的文件。但它也有一个缺点,就是一次write的数据不能太多,否则系统无法分配足够的内存来存储全部数据。

这两种模式中MODE_STREAM模式相对更常见,并且也更复杂,我们的分析将以它为主。

注意 如果采用STATIC模式,须先调用write写数据,然后再调用play。

2.音频流的类型

在AudioTrack的构造函数中,会接触到AudioManager.STREAM_MUSIC这个参数。它的含义与Android系统对音频流的管理和分类有关。

Android将系统的声音分为好几种流类型,下面是几个常见的:

STREAM_ALARM:警告声

STREAM_MUSIC:音乐声,例如music等

STREAM_RING:铃声

STREAM_SYSTEM:系统声音,例如低电提示音、锁屏音等

STREAM_VOCIE_CALL:通话声

注意 上面这些类型的划分和音频数据本身并没有关系。例如MUSIC和RING类型都可以是某首MP3歌曲。另外,声音流类型的选择没有固定的标准,例如,铃声预览中的铃声可以设置为MUSIC类型。

音频流类型的划分和Audio系统对音频的管理策略有关。其具体的作用在以后的分析中再详细介绍。在目前的用例中,把它当做一个普通数值即可。

3.Buffer分配和Frame的概念

在用例中碰到的第一个重要函数就是getMinBufferSize。这个函数对于确定应用层分配多大的数据Buffer具有重要指导意义。先回顾一下它的调用方式:


[—>AudioTrack API使用例子(Java层)]

//注意这些参数的值。想象我们正在一步步的Trace,这些参数都会派上用场。

AudioTrack.getMinBufferSize(8000,//每秒8000个点

AudioFormat.CHANNEL_CONFIGURATION_STEREO,//双声道

AudioFormat.ENCODING_PCM_16BIT);


来看这个函数的实现:


[—>AudioTrack.java]

static public int getMinBufferSize(int sampleRateInHz,int channelConfig,

int audioFormat){

int channelCount=0;

switch(channelConfig){

case AudioFormat.CHANNEL_OUT_MONO:

case AudioFormat.CHANNEL_CONFIGURATION_MONO:

channelCount=1;

break;

case AudioFormat.CHANNEL_OUT_STEREO:

case AudioFormat.CHANNEL_CONFIGURATION_STEREO:

channelCount=2;//目前最多支持双声道。

break;

default:

return AudioTrack.ERROR_BAD_VALUE;

}

//目前只支持PCM8和PCM16精度的音频数据。

if((audioFormat!=AudioFormat.ENCODING_PCM_16BIT)

&&(audioFormat!=AudioFormat.ENCODING_PCM_8BIT)){

return AudioTrack.ERROR_BAD_VALUE;

}

//对采样频率也有要求,太低或太高都不行。

if((sampleRateInHz<4000)||(sampleRateInHz>48000))

return AudioTrack.ERROR_BAD_VALUE;

/*

调用Native函数,先想想为什么,如果是简单计算,那么Java层做不到吗?

原来,还需要确认硬件是否支持这些参数,当然得进入Native层查询了。

*/

int size=native_get_min_buff_size(sampleRateInHz,

channelCount,audioFormat);

if((size==-1)||(size==0)){

return AudioTrack.ERROR;

}

else{

return size;

}

}


Native的函数将查询Audio系统中音频输出硬件HAL对象的一些信息,并确认它们是否支持这些采样率和采样精度。

说明 HAL对象的具体实现和硬件厂商有关系,如果没有特殊说明,我们则把硬件和HAL作为一种东西讨论。

来看Native的native_get_min_buff_size函数,它在android_media_track.cpp中。


[—>android_media_track.cpp]

/*

注意我们传入的参数是:

sampleRateInHertz=8000,nbChannels=2

audioFormat=AudioFormat.ENCODING_PCM_16BIT

*/

static jint android_media_AudioTrack_get_min_buff_size(

JNIEnv*env,jobject thiz,

jint sampleRateInHertz,jint nbChannels,jint audioFormat)

{

int afSamplingRate;

int afFrameCount;

uint32_t afLatency;

/*

下面这些调用涉及了AudioSystem,这个和AudioPolicy有关系。这里仅把它们看成是

信息查询即可。

*/

//查询采样率,一般返回的是所支持的最高采样率,例如44100。

if(AudioSystem:getOutputSamplingRate(&afSamplingRate)!=NO_ERROR){

return-1;

}

//①查询硬件内部缓冲的大小,以Frame为单位。什么是Frame?

if(AudioSystem:getOutputFrameCount(&afFrameCount)!=NO_ERROR){

return-1;

}

//查询硬件的延时时间。

if(AudioSystem:getOutputLatency(&afLatency)!=NO_ERROR){

return-1;

}

……


这里有必要插入一个说明,因为代码中出现了音频系统中的一个重要概念:Frame(帧)。

说明 Frame是一个单位,经多方查寻,最终在ALSA的wiki中找到了对它的解释。Frame被用来直观地描述数据量的多少,例如,一帧等于多少字节。1单位的Frame等于1个采样点的字节数×声道数(比如PCM16,双声道的1个Frame等于2×2=4字节)。

我们知道,1个采样点只针对一个声道,而实际上可能会有一或多个声道。由于不能用一个独立的单位来表示全部声道一次采样的数据量,也就引出了Frame的概念。Frame的大小,就是一个采样点的字节数×声道数。另外,在目前的声卡驱动程序中,其内部缓冲区也是采用Frame作为单位来分配和管理的。

OK,继续native_get_min_buff_size函数。


……

//minBufCount表示缓冲区的最少个数,它以Frame作为单位。

uint32_t minBufCount=afLatency/((1000*afFrameCount)/afSamplingRate);

if(minBufCount<2)minBufCount=2;//至少要两个缓冲。

//计算最小帧个数。

uint32_t minFrameCount=

(afFrameCountsampleRateInHertzminBufCount)/afSamplingRate;

//下面根据最小的FrameCount计算最小的缓冲大小。

int minBuffSize=minFrameCount//计算方法完全符合我们前面关于Frame的介绍。

*(audioFormat==javaAudioTrackFields.PCM16?2:1)

*nbChannels;

return minBuffSize;

}


getMinBufSize会在综合考虑硬件的情况(诸如是否支持采样率,硬件本身的延迟情况等)后,得出一个最小缓冲区的大小。一般我们分配的缓冲大小会是它的整数倍。

好了,介绍完一些基本概念后,开始要分析AudioTrack了。