Web API 內建的 MediaRecorder
並無法將麥克風的聲音儲存成 WAV
格式檔案,而是 Google 自己的 WebM
格式,必須使用 Web Audio API 的 ScriptProcessor
內自己將 WebM
轉成 PCM
,才能夠產生 PCM
格式檔案下載。
Version
Vue 3.3
Architecture
MediaStream
: 從麥克風取得 streamAudioContext
: 產生 16 bit stream- 由
AudioContext
產生SrcNode
、ScriptNode
與DestNode
,需使用connect()
將各 node 連結在一起 ScriptNode
:將WebM
轉PCM
ScriptProcessor
<template>
<button @click="onStart">Start</button>
<button @click="onStop">Stop</button>
</template>
<script setup>
const SAMPLE_RATE = 16000
const FILE_NAME = 'sample.pcm'
let isRecording = false
let chunks = []
let encodePCM = (output, input) => {
let offset = 0
for (let i = 0; i < input.length; i++, offset += 2) {
let s = Math.max(-1, Math.min(1, input[i]))
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true)
}
return output
}
let onStart = async () => {
isRecording = true
try {
let mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true })
let audioContext = new AudioContext({ sampleRate: SAMPLE_RATE })
let srcNode = audioContext.createMediaStreamSource(mediaStream)
let scriptNode = audioContext.createScriptProcessor(4096, 1, 1)
let destNode = audioContext.createMediaStreamDestination()
srcNode.connect(scriptNode)
scriptNode.connect(destNode)
scriptNode.onaudioprocess = e => {
let inputData = e.inputBuffer.getChannelData(0)
if (isRecording === true) {
for (let x of inputData) {
chunks.push(x)
}
}
}
} catch (err) {
console.warn(err)
}
}
let onStop = () => {
isRecording = false
let arrayBuffer = new ArrayBuffer(chunks.length * 2)
let dataView = new DataView(arrayBuffer)
let pcms = encodePCM(dataView, chunks)
let blob = new Blob([pcms])
let link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = FILE_NAME
link.click()
chunks.length = 0
URL.revokeObjectURL(link.href)
}
</script>
Line 7
const SAMPLE_RATE = 16000
const FILE_NAME = 'sample.pcm'
設定可改變的變數:
SAMPLE_RATE
:取樣頻率FILE_NAME
:下載檔名
Line 10
let isRecording = false
let chunks = []
isRecording
:是否正在錄音chunks
:儲存音訊的chunks
array
Line 23
let onStart = async () => {
isRecording = true
}
- 當按下 Start 時,將
isRecording
設定為true
表示開始錄音
Line 27
let mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true })
- 使用 Web API 內建的
navigator.mediaDevices.getUserMedia()
取得MediaStream
Line 28
let audioContext = new AudioContext({ sampleRate: SAMPLE_RATE })
- 使用
AudioContext
產生 16 bit 的 stream
Line 30
let srcNode = audioContext.createMediaStreamSource(mediaStream)
let scriptNode = audioContext.createScriptProcessor(4096, 1, 1)
let destNode = audioContext.createMediaStreamDestination()
- 由
AudioContext
建立srcNode
、scriptNode
與destNode
srcNode
:起始節點scriptNode
:將WebM
轉PCM
destNode
:結束節點
Line 34
srcNode.connect(scriptNode)
scriptNode.connect(destNode)
- 將
srcNode
、scriptNode
與destNode
連在一起
Line 37
scriptNode.onaudioprocess = e => {
let inputData = e.inputBuffer.getChannelData(0)
if (isRecording === true) {
for (let x of inputData) {
chunks.push(x)
}
}
}
- 麥克風的聲音會不斷地從
audioprocess
event 傳入 - 由
getChannelData()
傳入0
,取得 Mono Channel 數據 - 由
isRecording
判斷是否在錄音 - 將 inputData 依序塞入儲存音訊的
chunks
array
Line 50
let onStop = () => {
isRecording = false
let arrayBuffer = new ArrayBuffer(chunks.length * 2)
let dataView = new DataView(arrayBuffer)
let pcms = encodePCM(dataView, chunks)
let blob = new Blob([pcms])
let link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = FILE_NAME
link.click()
chunks.length = 0
URL.revokeObjectURL(link.href)
}
- 設定
isRecording
為false
表示停止錄音
- 開始將儲存音訊的
chunks
寫入PCM
檔案
Line 13
let encodePCM = (output, input) => {
let offset = 0
for (let i = 0; i < input.length; i++, offset += 2) {
let s = Math.max(-1, Math.min(1, input[i]))
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true)
}
return output
}
- 將
WebM
轉成PCM
Conclusion
- 使用 Audio API 的
ScriptProcessor
就可將WebM
轉成PCM
,且不需使用MediaRecorder