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了。