Android 32位PCM WAV录音异常求助(API 23+)
32位浮点PCM录制噪音问题的排查与修复
嘿,我帮你梳理了代码里的几个核心问题,这就是你用ENCODING_PCM_FLOAT录制后生成噪音的原因,下面逐个拆解并给出修复方案:
1. AudioRecord初始化逻辑完全写反了
你这段判断代码简直是“反向操作”:
if (mRecorder.getState() == AudioRecord.STATE_INITIALIZED && mBufferSize != AudioRecord.ERROR_BAD_VALUE) { return; }
意思是只要AudioRecord初始化成功,就直接返回,根本不会启动录制线程!这会导致temp文件里全是空数据或者垃圾数据,播放自然是噪音。正确的逻辑应该是:如果初始化失败或者缓冲区参数错误,才终止流程:
if (mRecorder.getState() != AudioRecord.STATE_INITIALIZED || mBufferSize == AudioRecord.ERROR_BAD_VALUE) { Log.e("Recorder", "AudioRecord初始化失败,无法开始录制"); return; }
2. WAV头格式标识错误(最关键的问题)
标准WAV文件中,16位线性PCM对应的format字段是1,但32位浮点PCM对应的format值是3(IEEE_FLOAT)。你当前代码里header[20] = 1;,会让播放器把浮点数据当成16位整数PCM解析,这完全是两种不同的数据格式,必然产生刺耳的噪音。
把这里改成3就解决了播放器解析错误的问题:
header[20] = 3; // 替换原来的1,表示这是IEEE浮点格式的PCM
3. 写入PCM数据时未使用实际读取的长度
你现在每次读取到readSize个float,但写入时却把整个audioData数组转成字节写入,这会把数组中未被填充的默认0值也写进去,导致音频出现卡顿、静音段或者额外噪音。应该只写入实际读取到的float对应的字节:
int readSize = mRecorder.read(audioData, 0, audioData.length, AudioRecord.READ_BLOCKING); if (readSize == AudioRecord.ERROR_INVALID_OPERATION || readSize == AudioRecord.ERROR_BAD_VALUE) { break; } // 只转换并写入实际读取到的readSize个float,而不是整个数组 byte[] bytes = new byte[readSize * 4]; ByteBuffer.wrap(bytes).order(ByteOrder.nativeOrder()).asFloatBuffer().put(audioData, 0, readSize); try { outputStream.write(bytes); } catch (IOException e) { Log.e("Recorder", e.getMessage(), e); }
4. CopyAudioTask中读取文件的小瑕疵
当前代码里while (in.read(data) != -1) { out.write(data); },如果最后一次读取的字节数小于mBufferSize,会写入多余的字节(数组里残留的旧数据),应该改成只写入实际读取的长度:
int len; while ((len = in.read(data)) != -1) { out.write(data, 0, len); }
修复后的完整关键代码片段
修正后的startRecording方法
@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) { Log.e("Recorder", "AudioRecord初始化失败,无法开始录制"); 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; } // 只转换实际读取的float数据 byte[] bytes = new byte[readSize * 4]; ByteBuffer.wrap(bytes).order(ByteOrder.nativeOrder()).asFloatBuffer().put(audioData, 0, readSize); 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(); }
修正后的writeWaveFileHeader方法
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] = 3; // 关键:改为3表示IEEE_FLOAT格式 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); }
修正后的CopyAudioTask的doInBackground方法
@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); int len; // 写入实际读取的字节长度,避免多余数据 while ((len = in.read(data)) != -1) { out.write(data, 0, len); } in.close(); out.close(); } catch (Exception e) { Log.e("Recorder", e.getMessage(), e); } return null; }
把这些修复点都改完后,你在API23+设备上录制的32位浮点WAV应该就能正常播放了,不会再有噪音问题。
内容的提问来源于stack exchange,提问作者jalal
相关产品推荐
相关产品推荐

