优化后的语音识别:使用navigator.mediaDevices.getUserMedia进行音频录制与处理
描述:公司项目有个语音识别文字的功能,之前已经写过如何实现了(语音识别文字-CSDN博客)。
本次的录制音频是在上次封装的代码上面做了一些修改优化,封装后的代码简单明了,更加入手。
技术:
- 利用API:navigator.mediaDevices.getUserMedia()是一种用于访问流媒体设备(如摄像机或麦克风)的API,它是Navigator.mediaDevices API的一部分。它允许网页访问摄像头和/或麦克风,并捕获实时流视频/音频。这是一个采用异步的方式,通过返回一个Promise来提供访问流媒体设备的权限。如果用户允许,API将返回一个MediaStream对象,该对象可以进一步用于捕获视频和音频
想看API文档的可点击MediaDevices.getUserMedia() - Web API 接口参考 | MDN (mozilla.org) window.webkitAudioContext 各浏览器不同,存在兼容性。
注意:调用本地麦克风录音或者相机拍摄功能,由于权限问题使用只能在本地运行,线上需用 https 域名才可以使用。
代码实现:
封装核心代码
function HZRecorder(stream, config) { config = config || { } config.sampleBits = config.sampleBits || 16 // 采样数位 8, 16 config.sampleRate = config.sampleRate || 16000 // 采样率16khz const context = new (window.webkitAudioContext || window.AudioContext)() const audioInput = context.createMediaStreamSource(stream) const createScript = context.createScriptProcessor || context.createJavaScriptNode const recorder = createScript.apply(context, [4096, 1, 1]) const audioData = { size: 0, // 录音文件长度 buffer: [], // 录音缓存 inputSampleRate: context.sampleRate, // 输入采样率 inputSampleBits: 16, // 输入采样数位 8, 16 outputSampleRate: config.sampleRate, // 输出采样率 oututSampleBits: config.sampleBits, // 输出采样数位 8, 16 input: function (data) { this.buffer.push(new Float32Array(data)) this.size += data.length }, compress: function () { // 合并压缩 // 合并 const data = new Float32Array(this.size) let offset = 0 for (let i = 0; i < this.buffer.length; i++) { data.set(this.buffer[i], offset) offset += this.buffer[i].length } // 压缩 const compression = parseInt(this.inputSampleRate / this.outputSampleRate) const length = data.length / compression const result = new Float32Array(length) // eslint-disable-next-line one-var let index = 0, j = 0 while (index < length) { result[index] = data[j] j += compression index++ } return result }, encodeWAV: function () { const sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate) const sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits) const bytes = this.compress() const dataLength = bytes.length * (sampleBits / 8) const buffer = new ArrayBuffer(44 + dataLength) const data = new DataView(buffer) const channelCount = 1// 单声道 let offset = 0 const writeString = function (str) { for (let i = 0; i < str.length; i++) { data.setUint8(offset + i, str.charCodeAt(i)) } } // 资源交换文件标识符 writeString('RIFF') offset += 4 // 下个地址开始到文件尾总字节数,即文件大小-8 data.setUint32(offset, 36 + dataLength, true) offset += 4 // WAV文件标志 writeString('WAVE') offset += 4 // 波形格式标志 writeString('fmt ') offset += 4 // 过滤字节,一般为 0x10 = 16 data.setUint32(offset, 16, true) offset += 4 // 格式类别 (PCM形式采样数据) data.setUint16(offset, 1, true) offset += 2 // 通道数 data.setUint16(offset, channelCount, true) offset += 2 // 采样率,每秒样本数,表示每个通道的播放速度 data.setUint32(offset, sampleRate, true) offset += 4 // 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8 data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true) offset += 4 // 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8 data.setUint16(offset, channelCount * (sampleBits / 8), true) offset += 2 // 每样本数据位数 data.setUint16(offset, sampleBits, true) offset += 2 // 数据标识符 writeString('data') offset += 4 // 采样数据总数,即数据总大小-44 data.setUint32(offset, dataLength, true) offset += 4 // 写入采样数据 if (sampleBits === 8) { for (let i = 0; i < bytes.length; i++, offset++) { const s = Math.max(-1, Math.min(1, bytes[i])) let val = s < 0 ? s * 0x8000 : s * 0x7FFF val = parseInt(255 / (65535 / (val + 32768))) data.setInt8(offset, val, true) } } else { for (let i = 0; i < bytes.length; i++, offset += 2) { const s = Math.max(-1, Math.min(1, bytes[i])) data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true) } } return new Blob([data], { type: 'audio/wav' }) } } // 开始录音 this.start = function () { audioInput.connect(recorder) recorder.connect(context.destination) } // 停止 this.stop = function () { recorder.disconnect() } // 重新设置音段 this.inItsegment = function () { audioData.size = 0 audioData.buffer = [] } // 获取音频文件 this.getBlob = function () { this.stop() //录音停止 this.closeMike() //关闭麦克风 return audioData.encodeWAV() } // 回放 this.play = function (audio) { const blob = this.getBlob() // saveAs(blob, "F:/3.wav"); // window.open(window.URL.createObjectURL(this.getBlob())) audio.src = window.URL.createObjectURL(this.getBlob()) } // 上传 this.upload = function () { return this.getBlob() } // 音频采集 recorder.onaudioprocess = function (e) { audioData.input(e.inputBuffer.getChannelData(0)) // record(e.inputBuffer.getChannelData(0)); } // 关闭麦克风 this.closeMike = function () { stream.getTracks().forEach(track => { track.stop() } ) } return this}export { HZRecorder}
具体使用:
不受框架限制,都可以使用。下面写个案例,使用的是vue2版本。
下面函数执行顺序:initMedia()——>getUserMedia ()——>audioCHangeWord()
语音录制设置了60秒定时,功能都在,配合你的页面逻辑就能实现了。
methods:{ // 初始化navigator initMedia(){ if (navigator.mediaDevices.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia) { this.getUserMedia({ video: false, audio: true }) // 调用用户媒体设备,访问摄像头、录 音 } else { console.log('你的浏览器不支持访问用户媒体设备') } }, getUserMedia (constrains) { let that = this if (navigator.mediaDevices.getUserMedia) { // 最新标准API navigator.mediaDevices.getUserMedia(constrains).then(stream => { that.success(stream) that.recorder = new HZRecorder(stream) console.log('录音初始化准备完成') this.audioCHangeWord() }).catch(err => { that.error(err) }) } else if (navigator.webkitGetUserMedia) { // webkit内核浏览器 navigator.webkitGetUserMedia(constrains).then(stream => { that.success(stream) that.recorder = new HZRecorder(stream) console.log('录音初始化准备完成') this.audioCHangeWord() }).catch(err => { that.error(err) }) } else if (navigator.mozGetUserMedia) { // Firefox浏览器 navigator.mozGetUserMedia(constrains).then(stream => { that.success(stream) that.recorder = new HZRecorder(stream) console.log('录音初始化准备完成') this.audioCHangeWord() }).catch(err => { that.error(err) }) } else if (navigator.getUserMedia) { // 旧版API navigator.getUserMedia(constrains).then(stream => { that.success(stream) that.recorder = new HZRecorder(stream) console.log('录音初始化准备完成') this.audioCHangeWord() }).catch(err => { that.error(err) }) } }, // 成功的回调函数 success (stream) { console.log('已点击允许,开启成功') }, // 异常的回调函数 error (error) { console.log('访问用户媒体设备失败:', error.name, error.message) }, // 录制语音 audioCHangeWord() { if(this.recordFlag)return //避免重复点击录音 this.AudioMsg=true //打开录音动画 this.recorder.start()//开始录音 // 设置录制一分钟 this.timerAudio = setTimeout(() => { const file = this.recorder.getBlob() this.recorder.inItsegment() this.AudioMsg=false this.recordFlag=false this.recorder=null this.getUploadVoice(file) }, 60000); }, //关闭录制 closeAudioCHangeWord(){ clearTimeout(this.timerAudio) const file = this.recorder.getBlob() this.recorder.inItsegment() this.AudioMsg=false this.recordFlag=false this.recorder=null console.log('录制的音频',file) },}
本文地址:http://cdn.baiduyun.im/video/www.bfzx365.com/video/183c58299234.html
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。