工具:MediaDevices接口提供访问连接媒体输入的设备,如照相机和麦克风,以及屏幕共享等。它可以使你取得任何硬件资源的媒体数据。
实现功能点:在web页面中,获取音频流
开始
采用navigator.mediaDevices.getUserMedia() 获取音频流,需要考虑以下几点:
- 浏览器兼容性问题
- 权限问题
- 音频流格式问题
一、浏览器兼容性问题
但不同浏览器的实现存在差异 先说浏览器兼容性问题,目前主流浏览器都支持getUserMedia(),但不同浏览器的实现存在差异,需要根据不同浏览器进行适配。
主要有以下几点不同:
1. API命名差异:
- 现代浏览器统一使用 navigator.mediaDevices.getUserMedia()
- 旧版Chrome使用 webkitGetUserMedia()
- 旧版Firefox使用 mozGetUserMedia()
- 旧版Edge使用 msGetUserMedia()
2. 参数配置差异:
- 所有浏览器都要求必须设置audio或video参数
- Firefox支持更多音频编解码器和格式
- Safari对某些高级音频参数支持有限
3. 错误处理差异:
- 现代浏览器返回Promise对象
- 旧版本使用回调函数
- 不同浏览器的错误信息格式不统一
4. 兼容性处理方案: 一般用不上,使用标准的navigator.mediaDevices.getUserMedia()方法,对于不支持的浏览器给出明确提示,用户浏览器版本不支持即可,对就是那么硬!
// 定义一个兼容性处理函数
function getUserMediaSupport() {
// 标准方法
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
return navigator.mediaDevices.getUserMedia;
}
// 旧版浏览器前缀方法
const getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia;
if (!getUserMedia) {
return Promise.reject(new Error('getUserMedia 不被支持'));
}
// 将老式的getUserMedia API封装成Promise
return function(constraints) {
return new Promise((resolve, reject) => {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
}
// 使用示例
const getMedia = getUserMediaSupport();
getMedia({
audio: {
sampleRate: 44100, // 采样率
channelCount: 1, // 声道数
echoCancellation: true // 回声消除
},
video: false
})
.then(stream => {
// 成功获取音频流
console.log('获取音频流成功:', stream);
})
.catch(err => {
// 处理错误
console.error('获取音频流失败:', err);
});
二、权限问题
在获取权限的时候,需要考虑到权限问题,比如麦克风权限,需要用户授权,否则无法获取音频流。 而限制上由于http协议是明文传输,所以无法保证音频流的安全性,所以浏览器的安全策略会限制获取音频流。
注意了,当获取不到权限的时候,考虑一下你的域名是否是http的协议,如果是http的协议不行的,需要使用https的协议或者localhost本地才能获取音频流。
三、音频流的格式问题
音频流格式涉及编码和容器格式两个层面。以下是常见的Web音频格式:
1. 容器格式:
- WebM: 由Google开发的开源多媒体容器格式,基于Matroska
- Ogg: 由Xiph.Org基金会开发的开源多媒体容器格式
- MP4: 基于ISO/IEC 14496-14标准的多媒体容器格式
- MPEG: Moving Picture Experts Group制定的标准容器格式
2. 编解码器:
Opus是一个完全开放、免版税且多功能的音频编解码器,由IETF于2012年标准化。它具有以下特点:
- 支持从6 kbit/s窄带语音到510 kbit/s全带立体声音乐的比特率
- 算法延迟从2.5ms到60ms可调
- 支持恒定比特率(CBR)和可变比特率(VBR)编码
- 支持语音和音乐编码
- 支持多种采样率(8-48 kHz)
- 内置丢包隐藏和前向纠错功能
在Web录音中常用的MIME类型:
- audio/webm: WebM容器,默认使用Opus编解码器
- audio/webm;codecs=opus: 显式指定使用Opus编解码器的WebM容器
- audio/ogg;codecs=opus: Ogg容器使用Opus编解码器
- audio/mp4: MP4容器格式
- audio/mpeg: MPEG容器格式
WebM+Opus组合在Web应用中被广泛采用,原因是:
- 开源免费,无专利费用
- 编码效率高,音质好
- 浏览器支持广泛
- 延迟低,适合实时通信
- 具有先进的音频处理能力(回声消除、降噪等)
webm 可以转成mp3格式,但是转成mp3格式后,不支持回声消除和降噪。
录音代码
那我们写一个封装好的类,来获取音频流,并保存为mp3格式。
标识挺清晰的,可以参考一下。
简单的录音类
class AudioRecorder{
constructor(){
this.mediaRecorder = null // 媒体实例
this.stream = null // stream 音频流
this.audioChunks = [] // 音频的数据
this.recordStartTime = null // 录音了开始时间
this.isRecording = false // 是否开启录音
}
//初始化录音
async init(){
try {
if(this.stream){
this.stream.getTracks().forEach(track => track.stop());
}
this.mediaRecorder = null
this.stream = await navigator.mediaDevices.getUserMedia({
audio: {
sampleRate: 44100, // 降低采样率到标准CD音质
channelCount: 1, // 使用单声道
echoCancellation: true, // 开启回声消除
noiseSuppression: true, // 开启降噪
autoGainControl: true // 开启自动增益
}
})
// 'audio/mpeg',
// 'audio/webm',
// 'audio/webm;codecs=opus',
// 'audio/ogg;codecs=opus',
// 'audio/mp4'
// 使用标准音频
const mimeType = 'audio/webm;codecs=opus' // WebM 格式通常支持较好
this.mediaRecorder = new MediaRecorder(this.stream, {
mimeType: mimeType,
audioBitsPerSecond: 12800, // 更高的比特率
})
// 设置ondataavailable事件处理
this.mediaRecorder.ondataavailable = (event) => {
if(event.data.size > 0){
this.audioChunks.push(event.data)
}
}
return true
} catch (error) {
console.error('录音初始化失败:', error)
throw error // 抛出问题
}
}
// 开始录音
start(){
if(!this.mediaRecorder || this.mediaRecorder.state !== 'inactive') return false
try {
this.audioChunks = []
this.stream = null
this.recordStartTime = Date.now()
this.mediaRecorder.start(100) // 每100ms触发一次 ondataavailable,可以获取这100ms内的录音数据
this.isRecording = true
return true
} catch (error) {
console.error('开始录音失败:', error)
this.isRecording = false
return false
}
}
// 停止录音
stop(){
if (!this.mediaRecorder || this.mediaRecorder.state !== 'recording') return null
return new Promise((resolve) =>{
const startTime = this.recordStartTime
// 设置 onstop 事件处理器
this.mediaRecorder.onstop = () =>{
const duration = Date.now() - startTime
const audioBlob = new Blob(this.audioChunks, {type: this.mediaRecorder.mimeType})
const audioFile = new File([audioBlob], `voice_${Date.now()}.mp3`, {
type: this.mediaRecorder.mimeType
})
resolve({
file: audioFile,
duration: duration
})
}
this.mediaRecorder.stop()
this.isRecording = false
if(this.stream){
this.stream.getTracks().forEach(track => track.stop())
}
this.recordStartTime = null
})
}
// 销毁实例
destroy() {
if (this.stream) {
this.stream.getTracks().forEach(track => track.stop())
}
this.mediaRecorder = null
this.audioChunks = []
}
}
export default AudioRecorder
上面的代码,并不固定,你可以根据你的需求,来修改代码。
使用方法
常用的方法:
- 初始化录音
const audioRecorder = new AudioRecorder()
audioRecorder.init()
- 开始录音
audioRecorder.start()
- 停止录音
audioRecorder.stop()
- 销毁实例
audioRecorder.destroy()
业务逻辑参考
一些业务逻辑,需要自己去实现,比如:
- 录音的时长限制:小于2000ms,则不保存,并提示用户说话时间太短等等。
- 录音的格式转换:mp3格式。
- 录音的保存:保存到本地或者上传到服务器。
- 录音的播放:播放录音,可以存变量之后本地拉取播放、也可以去服务器请求到内容再去播放。