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
相关产品推荐
相关产品推荐

