991 字
5 分钟
在浏览器中实时获取声音频率
前言
当你在给乐器调音时、又或者在小提琴、二胡这样的无品格乐器上演奏但无法确定音准时,你可以借助电脑或者移动设备的麦克风采集当前演奏乐器的声音,并通过算法实时分析声音的基音音高以及响度,这可以更好地帮助我们把握乐器的音准。
开始
在浏览器获取麦克风权限
在这里我们使用 Web Api 中的 getUserMedia 方法来获取设备麦克风。
async function getMicroPhoneDevice() { if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { console.log("浏览器不支持音频输入"); return; }
try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const audioContext = new window.AudioContext(); const mediaStreamSource = audioContext.createMediaStreamSource(stream);
const processor = audioContext.createScriptProcessor(4096, 1, 1); mediaStreamSource.connect(processor); processor.connect(audioContext.destination); processor.onaudioprocess = (e) => { let inputBuffer = e.inputBuffer.getChannelData(0); // 读取第一个声道的数据 console.log(inputBuffer); }; } catch (error) { console.error("音频解析失败,请检查麦克风或重新尝试!", error); }}这里的 inputBuffer 就是麦克风实时获取的音频数据,格式为 Buffer 数组。
处理获取的音频数据
现在我们得到了原始的音频数据,如果想要知道这段音频的频率,我们还需要使用自相关算法得出音频的基音以及周期。
1.首先使用 RMS 计算信号强度
let rms = 0;let SIZE = inputBuffer.length;for (let i = 0; i < SIZE; i++) { let val = inputBuffer[i]; rms += val * val;}rms = Math.sqrt(rms / SIZE);if (rms < 0.01) { console.log("信号强度不足");}2.筛除噪声
let startIndex = 0, endIndex = SIZE - 1, threshold = 0.2;
for (let i = 0; i < SIZE / 2; i++) { if (Math.abs(inputBuffer[i]) < threshold) { startIndex = i; break; }}for (let i = 1; i < SIZE / 2; i++) { if (Math.abs(inputBuffer[SIZE - i]) < threshold) { endIndex = SIZE - i; break; }}
inputBuffer = inputBuffer.slice(startIndex, endIndex);SIZE = inputBuffer.length;3.自相关计算(Autocorrelation Function, ACF)
let autocorrelation = new Array(SIZE).fill(0);
for (let i = 0; i < SIZE; i++) { for (let j = 0; j < SIZE - i; j++) { autocorrelation[i] += inputBuffer[j] * inputBuffer[j + i]; }}4.峰值检测
let peakIndex = 0;while (autocorrelation[peakIndex] > autocorrelation[peakIndex + 1]) peakIndex++;
let maxCorrelationValue = -1, maxCorrelationPosition = -1;for (let i = peakIndex; i < SIZE; i++) { if (autocorrelation[i] > maxCorrelationValue) { maxCorrelationValue = autocorrelation[i]; maxCorrelationPosition = i; }}
let period = maxCorrelationPosition;5.二次插值提高精度
let prevValue = autocorrelation[period - 1], currentValue = autocorrelation[period], nextValue = autocorrelation[period + 1];let quadraticA = (prevValue + nextValue - 2 * currentValue) / 2;let quadraticB = (nextValue - prevValue) / 2;if (quadraticA) period = period - quadraticB / (2 * quadraticA);6.频率计算
频率计算的公式为:f = 采样率 / 周期
let sampleRate = audioContext.sampleRate;let frequency = sampleRate / period;完整代码
async function getMicroPhoneDevice() { if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { console.log("浏览器不支持音频输入"); return; }
try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const audioContext = new window.AudioContext(); const mediaStreamSource = audioContext.createMediaStreamSource(stream);
const processor = audioContext.createScriptProcessor(4096, 1, 1); mediaStreamSource.connect(processor); processor.connect(audioContext.destination); processor.onaudioprocess = (e) => { let inputBuffer = e.inputBuffer.getChannelData(0); // 读取第一个声道的数据 console.log(`音频频率为:${getFrequency(inputBuffer, audioContext.sampleRate)}`); }; } catch (error) { console.error("音频解析失败,请检查麦克风或重新尝试!", error); }}
function getFrequency(inputBuffer, sampleRate) { let rms = 0; let SIZE = inputBuffer.length; for (let i = 0; i < SIZE; i++) { let val = inputBuffer[i]; rms += val * val; } rms = Math.sqrt(rms / SIZE); if (rms < 0.01) { console.log("信号强度不足"); } let startIndex = 0, endIndex = SIZE - 1, threshold = 0.2;
for (let i = 0; i < SIZE / 2; i++) { if (Math.abs(inputBuffer[i]) < threshold) { startIndex = i; break; } } for (let i = 1; i < SIZE / 2; i++) { if (Math.abs(inputBuffer[SIZE - i]) < threshold) { endIndex = SIZE - i; break; } }
inputBuffer = inputBuffer.slice(startIndex, endIndex); SIZE = inputBuffer.length;
let autocorrelation = new Array(SIZE).fill(0);
for (let i = 0; i < SIZE; i++) { for (let j = 0; j < SIZE - i; j++) { autocorrelation[i] += inputBuffer[j] * inputBuffer[j + i]; } }
let peakIndex = 0; while (autocorrelation[peakIndex] > autocorrelation[peakIndex + 1]) peakIndex++;
let maxCorrelationValue = -1, maxCorrelationPosition = -1; for (let i = peakIndex; i < SIZE; i++) { if (autocorrelation[i] > maxCorrelationValue) { maxCorrelationValue = autocorrelation[i]; maxCorrelationPosition = i; } }
let period = maxCorrelationPosition; let prevValue = autocorrelation[period - 1], currentValue = autocorrelation[period], nextValue = autocorrelation[period + 1]; let quadraticA = (prevValue + nextValue - 2 * currentValue) / 2; let quadraticB = (nextValue - prevValue) / 2; if (quadraticA) period = period - quadraticB / (2 * quadraticA); if (period === 0) return null; let frequency = sampleRate / period; return frequency;} 在浏览器中实时获取声音频率
https://fuwari.vercel.app/posts/2024年/在浏览器中实时获取声音频率/