有没有想过,网站是如何让您录制语音笔记、玩带有动态音效的互动游戏,甚至将音乐可视化的?这些神奇的事情就发生在浏览器中,由专门用于处理音频流的复杂 JavaScript API 驱动。
如果您刚开始接触涉及声音的 Web 开发,您很快就会遇到两个主要 API:MediaRecorder API 和 Web Audio API。这两个 API 都能处理音频,但它们的用途和运行方式却截然不同。
本文以初学者易于理解的方式来分解这些 API,探索它们的工作原理,查看一些代码示例,并弄清楚何时使用哪种 API。
音频流到底是什么?
在浏览器中,音频流本质上是连续的音频数据流。可以把它想象成一条声音信息的河流。
音频流的来源有多种:
- 麦克风:使用
navigator.mediaDevices.getUserMedia()
方法,我们可以请求访问用户的麦克风并获取实时音频流。 - 音频文件:可以加载音频文件(如 MP3 或 WAV),并将其内容作为流进行处理。
- Web Audio API 生成:Web Audio API 本身可以从头开始创建音频流(如合成器)。
获取实时音频流(如从麦克风获取)的最常用方法是使用 getUserMedia
。它会返回一个 MediaStream 对象,这是 MediaRecorder 和 Web Audio API 都能使用的原始数据。
// 示例:获取麦克风流
async function getMicrophoneStream() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
console.log("Microphone access granted!");
// 现在 'stream' 保存了实时音频数据流
return stream;
} catch (err) {
console.error("Error accessing microphone:", err);
alert("Could not access the microphone. Please check permissions.");
return null;
}
}
// Later you might call:
// const myAudioStream = await getMicrophoneStream();
// if (myAudioStream) { /* Do something with the stream */ }
现在,让我们看看两个 API 如何处理这个 MediaStream。
MediaRecorder API 与 Web Audio API
MediaRecorder API:
可以将其视为简易录音机。它的主要功能是获取 MediaStream 并将其录制成标准音频文件格式(如 WebM、Ogg 或 MP4,具体取决于浏览器)。它是一种简单高效的音频采集工具。
Web Audio API:
可将其视为高级音频实验室。它专为实时处理、分析和合成音频而设计。它可以对声音进行精细控制,让您以各种方式对其进行操作(调节音量、添加效果、可视化频率、从头开始创建声音)。
MediaRecorder API
当您的目标仅仅是从流中捕获音频(或视频)并将其保存为文件时,MediaRecorder API 就是理想选择。
工作原理
- 获取 MediaStream:通常来自 getUserMedia(麦克风),也可能来自
<video>
或<canvas>
元素(尽管我们关注的是音频)。 - 创建 MediaRecorder 实例:创建一个新的
MediaRecorder
对象,并将流传递给其构造函数。您可以选择指定所需的输出格式(mimeType)。 - 监听数据: 为
ondataavailable
事件添加一个事件监听器。该事件会在录音时定期触发(停止时也会触发一次),为您提供已录制的音频数据块。 - 开始录制:调用
start()
方法。您可以选择向start()
方法传递一个 timeslice 参数(以毫秒为单位),使ondataavailable
每 X 毫秒触发一次。如果不提供 timeslice 参数,通常只会在记录停止时触发一次。 - 收集数据块:在
ondataavailable
事件处理程序中,将接收到的数据块(即 Blob 对象)收集到一个数组中。 - 停止录制:调用
stop()
方法。这会触发最后一个ondataavailable
事件,然后触发一个 stop 事件。 - 合成最终文件:录音停止后,将所有收集到的数据块合成一个 Blob。该 Blob 代表最终音频文件(如 .webm 或 .ogg 文件)。
- 使用 Blob:可以为 Blob 创建一个可下载链接,将其上传到服务器,或在
<audio>
元素中播放。
数据处理:
- MediaRecorder 会将数据流中的原始音频数据编码为由 mimeType 指定的压缩格式(如 audio/webm;codecs=opus 或 audio/ogg;codecs=opus)。
- 它以 Blob 对象的形式传输编码后的数据。Blob(二进制大对象)是一种代表原始、不可变数据的对象,本质上就像内存中的文件。
- 使用 MediaRecorder,不能直接访问原始音频样本(如单个声波值)。您只需获取随时可用的编码文件块。
代码示例:录制麦克风音频
<!DOCTYPE html>
<html>
<head>
<title>MediaRecorder Example</title>
</head>
<body>
<h1>Simple Audio Recorder</h1>
<button id="startButton">Start Recording</button>
<button id="stopButton" disabled>Stop Recording</button>
<audio id="audioPlayback" controls></audio>
<a id="downloadLink" style="display: none;">Download Recording</a>
<script>
const startButton = document.getElementById('startButton');
const stopButton = document.getElementById('stopButton');
const audioPlayback = document.getElementById('audioPlayback');
const downloadLink = document.getElementById('downloadLink');
let mediaRecorder;
let audioChunks = [];
let audioStream;
startButton.addEventListener('click', async () => {
try {
// 1. Get microphone stream
audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
// 2. Create MediaRecorder instance
// Let the browser pick the best mimeType
mediaRecorder = new MediaRecorder(audioStream);
// 3. Listen for data
mediaRecorder.ondataavailable = event => {
if (event.data.size > 0) {
audioChunks.push(event.data);
console.log("Received chunk:", event.data);
}
};
// Handle stop event
mediaRecorder.onstop = () => {
console.log("Recording stopped.");
// 7. Assemble the final file (Blob)
const audioBlob = new Blob(audioChunks, { type: mediaRecorder.mimeType || 'audio/webm' }); // Use recorded mimeType
// 8. Use the Blob
const audioUrl = URL.createObjectURL(audioBlob);
audioPlayback.src = audioUrl;
downloadLink.href = audioUrl;
// Use a more descriptive filename
const fileExtension = (mediaRecorder.mimeType || 'audio/webm').split('/')[1].split(';')[0];
downloadLink.download = `recording.${fileExtension}`;
downloadLink.style.display = 'block';
// Clean up
audioChunks = []; // Reset chunks for next recording
// Stop the tracks of the stream to turn off the mic light
audioStream.getTracks().forEach(track => track.stop());
startButton.disabled = false;
stopButton.disabled = true;
};
// 4. Start recording
mediaRecorder.start(); // You could use mediaRecorder.start(1000); for 1-second chunks
console.log("Recording started...");
startButton.disabled = true;
stopButton.disabled = false;
downloadLink.style.display = 'none'; // Hide link during recording
audioPlayback.src = ''; // Clear previous playback
} catch (err) {
console.error("Error starting recording:", err);
alert("Could not start recording. Check microphone permissions.");
}
});
stopButton.addEventListener('click', () => {
if (mediaRecorder && mediaRecorder.state === 'recording') {
// 6. Stop recording
mediaRecorder.stop();
// The rest happens in the 'onstop' handler
}
});
</script>
</body>
</html>
MediaRecorder 的用例:
- 聊天应用中的语音消息录制。
- 简单的音频笔记应用程序。
- 捕获用户音频反馈。
- 直接在浏览器中使用基本的播客录制工具。
The Web Audio API
The Web Audio API 是一个完全不同的工具。它主要不是用于录制文件,而是用于底层上处理声音。
工作原理
核心概念是音频上下文(Audio Context)和音频路由图(Audio Routing Graph)。
创建 Audio Context:
这是管理一切的中心对象。所有音频操作都在此上下文中进行。
const audioContext = new ( window.AudioContext || window.webkitAudioContext ) ( ) ;
创建音频节点(Audio Nodes)
Web Audio API 使用节点系统。每个节点代表一个音频源、一个处理步骤(效果)或一个目标。常见的节点类型包括:
- 音源节点:生成音频或将音频引入图表(例如,用于创建音调的
OscillatorNode
、用于播放已加载音频文件的AudioBufferSourceNode
、用于使用类似麦克风MediaStreamAudioSourceNode
)。 - 处理节点:修改音频信号(例如,控制音量的
GainNode
、均衡/滤波器的BiquadFilterNode
、回声DelayNode
、混响的ConvolverNode
)。 - 目标节点:通常是
audioContext.destination
,表示计算机的扬声器。通常只有一个主目标节点。 - 分析节点:在不修改音频信号的情况下检查音频信号(例如,
AnalyserNode
用于获取频率或时域数据以进行可视化)。
连接节点
使用 connect()
方法将一个节点的输出连接到另一个节点的输入。这将创建一个处理链或图。音频从音源流出,经过处理节点,最后到达目的地。
示例:
Microphone (MediaStreamAudioSourceNode) -> Volume Control (GainNode) -> Speakers (audioContext.destination)
数据处理:
- Web Audio API 处理原始的、未压缩的音频数据。这些数据通常表示为浮点数数组(通常在 -1.0 到 +1.0 之间),表示特定时间点声波的振幅。这种格式通常称为脉冲编码调制 (PCM)。
- 加载音频文件时,它们会被解码为一个
AudioBuffer
对象,该对象存储原始 PCM 数据。 - 在处理实时流或生成声音时,API 会在内部管理节点之间流动的原始数据的缓冲区。
- 这种原始数据访问允许进行精确的操作、分析和综合。
代码示例:带音量控制的麦克风输入
<!DOCTYPE html>
<html>
<head>
<title>Web Audio API Example</title>
</head>
<body>
<h1>Microphone Input Volume Control</h1>
<button id="startButton">Start Audio</button>
<label for="volumeSlider">Volume:</label>
<input type="range" id="volumeSlider" min="0" max="2" step="0.01" value="1">
<script>
const startButton = document.getElementById('startButton');
const volumeSlider = document.getElementById('volumeSlider');
let audioContext;
let microphoneSource;
let gainNode;
startButton.addEventListener('click', async () => {
if (!audioContext) {
// 1. Create AudioContext (only once!)
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
// Resume context if it was suspended (autoplay policies)
if (audioContext.state === 'suspended') {
await audioContext.resume();
}
try {
// Get microphone stream
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
// 2. Create Source Node from the stream
microphoneSource = audioContext.createMediaStreamSource(stream);
// 2. Create Processing Node (Gain for volume)
gainNode = audioContext.createGain();
gainNode.gain.value = volumeSlider.value; // Set initial volume
// 3. Connect the Nodes
// Mic -> Gain -> Speakers (Destination)
microphoneSource.connect(gainNode);
gainNode.connect(audioContext.destination);
console.log("Audio graph connected: Mic -> Gain -> Speakers");
startButton.textContent = "Audio Running";
startButton.disabled = true; // Prevent multiple starts
} catch (err) {
console.error("Error setting up Web Audio:", err);
alert("Could not set up audio. Check microphone permissions.");
}
});
volumeSlider.addEventListener('input', () => {
if (gainNode) {
// Update gain value smoothly
gainNode.gain.setValueAtTime(volumeSlider.value, audioContext.currentTime);
console.log("Volume set to:", volumeSlider.value);
}
});
</script>
</body>
</html>
结论
现代浏览器提供了许多出色的音频处理工具。MediaRecorder API提供了一种简单易用的音频流捕获和保存方法,非常适合录制任务。另一方面,Web Audio API 则开启了无限的创意可能,通过其强大的基于节点的架构,可以对声音进行精细的操作、分析和合成。
作者:Tihomir Manushev
内容源自medium。
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/yinshipin/57999.html