diff --git a/vetti-admin/src/main/java/com/vetti/socket/ChatWebSocketHandler.java b/vetti-admin/src/main/java/com/vetti/socket/ChatWebSocketHandler.java index 648aec6..9339518 100644 --- a/vetti-admin/src/main/java/com/vetti/socket/ChatWebSocketHandler.java +++ b/vetti-admin/src/main/java/com/vetti/socket/ChatWebSocketHandler.java @@ -4,61 +4,43 @@ import com.vetti.common.ai.elevenLabs.ElevenLabsClient; import com.vetti.common.ai.gpt.ChatGPTClient; import com.vetti.common.ai.whisper.WhisperClient; import com.vetti.common.config.RuoYiConfig; +import com.vetti.common.utils.spring.SpringUtils; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import javax.sound.sampled.AudioFileFormat; -import javax.sound.sampled.AudioFormat; -import javax.sound.sampled.AudioInputStream; -import javax.sound.sampled.AudioSystem; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; -import java.io.*; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * 语音面试 web处理器 - * */ @Slf4j @ServerEndpoint("/voice-websocket/{clientId}") @Component public class ChatWebSocketHandler { - // 音频参数配置 - 根据实际发送的音频流参数调整 - private static final float SAMPLE_RATE = 16000.0f; // 采样率 - private static final int SAMPLE_SIZE_IN_BITS = 16; // 采样位数 - private static final int CHANNELS = 1; // 声道数 (1=单声道, 2=立体声) - private static final boolean SIGNED = true; // 是否有符号 - private static final boolean BIG_ENDIAN = false; // 字节序 - // 语音文件保存目录 private static final String VOICE_STORAGE_DIR = "/voice_files/"; // 语音结果文件保存目录 private static final String VOICE_STORAGE_RESULT_DIR = "/voice_result_files/"; - @Autowired - private ElevenLabsClient elevenLabsClient; - - @Autowired - private ChatGPTClient chatGPTClient; - - @Autowired - private WhisperClient whisperClient; - public ChatWebSocketHandler() { // 初始化存储目录 - File dir = new File(RuoYiConfig.getProfile()+VOICE_STORAGE_DIR); + File dir = new File(RuoYiConfig.getProfile() + VOICE_STORAGE_DIR); if (!dir.exists()) { dir.mkdirs(); } - File resultDir = new File(RuoYiConfig.getProfile()+VOICE_STORAGE_RESULT_DIR); + File resultDir = new File(RuoYiConfig.getProfile() + VOICE_STORAGE_RESULT_DIR); if (!resultDir.exists()) { resultDir.mkdirs(); } @@ -67,7 +49,7 @@ public class ChatWebSocketHandler { // 连接建立时调用 @OnOpen public void onOpen(Session session, @PathParam("clientId") String clientId) { - log.info("WebSocket 链接已建立:{}",clientId); + log.info("WebSocket 链接已建立:{}", clientId); //创建会话 } @@ -88,37 +70,43 @@ public class ChatWebSocketHandler { // 接收二进制消息(流数据) @OnMessage public void onBinaryMessage(Session session, @PathParam("clientId") String clientId, ByteBuffer byteBuffer) { - try{ - log.info("客户端ID为:{}",clientId); + try { + log.info("客户端ID为:{}", clientId); // 处理二进制流数据 - byte[] data = new byte[byteBuffer.remaining()]; + byte[] bytes = new byte[byteBuffer.remaining()]; + //从缓冲区中读取数据并存储到指定的字节数组中 + byteBuffer.get(bytes); // 生成唯一文件名 - String fileName = clientId + "_" + System.currentTimeMillis() + ".mp3"; + String fileName = clientId + "_" + System.currentTimeMillis() + ".webm"; String pathUrl = RuoYiConfig.getProfile()+VOICE_STORAGE_DIR + fileName; - log.info("文件路径为:{}",pathUrl); - saveAsWebM(data,pathUrl); -// //拿到文件进行文字转换 -// String resultText = whisperClient.handleVoiceToText(pathUrl); -// //把提问的文字发送给CPT -// String resultMsg = chatGPTClient.handleAiChat(resultText); -// //把结果文字转成语音文件 -// //生成文件 -// // 生成唯一文件名 -// String resultFileName = clientId + "_" + System.currentTimeMillis() + ".webm"; -// String resultPathUrl = RuoYiConfig.getProfile()+VOICE_STORAGE_DIR + resultFileName; -// elevenLabsClient.handleTextToVoice(resultMsg,resultPathUrl); +// String pathUrl = "/Users/wangxiangshun/Desktop/0.8733346782733291.webm"; + log.info("文件路径为:{}", pathUrl); + saveAsWebM(bytes, pathUrl); + //拿到文件进行文字转换 + WhisperClient whisperClient = SpringUtils.getBean(WhisperClient.class); + String resultText = whisperClient.handleVoiceToText(pathUrl); + //把提问的文字发送给CPT + ChatGPTClient chatGPTClient = SpringUtils.getBean(ChatGPTClient.class); + String resultMsg = chatGPTClient.handleAiChat(resultText); + //把结果文字转成语音文件 + //生成文件 + // 生成唯一文件名 + String resultFileName = clientId + "_" + System.currentTimeMillis() + ".webm"; + String resultPathUrl = RuoYiConfig.getProfile() + VOICE_STORAGE_RESULT_DIR + resultFileName; + ElevenLabsClient elevenLabsClient = SpringUtils.getBean(ElevenLabsClient.class); + elevenLabsClient.handleTextToVoice(resultMsg, resultPathUrl); //把语音文件转换成流,发送给前端 - System.out.println("接收到二进制数据,长度: " + data.length + " bytes"); + System.out.println("接收到二进制数据,长度: " + bytes.length + " bytes"); try { //文件转换成文件流 -// ByteBuffer outByteBuffer = convertFileToByteBuffer(resultPathUrl); -// session.getBasicRemote().sendBinary(outByteBuffer); + ByteBuffer outByteBuffer = convertFileToByteBuffer(resultPathUrl); + session.getBasicRemote().sendBinary(outByteBuffer); // 发送响应确认 - session.getBasicRemote().sendText("已收到二进制数据,长度: " + data.length); + session.getBasicRemote().sendText("已收到二进制数据,长度: " + bytes.length); } catch (IOException e) { e.printStackTrace(); } - }catch (Exception e){ + } catch (Exception e) { e.printStackTrace(); } } @@ -138,6 +126,7 @@ public class ChatWebSocketHandler { /** * 将字节数组保存为WebM文件 + * * @param byteData 包含WebM数据的字节数组 * @param filePath 目标文件路径 * @return 操作是否成功 @@ -181,43 +170,13 @@ public class ChatWebSocketHandler { return false; } - /** - * 将接收的音频数据保存为WAV文件 - */ - private void saveAudioToFile(byte[] audioData,String pathUrl) { - if (audioData == null || audioData.length == 0) { - System.out.println("没有接收到音频数据,不保存文件"); - return; - } - try { - // 合并所有音频数据 - // 创建音频格式 - AudioFormat format = new AudioFormat( - SAMPLE_RATE, - SAMPLE_SIZE_IN_BITS, - CHANNELS, - SIGNED, - BIG_ENDIAN - ); - // 创建音频输入流 - ByteArrayInputStream bais = new ByteArrayInputStream(audioData); - AudioInputStream ais = new AudioInputStream(bais, format, audioData.length / format.getFrameSize()); - // 保存为WAV文件 - File outputFile = new File(pathUrl); - AudioSystem.write(ais, AudioFileFormat.Type.WAVE, outputFile); - System.out.println("音频文件保存成功: " + outputFile.getAbsolutePath()); - } catch (IOException e) { - System.err.println("保存音频文件失败: " + e.getMessage()); - e.printStackTrace(); - } - } - /** * File 转换成 ByteBuffer + * * @param fileUrl 文件路径 * @return */ - private ByteBuffer convertFileToByteBuffer(String fileUrl){ + private ByteBuffer convertFileToByteBuffer(String fileUrl) { File file = new File(fileUrl); // 使用RandomAccessFile获取FileChannel try (RandomAccessFile raf = new RandomAccessFile(file, "r"); @@ -229,7 +188,7 @@ public class ChatWebSocketHandler { // 切换为读模式 buffer.flip(); return buffer; - }catch (Exception e){ + } catch (Exception e) { e.printStackTrace(); } return null; diff --git a/vetti-admin/src/main/resources/application-druid.yml b/vetti-admin/src/main/resources/application-druid.yml index 71993ad..778d18f 100644 --- a/vetti-admin/src/main/resources/application-druid.yml +++ b/vetti-admin/src/main/resources/application-druid.yml @@ -152,7 +152,7 @@ whisper: apiUrl: https://api.openai.com/v1/audio/transcriptions model: whisper-1 apiKey: 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 - language: zh + language: en # AI 聊天 chatGpt: diff --git a/vetti-admin/target/classes/application-druid.yml b/vetti-admin/target/classes/application-druid.yml index 71993ad..778d18f 100644 --- a/vetti-admin/target/classes/application-druid.yml +++ b/vetti-admin/target/classes/application-druid.yml @@ -152,7 +152,7 @@ whisper: apiUrl: https://api.openai.com/v1/audio/transcriptions model: whisper-1 apiKey: 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 - language: zh + language: en # AI 聊天 chatGpt: