一、概述
通过WebRTC,实现在页面上进行录音,并将录音结果转换为.wav格式进行播放
二、录音实现
- 检测是否有麦克风权限
navigator.mediaDevices.enumerateDevices().then(function(devices) {
// 检测视频输入
let video = devices.find((device) => {
return device.kind === 'videoinput'
})
// 检测音频输入
let audio = devices.find((device) => {
return device.kind === 'audioinput'
})
// getUserMedia的参数
let mediaConstraints = {
video: !!video,
audio: !!audio
}
let constraints = { video: video && mediaConstraints.video, audio: audio && mediaConstraints.audio }
return navigator.mediaDevices.getUserMedia(constraints).then(mediaStream => {
stream = mediaStream // stream 记录音频流,在停止录音等时使用
// 开始录音...
beginRecord(mediaStream )
})
})
- 调用getUserMedia方法,获取音频流,并开始录音
// 录音
function beginRecord(mediaStream) {
let audioContext = new (window.AudioContext || window.webkitAudioContext)
let mediaNode = audioContext.createMediaStreamSource(mediaStream)
// 创建一个jsNode
let jsNode = createJSNode(audioContext)
// 需要连接扬声器消费到outputBuffer, process回调才能出发,且不给outputBuffer设置内容扬声器不会播出声音
jsNode.connect(audioContext.destination)
jsNode.onaudioprocess = (audioProcessingEvent) => {
let audioBuffer = audioProcessingEvent.inputBuffer
let leftChannelData = audioBuffer.getChannelData(0) // 左声道
let rightChannelData = audioBuffer.getChannelData(1) // 右声道
// 将音频数据存入dataList
dataList.left.push(leftChannelData.slice(0))
dataList.right.push(rightChannelData.slice(0))
}
// 把mediaNode连接到jsNode
mediaNode.connect(jsNode)
}
以上步骤完成,便开始录音,并且将音频数据存入了dataList
数组中。
到这一步,其实录音的工作已经完成了。下面的工作是将录音数据保存成特定格式(如.mp3),并插入到页面的audio元素中进行播放。
三、停止录音
停止录音需要做三个工作:
- 合并数据
- 创建指定格式的文件
- 录音播放
停止录音的总体方法为:
function stopRecord() {
let leftData = mergeArray(dataList.left)
let rightData = mergeArray(dataList.right)
let allData = intervalLeftAndRight(leftData, rightData) // 合并数据
let wavBuffer = createWavFile(allData) // 创建指定格式的数据
playRecord(wavBuffer) // 播放录音
}
下面按照具体步骤实现。
- 合并数据
// 交叉合并左右声道的数据
function intervalLeftAndRight(left, right) {
let totalLength = left.length + right.length
let data = new Float32Array(totalLength)
for(let i = 0; i < left.length; i++) {
let k = i * 2
data[k] = left[i]
data[k + 1] = right[i]
}
return data
}
function mergeArray(list) {
let length = list.length * list[0].length
let data = new Float32Array(length)
let offset = 0
for(let i = 0; i < list.length; i++) {
data.set(list[i], offset)
offset += list[i].length
}
return data
}
- 创建指定格式的文件,这一块的代码是完全从网上找的资料,具体出处地址不记得了。此处是创建
.wav
文件
function createWavFile(audioData) {
const WAV_HEAD_SIZE = 44
let buffer = new ArrayBuffer(audioData.length * 2 + WAV_HEAD_SIZE)
let view = new DataView(buffer)
// 写入wav头部信息
writeUTFBytes(view, 0, 'RIFF')
view.setUint32(4, 44 + audioData.length * 2, true)
writeUTFBytes(view, 8, 'WAVE')
writeUTFBytes(view, 12, 'fmt ');
view.setUint32(16, 16, true);
view.setUint16(20, 1, true);
view.setUint16(22, 2, true);
view.setUint32(24, 44100, true);
view.setUint32(28, 44100 * 2, true);
view.setUint16(32, 2 * 2, true);
view.setUint16(34, 16, true);
writeUTFBytes(view, 36, 'data');
view.setUint32(40, audioData.length * 2, true);
// 写入wav头部,代码同上
let length = audioData.length;
let index = 44;
let volume = 1;
for (let i = 0; i < length; i++) {
view.setInt16(index, audioData[i] * (0x7FFF * volume), true);
index += 2;
}
return buffer;
}
function writeUTFBytes(view, offset, string) {
var lng = string.length;
for (var i = 0; i < lng; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
}
- 录音播放
function playRecord(arrayBuffer) {
let blob = new Blob([new Uint8Array(arrayBuffer)])
let blobUrl = URL.createObjectURL(blob); // 创建BlobURL
document.querySelector('.audio-node').src = blobUrl // 插入到页面的音频播放组件
}
四、关闭录音功能
在停止录音的时候,浏览器的录音功能是需要手动关闭的,否则会在浏览器顶部出现录音的红色点点,下图这样的:
关闭录音功能的方法:
stream.getTracks().forEach(track => {
if(track.readyState === 'live') {
track.stop()
}
})
以上,就是一个完整的web端录音功能的实现。