AudioTrack是Android平台用于播放原始PCM音频数据的核心类。它不负责解码(如MP3转PCM),只负责将已经解码好的PCM数据“推送”给音频设备。下面从构造方法演进、两种工作模式、完整生命周期、实用技巧四个维度,通过可直接运行的代码实例,讲透AudioTrack的具体使用。
一、核心概念速览:两个关键选择
在使用AudioTrack之前,必须明确两个基本决策:
| 决策维度 | 选项 | 典型场景 |
|---|---|---|
| 数据加载模式 | MODE_STREAM | 播放网络音频、长音频、实时音频流(边写边播) |
MODE_STATIC | 播放短促音效(按键音、游戏击中、铃声),一次性加载 | |
| 音频流类型 | STREAM_MUSIC | 媒体播放、游戏音效 |
STREAM_RING | 铃声、通知 | |
STREAM_VOICE_CALL | 通话场景 |
音频流类型不改变音频数据本身,而是告知系统该音频的策略归属(如音量管控、焦点竞争)。
二、创建AudioTrack:新旧两种写法
✅ 现代写法(API 21+,Android 5.0+ 强烈推荐)
使用 AudioTrack.Builder,代码清晰,默认值智能。
// 1. 获取最小缓冲区大小(流模式必需)
int minBufferSize = AudioTrack.getMinBufferSize(44100,
AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT);
// 2. 通过Builder链式构建
AudioTrack audioTrack = new AudioTrack.Builder()
.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA) // 策略:媒体音乐
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build())
.setAudioFormat(new AudioFormat.Builder()
.setSampleRate(44100) // 采样率 44.1kHz
.setEncoding(AudioFormat.ENCODING_PCM_16BIT) // 16bit
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO) // 双声道
.build())
.setBufferSizeInBytes(minBufferSize) // 缓冲区大小
.setTransferMode(AudioTrack.MODE_STREAM) // 流式模式
.build();
Builder默认行为(不显式设置时):
setAudioAttributes缺省 →USAGE_MEDIAsetAudioFormat缺省 → 立体声、PCM_16BIT,采样率由设备决定setBufferSizeInBytes缺省 → 流模式下自动取最小值setTransferMode缺省 →MODE_STREAMsetSessionId缺省 → 自动生成新ID
🔧 传统写法(仍兼容,但已过时)
// API 21之前或遗留代码中常见
int bufferSize = AudioTrack.getMinBufferSize(44100,
AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT);
AudioTrack audioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC, // 流类型
44100, // 采样率
AudioFormat.CHANNEL_OUT_STEREO, // 声道
AudioFormat.ENCODING_PCM_16BIT, // 位深
bufferSize, // 缓冲区大小
AudioTrack.MODE_STREAM // 模式
);
三、两种工作模式深度解析
这是AudioTrack使用中最关键的决策点,直接决定调用时序和数据写入方式。
1. MODE_STREAM(流式模式)
特征:边写入边播放,write操作阻塞直到数据被拷贝到内部缓冲区。
适用:音频文件较大、持续播放(音乐、语音聊天、实时解码)。
// 流式模式标准调用顺序
audioTrack.play(); // ① 先启动播放
while (hasData) {
byte[] pcmData = getNextPcmChunk(); // 从文件/网络/解码器获取
audioTrack.write(pcmData, 0, pcmData.length); // ② 循环写入
}
// 播放完毕
audioTrack.stop();
audioTrack.release();
⚠️ 关键:play() 必须在 write() 之前调用,否则数据会被丢弃或阻塞。
2. MODE_STATIC(静态模式)
特征:一次性将所有数据写入内部缓冲区,play() 从缓冲区读取,可零拷贝循环播放。
适用:短促音效(<1秒)、需要频繁重复播放(如游戏子弹、点击反馈)。
// 静态模式标准调用顺序
byte[] allPcmData = loadCompleteSound(); // 完整PCM数据
audioTrack.write(allPcmData, 0, allPcmData.length); // ① 先全部写入
audioTrack.play(); // ② 开始播放
// 循环播放(无需重复write)
audioTrack.setLoopPoints(0, totalSamples, -1); // -1表示无限循环
audioTrack.play(); // 再次触发播放
⚠️ 关键:write() 必须在 play() 之前调用,顺序与流模式相反。
四、完整生命周期:标准代码模板
以下是一个流式模式播放PCM文件的完整范例,涵盖异常处理和资源释放:
public class PcmPlayer {
private AudioTrack audioTrack;
private boolean isPlaying = false;
private final int sampleRate = 44100;
private final int channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
private final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
public void startPlayback(String pcmFilePath) {
// 1. 计算最小缓冲区
int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
// 2. 构建AudioTrack(推荐Builder模式)
audioTrack = new AudioTrack.Builder()
.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.build())
.setAudioFormat(new AudioFormat.Builder()
.setSampleRate(sampleRate)
.setEncoding(audioFormat)
.setChannelMask(channelConfig)
.build())
.setBufferSizeInBytes(minBufferSize * 4) // 通常分配4倍最小缓冲,减少欠载风险
.setTransferMode(AudioTrack.MODE_STREAM)
.build();
// 3. 启动播放器(必须先play)
audioTrack.play();
isPlaying = true;
// 4. 开启工作线程写入数据
new Thread(() -> {
try (FileInputStream fis = new FileInputStream(pcmFilePath);
BufferedInputStream bis = new BufferedInputStream(fis)) {
byte[] buffer = new byte[minBufferSize];
int read;
while (isPlaying && (read = bis.read(buffer)) > 0) {
// write是阻塞操作,返回实际写入的字节数
int written = audioTrack.write(buffer, 0, read);
if (written != read) {
// 缓冲区满,等待;write内部已处理等待,这里仅做监控
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5. 播放结束,清理资源
stopPlayback();
}
}).start();
}
public void stopPlayback() {
isPlaying = false;
if (audioTrack != null) {
if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
audioTrack.stop(); // 停止播放
}
audioTrack.release(); // 释放native资源
audioTrack = null;
}
}
}
五、实用技巧与高频场景
1. 如何正确计算缓冲区大小?
永远不要硬编码,必须调用 AudioTrack.getMinBufferSize()。该函数会查询硬件HAL层,综合考虑采样率、声道数、位深和硬件延迟,返回一个保证不欠载的最小帧数。
int minBufferSize = AudioTrack.getMinBufferSize(44100,
AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT);
// 实际使用建议:minBufferSize * 2 或 *4,防止写入速度跟不上播放速度
int actualBufferSize = minBufferSize * 4;
2. 如何实现无缝循环播放?
- 静态模式:直接使用
setLoopPoints(startFrame, endFrame, loopCount)。loopCount = -1表示无限循环。 - 流式模式:需要在写入线程中检测文件末尾,重新从开头读取文件,注意不要调用stop(),保持play状态持续写入。
3. 如何获取当前播放进度?
int currentPosition = audioTrack.getPlaybackHeadPosition(); // 单位:帧
long currentTimeMicro = audioTrack.getTimestamp(new AudioTimestamp()); // API 19+
注意:getPlaybackHeadPosition() 在静音或停止时可能不准。
4. 如何设置音效(音量、速度、双声道模式)?
// 设置播放速度(变调不变速效果不同)
PlaybackParams params = new PlaybackParams();
params.setSpeed(1.5f); // 1.5倍速
audioTrack.setPlaybackParams(params);
// 设置双单声道模式(立体声转单声道复制)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
audioTrack.setDualMonoMode(AudioTrack.DUAL_MONO_MODE_LR); // 左右混合
}
5. 性能模式与低延迟
// API 23+ 设置性能模式
audioTrack = new AudioTrack.Builder()
.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY) // 低延迟
// ...
.build();
低延迟模式适用于游戏、实时交互应用,会强制使用更小的缓冲区。
六、常见误区与避坑指南
-
误区:MediaPlayer能播MP3,AudioTrack也能播MP3
❌ AudioTrack只能播放PCM数据。给AudioTrack传MP3文件会发出刺耳噪音。 -
误区:MODE_STATIC必须先play再write
❌ 完全错误。STATIC模式必须先write,再play。顺序反了会导致无声或异常。 -
误区:write返回写入字节数,必须等于请求数
❌ 实际可能小于请求数,尤其是在缓冲区即将写满时。必须检查返回值并处理剩余数据。 -
误区:stop()后可以直接play()继续播放
❌ stop()会丢弃缓冲区所有残留数据。如需暂停/恢复,应使用pause()和play(),stop()用于彻底停止。 -
误区:不调用release()也没关系
❌ AudioTrack持有native层的音频线程和内存,不释放会造成内存泄漏和音频服务资源占用。