4、音频采集与播放:AudioRecord与AudioTrack的使用、音频数据格式转换、PCM与WAV文件读写、音频焦点处理

在Android音视频开发体系中,音频的采集与播放是构建一切上层应用(如录音机、语音通话、音频编辑器、播放器)的基石。本章将深入剖析Android平台提供的两大原生音频API——AudioRecord(采集)与AudioTrack(播放),并围绕PCM原始音频数据的处理、WAV文件封装格式的读写,以及多应用场景下至关重要的音频焦点管理展开系统讲解。理解这些底层机制,不仅能帮助你摆脱对第三方库的依赖,更能为后续处理音频编解码、实时特效处理等高级主题打下坚实基础。

Android音频采集与播放核心链路 音频输入源 麦克风 / 音频录制器 AudioRecord 音频输出设备 扬声器 / 耳机 AudioTrack 数据格式转换 PCM 16bit ↔ 8bit 单声道 ↔ 双声道 文件读写 PCM 裸数据 WAV 封装格式 音频焦点管理 AudioFocusRequest 焦点请求与释放 PCM流 存储/读取 PCM流 控制 核心参数配置 采样率: 44100/48000Hz 声道: 单/双声道 位深: 8bit/16bit 编码: PCM 图:AudioRecord采集 → 数据转换/文件读写 → AudioTrack播放,全程受音频焦点调控

上图展示了本章的核心知识框架:音频采集(AudioRecord)与播放(AudioTrack)作为两端,中间经过数据格式转换、PCM/WAV文件读写,并由音频焦点机制进行全局协调。下面我们将逐一拆解每个环节的原理、操作流程与实战要点。

4.1 AudioRecord与AudioTrack核心原理

4.1.1 AudioRecord采集机制

AudioRecord是Android提供的低延迟音频采集类,它直接从音频输入设备(如麦克风)读取PCM(脉冲编码调制)数据。其工作模式为生产者-消费者模型:底层音频硬件持续产生数据写入内核缓冲区,应用层通过循环调用read()方法将数据取出。

初始化AudioRecord需要配置一组关键参数,这些参数直接决定采集数据的质量与格式:

参数典型值说明
audioSourceMediaRecorder.AudioSource.MIC音频源,可选麦克风、摄像头麦克风等
sampleRateInHz44100 / 48000采样率,CD音质为44100Hz,电话音质为8000Hz
channelConfigCHANNEL_IN_MONO / STEREO声道配置,单声道或双声道
audioFormatENCODING_PCM_16BIT / 8BIT位深,16bit为最常见格式
bufferSizeInBytesgetMinBufferSize()返回值缓冲区大小,必须不小于系统最小值

核心要点:缓冲区大小并非越大越好。过大的缓冲区会引入明显延迟(适合后台录音),过小则可能导致数据丢失(underrun)。建议使用AudioRecord.getMinBufferSize()获取系统推荐值,再根据业务场景乘以系数(如2倍)以平衡延迟与稳定性。

4.1.2 AudioTrack播放机制

AudioTrack用于将PCM数据写入音频输出设备。与AudioRecord对称,它采用消费者-生产者模型:应用层通过write()方法将数据送入缓冲区,底层音频硬件按固定节奏消费数据并播放。

AudioTrack支持两种播放模式:

  • 静态模式(MODE_STATIC):一次性写入全部数据,适合播放短音频(如按键音、提示音),内存占用小。
  • 流式模式(MODE_STREAM):持续写入数据流,适合播放长音频或实时音频流(如音乐播放、语音通话),需要配合循环写入。

在实际开发中,流式模式更为常用,因为它能处理任意长度的音频数据,且内存占用可控。

4.2 音频数据格式转换实战

不同场景对音频格式要求各异:语音识别可能需要16kHz单声道16bit,而音频编辑可能需要48kHz立体声。因此,掌握PCM数据格式转换是必备技能。

4.2.1 位深转换(16bit ↔ 8bit)

16bit PCM数据范围为-32768~32767,8bit PCM数据范围为-128~127。转换时需注意符号处理:

// 16bit转8bit(取高8位)
byte[] pcm16To8(short[] pcm16) {
    byte[] pcm8 = new byte[pcm16.length];
    for (int i = 0; i < pcm16.length; i++) {
        pcm8[i] = (byte) (pcm16[i] >> 8);
    }
    return pcm8;
}

// 8bit转16bit(左移8位)
short[] pcm8To16(byte[] pcm8) {
    short[] pcm16 = new short[pcm8.length];
    for (int i = 0; i < pcm8.length; i++) {
        pcm16[i] = (short) (pcm8[i] << 8);
    }
    return pcm16;
}

4.2.2 声道转换(单声道 ↔ 双声道)

单声道转双声道时,通常将同一帧数据复制到左右声道;双声道转单声道时,可取左右声道平均值(或直接取左声道)。注意:16bit双声道交错存储,即数据排列为L0, R0, L1, R1, ...

