如果有人声并且大于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, //用于设置录音阈值的属性 db
canvasContext: 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>