概述WebAudio 可从麦克风采集 PCM。本文示例封装 WAV 头并保存。采集与封装async function capturePCM() {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const ctx = new AudioContext();
const src = ctx.createMediaStreamSource(stream);
const processor = ctx.createScriptProcessor(4096, 1, 1);
const chunks = [];
processor.onaudioprocess = e => { const data = e.inputBuffer.getChannelData(0); chunks.push(Float32Array.from(data)); };
src.connect(processor); processor.connect(ctx.destination);
return { stop: async () => { processor.disconnect(); src.disconnect(); stream.getTracks().forEach(t => t.stop()); return chunks; } };
}
function pcmToWav(floatChunks, sampleRate = 44100) {
const samples = Float32Array.from(floatChunks.reduce((a,c) => a.concat(Array.from(c)), []));
const buf = new ArrayBuffer(44 + samples.length * 2);
const view = new DataView(buf);
const writeStr = (off, s) => { for (let i=0;i<s.length;i++) view.setUint8(off+i, s.charCodeAt(i)); };
writeStr(0, 'RIFF'); view.setUint32(4, 36 + samples.length * 2, true); writeStr(8, 'WAVE'); writeStr(12, 'fmt ');
view.setUint32(16, 16, true); view.setUint16(20, 1, true); view.setUint16(22, 1, true); view.setUint32(24, sampleRate, true);
view.setUint32(28, sampleRate * 2, true); view.setUint16(32, 2, true); view.setUint16(34, 16, true);
writeStr(36, 'data'); view.setUint32(40, samples.length * 2, true);
let off = 44; for (let i=0;i<samples.length;i++) { const s = Math.max(-1, Math.min(1, samples[i])); view.setInt16(off, s < 0 ? s * 0x8000 : s * 0x7FFF, true); off += 2; }
return new Blob([buf], { type: 'audio/wav' });
}

发表评论 取消回复