共计 4704 个字符,预计需要花费 12 分钟才能阅读完成。
一. AudioTrack 根本应用
AudioTrack 类能够实现 Android 平台上 音频数据的输入 工作,AudioTrack 有两种数据加载模式(MODE_STREAM 和 MODE_STATIC),对应的是数据加载模式和音频流类型,对应着两种齐全不同的应用场景。
MODE_STREAM:在这种模式下,通过 write 一次次把音频数据写到 AudioTrack 中。这和平时通过 write 零碎调用往文件中写数据相似,但这种工作形式每次都须要把数据从用户提供的 Buffer 中拷贝到 AudioTrack 外部的 Buffer 中,这肯定水平上会使引入延时 ,为了解决这一问题,AudioTrack 就引入了第二组模式。
MODE_STATIC: 这种模式下,在 play 之前 只须要把所有数据通过一次 write 调用传递到 AudioTrack 中的外部缓冲区,后续就不用再传递数据了。这种模式实用于像铃声这种内存占用量较小,延时要求较高的文件。但它也有一个毛病,就是一次 write 的数据不能太多,否则零碎无奈调配足够的内存来存储全副数据。
1.1 MODE_STATIC 模式
MODE_STATIC 模式输入音频的形式如下(留神:应用 STATIC 模式须要先调用 write 写数据,而后再调用 play。)
public class AudioTrackPlayerDemoActivity extends Activity implements OnClickListener{
private static final String TAG = "AudioTrackPlayerDemoActivity";
private Button button;
private byte[] audioData;
private AudioTrack audioTrack;
@Override
public void onCreate(Bundle savedInstanceState)
{super.onCreate(savedInstanceState);
super.setContentView(R.layout.main);
this.button = (Button) super.findViewById(R.id.play);
this.button.setOnClickListener(this);
this.button.setEnabled(false);
new AsyncTask<Void,Void,Void>(){
@Override
protected Void doInBackground(Void... params){
try{InputStream in = getResources().openRawResource(R.raw.ding);
try{ByteArrayOutputStream out = new ByteArrayOutputStream(264848);
for(int b; (b = in.read())!=-1;){out.write(b);
}
Log.d(TAG,"Got the data");
audioData = out.toByteArray();}finally{in.close();
}
}catch(IOException e){Log.wtf(TAG,"Failed to read",e);
}
return null;
}
@Override
protected void onPostExecute(void v){Log.d(TAG,"Createing track...");
button.setEnable(true);
Log.d(TAG,"Enabled button");
}
}.execute();}
public void onClick(View view) {this.button.setEnabled(false);
this.releaseAudioTrack();
this.audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT,
audioData.length, AudioTrack.MODE_STATIC);
Log.d(TAG, "Writing audio data...");
this.audioTrack.write(audioData, 0, audioData.length);
Log.d(TAG, "Starting playback");
audioTrack.play();
Log.d(TAG, "Playing");
this.button.setEnabled(true);
}
private void releaseAudioTrack() {if (this.audioTrack != null) {Log.d(TAG, "Stopping");
audioTrack.stop();
Log.d(TAG, "Releasing");
audioTrack.release();
Log.d(TAG, "Nulling");
}
}
public void onPause() {super.onPause();
this.releaseAudioTrack();}
}
1.2 MODE_STREAM 模式
MODE_STREAM 模式输入音频的形式如下:
byte[] tempBuffer = new byte[bufferSize];
int readCount = 0;
while (dis.available() > 0) {readCount = dis.read(tempBuffer);
if (readCount == AudioTrack.ERROR_INVALID_OPERATION || readCount == AudioTrack.ERROR_BAD_VALUE) {continue;}
if (readCount != 0 && readCount != -1) {audioTrack.play();
audioTrack.write(tempBuffer, 0, readCount);
}
}
二. AudioTrack 详解
2.1 音频流的类型
在 AudioTrack 构造函数中,会接触到 AudioManager.STREAM_MUSIC 这个函数,它的含意与 Android 系统对音频流的治理和分类无关。
Android 将零碎的声音分为好几种流类型,上面是几个常见的:
——STREAM_ALARM:警告声
——STREAM_MUSIC:音乐声,例如 music 等
——STREAM_RING:铃声
——STREAM_SYSTEM:零碎声音,例如低电提示音,锁屏音等
——STREAM_VOCIE_CALL:通话声
留神:下面这些类型的划分和音频数据自身并没有关系 。例如 MUSIC 和 RING 类型都能够是某首 MP3 歌曲。另外, 声音流类型的抉择没有固定的规范 ,例如,铃声预览中的铃声能够设置为 MUSIC 类型。 音频流类型的划分和 Audio 系统对音频的管理策略无关。
2.2 Buffer 调配和 Frame 的概念
在计算 Buffe 调配的大小时,咱们常常用到的一个办法就是:getMinBufferSize。这个函数决定了应用层调配多大的数据 Buffer。
AudioTrack.getMinBufferSize(8000/* 每秒 8k 个采样点 */,AudioFormat.CHANNEL_CONFIGURATION_STEREO/* 双声道 */,AudioFormat.ENCODEING_PCM_16BIT);
从 AudioTrack.getMinBufferSize 开始追溯代码,能够发现在底层的代码中有一个很重要的概念:Frame(帧)。Frame 是一个单位,用来形容数据量的多少 。 1 单位的 Frame 等于 1 个采样点的字节数 x 声道数(比方 PCM16,双声道的 1 个 Frame 等于 2 ×2= 4 字节)。1 个采样点只针对一个声道,而实际上可能会有一或多个声道。因为不能用一个独立的单位来示意全副声道一次采样的数据量,也就引出了 Frame 的概念。在目前的声卡驱动程序中,其外部缓冲区也是采样 Frame 作为单位来调配和治理的。
上面是追溯到的 native 层的办法:
//minBufCount 示意缓冲区的起码个数,它以 Frame 作为单位
uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSamplingRate);
if(minBufCount < 2) minBufCount = 2; // 至多要两个缓冲
// 计算最小帧个数
uint32_tminFrameCount = (afFrameCount*sampleRateInHertz*minBufCount)/afSamplingRate;
// 依据最小的 FrameCount 计算最小的缓冲大小
intminBufferSize = minFrameCount * (audioFormat = javaAudioTrackFields.PCM16?2:1) * nbChannels;
return minBuffSize;
getMinBufSize 会综合思考硬件的状况(诸如是否反对采样率,硬件自身的提早状况等)后,得出一个最小缓冲区的大小。个别咱们调配的缓冲大小会是它的整数倍。
2.3 AudioTrack 结构过程
每一个音频流对应这一个 AudioTrack 类的一个实例,每个 AudioTrack 会在创立时注册到 AudioFlinger 中,由 AudioFlinger 把所有的 AudioTrack 进行混合(Mixer),而后输送到 AudioHardware 中进行播放,目前 Android 同时最多能够创立 32 个音频流,也就是说,Mixer 最多会同时解决 32 个 AudioTrack 的数据流。
三. AudioTrack 与 MediaPlayer 的比照
播放声音能够用 MediaPlayer 和 AudioTrack,两者都提供了 Java API 供给用开发这应用。尽管都能够播放声音,但两者还是有很大区别的。
3.1 区别
其中最大的区别是MediaPlayer 能够播放多种格局的声音文件,例如 MP3、AAC、WAV、OGG、MIDI 等。MediaPlayer 会在 framework 层创立对应的音频解码器。而AudioTrack 只能播放曾经解码的 PCM 流,如果比照反对的文件格式的话则是 AudioTrack 只反对 wav 格局的音频文件,因为 wav 格局的音频文件大部分都是 PCM 流。AudioTrack 不创立解码器,所以只能播放不须要解码的 wav 文件。
3.2 分割
MediaPlayer 在 framework 层还是会创立 AudioTrack,把解码后的 PCM 数据流传递给 AudioTrack,AudioTrack 再传递给 AudioFlinger 进行混音,而后才传递给硬件播放,所以是 MediaPlayer 蕴含了 AudioTrack。
3.3 SoundPool
在接触 Android 音频播放 API 的时候,发现 SoundPool 也能够用于播放音频。上面是三者的应用场景:MediaPlayer 更加适宜在后盾长时间播放本地音乐文件或者在线的流式资源;SoundPool 则适宜播放比拟短的音频片段,比方游戏声音、按键声、铃声片段等,它能够同时播放多个音频;而 AudioTrack 则更靠近底层,提供了十分弱小的控制能力,反对低提早播放,适宜流媒体和 VolP 语音电话等场景。
四. 源码
http://github.com/renhui/Audi…