Переглянути джерело

语音识别API组件加入声音检测,检测到说话停顿1秒自动停止

liyanbo 1 місяць тому
батько
коміт
fffba48a55
1 змінених файлів з 74 додано та 0 видалено
  1. 74 0
      src/components/ai/voice/VoiceInput_Api.vue

+ 74 - 0
src/components/ai/voice/VoiceInput_Api.vue

@@ -71,6 +71,10 @@ const scriptProcessor = ref(null) // 脚本处理器
 const audioDataBuffer = ref([]) // 音频数据缓冲区
 const MAX_BUFFER_SIZE = 1024 * 10 // 10KB
 let batchSendTimer = null // 批量发送定时器
+// 声音检测相关变量
+let silenceTimer = null // 静音检测定时器
+const SILENCE_THRESHOLD = 0.01 // 静音阈值
+const SILENCE_DURATION = 1000 // 静音持续时间(毫秒)
 
 // 提取资源释放函数
 const releaseResources = () => {
@@ -104,6 +108,11 @@ const resetRecordingState = () => {
   countdown.value = 0
   emit('recordingStatusChanged', false)
   releaseResources()
+  // 清除静音检测定时器
+  if (silenceTimer) {
+    clearTimeout(silenceTimer)
+    silenceTimer = null
+  }
 }
 
 // 增强错误处理
@@ -285,12 +294,60 @@ const sendAudioData = async (arrayBuffer) => {
     audioDataBuffer.value.push(data[i])
   }
   
+  // 检测声音
+  const hasSound = detectSound(data)
+  
+  // 处理声音检测结果
+  if (hasSound) {
+    // 检测到声音,重置静音定时器
+    resetSilenceTimer()
+  } else {
+    // 检测到静音,启动静音定时器
+    startSilenceTimer()
+  }
+  
   // 如果缓冲区达到阈值,立即发送
   if (audioDataBuffer.value.length >= MAX_BUFFER_SIZE) {
     await sendAudioDataBatch()
   }
 }
 
+// 检测声音
+const detectSound = (data) => {
+  // 计算音频数据的平均幅度
+  let sum = 0
+  for (let i = 0; i < data.length; i++) {
+    sum += Math.abs(data[i])
+  }
+  const average = sum / data.length
+  
+  // 与阈值比较,判断是否有声音
+  return average > SILENCE_THRESHOLD * 32768
+}
+
+// 重置静音定时器
+const resetSilenceTimer = () => {
+  if (silenceTimer) {
+    clearTimeout(silenceTimer)
+    silenceTimer = null
+  }
+  // 重新启动静音定时器
+  startSilenceTimer()
+}
+
+// 启动静音定时器
+const startSilenceTimer = () => {
+  if (!silenceTimer && isRecording.value) {
+    silenceTimer = setTimeout(() => {
+      // 静音时间超过阈值,自动停止录音
+      if (isRecording.value) {
+        resetRecordingState()
+        stopBackendRecognition()
+      }
+    }, SILENCE_DURATION)
+  }
+}
+
 // 动态调整轮询频率
 const adjustPollingInterval = (baseInterval = 300) => {
   if (navigator.connection) {
@@ -447,6 +504,11 @@ const toggleSpeechInput = async () => {
   clearInterval(countdownTimer.value)
   countdownTimer.value = null
   stopResultPolling()
+  // 清除静音检测定时器
+  if (silenceTimer) {
+    clearTimeout(silenceTimer)
+    silenceTimer = null
+  }
 
   if (isRecording.value) {
     // 手动停止时立即重置状态,确保在所有浏览器中波纹都能立即关闭
@@ -598,9 +660,21 @@ onUnmounted(() => {
   stopResultPolling()
   releaseResources()
   
+  // 清除静音检测定时器
+  if (silenceTimer) {
+    clearTimeout(silenceTimer)
+    silenceTimer = null
+  }
+  
   // 通知后端停止识别
   stopBackendRecognition()
 })
+
+// 暴露方法给父组件
+defineExpose({
+  toggleSpeechInput,
+  isRecording
+})
 </script>
 
 <style scoped lang="scss">