【问题标题】:Seperating two audio channels on Android by Stereo recording通过立体声录制在 Android 上分离两个音频通道
【发布时间】:2022-01-22 22:18:00
【问题描述】:

我正在尝试在android上使用AudioRecord录制音频并将左右声道录音分成两个不同的文件,然后将其转换为wav以便能够在手机上播放。但是录制的文件速度很快,而且音高高。

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

这是我的 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 函数。我没有包含其他写入函数以保持帖子简单。

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];
        }

【问题讨论】:

  • 什么是short2byte?为什么要使用尽可能小的缓冲区长度?
  • 输出文件的格式是什么?如果它们是“立体声”,那么您需要为每个样本编写两次(左右声道)
  • 对不起,我刚刚更新了代码并添加了 short2bye 功能。我不确定缓冲区的值应该是多少,所以我使用了基于采样频率的最小可能值。
  • 我不确定您所说的文件格式是什么意思。我只想将两个通道分开,这样我就可以保存两个具有相同数据的文件,每个麦克风一个(因此录制立体声)。

标签: android audio


【解决方案1】:

在 WAV 标题中,您有 2 个通道(立体声)输出格式:

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

如果是这样,那么 byterate 应该是 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

对于输入缓冲区,您需要选择足够大的大小(例如 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时,它返回的是获取数据的实际大小,不超过缓冲区大小(作为参数传递),不超过缓冲区中可用的数据。

【讨论】:

  • 谢谢。你绝对是对的。我应该将输出通道保持为 1,因为每个文件需要一个通道。但现在看来缓冲区很小,因为声音质量不好,速度还是有点高。你能告诉我缓冲区的合适值是多少吗?
  • @NahidM 我已经更新了答案
  • 更改缓冲区提高了质量。但是,似乎在每个通道中填充字节的循环不正确,所以我用正确的循环对其进行了更新。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-26
  • 2017-06-26
  • 2020-03-08
  • 1970-01-01
  • 2018-04-10
  • 2014-12-24
  • 2016-08-01
相关资源
最近更新 更多