概述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' }); }

发表评论 取消回复