如果有人声并且大于20db,则开始录制。低于20db超过4秒,停止录制
语音实时检测
<template><div class="auto-recorder"><canvas ref="canvas"></canvas><button @click="toggleRecording" :disabled="isRecording">{{ isRecording ? '录音中' : '未录音' }}</button><audio v-if="audioUrl" controls :src="audioUrl"></audio></div>
</template><script>export default {data() {return {isRecording: false, //显示当前录音状态mediaRecorder: null,audioChunks: [], //存储录音数据的数组audioContext: null, //用于处理音频analyser: null, //用于分析音频频谱bufferSize: 2048, //用于设置分析音频的缓冲区大小。threshold: 0, //用于设置录音阈值的属性 dbcanvasContext: null, //用于存储 Canvas 上下文的属性。canvasWidth: 400,canvasHeight: 200,silentTime: 0, //记录静音时间audioUrl: '',silenceInterval: null //计时器ID};},mounted() {this.setupAudio();this.setupCanvas();this.draw();},methods: {async setupAudio() { //异步方法,用于设置音频环境。//使用 navigator.mediaDevices.getUserMedia 方法请求用户媒体设备(麦克风)的权限//返回一个 MediaStream 对象。const stream = await navigator.mediaDevices.getUserMedia({audio: true});//创建一个新的 AudioContext 对象,用于处理音频。this.audioContext = new AudioContext();//创建 AnalyserNode 对象,用于分析音频频谱。this.analyser = this.audioContext.createAnalyser();//创建一个 MediaStreamAudioSourceNode 对象,表示从媒体流中读取数据。const microphone = this.audioContext.createMediaStreamSource(stream);//将媒体流连接到分析器节点。microphone.connect(this.analyser);//设置 FFT(Fast Fourier Transform)大小,用于控制音频数据的分析精度。this.analyser.fftSize = this.bufferSize;},//设置 Canvas 元素的方法setupCanvas() {// 获取组件中引用的 Canvas 元素。const canvas = this.$refs.canvas;// 获取 Canvas 2D 上下文,用于绘制图形。this.canvasContext = canvas.getContext('2d');// 设置 Canvas 宽度。canvas.width = this.canvasWidth;// 设置 Canvas 高度。canvas.height = this.canvasHeight;// 设置画布背景颜色为黑色。this.canvasContext.fillStyle = '#000';// 绘制一个填充矩形,覆盖整个画布,用黑色填充。this.canvasContext.fillRect(0, 0, this.canvasWidth, this.canvasHeight);},//这是一个负责绘制音频波形图的函数,并且会检查声音的分贝是否超过阈值。draw() {//请求下一帧动画requestAnimationFrame(this.draw);if (this.analyser != null) { //当前不在录音的情况// 获取音频频域数据的数组长度const bufferLength = this.analyser.frequencyBinCount;// 创建一个无符号 8 位整数数组,用于存储音频频域数据const dataArray = new Uint8Array(bufferLength);// 从 AnalyserNode 获取音频频域数据this.analyser.getByteTimeDomainData(dataArray);// 设置画布的填充颜色为黑色this.canvasContext.fillStyle = '#000';// 绘制一个黑色的矩形,覆盖整个画布,用于清空画布this.canvasContext.fillRect(0, 0, this.canvasWidth, this.canvasHeight);// 设置绘制线条的宽度为 2 像素this.canvasContext.lineWidth = 2;// 设置绘制线条的颜色为绿色this.canvasContext.strokeStyle = '#00ff00';// 开始绘制新路径this.canvasContext.beginPath();// 计算每个数据片段在画布上的宽度const sliceWidth = this.canvasWidth * 1.0 / bufferLength;// 初始化 x 坐标let x = 0;// 遍历音频频域数据数组,绘制波形图for (let i = 0; i < bufferLength; i++) {// 将音频数据归一化到 [-1, 1] 的范围const v = dataArray[i] / 128.0;// 根据归一化的数据计算波形图在画布上的 y 坐标const y = v * this.canvasHeight / 2;// 如果是第一个数据点,则将画笔移动到该点;否则,从上一个点绘制直线到当前点if (i === 0) {this.canvasContext.moveTo(x, y);} else {this.canvasContext.lineTo(x, y);}// 更新 x 坐标x += sliceWidth;}// 绘制直线到画布的右边中央this.canvasContext.lineTo(this.canvasWidth, this.canvasHeight / 2);// 绘制路径this.canvasContext.stroke();// 检测声音分贝是否超过阈值const average = this.calculateAverage(dataArray);// 如果平均值超过阈值if (average > this.threshold) {if (!this.isRecording) {this.startRecording(); //开始录音}this.silentTime = 0; //重置静音时间} else {if (this.isRecording) {// 开始计时,如果静音时间超过4秒,停止录音if (!this.silenceInterval) {this.silenceInterval = setInterval(() => {this.silentTime++;if (this.silentTime >= 4) {this.stopRecording();clearInterval(this.silenceInterval);this.silenceInterval = null;this.silentTime = 0; //重置静音时间}}, 1000);}}}}},// 定义了一个名为 calculateAverage 的方法,接受一个 dataArray 参数。calculateAverage(dataArray) {// 初始化一个 sum 变量用于存储音频数据的总和。let sum = 0;// 获取 dataArray 的长度并存储在 length 变量中。const length = dataArray.length;// 使用 for 循环遍历 dataArray。// 对于每个元素,减去128并加到 sum 上。// 这是因为音频数据范围在0到255之间,128表示音量的中值。for (let i = 0; i < length; i++) {sum += (dataArray[i] - 128);}// 返回音频数据的平均值。return sum / length;},// 控制音频录制的功能async toggleRecording() {if (!this.isRecording) {try {// 请求用户麦克风的音频输入const stream = await navigator.mediaDevices.getUserMedia({audio: true});// 创建 MediaRecorder 实例,用于录制音频this.mediaRecorder = new MediaRecorder(stream);// 添加事件监听器,当有音频数据可用时,将其存储到 audioChunks 中// 保存数据(数据有效)this.mediaRecorder.addEventListener('dataavailable', event => {if (event.data.size > 0) {this.audioChunks.push(event.data);}});// 开始录制媒体流this.mediaRecorder.start();this.isRecording = true;} catch (error) {console.error('录音失败...', error);}}},async stopRecording() {// 停止录制媒体流。this.mediaRecorder.stop();// 等待 MediaRecorder 停止录制完成await new Promise(resolve => {this.mediaRecorder.addEventListener('stop', resolve);});// 创建一个 Blob 对象,用于存储录制的音频数据const blob = new Blob(this.audioChunks, {type: 'audio/pcm'});console.log('Blob size:', blob.size);if (blob.size > 0) {console.log('Blob contains audio data');} else {console.log('Blob is empty');}this.audioUrl = URL.createObjectURL(blob);console.log("this.audioChunks", this.audioChunks);console.log("已发送给后端!");// 将录音数据发送给后端// 这里需要实现发送录音数据给后端的逻辑console.log('录音数据', blob);// 重置录音状态和录音数据数组this.isRecording = false;this.audioChunks = [];},async startRecording() {this.toggleRecording();console.log('录音中');}}};
</script><style>.auto-recorder {display: flex;flex-direction: column;align-items: center;}canvas {margin-top: 20px;}
</style>