Android audiorecord数据 6通道中取前四通道

在Android的AudioRecord中,一个通道的单个样本数据占用的字节数,完全取决于你在初始化AudioRecord时设置的audioFormat(音频格式)参数

对于6通道录音,这个参数通常有以下两种可能:

音频格式 (audioFormat) 一个通道的单个样本数据大小 说明
AudioFormat.ENCODING_PCM_16BIT 2 个字节 这是最常用、兼容性最好的格式,也是Android官方推荐并保证在所有设备上都能正常使用的格式。每个采样点用16位(2字节)的整数表示。
AudioFormat.ENCODING_PCM_FLOAT 4 个字节 每个采样点用32位单精度浮点数表示,能提供更大的动态范围,常用于专业音频处理。并非所有老旧设备都支持。
AudioFormat.ENCODING_PCM_8BIT 1 个字节 需要注意: 从Android 6.0(API级别23)开始,系统不再支持8位PCM编码。因此在实际开发中,基本可以忽略这个选项。

如何确认你的设置?

你可以直接检查你初始化AudioRecord时传入的audioFormat参数。

例如,常见的创建方式如下:

// 如果代码中是这么写的,那么每个样本就是 2 个字节
AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
                                         44100,
                                         AudioFormat.CHANNEL_IN_STEREO, // 假设这里是6通道的配置
                                         AudioFormat.ENCODING_PCM_16BIT, // 关注这一行!
                                         bufferSizeInBytes);

这对你的数据提取有什么影响?

这个问题的答案直接关系到提取前4通道数据的操作。

  • 如果样本大小是2字节(16BIT):那么在处理byte[]数据时,你需要每两个字节一组视为一个采样点。在你上一轮我提供的byte[]示例代码中,正是基于16位立体声(2字节/样本)这个前提来移动指针和复制数据的(每12个字节为一个6通道的帧,复制前8个字节)。
  • 如果样本大小是4字节(FLOAT):那么每个采样点占4个字节。在6通道的交错数据中,一个完整的帧就是 6 * 4 = 24 个字节。要提取前4通道,你需要复制前 4 * 4 = 16 个字节。

所以,在进行数据切片前,确认你的audioFormat设置是关键的第一步。

在Android中处理6通道语音数据并提取前4通道,使用System.arraycopy()是最优选择。以下是针对不同数据类型的完整实现:

1. 最常见情况:short[] 数组(16位PCM)

/**
 * 从6通道交错short数据中提取前4通道
 * @param input6ch 6通道的short数组
 * @return 4通道的short数组
 */
public short[] extractFirst4ChannelsFromShort(short[] input6ch) {
    if (input6ch == null || input6ch.length == 0) {
        return new short[0];
    }
    
    // 6通道每帧6个short,4通道每帧4个short
    int frameCount = input6ch.length / 6;
    short[] output4ch = new short[frameCount * 4];
    
    // 逐帧复制前4个通道
    for (int i = 0; i < frameCount; i++) {
        // 源数组位置:i * 6
        // 目标数组位置:i * 4
        // 复制长度:4个元素
        System.arraycopy(input6ch, i * 6, output4ch, i * 4, 4);
    }
    
    return output4ch;
}

// 使用示例
short[] sixChannelData = ...; // 你的6通道数据
short[] fourChannelData = extractFirst4ChannelsFromShort(sixChannelData);

2. 原始数据:byte[] 数组(16位PCM)

/**
 * 从6通道交错byte数据中提取前4通道
 * 注意:16位PCM每个样本占2字节
 * @param inputBytes 6通道的byte数组
 * @return 4通道的byte数组
 */
public byte[] extractFirst4ChannelsFromByte(byte[] inputBytes) {
    if (inputBytes == null || inputBytes.length == 0) {
        return new byte[0];
    }
    
    // 6通道:每帧 6个样本 * 2字节 = 12字节
    // 4通道:每帧 4个样本 * 2字节 = 8字节
    int frameCount = inputBytes.length / 12;
    byte[] outputBytes = new byte[frameCount * 8];
    
    // 逐帧复制前4个通道(8字节)
    for (int i = 0; i < frameCount; i++) {
        System.arraycopy(inputBytes, i * 12, outputBytes, i * 8, 8);
    }
    
    return outputBytes;
}

3. 专业音频:float[] 数组(32位浮点)

/**
 * 从6通道交错float数据中提取前4通道
 * @param input6ch 6通道的float数组
 * @return 4通道的float数组
 */
public float[] extractFirst4ChannelsFromFloat(float[] input6ch) {
    if (input6ch == null || input6ch.length == 0) {
        return new float[0];
    }
    
    int frameCount = input6ch.length / 6;
    float[] output4ch = new float[frameCount * 4];
    
    for (int i = 0; i < frameCount; i++) {
        System.arraycopy(input6ch, i * 6, output4ch, i * 4, 4);
    }
    
    return output4ch;
}

4. 实时流处理(避免频繁创建新数组)

如果你是在实时录音,建议复用输出数组以减少GC:

/**
 * 实时处理版本 - 复用输出数组
 * @param input6ch 输入数据(会被处理)
 * @param output4ch 输出数组(必须已经分配好足够空间)
 * @return 实际处理的帧数
 */
