Android AudioTrack 笔记

3 阅读5分钟

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_MEDIA
  • setAudioFormat 缺省 → 立体声、PCM_16BIT,采样率由设备决定
  • setBufferSizeInBytes 缺省 → 流模式下自动取最小值
  • setTransferMode 缺省 → MODE_STREAM
  • setSessionId 缺省 → 自动生成新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();

低延迟模式适用于游戏、实时交互应用,会强制使用更小的缓冲区。

六、常见误区与避坑指南

  1. 误区:MediaPlayer能播MP3,AudioTrack也能播MP3
    ❌ AudioTrack只能播放PCM数据。给AudioTrack传MP3文件会发出刺耳噪音。

  2. 误区:MODE_STATIC必须先play再write
    完全错误。STATIC模式必须先write,再play。顺序反了会导致无声或异常。

  3. 误区:write返回写入字节数,必须等于请求数
    ❌ 实际可能小于请求数,尤其是在缓冲区即将写满时。必须检查返回值并处理剩余数据。

  4. 误区:stop()后可以直接play()继续播放
    ❌ stop()会丢弃缓冲区所有残留数据。如需暂停/恢复,应使用pause()play()stop()用于彻底停止。

  5. 误区:不调用release()也没关系
    ❌ AudioTrack持有native层的音频线程和内存,不释放会造成内存泄漏和音频服务资源占用。