前言

简单记录一下Android系统中AudioFormat类中参数的定义,这个参数主要应用于AudioTrack[播放音频]和AudioRecord[录制音频],只要是介绍一下,偶尔忘记。

记录于此,方便自己查阅。

正文

AudioFormat 主要由三个关键参数组成,它们共同决定了一个音频“帧”(Frame)的大小和含义:

采样率 (Sample Rate)

  • 含义:表示每秒对声音信号进行采样的次数,单位是赫兹 (Hz)。

  • 说明:它描述了每秒每个声道的音频样本数量。

  • 常见值:通常推荐使用 48kHz44.1kHz,以避免系统频繁进行采样率转换,从而提高效率并减少音质损失。

如果设置的采样率与设备硬件不匹配,平台会自动进行转换,但这可能会导致性能开销。

编码格式 (Encoding)

  • 含义:描述音频数据的位表示方式,即每个采样点占用多少比特,以及是整数还是浮点数。

  • 主要类型:

    • 线性 PCM (未压缩)

      • ENCODING_PCM_16BIT:最常用。16位有符号整数,范围 [-32768, 32767]

      • ENCODING_PCM_8BIT:8位无符号整数,范围 [0, 255],精度较低

      • ENCODING_PCM_FLOAT:32位浮点数,范围通常在 [-1.0, 1.0]。适合需要高精度处理的场景(如音频特效处理)

      • ENCODING_PCM_24BIT_PACKED / ENCODING_PCM_32BIT:更高精度的整数格式

    • 压缩音频 (Compressed)

      • ENCODING_AC3, ENCODING_DTS, ENCODING_MP3 等。这些通常用于直接透传给支持解码的硬件(如 HDMI 连接的家庭影院),而不是由手机 CPU 解码

声道掩码 (Channel Mask)

这个分声道位置掩码 (Channel Position Mask)声道索引掩码 (Channel Index Mask)介绍。

声道位置掩码

它定义了扬声器或麦克风的物理位置。

扬声器
  1. CHANNEL_OUT_MONO:单声道

  2. CHANNEL_OUT_STEREO:立体声(左 + 右)

  3. CHANNEL_OUT_5POINT1:5.1 环绕声

    原理:通过位运算组合,例如立体声 = CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT

麦克风
  1. CHANNEL_IN_MONO:单声道录制(保证在所有设备上可用)。

  2. CHANNEL_IN_STEREO :双声道录制

场景:使用手机的麦克风录制一段立体声音乐

AudioFormat positionBasedFormat = new AudioFormat.Builder()
        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
        .setSampleRate(48000)
        // 指定使用“前方左侧”和“前方右侧”的麦克风
        .setChannelMask(AudioFormat.CHANNEL_IN_STEREO) 
        .build();
声道索引掩码

声道索引掩码 (Channel Index Mask),主要用于 USB 音频设备等场景,当你不关心物理位置,只关心“第几个通道”时使用。

它是一个二进制数,从右往左数,第 N 位为 1,就代表选中了第 N 个通道(通道编号从 0 开始)。

  1. 通道 0 对应二进制的第 0 位 (...0001) //第一个通道

  2. 通道 1 对应二进制的第 1 位 (...0010) // 第二个通道

  3. 通道 2 对应二进制的第 2 位 (...0100) // 第三个通道

  4. 通道 3 对应二进制的第 3 位 (...1000) // 第四个通道

案例

假设你有一个4通道的USB麦克风,通道编号为 0, 1, 2, 3。

  1. 案例1:录制第1个和第3个通道的声音

    根据上面知识,需要通道 0 和通道 2,对应的二进制是 0101。转换为十六进制就是 0x5。

    AudioFormat format = new AudioFormat.Builder()
            .setChannelIndexMask(0x5) // 选中第1和第3个通道
            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
            .setSampleRate(48000)
            .build();
  2. 案例2:录制全部4个通道的声音

    根据上面知识,四个通道都需要,对应的二进制是 1111。转换为十六进制就是 0xf。

    AudioFormat format = new AudioFormat.Builder()
            .setChannelIndexMask(0xf) // 选中1、2、3、4个通道
            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
            .setSampleRate(48000)
            .build();

音频帧 (Audio Frame)

