前言
Android SDK提供了多种音频播放API来满足不同场景的需求
- MediaPlayer:
android.media.MediaPlayer是 Android 中一个常用的音频和视频播放类。它可以用来播放各种格式的媒体文件,如 MP3、AAC 等,并且支持本地文件和网络流媒体。 - SoundPool:
android.media.SoundPool类适用于短音频片段的播放,特别适合游戏中的音效。它能够并发播放多个声音,并提供音量控制、优先级等特性。 - AudioTrack:
android.media.AudioTrack类用于直接从内存中播放原始音频数据流,例如 PCM 格式的数据。对于需要低延迟播放或者处理自定义编码音频的应用非常有用。
在这里,我们主要了解一下 AudioTrack 是如何来播放音频的。
AudioTrack 的工作流程
初始化 AudioTrack 对象
首先看一下AudioTrack 的构造函数
public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,
int bufferSizeInBytes, int mode, int sessionId)
创建 AudioTrack 实例时,需要指定一系列参数:
- streamType 音频流类型:这个参数代表着当前应用使用的哪一种音频管理策略,当系统有多个进程需要播放音频时,这个管理策略会决定最终的展现效果,常见的有:
- STREAM_VOCIE_CALL:电话声音
- STREAM_SYSTEM:系统声音
- STREAM_RING:铃声
- STREAM_MUSCI:音乐声
- STREAM_ALARM:警告声
- STREAM_NOTIFICATION:通知声
- sampleRateInHz 采样率:指定音频数据每秒的采样次数。采样率越高,音质越好,但也会占用更多存储空间和计算资源。现在唯一能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。
- channelConfig 声道配置:使用
AudioFormat类中的常量指定声道数。AudioFormat.CHANNEL_OUT_MONO表示单声道,AudioFormat.CHANNEL_OUT_STEREO表示立体声双声道。
- audioFormat 编码格式:使用
AudioFormat类中的常量指定音频数据的编码格式,这个参数是用来配置 数据位宽 的ENCODING_PCM_16BIT(16bit)表示16位有符号PCM格式 (这个格式可以保证兼容所有Android手机)ENCODING_PCM_8BIT(8bit)表示8位无符号PCM格式
- bufferSizeInBytes 缓冲区大小:使用
AudioTrack.getMinBufferSize()方法计算最小有效缓冲区大小 - mode 播放模式:通过
AudioTrack.MODE_STREAM或AudioTrack.MODE_STATIC进行设置。MODE_STREAM:连续播放模式,适用于实时生成或者持续写入音频数据的情况。MODE_STATIC:静态播放模式,适用于一次性加载所有音频数据到缓冲区中并一次性播放完的情况。
填充音频数据
- 从某个来源获取音频数据(例如读取文件或接收网络传输的PCM数据)。
- 将音频数据写入
AudioTrack的内部缓冲区。这通常在一个循环或者单独的线程中进行以保证连续播放。
byte[] audioData = ...; // 填充音频数据
audioTrack.write(audioData, 0, audioData.length); // 将音频数据写入AudioTrack的缓冲区
准备和启动播放
- 在将音频数据写入缓冲区后,调用
AudioTrack.play()方法开始播放。
audioTrack.play();
实时音频处理
- 如果需要进行实时音频处理(比如回声消除、音量控制等),则在写入音频数据到
AudioTrack之前进行相应的处理。
停止和释放资源
- 当音频播放完毕或者需要停止播放时,调用
AudioTrack.stop()方法停止播放,并且在不再需要AudioTrack对象时,调用release()方法释放相关的系统资源。
audioTrack.stop();
audioTrack.release();
音频录制播放代码示例:
这个是
MODE_STREAM模式的代码示例,如果是MODE_STATIC模式,需要先把音频一次性加载到缓冲区,然后再播放
/**
* 采样率,现在唯一能够保证在所有设备上使用的采样率是44100Hz。
*/
public static final int SAMPLE_RATE_INHZ = 44100;
/**
* 声道数。
*/
public static final int CHANNEL_IN_CONFIG = AudioFormat.CHANNEL_IN_MONO;
public static final int CHANNEL_OUT_CONFIG = AudioFormat.CHANNEL_OUT_MONO;
/**
* 音频数据的格式。
*/
public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
public void startRecord() {
final int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_IN_CONFIG, AUDIO_FORMAT);
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE_INHZ,
CHANNEL_IN_CONFIG, AUDIO_FORMAT, minBufferSize);
final byte data[] = new byte[minBufferSize];
final File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "audioDemo.pcm");
if (!file.mkdirs()) {
Log.e(TAG, "Directory not created");
}
if (file.exists()) {
file.delete();
}
audioRecord.startRecording();
isRecording = true;
new Thread(new Runnable() {
@Override
public void run() {
FileOutputStream os = null;
try {
os = new FileOutputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if (null != os) {
while (isRecording) {
int read = audioRecord.read(data, 0, minBufferSize);
// 如果读取音频数据没有出现错误,就将数据写入到文件
if (AudioRecord.ERROR_INVALID_OPERATION != read) {
try {
os.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
public void stopRecord() {
isRecording = false;
// 释放资源
if (null != audioRecord) {
audioRecord.stop();
audioRecord.release();
audioRecord = null;
}
}
private void startPlay() {
final int minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_OUT_CONFIG, AUDIO_FORMAT);
audioTrack = new AudioTrack(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build(),
new AudioFormat.Builder().setSampleRate(SAMPLE_RATE_INHZ)
.setEncoding(AUDIO_FORMAT)
.setChannelMask(CHANNEL_OUT_CONFIG)
.build(),
minBufferSize,
AudioTrack.MODE_STREAM,
AudioManager.AUDIO_SESSION_ID_GENERATE);
audioTrack.play();
File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "audioDemo.pcm");
try {
fileInputStream = new FileInputStream(file);
new Thread(new Runnable() {
@Override
public void run() {
try {
byte[] tempBuffer = new byte[minBufferSize];
while (fileInputStream.available() > 0) {
int readCount = fileInputStream.read(tempBuffer);
if (readCount == AudioTrack.ERROR_INVALID_OPERATION ||
readCount == AudioTrack.ERROR_BAD_VALUE) {
continue;
}
if (readCount != 0 && readCount != -1) {
audioTrack.write(tempBuffer, 0, readCount);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
}
private void stopPlay() {
if (audioTrack != null) {
audioTrack.stop();
audioTrack.release();
}
}