Android audiorecord數(shù)據(jù) 6通道中取前四通道

在Android的AudioRecord中,一個(gè)通道的單個(gè)樣本數(shù)據(jù)占用的字節(jié)數(shù),完全取決于你在初始化AudioRecord時(shí)設(shè)置的audioFormat(音頻格式)參數(shù)。

對(duì)于6通道錄音,這個(gè)參數(shù)通常有以下兩種可能:

音頻格式 (audioFormat) 一個(gè)通道的單個(gè)樣本數(shù)據(jù)大小 說(shuō)明
AudioFormat.ENCODING_PCM_16BIT 2 個(gè)字節(jié) 這是最常用、兼容性最好的格式,也是Android官方推薦并保證在所有設(shè)備上都能正常使用的格式。每個(gè)采樣點(diǎn)用16位(2字節(jié))的整數(shù)表示。
AudioFormat.ENCODING_PCM_FLOAT 4 個(gè)字節(jié) 每個(gè)采樣點(diǎn)用32位單精度浮點(diǎn)數(shù)表示,能提供更大的動(dòng)態(tài)范圍,常用于專業(yè)音頻處理。并非所有老舊設(shè)備都支持。
AudioFormat.ENCODING_PCM_8BIT 1 個(gè)字節(jié) 需要注意: 從Android 6.0(API級(jí)別23)開(kāi)始,系統(tǒng)不再支持8位PCM編碼。因此在實(shí)際開(kāi)發(fā)中,基本可以忽略這個(gè)選項(xiàng)。

如何確認(rèn)你的設(shè)置?

你可以直接檢查你初始化AudioRecord時(shí)傳入的audioFormat參數(shù)。

例如,常見(jiàn)的創(chuàng)建方式如下:

// 如果代碼中是這么寫(xiě)的,那么每個(gè)樣本就是 2 個(gè)字節(jié)
AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
                                         44100,
                                         AudioFormat.CHANNEL_IN_STEREO, // 假設(shè)這里是6通道的配置
                                         AudioFormat.ENCODING_PCM_16BIT, // 關(guān)注這一行!
                                         bufferSizeInBytes);

這對(duì)你的數(shù)據(jù)提取有什么影響?

這個(gè)問(wèn)題的答案直接關(guān)系到提取前4通道數(shù)據(jù)的操作。

  • 如果樣本大小是2字節(jié)(16BIT):那么在處理byte[]數(shù)據(jù)時(shí),你需要每?jī)蓚€(gè)字節(jié)一組視為一個(gè)采樣點(diǎn)。在你上一輪我提供的byte[]示例代碼中,正是基于16位立體聲(2字節(jié)/樣本)這個(gè)前提來(lái)移動(dòng)指針和復(fù)制數(shù)據(jù)的(每12個(gè)字節(jié)為一個(gè)6通道的幀,復(fù)制前8個(gè)字節(jié))。
  • 如果樣本大小是4字節(jié)(FLOAT):那么每個(gè)采樣點(diǎn)占4個(gè)字節(jié)。在6通道的交錯(cuò)數(shù)據(jù)中,一個(gè)完整的幀就是 6 * 4 = 24 個(gè)字節(jié)。要提取前4通道,你需要復(fù)制前 4 * 4 = 16 個(gè)字節(jié)。

所以,在進(jìn)行數(shù)據(jù)切片前,確認(rèn)你的audioFormat設(shè)置是關(guān)鍵的第一步。

在Android中處理6通道語(yǔ)音數(shù)據(jù)并提取前4通道,使用System.arraycopy()是最優(yōu)選擇。以下是針對(duì)不同數(shù)據(jù)類(lèi)型的完整實(shí)現(xiàn):

1. 最常見(jiàn)情況:short[] 數(shù)組(16位PCM)

/**
 * 從6通道交錯(cuò)short數(shù)據(jù)中提取前4通道
 * @param input6ch 6通道的short數(shù)組
 * @return 4通道的short數(shù)組
 */
public short[] extractFirst4ChannelsFromShort(short[] input6ch) {
    if (input6ch == null || input6ch.length == 0) {
        return new short[0];
    }
    
    // 6通道每幀6個(gè)short,4通道每幀4個(gè)short
    int frameCount = input6ch.length / 6;
    short[] output4ch = new short[frameCount * 4];
    
    // 逐幀復(fù)制前4個(gè)通道
    for (int i = 0; i < frameCount; i++) {
        // 源數(shù)組位置:i * 6
        // 目標(biāo)數(shù)組位置:i * 4
        // 復(fù)制長(zhǎng)度:4個(gè)元素
        System.arraycopy(input6ch, i * 6, output4ch, i * 4, 4);
    }
    
    return output4ch;
}

