You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:25:38