在Android的AudioRecord中,一个通道的单个样本数据占用的字节数,完全取决于你在初始化AudioRecord时设置的audioFormat(音频格式)参数。
对于6通道录音,这个参数通常有以下两种可能:
音频格式 (audioFormat) |
一个通道的单个样本数据大小 | 说明 |
|---|---|---|
AudioFormat.ENCODING_PCM_16BIT |
2 个字节 | 这是最常用、兼容性最好的格式,也是Android官方推荐并保证在所有设备上都能正常使用的格式。每个采样点用16位(2字节)的整数表示。 |
AudioFormat.ENCODING_PCM_FLOAT |
4 个字节 | 每个采样点用32位单精度浮点数表示,能提供更大的动态范围,常用于专业音频处理。并非所有老旧设备都支持。 |
AudioFormat.ENCODING_PCM_8BIT |
需要注意: 从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倍
}
总结
-
最简洁方式:使用逐帧
System.arraycopy()循环 -
数据类型决定步长:
short[]每帧6个元素,byte[]每帧12字节 -
性能最佳:
System.arraycopy()比手动循环快 - 实时处理:复用输出数组避免内存分配