// 使用示例
short[] sixChannelData = ...; // 你的6通道數(shù)據(jù)
short[] fourChannelData = extractFirst4ChannelsFromShort(sixChannelData);

2. 原始數(shù)據(jù):byte[] 數(shù)組(16位PCM)

/**
 * 從6通道交錯(cuò)byte數(shù)據(jù)中提取前4通道
 * 注意:16位PCM每個(gè)樣本占2字節(jié)
 * @param inputBytes 6通道的byte數(shù)組
 * @return 4通道的byte數(shù)組
 */
public byte[] extractFirst4ChannelsFromByte(byte[] inputBytes) {
    if (inputBytes == null || inputBytes.length == 0) {
        return new byte[0];
    }
    
    // 6通道:每幀 6個(gè)樣本 * 2字節(jié) = 12字節(jié)
    // 4通道:每幀 4個(gè)樣本 * 2字節(jié) = 8字節(jié)
    int frameCount = inputBytes.length / 12;
    byte[] outputBytes = new byte[frameCount * 8];
    
    // 逐幀復(fù)制前4個(gè)通道(8字節(jié))
    for (int i = 0; i < frameCount; i++) {
        System.arraycopy(inputBytes, i * 12, outputBytes, i * 8, 8);
    }
    
    return outputBytes;
}

3. 專業(yè)音頻:float[] 數(shù)組(32位浮點(diǎn))

/**
 * 從6通道交錯(cuò)float數(shù)據(jù)中提取前4通道
 * @param input6ch 6通道的float數(shù)組
 * @return 4通道的float數(shù)組
 */
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. 實(shí)時(shí)流處理(避免頻繁創(chuàng)建新數(shù)組)

如果你是在實(shí)時(shí)錄音,建議復(fù)用輸出數(shù)組以減少GC:

/**
 * 實(shí)時(shí)處理版本 - 復(fù)用輸出數(shù)組
 * @param input6ch 輸入數(shù)據(jù)(會(huì)被處理)
 * @param output4ch 輸出數(shù)組(必須已經(jīng)分配好足夠空間)
 * @return 實(shí)際處理的幀數(shù)
 */
public int processRealTime(short[] input6ch, short[] output4ch) {
    int frameCount = input6ch.length / 6;
    int requiredOutputSize = frameCount * 4;
    
    // 確保輸出數(shù)組足夠大
    if (output4ch.length < requiredOutputSize) {
        throw new IllegalArgumentException("輸出數(shù)組太小");
    }
    
    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]; // 提前分配好

// 在錄音回調(diào)中
int read = audioRecord.read(buffer6ch, 0, buffer6ch.length);
processRealTime(buffer6ch, buffer4ch);
// 現(xiàn)在 buffer4ch 包含前4通道數(shù)據(jù)

5. 優(yōu)化版本:批量復(fù)制(減少循環(huán)次數(shù))

對(duì)于較大的數(shù)據(jù)塊,可以一次復(fù)制多幀:

/**
 * 批量復(fù)制優(yōu)化版本
 * 一次復(fù)制多幀,減少循環(huán)次數(shù)
 */
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);
        
        // 計(jì)算源和目標(biāo)位置
        int srcPos = framesProcessed * 6;
        int destPos = framesProcessed * 4;
        // 復(fù)制 framesThisBatch * 4 個(gè)元素
        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, // 注意:實(shí)際6通道用CHANNEL_IN_STEREO?這里只是示例
            AudioFormat.ENCODING_PCM_16BIT
        ) * 3; // 調(diào)整為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, // 實(shí)際需要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中的數(shù)據(jù)...
                processFourChannelData(outputBuffer, framesRead * CHANNELS_4);
            }
        }
    }
    
    private void processFourChannelData(short[] data, int length) {
        // 在這里處理你的4通道音頻數(shù)據(jù)
    }
}

性能對(duì)比

// 測(cè)試不同方法的性能
public void performanceTest() {
    short[] input = new short[600000]; // 10萬(wàn)幀6通道數(shù)據(jù)
    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:手動(dòng)循環(huán)(慢很多)
    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通???-4倍
}

總結(jié)

  1. 最簡(jiǎn)潔方式:使用逐幀System.arraycopy()循環(huán)
  2. 數(shù)據(jù)類(lèi)型決定步長(zhǎng)short[]每幀6個(gè)元素,byte[]每幀12字節(jié)
  3. 性能最佳System.arraycopy()比手動(dòng)循環(huán)快
  4. 實(shí)時(shí)處理:復(fù)用輸出數(shù)組避免內(nèi)存分配
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容