通过立体声录音在 Android 上分离两个音频通道

Seperating two audio channels on Android by Stereo recording

我正在尝试在 android 上使用 AudioRecord 录制音频,并将左右声道录音分成两个不同的文件,然后将其转换为 wav 以便能够在 phone.But 上播放录制的文件速度快,音调高

我阅读了所有示例并编写了这段代码,但我不确定是哪一部分导致了问题。

这是我的 AudioRecord 定义。

    minBufLength = AudioTrack.getMinBufferSize(48000,AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);

    recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 48000, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT, minBufLength);

然后我读取短数据,然后将短数据转换为字节,最后将它分离成两个通道的字节数组。

 shortData = new short[minBufLength/2];
 int readSize = recorder.read(shortData,0,minBufLength/2);

 byte bData[] = short2byte(shortData);

 for(int i = 0; i < readSize/2; i++)
  {

    final int offset = i * 2 * 2; // two bytes per sample and 2 channels
    rightChannelFos.write(bData, offset , 2);
    leftChannelFos.write(bData, offset + 2 , 2 );
  }

File rightChannelF1 = new File("/sdcard/rightChannelaudio"); // The location of your PCM file
File leftChannelF1 = new File("/sdcard/leftChannelaudio"); // The location of your PCM file
File rightChannelF2 = new File("/sdcard/rightChannelaudio.wav"); // The location where you want your WAV file
File leftChannelF2 = new File("/sdcard/leftChannelaudio.wav"); // The location where you want your WAV file
rawToWave(rightChannelF1, rightChannelF2);
rawToWave(leftChannelF1, leftChannelF2);

// convert short to byte
private byte[] short2byte(short[] sData) {
    int shortArrsize = sData.length;
    byte[] bytes = new byte[shortArrsize * 2];
    for (int i = 0; i < shortArrsize; i++) {
        bytes[i * 2] = (byte) (sData[i] & 0x00FF);
        bytes[(i * 2) + 1] = (byte) (sData[i] >> 8);
        sData[i] = 0;
    }
    return bytes;

}

这是 rawToWave 函数。我没有包含其他写入函数以保持 post 简单。

private void rawToWave(final File rawFile, final File waveFile) throws IOException {

    byte[] rawData = new byte[(int) rawFile.length()];
    DataInputStream input = null;
    try {
        input = new DataInputStream(new FileInputStream(rawFile));
        input.read(rawData);
    } finally {
        if (input != null) {
            input.close();
        }
    }

    DataOutputStream output = null;
    try {
        output = new DataOutputStream(new FileOutputStream(waveFile));
        // WAVE header
        // see http://ccrma.stanford.edu/courses/422/projects/WaveFormat/
        writeString(output, "RIFF"); // chunk id
        writeInt(output, 36 + rawData.length); // chunk size
        writeString(output, "WAVE"); // format
        writeString(output, "fmt "); // subchunk 1 id
        writeInt(output, 16); // subchunk 1 size
        writeShort(output, (short) 1); // audio format (1 = PCM)
        writeShort(output, (short) 2); // number of channels
        writeInt(output, 48000); // sample rate
        writeInt(output, 48000 * 2); // byte rate
        writeShort(output, (short) 2); // block align
        writeShort(output, (short) 16); // bits per sample
        writeString(output, "data"); // subchunk 2 id
        writeInt(output, rawData.length); // subchunk 2 size
        // Audio data (conversion big endian -> little endian)
        short[] shorts = new short[rawData.length / 2];
        ByteBuffer.wrap(rawData).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shorts);
        ByteBuffer bytes = ByteBuffer.allocate(shorts.length * 2);
        for (short s : shorts) {
            bytes.putShort(s);
        }

        output.write(fullyReadFileToBytes(rawFile));
    } finally {
        if (output != null) {
            output.close();
        }
    }
}

更新:

我将此添加为更新,以防其他人遇到此类问题。由于某种我不明白的原因,频道更新循环无法正常工作。所以我分别更新了每个通道的字节数组。现在因为它是一个 16 位方案,那么它意味着每个样本有 2 个字节,所以来自原始数据的样本采用这种格式 [LL][RR][LL][RR] 这就是循环应该基于的原因以下

 for(int i = 0; i < readSize; i= i + 2)
        {
            leftChannelAudioData[i] = bData[2*i];
            leftChannelAudioData[i+1] = bData[2*i+1];

            rightChannelAudioData[i] =  bData[2*i+2];
            rightChannelAudioData[i+1] = bData[2*i+3];
        }

在 WAV-header 中你有 2 个通道(立体声)输出格式:

writeShort(output, (short) 2); // number of channels

如果是这样,则字节率应为 48000 * 4(= 每个通道 2 个字节 * 每个样本 2 个通道) 出于同样的原因,块对齐也应为 4。

此外,您需要将每个样本写入两次,因为您的输出是立体声:每个通道一次。例如:

    rightChannelFos.write(bData, offset , 2);
    rightChannelFos.write(bData, offset , 2);
    leftChannelFos.write(bData, offset + 2 , 2 );
    leftChannelFos.write(bData, offset + 2 , 2 );

但更简单的解决方案是将输出格式更改为单声道(1 声道):

writeShort(output, (short) 1); // number of channels

UPD

对于输入缓冲区,您需要 select 其大小足够大(例如 1 秒),以便在您以小块读取时不会欠载。它会在您处理数据时由系统保持填充 例如:

recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 48000, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT, 48000 * 4); // 1 second long

您可以保持较小的读取缓冲区,但某些预定义的大小较小。 (例如 1024-4096 个样本)。当您调用 recorder.read 时,它 returns 获取数据的实际大小,不超过缓冲区大小(作为参数传递)并且不超过缓冲区中可用的数据。