Android中Java层AudioTrack的使用详解与总结
介绍:AudioTrack是安卓sdk提供的一种只能播放音频裸数据(解码后的pcm)的一个解决方案。
AudioTrack有两种载入数据的方式:
MODE_STATIC :静态方式:意思是一次性载入全部待播放的pcm数据,后续再播放出来。适合短小的音频播放,例如提示音等场景。不适合长时间播放音频场景。因为消耗内存太大。
MODE_STREAM:流的放方式:意思是可以源源不断往AudioTrack缓冲区中写入pcm数据。适合听音乐,看电影等长时间播放音频场景。
音频知识点:
采样率 {如44100 hz}:是指在声音录制过程,声音的模拟信号转为数字信号时单位时间内{1秒钟} 的采样点数。理论上采样率越高,声音还原度越高 ,但人耳对声音的敏感也有极限,目前最高是192k hz。
声道数 {如2个声道}:声道数量,现代有的声音最高可以做到24声道。奔驰S的大柏林了解一下。
采样位宽【常见的16位{2字节}、32位{4字节}】:量化位宽。
一秒钟播放的pcm字节数= 采样率 * 声道数 * 采样位宽{字节} 如采样率为 44100 hz,2声道、采样位宽是16位{2字节},则有一秒钟播放了:44100 * 2 * 2 = 176400 字节。约等于 172.27kb 存储。
所以由此可见【根据公式计算】,未经过压缩处理的音频裸数据,占用的存储空间还是蛮大的
使用AudioTrack播放音频的关键api:
1、初始化AudioTrack。
1.1可以使用构造函数new一个实例对象、关键点是buffersize要计算正确:
AudioTrack.getMinBufferSize(audioSampleRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT); 参数分别是采样率,声道布局配置、采样位宽
1.2 也可以使用AudioTrack内部的Builder来构建支持更多参数设置的实例对象。
2、write方法:这个方法要求写入解码后的字节数据,通常是字节数组。write方法提供了好几个方式写入数据。很简单,对着参数要求传入即可。详细自行去看sdk中的方法api定义。
3、flush方法。在write后一定要调用flush方法刷出缓冲区内的数据,否则在播放某些音频可能会出现声音不连续的情况。
4、如何计算当前音频的播放时长?和 总时长?
总时长其实很简单:如果你播放的就是.pcm文件,则直接计算文件的总byte大小,然后除以 1秒的播放量就行。
如果你播放的不是.pcm文件,而是封装过的音频文件,那么在解封装时,有api获取。
例如你使用 MediaExtractor来解封装MP3文件,那么可以通过下面方式获取
按照上面说的,可以使用: 总的播放字节数 / 一秒钟播放的字节数 = 播放了多少秒。
只要知道 总的播放字节数就能求出。
我们来简化一下公式:1 秒的数据量 * X 秒 = 总的播放数据量
有:采样率* 声道数 * 采样位宽{字节} * X秒 = 总的播放数据量
总的播放数据量 作为右边值,其声道数 和 采样位宽与左边公式的其实是一致的。于是有
总的采样点数 / 采样率{1秒钟的采样点数} = 播放了多少秒。
AudioTrack中有提供api 获取总共播放的采样点数:
audioTrack.getPlaybackHeadPosition() 这个方法合适以流的方式载入数据的场景。
于是有:
int playSize = audioTrack.getPlaybackHeadPosition(); int rate = audioTrack.getPlaybackRate(); float currentPlayTime = playSize * 1.0f / rate; MyLog.v("音频当前播放到="+currentPlayTime+ "秒了");
实际使用中其实是希望有个监听器来更新当前音频播放时长的。AudioTrack也提供了方法。如下
audioTrack.setPositionNotificationPeriod(1000);//回调频率,一定要设置这个,否则不会回调, audioTrack.setPlaybackPositionUpdateListener(new AudioTrack.OnPlaybackPositionUpdateListener() { @Override public void onMarkerReached(AudioTrack track) { } @Override public void onPeriodicNotification(AudioTrack audioTrack) { if (track == null || track.getState() == AudioTrack.STATE_UNINITIALIZED) { return; } if (track.getPlayState() != AudioTrack.PLAYSTATE_PLAYING) { return; } int playSize = audioTrack.getPlaybackHeadPosition(); int rate = audioTrack.getPlaybackRate(); float currentPlayTime = playSize * 1.0f / rate; MyLog.v("音频当前播放到: "+currentPlayTime+ "秒了"); } });
其他的api,诸如 暂停,播放,停止,释放资源等,直接调用就行。
注意,音频播放几乎都是多线程场景下运行代码,要注意临界资源问题。
作者:达拉土