我一直在尝试使用32位深度的AudioRecord在Android上录制人声并将其写入wav文件。我知道32位精度的录制仅适用于使用AudioFormat.ENCODING_PCM_FLOAT
的23级或更高级别的API(根据文档说明)。我在API级别为23的设备上进行了一些测试,但仍然由于某种原因,结果音频已损坏(我只能听到完全的噪音)。这是我的代码的样子:
private AudioRecord mRecorder;
private int mBufferSize;
private Thread mRecordingThread;
private boolean mIsRecording;
private String tempPath = "/some/path/tempFile.wav";
private String outputPath = "/some/path/recording.wav";
@TargetApi(23)
public void startRecording() {
mBufferSize = AudioRecord.getMinBufferSize(16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_FLOAT);
mRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 16000, AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_FLOAT, mBufferSize);
if (mRecorder.getState() == AudioRecord.STATE_INITIALIZED
&& mBufferSize != AudioRecord.ERROR_BAD_VALUE) {
return;
}
mRecordingThread = new Thread(new Runnable() {
@Override
public void run() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
try {
FileOutputStream outputStream = new FileOutputStream(tempPath);
float[] audioData = new float[mBufferSize / 4];
mRecorder.startRecording();
mIsRecording = true;
while (mIsRecording) {
int readSize = mRecorder.read(audioData, 0, audioData.length, AudioRecord.READ_BLOCKING);
if (readSize == AudioRecord.ERROR_INVALID_OPERATION || readSize == AudioRecord.ERROR_BAD_VALUE) {
break;
}
// convert float to byte
byte[] bytes = new byte[audioData.length * 4];
ByteBuffer.wrap(bytes).order(ByteOrder.nativeOrder()).asFloatBuffer().put(audioData);
try {
outputStream.write(bytes);
} catch (IOException e) {
Log.e("Recorder", e.getMessage(), e);
}
}
outputStream.close();
} catch (IOException e) {
Log.e("Recorder", e.getMessage(), e);
} catch (Exception e) {
Log.e("Recorder", e.getMessage(), e);
}
}
});
mRecordingThread.start();
}
public void stopRecording() {
if (mIsRecording) {
mIsRecording = false;
if (mRecordingThread != null && mRecordingThread.getState() != Thread.State.NEW
&& mRecordingThread.getState() != Thread.State.TERMINATED) {
try {
mRecordingThread.join();
} catch (InterruptedException e) {
// ...
}
}
if (mRecorder.getState() == AudioRecord.RECORDSTATE_RECORDING) {
mRecorder.stop();
}
mRecordingThread = null;
generateFinalAudio();
}
}
public void generateFinalAudio() {
CopyAudioTask task = new CopyAudioTask();
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, tempPath, outputPath);
}
private class CopyAudioTask extends AsyncTask<String, String, Void> {
@Override
protected Void doInBackground(String... params) {
String tmpPath = params[0];
String outPath = params[1];
try {
FileInputStream in = new FileInputStream(tmpPath);
FileOutputStream out = new FileOutputStream(outPath);
byte[] data = new byte[mBufferSize];
writeWaveFileHeader(in, out);
while (in.read(data) != -1) {
out.write(data);
}
in.close();
out.close();
} catch (Exception e) {
Log.e("Recorder", e.getMessage(), e);
}
return null;
}
}
private void writeWaveFileHeader(FileInputStream in, FileOutputStream out) throws IOException {
int bitWidth = 32;
long longSampleRate = mRecorder.getSampleRate();
int channels = mRecorder.getChannelCount();
long totalAudioLen = in.getChannel().size();
long totalDataLen = totalAudioLen + 36;
long byteRate = bitWidth * longSampleRate * channels / 8;
byte[] header = new byte[44];
header[0] = 'R'; // RIFF/WAVE header
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
header[12] = 'f'; // 'fmt ' chunk
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
header[16] = 16; // 4 bytes: size of 'fmt ' chunk
header[17] = 0;
header[18] = 0;
header[19] = 0;
header[20] = 1; // format = 1
header[21] = 0;
header[22] = (byte) channels;
header[23] = 0;
header[24] = (byte) (longSampleRate & 0xff);
header[25] = (byte) ((longSampleRate >> 8) & 0xff);
header[26] = (byte) ((longSampleRate >> 16) & 0xff);
header[27] = (byte) ((longSampleRate >> 24) & 0xff);
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
header[32] = (byte) (channels * bitWidth / 8); // block align
header[33] = 0;
header[34] = (byte) bitWidth; // bits per sample
header[35] = 0;
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
}
有谁能弄明白我在做错了什么?我尝试过使用16位(ENCODING_PCM_16BIT
,并使用short[]
作为数据缓冲区)并且它运行良好。
任何帮助都会非常感激。谢谢!
答案 0 :(得分:2)
好的,我现在已经明白了。问题在于标题。对于32位浮点PCM数据,header[20]
的值应为3
或0x0003
,如page中所述。
答案 1 :(得分:0)
对于将问题中的代码用作模板的其他人(即 Ctrl+C Ctrl+V),请不要忘记将这里第一行中的 == 更改为 !=。好像是复制粘贴错误什么的。无论如何,更改此行后它将开始工作。
if (mRecorder.getState() == AudioRecord.STATE_INITIALIZED
&& mBufferSize != AudioRecord.ERROR_BAD_VALUE) {
return;
}