音频资源在播放时,会经常出现冲突的情况,如在进行音乐播放时有电话呼入、有新消息的提示音需要播放等,此类的并发处理就需要有一个统一的处理策略。在Android系统开发中,通过为不同的场景配置不同的播放接口,在底层执行统一的并发策略,使得开发者可以将精力更集中在应用本身。
AudioTrack、MediaPlayer、SoundPool、Ringtone、JetPlayer等都是Android音频处理中常用接口,本文将针对AudioTrack接口进行详细说明。
AudioTrack
AudioTrack用于管理单个的音频资源。 在构造AudioTrack实例时,会涉及到流类型、采样率、通道配置、音频格式、缓冲大小、播放模式等因素。
AudioTrack支持STREAM_VOICE_CALL、STREAM_SYSTEM、STREAM_RING、STREAM_MUSIC和STREAM_ALARM等流类型。
AudioTrack支持44100Hz、22050Hz、11025Hz等采样率。
AudioTrack支持单声道(CHANNEL_OUT_MONO)、立体声(CHANNEL_OUT_STEREO)等两种通道。
AudioTrack支持ENCODING_PCM_16BIT、ENCODING_PCM_8BIT等两种编码格式。
AudioTrack支持两种播放模式:静态模式(static mode)和流模式(Streaming mode)。其中静态模式由于没有从Java层向原生层传递数据造成的延迟,时延很小,当然受限于音频缓冲的大小,通常在游戏场景中用于播放时长很短的音频资源。当音频流较大不足以在音频缓冲中一次写入时,可采用流模式。
AudioTrack的播放状态包括PLAYSTATE_STOPPED、PLAYSTATE_PAUSED、PLAYSTATE_PLAYING等。
AudioTrack实例的状态包括STATE_INITIALIZED、STATE_NO_STATIC_DATA、STATE_UNINITIALIZED等。
向音频缓冲中添加数据的方法为write()。在设置音频缓冲时,其大小与采样率、通道和音频格式有关。其计算公式为:
缓冲大小=小帧数×(通道==CHANNEL_OUT_STEREO?2:1)×(音频格式== PCM16?2:1)
而小帧数则受制于采样率和音频设备的延迟等因素。
另外,在Android 2.3中,还引入了会话的概念,便于对单曲的音效进行处理。相应的方法包括:attachAuxEffect()、getAudioSessionId()、setAuxEffectSendLevel()等。
通过AudioTrack.OnPlaybackPositionUpdateListener监听器可以监听播放进度。
下面是一个背景音频的播放过程:
代码10-3 AudioTrack播放音频文件
public class BackgroundAudio extends Thread {
public static final int SAMPLE_RATE = 16000;
public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
public static final int BYTES_PER_SAMPLE = 2;
public static final int PLAYBACK_STREAM = AudioManager.STREAM_MUSIC;
……
public BackgroundAudio(byte[] data) {
//计算缓冲大小
final int minBufferSize = (BUFFER_TIME *SAMPLE_RATE*BYTES_PER_SAMPLE) / 1000;
//计算硬件的小缓冲
final int minHardwareBufferSize =
AudioTrack.getMinBufferSize(SAMPLE_RATE,
AudioFormat.CHANNEL_OUT_MONO,AUDIO_FORMAT);
mBufferSize = Math.max(minHardwareBufferSize, minBufferSize);
mAudioTrack = new AudioTrack(PLAYBACK_STREAM,
SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO,
AUDIO_FORMAT, mBufferSize, AudioTrack.MODE_STREAM);
if (mAudioTrack.getState() == AudioTrack.STATE_INITIALIZED) {
writeAudio();
start(); // 启动背景线程去推送音频数据
mAudioTrack.play();
} else {
Log.e(TAG, "Error initializing audio track.");
}
}
……
private void writeAudio() {
int len = mData.length;
int count;
int maxBytes = Math.min(mBufferSize, len - mPos);
count = mAudioTrack.write(mData, mPos, maxBytes);
if (count < 0) {
Log.e(TAG, "Error writing looped audio data");
halt();
return;
}
mPos += count;
if (mPos == len) {
mPos = 0; // Wraparound
}
}
}