Browse Source

Merge branch 'master' of http://59.110.91.129:3000/zhangmengying/AIClass into wanzi

丸子 3 tháng trước cách đây
mục cha
commit
7d36db6e04
2 tập tin đã thay đổi với 20 bổ sung104 xóa
  1. 17 100
      src/api/tts/useAudioPlayer.js
  2. 3 4
      src/components/ai/vision/VisionThink.vue

+ 17 - 100
src/api/tts/useAudioPlayer.js

@@ -3,59 +3,20 @@ export function useAudioPlayer() {
     let audioQueue = [];
     let isPlaying = false;
     let currentTime = 0; // 当前播放时间(用于连续播放)
-    const TARGET_SAMPLE_RATE = 16000; // 目标采样率(匹配后端)
+    const SAMPLE_RATE = 16000; // 匹配后端采样率
     const CHANNELS = 1; // 单声道
     const BIT_DEPTH = 16; // 16位深
-    const FADE_DURATION = 0.01; // 淡入淡出持续时间(秒)
-    let actualSampleRate = TARGET_SAMPLE_RATE; // 实际使用的采样率
 
     // 初始化AudioContext
     const initAudioContext = () => {
         if (!audioContext) {
-            try {
-                // 尝试创建指定采样率的AudioContext
-                audioContext = new (window.AudioContext || window.webkitAudioContext)({
-                    sampleRate: TARGET_SAMPLE_RATE
-                });
-            } catch (e) {
-                // 如果浏览器不支持指定的采样率,使用默认采样率
-                console.warn(`浏览器不支持${TARGET_SAMPLE_RATE}Hz采样率,使用默认采样率:`, e);
-                audioContext = new (window.AudioContext || window.webkitAudioContext)();
-            }
-            actualSampleRate = audioContext.sampleRate;
-            console.log(`使用的音频采样率: ${actualSampleRate}Hz`);
+            audioContext = new (window.AudioContext || window.webkitAudioContext)({
+                sampleRate: SAMPLE_RATE
+            });
             currentTime = 0; // 重置播放时间
         }
     };
 
-    // 添加淡入淡出效果,减少音频块过渡时的突变噪声
-    const applyFadeInOut = (audioBuffer) => {
-        const channelData = audioBuffer.getChannelData(0);
-        const fadeSamples = Math.floor(FADE_DURATION * audioBuffer.sampleRate);
-
-        // 淡入效果
-        for (let i = 0; i < fadeSamples && i < channelData.length; i++) {
-            channelData[i] *= (i / fadeSamples);
-        }
-
-        // 淡出效果
-        for (let i = channelData.length - fadeSamples; i < channelData.length; i++) {
-            if (i >= 0) {
-                channelData[i] *= ((channelData.length - i) / fadeSamples);
-            }
-        }
-    };
-
-    // 创建低通滤波器,减少高频噪声
-    const createLowPassFilter = () => {
-        const filter = audioContext.createBiquadFilter();
-        filter.type = 'lowpass';
-        filter.frequency.value = 4000; // 4kHz截止频率
-        filter.Q.value = 0.707; // 巴特沃斯滤波器
-        filter.connect(audioContext.destination);
-        return filter;
-    };
-
     // 播放音频块(支持流式PCM)
     const playAudioChunk = async (base64Audio) => {
         initAudioContext();
@@ -84,96 +45,52 @@ export function useAudioPlayer() {
             if (currentTime === 0) {
                 // 解码完整WAV文件(仅首次)
                 const audioBuffer = await audioContext.decodeAudioData(audioData.buffer);
-                applyFadeInOut(audioBuffer); // 添加淡入淡出
                 playBuffer(audioBuffer);
                 currentTime += audioBuffer.duration; // 更新播放时间
             }
             // 2. 处理后续PCM分片(无文件头)
             else {
-                // 确保数据是16位PCM格式
-                if (audioData.length % 2 !== 0) {
-                    // 如果数据长度不是偶数,可能存在问题,丢弃最后一个字节
-                    audioData = audioData.slice(0, audioData.length - 1);
-                }
-
                 // 将16位PCM字节转换为Float32Array(AudioContext要求格式)
                 const float32Data = convertPCMToFloat32(audioData);
-
-                // 如果实际采样率与目标采样率不一致,进行重采样
-                const resampledData = actualSampleRate !== TARGET_SAMPLE_RATE
-                    ? resampleAudio(float32Data, TARGET_SAMPLE_RATE, actualSampleRate)
-                    : float32Data;
-
                 // 创建音频缓冲区
-                const audioBuffer = audioContext.createBuffer(CHANNELS, resampledData.length, actualSampleRate);
-                audioBuffer.copyToChannel(resampledData, 0); // 复制到音频通道
-
-                applyFadeInOut(audioBuffer); // 添加淡入淡出效果
+                const audioBuffer = audioContext.createBuffer(CHANNELS, float32Data.length, SAMPLE_RATE);
+                audioBuffer.copyToChannel(float32Data, 0); // 复制到音频通道
                 playBuffer(audioBuffer);
                 currentTime += audioBuffer.duration; // 更新播放时间
             }
         } catch (error) {
-            console.error("音频处理失败:", error);
+            console.error('音频处理失败:', error);
             isPlaying = false;
         }
     };
 
-    // 重采样音频数据以匹配AudioContext的采样率
-    const resampleAudio = (inputData, inputSampleRate, outputSampleRate) => {
-        const inputLength = inputData.length;
-        const outputLength = Math.floor(inputLength * (outputSampleRate / inputSampleRate));
-        const outputData = new Float32Array(outputLength);
-
-        for (let i = 0; i < outputLength; i++) {
-            const inputIndex = i * (inputSampleRate / outputSampleRate);
-            const index1 = Math.floor(inputIndex);
-            const index2 = Math.min(index1 + 1, inputLength - 1);
-            const fraction = inputIndex - index1;
-
-            // 线性插值
-            outputData[i] = inputData[index1] * (1 - fraction) + inputData[index2] * fraction;
-        }
-
-        return outputData;
-    };
-
     // 播放音频缓冲区并调度下一个分片
     const playBuffer = (audioBuffer) => {
         const source = audioContext.createBufferSource();
         source.buffer = audioBuffer;
-
-        // 添加低通滤波器减少噪声
-        const filter = createLowPassFilter();
-        source.connect(filter);
-
-        // 确保从正确的时间点开始播放,处理可能的微小偏差
-        const scheduledTime = Math.max(currentTime, audioContext.currentTime);
-        source.start(scheduledTime);
-
+        source.connect(audioContext.destination);
+        source.start(currentTime); // 从当前时间点开始播放
         // 播放结束后继续处理队列
         source.onended = processAudioQueue;
     };
 
     // 将16位PCM字节转换为Float32Array([-1.0, 1.0]范围)
     const convertPCMToFloat32 = (bytes) => {
-        // 创建DataView确保正确处理小端字节序
-        const dataView = new DataView(bytes.buffer);
-        const float32Array = new Float32Array(bytes.length / 2); // 16位PCM,每2字节一个样本
-
-        for (let i = 0; i < float32Array.length; i++) {
-            // 使用getInt16方法并指定littleEndian=true来确保正确的字节序
-            const int16 = dataView.getInt16(i * 2, true);
-            float32Array[i] = int16 / 32768; // 16位PCM最大值为32767,除以32768可得到[-1, 1)范围
+        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,只重置播放状态
-            currentTime = 0; // 重置播放时间
+            audioContext.close().then(() => {
+                audioContext = null;
+                currentTime = 0; // 重置播放时间
+            });
         }
         audioQueue = [];
         isPlaying = false;

+ 3 - 4
src/components/ai/vision/VisionThink.vue

@@ -258,7 +258,7 @@ const sendMessage = async() => {
     // 调用子组件的方法清除预览图
     imageUploadRef.value?.clearPreview();
     // 清空上传的图片路径
-    uploadedImage.value = '';
+    uploadedImage.value = null;
 
     // 通过emit事件通知父组件保存进度
     emits('saveProgress', "aiCount", 1)
@@ -271,11 +271,11 @@ const sendMessage = async() => {
     localStorage.setItem(props.cacheDataHistoryKey, JSON.stringify(cacheDataHistoryList.value));
 
     try {
-      
+
       const res = await VisionThink({
         "modelId": modelId.value,
         "prompt":content,
-        "promptImage":[uploadedImage.value]
+        "promptImage":[userMessage.imageUrl]
       });
       console.log("视觉理解结果", res);
 
@@ -318,7 +318,6 @@ const sendMessage = async() => {
     // 如果没有上传图片,显示提示信息
     Message().error('请先上传参考图!', true);
   }
-
 };