1分钟部署网站📞AI智能客服,大模型训练自有数据,简单好用,有效降低客服成本 广告
这个用例很简单,但其中会有一些重要概念,应注意理解。 注意:要了解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了。