<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>我的简单网页</title> <!-- 这里可以添加CSS样式链接,例如:<link rel="stylesheet" href="styles.css"> --> </head> <body> <header> <h1>欢迎来到我的网页!</h1> </header> <main> <p>这是一个简单的语音唤醒页面示例。</p> <p>在页面授权后 可以在任意事件点对着麦克风说话 系统会自动收集你说话声音触发录音 将说话声音生成为audio出现在下方可以看到测试结果</p> </main> <footer> <p>版权所有 © 2023 我的网站</p> </footer> <!-- 这里可以添加JavaScript脚本链接,例如:<script src="script.js"></script> --> </body> <script> class EventManager { constructor() { // 使用一个对象来存储事件和对应的回调函数 this.events = {}; } // 添加事件监听器 on(eventName, callback) { if (!this.events[eventName]) { this.events[eventName] = []; } this.events[eventName].push(callback); } // 移除事件监听器 off(eventName, callback) { if (this.events[eventName]) { const index = this.events[eventName].indexOf(callback); if (index > -1) { this.events[eventName].splice(index, 1); } } } // 触发事件 emit(eventName, ...args) { if (this.events[eventName]) { this.events[eventName].forEach(callback => { callback(...args); }); } } // 检查是否有某个事件 hasEvent(eventName) { return !!this.events[eventName]; } // 移除所有事件监听器 removeAllListeners(eventName) { if (eventName) { delete this.events[eventName]; } else { this.events = {}; } } } class AudioTimer { // MediaRecorder实例 mediaRecorder = null; // 关于音频处理器状态 // 0 未启动 通常是因为没有获取到权限导致的 // 1 麦克风启动中 // 2 在进行语音音频音量处理 对环境噪音进行过滤 // 3 在请求服务器进行文字识别 stateStyle = 0; /** * 创建一个用于存储录制数据的数组 **/ recordedChunks = []; audioContext = new (window.AudioContext || window.webkitAudioContext)(); /** * 事件管理器 * 1. onRms // 声闻超出标准值 触发 */ $eventManager = new EventManager(); /** * 零界点 进入录音保存模式 */ isRmsUp = false; /** * 关于RMS值的临界值 */ RMSVALUE = 0.05; /** * 构造函数 */ constructor() { // 获取权限 this.getPermissions().then((stream) => { // 获取权限后才能正常运行 // 创建一个MediaRecorder实例来录制音频 this.createMediaRecorder(stream); // 开启麦克风定时启动监听音频流 this.audioTimer(); }); } on(name, call) { // 绑定事件 return this.$eventManager.on(name, call); } /** * 关于麦克风的循环监听流处理 * 考虑到性能并发问题 采用流式队列处理器 */ audioTimer() { this.audioPeriodicity().then(() => { this.audioTimer(); }); } stop() { console.log("关闭音频监控") this.mediaRecorder.stop(2000); } start() { console.log("开启音频监控") this.mediaRecorder.start(2000); } /** * 启动一段音频处理周期 */ audioPeriodicity() { return new Promise((resovle, err) => { // 开启一个监听 // 开始录制(可以指定选项,例如{ mimeType: 'audio/webm; codecs=opus' }) // 监听dataavailable事件以收集录制的数据 this.mediaRecorder.ondataavailable = (event) => { if (event.data.size > 0 && event.data.size > 10000) { // console.log("ondataavailableEvent", event); console.log("ondataavailable", this.recordedChunks.length); // 将阶段音频存入缓存中 this.addRecordedChunks(event.data); // 处理一下缓存数据的RMS值 this.handleAudioRms(event.data).then((Rms) => { console.log(Rms) // 判断RMS值是否达到一定的零界点 如果达到了 就判断为是为近距离说话 if (Rms > this.RMSVALUE) { // 如果是出发了零界点 则开启录制模式 this.isRmsUp = true; } else if (Rms < this.RMSVALUE) { if (this.isRmsUp) { // 如果没有达到临界值 但是 又是录音状态则挂不必录音状态 this.isRmsUp = false; // 然后将保存后的录音缓存开启并进行返回 this.$eventManager.emit("onRms", this.recordedChunks.concat()); } // 清除掉监听事件 this.mediaRecorder.ondataavailable = function () { }; // 然后清空缓存 this.clearRecordedChunks(); // 停止掉此次的监听 this.stop(); resovle(); } }).catch(() => { console.log("音频RMS计算出现了未知错误"); // 清除掉监听事件 this.mediaRecorder.ondataavailable = null; // 停止掉此次的监听 this.stop(); // 然后清空缓存 this.clearRecordedChunks(); resovle(); }) } else { console.log("因为音频size小于10000字节判断为关闭stop残留数据 执行清除"); // 清除掉监听事件 // this.mediaRecorder.ondataavailable = null; // 停止掉此次的监听 // this.stop(); // 然后清空缓存 // this.clearRecordedChunks(); // resovle(); } }; this.start(2000); // setTimeout(() => { // // 停止录制(例如,在一段时间后或用户点击按钮时) // this.mediaRecorder.stop(); // // 处理一下数据的RMS值 // this.handleAudioRms().then((Rms) => { // console.log(Rms) // if (Rms > 0.01) { // this.$eventManager.emit("onRms", Rms); // } // }).finally(() => { // resovle(); // }); // }, 3000); // 10秒后停止录制 }) } /** * 对音频流进行RMS判断 * RMS是一种对一段音频进行峰值判断的计算方法 * 当RMS达到一定值 我则认为是有效语音 不然则认为是环境音不做识别 */ async handleAudioRms(chunks) { console.log("处理音频RMS", this.recordedChunks.map((e) => e.size)); // 创建一个Blob对象,它包含所有录制的数据块 this.recordedChunks const blob = new Blob(this.recordedChunks, { 'type': 'audio/ogg; codecs=opus' }); // 解码音频 Blob const audioBuffer = await this.audioContext.decodeAudioData(await blob.arrayBuffer()); let rmsSum = 0; let numSamples = 0; // 遍历所有通道和样本 for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) { console.log("处理音频RMS最终数据Size", audioBuffer.numberOfChannels); let data = audioBuffer.getChannelData(audioBuffer.numberOfChannels - 1); data = data.slice(data.length - data.length / this.recordedChunks.length, data.length); for (let i = 0; i < data.length; i++) { const sample = data[i]; rmsSum += sample * sample; // 计算平方和 numSamples++; } } // 计算 RMS const rms = Math.sqrt(rmsSum / numSamples); return rms; } /** * 添加音频buffer数据缓存 * 缓存会保留10秒内的数据 */ addRecordedChunks(arrayBuffer) { const index = this.recordedChunks.push(arrayBuffer); // if (index > 1) { // this.recordedChunks.splice(0, 1); // } } // 清除音频缓存 clearRecordedChunks() { this.recordedChunks = []; } /** * 创建一个MediaRecorder实例来录制音频 */ createMediaRecorder(stream) { // 创建一个MediaRecorder实例来录制音频 const mediaRecorder = new MediaRecorder(stream); this.mediaRecorder = mediaRecorder; // 监听dataavailable事件以收集录制的数据 // mediaRecorder.ondataavailable = (event) => { // if (event.data.size > 0) { // console.log("ondataavailable", this.recordedChunks.length); // this.addRecordedChunks(event.data); // } // }; // 监听stop事件以处理录制完成 // mediaRecorder.onstop = function () { // // 创建一个Blob对象,它包含所有录制的数据块 // const blob = new Blob(recordedChunks, { 'type': 'audio/ogg; codecs=opus' }); // // 创建一个指向Blob的URL,该URL可用于在浏览器中播放音频 // // const audioURL = URL.createObjectURL(blob); // // 你可以在这里使用audioURL,例如将其设置为audio元素的src属性 // // const audioElement = document.createElement('audio'); // // audioElement.controls = true; // // audioElement.src = audioURL; // // document.body.appendChild(audioElement); // // 添加到音频处理器 // // window.testAudio(recordedChunks); // // var reader = new FileReader() // // reader.onload = function() { // // console.log(this.result) // window.testAudio(blob); // // } // // reader.readAsArrayBuffer(blob) // // 清理资源(可选) // // URL.revokeObjectURL(audioURL); // }; } /** * 初始化麦克风权限 判断是否存在权限 */ getPermissions() { // 请求访问麦克风 return navigator.mediaDevices.getUserMedia({ audio: true }) .catch(function (err) { console.log('无法访问麦克风: ' + err); }); } } const audios = new AudioTimer(); audios.on("onRms", (chunks) => { debugger; console.log("触发语音唤醒输入") // 创建一个Blob对象,它包含所有录制的数据块 const blob = new Blob(chunks, { 'type': 'audio/ogg; codecs=opus' }); // 创建一个指向Blob的URL,该URL可用于在浏览器中播放音频 const audioURL = URL.createObjectURL(blob); const audio = document.getElementsByTagName("audio")[0]; audio && audio.remove(); // 你可以在这里使用audioURL,例如将其设置为audio元素的src属性 const audioElement = document.createElement('audio'); audioElement.controls = true; audioElement.src = audioURL; document.body.appendChild(audioElement); }) </script> </html>
1. 思路大概就是 监听麦克风 然后根据麦克风捕捉到的音频RMS值来判断是否是高音 如果是 则判断为近距离说话 从而进行捕捉截取
后续 我是采用的第三方语音识别来转化为文字 在使用 pinyin库 转化为拼音来匹配识别的。。
标签:const,log,麦克风,data,音频,eventName,语音,console,唤醒 From: https://www.cnblogs.com/liao1992/p/18234626