浏览器播放录制的PCM音频数据遇编码错误求助
问题:浏览器中PCM音频播放报错的原因与解决方法
背景
我尝试在浏览器中使用JavaScript从麦克风获取原始PCM格式的音频数据,并播放录制好的PCM格式音频数据。为此编写了两个简单的类:
AudioRecorder.startRecording()AudioRecorder.stopRecording():返回录制的音频数据为PCM BufferArrayAudioPlayer.play(pcmAudioDataBufferArray)AudioPlayer.stop()
AudioRecorder类运行正常,但AudioPlayer.play()方法存在问题。
问题代码
AudioPlayer.play() 方法
async play(pcmAudioDataBufferArray) { if (this.playing) { throw new Error("Audio is already playing!"); } if (!pcmAudioDataBufferArray) { throw new Error("No audio data provided!"); } this.playing = true; const audioBuffer = await this.audioContext.decodeAudioData(pcmAudioDataBufferArray); const source = this.audioContext.createBufferSource(); source.buffer = audioBuffer; source.connect(this.audioContext.destination); const endedPromise = new Promise((resolve, reject) => { source.addEventListener("ended", resolve, { once: true }); source.addEventListener('error', reject, { once: true }); }); source.start(); await endedPromise; this.playing = false; }
报错信息
Error: EncodingError: The buffer passed to decodeAudioData contains an unknown content type.
音频转换代码
在Firefox中测试时,浏览器默认以ogg/opus格式录制为Blob,之后通过以下代码将其转换为PCM BufferArray:
async audioBlobToPcmBuffer(blob) { const fileReader = new FileReader(); const arrayBuffer = await new Promise((resolve, reject) => { fileReader.onload = () => resolve(fileReader.result); fileReader.onerror = reject; fileReader.readAsArrayBuffer(blob); }); const audioCtx = new AudioContext({ sampleRate: this.sampleRate }); const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer); const pcmBuffer = audioBuffer.getChannelData(0).buffer; return pcmBuffer; }
错误原因
decodeAudioData方法的作用是解码带有容器格式的音频数据(如OGG、MP3、WAV等),这类数据包含文件头和格式描述信息。但你传入的是原始PCM数据——它没有任何格式标识,浏览器无法识别其编码规则,因此抛出编码错误。
另外,audioBlobToPcmBuffer方法中,audioBuffer.getChannelData(0).buffer返回的是仅包含原始采样值的ArrayBuffer,缺少采样率、声道数、位深这些关键播放参数,直接传给decodeAudioData必然无法解析。
解决方法
播放原始PCM数据不能用decodeAudioData,需要手动创建AudioBuffer并填充数据,再通过BufferSourceNode播放。修改AudioPlayer.play()方法如下:
async play(pcmAudioDataBufferArray) { if (this.playing) { throw new Error("Audio is already playing!"); } if (!pcmAudioDataBufferArray) { throw new Error("No audio data provided!"); } this.playing = true; // 创建AudioBuffer:参数依次为声道数、采样点数、采样率 // 这里假设是单声道、32位浮点型PCM(和getChannelData返回的格式一致) const audioBuffer = this.audioContext.createBuffer( 1, pcmAudioDataBufferArray.byteLength / Float32Array.BYTES_PER_ELEMENT, this.audioContext.sampleRate ); // 将原始PCM数据写入AudioBuffer的声道中 const channelData = audioBuffer.getChannelData(0); channelData.set(new Float32Array(pcmAudioDataBufferArray)); const source = this.audioContext.createBufferSource(); source.buffer = audioBuffer; source.connect(this.audioContext.destination); const endedPromise = new Promise((resolve, reject) => { source.addEventListener("ended", resolve, { once: true }); source.addEventListener('error', reject, { once: true }); }); source.start(); await endedPromise; this.playing = false; }
关键注意点
- 确保
this.audioContext.sampleRate与录制时的采样率完全一致,否则播放速度会出现失真。 - 如果你的PCM数据是其他格式(如16位整型),需要将
Float32Array替换为对应类型(如Int16Array),并在写入AudioBuffer时完成格式转换(比如将整型转成浮点型)。 - 若为多声道录制,需修改
createBuffer的第一个参数为对应声道数,并分别给每个声道写入数据。
内容的提问来源于stack exchange,提问作者Forivin
相关产品推荐
相关产品推荐

