一、概述
通过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)// 创建一个jsNodelet jsNode = createJSNode(audioContext)// 需要连接扬声器消费到outputBuffer, process回调才能出发,且不给outputBuffer设置内容扬声器不会播出声音jsNode.connect(audioContext.destination)jsNode.onaudioprocess = (audioProcessingEvent) => {let audioBuffer = audioProcessingEvent.inputBufferlet leftChannelData = audioBuffer.getChannelData(0) // 左声道let rightChannelData = audioBuffer.getChannelData(1) // 右声道// 将音频数据存入dataListdataList.left.push(leftChannelData.slice(0))dataList.right.push(rightChannelData.slice(0))}// 把mediaNode连接到jsNodemediaNode.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.lengthlet data = new Float32Array(totalLength)for(let i = 0; i < left.length; i++) {let k = i * 2data[k] = left[i]data[k + 1] = right[i]}return data
}function mergeArray(list) {let length = list.length * list[0].lengthlet data = new Float32Array(length)let offset = 0for(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 = 44let 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); // 创建BlobURLdocument.querySelector('.audio-node').src = blobUrl // 插入到页面的音频播放组件
}
四、关闭录音功能
在停止录音的时候,浏览器的录音功能是需要手动关闭的,否则会在浏览器顶部出现录音的红色点点,下图这样的:
关闭录音功能的方法:
stream.getTracks().forEach(track => {if(track.readyState === 'live') {track.stop()}
})
以上,就是一个完整的web端录音功能的实现。