Преглед на файлове

1、ai对话语音采集率更改为16000

liyanbo преди 8 месеца
родител
ревизия
ed20a30bff
променени са 1 файла, в които са добавени 91 реда и са изтрити 60 реда
  1. 91 60
      src/components/TTS/useAudioPlayer.js

+ 91 - 60
src/components/TTS/useAudioPlayer.js

@@ -1,72 +1,103 @@
-import { ref } from 'vue';
-
 export function useAudioPlayer() {
-    let audioContext = null;
-    let audioQueue = [];
-    let isPlaying = false;
+  let audioContext = null;
+  let audioQueue = [];
+  let isPlaying = false;
+  let currentTime = 0; // 当前播放时间(用于连续播放)
+  const SAMPLE_RATE = 16000; // 匹配后端采样率
+  const CHANNELS = 1; // 单声道
+  const BIT_DEPTH = 16; // 16位深
 
-    // 初始化AudioContext
-    const initAudioContext = () => {
-        if (!audioContext) {
-            audioContext = new (window.AudioContext || window.webkitAudioContext)({
-                sampleRate: 24000 // 匹配TTS采样率
-            });
-        }
-    };
+  // 初始化AudioContext
+  const initAudioContext = () => {
+    if (!audioContext) {
+      audioContext = new (window.AudioContext || window.webkitAudioContext)({
+        sampleRate: SAMPLE_RATE
+      });
+      currentTime = 0; // 重置播放时间
+    }
+  };
 
-    // 播放音频块
-    const playAudioChunk = async (base64Audio) => {
+  // 播放音频块(支持流式PCM)
+  const playAudioChunk = async (base64Audio) => {
+    initAudioContext();
 
-        // console.log('playAudioChunk=========', base64Audio);
-        initAudioContext();
+    // 解码Base64音频数据
+    const audioBytes = Uint8Array.from(atob(base64Audio), c => c.charCodeAt(0));
+    audioQueue.push(audioBytes);
 
-        // 解码Base64音频数据
-        const audioBytes = Uint8Array.from(atob(base64Audio), c => c.charCodeAt(0));
-        audioQueue.push(audioBytes);
+    if (!isPlaying) {
+      processAudioQueue();
+    }
+  };
 
-        if (!isPlaying) {
-            processAudioQueue();
-        }
-    };
+  // 处理音频队列(核心流式播放逻辑)
+  const processAudioQueue = async () => {
+    if (audioQueue.length === 0) {
+      isPlaying = false;
+      return;
+    }
 
-    // 处理音频队列
-    const processAudioQueue = async () => {
-        if (audioQueue.length === 0) {
-            isPlaying = false;
-            return;
-        }
+    isPlaying = true;
+    const audioData = audioQueue.shift();
 
-        isPlaying = true;
-        const audioData = audioQueue.shift();
+    try {
+      // 1. 处理首个WAV分片(带文件头)
+      if (currentTime === 0) {
+        // 解码完整WAV文件(仅首次)
+        const audioBuffer = await audioContext.decodeAudioData(audioData.buffer);
+        playBuffer(audioBuffer);
+        currentTime += audioBuffer.duration; // 更新播放时间
+      }
+      // 2. 处理后续PCM分片(无文件头)
+      else {
+        // 将16位PCM字节转换为Float32Array(AudioContext要求格式)
+        const float32Data = convertPCMToFloat32(audioData);
+        // 创建音频缓冲区
+        const audioBuffer = audioContext.createBuffer(CHANNELS, float32Data.length, SAMPLE_RATE);
+        audioBuffer.copyToChannel(float32Data, 0); // 复制到音频通道
+        playBuffer(audioBuffer);
+        currentTime += audioBuffer.duration; // 更新播放时间
+      }
+    } catch (error) {
+      console.error('音频处理失败:', error);
+      isPlaying = false;
+    }
+  };
 
-        try {
-            const audioBuffer = await audioContext.decodeAudioData(audioData.buffer);
-            const source = audioContext.createBufferSource();
-            source.buffer = audioBuffer;
-            source.connect(audioContext.destination);
-            source.start(0);
+  // 播放音频缓冲区并调度下一个分片
+  const playBuffer = (audioBuffer) => {
+    const source = audioContext.createBufferSource();
+    source.buffer = audioBuffer;
+    source.connect(audioContext.destination);
+    source.start(currentTime); // 从当前时间点开始播放
+    // 播放结束后继续处理队列
+    source.onended = processAudioQueue;
+  };
 
-            // 播放完成后继续处理队列
-            source.onended = processAudioQueue;
-        } catch (error) {
-            console.error('音频解码失败:', error);
-            isPlaying = false;
-        }
-    };
+  // 将16位PCM字节转换为Float32Array([-1.0, 1.0]范围)
+  const convertPCMToFloat32 = (bytes) => {
+    const int16Array = new Int16Array(bytes.buffer);
+    const float32Array = new Float32Array(int16Array.length);
+    for (let i = 0; i < int16Array.length; i++) {
+      float32Array[i] = int16Array[i] / 32768; // 16位PCM最大值为32767
+    }
+    return float32Array;
+  };
 
-    // 停止播放并清理
-    const stopPlayback = () => {
-        if (audioContext) {
-            audioContext.close().then(() => {
-                audioContext = null;
-            });
-        }
-        audioQueue = [];
-        isPlaying = false;
-    };
+  // 停止播放并清理
+  const stopPlayback = () => {
+    if (audioContext) {
+      audioContext.close().then(() => {
+        audioContext = null;
+        currentTime = 0; // 重置播放时间
+      });
+    }
+    audioQueue = [];
+    isPlaying = false;
+  };
 
-    return {
-        playAudioChunk,
-        stopPlayback
-    };
-}
+  return {
+    playAudioChunk,
+    stopPlayback
+  };
+}