Audio Frame 对计算缓冲区大小至关重要:

  • 定义:一个音频帧包含同一时刻所有声道的采样数据集合。

  • 举例

    • 立体声 (2声道) + 16位 PCM (2字节):1个帧 = 2 × 2 = 4字节

    • 单声道 (1声道) + 8位 PCM (1字节):1个帧 = 1 × 1 = 1字节

小结

参数类别常量名说明
编码ENCODING_PCM_16BIT16位整型,兼容性好,音质尚可
ENCODING_PCM_FLOAT32位浮点型,动态范围大,适合音频处理
ENCODING_PCM_8BIT8位整型,音质较差,一般不推荐
声道CHANNEL_OUT_MONO单声道输出
CHANNEL_OUT_STEREO双声道立体声输出(最常用)
CHANNEL_IN_MONO单声道录制(保证在所有设备上可用)
CHANNEL_IN_STEREO双声道录制

使用

使用 AudioFormat.Builder 来创建实例,这样代码更清晰且不易出错。

  1. demo1

    // 1. 使用声道位置掩码来配置 AudioFormat
    AudioFormat positionBasedFormat = new AudioFormat.Builder()
            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
            .setSampleRate(48000)
            // 指定使用“前方左侧”和“前方右侧”的麦克风
            .setChannelMask(AudioFormat.CHANNEL_IN_STEREO) 
            .build();
    
    // 2. 创建 AudioRecord 对象
    int minBufferSize = AudioRecord.getMinBufferSize(
            48000,
            AudioFormat.CHANNEL_IN_STEREO,
            AudioFormat.ENCODING_PCM_16BIT);
    
    AudioRecord audioRecord = new AudioRecord(
            MediaRecorder.AudioSource.DEFAULT,
            48000,
            AudioFormat.CHANNEL_IN_STEREO,
            AudioFormat.ENCODING_PCM_16BIT,
            minBufferSize);
    
    // 3. 开始录音
    audioRecord.startRecording();
    
    // 4. 读取数据
    // 因为是立体声 (2个声道),16位编码 (每个采样点2字节)
    // 所以每一帧音频数据的大小是 2 * 2 = 4 字节
    byte[] buffer = new byte[1024]; 
    int bytesRead = audioRecord.read(buffer, 0, buffer.length);
    
    // 此时,buffer 中的数据就是交替排列的:[左声道样本1, 右声道样本1, 左声道样本2, 右声道样本2, ...]
  2. demo2

    // 1. 使用声道索引掩码来配置 AudioFormat
    AudioFormat indexBasedFormat = new AudioFormat.Builder()
            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
            .setSampleRate(48000)
            // 核心:不设置 setChannelMask,而是设置 setChannelIndexMask
            // 0x5 的二进制是 0101,表示选中第1个(位0)和第3个(位2)通道
            .setChannelIndexMask(0x5) 
            .build();
    
    // 2. 创建 AudioRecord 对象
    // 注意:AudioRecord 的构造函数也需要传入 channelMask 参数,
    // 但当我们使用索引掩码时,这里传入的 channelMask 会被忽略。
    // 为了代码规范,通常还是传入一个与索引掩码通道数匹配的值,比如 CHANNEL_IN_STEREO。
    int minBufferSize = AudioRecord.getMinBufferSize(
            48000,
            AudioFormat.CHANNEL_IN_STEREO, // 因为索引掩码选中了2个通道
            AudioFormat.ENCODING_PCM_16BIT);
    
    AudioRecord audioRecord = new AudioRecord(
            MediaRecorder.AudioSource.DEFAULT,
            48000,
            AudioFormat.CHANNEL_IN_STEREO, 
            AudioFormat.ENCODING_PCM_16BIT,
            minBufferSize);
    
    // 3. 开始录音
    audioRecord.startRecording();
    
    // 4. 读取数据
    byte[] buffer = new byte[1024];
    int bytesRead = audioRecord.read(buffer, 0, buffer.length);
    
    // 此时,buffer 中的数据是:[来自USB设备通道0的样本1, 来自USB设备通道2的样本1, ...]
    // 系统会自动把物理上的通道0和通道2的数据,填充到你逻辑上的第1和第2个声道位置。

参考文章

  1. AI

相关文章

笔友城堡 - 可定义的个人主页

暂无评论

评论审核已启用。您的评论可能需要一段时间后才能被显示。

none
暂无评论...