通过立体声录音在 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 获取数据的实际大小,不超过缓冲区大小(作为参数传递)并且不超过缓冲区中可用的数据。
我正在尝试在 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 获取数据的实际大小,不超过缓冲区大小(作为参数传递)并且不超过缓冲区中可用的数据。