实时通讯

This commit is contained in:
高保安
2025-12-15 11:46:00 +08:00
parent 7fb4bf94c4
commit 01ea7e15fa
53 changed files with 1755 additions and 135 deletions

View File

@@ -1,90 +1,90 @@
package com.realtime.config;
import com.realtime.service.DoubaoVoiceService;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.BinaryMessage;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.BinaryWebSocketHandler;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
@Slf4j
@Component
public class ClientVoiceHandler extends BinaryWebSocketHandler {
private final DoubaoVoiceService doubaoVoiceService;
public ClientVoiceHandler(DoubaoVoiceService doubaoVoiceService) {
this.doubaoVoiceService = doubaoVoiceService;
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 客户端会话
// 建立与豆包 API 的连接
System.out.println(session);
doubaoVoiceService.connect(session);
}
@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
System.out.println("2");
// 接收客户端的音频数据(如 PCM 片段)
byte[] audioData = message.getPayload().array();
// 预处理:确保格式符合豆包 API 要求(如采样率、位深)
byte[] processedData = preprocessAudio(audioData);
// 发送给豆包 API
doubaoVoiceService.sendAudio(processedData);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
// 关闭与豆包 API 的连接
doubaoVoiceService.close();
}
private byte[] preprocessAudio(byte[] audioData) {
if (audioData == null || audioData.length == 0) {
log.warn("接收到空的音频数据,跳过处理");
return new byte[0]; // 返回空数组,避免后续错误
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(new ByteArrayInputStream(audioData));
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputStream, 1)) {
grabber.start();
// 打印输入音频信息(调试用)
log.debug("输入音频格式:{},采样率:{},声道:{}",
grabber.getFormat(), grabber.getSampleRate(), grabber.getAudioChannels());
recorder.setFormat("s16le");
recorder.setSampleRate(16000);
recorder.setAudioChannels(1);
recorder.start();
Frame frame;
int frameCount = 0;
while ((frame = grabber.grab()) != null) {
recorder.record(frame);
frameCount++;
}
log.debug("成功处理 {} 帧音频", frameCount);
recorder.stop();
grabber.stop();
return outputStream.toByteArray();
} catch (Exception e) {
log.error("音频预处理失败", e);
return new byte[0];
}
}
}
//package com.realtime.config;
//
//import com.realtime.service.DoubaoVoiceService;
//import lombok.extern.slf4j.Slf4j;
//import org.bytedeco.javacv.FFmpegFrameGrabber;
//import org.bytedeco.javacv.FFmpegFrameRecorder;
//import org.bytedeco.javacv.Frame;
//import org.springframework.stereotype.Component;
//import org.springframework.web.socket.BinaryMessage;
//import org.springframework.web.socket.CloseStatus;
//import org.springframework.web.socket.WebSocketSession;
//import org.springframework.web.socket.handler.BinaryWebSocketHandler;
//
//import java.io.ByteArrayInputStream;
//import java.io.ByteArrayOutputStream;
//import java.io.EOFException;
//@Slf4j
//@Component
//public class ClientVoiceHandler extends BinaryWebSocketHandler {
// private final DoubaoVoiceService doubaoVoiceService;
//
// public ClientVoiceHandler(DoubaoVoiceService doubaoVoiceService) {
// this.doubaoVoiceService = doubaoVoiceService;
// }
//
// @Override
// public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// // 客户端会话
// // 建立与豆包 API 的连接
//
// System.out.println(session);
// doubaoVoiceService.connect(session);
// }
//
// @Override
// protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
// System.out.println("2");
// // 接收客户端的音频数据(如 PCM 片段)
// byte[] audioData = message.getPayload().array();
// // 预处理:确保格式符合豆包 API 要求(如采样率、位深)
// byte[] processedData = preprocessAudio(audioData);
// // 发送给豆包 API
// doubaoVoiceService.sendAudio(processedData);
// }
//
// @Override
// public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
// // 关闭与豆包 API 的连接
// doubaoVoiceService.close();
// }
//
// private byte[] preprocessAudio(byte[] audioData) {
// if (audioData == null || audioData.length == 0) {
// log.warn("接收到空的音频数据,跳过处理");
// return new byte[0]; // 返回空数组,避免后续错误
// }
//
// ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
//
// try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(new ByteArrayInputStream(audioData));
// FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputStream, 1)) {
//
// grabber.start();
// // 打印输入音频信息(调试用)
// log.debug("输入音频格式:{},采样率:{},声道:{}",
// grabber.getFormat(), grabber.getSampleRate(), grabber.getAudioChannels());
//
// recorder.setFormat("s16le");
// recorder.setSampleRate(16000);
// recorder.setAudioChannels(1);
// recorder.start();
//
// Frame frame;
// int frameCount = 0;
// while ((frame = grabber.grab()) != null) {
// recorder.record(frame);
// frameCount++;
// }
// log.debug("成功处理 {} 帧音频", frameCount);
//
// recorder.stop();
// grabber.stop();
// return outputStream.toByteArray();
//
// } catch (Exception e) {
// log.error("音频预处理失败", e);
// return new byte[0];
// }
// }
//}