利用 GetUserMedia 和 MediaRecorder API 玩轉(zhuǎn)音頻錄制、播放和下載

在這個(gè)數(shù)字化的時(shí)代,網(wǎng)頁端的音頻處理能力已經(jīng)成為一個(gè)非常熱門的需求。本文將詳細(xì)介紹如何利用 getUserMedia 和 MediaRecorder 這兩個(gè)強(qiáng)大的 API,實(shí)現(xiàn)網(wǎng)頁端音頻的錄制、處理和播放等功能。
讓我們開始這個(gè)音頻處理的旅程吧!
1、getUserMedia 和 MediaRecorder API 簡介
getUserMedia 和 MediaRecorder 是 HTML5 中兩個(gè)非常重要的 API,用于訪問設(shè)備媒體輸入流并對其進(jìn)行操作。
getUserMedia
getUserMedia 允許網(wǎng)頁端訪問用戶設(shè)備的媒體輸入設(shè)備,比如攝像頭和麥克風(fēng)。通過該 API,在獲得用戶授權(quán)后,我們可以獲取這些媒體流的數(shù)據(jù),并用于各種網(wǎng)頁應(yīng)用場景中。
典型的使用方式如下:
// 請求獲取音頻流
navigator.mediaDevices.getUserMedia({
audio: true
})
.then(stream => {
// 在此處理音頻流
})getUserMedia 接受一個(gè) constraints 對象作為參數(shù),通過設(shè)置配置來請求獲取指定的媒體類型,常見的配置有:
- audio: Boolean 值,是否獲取音頻輸入。
- video: Boolean 值,是否獲取視頻輸入。
- 以及更詳細(xì)的各種音視頻參數(shù)設(shè)置。
MediaRecorder
MediaRecorder API 可以獲取由 getUserMedia 生成的媒體流,并對其進(jìn)行編碼和封裝,輸出可供播放和傳輸?shù)拿襟w文件。
典型的用法如下:
// 獲取媒體流
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
// 創(chuàng)建 MediaRecorder 實(shí)例
const mediaRecorder = new MediaRecorder(stream);
// 注冊數(shù)據(jù)可用事件,以獲取編碼后的媒體數(shù)據(jù)塊
mediaRecorder.ondataavailable = event => {
audioChunks.push(event.data);
}
// 開始錄制
mediaRecorder.start();
// 錄制完成后停止
mediaRecorder.stop();
// 將錄制的數(shù)據(jù)組裝成 Blob
const blob = new Blob(audioChunks, {
type: 'audio/mp3'
});簡單來說,getUserMedia 獲取輸入流,MediaRecorder 對流進(jìn)行編碼和處理,兩者結(jié)合就可以實(shí)現(xiàn)強(qiáng)大的音視頻處理能力。
2、獲取和處理音頻流
了解了基本 API 使用方法后,我們來看看如何獲取和處理音頻流。
獲取音頻流
首先需要調(diào)用 getUserMedia 來獲取音頻流,典型的配置是:
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
channelCount: 2,
sampleRate: 44100,
sampleSize: 16,
echoCancellation: true
}
});我們可以指定聲道數(shù)、采樣率、采樣大小等參數(shù)來獲取音頻流。
選擇錄音設(shè)備
使用 navigator.mediaDevices.enumerateDevices() 可以獲得所有可用的媒體設(shè)備列表,這樣我們就可以提供設(shè)備選擇功能給用戶,而不僅僅是默認(rèn)設(shè)備。
舉例來說,如果我們想要讓用戶選擇要使用的錄音設(shè)備:
// 1. 獲取錄音設(shè)備列表
const audioDevices = await navigator.mediaDevices.enumerateDevices();
const mics = audioDevices.filter(d => d.kind === 'audioinput');
// 2. 提供設(shè)備選擇 UI 供用戶選擇
const selectedMic = mics[0];
// 3. 根據(jù)選擇配置進(jìn)行獲取流
const constraints = {
audio: {
deviceId: selectedMic.deviceId
}
};
const stream = await navigator.mediaDevices.getUserMedia(constraints);這樣我們就可以獲得用戶選擇的設(shè)備錄音了。
處理音頻流
獲得原始音頻流后,我們可以利用 Web Audio API 對其進(jìn)行處理。
例如添加回聲效果:
// 創(chuàng)建音頻環(huán)境
const audioContext = new AudioContext();
// 創(chuàng)建流源節(jié)點(diǎn)
const source = audioContext.createMediaStreamSource(stream);
// 創(chuàng)建回聲效果節(jié)點(diǎn)
const echo = audioContext.createConvolver();
// 連接處理鏈
source.connect(echo);
echo.connect(audioContext.destination);
// 加載回聲沖擊響應(yīng)并應(yīng)用
const impulseResponse = await fetch('impulse.wav');
const buffer = await impulseResponse.arrayBuffer();
const audioBuffer = await audioContext.decodeAudioData(buffer);
echo.buffer = audioBuffer;通過這樣的音頻處理鏈,我們就可以在錄音時(shí)添加回聲、混響等音效了。
3、實(shí)現(xiàn)音頻的錄制和播放
接下來看看如何利用這些 API 實(shí)現(xiàn)音頻的錄制和播放。
錄制音頻
點(diǎn)擊開始錄音后,我們進(jìn)行以下步驟:
- 調(diào)用 getUserMedia 獲取音頻流。
- 創(chuàng)建 MediaRecorder 實(shí)例,傳入音頻流。
- 注冊數(shù)據(jù)可用回調(diào),以獲取編碼后的音頻數(shù)據(jù)塊。
- 調(diào)用 recorder.start() 開始錄制。
- 錄制完成后調(diào)用 recorder.stop()。
let recorder;
let audioChunks = [];
// 開始錄音 handler
const startRecording = async () => {
const stream = await navigator.mediaDevices.getUserMedia({
audio: true
});
recorder = new MediaRecorder(stream);
recorder.ondataavailable = event => {
audioChunks.push(event.data);
};
recorder.start();
}
// 停止錄音 handler
const stopRecording = () => {
if(recorder.state === "recording") {
recorder.stop();
}
}播放音頻
錄音完成后,我們可以將音頻數(shù)據(jù)組裝成一個(gè) Blob 對象,然后賦值給一個(gè) <audio> 元素的 src 屬性進(jìn)行播放:
// 錄音停止后
const blob = new Blob(audioChunks, { type: 'audio/ogg' });
const audioURL = URL.createObjectURL(blob);
const player = document.querySelector('audio');
player.src = audioURL;
// 調(diào)用播放
player.play();這樣就可以播放剛剛錄制的音頻了。
后續(xù)也可以添加下載功能等。
4、音頻效果的處理
利用 Web Audio API,我們可以添加各種音頻效果,進(jìn)行音頻處理。
例如添加回聲效果:
const audioContext = new AudioContext();
// 原始音頻節(jié)點(diǎn)
const source = audioContext.createMediaStreamSource(stream);
// 回聲效果節(jié)點(diǎn)
const echo = audioContext.createConvolver();
// 連接處理鏈
source.connect(echo);
echo.connect(audioContext.destination);
// 加載沖擊響應(yīng)作為回聲效果
const impulseResponse = await fetch('impulse.wav');
const arrayBuffer = await impulseResponse.arrayBuffer();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
echo.buffer = audioBuffer;這樣在錄制時(shí)音頻流就會(huì)經(jīng)過回聲效果處理了。
此外,我們還可以添加混響、濾波、均衡器、壓縮等多種音頻效果,使得網(wǎng)頁端也能處理出專業(yè)級的音頻作品。
5、實(shí)時(shí)語音通話的應(yīng)用
利用 getUserMedia 和 WebRTC 技術(shù),我們還可以在網(wǎng)頁端實(shí)現(xiàn)實(shí)時(shí)的點(diǎn)對點(diǎn)語音通話。
簡述流程如下:
- 通過 getUserMedia 獲取本地音視頻流。
- 創(chuàng)建 RTCPeerConnection 實(shí)例。
- 將本地流添加到連接上。
- 交換 ICE 候選信息,建立連接。
- 當(dāng)檢測到連接后,渲染遠(yuǎn)端用戶的音視頻流。
這樣就可以實(shí)現(xiàn)類似 Skype 的網(wǎng)頁端語音通話功能了。
// 1. 獲取本地流
const localStream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
// 2. 創(chuàng)建連接對象
const pc = new RTCPeerConnection();
// 3. 添加本地流
localStream.getTracks().forEach(track => pc.addTrack(track, localStream));
// 4. 交換 ICE 等信令,處理 ONADDSTREAM 等事件
// ...
// 5. 收到遠(yuǎn)端流,渲染到頁面
pc.ontrack = event => {
remoteVideo.srcObject = event.streams[0];
}獲取本地輸入流后,經(jīng)過編碼和傳輸就可以實(shí)現(xiàn)語音聊天了。
6、兼容性和 Latency 問題
盡管 getUserMedia 和 MediaRecorder 在現(xiàn)代瀏覽器中已經(jīng)得到了較好的支持,但由于不同廠商和版本實(shí)現(xiàn)存在差異,在實(shí)際應(yīng)用中還是需要注意一些兼容性問題:
- 檢測 API 支持情況,提供降級方案。
- 注意不同瀏覽器對 Codec、采樣率等參數(shù)支持的差異。
- 封裝瀏覽器差異,提供統(tǒng)一的 API。
此外,錄音和播放也存在一定的延遲問題。我們需要針對 Latency 進(jìn)行優(yōu)化,比如使用更小的 buffer 大小,壓縮數(shù)據(jù)包大小等方法。
7、結(jié)語:開啟音頻創(chuàng)作的新紀(jì)元
getUserMedia 和 MediaRecorder 為網(wǎng)頁端帶來了強(qiáng)大的媒體處理能力。通過它們,現(xiàn)在我們可以方便地在網(wǎng)頁中實(shí)現(xiàn)錄音、音頻效果處理以及實(shí)時(shí)語音通話等功能了。
當(dāng)然,在使用時(shí)也需要注意瀏覽器兼容性,以及保障用戶隱私等問題。了解這些 API 的工作原理,可以讓我們開發(fā)出更加優(yōu)秀的音頻類 Web 應(yīng)用。























