Web API 內建的 MediaRecorder 並無法將麥克風的聲音儲存成 PCM 格式檔案,而 extendable-media-recorder 提供與 MediaRecorder 相同的 interface,但可支援 PCM 格式。
Version
Vue 3.3
extendable-media-recorder 9.1.6
extendable-media-recorder-wav-encoder 7.0.101
Install Package
$ npm install extendable-media-recorder
$ npm install extendable-media-recorder-wav-encoder
extendable-media-recorder:提供與原生MediaRecorder相同 interface 的新MediaRecorderextendable-media-recorder-wav-encoder:支援WAV格式
Architecture
MediaStream: 從麥克風取得 streamAudioContext: 產生 16 bit stream- 由
AudioContext產生SrcNode與DestNode,需使用connect()將各 node 連結在一起 MediaRecorder:將WebM轉PCM
Extendable Media Recorder
main.js
import { createApp } from 'vue'
import App from './App.vue'
import { register } from 'extendable-media-recorder'
import { connect } from 'extendable-media-recorder-wav-encoder'
await register(await connect())
createApp(App).mount('#app')
Line 7
await register(await connect())
- 與原生
MediaRecorder不同,extendable-media-recorder所提供的MediaRecorder必須先註冊才能使用 register()不能寫在每個頁面的mounted(),只要 route 改變重新進入該 page,就會造成重複註冊的錯誤,因此只能寫在main.js只註冊一次
App.vue
<template>
<button @click="onStart">Start</button>
<button @click="onStop">Stop</button>
</template>
<script setup>
import { MediaRecorder } from 'extendable-media-recorder'
const SAMPLE_RATE = 16000
const CHANNEL_COUNT = 1
const SAMPLE_INTERVAL = 1000
const FILE_NAME = 'sample.pcm'
let mediaRecorder = null
let chunks = []
let onStart = async () => {
try {
let mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true })
let audioContext = new AudioContext({ sampleRate: SAMPLE_RATE })
let srcNode = new MediaStreamAudioSourceNode(audioContext, { mediaStream })
let destNode = new MediaStreamAudioDestinationNode(audioContext, {
channelCount: CHANNEL_COUNT
})
srcNode.connect(destNode)
mediaRecorder = new MediaRecorder(destNode.stream, {
mimeType: 'audio/wav'
})
mediaRecorder.ondataavailable = e => {
chunks.push(e.data)
}
mediaRecorder.onstop = () => {
let blob = new Blob(chunks)
let link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = FILE_NAME
link.click()
URL.revokeObjectURL(link.href)
}
mediaRecorder.start(SAMPLE_INTERVAL)
} catch (err) {
console.warn(err)
}
}
let onStop = () => {
mediaRecorder.stop()
}
</script>
Line 7
import { MediaRecorder } from 'extendable-media-recorder'
- 使用
extendable-media-recorder所提供的MediaRecorder取代 Web API 內建的MediaRecorder
Line 9
const SAMPLE_RATE = 16000
const CHANNEL_COUNT = 1
const SAMPLE_INTERVAL = 1000
const FILE_NAME = 'sample.pcm'
設定可改用的變數:
SAMPLE_RATE:取樣頻率CHANNEL_COUNT:Mono channelPERIOD:每次取樣時間FILE_NAME:下載檔名
Line 19
let mediaStream = await navigator.mediaDevices.getUserMedia({
audio: true,
})
- 使用 Web API 內建的
navigator.mediaDevices.getUserMedia()取得MediaStream
Line 21
let audioContext = new AudioContext({ sampleRate: SAMPLE_RATE })
- 要改變取樣頻率,必須使用
AudioContext
Line 23
let srcNode = new MediaStreamAudioSourceNode(audioContext, { mediaStream })
let destNode = new MediaStreamAudioDestinationNode(audioContext)
AudioConext必須靠node方式運行- 由
AudioContext與MediaStream建立srcNode - 由
AudioContext建立destNode
Line 28
srcNode.connect(destNode)
- 使用
connect()連結srcNode與destNode
Line 30
mediaRecorder = new MediaRecorder(destNode.stream, {
mimeType: 'audio/wav',
})
- 使用
extendable-media-recorder提供的MediaRecorder,並改由destNode所處理過的MediaStream - 指定
mineType為audio/wav
Line 55
let onStop = () => {
mediaRecorder.stop()
}
- 啟動
MediaRecoder開始錄音
Line 34
mediaRecorder.ondataavailable = e => {
chunks.push(e.data)
}
- 觸發
dataavaliableevent 取得音訊 - 依序塞入儲存音訊的
chunksarray
Line 38
mediaRecorder.onstop = () => {
let blob = new Blob(chunks)
let link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = FILE_NAME
link.click()
URL.revokeObjectURL(link.href)
}
- 將
chunksarray 轉成Blob - 建立臨時 url 下載
PCM檔案
Conclusion
- Web API 內建的
MediaRecorder並不支援WAV格式 extendable-media-recorder提供與MediaRecorder相同的 interface,但可支援WAV格式- 但
extendable-media-recorder的MediaRecorder必須register()之後才能使用,且與 Vue 的生命週期不太一樣,不能在mounted()寫register(),會造成重複註冊問題,必須改寫在main.js只重複一次