This commit is contained in:
parent
4d1bea502a
commit
72a10ad32b
2 changed files with 78 additions and 28 deletions
|
|
@ -780,31 +780,56 @@ async function buildAudioPipeline(rawStream) {
|
|||
}
|
||||
}
|
||||
|
||||
// Metering/Speaking detection
|
||||
// Metering/Speaking detection with adaptive threshold and hysteresis.
|
||||
const analyser = ctx.createAnalyser();
|
||||
analyser.fftSize = 512;
|
||||
analyser.fftSize = 1024;
|
||||
processedSource.connect(analyser);
|
||||
|
||||
const dataArray = new Uint8Array(analyser.frequencyBinCount);
|
||||
const timeData = new Float32Array(analyser.fftSize);
|
||||
let localIsSpeaking = false;
|
||||
let speakCounter = 0;
|
||||
let speechFrames = 0;
|
||||
let silenceFrames = 0;
|
||||
let noiseFloor = 0.004;
|
||||
let levelEma = 0;
|
||||
|
||||
const checkVolume = () => {
|
||||
if (!state.voice.audioContext || state.voice.audioContext.state === 'closed') return;
|
||||
analyser.getByteFrequencyData(dataArray);
|
||||
let sum = 0;
|
||||
for (let i = 0; i < dataArray.length; i++) sum += dataArray[i];
|
||||
const avg = sum / dataArray.length;
|
||||
|
||||
const isCurrentlySpeaking = avg > 30; // Calibrated for normal speech
|
||||
analyser.getFloatTimeDomainData(timeData);
|
||||
|
||||
if (isCurrentlySpeaking) {
|
||||
speakCounter = Math.min(speakCounter + 1, 5);
|
||||
} else {
|
||||
speakCounter = Math.max(speakCounter - 1, 0);
|
||||
let sumSquares = 0;
|
||||
let peak = 0;
|
||||
for (let i = 0; i < timeData.length; i++) {
|
||||
const v = timeData[i];
|
||||
sumSquares += v * v;
|
||||
const abs = Math.abs(v);
|
||||
if (abs > peak) peak = abs;
|
||||
}
|
||||
|
||||
const newSpeakingState = speakCounter >= 2;
|
||||
const rms = Math.sqrt(sumSquares / timeData.length);
|
||||
const level = Math.max(rms, peak * 0.5);
|
||||
levelEma = levelEma * 0.75 + level * 0.25;
|
||||
|
||||
if (!localIsSpeaking) {
|
||||
// Learn room noise slowly while idle.
|
||||
noiseFloor = noiseFloor * 0.98 + levelEma * 0.02;
|
||||
} else {
|
||||
// Do not let noise floor jump up while speaking.
|
||||
noiseFloor = Math.min(noiseFloor, levelEma);
|
||||
}
|
||||
|
||||
const startThreshold = Math.max(noiseFloor * 2.2, 0.010);
|
||||
const stopThreshold = Math.max(noiseFloor * 1.5, 0.006);
|
||||
|
||||
if (levelEma > startThreshold) {
|
||||
speechFrames = Math.min(speechFrames + 1, 8);
|
||||
silenceFrames = 0;
|
||||
} else if (levelEma < stopThreshold) {
|
||||
silenceFrames = Math.min(silenceFrames + 1, 8);
|
||||
speechFrames = Math.max(speechFrames - 1, 0);
|
||||
}
|
||||
|
||||
const newSpeakingState = localIsSpeaking ? silenceFrames < 3 : speechFrames >= 2;
|
||||
|
||||
if (newSpeakingState !== localIsSpeaking) {
|
||||
localIsSpeaking = newSpeakingState;
|
||||
|
|
@ -821,7 +846,7 @@ async function buildAudioPipeline(rawStream) {
|
|||
myVideo.parentElement.classList.toggle('speaking', localIsSpeaking);
|
||||
}
|
||||
}
|
||||
setTimeout(checkVolume, 100);
|
||||
setTimeout(checkVolume, 60);
|
||||
};
|
||||
checkVolume();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue