package com.realtime.service; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.realtime.config.DoubaoVoiceConfig; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import okio.ByteString; import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Service; import org.springframework.web.socket.BinaryMessage; import org.springframework.web.socket.WebSocketSession; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @Slf4j @Service public class DoubaoVoiceService { private final DoubaoVoiceConfig config; private final OkHttpClient okHttpClient; private WebSocket doubaoWebSocket; // 与豆包 API 的 WebSocket 连接 public DoubaoVoiceService(DoubaoVoiceConfig config) { this.config = config; this.okHttpClient = new OkHttpClient.Builder() .readTimeout(0, TimeUnit.MILLISECONDS) // 实时流禁用超时 .build(); } /** * 建立与豆包实时语音接口的 WebSocket 连接 * @param clientSession 客户端的 WebSocket 会话(用于将结果返回给客户端) */ public void connect(WebSocketSession clientSession) { Request request = new Request.Builder() .addHeader("X-Api-App-ID",config.getAppId()) .addHeader("X-Api-Access-Key",config.getAccessToken()) .addHeader("X-Api-Resource-Id","volc.speech.dialog").addHeader("X-Api-App-Key",config.getApiKey()) .url("wss://openspeech.bytedance.com/api/v3/realtime/dialogue") .build(); // 连接豆包 API 并处理回调 doubaoWebSocket = okHttpClient.newWebSocket(request, new WebSocketListener() { @Override public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) { // 连接成功:可发送初始化参数(如语音类型、采样率) sendInitParams(webSocket); } @Override public void onMessage(@NotNull WebSocket webSocket, ByteString bytes) { // 接收豆包返回的实时结果(如识别文本、合成语音) try { // 将结果转发给客户端 clientSession.sendMessage(new BinaryMessage(bytes.toByteArray())); } catch (IOException e) { log.error("转发结果到客户端失败", e); } } @Override public void onFailure(WebSocket webSocket, Throwable t, Response response) { System.out.println(t.getMessage()); log.error("豆包 WebSocket 连接失败", t); } }); } /** * 向豆包 API 发送音频数据(实时流) */ public void sendAudio(byte[] audioData) { if (doubaoWebSocket != null) { doubaoWebSocket.send(ByteString.of(audioData)); } } /** * 发送初始化参数(根据豆包 API 要求) */ private void sendInitParams(WebSocket webSocket) { Map params = new HashMap<>(); params.put("action", "init"); params.put("format", "pcm"); // 音频格式 params.put("sample_rate", 16000); // 采样率 params.put("mode", "realtime"); // 实时模式 try { webSocket.send(new ObjectMapper().writeValueAsString(params)); } catch (JsonProcessingException e) { log.error("发送初始化参数失败", e); } } /** * 关闭连接 */ public void close() { if (doubaoWebSocket != null) { doubaoWebSocket.close(1000, "正常关闭"); } } }