优化后的语音识别:使用navigator.mediaDevices.getUserMedia进行音频录制与处理

2025-06-24 13:11:07 6242

描述:公司项目有个语音识别文字的功能,之前已经写过如何实现了(语音识别文字-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
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

热门标签

全站热门

Docker 安装Postgres和Postgis,制作镜像

关于勇敢的心——华严经这样说是道则进,非道则退,道需要勇敢的心

2025年最新Python机器视觉实战:基于OpenCV和深度学习的多功能工业视觉检测系统(附完整代码)

探索角色扮演游戏的十大经典

前端无痛作为产品UI:MasterGo AI 有助于高效设计和开发

下载采矿游戏 十大必玩采矿游戏排名

【Spring Cloud】全面解析服务容错中间件 Sentinel 持久化两种模式

宇宙机器人获得年度最佳游戏奖 玩家不满 UP主恶搞引发热议

友情链接