// 单声道16bit转双声道16bit
short[] monoToStereo(short[] mono) {
    short[] stereo = new short[mono.length * 2];
    for (int i = 0; i < mono.length; i++) {
        stereo[2 * i] = mono[i];     // 左声道
        stereo[2 * i + 1] = mono[i]; // 右声道
    }
    return stereo;
}

经验提醒:进行格式转换时,务必明确原始数据的字节序(Android原生为小端序)。在跨平台传输或文件存储时,需统一字节序规范,否则会导致音频数据错乱。

4.3 PCM与WAV文件读写

PCM是纯音频数据,不含任何头部信息;WAV(RIFF格式)则在PCM数据前添加44字节的文件头,用于描述采样率、位深、声道数等元数据。WAV文件结构如下:

偏移地址字段名大小(字节)说明
0ChunkID4"RIFF"
4ChunkSize4文件总长度-8
8Format4"WAVE"
12Subchunk1ID4"fmt "
16Subchunk1Size416(PCM格式)
20AudioFormat21(PCM)
22NumChannels21或2
24SampleRate4采样率
28ByteRate4SampleRate × NumChannels × BitsPerSample/8
32BlockAlign2NumChannels × BitsPerSample/8
34BitsPerSample216或8
36Subchunk2ID4"data"
40Subchunk2Size4PCM数据长度
44DataNPCM数据

写入WAV文件时,需先写入44字节头部,再追加PCM数据;读取时则先解析头部获取参数,再读取数据区。注意:ChunkSize和Subchunk2Size需在数据写入完成后回填,因此通常采用先写占位符、最后修正的策略。

4.4 音频焦点处理

音频焦点(Audio Focus)是Android多应用音频协调机制,确保多个应用不会同时发声造成混乱。例如,当音乐播放器正在播放时,导航应用播报语音应短暂降低音乐音量(duck),播报结束后恢复。

音频焦点处理流程如下:

  1. 请求焦点:构建AudioFocusRequest对象,指定焦点类型(如AudioManager.AUDIOFOCUS_GAIN用于长时间播放,AUDIOFOCUS_GAIN_TRANSIENT用于短暂提示音)。
  2. 注册监听:通过AudioManager.requestAudioFocus()发起请求,并传入AudioFocusChangeListener回调。
  3. 处理焦点变化:在回调中根据焦点变化类型(丢失、暂时丢失、可duck)执行相应操作。
  4. 放弃焦点:播放结束后调用abandonAudioFocusRequest()释放焦点。
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
AudioFocusRequest focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
        .setAudioAttributes(new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build())
        .setOnAudioFocusChangeListener(focusChange -> {
            switch (focusChange) {
                case AudioManager.AUDIOFOCUS_LOSS:
                    // 长时间丢失焦点,应停止播放
                    stopPlayback();
                    break;
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                    // 暂时丢失,暂停播放
                    pausePlayback();
                    break;
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                    // 可降低音量继续播放
                    setVolume(0.3f);
                    break;
                case AudioManager.AUDIOFOCUS_GAIN:
                    // 重新获得焦点,恢复播放
                    resumePlayback();
                    break;
            }
        })
        .build();
int result = audioManager.requestAudioFocus(focusRequest);

风险提示:Android 8.0(API 26)之后,必须使用AudioFocusRequest替代已废弃的requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int)方法。同时,未正确处理焦点丢失可能导致应用在后台继续播放,被系统判定为不良行为。

4.5 案例拆解:录音机应用实现

下面通过一个完整的录音机案例,串联本章所有知识点。该案例实现录音、暂停、播放、保存WAV文件的功能。

场景与约束:应用需在后台录音时不被系统杀死,且播放时能与其他音乐应用协调。目标:实现一个功能完整、内存占用可控的录音机。

关键实现步骤:

  1. 配置AudioRecord:采样率44100Hz,单声道,16bit,缓冲区设为最小值的2倍。
  2. 录音线程:循环调用read()将数据写入ByteArrayOutputStream,同时实时写入PCM临时文件。
  3. 暂停/恢复:通过标志位控制循环,暂停时停止读取但保留AudioRecord实例。
  4. 停止并保存:停止录音后,将PCM数据加上WAV头写入最终文件。
  5. 播放:使用AudioTrack流式模式播放WAV文件,播放前请求音频焦点。

结果与复盘:实测在Pixel 6设备上,录音延迟约50ms,播放无卡顿。关键经验:

  • 录音线程需设置为Process.THREAD_PRIORITY_URGENT_AUDIO优先级,减少调度延迟。
  • WAV文件写入时,先写头部占位,数据写完后用RandomAccessFile回填长度字段。
  • 播放时若焦点丢失,立即暂停并保存播放位置,焦点恢复后从断点续播。

4.6 常见误区与总结

在实际开发中,以下问题高频出现,需要特别留意:

误区后果正确做法
缓冲区设置过小音频数据丢失,出现爆音使用getMinBufferSize()并适当放大
忽略音频焦点与其他应用声音冲突严格实现焦点请求与释放
WAV头部长度不更新文件损坏,播放器无法识别写完数据后回填ChunkSize
主线程操作AudioRecordANR或卡顿使用子线程处理音频数据
未释放资源内存泄漏,麦克风被占用在onDestroy中release()