近期开发一些应用,需要用到语音唤醒,做了一些简单的测试,代码基于React。
1,简单的语音操作
代码如下:
// 新增:音量获取函数
const getVolume = (stream: MediaStream) => {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const analyser = audioContext.createAnalyser();
const source = audioContext.createMediaStreamSource(stream);
source.connect(analyser);
analyser.fftSize = ;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
const updateVolume = () => {
analyser.getByteFrequencyData(dataArray);
let sum = 0;
for (let i = 0; i < bufferLength; i++) {
sum += dataArray[i];
}
const average = sum / bufferLength;
setVolume(average);
requestAnimationFrame(updateVolume);
// 逻辑:如果音量大于,则进行处理;
// 逻辑:如果一段时间(1秒)内音量小于,则结束处理;
if (average > ) {
//开始声音处理
}
if (average < ) {
setTimeout(() => {
if (average < ) {
//结束声音处理
}
}, );
}
};
updateVolume();
};
useEffect(() => {
navigator.mediaDevices.getUserMedia({ audio: true })
.then((stream) => {
getVolume(stream); // 开始监听音量
})
.catch((err) => {
console.error(Error accessing microphone: , err);
});
}, []);
2, 增加防抖及过零点检测等机制
直接用音量处理发现无法区分语音与噪音,于是测试了平滑、频域限制、过零点检测等方式,发现过零点检测结合防抖机制效果稍好点。代码(React)如下:
let intLowround: int = 0; //无声时间轮数
const minEnergyThreshold = ; // 能量阈值
const minZCRThreshold = ; // 过零率阈值
// 新增:音量获取函数
const getVolume = debounce((stream: MediaStream) => {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const analyser = audioContext.createAnalyser();
const source = audioContext.createMediaStreamSource(stream);
source.connect(analyser);
analyser.fftSize = ;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
const updateVolume = () => {
analyser.getByteFrequencyData(dataArray);
// 计算音频的能量(简单求和)
let energy = 0;
for (let i = 0; i < bufferLength; i++) {
energy += dataArray[i] ** 2;
}
const normalizedEnergy = Math.sqrt(energy / bufferLength);
// 计算过零率(Zero Crossing Rate)
let zeroCrossings = 0;
for (let i = 1; i < bufferLength; i++) {
if ((dataArray[i - 1] > && dataArray[i] < ) || (dataArray[i - 1] < && dataArray[i] > )) {
zeroCrossings++;
}
}
const zcr = zeroCrossings / bufferLength;
// 判断是否是语音
const isVoice = normalizedEnergy > minEnergyThreshold && zcr > minZCRThreshold;
if (isVoice) {
setVolume(normalizedEnergy);
} else {
setVolume(0); // 如果没有语音,设置音量为0
}
requestAnimationFrame(updateVolume);
// 新增逻辑:如果音量大于startVolume,则调用语音处理逻辑
if (!isRecording && !isProcessing && isVoice && normalizedEnergy>) {
//语音处理代码
intLowround = 0;
}
// 新增逻辑:如果一段时间(秒)内音量小于,则结束语音处理
if (isRecording && !isProcessing){
if(!isVoice && normalizedEnergy<) {
if (intLowround >= ) {
//结束语音处理
} else {
intLowround = intLowround + 1;
}
} else {
intLowround = 0
}
}
};
updateVolume();
}, );
useEffect(() => {
navigator.mediaDevices.getUserMedia({ audio: true })
.then((stream) => {
getVolume(stream); // 开始监听音量
})
.catch((err) => {
console.error(Error accessing microphone: , err);
});
}, []);
虽然过零点机制能一定程度上区分语音与噪音,不过效果一般。于是又查了点资料发现这个东西有专门的技术叫VAD(Voice Activity Detection)。
3, VAD效果测试
找了一个基于JavaScript的VAD开源项目:
https://github.com/ricky0123/vad
添加图片注释,不超过 字(可选)
项目说明如下:此软件包旨在提供一个准确、用户友好的声音活动检测器(VAD),可在浏览器中运行。通过使用此软件包,您可以提示用户授予麦克风权限,开始录制音频,将包含语音的音频片段发送到您的服务器进行处理,或者在用户说话时显示特定的动画或指示器。
调用说明:
Voice Activity Detection for Javascript
Voice Activity Detection for Javascript
运行它提供的Demo,发现效果不错,能很准确的区分噪音和语音。而且录制的语音很完整。运行Demo的步骤如下:
npm i@ricky0123/vad-react
git clone https://github.com/ricky0123/vad
cd ./vad/examples/react-bundler
npm run build && npm run start
添加图片注释,不超过 字(可选)