public int processRealTime(short[] input6ch, short[] output4ch) {
    int frameCount = input6ch.length / 6;
    int requiredOutputSize = frameCount * 4;
    
    // 确保输出数组足够大
    if (output4ch.length < requiredOutputSize) {
        throw new IllegalArgumentException("输出数组太小");
    }
    
    for (int i = 0; i < frameCount; i++) {
        System.arraycopy(input6ch, i * 6, output4ch, i * 4, 4);
    }
    
    return frameCount;
}

// 使用示例
short[] buffer6ch = new short[4096]; // 录音buffer
short[] buffer4ch = new short[4096]; // 提前分配好

// 在录音回调中
int read = audioRecord.read(buffer6ch, 0, buffer6ch.length);
processRealTime(buffer6ch, buffer4ch);
// 现在 buffer4ch 包含前4通道数据

5. 优化版本:批量复制(减少循环次数)

对于较大的数据块,可以一次复制多帧:

/**
 * 批量复制优化版本
 * 一次复制多帧,减少循环次数
 */
public short[] extractOptimized(short[] input6ch) {
    int frameCount = input6ch.length / 6;
    short[] output4ch = new short[frameCount * 4];
    
    int batchSize = 100; // 每批处理100帧
    int framesProcessed = 0;
    
    while (framesProcessed < frameCount) {
        int framesThisBatch = Math.min(batchSize, frameCount - framesProcessed);
        
        // 计算源和目标位置
        int srcPos = framesProcessed * 6;
        int destPos = framesProcessed * 4;
        // 复制 framesThisBatch * 4 个元素
        System.arraycopy(input6ch, srcPos, output4ch, destPos, framesThisBatch * 4);
        
        framesProcessed += framesThisBatch;
    }
    
    return output4ch;
}

6. 完整示例:从AudioRecord到4通道输出

public class SixChannelProcessor {
    private static final int SAMPLE_RATE = 44100;
    private static final int CHANNELS_6 = 6;
    private static final int CHANNELS_4 = 4;
    private static final int BYTES_PER_SAMPLE = 2; // 16位PCM
    
    private AudioRecord audioRecord;
    private short[] inputBuffer;
    private short[] outputBuffer;
    private int bufferSize;
    
    public void startRecording() {
        bufferSize = AudioRecord.getMinBufferSize(
            SAMPLE_RATE,
            AudioFormat.CHANNEL_IN_STEREO, // 注意:实际6通道用CHANNEL_IN_STEREO?这里只是示例
            AudioFormat.ENCODING_PCM_16BIT
        ) * 3; // 调整为6通道需要的buffer
        
        // 分配输入输出buffer
        inputBuffer = new short[bufferSize];
        // 输出buffer大小 = (4/6) * 输入大小
        outputBuffer = new short[(bufferSize / CHANNELS_6) * CHANNELS_4];
        
        audioRecord = new AudioRecord(
            MediaRecorder.AudioSource.MIC,
            SAMPLE_RATE,
            AudioFormat.CHANNEL_IN_STEREO, // 实际需要6通道配置
            AudioFormat.ENCODING_PCM_16BIT,
            bufferSize * BYTES_PER_SAMPLE
        );
        
        audioRecord.startRecording();
        
        new Thread(this::processAudio).start();
    }
    
    private void processAudio() {
        while (isRecording) {
            int read = audioRecord.read(inputBuffer, 0, inputBuffer.length);
            
            if (read > 0) {
                int framesRead = read / CHANNELS_6;
                
                // 使用arraycopy提取前4通道
                for (int i = 0; i < framesRead; i++) {
                    System.arraycopy(
                        inputBuffer, i * CHANNELS_6,
                        outputBuffer, i * CHANNELS_4,
                        CHANNELS_4
                    );
                }
                
                // 处理outputBuffer中的数据...
                processFourChannelData(outputBuffer, framesRead * CHANNELS_4);
            }
        }
    }
    
    private void processFourChannelData(short[] data, int length) {
        // 在这里处理你的4通道音频数据
    }
}

性能对比

// 测试不同方法的性能
public void performanceTest() {
    short[] input = new short[600000]; // 10万帧6通道数据
    short[] output = new short[400000];
    
    long startTime = System.nanoTime();
    
    // 方法1:System.arraycopy(推荐)
    for (int i = 0; i < input.length / 6; i++) {
        System.arraycopy(input, i * 6, output, i * 4, 4);
    }
    
    long arrayCopyTime = System.nanoTime() - startTime;
    
    // 方法2:手动循环(慢很多)
    startTime = System.nanoTime();
    for (int i = 0, j = 0; i < input.length; i += 6, j += 4) {
        output[j] = input[i];
        output[j + 1] = input[i + 1];
        output[j + 2] = input[i + 2];
        output[j + 3] = input[i + 3];
    }
    
    long manualTime = System.nanoTime() - startTime;
    
    Log.d("Performance", "arraycopy: " + arrayCopyTime + " ns");
    Log.d("Performance", "manual: " + manualTime + " ns");
    // arraycopy通常快2-4倍
}

总结

  1. 最简洁方式:使用逐帧System.arraycopy()循环
  2. 数据类型决定步长short[]每帧6个元素,byte[]每帧12字节
  3. 性能最佳System.arraycopy()比手动循环快
  4. 实时处理:复用输出数组避免内存分配
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容