在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 |
需要注意: 從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é)
-
最簡(jiǎn)潔方式:使用逐幀
System.arraycopy()循環(huán) -
數(shù)據(jù)類(lèi)型決定步長(zhǎng):
short[]每幀6個(gè)元素,byte[]每幀12字節(jié) -
性能最佳:
System.arraycopy()比手動(dòng)循環(huán)快 - 實(shí)時(shí)處理:復(fù)用輸出數(shù)組避免內(nèi)存分配