Files
real-time/src/main/java/com/realtime/service/DoubaoVoiceService.java
2025-12-01 10:08:41 +08:00

106 lines
3.7 KiB
Java

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<String, Object> 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, "正常关闭");
}
}
}