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

Android中使用AudioRecord录制的音频经AudioTrack播放存在杂音

Android AudioTrack播放带手动添加WAV头的音频出现杂音,Web端正常

我使用AudioRecord录制语音并发送至服务器,在Android端通过AudioTrack播放时出现“tttttt”杂音,但Web端播放无此问题,推测Web API可能对杂音做了处理。

录制并发送音频的代码

recorder = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSize);
int audioSession = recorder.getAudioSessionId();
NoiseSuppressor.create(audioSession);
AutomaticGainControl.create(audioSession);
AcousticEchoCanceler.create(audioSession);
...
try {
    int bytesRead;
    int count = 0;
    byte[] buffer = new byte[bufferSize];

    while (isRecording) {
        bytesRead = recorder.read(buffer, 0, buffer.length);

        // skip first 2 buffers to eliminate "click sound"
        if (bytesRead > 0 && ++count > 2) {

            // I'm manually adding wav header to make it playable on web
            byte[] combined = new byte[wavHeaders.length + buffer.length];
            System.arraycopy(wavHeaders, 0, combined, 0, wavHeaders.length);
            System.arraycopy(buffer, 0, combined, wavHeaders.length, buffer.length);

            socket.emit("voice", combined);
        }
    }
    recorder.stop();
} catch (Exception e) {
    // e.printStackTrace();
}

播放音频的代码

player = new AudioTrack.Builder()
        .setAudioAttributes(new AudioAttributes.Builder()
                // .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                .build())
        .setAudioFormat(new AudioFormat.Builder()
                .setEncoding(audioFormat)
                .setSampleRate(sampleRateInHz)
                .setChannelMask(outputChannelConfig)
                .build())
        .setBufferSizeInBytes(bufferSize)
        .build();
...
player.play();
...
public void addPlay(byte[] audioBuffer) {
    player.write(audioBuffer, 0, audioBuffer.length, 0);
}
...
try {
    addPlay((byte[]) args[0]);
} catch (Exception e) {
    // TODO: handle exception
}

更新:我发现移除手动生成的WAV头后,杂音消失,但Web端需要该头才能播放音频,特此求助。


问题原因与解决方案

核心原因

AudioTrack的作用是直接播放原始PCM音频数据,而你传给它的byte[]包含了WAV头——这部分是文件格式描述信息,不是有效的音频采样数据,播放时自然会产生杂音。Web端的音频API(如HTML5 Audio)能识别WAV格式,会自动解析并跳过头部,只播放后面的PCM内容,所以没有杂音。

可行解决方案

方案1:Android端本地剥离WAV头

在播放前先从收到的音频数据中剔除WAV头,只把纯PCM数据传给AudioTrack:

public void addPlay(byte[] audioBuffer) {
    // 替换为你实际生成的wavHeaders的长度(标准WAV头通常是44字节)
    int headerLength = wavHeaders.length;
    if (audioBuffer.length > headerLength) {
        byte[] purePcmData = new byte[audioBuffer.length - headerLength];
        System.arraycopy(audioBuffer, headerLength, purePcmData, 0, purePcmData.length);
        player.write(purePcmData, 0, purePcmData.length, 0);
    }
}

方案2:服务器端拆分数据

服务器收到带WAV头的音频后,分别处理:

  • 剥离WAV头,将纯PCM数据转发给Android客户端;
  • 保留带WAV头的数据,供Web端请求播放。
    这种方式让两端各自拿到适配的数据,无需客户端额外处理。

方案3:统一传输纯PCM,两端按需生成WAV头

  • 录制时只发送纯PCM数据,不添加WAV头,减少传输冗余;
  • Web端收到PCM后,在前端动态生成WAV头并与PCM合并,通过Blob构造可播放的WAV音频:
    // 示例:前端生成WAV头(需匹配采样率、通道数、位深度等参数)
    function createWavBlob(pcmData, sampleRate, channels, bitDepth) {
        const buffer = new ArrayBuffer(44 + pcmData.length);
        const view = new DataView(buffer);
        
        // 写入WAV头信息
        view.setUint8(0, 0x52); view.setUint8(1, 0x49); view.setUint8(2, 0x46); view.setUint8(3, 0x46); // RIFF
        view.setUint32(4, 36 + pcmData.length, true);
        view.setUint8(8, 0x57); view.setUint8(9, 0x41); view.setUint8(10, 0x56); view.setUint8(11, 0x45); // WAVE
        view.setUint8(12, 0x66); view.setUint8(13, 0x6D); view.setUint8(14, 0x74); view.setUint8(15, 0x20); // fmt 
        view.setUint32(16, 16, true); // PCM格式
        view.setUint16(20, 1, true); // 音频格式(PCM=1)
        view.setUint16(22, channels, true);
        view.setUint32(24, sampleRate, true);
        view.setUint32(28, sampleRate * channels * bitDepth / 8, true); // 字节率
        view.setUint16(32, channels * bitDepth / 8, true); // 块对齐
        view.setUint16(34, bitDepth, true);
        view.setUint8(36, 0x64); view.setUint8(37, 0x61); view.setUint8(38, 0x74); view.setUint8(39, 0x61); // data
        view.setUint32(40, pcmData.length, true);
        
        // 写入PCM数据
        const pcmView = new Uint8Array(buffer, 44);
        pcmView.set(pcmData);
        
        return new Blob([buffer], { type: 'audio/wav' });
    }
    

内容的提问来源于stack exchange,提问作者Amir

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 13:35:18