Merge pull request #1086 from xinnan-tech/hot-fix

update:上报聊天音频
This commit is contained in:
欣南科技
2025-05-02 16:29:48 +08:00
committed by GitHub
6 changed files with 71 additions and 24 deletions
@@ -177,5 +177,5 @@ public interface Constant {
/**
* 版本号
*/
public static final String VERSION = "0.3.13";
public static final String VERSION = "0.3.14";
}
@@ -26,6 +26,6 @@ public class AgentChatHistoryReportDTO {
@Schema(description = "聊天内容", example = "你好呀")
@NotBlank
private String content;
@Schema(description = "文件数据(opus编码)", example = "")
private String opusDataBase64;
@Schema(description = "base64编码的opus音频数据", example = "")
private String audioBase64;
}
@@ -1,5 +1,7 @@
package xiaozhi.modules.agent.service.biz.impl;
import java.util.Base64;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -43,12 +45,11 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic
// 1. base64解码report.getOpusDataBase64(),存入ai_agent_chat_audio表
String audioId = null;
if (report.getOpusDataBase64() != null && !report.getOpusDataBase64().isEmpty()) {
if (report.getAudioBase64() != null && !report.getAudioBase64().isEmpty()) {
try {
// TODO: 需要考虑保留什么格式的音频数据,比如是opus还是wave
// byte[] audioData = Base64.getDecoder().decode(report.getOpusDataBase64());
// audioId = agentChatAudioService.saveAudio(audioData);
// log.info("音频数据保存成功,audioId={}", audioId);
byte[] audioData = Base64.getDecoder().decode(report.getAudioBase64());
audioId = agentChatAudioService.saveAudio(audioData);
log.info("音频数据保存成功,audioId={}", audioId);
} catch (Exception e) {
log.error("音频数据保存失败", e);
return false;
+1 -1
View File
@@ -4,7 +4,7 @@ from loguru import logger
from config.config_loader import load_config
from config.settings import check_config_file
SERVER_VERSION = "0.3.13"
SERVER_VERSION = "0.3.14"
def get_module_abbreviation(module_name, module_dict):
@@ -146,24 +146,12 @@ def get_agent_models(
def report(
mac_address: str, session_id: str, chat_type: int, content: str, opus_data
mac_address: str, session_id: str, chat_type: int, content: str, audio
) -> Optional[Dict]:
"""带熔断的业务方法示例"""
if not content or not ManageApiClient._instance:
return None
try:
# 处理opus_data为列表的情况
if isinstance(opus_data, list):
# 将列表中的所有bytes数据合并
combined_data = b"".join(opus_data)
else:
combined_data = opus_data
# 将二进制数据转换为Base64编码的字符串
opus_data_base64 = (
base64.b64encode(combined_data).decode("utf-8") if combined_data else None
)
return ManageApiClient._instance._execute_request(
"POST",
f"/agent/chat-history/report",
@@ -172,7 +160,9 @@ def report(
"sessionId": session_id,
"chatType": chat_type,
"content": content,
"opusDataBase64": opus_data_base64,
"audioBase64": (
base64.b64encode(audio).decode("utf-8") if audio else None
),
},
)
except Exception as e:
@@ -9,6 +9,11 @@ TTS上报功能已集成到ConnectionHandler类中。
具体实现请参考core/connection.py中的相关代码。
"""
import os
import uuid
import wave
import opuslib_next
from config.logger import setup_logging
from config.manage_api_client import report
@@ -26,18 +31,69 @@ def report_tts(conn, type, text, opus_data):
opus_data: opus音频数据
"""
try:
if opus_data:
audio_data = opus_to_wav(opus_data)
else:
audio_data = None
# 执行上报
report(
mac_address=conn.device_id,
session_id=conn.session_id,
chat_type=type,
content=text,
opus_data=opus_data,
audio=audio_data,
)
except Exception as e:
logger.bind(tag=TAG).error(f"TTS上报失败: {e}")
def opus_to_wav(opus_data):
"""将Opus数据转换为WAV格式的字节流
Args:
output_dir: 输出目录(保留参数以保持接口兼容)
opus_data: opus音频数据
Returns:
bytes: WAV格式的音频数据
"""
decoder = opuslib_next.Decoder(16000, 1) # 16kHz, 单声道
pcm_data = []
for opus_packet in opus_data:
try:
pcm_frame = decoder.decode(opus_packet, 960) # 960 samples = 60ms
pcm_data.append(pcm_frame)
except opuslib_next.OpusError as e:
logger.bind(tag=TAG).error(f"Opus解码错误: {e}", exc_info=True)
if not pcm_data:
raise ValueError("没有有效的PCM数据")
# 创建WAV文件头
pcm_data_bytes = b"".join(pcm_data)
num_samples = len(pcm_data_bytes) // 2 # 16-bit samples
# WAV文件头
wav_header = bytearray()
wav_header.extend(b"RIFF") # ChunkID
wav_header.extend((36 + len(pcm_data_bytes)).to_bytes(4, "little")) # ChunkSize
wav_header.extend(b"WAVE") # Format
wav_header.extend(b"fmt ") # Subchunk1ID
wav_header.extend((16).to_bytes(4, "little")) # Subchunk1Size
wav_header.extend((1).to_bytes(2, "little")) # AudioFormat (PCM)
wav_header.extend((1).to_bytes(2, "little")) # NumChannels
wav_header.extend((16000).to_bytes(4, "little")) # SampleRate
wav_header.extend((32000).to_bytes(4, "little")) # ByteRate
wav_header.extend((2).to_bytes(2, "little")) # BlockAlign
wav_header.extend((16).to_bytes(2, "little")) # BitsPerSample
wav_header.extend(b"data") # Subchunk2ID
wav_header.extend(len(pcm_data_bytes).to_bytes(4, "little")) # Subchunk2Size
# 返回完整的WAV数据
return bytes(wav_header) + pcm_data_bytes
def enqueue_tts_report(conn, type, text, opus_data):
if not conn.read_config_from_api:
return