如何通过Tone.js获取变调后扬声器输出音频的PCM数据?
问题背景
我需要把WAV文件对应的字节数组转换成升调后的新字节数组,但自身缺乏实现变调算法的数学能力。用Tone.js能快速实现变调音频的播放,但无法获取到处理后的实际音频数据。
我了解到Tone.js基于Web Audio API,常规Web Audio API中可以通过AudioBufferSourceNode.buffer.getChannelData()获取原始PCM数据,但查看Tone.js源码里ToneBufferSource.ts的start()方法时,发现打印出的_source的buffer仍是未变调的原始数据;进一步查看它依赖的standardized-audio-context库中的nativeAudioBufferSourceNode,其buffer数据也没有变化——明明输出的音频已经是变调后的效果,但源buffer始终保持原始状态。
现在想请教:有没有办法获取到扬声器输出的变调音频对应的PCM数据?
解决方案
核心思路是用OfflineAudioContext替代实时AudioContext,在离线环境中完整渲染音频处理链路,直接获取处理后的音频数据,具体步骤如下:
1. 将WAV字节数组解码为AudioBuffer
async function decodeWavByteArray(wavBytes) { const baseAudioCtx = new (window.AudioContext || window.webkitAudioContext)(); return baseAudioCtx.decodeAudioData(wavBytes); }
2. 用OfflineAudioContext渲染变调后的音频
以Tone.js为例构建处理链路,离线渲染后提取PCM数据:
async function getPitchShiftedPCMData(originalBuffer, pitchShiftValue) { // 创建与原始音频参数匹配的离线上下文 const offlineCtx = new OfflineAudioContext( originalBuffer.numberOfChannels, originalBuffer.length, originalBuffer.sampleRate ); // 将Tone.js绑定到离线上下文 await Tone.start(); Tone.setContext(offlineCtx); // 构建变调处理链路 const audioPlayer = new Tone.Player(originalBuffer); const pitchShifter = new Tone.PitchShift(pitchShiftValue); audioPlayer.connect(pitchShifter); pitchShifter.connect(Tone.getDestination()); // 启动离线渲染 audioPlayer.start(); const processedBuffer = await offlineCtx.startRendering(); // 提取多声道PCM数据 const pcmArrays = []; for (let channelIdx = 0; channelIdx < processedBuffer.numberOfChannels; channelIdx++) { pcmArrays.push(processedBuffer.getChannelData(channelIdx)); } return pcmArrays; }
3. (可选)将PCM数据转换为WAV字节数组
如果需要输出WAV格式的字节数组,可将提取到的Float32Array格式PCM数据转换为Int16Array(WAV标准格式),再手动添加WAV文件头即可完成封装。
原理说明
实时播放时,Web Audio API的处理节点是将音频数据流式传输到扬声器,不会修改原始的AudioBuffer;而OfflineAudioContext会模拟整个音频处理流程,将最终处理后的完整音频数据输出到一个新的AudioBuffer中,通过这个AudioBuffer就能直接获取到变调后的PCM数据。
内容的提问来源于stack exchange,提问作者Holy Moeller

