7.2.1 用例介绍
最后更新于:2022-04-02 05:51:16
这个用例很简单,但其中会有一些重要概念,应注意理解。
注意:要了解AudioTrack Java API的具体信息,需要仔细阅读Android API中的相关文档。阅读API文档,是一个能快速掌握相关知识的好方法。
**AudioTrackAPI使用例子(Java层)**
~~~
//① 根据音频数据的特性来确定所要分配的缓冲区的最小size
int bufsize =
AudioTrack.getMinBufferSize(8000,//采样率:每秒8K个点
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模式相对常见和复杂,我们的分析将以它为主。
>[info]**注意**:如果采用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具有重要指导意义。先回顾一下它的调用方式:
**AudioTrackAPI使用例子(Java层)**
~~~
//注意这些参数的值。想象我们正在一步步的Trace,这些参数都会派上用场
AudioTrack.getMinBufferSize(8000,//每秒8K个点
AudioFormat.CHANNEL_CONFIGURATION_STEREO,//双声道
AudioFormat.ENCODING_PCM_16BIT);
~~~
来看这个函数的实现:
**AudioTrack.java**
~~~
static public int getMinBufferSize(intsampleRateInHz, int channelConfig,
intaudioFormat) {
int channelCount = 0;
switch(channelConfig) {
case AudioFormat.CHANNEL_OUT_MONO:
caseAudioFormat.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 jintandroid_media_AudioTrack_get_min_buff_size(
JNIEnv*env, jobject thiz,
jintsampleRateInHertz, jint nbChannels, jint audioFormat)
{
intafSamplingRate;
intafFrameCount;
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_tminFrameCount =
(afFrameCount*sampleRateInHertz*minBufCount)/afSamplingRate;
//下面根据最小的FrameCount计算最小的缓冲大小
intminBuffSize = minFrameCount //计算方法完全符合我们前面关于Frame的介绍
* (audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1)
* nbChannels;
returnminBuffSize;
}
~~~
getMinBufSize会综合考虑硬件的情况(诸如是否支持采样率,硬件本身的延迟情况等)后,得出一个最小缓冲区的大小。一般我们分配的缓冲大小会是它的整数倍。
好了,介绍完一些基本概念后,开始要分析AudioTrack了。
';