From: 孙春阳 Date: Fri, 29 Aug 2025 10:14:53 +0000 (+0800) Subject: 添加ai新框架(没有验证,只是编译通过) X-Git-Tag: leiniao_debug_1.0.0_20250922_020716~134 X-Git-Url: http://git.snicker.com.cn/css/static/gitweb.css?a=commitdiff_plain;h=401d117a4d407868d96d7b1f845725e14fc4f3ca;p=mirrors%2FFD230.git 添加ai新框架(没有验证,只是编译通过) 修复编译错误,禁止电信aisdk的编译开关 --- diff --git a/code/sdk/application/wearable/fd230/nativeapp/nativeui/aivoice/src/AiVoiceModel.cpp b/code/sdk/application/wearable/fd230/nativeapp/nativeui/aivoice/src/AiVoiceModel.cpp index 9b3ec27b1..19e457eff 100644 --- a/code/sdk/application/wearable/fd230/nativeapp/nativeui/aivoice/src/AiVoiceModel.cpp +++ b/code/sdk/application/wearable/fd230/nativeapp/nativeui/aivoice/src/AiVoiceModel.cpp @@ -32,7 +32,6 @@ #include #include #include "custom_runtime.h" -#include "cat1_ws.h" #include "voice_call_volte_sample_wrapper.h" #include namespace OHOS diff --git a/code/sdk/application/wearable/fd230/nativeapp/nativeui/aivoice/src/AiVoicePresenter.cpp b/code/sdk/application/wearable/fd230/nativeapp/nativeui/aivoice/src/AiVoicePresenter.cpp index c64d882b6..30908df96 100644 --- a/code/sdk/application/wearable/fd230/nativeapp/nativeui/aivoice/src/AiVoicePresenter.cpp +++ b/code/sdk/application/wearable/fd230/nativeapp/nativeui/aivoice/src/AiVoicePresenter.cpp @@ -28,7 +28,6 @@ #include "NativeRegisterManager.h" #include "aivoice/include/AiVoiceSelectView.h" #include "graphic_service.h" -#include "cat1_ws.h" namespace OHOS { diff --git a/code/sdk/build/config/target_config/brandy/config.py b/code/sdk/build/config/target_config/brandy/config.py index 505a57630..6a084ed30 100644 --- a/code/sdk/build/config/target_config/brandy/config.py +++ b/code/sdk/build/config/target_config/brandy/config.py @@ -366,9 +366,9 @@ target = { # cat1 相关配置 'FT_SINGLE_UART', - 'ENABLE_SLIP', + # 'ENABLE_SLIP', 'SUPPORT_CAT1_SERVICE', - 'SUPPORT_TCPIP', + # 'SUPPORT_TCPIP', 'CONFIG_EFUSE_READ_TO_RAM', 'CUSTOM_UTILS', @@ -395,7 +395,6 @@ target = { 'SUPPORT_CYWEE_SERVICE', 'SUPPORT_BATTERY_SERVICE', 'FD230', - 'SUPPORT_AT_OHOSFWK', # 'SUPPORT_AUTO_OTA_SERVER', # 'CONFIG_DFX_SUPPORT_DIAG_UP_MACHINE=1', #"MEM_CHECK", @@ -439,12 +438,12 @@ target = { "ppg_service", "cywee_service", "batterry_service", - "AIsystem", - "AImain", + # "AIsystem", + # "AImain", # "AIsyscall", - "AIagent", - "AIota", - "AIsdk", + # "AIagent", + # "AIota", + # "AIsdk", "bleservice", "blemoder", "transspecard", @@ -942,12 +941,9 @@ target = { #自定义配置项,选择硬件及功能参数 'defines': [ 'BOARD_N9', - # cat1 相关配置 'FT_SINGLE_UART', - 'ENABLE_SLIP', 'SUPPORT_CAT1_SERVICE', - 'SUPPORT_TCPIP', 'CONFIG_EFUSE_READ_TO_RAM', 'CUSTOM_UTILS', @@ -999,45 +995,49 @@ target = { ], #添加自定义应用 'ram_component': [ - 'audio_at_service', - 'media_at_service', - 'bt_manager_at_service', - "ppg_sensor_pah8009", - "ppgBleService", - "mag_sensor_mmc5603", - "vibrator", - "vibrator_service", - #"-:lwip", - "cat1Service", - "cat1Test", - "zhihui_websocket", - # "aiService", - "audio_manager", - "CustomUIcomponentTool", - "nativeabilityfwk", - "custom_utils", - "custom_utilscpp", - "custom_config", - "rotary_encoder", - "nativelauncher", - "ppg_service", - "cywee_service", - "batterry_service", - "bleservice", - "blemoder", - "transspecard", - "factorytest_service", - # "hid_service", - "producttest", - "ppg_hal", - "mag_service", - "cywee_dml", - "gnss", - "pgnss_sample", - # "gnss_sample", - "cyweeBleService", - "gyro", - ], + 'audio_at_service', + 'media_at_service', + 'bt_manager_at_service', + "ppg_sensor_pah8009", + "ppgBleService", + "mag_sensor_mmc5603", + "vibrator", + "vibrator_service", + #cat1 相关配置 + "cat1Service", + "cat1Test", + #websocket 相关配置 + "zhihui_websocket", + #ai 相关配置 + "aiMediaService", + "aiService", + #音频相关配置 + "audio_manager", + "CustomUIcomponentTool", + "nativeabilityfwk", + "custom_utils", + "custom_utilscpp", + "custom_config", + "rotary_encoder", + "nativelauncher", + "ppg_service", + "cywee_service", + "batterry_service", + "bleservice", + "blemoder", + "transspecard", + "factorytest_service", + # "hid_service", + "producttest", + "ppg_hal", + "mag_service", + "cywee_dml", + "gnss", + "pgnss_sample", + # "gnss_sample", + "cyweeBleService", + "gyro", + ], 'image_analysis': False, 'fs_image_path':'n9', 'ssb_version': 'brandy-native-js-n9-ssb', diff --git a/code/sdk/zhihui/CMakeLists.txt b/code/sdk/zhihui/CMakeLists.txt index 2fed3fe8b..1ad204a7d 100644 --- a/code/sdk/zhihui/CMakeLists.txt +++ b/code/sdk/zhihui/CMakeLists.txt @@ -19,4 +19,5 @@ else() endif() add_subdirectory_if_exist(4gTest) add_subdirectory_if_exist(ai) +add_subdirectory_if_exist(ai/media) add_subdirectory_if_exist(websocket) diff --git a/code/sdk/zhihui/ai/CMakeLists.txt b/code/sdk/zhihui/ai/CMakeLists.txt index e753c4611..c8d974e72 100644 --- a/code/sdk/zhihui/ai/CMakeLists.txt +++ b/code/sdk/zhihui/ai/CMakeLists.txt @@ -8,40 +8,40 @@ set(COMPONENT_NAME "aiService") set(PUBLIC_HEADER ) - set(PRIVATE_HEADER -${CMAKE_CURRENT_SOURCE_DIR}/include -${ROOT_DIR}/zhihui/websocket/include +${ROOT_DIR}/zhihui/ai/include +${ROOT_DIR}/zhihui/4gTest +${ROOT_DIR}/application/wearable/ohos_deps +${ROOT_DIR}/open_source/openharmony/third_party/cJSON +${ROOT_DIR}/open_source/openharmony/foundation/ace/ace_engine_lite/modules/netstack/websocket/include ) set(AI_SOURCES -${CMAKE_CURRENT_SOURCE_DIR}/src/cat1_ws.c -${CMAKE_CURRENT_SOURCE_DIR}/src/cat1_ai_server.c -${CMAKE_CURRENT_SOURCE_DIR}/src/dev_authentication.c +${CMAKE_CURRENT_SOURCE_DIR}/src/ai_service_common.c +${CMAKE_CURRENT_SOURCE_DIR}/src/asr_server_enhanced.c +${CMAKE_CURRENT_SOURCE_DIR}/src/chat_server_enhanced.c +${CMAKE_CURRENT_SOURCE_DIR}/src/tts_server_enhanced.c +${CMAKE_CURRENT_SOURCE_DIR}/src/ai_service_manager.c ) set(SOURCES - ${WEBSOCKET_SOURCES} + ${AI_SOURCES} ) set(COMPONENT_PUBLIC_CCFLAGS ) set(COMPONENT_CCFLAGS - # -std=c++11 - -Wno-error + -std=c99 + -Wall + -Wextra + -Werror + -ffunction-sections + -fdata-sections -Wno-unused-parameter -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables - -Wno-undef - -Wno-bitwise-op-parentheses - -Wno-shift-op-parentheses - -nostdlibinc - -nostdinc - -nostdlib - # -Wno-c++11-narrowing - -Wno-error=unused-function ) set(WHOLE_LINK @@ -53,7 +53,10 @@ set(MAIN_COMPONENT ) set(PUBLIC_DEFINES -SUPPORT_CAT1_SERVICE +AI_SERVICE_VERSION_MAJOR=1 +AI_SERVICE_VERSION_MINOR=0 +AI_SERVICE_VERSION_PATCH=0 +SUPPORT_ZHIHUI_WEBSOCKET ) set(PRIVATE_DEFINES diff --git a/code/sdk/zhihui/ai/README.md b/code/sdk/zhihui/ai/README.md new file mode 100644 index 000000000..da9769b84 --- /dev/null +++ b/code/sdk/zhihui/ai/README.md @@ -0,0 +1,356 @@ +# AI服务架构说明 + +## 概述 + +本项目将原有的`cat1_ai_server.c`拆分为三个独立的AI服务模块,每个模块都使用与`net_websocket.h`相同的WebSocket接口,并具备完整的断开重连机制。 + +## 架构设计 + +### 服务模块 + +1. **ASR Server (asr_server.c/h)** - 语音识别服务 + - 负责建立与ASR服务器的WebSocket连接管理 + - 上传录音数据给服务器 + - 接收语音识别结果 + - 将识别结果发送给Chat服务 + +2. **TTS Server (tts_server.c/h)** - 文本转语音服务 + - 负责建立与TTS服务器的WebSocket连接管理 + - 提供文本上传接口 + - 接收反馈的音频文件流 + - 直接播放音频 + +3. **Chat Server (chat_server.c/h)** - 大模型对话服务 + - 负责建立与Chat大模型服务器的WebSocket连接管理 + - 提供上传文本的接口 + - 接收大模型回复文本 + - 通过TTS服务接口发送给TTS服务器 + +4. **AI Service Common (ai_service_common.c/h)** - 通用基础服务 + - 提供所有AI服务的通用功能 + - WebSocket连接管理 + - 断开重连机制 + - 统计信息收集 + - 错误处理 + +## 文件结构 + +``` +code/sdk/zhihui/ai/ +├── include/ +│ ├── ai_service_common.h # 通用服务头文件 +│ ├── asr_server.h # ASR服务头文件 +│ ├── tts_server.h # TTS服务头文件 +│ └── chat_server.h # Chat服务头文件 +├── src/ +│ ├── ai_service_common.c # 通用服务实现 +│ ├── asr_server.c # ASR服务实现 +│ ├── tts_server.c # TTS服务实现 +│ └── chat_server.c # Chat服务实现 +├── example/ +│ └── ai_services_example.c # 集成示例代码 +└── README.md # 本文档 +``` + +## 核心特性 + +### 1. 统一的WebSocket接口 +- 所有服务都使用`net_websocket.h`中定义的标准WebSocket接口 +- 统一的回调函数机制 +- 一致的错误处理方式 + +### 2. 完整的断开重连机制 +- 自动检测连接状态 +- 可配置的重连间隔和重试次数 +- 连接失败时的优雅降级 +- 心跳机制保持连接活跃 + +### 3. 模块化设计 +- 每个服务独立运行,互不干扰 +- 清晰的接口定义和职责分离 +- 易于扩展和维护 + +### 4. 完善的错误处理 +- 统一的错误码定义 +- 详细的错误日志记录 +- 异常情况的自动恢复 + +### 5. 统计信息收集 +- 连接次数、断开次数、重连次数 +- 发送和接收的数据量统计 +- 服务运行时间统计 +- 错误次数统计 + +## 使用方法 + +### 1. 初始化服务 + +```c +#include "asr_server.h" +#include "tts_server.h" +#include "chat_server.h" + +// 创建服务上下文 +asr_server_context_t asr_context; +tts_server_context_t tts_context; +chat_server_context_t chat_context; + +// 配置ASR服务 +asr_audio_config_t asr_audio_config; +asr_server_create_default_audio_config(&asr_audio_config); + +asr_recognition_config_t asr_recognition_config; +asr_server_create_default_recognition_config(&asr_recognition_config); + +// 初始化ASR服务 +ai_service_error_t ret = asr_server_init(&asr_context, + "ws://asr.example.com/ws", + &asr_audio_config, + &asr_recognition_config); +``` + +### 2. 设置回调函数 + +```c +// 设置ASR回调 +asr_server_set_callbacks(&asr_context, + on_recognition_result, + on_recognition_start, + on_recognition_end, + on_audio_level); + +// 设置TTS回调 +tts_server_set_callbacks(&tts_context, + on_synthesis_start, + on_synthesis_result, + on_synthesis_complete, + on_playback_start, + on_playback_complete, + on_playback_error); + +// 设置Chat回调 +chat_server_set_callbacks(&chat_context, + on_chat_start, + on_chat_response, + on_chat_complete, + on_chat_streaming); +``` + +### 3. 启动服务 + +```c +// 启动所有服务 +asr_server_start(&asr_context); +tts_server_start(&tts_context); +chat_server_start(&chat_context); +``` + +### 4. 使用服务 + +```c +// ASR - 开始语音识别 +asr_server_start_recognition(&asr_context, NULL); + +// ASR - 发送音频数据 +asr_server_send_audio(&asr_context, audio_data, audio_length); + +// ASR - 停止语音识别 +asr_server_stop_recognition(&asr_context); + +// TTS - 合成语音 +tts_server_synthesize_text(&tts_context, "你好,世界!", NULL); + +// Chat - 发送消息 +chat_server_send_message(&chat_context, "今天天气怎么样?", NULL); +``` + +### 5. 服务间协作 + +```c +// 设置Chat到TTS的接口 +ai_service_error_t tts_interface(const char *text, const char *session_id) { + return tts_server_synthesize_text(&tts_context, text, session_id); +} + +chat_server_set_tts_interface(&chat_context, tts_interface); + +// ASR识别结果自动发送到Chat +void on_recognition_result(void *context, const asr_recognition_result_t *result) { + if (result->is_final && result->text) { + chat_server_send_message(&chat_context, result->text, result->session_id); + } +} +``` + +## 配置选项 + +### 通用配置 (ai_service_common.h) + +```c +#define AI_CONNECT_TIMEOUT_MS 30000 // 连接超时时间 +#define AI_RECEIVE_TIMEOUT_MS 10000 // 接收超时时间 +#define AI_RECONNECT_INTERVAL_MS 5000 // 重连间隔 +#define AI_HEARTBEAT_INTERVAL_MS 30000 // 心跳间隔 +#define AI_MAX_RETRY_COUNT 3 // 最大重试次数 +``` + +### ASR配置 + +```c +// 音频配置 +typedef struct { + asr_audio_format_t format; // 音频格式 (PCM/WAV/MP3/AAC) + uint32_t sample_rate; // 采样率 (默认16000) + uint8_t channels; // 声道数 (默认1) + uint8_t bits_per_sample; // 位深度 (默认16) + uint16_t frame_size_ms; // 帧大小 (默认20ms) +} asr_audio_config_t; + +// 识别配置 +typedef struct { + asr_recognition_mode_t mode; // 识别模式 (连续/单次/流式) + asr_language_t language; // 语言 (中文/英文/自动) + bool enable_punctuation; // 启用标点符号 + bool enable_word_timestamp; // 启用词级时间戳 + bool enable_confidence_score; // 启用置信度 + uint32_t max_silence_ms; // 最大静音时间 + uint32_t max_speech_ms; // 最大语音时间 +} asr_recognition_config_t; +``` + +### TTS配置 + +```c +// 音频配置 +typedef struct { + tts_audio_format_t format; // 音频格式 + uint32_t sample_rate; // 采样率 + uint8_t channels; // 声道数 + uint8_t bits_per_sample; // 位深度 + uint32_t bitrate; // 比特率 +} tts_audio_config_t; + +// 语音配置 +typedef struct { + char voice_name[64]; // 语音名称 + tts_voice_type_t voice_type; // 语音类型 (男/女/儿童/老人) + tts_language_t language; // 语言 + tts_emotion_t emotion; // 情感 (中性/高兴/悲伤等) + float speed; // 语速 (0.5-2.0) + float pitch; // 音调 (0.5-2.0) + float volume; // 音量 (0.0-1.0) +} tts_voice_config_t; +``` + +### Chat配置 + +```c +// 模型配置 +typedef struct { + char model_name[64]; // 模型名称 + chat_model_type_t model_type; // 模型类型 (GPT/Claude/LLaMA等) + float temperature; // 温度参数 (0.0-2.0) + float top_p; // Top-p参数 (0.0-1.0) + uint32_t max_tokens; // 最大token数 + bool enable_history; // 启用历史记录 + uint32_t max_history_count; // 最大历史记录数 +} chat_model_config_t; +``` + +## 错误处理 + +### 错误码定义 + +```c +typedef enum { + AI_SERVICE_OK = 0, // 成功 + AI_SERVICE_ERROR_INVALID_PARAM = -1, // 无效参数 + AI_SERVICE_ERROR_MEMORY_ALLOC = -2, // 内存分配失败 + AI_SERVICE_ERROR_NETWORK = -3, // 网络错误 + AI_SERVICE_ERROR_TIMEOUT = -4, // 超时 + AI_SERVICE_ERROR_NOT_CONNECTED = -5, // 未连接 + AI_SERVICE_ERROR_ALREADY_CONNECTED = -6, // 已连接 + AI_SERVICE_ERROR_INTERNAL = -7 // 内部错误 +} ai_service_error_t; +``` + +### 错误处理策略 + +1. **连接错误**: 自动重连机制 +2. **超时错误**: 重新发送请求 +3. **内存错误**: 释放资源并报告错误 +4. **参数错误**: 立即返回错误码 + +## 性能优化 + +### 1. 内存管理 +- 动态内存分配和释放 +- 内存池机制(可选) +- 内存泄漏检测 + +### 2. 网络优化 +- 连接复用 +- 数据压缩 +- 批量发送 + +### 3. 线程管理 +- 合理的线程优先级 +- 线程池机制 +- 避免线程竞争 + +## 调试和监控 + +### 1. 日志系统 +- 分级日志记录 +- 详细的错误信息 +- 性能统计日志 + +### 2. 统计信息 +```c +// 获取服务统计信息 +const ai_service_stats_t* stats = ai_service_get_stats(&context->base); +printf("连接次数: %u\n", stats->connect_count); +printf("发送字节数: %u\n", stats->total_bytes_sent); +printf("接收字节数: %u\n", stats->total_bytes_received); +``` + +### 3. 状态监控 +```c +// 检查服务状态 +ai_service_state_t state = ai_service_get_state(&context->base); +bool connected = ai_service_is_connected(&context->base); +``` + +## 示例代码 + +完整的使用示例请参考 `example/ai_services_example.c` 文件,该文件演示了: + +1. 三个服务的初始化和配置 +2. 服务间的协作机制 +3. 完整的语音对话流程 +4. 错误处理和资源清理 +5. 统计信息的获取和显示 + +## 注意事项 + +1. **线程安全**: 所有接口都是线程安全的 +2. **资源管理**: 使用完毕后必须调用destroy函数释放资源 +3. **错误处理**: 所有函数调用都应检查返回值 +4. **内存管理**: 动态分配的内存需要手动释放 +5. **网络依赖**: 需要稳定的网络连接才能正常工作 + +## 扩展性 + +该架构设计具有良好的扩展性: + +1. **新增服务**: 可以基于`ai_service_common`轻松添加新的AI服务 +2. **协议扩展**: 支持不同的WebSocket子协议 +3. **编解码器**: 可以添加新的音频/文本编解码器 +4. **传输层**: 可以支持其他传输协议(如HTTP/2、gRPC等) + +## 版本历史 + +- v1.0.0: 初始版本,拆分原有cat1_ai_server.c为三个独立服务 +- 支持完整的断开重连机制 +- 统一的WebSocket接口 +- 完善的错误处理和统计功能 \ No newline at end of file diff --git "a/code/sdk/zhihui/ai/doc/AI\350\201\232\345\220\210\345\271\263\345\217\260\346\216\245\345\205\245\346\226\207\346\241\243.md" "b/code/sdk/zhihui/ai/doc/AI\350\201\232\345\220\210\345\271\263\345\217\260\346\216\245\345\205\245\346\226\207\346\241\243.md" new file mode 100644 index 000000000..3b53806a3 --- /dev/null +++ "b/code/sdk/zhihui/ai/doc/AI\350\201\232\345\220\210\345\271\263\345\217\260\346\216\245\345\205\245\346\226\207\346\241\243.md" @@ -0,0 +1,1443 @@ +# AI聚合平台接入文档 + +| 修改日期 | 修改内容 | 修改人 | +| ---------- | -------------------------- | ---------- | +| 2024-12-31 | 编写了文档的架构 | TrillGates | +| 2025-1-2 | 加了用户设备接入相关的接口 | TrillGates | + +# 业务架构 + +暂时无法在飞书文档外展示此内容 + +- https协议通讯,主要是走设备和用户通讯相关的业务 +- wss协议通讯主要是走服务能力的相关的业务 + +# 相关业务流程 + +## 设备激活流程 + +![img](https://zytjtkeb0k.feishu.cn/space/api/box/stream/download/asynccode/?code=N2FmMjQ2NTA0YWU5MWUxYjJmZGI3ZDYwZjZlZWVhZjdfRE1GV2xHME92NlNVR2J1b1MzNXpjWkNrRk9sMEVQZWRfVG9rZW46WTVaVmI2UFhjb1dHOGd4d2dJR2MxSnlJblNnXzE3NTYzNzU2OTI6MTc1NjM3OTI5Ml9WNA) + +# 用户设备接入 + +## BaseUrl + +```Shell +https://app-api.raisound.com +``` + +## 令牌说明 + +- 请求业务时,需要将令牌携带在header上 +- 令牌的key为: + - 用户:bz-ut + - 设备:bz-dt +- **令牌有效期为2个小时,如果过期了,会自动颁发新的令牌,所以要监听header上的bz-ut和bz-dt的内容,如果response不为空,那么就替换本地的令牌。** + +## 接口概览 + +- 设备激活 +- 设备登录 +- 设备令牌校验 +- 设备列表 +- 绑定设备 +- 解绑设备 +- 修改设备名 +- 发送手机验证码 +- 账号注册 +- 账号登录 +- 用户令牌校验 +- 账号退出登录 +- 账号注销 +- 修改账号密码 +- 修改手机号 +- 修改用户昵称 +- 修改用户头像 + +## 设备激活 + +| 接口名称 | 设备激活 | +| -------- | ------------------------------------------------------------ | +| 业务说明 | 当App检查密码时,发现没有密码,这个时候发起激活调用。携带用户的token,如果没有则会静默注册一个用户,并且返回token给App. | +| 请求路径 | /dev/auth/active | +| 请求方法 | PUT | +| 请求参数 | body`{ deviceId:'设备ID', pid:'产品ID', sign:'签名内容', timeStamp:'时间戳', signMethod:'签名方法,HMACSHA1/HMACSHA256/MD5' }`签名内容:deviceId+pid+timeStamp,加密算法的key是productSecret,从后台管理中心的产品页获取(签名是为了防止篡改)MD5签名示例:`# 设备ID:1234567890 # PID:Amg1HJ3dJkX3kLiLPNFg # 时间戳:1717752535000 # 秘钥 productSecret # 签名内容:1234567890Amg1HJ3dJkX3kLiLPNFg1717752535000productSecret # 签名结果:34bf037d9f6b22cf07aeb028e1ea7cc8` | +| 请求结果 | 激活成功`{ success:true, msg:'激活成功', code:20000, data:{ deviceId:'设备ID', password:'设备密码', nonce:'16个字符的随机字符串' } }`密码通讯加密,请参考附录,注意,需要把密码解密出来,保存到一个安全空间。激活失败`{ success:false, msg:'激活失败', code:50025, data:null }`非法设备`{ success:false, msg:'非法设备', code:50026, data:null }`签名错误`{ success:false, msg:'签名错误', code:50019, data:null }` | + +## 设备登录 + +| 接口名称 | 设备登录 | +| -------- | ------------------------------------------------------------ | +| 业务说明 | 如果token(50027)失效的时候 | +| 请求路径 | /dev/auth/sign_in | +| 请求方法 | POST | +| 请求参数 | body参数`{ deviceId:'设备ID', password:'密码', nonce:'16个字符的随机字符串' }`密码通讯加密,请参考附录[#密码通讯加密] | +| 请求结果 | 登录成功`{ success:true, msg:'登录成功', code:20000, data:{ id:'设备ID', deviceName:'设备名称', productName:'产品名称', quotaList:[ { serviceName:'服务名称,比如说TTS/ASR..', platform:'平台,比如说aliyun/spark..', balance:'余额,整数', type:'limit按余额算,unlimit不限制', unit:'单位,比如说token/次/分钟' } ] } }`设备未激活/密码错误(这种情况几乎没有)`{ success:false, msg:'设备未激活/密码错误', code:50000, data:null }`登录成功token会在头部的`bz-dt`字段返回,保存起来,并且要监听每个请求头的`bz-dt`是否有值,有的话覆盖本地的设备令牌。 | + +## 设备令牌检查 + +| 接口名称 | 检查设备令牌是否有效 | +| -------- | ------------------------------------------------------------ | +| 业务说明 | 如果手上有bz-dt的令牌,可以检查一下是否有效,无效的话可以去登陆 | +| 请求路径 | /dev/auth/token | +| 请求方法 | GET | +| 请求参数 | 无 | +| 请求结果 | 账号已登录`{ success:true, msg:'账号已登录', code:20000, data:{ "deviceId":"设备ID", "productName":"产品名称", "deviceName":"设备名称", "companyId":"公司ID", "productId":"产品ID", "productImage":"产品图片", } }`账号未登录`{ success:false, msg:'账号未登录', code:50001, data:null }` | + +## 设备列表 + +| 接口名称 | 设备列表 | +| -------- | ------------------------------------------------------------ | +| 业务说明 | 当App需要显示设备列表时,或者需要设备列表时,可通过此接口获取到当前用户所绑定的设备列表。 | +| 请求路径 | /dev/device/list | +| 请求方法 | GET | +| 请求参数 | 头部携带用户令牌 | +| 请求结果 | `{ success:true, msg:'获取设备列表成功', code:20000, data:[ { deviceId:'设备ID', productName:'产品名称:比如说酷鼠', deviceName:'设备名称:xxx的鼠标,xxx的AI音响', description:'设备介绍:亚洲第二好用的AI鼠标', quota: [{ cnName:'语音识别', enName:'ASR', inQuota:'上行配额,整数', inBlance:'上行余额,整数' maxIn:'最大限额,为0表示不限制,非0则显示inQuota,整数', outQuota:'10000整数', outBlance:'下行余额,整数' maxOut:'最大限额,为0表示不限制,非0则显示outQuota,整数', unit:'分钟' }] } ] }` | + +## 解绑设备 + +| 接口名称 | 解绑设备 | +| -------- | ------------------------------------------------------------ | +| 业务说明 | 当用户打开设备列表,点击解绑时,先清空设备的密码(不清也可以,清了的目的类似于重置了设备,下次重新走激活流程,目前设备为绑定所有权,设备在哪个用户手上,就绑定到对应用户的名下,也就是没有解绑的情况下,你仍然可以绑定到自己名下,前提是有设备的密码。清空密码的唯一目的:如果有用户说自己没有泄露密码,但是触发了调用上限,此时用户可以解绑设备,重新绑定的过程去更新密码),再调用此接口,解除用户与设备的绑定关系。 | +| 请求路径 | /dev/unbind/{deviceId} | +| 请求方法 | DELETE | +| 请求参数 | pathdeviceId:设备ID | +| 请求结果 | 解绑成功`{ success:true, msg:'解绑成功', code:20000, data:null } `无权限解绑`{ success:false, msg:'无权限解绑', code:50011, data:null }`两种情况下有权限:设备拥有者的账号token可以解绑设备设备的token可以解绑自己 | + +## 绑定设备 + +| 接口名称 | 绑定设备 | +| -------- | ------------------------------------------------------------ | +| 业务说明 | 把当前用户和设备绑定起来,需要携带bz-dt和bz-ut | +| 请求路径 | /dev/bind | +| 请求方法 | PUT | +| 请求参数 | 无 | +| 请求结果 | 设备未登录/用户未登录绑定成功`{ success:true, msg:'绑定成功', code:20000, data:null }` | + +## 修改设备名 + +| 接口名称 | 修改设备名称 | +| -------- | ------------------------------------------------------------ | +| 业务说明 | 用户点击用户名编辑的时候 | +| 请求路径 | /dev/device/nickname/{deviceId}?nickname=nickname | +| 请求方法 | PUT | +| 请求参数 | pathdeviceId:设备IDnickname:设备名称 | +| 请求结果 | 更新成功`{ success:true, msg:'更新成功', code:20000, data:null }`无权限操作`{ success:false, msg:'无权限操作', code:50011, data:null }`解绑的设备前提条件:设备所绑定的账号是当前账号。设备token可以更新自己设备的名称 | + +## 发送手机验证码 + +| 接口名称 | 发送手机验证码 | +| -------- | ------------------------------------------------------------ | +| 业务说明 | 账号注册\修改手机号\修改密码 | +| 请求路径 | 账号注册:/sys/sms/register/user/{phone}修改手机号:/sys/sms/modify/user/{phone}修改密码:/sys/sms/forget/user/{phone} | +| 请求方法 | GET | +| 请求参数 | pathphone 手机号 | +| 请求结果 | 发送成功`{ success:true, msg:'验证码发送成功', code:20000, data:null }`该号码已注册`{ success:false, msg:'该号码已注册', code:50000, data:null }`调用太频繁,请稍后重试`{ success:false, msg:'调用太频繁,请稍后重试', code:50023, data:null }`一个号码/来源调用次数超过了特定的次数,则会返回该状态 | + +## 账号注册 + +| 接口名称 | 账号注册 | +| -------- | ------------------------------------------------------------ | +| 业务说明 | 通常是静默注册;但当用户直接从官网下载APP时,没有硬件的情况下,也支持主动注册,注册完以后可以购买对应的功能服务。 | +| 请求路径 | /uc/sign_in?smsCode=手机验证码 | +| 请求方法 | POST | +| 请求参数 | querysmsCode 短信验证码body`{ phone:'手机号(唯一)', password:'密码,加密请参考附录', userName:'用户名', nonce:'16个字符的随机字符串' }`密码不可传明文,AES(AES/CBC/PKCS5Padding)进行加密,向量值为nonce,秘钥为当天日期,格式为yyyyMMdd00000000,边界情况:晚上23时59分59秒发起的请求,这种情况由服务器处理,服务器会使用两个key,优先使用当天的时间,如果当天时间解密失败,就使用昨天的时间 | +| 请求结果 | 注册成功`{ success:true, msg:'注册成功', code:20000, data:null }`验证码错误`{ success:false, msg:'验证码错误', code:50000, data:null }` | + +## 账号登录 + +| 接口名称 | 账号登录 | +| -------- | ------------------------------------------------------------ | +| 业务说明 | 用户未登录状态下点击登录,或者用户按AI按键,当前账号未登录的状态下跳转以登录,用户点击登录时调用此接口。 | +| 请求路径 | /uc/sign_in | +| 请求方法 | POST | +| 请求参数 | body`{ account:'手机号/用户名(要求唯一)/邮箱地址', password:'密码不可传明文,AES(AES/CBC/PKCS5Padding)进行加密,向量值为nonce,秘钥为当天日期,格式为yyyyMMdd00000000,边界情况:晚上23时59分59秒发起的请求,这种情况由服务器处理,服务器会使用两个key,优先使用当天的时间,如果当天时间解密失败,就使用昨天的时间。', nacos:'随机数16个字符串的长度,向量值' }` | +| 请求结果 | 登录成功`{ success:true, msg:'登录成功', code:20000, data:{ id:'用户ID', userName:'用户名', gender:'性别', avatar:'头像' } }`密码或账号错误`{ success:false, msg:'账号或密码错误', code:50000, data:null }`注意拦截header的`bz_ut`,要存储起来,监听请求中的`bz_ut`如果有新的,就覆盖本地存储的用户token | + +## 用户令牌校验 + +| 接口名称 | 用户令牌校验 | +| -------- | ------------------------------------------------------------ | +| 业务说明 | app携带用户令牌请求服务时,需要校验用户的token | +| 请求路径 | /uc/token | +| 请求方法 | GET | +| 请求参数 | 无,携带令牌在头部即可:用户的key为`bz-ut` | +| 请求结果 | 已登录`{ success:true, msg:'账号已登录.', code:20000, data:{ id:'用户ID', avatar:'用户头像', userName:'用户名', createTime:'注册时间', token:'新令牌,如果有的话', status:'状态,状态1正常,0禁用', roleId:'角色ID', roleName:'角色名称', companyId:'公司ID', menuVersion:'菜单版本', gender:'性别', verify:'是否有认证1认证,0未认证', companyName:'公司名', phone:'手机号' } }`未登录`{ success:false, msg:'账号未登录', code:50001, data:null }` | + +## 账号退出登录 + +| 接口名称 | 账号退出登录 | +| -------- | ------------------------------------------------------------ | +| 业务说明 | 用户退出账号登录 | +| 请求路径 | /uc/sign_out | +| 请求方法 | GET | +| 请求参数 | 无 | +| 请求结果 | `{ success:true, msg:'账号登录成功', code:20000, data:null }` | + +## 账号注销 + +| 接口名称 | 账号注销 | +| -------- | ------------------------------------------------------------ | +| 业务说明 | 如果是手机app上架某些应用市场,可能要求可以注销用户账号。 | +| 请求路径 | /uc/sign_away | +| 请求方法 | GET | +| 请求参数 | 无 | +| 请求结果 | `{ success:true, msg:'账号注销申请已提交', code:20000, data:null }` | + +## 修改账号密码 + +| 接口名称 | 修改账号密码 | +| -------- | ------------------------------------------------------------ | +| 业务说明 | 当用户修改账号密码时调用 | +| 请求路径 | /uc/reset | +| 请求方法 | POST | +| 请求参数 | body`{ phone:'手机号', password:'密码不可传明文,AES(AES/CBC/PKCS5Padding)进行加密,向量值为nonce,秘钥为当天日期,格式为yyyyMMdd00000000,边界情况:晚上23时59分59秒发起的请求,这种情况由服务器处理,服务器会使用两个key,优先使用当天的时间,如果当天时间解密失败,就使用昨天的时间。', nonce:'随机数,向量值' smsCode:'短信验证码' }` | +| 请求结果 | 密码修改成功`{ success:true, msg:'密码修改成功', code:20000, data:null }`验证码错误`{ success:false, msg:'验证码错误', code:50000, data:null }`注意,修改完密码以后,原来的token会自动失效,需要重新登录 | + +## 修改手机号 + +| 接口名称 | 修改手机号 | +| -------- | ------------------------------------------------------------ | +| 业务说明 | 用户需要修改手机号的时候。要求:这个新的手机号是不在库里的,也就是没有在本平台注册过的。 | +| 请求路径 | /uc/phone?phone=xxx&smsCode=xxx | +| 请求方法 | PUT | +| 请求参数 | queryphone 手机号smsCode 短信验证码 | +| 请求结果 | `{ success:true, msg:'更新成功', code:20000, data:null }` | + +## 修改用户昵称 + +| 接口名称 | 修改昵称 | +| -------- | ------------------------------------------------------------ | +| 业务说明 | 修改昵称 | +| 请求路径 | /uc/username?username=username | +| 请求方法 | PUT | +| 请求参数 | queryusername 用户名 | +| 请求结果 | `{ success:true, msg:'修改用户名成功', code:20000, data:null }` | + +## 修改用户头像 + +| 接口名称 | 修改用户头像 | +| -------- | -------------------------- | +| 业务说明 | 更新头像 | +| 请求路径 | /md/image/avatar | +| 请求方法 | POST | +| 请求参数 | image,类型为MultipartFile | +| 请求结果 | 文件上传成功 | + +## 密码加密算法 + +这里使用的是对称加密,key用的是productSecret(32个字符,用前16个字符-128位)nonce为16位的随机数-128位 + +目的: + +- 非明文传输密码 +- 每次传输的加密后密码不一样 + +Java参考代码: + +```Java +public class AESUtil { + + //注意算法 + private static final String algorithm = "AES/CBC/PKCS5Padding"; + + //加密 + public static String encrypt(String input, String key, String ivStr) { + try { + byte[] ivBytes = ivStr.getBytes(); + IvParameterSpec iv = new IvParameterSpec(ivBytes); + Cipher cipher = Cipher.getInstance(algorithm); + SecretKeySpec secretKey = createSecretKey(key.getBytes(StandardCharsets.UTF_8)); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv); + byte[] encrypted = cipher.doFinal(input.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(encrypted); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + + //解密 + public static String decrypt(String encryptedBase64, String key,String ivStr) { + try { + byte[] ivBytes = ivStr.getBytes(); + IvParameterSpec iv = new IvParameterSpec(ivBytes); + SecretKeySpec secretKey = createSecretKey(key.getBytes(StandardCharsets.UTF_8)); + Cipher cipher = Cipher.getInstance(algorithm); + cipher.init(Cipher.DECRYPT_MODE, secretKey, iv); + byte[] encrypted = Base64.getDecoder().decode(encryptedBase64); + byte[] original = cipher.doFinal(encrypted); + return new String(original, StandardCharsets.UTF_8); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + private static SecretKeySpec createSecretKey(byte[] keyBytes) { + return new SecretKeySpec(keyBytes, "AES"); + } + +} +``` + +参考网址 + +```Shell +https://www.ssleye.com/ssltool/aes_cipher.html +``` + +## 自动充值-查询豆包的配额 + +业务说明: 如果查询配额发现配额低于指定阈值,将会进行自动充值,所以需定时查询一次配额,确保有足够的配额可以使用 + +- 请求地址:/sv/xm/quota/auto-charge-check + +- 请求方式:GET + +- 请求query: + + - | 参数 | 是否必须 | 示例 | 说明 | + | -------- | -------- | ------ | ---------------------------- | + | deviceId | 是 | xxxxxx | 设备号 | + | bz-dt | 是 | xxxxxx | 设备令牌,放在请求headers里面 | + +- 返回参数 + + - ```JSON + { success: true, msg: '查询成功.', code: 20000, data: null } + ``` + +# 服务能力接入 + +## ASR 服务 + +> 指令、事件皆为WebSocket协议Text类型的DataFrame 音频 为 WebSocket 协议 二进制帧BinaryFrame + +- 调用时序 + +![img](https://zytjtkeb0k.feishu.cn/space/api/box/stream/download/asynccode/?code=M2EzNDliYWI1YTNmMGE2MWIzOTBlYTdlZjVmOTdlMDFfTnYyM3pmSm5mam14TTRiVU5ZalAwWE9ZbUcydktud3pfVG9rZW46WDRWYmJka1g2b1F6OEV4aHVJQmNMZXpObmhnXzE3NTYzNzU2OTI6MTc1NjM3OTI5Ml9WNA) + +- 调用地址 + + - | 地址 | 协议 | + | ----------------------- | ------ | + | amp.raisound.com/asr/ws | ws/wss | + +- 请求参数 + + - | 参数 | 是否必须 | 示例 | 说明 | + | -------- | -------- | ---------------------------------- | ------------------------------------------- | + | lang | 否 | zh-cn | 语言。目前大模型不支持调整 默认支持 中/英文 | + | dt | 是 | 95666854765b48b16e67eb6b7801175a68 | 设备令牌。 通过设备激活后登录获取设备令牌 | + | platform | 是 | huoshan | 固定值 | + +- 事件交互(客户端发送事件) + + - StartTranscription (开启asr识别) + - ```JSON + { + "header": { + "event": "StartTranscription", // 开启事件 + "trace_id": "3fa6b9e6a30b407ea38ad7ae69e6c291" // 随机数,客户端生成 + }, + "payload": { // 目前支持 16 bit 16k 1(mono) pcm 音频流 + "format": "pcm", // 音频格式 + "sample_rate": 16000 // 采样率 + } + } + ``` + - 音频发送 + - ```Markdown + BinaryFrame 二进制帧 发送音频流 + ``` + - StopTranscription (停止音频发送) + - ```JSON + { + "header": { + "event": "StopTranscription", + "trace_id": "3fa6b9e6a30b407ea38ad7ae69e6c291" + } + } + ``` + +- 事件交互(客户端接受事件) + + - TranscriptionStarted (开启成功) + - ```JSON + { + "status": 20000000, + "msg": "success", + "task_id": "80228c9f62821ffe3f867e1a5042d887", + "event": "TranscriptionStarted", + "result": null + } + ``` + - Changed (转写临时结果) + - ```JSON + { + "status": 20000000, + "msg": "success", + "task_id": "80228c9f62821ffe3f867e1a5042d887", + "event": "Changed", + "result": { + "result": "以", // 识别内容 + "is_end": false, + "sentence_id": "9df197b9a57acc331ae4c334d4c02732", + "start_time": 10290, + "end_time": 10760 + } + } + ``` + - SentenceEnd (一句话识别结束) + - ```JSON + { + "status": 20000000, + "msg": "success", + "task_id": "80228c9f62821ffe3f867e1a5042d887", + "event": "SentenceEnd", + "result": { + "result": "嗯。", + "is_end": true, + "sentence_id": "d7e62c6fb0ee847b85d674828797f3f3", + "start_time": 13610, + "end_time": 14520 + } + } + ``` + - TranscriptionCompleted (本次合成结束) + - ```JSON + { + "status": 20000000, + "msg": "success", + "task_id": "8d12cc56aa5fcd66a4c8667e20b5db25", + "event": "TranscriptionCompleted", + "result": null + } + ``` + - Failed (异常事件 收到后需要重新开启) + - ```JSON + { + "status": 40000000, + "event": "Failed", + "msg": "request timeout after 23 seconds.", + "task_id": "5b47d64b3553f75fb84d51e21144bd72" + } + ``` + +## TTS 服务 + +> 指令、事件皆为WebSocket协议Text类型的DataFrame 音频 为 WebSocket 协议 二进制帧BinaryFrame + +- 调用地址 + + - | 地址 | 协议 | + | --------------------------- | ------ | + | amp.raisound.com/tts/stream | ws/wss | + +- 请求参数 + + - | 参数 | 是否必须 | 示例 | 说明 | + | -------- | -------- | ---------------------------------- | ----------------------------------------- | + | dt | 是 | 95666854765b48b16e67eb6b7801175a68 | 设备令牌。 通过设备激活后登录获取设备令牌 | + | platform | 是 | huoshan | 固定值 | + +- 事件交互-长文本单次合成(客户端发送事件) + + - LongTextStream (开启合成) + - ```JSON + { + "header": { + "event": "LongTextStream", + "trace_id": "3fa6b9e6a30b407ea38ad7ae69e6c291" + }, + "payload": { + "voice":"xiaoyun", // 音色 xiaoyun zhixiaomei + "format":"WAV", + "sample_rate":16000, + "volume":50, + "speech_rate":0, + "text":"深圳市北科瑞声科技股份有限公司,是由国家重大人才计划特聘专家刘轶博士创办的国内人工智能音频领军企业,专注于行业智能音频交互技术,拥有全链条核心自主知识产权,汇集世界一流的研发运营团队。" + } + } + ``` + +- 事件交互-长文本单次合成(客户端接收事件) + + - 二进制音频流 + - > BinaryFrame 音频流 + - EndSynthesis (合成结束) + - ```JSON + { + "header": { + "event": "EndSynthesis", + "msg": "success", + "status": 20000000, + "task_id": "d724f070163b25b1c16da3671c939456" + } + } + ``` + +- 事件交互-双向流式合成(客户端发送事件) + + - StartSynthesis (开启tts合成) + - ```JSON + { + "header": { + "event": "StartSynthesis", + "trace_id": "3fa6b9e6a30b407ea38ad7ae69e6c291" + }, + "payload": { + "voice":"xiaoyun", // 音色 xiaoyun zhixiaomei + "format":"WAV", + "sample_rate":16000, + "volume":50, + "speech_rate":0 + } + } + ``` + - RunSynthesis (发送合成文本-可以多次发送) + - ```JSON + { + "header": { + "event": "RunSynthesis", + "trace_id": "3fa6b9e6a30b407ea38ad7ae69e6c291" + }, + "payload": { + "text":"深圳市北科瑞声科技股份有限公司,是由国家重大人才计划特聘专家刘轶博士创办的国内人工智能音频领军企业,专注于行业智能音频交互技术,拥有全链条核心自主知识产权,汇集世界一流的研发运营团队。" + } + } + ``` + - StopSynthesis (文本输入结束) + - ```JSON + { + "header": { + "event": "StopSynthesis", + "trace_id": "3fa6b9e6a30b407ea38ad7ae69e6c291" + } + } + ``` + +- 事件交互-双向流式合成(客户端接收事件) + + - SentenceBegin (表示服务端检测到了一句话的开始) + - ```JSON + { + "header": { + "event": "SentenceBegin", + "msg": "success", + "status": 20000000, + "task_id": "b74e0800599135a5f8fe242991aea18f" + }, + "payload": { + "index": 0 + } + } + ``` + - 二进制音频流 + - > BinaryFrame 音频流 + - SentenceSynthesis (表示有新的合成结果返回) + - ```JSON + { + "header": { + "event": "SentenceSynthesis", + "msg": "success", + "status": 20000000, + "task_id": "b74e0800599135a5f8fe242991aea18f" + }, + "payload": { + "text": "" + } + } + ``` + - SentenceEnd(服务端检测到了一句话的结束) + - ```JSON + { + "header": { + "event": "SentenceEnd", + "msg": "success", + "status": 20000000, + "task_id": "b74e0800599135a5f8fe242991aea18f" + }, + "payload": { + "text": "" + } + } + ``` + - EndSynthesis (合成结束) + - ```JSON + { + "header": { + "event": "EndSynthesis", + "msg": "success", + "status": 20000000, + "task_id": "d724f070163b25b1c16da3671c939456" + } + } + ``` + +## 文生图 + +| 主机地址 | 协议 | +| ---------------- | ---------- | +| amp.raisound.com | http/https | + +### 获取算法类型 + +| 请求地址 | /aigc/get-style | | +| ---------- | ------------------------------------------------------------ | --------------------------------------------------- | +| 请求方式 | POST | | +| Query参数 | platform=huoshan&dt=95666854765b48b16e67eb6b7801175a68 | platform 固定值 huoshandt 设备令牌 通过激活登录获取 | +| Header参数 | Content-Type:application/json | | +| Body | `{ "type":5 }` | 0:⽂字⽣图 1: ⽂+图⽣图 5:图⽣图(⻛格变换) | +| 返回结果 | `{ "status": 0, "data": [ { "code": "", "name": "", "url": "" } ] }` | Code::算法值Name:算法名称Url:示例图 | + +### 创建任务 + +| 请求地址 | /aigc/create-image | | +| ---------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 请求方式 | POST | | +| Query参数 | platform=huoshan&dt=95666854765b48b16e67eb6b7801175a68 | platform 固定值 huoshandt 设备令牌 通过激活登录获取 | +| Header参数 | Content-Type:application/json | | +| Body | `{ "subject": "孙悟空喝咖啡", "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAwAAAAMACU5ErkJggg==", "type": 0, "algorithm": "auto", "amount": 1 }` | subject:当 type=5 参数不生效image_base64:data:image/png;base64,{image_base64} 只要图片内容 type=0 不需要提交 type=5 必须提交type: 0: ⽂字⽣图,1: ⽂+图⽣图 5: 图⽣图(⻛格变换)Algorithm: 算法id可⽤值⻅get-styleAmount: ⽣成的图⽚数量 type=5 参数不生效 | +| 返回结果 | `{ "status": 0, "data": { "status": "PENDING", "task_id": "1706fbfd-8053-41d6-957f-0465b95e51e6" } }` | data.status: 状态 PENDING:任务排队中RUNNING:任务处理中SUSPENDED:任务挂起SUCCEEDED:任务执行成功FAILED:任务执行失败UNKNOWN:任务不存在或状态未知data.task_id: 任务ID | + +### 查询任务状态 + +| 请求地址 | /aigc/get-result | | +| ---------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 请求方式 | POST | | +| Query参数 | platform=huoshan&dt=95666854765b48b16e67eb6b7801175a68 | platform 固定值 huoshandt 设备令牌 通过激活登录获取 | +| Header参数 | Content-Type:application/json | | +| Body | `{ "task_id": "6c5fde18-9c98-4dad-b704-e8aeae62d0f5", "bit_depth": 32, "compression_quality": 80, "format": "jpeg", "width": 128, "height": 128 }` | task_id: 任务IDbit_depth:位深度 只支持 8,16,24,32compression_quality:压缩率format:格式 目前支持 png,jpeg,jpgwidth:宽度height:高度 | +| 返回结果 | `{ "status": 0, "data": { "status": "SUCCEEDED", "task_id": "111793d3-1999-4103-b3db-6f879d3b6ccb", "result": [ "http://118.145.158.207:80/aigc/image", ] } }` | data.status: 状态 PENDING:任务排队中RUNNING:任务处理中SUSPENDED:任务挂起SUCCEEDED:任务执行成功FAILED:任务执行失败UNKNOWN:任务不存在或状态未知data.task_id: 任务IDdata.result: 图片地址 | + +## 大模型 + +### Websocket + +- 调用地址 + + - | 地址 | 协议 | + | ------------------------ | ------ | + | amp.raisound.com/chat/ws | ws/wss | + +- 请求参数 + + - | 参数 | 是否必须 | 示例 | 说明 | + | -------- | -------- | ---------------------------------- | ----------------------------------------- | + | dt | 是 | 95666854765b48b16e67eb6b7801175a68 | 设备令牌。 通过设备激活后登录获取设备令牌 | + | platform | 是 | huoshan | 固定值 | + +- 事件交互 (客户端发送事件) + + - chat 发送对话 + - ```JSON + { + "type": "chat", + "content": "今天天气怎么样" + } + ``` + +- 事件交互 (客户端收到事件) + + - 回复开始 BinaryFrame 帧 直接转成string 为json字符串 + - ```Bash + hexdump: + 00000000: 7B 22 74 79 70 65 22 3A 22 73 74 61 72 74 22 2C {"type":"start", + 00000010: 22 63 6F 6E 74 65 6E 74 22 3A 6E 75 6C 6C 7D "content":null} + ``` + + - ```JSON + { + "type": "start", + "content": null + } + ``` + - 回复中 BinaryFrame 帧 直接转成string 为json字符串 + - ```Bash + hexdump: + 00000000: 7B 22 74 79 70 65 22 3A 22 6D 69 64 64 6C 65 22 {"type":"middle" + 00000010: 2C 22 63 6F 6E 74 65 6E 74 22 3A 22 E9 A2 84 E6 ,"content":".... + 00000020: B5 8B E6 88 96 E8 80 85 E6 B0 94 E5 80 99 E5 8F ................ + 00000030: 98 E5 8C 96 E7 AD 89 E7 9B B8 E5 85 B3 22 7D ............."} + ``` + + - ```JSON + { + "type": "middle", + "content": "预测或者气候变化等相关" + } + ``` + - 回复结束 BinaryFrame 帧 直接转成string 为json字符串 + - ```Bash + hexdump: + 00000000: 7B 22 74 79 70 65 22 3A 22 65 6E 64 22 2C 22 63 {"type":"end","c + 00000010: 6F 6E 74 65 6E 74 22 3A 6E 75 6C 6C 7D ontent":null} + ``` + + - ```JSON + { + "type": "end", + "content": null + } + ``` + +### Http 请求 + +| 主机地址 | 协议 | +| ---------------- | ---------- | +| amp.raisound.com | http/https | + +- 请求地址:/chat + +- 请求方式:POST + +- 请求query: + + - | 参数 | 是否必须 | 示例 | 说明 | + | -------- | -------- | ---------------------------------- | ----------------------------------------- | + | dt | 是 | 95666854765b48b16e67eb6b7801175a68 | 设备令牌。 通过设备激活后登录获取设备令牌 | + | platform | 是 | huoshan | 固定值 | + +- Body 参数 + + - | 参数 | 是否必须 | 示例 | 说明 | + | ------------------ | -------- | ---------------------------------- | -------------------------------------------------- | + | stream | 是 | true | 是否开启流式返回 | + | messages | 是 | [{"role":"user","content":"你好"}] | 消息数组 可以加上历史消息 最后一条 role 必须为user | + | message[0].role | 是 | user | assistant user | + | message[0].content | 是 | 你好 | 发送文本内容 | + +- 返回参数 + + - 非流式返回 + - ```JSON + { + "choices": [ + { + "delta": { + "role": "assistant", + "content": "你好!有什么我可以帮忙的吗?" + }, + "finish_reason": "stop" + } + ], + "task_id": "d27a8379f9c3c4bde583f9e3631a59bb" + } + ``` + - 流式返回 + - ```Bash + data: {"choices":[{"delta":{"role":"assistant","content":"ä½ "},"finish_reason":""}],"task_id":"79120ad1ab67d119c499ba63282cd8b6"} + + data: {"choices":[{"delta":{"role":"assistant","content":"好"},"finish_reason":""}],"task_id":"79120ad1ab67d119c499ba63282cd8b6"} + + data: {"choices":[{"delta":{"role":"assistant","content":"!"},"finish_reason":""}],"task_id":"79120ad1ab67d119c499ba63282cd8b6"} + + data: {"choices":[{"delta":{"role":"assistant","content":"有"},"finish_reason":""}],"task_id":"79120ad1ab67d119c499ba63282cd8b6"} + + data: {"choices":[{"delta":{"role":"assistant","content":"什么"},"finish_reason":""}],"task_id":"79120ad1ab67d119c499ba63282cd8b6"} + + data: {"choices":[{"delta":{"role":"assistant","content":"我"},"finish_reason":""}],"task_id":"79120ad1ab67d119c499ba63282cd8b6"} + + data: {"choices":[{"delta":{"role":"assistant","content":"可以"},"finish_reason":""}],"task_id":"79120ad1ab67d119c499ba63282cd8b6"} + + data: {"choices":[{"delta":{"role":"assistant","content":"帮助"},"finish_reason":""}],"task_id":"79120ad1ab67d119c499ba63282cd8b6"} + + data: {"choices":[{"delta":{"role":"assistant","content":"你的"},"finish_reason":""}],"task_id":"79120ad1ab67d119c499ba63282cd8b6"} + + data: {"choices":[{"delta":{"role":"assistant","content":"吗"},"finish_reason":""}],"task_id":"79120ad1ab67d119c499ba63282cd8b6"} + + data: {"choices":[{"delta":{"role":"assistant","content":"?"},"finish_reason":""}],"task_id":"79120ad1ab67d119c499ba63282cd8b6"} + + data: {"choices":[{"delta":{},"finish_reason":"stop"}],"task_id":"79120ad1ab67d119c499ba63282cd8b6"} + ``` + +## OCR + +| 主机地址 | 协议 | +| ---------------- | ---------- | +| amp.raisound.com | http/https | + +### 统一识别入口 + +- 请求地址:/ocr/all-text + +- 请求方式:POST + +- 请求query: + + - | 参数 | 是否必须 | 示例 | 说明 | + | -------- | -------- | ---------------------------------- | ----------------------------------------- | + | dt | 是 | 95666854765b48b16e67eb6b7801175a68 | 设备令牌。 通过设备激活后登录获取设备令牌 | + | platform | 是 | huoshan | 固定值 | + +- Body 参数 + + - | 参数 | 是否必须 | 示例 | 说明 | + | ------ | -------- | ------------------------------------------------------------ | ------------------------------------------------------------ | + | url | 否 | https://openstorge.raisound.com/public/images/%E6%88%AA%E5%B1%8F2024-06-07%2013.38.18.png | 图片地址 和 base64 二选一 同时填写 优先解析base64 | + | base64 | 否 | iVBORw0KGgoAAAANSUhEUgAAAHg…… | base64 只需要传图片的base64值,类似data:image/png;base64的图片头不要传入 | + | type | 是 | General | General 通用识别 IdCard 身份证MultiLanguage 多语言识别 | + +- 返回参数 + + - ```JSON + // 通用识别 + { + "status": 0, + "data": { + "content": "识别内容 " + } + } + + // 身份证识别 + { + "status": 0, + "data": { + "info": { + "card_front": { + "name": "识别君", + "sex": "女", + "ethnicity": "汉", + "birthDate": "1998å¹´8月8日", + "address": "上海市宝山区陆翔路XXX弄X号楼XXX室", + "idNumber": "456789123456XXXXX" + }, + "card_back": { + "issueAuthority": "", + "validPeriod": "" + } + } + } + } + // 多语种识别返回格式 + + { + "status": 0, + "error": "", + "errdlt": "", + "data": { + "info": { + "items": [ + { + "lang": "not_lang", + "text": "https://www.volcengine.com/" + }, + { + "lang": "zh", + "text": "北京火山引擎科技有限公司" + }, + { + "lang": "zh", + "text": "业务咨询:service@volcengine.com" + }, + { + "lang": "zh", + "text": "电话:400-850-0030(周一至周五 10:00-18:00)" + } + ] + } + } + } + ``` + +## 翻译 + +| 主机地址 | 协议 | +| ---------------- | ---------- | +| amp.raisound.com | http/https | + +### 通用翻译 + +- 请求地址:/translate/general + +- 请求方式:POST + +- 请求query: + + - | 参数 | 是否必须 | 示例 | 说明 | + | -------- | -------- | ---------------------------------- | ----------------------------------------- | + | dt | 是 | 95666854765b48b16e67eb6b7801175a68 | 设备令牌。 通过设备激活后登录获取设备令牌 | + | platform | 是 | huoshan | 固定值 | + +- Body 参数 + + - | 参数 | 是否必须 | 示例 | 说明 | + | ---- | -------- | ----------------- | --------------------------------------------------------- | + | sl | 是 | en | 源语言标识 (可以通过“翻译支持语言列表” 获取 code 值) | + | tl | 是 | zh | 转换后语言标识(可以通过“翻译支持语言列表” 获取 code 值) | + | text | 是 | Translate General | 翻译文本 | + +- 返回参数 + + - ```JSON + { + "status": 0, + "data": { + "count": 17, + "translated": "一般翻译" + } + } + ``` + +### 翻译支持语言列表 + +- 请求地址:/translate/languages + +- 请求方式:POST + +- 请求query: + + - | 参数 | 是否必须 | 示例 | 说明 | + | -------- | -------- | ---------------------------------- | ----------------------------------------- | + | dt | 是 | 95666854765b48b16e67eb6b7801175a68 | 设备令牌。 通过设备激活后登录获取设备令牌 | + | platform | 是 | huoshan | 固定值 | + +- 返回参数 + + - ```JSON + { + "status": 0, + "data": [ + { + "cn": "中文(简体)", + "en": "Chinese (simplified)", + "code": "zh" + }, + { + "cn": "中文(繁体)", + "en": "Chinese (traditional)", + "code": "zh-Hant" + } + ] + } + ``` + +## ChatPPT + +### 通用授权 + +- 请求地址:/chat-ppt/token +- 请求方式:POST +- 请求query: + +| 参数 | 是否必须 | 示例 | 说明 | +| -------- | -------- | ---------------------------------- | ----------------------------------------- | +| dt | 是 | 95666854765b48b16e67eb6b7801175a68 | 设备令牌。 通过设备激活后登录获取设备令牌 | +| platform | 是 | wernicke | 固定值 | +| appid | 是 | 3T8uxxx | | + +- 返回参数 + +```json +{ + "status": 0, + "error": "", + "errdlt": "", + "data": { + "token": "REUJ2RVBBXA7PCSJ9YBSKXWFAYCSNKRF", // token + "url": "https://aigc.yoo-ai.com/saaseditPPT?channel=yoo&urlKey=saas&tokens=REUJ2RVBBXA7PCSJ9YBSKXWFAYCSNKRF&appid=3T8uxxx" // 跳转链接 + } +} +``` + +### 新版本授权 + +- 请求地址:/chat-ppt/authorize +- 请求方式:POST +- 请求query: + +| 参数 | 是否必须 | 示例 | 说明 | +| -------- | -------- | ---------------------------------- | ----------------------------------------- | +| dt | 是 | 95666854765b48b16e67eb6b7801175a68 | 设备令牌。 通过设备激活后登录获取设备令牌 | +| platform | 是 | wernicke | 固定值 | +| appid | 是 | 3466422XXX | | + +- 返回参数 + +```json +{ + "status": 0, + "data": { + "url": "https://ppt.yoo-ai.com?code=GcDryQbGbu" + } +} +``` + +# 设备windows的SDK接入 + +## SDK集成框图 + +​ + +![img](https://zytjtkeb0k.feishu.cn/space/api/box/stream/download/asynccode/?code=ZmVlMzVhYzYyZmEzZWY3YzFmMDE3N2QwNzY3ZjA1ZDBfd01aRzdjQ0tXdlpPcG9jZWpHbUZpUzByZjMyNm55eVRfVG9rZW46UTV1UmJTQXUwbzJpUEt4T1E2UmNRYzhXbnZoXzE3NTYzNzU2OTI6MTc1NjM3OTI5Ml9WNA) + +## SDK库导入 + +​ 应用需要将2.4G库和蓝牙库导入到应用中,参考c#示例的给出方式 + + + +![img](https://zytjtkeb0k.feishu.cn/space/api/box/stream/download/asynccode/?code=OWI5NDljMTBjM2NiMDFlODhmNWY2YjM2ZGFhZTMwYTBfeGlmeVNOdE5BUzUzV2FEVml0R2l3UTIzOG5xWXU3UGVfVG9rZW46SzE3SmI1T3pab1RtVHl4SmNPc2NSMmFLbk1iXzE3NTYzNzU2OTI6MTc1NjM3OTI5Ml9WNA) + + 其中SDK_lib为应用需要加载的dll库目录,其中elecUSBMouse.dll为2.4G方式的库,testelec.dll为蓝牙方式的库,其他库为第三方使用的库,包含火山的SDK + +## 泰凌微鼠标滚码的获取方式 + +​ + +泰凌威设备2.4G通过SDK获取滚码信息说明 + +参看示例 + + + +![img](https://zytjtkeb0k.feishu.cn/space/api/box/stream/download/asynccode/?code=YTAxNDRlY2I2MWY3N2VhZjYxOGIxZGY4OTRmNzY1ODdfZWQ4eFVBNkNJQzRKeGs4dWgyd0loN0RJNEpqQjlQd2dfVG9rZW46QTVMRmIzODBDb0RBZ3l4OXpIS2Njb1Aybm5kXzE3NTYzNzU2OTI6MTc1NjM3OTI5Ml9WNA) + + + +​ 1 确保2.4G方式连上了AI鼠标. + +​ 2 点击初始化连接,返回成功后,就可以获取滚码相关信息,点击获取设备信息. + +​ 3 设备信息回调接口返回的信息为json格式的信息,其中就把包括滚码信息. + +​ + +​ **{"ROLLING_CODE":"C0345678AB1D"}** + +# 鼠标端2.4G接口函数 + +1. ## 鼠标蓝牙连接和初始化 + + 本文档以C# 类库DLL为标准,详细使用参看示例程序 + + 设备接口类 MOUSEUSB + + 示例: m_USBDevice = new MOUSEUSB(); + + + +注意:初始化前需插入USB,并将设备切换为2.4g模式 + + + +1. SDK初始化 + +​ Task initAIUSB(dynamic input); + +参数: + +​ dynamic input(默认赋值是string,参考示例) + +返回: + +​ + +​ Object类型(转化为string,详细看示例) + +​ “success” 初始化成功; “fail”初始化失败 + +​ 备注: + +​ 初始化SDK环境 + +1. 释放SDK环境 + +void releaseAIUSB(); + +参数: + +​ 无 + +返回: + +​ 无 + +​ 备注: + +​ 释放SDK环境 + + + +1. 获取设备信息 + +Task getDeviceInfoUSB(dynamic input); + +参数: + +​ dynamic input(默认赋值是string,参考示例) + + + +返回: + +​ + +​ Object类型(转化为string,详细看示例) + +​ “success” 初始化成功; “fail”初始化失败 + + + +​ 备注: + +​ 获取设备信息 + +​ 调用成功后,设备的消息将通过信息回调接口返回 + +例如: + + {"HS_TOKEN":"IKOin5pXaKqnm9y2Oe4Q2Fpv6FNXKdwK"}// 火山token + +{"HS_URLPARAM":"productKey=k1r8dNeY2Ui&deviceName=C0345678AB1D&iotInstanceId=iot-060aenzm×tamp=1735782559891&sign=cec8cecc4d14fe1ec751835b5aa00c43005f759cc92cfe00c65bbe912f1813d4"} + +{"ROLLING_CODE":"C0345678AB1D"}//设备滚码信息 + + + +1. ## 信息回调接口 + + 1. 设置回调函数(c#) + +​ m_USBDevice.cmdGetInfo += getUSBCmd; + + + +1. 回调函数(由应用层实现) + +void getUSBCmd(string keyValue); + +参数: + +| 序号 | 参数名 | 参数类型 | 说明 | +| ---- | -------- | -------- | ------------------ | +| 1 | keyValue | string | 设备返回的滚码信息 | + +返回: + +​ 无 备注: + +应用层定义声明的回调函数,用来接收蓝牙鼠标发送的键值变化。以下是不同功能按键的键值定义和功能说明(鼠标对应键值请根据鼠标型号进行确认,本文档以MA600_MS521Q32作为示例),具体应用按实际返回的参数。 + + + +| 1 | 语音键 | **长按:** HID:按下:**0x21** 抬起 **0x22****短按:**单击一下,进入连续语音状态;再单击一下,结束**HID:** 按下:**0x31** 抬起 **0x32** | APP监控到键值变化,做出响应按键按下时语音通道打开,松开关闭 | +| ---- | ------ | ------------------------------------------------------------ | ----------------------------------------------------------- | +| 2 | AI键 | 长按:HID: 按下 **:0x23** 抬起: **0x24****短按:HID:****(待确认)** | APP监控到键值变化,做出响应按键按下时语音通道打开,松开关闭 | +| 3 | M键 | HID: 按下 **:0x25** 抬起 **0x26** | APP监控到键值变化,做出响应按键按下时语音通道打开,松开关闭 | + + + +1. ## 录音数据接收回调接口 + + 1. 设置回调函数(c#) + + m_USBDevice.dataGetInfo += getUSBData; + +1. 回调函数说明 + +getUSBData回调函数原型 + +void getUSBData(byte[] data); + +参数: + +​ data byte数组 录音PCM数据 + +返回: + +​ 无 备注: + +​ 应用层定义的回调函数,用来接收蓝牙设备发送过来的录音数据。 + +​ + +1. ## 其他接口说明 + + + +1. 蓝牙设备鉴权接口 + +string lotBusinessAuth(); + +参数: + +​ 无 + +​ + + + +返回: + +​ string 返回设备相关信息 + +​ 备注: + +1. ​ 蓝牙设备鉴权接口 + + + + + + + + + +1. 获取设备CPI + + bool setCPIUSB(string *values); + + + +参数: + +​ values 写入的CPI值,五个挡位值{"600,800,1000,1300,1600"} + +1. 获取设备SN + + bool setSNUSB(string *values); + + + +参数: + +​ values 写入的SN值,例如”sn123456” + + + +返回: + +​ bool 写入成功或者失败 + +​ 备注: + +​ 设置设备CPI + + + +# 鼠标端蓝牙接口函数 + +1. ## 鼠标蓝牙连接和初始化 + + 本文档以C# 类库DLL为标准,详细使用参看示例程序 + + 设备接口类 MouseBle + + + +​ 示例: m_bleDevice = new MouseBle(); + + + +1. SDK初始化 + +​ Task initLd2(dynamic input); + +参数: + +​ dynamic input(默认赋值是string,参考示例) + + + +返回: + +​ Object类型(转化为string,详细看示例) + +​ “success” 初始化成功; “fail”初始化失败 + +​ 备注: + +​ 初始化SDK环境 + + + +回调函数 + +​ void notifyInitStauts(bool ret) + + + + + +1. 释放SDK环境 + +void releaseLd2(); + +参数: + +​ 无 + +返回: + +​ 无 + +​ 备注: + +​ 释放SDK环境 + + + +1. 获取设备信息接口 + +Task getDeviceInfo(dynamic input) + + + +参数: + +​ dynamic input(默认赋值是string,参考示例) + + + +返回: + +​ Object类型(转化为string,详细看示例) + +​ “success” 初始化成功; “fail”初始化失败 + +​ 备注: + +​ 获取设备信息 + +​ 调用成功后,设备的消息将通过信息回调接口返回 + +例如: + + {"HS_TOKEN":"IKOin5pXaKqnm9y2Oe4Q2Fpv6FNXKdwK"}// 火山token + +{"HS_URLPARAM":"productKey=k1r8dNeY2Ui&deviceName=C0345678AB1D&iotInstanceId=iot-060aenzm×tamp=1735782559891&sign=cec8cecc4d14fe1ec751835b5aa00c43005f759cc92cfe00c65bbe912f1813d4"} + +{"ROLLING_CODE":"C0345678AB1D"}//设备滚码信息 + + + +1. ## 信息回调接口 + + 1. 设置回调函数(c#) + +​ m_bleDevice.cmdGetInfo += getBLeCmd; + + + +1. 回调函数(由应用层实现) + +void getBLeCmd(byte[] keyValue); + +参数: + +| 序号 | 参数名 | 参数类型 | 说明 | +| ---- | ------ | -------- | -------------- | +| 1 | String | byte | 鼠标变化的键值 | + +返回: + +​ 无 备注: + +应用层定义声明的回调函数,用来接收蓝牙鼠标发送的键值变化。以下是不同功能按键的键值定义和功能说明(鼠标对应键值请根据鼠标型号进行确认,本文档以MA600_MS521Q32作为示例),具体应用按实际返回的参数。 + + + +| 1 | 语音键 | **长按:** HID:按下:**0x21** 抬起 **0x22****短按:**单击一下,进入连续语音状态;再单击一下,结束**HID:** 按下:**0x31** 抬起 **0x32** | APP监控到键值变化,做出响应按键按下时语音通道打开,松开关闭 | +| ---- | ------ | ------------------------------------------------------------ | ----------------------------------------------------------- | +| 2 | AI键 | 长按:HID: 按下 **:0x23** 抬起: **0x24****短按:HID:****(待确认)** | APP监控到键值变化,做出响应按键按下时语音通道打开,松开关闭 | +| 3 | M键 | HID: 按下 **:0x25** 抬起 **0x26** | APP监控到键值变化,做出响应按键按下时语音通道打开,松开关闭 | + + + +1. ## 录音数据接收回调接口 + + 1. 设置回调函数(c#) + + m_bleDevice.dataGetInfo += getBLeData; + +1. 回调函数说明 + +getBLeData回调函数原型 + +void getBLeData(byte[] data); + +参数: + +​ data byte数组 录音sbc压缩数据 + +返回: + +​ 无 备注: + +​ 应用层定义的回调函数,用来接收蓝牙设备发送过来的录音数据。 + +​ + +1. ## 其他接口说明 + +1. 蓝牙设备鉴权接口 + +bool lotBusinessAuth(); + +参数: + +​ 无 + +返回: + +​ bool 类型 + +​ true 鉴权成功; false 鉴权失败 + +​ 备注: + +​ 对连接的蓝牙设备进行鉴权 + + + + + +回调函数 + + void getLotAuthInfo(bool isAuth) + + + + + + + +1. 设置设备CPI + + bool setCPI(string *values); + + + +参数: + +​ values 写入的CPI值,五个挡位值{"600,800,1000,1300,1600"} + + + +1. 设置设备SN + + bool setSN(string *values); + + + +参数: + +​ values 示例 “sn123456” + +返回: + +​ bool 写入成功或者失败 + +​ 备注: + +​ 设置设备CPI + +# 火山AI能力云端接口 + + + +暂时无法在飞书文档外展示此内容 \ No newline at end of file diff --git "a/code/sdk/zhihui/ai/doc/\351\270\277\350\222\231AI\346\234\215\345\212\241-\346\236\266\346\236\204\350\256\276\350\256\241\345\233\276.md" "b/code/sdk/zhihui/ai/doc/\351\270\277\350\222\231AI\346\234\215\345\212\241-\346\236\266\346\236\204\350\256\276\350\256\241\345\233\276.md" new file mode 100644 index 000000000..13b80d312 --- /dev/null +++ "b/code/sdk/zhihui/ai/doc/\351\270\277\350\222\231AI\346\234\215\345\212\241-\346\236\266\346\236\204\350\256\276\350\256\241\345\233\276.md" @@ -0,0 +1,374 @@ +# 鸿蒙AI服务-架构设计图 + + + +![image架构图](鸿蒙ai架构图.png) + +[AI聚合平台接入文档](https://zytjtkeb0k.feishu.cn/docx/BJcAduKpdoidt6x3xgYcDhWPnPf) + +73519@5J + +1. ## WebSocketService + +1. ### 功能描述: + +提供基础Websocket连接能力的模块, + +支持指定网址连接 及断开连接, + +支持回调式的 连接状态反馈,接收消息(不区分文本和二进制数据) + + 接收和发送都是异步的,有缓存。 + + 支持文本模式,二进制模式,连续数据 模式的消息发送。 + +1. ### 接口定义:SDK650 730 Patch 适用 + +net_websocket_type.h + +net_websocket.h + +1. ## Audio Play Service + + 1. ### 功能描述: + + 提供基础音频播放能力, + + 支持指定播放格式的流媒体播放, + + 提供播放服务控制接口:开始播放服务,停止播放服务,发送播放数据。 + + 1. ### 接口定义: + +ai_media.h + +```C++ +/* + * @brief 初始化AI媒体服务 + * @return 0 on success, -1 on failure + */ +int32_t ai_media_service_init(void); + +/* + * @brief 反初始化AI媒体服务 + */ +void ai_media_service_deinit(void); + +/* +* + * @brief 开始播放 + * @param format 音频格式 "pcm","mp3","opus" + * @return 无 + */ +void ai_media_play_start(uint8_t* format); + +/* + * @brief 停止播放 + * @param 无 + * @return 无 + */ +void ai_media_play_stop(void); + +/* + * @brief 获取播放状态 + * @param 无 + * @return 播放状态 + */ +uint8_t ai_media_get_play_status(void); + +/* + * @brief 播放音频 + * @param 音频数据 + * @param 音频数据大小 + * @return 无 + */ +void ai_media_play_buffer(uint8_t *data, uint32_t size); +``` + +1. ## Audio Record Service + + 1. ### 功能描述: + + 提供基础音频录音能力, + + 支持指定格式的流媒体录音,支持格式"pcm","opus","mp3" + + 提供录音服务控制接口:开始录音服务,停止录音服务,录音数据回调。 + + 1. ### 接口定义: + + ai_media.h + + ```C++ + /* + * @brief 初始化AI媒体服务 + * @return 0 on success, -1 on failure + */ + int32_t ai_media_service_init(void); + + /* + * @brief 反初始化AI媒体服务 + */ + void ai_media_service_deinit(void); + /* + * @brief 开始录音,默认采样率16000,默认通道数1,默认位深16,默认音频格式为PCM + * @param format 音频格式 "pcm","mp3","opus" + * @return 无 + */ + void ai_media_record_start(uint8_t* format); + + /* + * @brief 停止录音 + * @param 无 + * @return 无 + */ + void ai_media_record_stop(void); + + /* + * @brief 获取录音状态 + * @param 无 + * @return 录音状态 + */ + uint8_t ai_media_get_record_status(void); + + /* + * @brief 录音回调 + * @param 录音数据 + * @param 录音数据大小 + * @return 无 + */ + typedef void (*ai_media_record_callback)(uint8_t *data, uint32_t size); + + /* + * @brief 设置录音回调 + * @param 录音回调 + * @return 无 + */ + void ai_media_set_record_callback(ai_media_record_callback callback); + ``` + +1. ## ASR Server + + 1. ### 功能描述: + + - 连接管理: + - ASR服务生命周期内,保持websocket在连接状态,退出服务,websocket也要断开连接。 + - 录音服务管理: + - ASR服务生命周期内,保持录音服务开启,随时可以通过callback拿到录音数据,只是根据上传控制来决定是否上传消息到服务器。退出服务,才停止录音服务。 + - 上传控制: + - 提供对话逻辑中使用的上传控制接口,实际上,Ai对话中,对话完毕并不会直接停止录音,只是不上传数据到服务器。这样随时可以快速进入录音对话模式,不容易丢失语音数据。 + - ASR消息解析: + - ASR服务器返回掉消息有两种,一种语音的二进制数据,一种是txt。他们都可能是json数据,本模组需要正确解析成json数据供下一步逻辑使用。 + - 本模块是消息管理里使用的关键解析工具。 + - 事件管理: + - 通过回调的方式,通知UI,让其对异常情况等进行响应。 + - 消息管理: + - 负责接收和解析服务器返回的消息。通过回调,让Ai全家桶去进行语音播放,或者执行对应的逻辑状态。 + + ​ 例如 通过下面的数据解析,区分处不同的event做不同的响应。 + + **TranscriptionStarted** 表示服务器连接成功,可以发送语音数据。必须有这个才能发送语音数据。 + + **Changed** 表示刚刚识别出的语句,这个可能不是最终结果,在这种event下,只要把当前语句追加显示即可。 注意是当前语句 ,之前的句子不必更改。这个是临时显示,只是为了显示的更快,给人体验更好。 + + ​ **SentenceEnd** 并且 **is_end**为true代表 这一句结束,识别的语句为最终的结论。之前临时作为优化体验的临时数据要用最终数据进行覆盖。在这样的语句出现后,才认为这句话为可信语句,可以被上传给 Chat Server开始对话。 + + ​ + + ​ + + 参考消息格式: + + ```JSON + {"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"TranscriptionStarted"} + {"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"Changed","result":{"result":"我,","is_end":false,"sentence_id":"","start_time":0,"end_time":0}} + {"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"SentenceEnd","result":{"result":"给我讲讲。","is_end":true,"sentence_id":"","start_time":0,"end_time":0}} + {"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"Changed","result":{"result":"没,","is_end":false,"sentence_id":"","start_time":0,"end_time":0}} + {"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"Changed","result":{"result":"今","is_end":false,"sentence_id":"","start_time":0,"end_time":0}} + {"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"Changed","result":{"result":"今天打算","is_end":false,"sentence_id":"","start_time":0,"end_time":0}} + {"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"Changed","result":{"result":"今天早上有你可能","is_end":false,"sentence_id":"","start_time":0,"end_time":0}} + {"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"Changed","result":{"result":"今天早上我去可能去温","is_end":false,"sentence_id":"","start_time":0,"end_time":0}} + {"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"SentenceEnd","result":{"result":"今天早上有没有什么趣味?","is_end":true,"sentence_id":"","start_time":0,"end_time":0}} + {"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"TranscriptionCompleted"} + + { + "status" : 20000000, + "msg" : "success", + "task_id" : "ff5809f9a303aa133542df23e171dfc7", + "event" : "SentenceEnd", + "result": { + "result" : "今天早上有没有什么趣味?", + "is_end" : true, + "sentence_id" : "", + "start_time" : 0, + "end_time" : 0 + } + } + ``` + + 1. ### 接口定义: + +1. ## Chat Server + + 1. ### 功能描述: + + - 连接管理 + - Chat服务生命周期内,保持websocket在连接状态,退出服务,websocket也要断开连接。 + - 事件管理 + - 通过回调的方式,通知UI,让其对异常情况等进行响应。 + - 消息管理 + - 负责接收和解析服务器返回的消息。通过回调,让Ai全家桶去进行回答内容的回显,以及执行对应的逻辑状态。 + - 消息解析使用Chat消息解析模块 + - 对话信息发送,可以连续, + - 对话打断只能用断连的方式 + - Chat消息解析 + - Type start 表示回复开始 + - Type middle 表示回复中,回复内容为递增数据,直接递增显示即可。实际上不管start还是end,只要middle就显示出来就行。换行等内容大模型都会自动给,无需手动添加。 + - Type end 回复结束。 + + 参考消息格式: + + ```JSON + {"type":"start","content":null,"task_id":"","reasoning_content":""} + {"type":"middle","content":"请","task_id":"","reasoning_content":""} + {"type":"middle","content":"ä½ ","task_id":"","reasoning_content":""} + {"type":"middle","content":"明确","task_id":"","reasoning_content":""} + {"type":"middle","content":"一下","task_id":"","reasoning_content":""} + {"type":"middle","content":"问题","task_id":"","reasoning_content":""} + {"type":"end","content":null,"task_id":"","reasoning_content":""} + ``` + + 1. ### 接口定义: + +1. ## TTS Server + + 1. ### 功能描述:双向流式合成 + + - 连接管理 + - TTS服务生命周期内,保持websocket在连接状态,退出服务,websocket也要断开连接。 + - 事件管理 + - 通过回调的方式,通知UI,让其对异常情况等进行响应。 + - 消息管理 + - 文本发送完毕,要发出特定指令。 + - ```JSON + { + "header": { + "event": "StopSynthesis", + "trace_id": "3fa6b9e6a30b407ea38ad7ae69e6c291" + } + } + ``` + + - 文本发送开始,要发送特定指令 + - ```JSON + { + "header": { + "event": "StartSynthesis", + "trace_id": "3fa6b9e6a30b407ea38ad7ae69e6c291" + }, + "payload": { + "voice":"xiaoyun", // 音色 xiaoyun zhixiaomei + "format":"WAV", + "sample_rate":16000, + "volume":50, + "speech_rate":0 + } + } + ``` + + - 文本发送 可多次发送 + +```JSON +{ + "header": { + "event": "RunSynthesis", + "trace_id": "3fa6b9e6a30b407ea38ad7ae69e6c291" + }, + "payload": { + "text":"深圳市北科瑞声科技股份有限公司,是由国家重大人才计划特聘专家刘轶博士创办的国内人工智能音频领军企业,专注于行业智能音频交互技术,拥有全链条核心自主知识产权,汇集世界一流的研发运营团队。" + } +``` + +服务器返回 + + SentenceBegin 检测到一句话开始 + +```JSON +{ + "header": { + "event": "SentenceBegin", + "msg": "success", + "status": 20000000, + "task_id": "b74e0800599135a5f8fe242991aea18f" + }, + "payload": { + "index": 0 + } +} +``` + + SentenceSynthesis (表示有新的合成结果返回) + +```JSON +{ + "header": { + "event": "SentenceSynthesis", + "msg": "success", + "status": 20000000, + "task_id": "b74e0800599135a5f8fe242991aea18f" + }, + "payload": { + "text": "" + } +} +``` + + SentenceEnd(服务端检测到了一句话的结束) + +```JSON +{ + "header": { + "event": "SentenceEnd", + "msg": "success", + "status": 20000000, + "task_id": "b74e0800599135a5f8fe242991aea18f" + }, + "payload": { + "text": "" + } +} +``` + + EndSynthesis (合成结束) + +```JSON +{ + "header": { + "event": "EndSynthesis", + "msg": "success", + "status": 20000000, + "task_id": "d724f070163b25b1c16da3671c939456" + } +} +``` + +- 播放服务管理 + - TTS服务生命周期内,保持播放服务开启,TTS 服务器收到音频合成数据后,可通过函数发送音频数据进行播放,只是根据播放控制来决定是否发送数据给播放服务。退出TTS服务,才停止录音服务。 +- 播放控制 + - 给上层UI逻辑控制用,对话开启,就可以播放,对话停止,就不能播放。 + - 开始和结束对话,要执行对应的消息给服务器。 +- TTS消息解析 + - TTS服务通过callback返回的消息,要区分是json数据还是二进制数据, + - json数据解析判定是什么事件(通过callback返回给Ai全家桶) + - 二进制数据 根据播放控制是否允许,决定是否发给音频服务的播放接口。(通过callback返回给Ai全家桶) + - + +1. ### 接口定义: + +1. ## Ai全家桶 + + 1. ### 功能描述: + + - Ai交互总入口,开启应用,直接开启ASR Server,Chat Server, TTS Server, 进行对话服务,直到退出服务才退出三个服务。 + + ​ 交互窗口, \ No newline at end of file diff --git "a/code/sdk/zhihui/ai/doc/\351\270\277\350\222\231ai\346\236\266\346\236\204\345\233\276.png" "b/code/sdk/zhihui/ai/doc/\351\270\277\350\222\231ai\346\236\266\346\236\204\345\233\276.png" new file mode 100644 index 000000000..a9d6bd1e2 Binary files /dev/null and "b/code/sdk/zhihui/ai/doc/\351\270\277\350\222\231ai\346\236\266\346\236\204\345\233\276.png" differ diff --git a/code/sdk/zhihui/ai/example/AI_AT_TEST_README.md b/code/sdk/zhihui/ai/example/AI_AT_TEST_README.md new file mode 100644 index 000000000..584e8ddd4 --- /dev/null +++ b/code/sdk/zhihui/ai/example/AI_AT_TEST_README.md @@ -0,0 +1,375 @@ +# AI AT Test Module - AI测试AT命令模块 + +## 概述 + +本模块从 `cat1_at_cmd.c` 中拆分出AI相关的测试功能,提供了一套完整的AT命令来测试ASR(语音识别)、TTS(文本转语音)、Chat(对话)和AI Media(AI媒体)服务。 + +## 文件结构 + +``` +ai/example/ +├── ai_at_test.h # AI AT测试模块头文件 +├── ai_at_test.c # AI AT测试模块实现 +├── ai_at_test_example.c # 使用示例 +└── AI_AT_TEST_README.md # 本说明文档 +``` + +## 功能特性 + +### 1. ASR (语音识别) 测试命令 + +| 命令 | 功能 | 参数 | +|------|------|------| +| `AT^ASRSTART` | 启动ASR服务 | 无 | +| `AT^ASRSTOP` | 停止ASR服务 | 无 | +| `AT^ASRRECORDTEST` | ASR录音测试 | 无 | +| `AT^ASRTASKSTART` | 启动ASR任务 | 无 | + +### 2. TTS (文本转语音) 测试命令 + +| 命令 | 功能 | 参数 | +|------|------|------| +| `AT^TTSSTART` | 启动TTS服务 | 无 | +| `AT^TTSSTOP` | 停止TTS服务 | 无 | +| `AT^TTSSAY=text` | 文本转语音 | text: 要转换的文本 | + +### 3. Chat (对话) 测试命令 + +| 命令 | 功能 | 参数 | +|------|------|------| +| `AT^CHATSEND=type,message` | 发送Chat消息 | type: 消息类型(0-文本)
message: 消息内容 | + +### 4. AI Media (AI媒体) 测试命令 + +| 命令 | 功能 | 参数 | +|------|------|------| +| `AT^AIRECORDSTART=format` | 启动AI录音 | format: 音频格式(pcm/mp3/opus) | +| `AT^AIRECORDSTOP` | 停止AI录音 | 无 | +| `AT^AIPLAYSTART=format` | 启动AI播放 | format: 音频格式(pcm/mp3/opus) | +| `AT^AIPLAYSTOP` | 停止AI播放 | 无 | +| `AT^AIMEDIASTATUS` | 查询AI媒体状态 | 无 | + +### 5. 综合测试命令 + +| 命令 | 功能 | 参数 | +|------|------|------| +| `AT^AISTATUS` | 查询AI服务状态 | 无 | +| `AT^AITEST=duration` | 完整AI测试 | duration: 测试时长(秒) | + +## 使用方法 + +### 1. 基本集成 + +```c +#include "ai_at_test.h" + +// 初始化AI AT测试模块 +int ret = ai_at_test_init(); +if (ret != 0) { + printf("Failed to initialize AI AT test module\n"); + return -1; +} + +// 获取AT命令表 +uint32_t cmd_count; +at_cmd_table_t *cmd_table = get_ai_at_cmd_table(&cmd_count); + +// 注册AT命令到系统AT命令处理器 +// (具体注册方法取决于系统的AT命令框架) + +// 反初始化 +ai_at_test_deinit(); +``` + +### 2. 与原有AT命令系统集成 + +在原有的 `cat1_at_cmd.c` 中添加AI命令: + +```c +#include "ai_at_test.h" + +// 在 get_cat1_cmd_table 函数中添加AI命令 +at_cmd_table_t *get_cat1_cmd_table(uint32_t *cmd_count) +{ + // 获取原有命令数量 + uint32_t cat1_cmd_count = ARRAY_COUNT(g_cat1_cmd); + + // 获取AI命令 + uint32_t ai_cmd_count; + at_cmd_table_t *ai_cmd_table = get_ai_at_cmd_table(&ai_cmd_count); + + // 合并命令表(需要动态分配内存) + // ... + + *cmd_count = cat1_cmd_count + ai_cmd_count; + return merged_table; +} +``` + +## 测试示例 + +### 1. ASR测试流程 + +```bash +# 1. 启动ASR服务 +AT^ASRSTART + +# 2. 启动ASR任务 +AT^ASRTASKSTART + +# 3. 进行录音测试 +AT^ASRRECORDTEST + +# 4. 停止ASR服务 +AT^ASRSTOP +``` + +### 2. TTS测试流程 + +```bash +# 1. 启动TTS服务 +AT^TTSSTART + +# 2. 文本转语音 +AT^TTSSAY=Hello, this is a TTS test + +# 3. 停止TTS服务 +AT^TTSSTOP +``` + +### 3. AI媒体测试流程 + +```bash +# 1. 启动录音 +AT^AIRECORDSTART=pcm + +# 2. 查询状态 +AT^AIMEDIASTATUS + +# 3. 停止录音 +AT^AIRECORDSTOP + +# 4. 启动播放 +AT^AIPLAYSTART=pcm + +# 5. 停止播放 +AT^AIPLAYSTOP +``` + +### 4. 完整语音对话测试 + +```bash +# 1. 查询AI服务状态 +AT^AISTATUS + +# 2. 启动ASR和录音 +AT^ASRSTART +AT^AIRECORDSTART=pcm + +# 3. (等待录音完成后) 停止录音 +AT^AIRECORDSTOP + +# 4. 发送识别结果到Chat +AT^CHATSEND=0,User said something + +# 5. 启动TTS播放回复 +AT^TTSSTART +AT^TTSSAY=AI response to user + +# 6. 清理服务 +AT^ASRSTOP +AT^TTSSTOP +``` + +### 5. 快速完整测试 + +```bash +# 运行10秒完整AI测试 +AT^AITEST=10 +``` + +## 配置选项 + +### 音频格式支持 + +- **PCM**: 未压缩的脉冲编码调制格式 +- **MP3**: MPEG Audio Layer III 压缩格式 +- **OPUS**: 开源的音频编解码器 + +### 消息类型 + +- **0**: 文本消息(用于Chat服务) + +## 错误处理 + +所有AT命令都会返回标准的错误码: + +- `ERRCODE_SUCC (0)`: 命令执行成功 +- `ERRCODE_FAIL (-1)`: 命令执行失败 + +详细的错误信息会通过日志输出,格式为:`[AI_AT] 错误描述` + +## 依赖项 + +### 必需的头文件 + +```c +#include "at_cmd_api.h" // AT命令API +#include "cat1_ws.h" // CAT1 WebSocket接口 +#include "ai_media.h" // AI媒体接口 +``` + +### 必需的函数 + +模块依赖以下外部函数,需要在链接时提供: + +```c +// ASR相关 +void uapi_start_asr(void); +void uapi_stop_asr(void); +void uapi_start_asr_task(void); +void asr_record_test(void); + +// TTS相关 +void uapi_start_tts(void); +void uapi_stop_tts(void); +bool uapi_start_tts_single_txt(char *buf); + +// Chat相关 +bool send_ai_txt(uint8_t type, char *msg); + +// AI Media相关 +void ai_media_record_start(uint8_t* format); +void ai_media_record_stop(void); +uint8_t ai_media_get_record_status(void); +void ai_media_play_start(uint8_t* format); +void ai_media_play_stop(void); +uint8_t ai_media_get_play_status(void); + +// 状态查询相关 +uint8_t get_ai_asr_service_init_flag(void); +uint8_t get_ai_chat_service_init_flag(void); +uint8_t get_ai_tts_server_init_flag(void); +``` + +## 编译配置 + +### CMakeLists.txt 示例 + +```cmake +# 添加AI AT测试模块 +set(AI_AT_TEST_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/ai/example/ai_at_test.c +) + +# 包含头文件路径 +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/ai/example + ${CMAKE_CURRENT_SOURCE_DIR}/ai/include +) + +# 添加到目标 +target_sources(your_target PRIVATE ${AI_AT_TEST_SOURCES}) +``` + +## 调试信息 + +### 日志级别 + +所有日志都使用 `[AI_AT]` 前缀,便于过滤和调试: + +``` +[AI_AT] Starting ASR service... +[AI_AT] ASR service start command sent +[AI_AT] TTS converting text to speech: Hello World +[AI_AT] AI media recording started successfully +``` + +### 调试技巧 + +1. **启用详细日志**: 确保系统日志级别允许 `printf` 输出 +2. **状态查询**: 使用 `AT^AISTATUS` 和 `AT^AIMEDIASTATUS` 查询服务状态 +3. **分步测试**: 先测试单个服务,再测试组合功能 +4. **错误检查**: 注意命令返回值和日志中的错误信息 + +## 扩展开发 + +### 添加新的AT命令 + +1. 在 `ai_at_test.c` 中添加命令处理函数: + +```c +static uint32_t at_new_command(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + // 命令处理逻辑 + return ERRCODE_SUCC; +} +``` + +2. 在 `g_ai_at_cmd_table` 中添加命令条目: + +```c +{ (char *)"AT^NEWCMD", at_new_command, (char *)"parameter_description" }, +``` + +### 自定义错误处理 + +可以修改错误处理逻辑,添加更详细的错误码和描述: + +```c +typedef enum { + AI_AT_ERROR_NONE = 0, + AI_AT_ERROR_PARAM_INVALID, + AI_AT_ERROR_SERVICE_NOT_READY, + AI_AT_ERROR_RESOURCE_BUSY, + // ... +} ai_at_error_t; +``` + +## 常见问题 + +### Q1: AT命令执行失败怎么办? + +**A**: 检查以下几点: +1. 确认相关AI服务已正确初始化 +2. 检查参数格式是否正确 +3. 查看系统日志中的详细错误信息 +4. 确认系统资源(内存、线程等)是否充足 + +### Q2: 如何集成到现有的AT命令系统? + +**A**: 有两种方式: +1. **静态集成**: 直接将AI命令添加到现有的命令表中 +2. **动态集成**: 运行时合并命令表,需要动态内存分配 + +### Q3: 音频格式不支持怎么办? + +**A**: +1. 检查底层AI媒体服务是否支持该格式 +2. 使用默认的PCM格式进行测试 +3. 根据需要扩展 `GetAudioFormatFromString` 函数 + +### Q4: 如何添加新的AI服务测试? + +**A**: +1. 在相应的AI服务模块中添加测试接口 +2. 在本模块中添加对应的AT命令处理函数 +3. 更新命令表和文档 + +## 版本历史 + +- **v1.0** (2024-12-28): 初始版本,从 `cat1_at_cmd.c` 拆分AI测试功能 + - 支持ASR、TTS、Chat、AI Media基本测试命令 + - 提供完整的使用示例和文档 + - 支持音频格式配置和状态查询 + +## 贡献指南 + +1. 遵循现有的代码风格和注释规范 +2. 添加新功能时同步更新文档 +3. 确保新增的AT命令有对应的测试示例 +4. 保持向后兼容性 + +## 许可证 + +Copyright (c) CompanyNameMagicTag. 2023-2023. All rights reserved. \ No newline at end of file diff --git a/code/sdk/zhihui/ai/example/CMakeLists.txt b/code/sdk/zhihui/ai/example/CMakeLists.txt new file mode 100644 index 000000000..c6dd88185 --- /dev/null +++ b/code/sdk/zhihui/ai/example/CMakeLists.txt @@ -0,0 +1,56 @@ +#=============================================================================== +# @brief cmake file +# Copyright (c) @CompanyNameMagicTag 2022-2022. All rights reserved. +#=============================================================================== + +set(COMPONENT_NAME "aiAtTest") + +set(PUBLIC_HEADER +) + +set(PRIVATE_HEADER +${ROOT_DIR}/zhihui/ai/include +${ROOT_DIR}/zhihui/ai/media +${ROOT_DIR}/zhihui/4gTest +) + +set(AI_SOURCES +${CMAKE_CURRENT_SOURCE_DIR}/ai_at_test.c +${CMAKE_CURRENT_SOURCE_DIR}/ai_at_test_example.c +) + +set(SOURCES + ${AI_SOURCES} +) + +set(COMPONENT_PUBLIC_CCFLAGS +) + +set(COMPONENT_CCFLAGS + -std=c99 + -Wall + -Wextra + -Werror + -Wno-unused-parameter + -fno-exceptions + -fno-unwind-tables + -fno-asynchronous-unwind-tables +) + +set(WHOLE_LINK + true +) + +set(MAIN_COMPONENT + false +) + +set(PUBLIC_DEFINES +AI_AT_TEST_VERSION_MAJOR=1 +AI_AT_TEST_VERSION_MINOR=0 +) + +set(PRIVATE_DEFINES +) + +build_component() \ No newline at end of file diff --git a/code/sdk/zhihui/ai/example/ai_at_test.c b/code/sdk/zhihui/ai/example/ai_at_test.c new file mode 100644 index 000000000..09a415945 --- /dev/null +++ b/code/sdk/zhihui/ai/example/ai_at_test.c @@ -0,0 +1,595 @@ +/* + * Copyright (c) CompanyNameMagicTag. 2023-2023. All rights reserved. + * Description: AI Service AT Command Test Module + * Author: AI Development Team + * Create: 2024-12-28 + */ + +#include "ai_at_test.h" +#include "at_cmd_api.h" +#include "cat1_ws.h" +#include "ai_media.h" +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ==================== 宏定义 ==================== */ + +#define AI_AT_MAX_ARGC 8 +#define AI_AT_MAX_ARGV_LEN 256 +#define AI_AT_SUCCESS 0 +#define AI_AT_FAILED (-1) + +/* ==================== 内部函数声明 ==================== */ + +static int ai_convert_cmd_to_param(uint8_t *para, uint32_t paraLen, + uint8_t argv[AI_AT_MAX_ARGC][AI_AT_MAX_ARGV_LEN], uint32_t* argc); + +/* ==================== 工具函数 ==================== */ + +/** + * @brief 将AT命令参数字符串转换为参数数组 + * @param para 输入参数字符串 + * @param paraLen 参数字符串长度 + * @param argv 输出参数数组 + * @param argc 输出参数个数 + * @return 0成功,-1失败 + */ +static int ai_convert_cmd_to_param(uint8_t *para, uint32_t paraLen, + uint8_t argv[AI_AT_MAX_ARGC][AI_AT_MAX_ARGV_LEN], uint32_t* argc) +{ + uint32_t pos = 0; + *argc = 0; + + for (uint32_t i = 0; i < paraLen; i++) { + if ((para[i] == ' ' || para[i] == ',' || para[i] == '\n') && pos != 0) { + argv[*argc][pos] = '\0'; + *argc += 1; + pos = 0; + if (*argc >= AI_AT_MAX_ARGC) { + return AI_AT_FAILED; + } + } else if (para[i] != ' ' && para[i] != ',' && para[i] != '\n') { + argv[*argc][pos] = para[i]; + pos++; + if (pos >= AI_AT_MAX_ARGV_LEN) { + return AI_AT_FAILED; + } + } + } + if (pos != 0) { + argv[*argc][pos] = '\0'; + *argc += 1; + } + + return AI_AT_SUCCESS; +} + +/* ==================== ASR 测试命令 ==================== */ + +/** + * @brief ASR服务启动AT命令处理函数 + * 格式: AT^ASRSTART + * 无参数 + */ +static uint32_t at_asr_start(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(paraLen); + UNUSED(strCommonCmd); + + printf("[AI_AT] Starting ASR service...\n"); + + uapi_start_asr(); + + printf("[AI_AT] ASR service start command sent\n"); + return ERRCODE_SUCC; +} + +/** + * @brief ASR服务停止AT命令处理函数 + * 格式: AT^ASRSTOP + * 无参数 + */ +static uint32_t at_asr_stop(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(paraLen); + UNUSED(strCommonCmd); + + printf("[AI_AT] Stopping ASR service...\n"); + + uapi_stop_asr(); + + printf("[AI_AT] ASR service stop command sent\n"); + return ERRCODE_SUCC; +} + +/** + * @brief ASR录音测试AT命令处理函数 + * 格式: AT^ASRRECORDTEST + * 无参数 + */ +static uint32_t at_asr_record_test(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(paraLen); + UNUSED(strCommonCmd); + + printf("[AI_AT] Starting ASR record test...\n"); + + asr_record_test(); + + printf("[AI_AT] ASR record test completed\n"); + return ERRCODE_SUCC; +} + +/** + * @brief ASR任务启动AT命令处理函数 + * 格式: AT^ASRTASKSTART + * 无参数 + */ +static uint32_t at_asr_task_start(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(paraLen); + UNUSED(strCommonCmd); + + printf("[AI_AT] Starting ASR task...\n"); + + uapi_start_asr_task(); + + printf("[AI_AT] ASR task start command sent\n"); + return ERRCODE_SUCC; +} + +/* ==================== TTS 测试命令 ==================== */ + +/** + * @brief TTS服务启动AT命令处理函数 + * 格式: AT^TTSSTART + * 无参数 + */ +static uint32_t at_tts_start(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(paraLen); + UNUSED(strCommonCmd); + + printf("[AI_AT] Starting TTS service...\n"); + + uapi_start_tts(); + + printf("[AI_AT] TTS service start command sent\n"); + return ERRCODE_SUCC; +} + +/** + * @brief TTS服务停止AT命令处理函数 + * 格式: AT^TTSSTOP + * 无参数 + */ +static uint32_t at_tts_stop(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(paraLen); + UNUSED(strCommonCmd); + + printf("[AI_AT] Stopping TTS service...\n"); + + uapi_stop_tts(); + + printf("[AI_AT] TTS service stop command sent\n"); + return ERRCODE_SUCC; +} + +/** + * @brief TTS单次文本转语音AT命令处理函数 + * 格式: AT^TTSSAY=text + * 参数: text - 要转换为语音的文本 + */ +static uint32_t at_tts_say(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(strCommonCmd); + + if (para == NULL || paraLen == 0) { + printf("[AI_AT] TTS text parameter is required\n"); + return ERRCODE_FAIL; + } + + // 确保文本以null结尾 + char *text = (char*)malloc(paraLen + 1); + if (text == NULL) { + printf("[AI_AT] Failed to allocate memory for TTS text\n"); + return ERRCODE_FAIL; + } + + memcpy(text, para, paraLen); + text[paraLen] = '\0'; + + printf("[AI_AT] TTS converting text to speech: %s\n", text); + + bool result = uapi_start_tts_single_txt(text); + + free(text); + + if (result) { + printf("[AI_AT] TTS text conversion started successfully\n"); + return ERRCODE_SUCC; + } else { + printf("[AI_AT] TTS text conversion failed\n"); + return ERRCODE_FAIL; + } +} + +/* ==================== Chat 测试命令 ==================== */ + +/** + * @brief Chat消息发送AT命令处理函数 + * 格式: AT^CHATSEND=type,message + * 参数: type - 消息类型 (0-文本消息) + * message - 要发送的消息内容 + */ +static uint32_t at_chat_send(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(strCommonCmd); + + uint8_t argv[AI_AT_MAX_ARGC][AI_AT_MAX_ARGV_LEN] = { 0 }; + uint32_t argc = 0; + + int ret = ai_convert_cmd_to_param(para, paraLen, argv, &argc); + if (ret != AI_AT_SUCCESS) { + printf("[AI_AT] Chat command parameter conversion failed\n"); + return ERRCODE_FAIL; + } + + if (argc != 2) { + printf("[AI_AT] Chat command requires 2 parameters: type,message\n"); + printf("Usage: AT^CHATSEND=0,Hello AI\n"); + return ERRCODE_FAIL; + } + + uint8_t type = (uint8_t)atoi((char*)argv[0]); + char *message = (char*)argv[1]; + + printf("[AI_AT] Sending chat message (type=%d): %s\n", type, message); + + bool result = send_ai_txt(type, message); + + if (result) { + printf("[AI_AT] Chat message sent successfully\n"); + return ERRCODE_SUCC; + } else { + printf("[AI_AT] Chat message send failed\n"); + return ERRCODE_FAIL; + } +} + +/* ==================== AI Media 测试命令 ==================== */ + +/** + * @brief AI录音启动AT命令处理函数 + * 格式: AT^AIRECORDSTART=format + * 参数: format - 音频格式 ("pcm", "mp3", "opus") + */ +static uint32_t at_ai_record_start(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(strCommonCmd); + + uint8_t *format = (uint8_t*)"pcm"; // 默认格式 + + if (para != NULL && paraLen > 0) { + // 使用用户指定的格式 + format = para; + // 确保字符串以null结尾 + if (para[paraLen - 1] != '\0') { + char *temp_format = (char*)malloc(paraLen + 1); + if (temp_format != NULL) { + memcpy(temp_format, para, paraLen); + temp_format[paraLen] = '\0'; + format = (uint8_t*)temp_format; + } + } + } + + printf("[AI_AT] Starting AI media recording with format: %s\n", (char*)format); + + ai_media_record_start(format); + + // 检查录音状态 + if (ai_media_get_record_status() == 1) { + printf("[AI_AT] AI media recording started successfully\n"); + + // 如果分配了临时内存,释放它 + if (para != NULL && paraLen > 0 && para[paraLen - 1] != '\0') { + free((char*)format); + } + + return ERRCODE_SUCC; + } else { + printf("[AI_AT] AI media recording start failed\n"); + + // 如果分配了临时内存,释放它 + if (para != NULL && paraLen > 0 && para[paraLen - 1] != '\0') { + free((char*)format); + } + + return ERRCODE_FAIL; + } +} + +/** + * @brief AI录音停止AT命令处理函数 + * 格式: AT^AIRECORDSTOP + * 无参数 + */ +static uint32_t at_ai_record_stop(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(paraLen); + UNUSED(strCommonCmd); + + printf("[AI_AT] Stopping AI media recording...\n"); + + ai_media_record_stop(); + + // 检查录音状态 + if (ai_media_get_record_status() == 0) { + printf("[AI_AT] AI media recording stopped successfully\n"); + return ERRCODE_SUCC; + } else { + printf("[AI_AT] AI media recording stop failed\n"); + return ERRCODE_FAIL; + } +} + +/** + * @brief AI播放启动AT命令处理函数 + * 格式: AT^AIPLAYSTART=format + * 参数: format - 音频格式 ("pcm", "mp3", "opus") + */ +static uint32_t at_ai_play_start(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(strCommonCmd); + + uint8_t *format = (uint8_t*)"pcm"; // 默认格式 + + if (para != NULL && paraLen > 0) { + // 使用用户指定的格式 + format = para; + // 确保字符串以null结尾 + if (para[paraLen - 1] != '\0') { + char *temp_format = (char*)malloc(paraLen + 1); + if (temp_format != NULL) { + memcpy(temp_format, para, paraLen); + temp_format[paraLen] = '\0'; + format = (uint8_t*)temp_format; + } + } + } + + printf("[AI_AT] Starting AI media playback with format: %s\n", (char*)format); + + ai_media_play_start(format); + + // 检查播放状态 + if (ai_media_get_play_status() == 1) { + printf("[AI_AT] AI media playback started successfully\n"); + + // 如果分配了临时内存,释放它 + if (para != NULL && paraLen > 0 && para[paraLen - 1] != '\0') { + free((char*)format); + } + + return ERRCODE_SUCC; + } else { + printf("[AI_AT] AI media playback start failed\n"); + + // 如果分配了临时内存,释放它 + if (para != NULL && paraLen > 0 && para[paraLen - 1] != '\0') { + free((char*)format); + } + + return ERRCODE_FAIL; + } +} + +/** + * @brief AI播放停止AT命令处理函数 + * 格式: AT^AIPLAYSTOP + * 无参数 + */ +static uint32_t at_ai_play_stop(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(paraLen); + UNUSED(strCommonCmd); + + printf("[AI_AT] Stopping AI media playback...\n"); + + ai_media_play_stop(); + + // 检查播放状态 + if (ai_media_get_play_status() == 0) { + printf("[AI_AT] AI media playback stopped successfully\n"); + return ERRCODE_SUCC; + } else { + printf("[AI_AT] AI media playback stop failed\n"); + return ERRCODE_FAIL; + } +} + +/** + * @brief AI媒体状态查询AT命令处理函数 + * 格式: AT^AIMEDIASTATUS + * 无参数 + */ +static uint32_t at_ai_media_status(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(paraLen); + UNUSED(strCommonCmd); + + printf("[AI_AT] AI Media Status Query\n"); + + uint8_t record_status = ai_media_get_record_status(); + uint8_t play_status = ai_media_get_play_status(); + + printf("[AI_AT] Recording Status: %s\n", record_status ? "Active" : "Stopped"); + printf("[AI_AT] Playback Status: %s\n", play_status ? "Active" : "Stopped"); + + return ERRCODE_SUCC; +} + +/* ==================== AI 综合测试命令 ==================== */ + +/** + * @brief AI服务状态查询AT命令处理函数 + * 格式: AT^AISTATUS + * 无参数 + */ +static uint32_t at_ai_status(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(paraLen); + UNUSED(strCommonCmd); + + printf("[AI_AT] AI Services Status Query\n"); + + // 查询各个服务的初始化状态 + uint8_t asr_init = get_ai_asr_service_init_flag(); + uint8_t chat_init = get_ai_chat_service_init_flag(); + uint8_t tts_init = get_ai_tts_server_init_flag(); + + printf("[AI_AT] ASR Service: %s\n", asr_init ? "Initialized" : "Not Initialized"); + printf("[AI_AT] Chat Service: %s\n", chat_init ? "Initialized" : "Not Initialized"); + printf("[AI_AT] TTS Service: %s\n", tts_init ? "Initialized" : "Not Initialized"); + + // 查询媒体服务状态 + uint8_t record_status = ai_media_get_record_status(); + uint8_t play_status = ai_media_get_play_status(); + + printf("[AI_AT] Media Recording: %s\n", record_status ? "Active" : "Stopped"); + printf("[AI_AT] Media Playback: %s\n", play_status ? "Active" : "Stopped"); + + return ERRCODE_SUCC; +} + +/** + * @brief AI完整测试流程AT命令处理函数 + * 格式: AT^AITEST=duration + * 参数: duration - 测试持续时间(秒),默认10秒 + */ +static uint32_t at_ai_full_test(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(strCommonCmd); + + uint32_t duration = 10; // 默认10秒 + + if (para != NULL && paraLen > 0) { + uint8_t argv[AI_AT_MAX_ARGC][AI_AT_MAX_ARGV_LEN] = { 0 }; + uint32_t argc = 0; + + int ret = ai_convert_cmd_to_param(para, paraLen, argv, &argc); + if (ret == AI_AT_SUCCESS && argc >= 1) { + duration = (uint32_t)atoi((char*)argv[0]); + if (duration == 0) { + duration = 10; // 防止0值 + } + } + } + + printf("[AI_AT] Starting AI full test for %u seconds...\n", duration); + + // 1. 启动ASR服务 + printf("[AI_AT] Step 1: Starting ASR service\n"); + uapi_start_asr(); + + // 2. 启动录音 + printf("[AI_AT] Step 2: Starting recording\n"); + ai_media_record_start((uint8_t*)"pcm"); + + // 3. 等待指定时间 + printf("[AI_AT] Step 3: Recording for %u seconds...\n", duration); + // 这里应该使用系统延时函数,简化处理 + for (uint32_t i = 0; i < duration; i++) { + printf("[AI_AT] Recording... %u/%u seconds\n", i + 1, duration); + // 实际应用中应该使用 osDelay 或类似函数 + // osDelay(1000); // 1秒延时 + } + + // 4. 停止录音 + printf("[AI_AT] Step 4: Stopping recording\n"); + ai_media_record_stop(); + + // 5. 停止ASR服务 + printf("[AI_AT] Step 5: Stopping ASR service\n"); + uapi_stop_asr(); + + // 6. 测试TTS + printf("[AI_AT] Step 6: Testing TTS\n"); + uapi_start_tts(); + bool tts_result = uapi_start_tts_single_txt("AI测试完成"); + if (tts_result) { + printf("[AI_AT] TTS test successful\n"); + } else { + printf("[AI_AT] TTS test failed\n"); + } + + printf("[AI_AT] AI full test completed\n"); + return ERRCODE_SUCC; +} + +/* ==================== AT命令表定义 ==================== */ + +static const at_cmd_table_t g_ai_at_cmd_table[] = { + // ASR 相关命令 + { (char *)"AT^ASRSTART", at_asr_start, (char *)"Start ASR service" }, + { (char *)"AT^ASRSTOP", at_asr_stop, (char *)"Stop ASR service" }, + { (char *)"AT^ASRRECORDTEST", at_asr_record_test, (char *)"ASR record test" }, + { (char *)"AT^ASRTASKSTART", at_asr_task_start, (char *)"Start ASR task" }, + + // TTS 相关命令 + { (char *)"AT^TTSSTART", at_tts_start, (char *)"Start TTS service" }, + { (char *)"AT^TTSSTOP", at_tts_stop, (char *)"Stop TTS service" }, + { (char *)"AT^TTSSAY", at_tts_say, (char *)"text" }, + + // Chat 相关命令 + { (char *)"AT^CHATSEND", at_chat_send, (char *)"type,message" }, + + // AI Media 相关命令 + { (char *)"AT^AIRECORDSTART", at_ai_record_start, (char *)"format" }, + { (char *)"AT^AIRECORDSTOP", at_ai_record_stop, (char *)"Stop AI recording" }, + { (char *)"AT^AIPLAYSTART", at_ai_play_start, (char *)"format" }, + { (char *)"AT^AIPLAYSTOP", at_ai_play_stop, (char *)"Stop AI playback" }, + { (char *)"AT^AIMEDIASTATUS", at_ai_media_status, (char *)"Query AI media status" }, + + // AI 综合命令 + { (char *)"AT^AISTATUS", at_ai_status, (char *)"Query AI services status" }, + { (char *)"AT^AITEST", at_ai_full_test, (char *)"duration" }, +}; + +/* ==================== 公共接口函数 ==================== */ + +/** + * @brief 获取AI AT命令表 + * @param cmd_count 输出命令数量 + * @return AT命令表指针 + */ +at_cmd_table_t *get_ai_at_cmd_table(uint32_t *cmd_count) +{ + if (cmd_count != NULL) { + *cmd_count = sizeof(g_ai_at_cmd_table) / sizeof(g_ai_at_cmd_table[0]); + } + return (at_cmd_table_t *)g_ai_at_cmd_table; +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/code/sdk/zhihui/ai/example/ai_at_test.h b/code/sdk/zhihui/ai/example/ai_at_test.h new file mode 100644 index 000000000..876efdc85 --- /dev/null +++ b/code/sdk/zhihui/ai/example/ai_at_test.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) CompanyNameMagicTag. 2023-2023. All rights reserved. + * Description: AI Service AT Command Test Module Header + * Author: AI Development Team + * Create: 2024-12-28 + */ + +#ifndef AI_AT_TEST_H +#define AI_AT_TEST_H + +#include +#include "at_cmd_api.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ==================== 宏定义 ==================== */ + +#ifndef UNUSED +#define UNUSED(x) ((void)(x)) +#endif + +#ifndef ERRCODE_SUCC +#define ERRCODE_SUCC 0 +#endif + +#ifndef ERRCODE_FAIL +#define ERRCODE_FAIL (-1) +#endif + +/* ==================== 类型定义 ==================== */ + +/** + * @brief AT命令表结构体 + */ +typedef struct { + char *at_cmd_name; // AT命令名称 + uint32_t (*at_cmd_func)(uint8_t *para, uint32_t paraLen, char *strCommonCmd); // 命令处理函数 + char *param_name; // 参数说明 +} at_cmd_table_t; + +/* ==================== 函数声明 ==================== */ + +/** + * @brief 获取AI AT命令表 + * @param cmd_count 输出命令数量指针 + * @return AT命令表指针 + */ +at_cmd_table_t *get_ai_at_cmd_table(uint32_t *cmd_count); + +/** + * @brief 初始化AI AT命令模块 + * @return 0成功,-1失败 + */ +int ai_at_test_init(void); + +/** + * @brief 反初始化AI AT命令模块 + */ +void ai_at_test_deinit(void); + +/* ==================== AI服务状态查询函数声明 ==================== */ + +/** + * @brief 获取ASR服务初始化标志 + * @return 初始化状态 (0: 未初始化, 1: 已初始化) + */ +uint8_t get_ai_asr_service_init_flag(void); + +/** + * @brief 获取Chat服务初始化标志 + * @return 初始化状态 (0: 未初始化, 1: 已初始化) + */ +uint8_t get_ai_chat_service_init_flag(void); + +/** + * @brief 获取TTS服务初始化标志 + * @return 初始化状态 (0: 未初始化, 1: 已初始化) + */ +uint8_t get_ai_tts_server_init_flag(void); + +#ifdef __cplusplus +} +#endif + +#endif /* AI_AT_TEST_H */ \ No newline at end of file diff --git a/code/sdk/zhihui/ai/example/ai_at_test_example.c b/code/sdk/zhihui/ai/example/ai_at_test_example.c new file mode 100644 index 000000000..b9a887e1c --- /dev/null +++ b/code/sdk/zhihui/ai/example/ai_at_test_example.c @@ -0,0 +1,276 @@ +/* + * Copyright (c) CompanyNameMagicTag. 2023-2023. All rights reserved. + * Description: AI AT Test Commands Usage Example + * Author: AI Development Team + * Create: 2024-12-28 + */ + +#include "ai_at_test.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ==================== 示例函数 ==================== */ + +/** + * @brief 演示基本ASR测试流程 + */ +void example_asr_basic_test(void) +{ + printf("\n========== ASR Basic Test Example ==========\n"); + + // 1. 启动ASR服务 + printf("1. Starting ASR service...\n"); + // 模拟AT命令: AT^ASRSTART + + // 2. 启动ASR任务 + printf("2. Starting ASR task...\n"); + // 模拟AT命令: AT^ASRTASKSTART + + // 3. 进行录音测试 + printf("3. Running ASR record test...\n"); + // 模拟AT命令: AT^ASRRECORDTEST + + // 4. 停止ASR服务 + printf("4. Stopping ASR service...\n"); + // 模拟AT命令: AT^ASRSTOP + + printf("ASR basic test completed!\n"); +} + +/** + * @brief 演示基本TTS测试流程 + */ +void example_tts_basic_test(void) +{ + printf("\n========== TTS Basic Test Example ==========\n"); + + // 1. 启动TTS服务 + printf("1. Starting TTS service...\n"); + // 模拟AT命令: AT^TTSSTART + + // 2. 文本转语音测试 + printf("2. Converting text to speech...\n"); + // 模拟AT命令: AT^TTSSAY=Hello, this is a TTS test + + // 3. 停止TTS服务 + printf("3. Stopping TTS service...\n"); + // 模拟AT命令: AT^TTSSTOP + + printf("TTS basic test completed!\n"); +} + +/** + * @brief 演示Chat测试流程 + */ +void example_chat_test(void) +{ + printf("\n========== Chat Test Example ==========\n"); + + // 1. 发送文本消息 + printf("1. Sending chat message...\n"); + // 模拟AT命令: AT^CHATSEND=0,How are you today? + + // 2. 发送另一条消息 + printf("2. Sending another chat message...\n"); + // 模拟AT命令: AT^CHATSEND=0,What's the weather like? + + printf("Chat test completed!\n"); +} + +/** + * @brief 演示AI媒体服务测试流程 + */ +void example_ai_media_test(void) +{ + printf("\n========== AI Media Test Example ==========\n"); + + // 1. 启动录音 + printf("1. Starting AI recording with PCM format...\n"); + // 模拟AT命令: AT^AIRECORDSTART=pcm + + // 2. 查询媒体状态 + printf("2. Checking AI media status...\n"); + // 模拟AT命令: AT^AIMEDIASTATUS + + // 3. 停止录音 + printf("3. Stopping AI recording...\n"); + // 模拟AT命令: AT^AIRECORDSTOP + + // 4. 启动播放 + printf("4. Starting AI playback with PCM format...\n"); + // 模拟AT命令: AT^AIPLAYSTART=pcm + + // 5. 停止播放 + printf("5. Stopping AI playback...\n"); + // 模拟AT命令: AT^AIPLAYSTOP + + printf("AI media test completed!\n"); +} + +/** + * @brief 演示完整AI测试流程 + */ +void example_full_ai_test(void) +{ + printf("\n========== Full AI Test Example ==========\n"); + + // 1. 查询AI服务状态 + printf("1. Checking AI services status...\n"); + // 模拟AT命令: AT^AISTATUS + + // 2. 运行完整AI测试(10秒) + printf("2. Running full AI test for 10 seconds...\n"); + // 模拟AT命令: AT^AITEST=10 + + // 3. 再次查询状态 + printf("3. Checking AI services status again...\n"); + // 模拟AT命令: AT^AISTATUS + + printf("Full AI test completed!\n"); +} + +/** + * @brief 演示语音对话流程 + */ +void example_voice_conversation_test(void) +{ + printf("\n========== Voice Conversation Test Example ==========\n"); + + // 1. 启动ASR和录音 + printf("1. Starting ASR and recording...\n"); + // AT^ASRSTART + // AT^AIRECORDSTART=pcm + + // 2. 模拟录音5秒 + printf("2. Recording for 5 seconds...\n"); + // (等待录音) + + // 3. 停止录音 + printf("3. Stopping recording...\n"); + // AT^AIRECORDSTOP + + // 4. 发送识别结果到Chat + printf("4. Sending ASR result to Chat...\n"); + // AT^CHATSEND=0,User said something + + // 5. 启动TTS播放回复 + printf("5. Starting TTS for response...\n"); + // AT^TTSSTART + // AT^TTSSAY=AI response to user + + // 6. 清理 + printf("6. Cleaning up services...\n"); + // AT^ASRSTOP + // AT^TTSSTOP + + printf("Voice conversation test completed!\n"); +} + +/** + * @brief 打印所有可用的AI AT命令 + */ +void example_print_available_commands(void) +{ + printf("\n========== Available AI AT Commands ==========\n"); + + printf("ASR Commands:\n"); + printf(" AT^ASRSTART - Start ASR service\n"); + printf(" AT^ASRSTOP - Stop ASR service\n"); + printf(" AT^ASRRECORDTEST - Run ASR record test\n"); + printf(" AT^ASRTASKSTART - Start ASR task\n"); + printf("\n"); + + printf("TTS Commands:\n"); + printf(" AT^TTSSTART - Start TTS service\n"); + printf(" AT^TTSSTOP - Stop TTS service\n"); + printf(" AT^TTSSAY=text - Convert text to speech\n"); + printf("\n"); + + printf("Chat Commands:\n"); + printf(" AT^CHATSEND=type,message - Send chat message\n"); + printf("\n"); + + printf("AI Media Commands:\n"); + printf(" AT^AIRECORDSTART=format - Start AI recording\n"); + printf(" AT^AIRECORDSTOP - Stop AI recording\n"); + printf(" AT^AIPLAYSTART=format - Start AI playback\n"); + printf(" AT^AIPLAYSTOP - Stop AI playback\n"); + printf(" AT^AIMEDIASTATUS - Query AI media status\n"); + printf("\n"); + + printf("General AI Commands:\n"); + printf(" AT^AISTATUS - Query AI services status\n"); + printf(" AT^AITEST=duration - Run full AI test\n"); + printf("\n"); + + printf("Format options: pcm, mp3, opus\n"); + printf("Message types: 0 (text message)\n"); +} + +/** + * @brief 主示例函数 + */ +int ai_at_test_example_main(void) +{ + printf("========== AI AT Test Examples ==========\n"); + + // 初始化AI AT测试模块 + if (ai_at_test_init() != 0) { + printf("Failed to initialize AI AT test module\n"); + return -1; + } + + // 打印可用命令 + example_print_available_commands(); + + // 运行各种测试示例 + example_asr_basic_test(); + example_tts_basic_test(); + example_chat_test(); + example_ai_media_test(); + example_full_ai_test(); + example_voice_conversation_test(); + + // 反初始化 + ai_at_test_deinit(); + + printf("\n========== All AI AT Test Examples Completed ==========\n"); + return 0; +} + +/** + * @brief 测试特定AT命令的示例 + */ +void example_test_specific_command(void) +{ + printf("\n========== Testing Specific AT Commands ==========\n"); + + uint32_t cmd_count; + at_cmd_table_t *cmd_table = get_ai_at_cmd_table(&cmd_count); + + if (cmd_table != NULL) { + printf("Found %u AI AT commands:\n", cmd_count); + for (uint32_t i = 0; i < cmd_count; i++) { + printf(" [%u] %s - %s\n", i + 1, + cmd_table[i].at_cmd_name, + cmd_table[i].param_name); + } + + // 示例:测试第一个命令 + if (cmd_count > 0) { + printf("\nTesting first command: %s\n", cmd_table[0].at_cmd_name); + // 这里可以调用具体的命令处理函数进行测试 + // cmd_table[0].at_cmd_func(NULL, 0, NULL); + } + } else { + printf("Failed to get AI AT command table\n"); + } +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/code/sdk/zhihui/ai/example/ai_service_usage_example.c b/code/sdk/zhihui/ai/example/ai_service_usage_example.c new file mode 100644 index 000000000..f2a7d6448 --- /dev/null +++ b/code/sdk/zhihui/ai/example/ai_service_usage_example.c @@ -0,0 +1,450 @@ +/* + * Copyright (c) CompanyNameMagicTag. 2023-2023. All rights reserved. + * Description: AI Service Usage Example - AI全家桶使用示例 + * Author: AI Development Team + * Create: 2024-12-28 + */ + +#include +#include +#include +#include "ai_service_manager.h" +#include "../../../../application/wearable/ohos_deps/wearable_log.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ==================== 示例配置 ==================== */ + +#define EXAMPLE_LOG_TAG "AI_EXAMPLE" + +// 服务器配置 +#define ASR_SERVER_URL "ws://amp.raisound.com/asr/ws" +#define CHAT_SERVER_URL "ws://chat.example.com/chat" +#define TTS_SERVER_URL "ws://tts.example.com/tts" + +/* ==================== 全局变量 ==================== */ + +static ai_manager_context_t g_ai_manager; +static bool g_conversation_active = false; +static bool g_listening = false; + +/* ==================== 事件回调函数 ==================== */ + +/** + * @brief AI事件回调函数 + */ +void on_ai_event(void *user_data, const ai_event_data_t *event) +{ + if (event == NULL) { + return; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Event: %s, Session: %s", + EXAMPLE_LOG_TAG, + ai_manager_event_type_to_string(event->type), + event->session_id); + + switch (event->type) { + case AI_EVENT_CONVERSATION_START: + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 对话开始", EXAMPLE_LOG_TAG); + g_conversation_active = true; + break; + + case AI_EVENT_SPEECH_START: + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 开始语音输入", EXAMPLE_LOG_TAG); + g_listening = true; + break; + + case AI_EVENT_SPEECH_PARTIAL: + if (event->text != NULL) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 识别中: %s (置信度: %.2f)", + EXAMPLE_LOG_TAG, event->text, event->confidence); + } + break; + + case AI_EVENT_SPEECH_END: + if (event->text != NULL) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 识别完成: %s (置信度: %.2f)", + EXAMPLE_LOG_TAG, event->text, event->confidence); + } + g_listening = false; + break; + + case AI_EVENT_CHAT_RESPONSE_START: + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] AI开始回复", EXAMPLE_LOG_TAG); + break; + + case AI_EVENT_CHAT_RESPONSE_PARTIAL: + if (event->text != NULL) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] AI回复中: %s", + EXAMPLE_LOG_TAG, event->text); + } + break; + + case AI_EVENT_CHAT_RESPONSE_END: + if (event->text != NULL) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] AI回复完成: %s", + EXAMPLE_LOG_TAG, event->text); + } + break; + + case AI_EVENT_TTS_START: + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 开始语音播放", EXAMPLE_LOG_TAG); + break; + + case AI_EVENT_TTS_END: + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 语音播放完成", EXAMPLE_LOG_TAG); + break; + + case AI_EVENT_CONVERSATION_END: + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 对话结束", EXAMPLE_LOG_TAG); + g_conversation_active = false; + g_listening = false; + break; + + case AI_EVENT_ERROR: + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] 错误: %d - %s", + EXAMPLE_LOG_TAG, event->error_code, + event->error_message ? event->error_message : "Unknown"); + break; + + default: + break; + } +} + +/* ==================== 示例函数 ==================== */ + +/** + * @brief 初始化AI服务示例 + */ +int ai_service_example_init(void) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 初始化AI服务示例", EXAMPLE_LOG_TAG); + + // 创建默认配置 + ai_manager_config_t config; + ai_manager_create_default_config(&config); + + // 设置服务器地址 + strcpy(config.asr_server_url, ASR_SERVER_URL); + config.asr_server_port = 80; + strcpy(config.chat_server_url, CHAT_SERVER_URL); + config.chat_server_port = 80; + strcpy(config.tts_server_url, TTS_SERVER_URL); + config.tts_server_port = 80; + + // 设置超时和重试 + config.connect_timeout_ms = 30000; + config.response_timeout_ms = 60000; + config.auto_reconnect = true; + config.max_retry_count = 3; + + // 初始化AI管理器 + ai_service_error_t ret = ai_manager_init(&g_ai_manager, &config); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] 初始化AI管理器失败: %d", EXAMPLE_LOG_TAG, ret); + return -1; + } + + // 设置事件回调 + ai_manager_set_event_callback(&g_ai_manager, on_ai_event, NULL); + + // 启动AI服务 + ret = ai_manager_start(&g_ai_manager); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] 启动AI服务失败: %d", EXAMPLE_LOG_TAG, ret); + ai_manager_destroy(&g_ai_manager); + return -1; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] AI服务初始化成功", EXAMPLE_LOG_TAG); + return 0; +} + +/** + * @brief 销毁AI服务示例 + */ +void ai_service_example_deinit(void) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 销毁AI服务示例", EXAMPLE_LOG_TAG); + + // 结束当前对话 + if (g_conversation_active) { + ai_manager_end_conversation(&g_ai_manager); + } + + // 停止AI服务 + ai_manager_stop(&g_ai_manager); + + // 销毁AI管理器 + ai_manager_destroy(&g_ai_manager); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] AI服务销毁完成", EXAMPLE_LOG_TAG); +} + +/** + * @brief 开始语音对话示例 + */ +int ai_service_example_start_voice_conversation(void) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 开始语音对话", EXAMPLE_LOG_TAG); + + // 检查服务状态 + if (!ai_manager_is_service_ready(&g_ai_manager, AI_SERVICE_TYPE_ALL)) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] AI服务未就绪", EXAMPLE_LOG_TAG); + return -1; + } + + // 开始对话 + ai_service_error_t ret = ai_manager_start_conversation(&g_ai_manager, "voice_session_001"); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] 开始对话失败: %d", EXAMPLE_LOG_TAG, ret); + return -1; + } + + // 开始语音输入 + ret = ai_manager_start_speech_input(&g_ai_manager); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] 开始语音输入失败: %d", EXAMPLE_LOG_TAG, ret); + ai_manager_end_conversation(&g_ai_manager); + return -1; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 语音对话已开始,请说话...", EXAMPLE_LOG_TAG); + return 0; +} + +/** + * @brief 停止语音输入示例 + */ +int ai_service_example_stop_voice_input(void) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 停止语音输入", EXAMPLE_LOG_TAG); + + if (!g_listening) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] 当前未在监听", EXAMPLE_LOG_TAG); + return -1; + } + + ai_service_error_t ret = ai_manager_stop_speech_input(&g_ai_manager); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] 停止语音输入失败: %d", EXAMPLE_LOG_TAG, ret); + return -1; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 语音输入已停止", EXAMPLE_LOG_TAG); + return 0; +} + +/** + * @brief 发送文本消息示例 + */ +int ai_service_example_send_text(const char *text) +{ + if (text == NULL || strlen(text) == 0) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] 文本内容为空", EXAMPLE_LOG_TAG); + return -1; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 发送文本消息: %s", EXAMPLE_LOG_TAG, text); + + // 如果没有活跃对话,先开始对话 + if (!g_conversation_active) { + ai_service_error_t ret = ai_manager_start_conversation(&g_ai_manager, "text_session_001"); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] 开始对话失败: %d", EXAMPLE_LOG_TAG, ret); + return -1; + } + } + + // 发送文本消息 + ai_service_error_t ret = ai_manager_send_text_message(&g_ai_manager, text); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] 发送文本消息失败: %d", EXAMPLE_LOG_TAG, ret); + return -1; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 文本消息已发送", EXAMPLE_LOG_TAG); + return 0; +} + +/** + * @brief 停止TTS播放示例 + */ +int ai_service_example_stop_tts(void) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 停止TTS播放", EXAMPLE_LOG_TAG); + + ai_service_error_t ret = ai_manager_stop_tts_playback(&g_ai_manager); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] 停止TTS播放失败: %d", EXAMPLE_LOG_TAG, ret); + return -1; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS播放已停止", EXAMPLE_LOG_TAG); + return 0; +} + +/** + * @brief 结束对话示例 + */ +int ai_service_example_end_conversation(void) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 结束对话", EXAMPLE_LOG_TAG); + + if (!g_conversation_active) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] 当前没有活跃对话", EXAMPLE_LOG_TAG); + return -1; + } + + ai_service_error_t ret = ai_manager_end_conversation(&g_ai_manager); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] 结束对话失败: %d", EXAMPLE_LOG_TAG, ret); + return -1; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 对话已结束", EXAMPLE_LOG_TAG); + return 0; +} + +/** + * @brief 获取服务状态示例 + */ +void ai_service_example_print_status(void) +{ + ai_manager_state_t state = ai_manager_get_state(&g_ai_manager); + const ai_manager_stats_t *stats = ai_manager_get_stats(&g_ai_manager); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ========== AI服务状态 ==========", EXAMPLE_LOG_TAG); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 当前状态: %s", EXAMPLE_LOG_TAG, ai_manager_state_to_string(state)); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 对话活跃: %s", EXAMPLE_LOG_TAG, g_conversation_active ? "是" : "否"); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 正在监听: %s", EXAMPLE_LOG_TAG, g_listening ? "是" : "否"); + + if (stats != NULL) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 对话次数: %u", EXAMPLE_LOG_TAG, stats->conversation_count); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ASR识别次数: %u", EXAMPLE_LOG_TAG, stats->asr_recognition_count); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Chat请求次数: %u", EXAMPLE_LOG_TAG, stats->chat_request_count); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS合成次数: %u", EXAMPLE_LOG_TAG, stats->tts_synthesis_count); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 总错误数: %u", EXAMPLE_LOG_TAG, stats->total_errors); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 平均响应时间: %.2f ms", EXAMPLE_LOG_TAG, stats->avg_response_time_ms); + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ASR服务就绪: %s", EXAMPLE_LOG_TAG, + ai_manager_is_service_ready(&g_ai_manager, AI_SERVICE_TYPE_ASR) ? "是" : "否"); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Chat服务就绪: %s", EXAMPLE_LOG_TAG, + ai_manager_is_service_ready(&g_ai_manager, AI_SERVICE_TYPE_CHAT) ? "是" : "否"); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS服务就绪: %s", EXAMPLE_LOG_TAG, + ai_manager_is_service_ready(&g_ai_manager, AI_SERVICE_TYPE_TTS) ? "是" : "否"); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ================================", EXAMPLE_LOG_TAG); +} + +/** + * @brief 完整对话流程示例 + */ +int ai_service_example_full_conversation_demo(void) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ========== 完整对话流程演示 ==========", EXAMPLE_LOG_TAG); + + // 1. 开始语音对话 + if (ai_service_example_start_voice_conversation() != 0) { + return -1; + } + + // 模拟等待用户说话 + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 模拟用户说话 3 秒...", EXAMPLE_LOG_TAG); + osDelay(3000); + + // 2. 停止语音输入 + if (ai_service_example_stop_voice_input() != 0) { + return -1; + } + + // 模拟等待AI回复 + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 等待AI回复...", EXAMPLE_LOG_TAG); + osDelay(2000); + + // 3. 发送文本消息 + if (ai_service_example_send_text("请介绍一下你自己") != 0) { + return -1; + } + + // 模拟等待AI回复 + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 等待AI文本回复...", EXAMPLE_LOG_TAG); + osDelay(3000); + + // 4. 再次语音输入 + if (ai_manager_start_speech_input(&g_ai_manager) == AI_SERVICE_OK) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 再次开始语音输入...", EXAMPLE_LOG_TAG); + osDelay(2000); + ai_manager_stop_speech_input(&g_ai_manager); + } + + // 5. 打印状态 + ai_service_example_print_status(); + + // 6. 结束对话 + if (ai_service_example_end_conversation() != 0) { + return -1; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ========== 对话流程演示完成 ==========", EXAMPLE_LOG_TAG); + return 0; +} + +/** + * @brief 主示例函数 + */ +int ai_service_example_main(void) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ========== AI服务使用示例 ==========", EXAMPLE_LOG_TAG); + + // 初始化AI服务 + if (ai_service_example_init() != 0) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] AI服务初始化失败", EXAMPLE_LOG_TAG); + return -1; + } + + // 等待服务就绪 + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 等待服务就绪...", EXAMPLE_LOG_TAG); + int retry_count = 0; + while (!ai_manager_is_service_ready(&g_ai_manager, AI_SERVICE_TYPE_ALL) && retry_count < 30) { + osDelay(1000); + retry_count++; + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 等待服务就绪... (%d/30)", EXAMPLE_LOG_TAG, retry_count); + } + + if (!ai_manager_is_service_ready(&g_ai_manager, AI_SERVICE_TYPE_ALL)) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] 服务未能就绪,退出示例", EXAMPLE_LOG_TAG); + ai_service_example_deinit(); + return -1; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 所有服务已就绪!", EXAMPLE_LOG_TAG); + + // 运行完整对话演示 + ai_service_example_full_conversation_demo(); + + // 额外的单独功能测试 + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] 测试单独文本对话...", EXAMPLE_LOG_TAG); + ai_service_example_send_text("今天天气怎么样?"); + osDelay(3000); + + ai_service_example_send_text("谢谢你的回答"); + osDelay(2000); + + // 最终状态打印 + ai_service_example_print_status(); + + // 清理资源 + ai_service_example_deinit(); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ========== 示例运行完成 ==========", EXAMPLE_LOG_TAG); + return 0; +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/code/sdk/zhihui/ai/example/ai_services_example.c b/code/sdk/zhihui/ai/example/ai_services_example.c new file mode 100644 index 000000000..0d5cd8ae6 --- /dev/null +++ b/code/sdk/zhihui/ai/example/ai_services_example.c @@ -0,0 +1,484 @@ +/* + * AI服务集成示例代码 + * 演示ASR、TTS、Chat三个服务的协同工作 + */ + +#include +#include +#include +#include "asr_server.h" +#include "tts_server.h" +#include "chat_server.h" +#include "../../../../../application/wearable/ohos_deps/wearable_log.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* 示例配置 */ +#define EXAMPLE_LOG_TAG "AI_EXAMPLE" +#define ASR_SERVER_URL "ws://asr.example.com/ws" +#define TTS_SERVER_URL "ws://tts.example.com/ws" +#define CHAT_SERVER_URL "ws://chat.example.com/ws" + +/* 全局服务上下文 */ +static asr_server_context_t g_asr_context; +static tts_server_context_t g_tts_context; +static chat_server_context_t g_chat_context; + +/* 示例音频数据(模拟录音) */ +static const char g_sample_audio[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + // ... 更多音频数据 +}; + +/* 回调函数声明 */ +static void on_asr_recognition_result(void *context, const asr_recognition_result_t *result); +static void on_asr_recognition_start(void *context, const char *session_id); +static void on_asr_recognition_end(void *context, const char *session_id); +static void on_asr_audio_level(void *context, float level); + +static void on_tts_synthesis_start(void *context, const char *session_id); +static void on_tts_synthesis_result(void *context, const tts_synthesis_result_t *result); +static void on_tts_synthesis_complete(void *context, const char *session_id); +static void on_tts_playback_start(void *context); +static void on_tts_playback_complete(void *context); +static void on_tts_playback_error(void *context, int error_code); + +static void on_chat_start(void *context, const char *session_id); +static void on_chat_response(void *context, const chat_response_t *response); +static void on_chat_complete(void *context, const char *session_id); +static void on_chat_streaming(void *context, const char *session_id, const char *partial_content); + +/* TTS接口函数 */ +static ai_service_error_t tts_send_text_interface(const char *text, const char *session_id); + +/* 示例函数声明 */ +static ai_service_error_t init_all_services(void); +static ai_service_error_t start_all_services(void); +static ai_service_error_t stop_all_services(void); +static ai_service_error_t destroy_all_services(void); +static void run_voice_conversation_example(void); +static void run_text_to_speech_example(void); +static void run_speech_recognition_example(void); +static void print_service_stats(void); +bool pre_AI_init(void) +{ + if(read_dev_token(NULL)==false) + { + return aut_login(); + } +} +/* 主函数 */ +int ai_services_example_main(void) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] AI Services Example Started", EXAMPLE_LOG_TAG); + + if (pre_AI_init()) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to initialize AI services", EXAMPLE_LOG_TAG); + return -1; + } + + // 初始化所有服务 + ai_service_error_t ret = init_all_services(); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to initialize services: %d", EXAMPLE_LOG_TAG, ret); + return -1; + } + + // 启动所有服务 + ret = start_all_services(); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to start services: %d", EXAMPLE_LOG_TAG, ret); + destroy_all_services(); + return -1; + } + + // 等待服务连接 + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Waiting for services to connect...", EXAMPLE_LOG_TAG); + osDelay(3000); // 等待3秒 + + // 运行示例 + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Running examples...", EXAMPLE_LOG_TAG); + + // 1. 语音识别示例 + run_speech_recognition_example(); + osDelay(2000); + + // 2. 文本转语音示例 + run_text_to_speech_example(); + osDelay(2000); + + // 3. 语音对话示例 + run_voice_conversation_example(); + osDelay(5000); + + // 打印统计信息 + print_service_stats(); + + // 停止和销毁服务 + stop_all_services(); + destroy_all_services(); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] AI Services Example Completed", EXAMPLE_LOG_TAG); + + return 0; +} + +/* 服务初始化函数 */ +static ai_service_error_t init_all_services(void) +{ + ai_service_error_t ret; + + // 初始化ASR服务 + asr_audio_config_t asr_audio_config; + asr_server_create_default_audio_config(&asr_audio_config); + + asr_recognition_config_t asr_recognition_config; + asr_server_create_default_recognition_config(&asr_recognition_config); + + ret = asr_server_init(&g_asr_context, ASR_SERVER_URL, &asr_audio_config, &asr_recognition_config); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to init ASR server: %d", EXAMPLE_LOG_TAG, ret); + return ret; + } + + // 设置ASR回调 + asr_server_set_callbacks(&g_asr_context, + on_asr_recognition_result, + on_asr_recognition_start, + on_asr_recognition_end, + on_asr_audio_level); + + // 初始化TTS服务 + tts_audio_config_t tts_audio_config; + tts_server_create_default_audio_config(&tts_audio_config); + + tts_voice_config_t tts_voice_config; + tts_server_create_default_voice_config(&tts_voice_config); + + ret = tts_server_init(&g_tts_context, TTS_SERVER_URL, &tts_audio_config, &tts_voice_config); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to init TTS server: %d", EXAMPLE_LOG_TAG, ret); + asr_server_destroy(&g_asr_context); + return ret; + } + + // 设置TTS回调 + tts_server_set_callbacks(&g_tts_context, + on_tts_synthesis_start, + on_tts_synthesis_result, + on_tts_synthesis_complete, + on_tts_playback_start, + on_tts_playback_complete, + on_tts_playback_error); + + // 初始化Chat服务 + chat_model_config_t chat_model_config; + chat_server_create_default_model_config(&chat_model_config); + + ret = chat_server_init(&g_chat_context, CHAT_SERVER_URL, &chat_model_config); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to init Chat server: %d", EXAMPLE_LOG_TAG, ret); + tts_server_destroy(&g_tts_context); + asr_server_destroy(&g_asr_context); + return ret; + } + + // 设置Chat回调 + chat_server_set_callbacks(&g_chat_context, + on_chat_start, + on_chat_response, + on_chat_complete, + on_chat_streaming); + + // 设置Chat到TTS的接口 + chat_server_set_tts_interface(&g_chat_context, tts_send_text_interface); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] All services initialized", EXAMPLE_LOG_TAG); + + return AI_SERVICE_OK; +} + +static ai_service_error_t start_all_services(void) +{ + ai_service_error_t ret; + + // 启动ASR服务 + ret = asr_server_start(&g_asr_context); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to start ASR server: %d", EXAMPLE_LOG_TAG, ret); + return ret; + } + + // 启动TTS服务 + ret = tts_server_start(&g_tts_context); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to start TTS server: %d", EXAMPLE_LOG_TAG, ret); + asr_server_stop(&g_asr_context); + return ret; + } + + // 启动Chat服务 + ret = chat_server_start(&g_chat_context); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to start Chat server: %d", EXAMPLE_LOG_TAG, ret); + tts_server_stop(&g_tts_context); + asr_server_stop(&g_asr_context); + return ret; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] All services started", EXAMPLE_LOG_TAG); + + return AI_SERVICE_OK; +} + +static ai_service_error_t stop_all_services(void) +{ + // 停止所有服务 + chat_server_stop(&g_chat_context); + tts_server_stop(&g_tts_context); + asr_server_stop(&g_asr_context); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] All services stopped", EXAMPLE_LOG_TAG); + + return AI_SERVICE_OK; +} + +static ai_service_error_t destroy_all_services(void) +{ + // 销毁所有服务 + chat_server_destroy(&g_chat_context); + tts_server_destroy(&g_tts_context); + asr_server_destroy(&g_asr_context); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] All services destroyed", EXAMPLE_LOG_TAG); + + return AI_SERVICE_OK; +} + +/* 示例函数实现 */ + +static void run_speech_recognition_example(void) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] === Speech Recognition Example ===", EXAMPLE_LOG_TAG); + + // 开始语音识别 + ai_service_error_t ret = asr_server_start_recognition(&g_asr_context, NULL); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to start recognition: %d", EXAMPLE_LOG_TAG, ret); + return; + } + + // 发送音频数据 + for (int i = 0; i < 5; i++) { + ret = asr_server_send_audio(&g_asr_context, g_sample_audio, sizeof(g_sample_audio)); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to send audio: %d", EXAMPLE_LOG_TAG, ret); + break; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Sent audio chunk %d", EXAMPLE_LOG_TAG, i + 1); + osDelay(500); // 500ms间隔 + } + + // 停止语音识别 + asr_server_stop_recognition(&g_asr_context); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Speech recognition example completed", EXAMPLE_LOG_TAG); +} + +static void run_text_to_speech_example(void) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] === Text-to-Speech Example ===", EXAMPLE_LOG_TAG); + + const char *text = "你好,这是一个文本转语音的示例。"; + + // 合成语音 + ai_service_error_t ret = tts_server_synthesize_text(&g_tts_context, text, NULL); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to synthesize text: %d", EXAMPLE_LOG_TAG, ret); + return; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Text synthesis started: %s", EXAMPLE_LOG_TAG, text); + + // 等待合成完成 + osDelay(3000); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Text-to-speech example completed", EXAMPLE_LOG_TAG); +} + +static void run_voice_conversation_example(void) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] === Voice Conversation Example ===", EXAMPLE_LOG_TAG); + + // 创建会话 + ai_service_error_t ret = chat_server_create_session(&g_chat_context, NULL); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create chat session: %d", EXAMPLE_LOG_TAG, ret); + return; + } + + // 模拟语音对话流程: + // 1. 开始语音识别 + ret = asr_server_start_recognition(&g_asr_context, NULL); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to start recognition: %d", EXAMPLE_LOG_TAG, ret); + return; + } + + // 2. 发送音频数据(模拟用户说话) + for (int i = 0; i < 3; i++) { + asr_server_send_audio(&g_asr_context, g_sample_audio, sizeof(g_sample_audio)); + osDelay(300); + } + + // 3. 停止语音识别 + asr_server_stop_recognition(&g_asr_context); + + // 4. 模拟发送识别结果到Chat服务 + const char *user_message = "今天天气怎么样?"; + ret = chat_server_send_message(&g_chat_context, user_message, NULL); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to send chat message: %d", EXAMPLE_LOG_TAG, ret); + return; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Voice conversation example completed", EXAMPLE_LOG_TAG); +} + +static void print_service_stats(void) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] === Service Statistics ===", EXAMPLE_LOG_TAG); + + // ASR统计 + uint32_t asr_audio_sent, asr_results_received, asr_sessions; + asr_server_get_stats(&g_asr_context, &asr_audio_sent, &asr_results_received, &asr_sessions); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ASR: Audio=%u bytes, Results=%u, Sessions=%u", + EXAMPLE_LOG_TAG, asr_audio_sent, asr_results_received, asr_sessions); + + // TTS统计 + uint32_t tts_text_sent, tts_audio_received, tts_synthesis_sessions, tts_playback_sessions; + tts_server_get_stats(&g_tts_context, &tts_text_sent, &tts_audio_received, + &tts_synthesis_sessions, &tts_playback_sessions); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS: Text=%u chars, Audio=%u bytes, Synthesis=%u, Playback=%u", + EXAMPLE_LOG_TAG, tts_text_sent, tts_audio_received, tts_synthesis_sessions, tts_playback_sessions); + + // Chat统计 + const chat_stats_t *chat_stats = chat_server_get_stats(&g_chat_context); + if (chat_stats) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Chat: Requests=%u, Responses=%u, Tokens_sent=%u, Tokens_received=%u, Conversations=%u", + EXAMPLE_LOG_TAG, chat_stats->total_requests, chat_stats->total_responses, + chat_stats->total_tokens_sent, chat_stats->total_tokens_received, chat_stats->conversation_count); + } +} + +/* TTS接口函数实现 */ +static ai_service_error_t tts_send_text_interface(const char *text, const char *session_id) +{ + if (!text) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Forwarding to TTS: %s", EXAMPLE_LOG_TAG, text); + + return tts_server_synthesize_text(&g_tts_context, text, session_id); +} + +/* ASR回调函数实现 */ +static void on_asr_recognition_result(void *context, const asr_recognition_result_t *result) +{ + if (!result) return; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ASR Result: '%s' (confidence: %.2f, final: %s)", + EXAMPLE_LOG_TAG, result->text ? result->text : "", + result->confidence, result->is_final ? "yes" : "no"); + + // 如果是最终结果,发送到Chat服务 + if (result->is_final && result->text) { + chat_server_send_message(&g_chat_context, result->text, result->session_id); + } +} + +static void on_asr_recognition_start(void *context, const char *session_id) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ASR Recognition started: %s", EXAMPLE_LOG_TAG, session_id); +} + +static void on_asr_recognition_end(void *context, const char *session_id) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ASR Recognition ended: %s", EXAMPLE_LOG_TAG, session_id); +} + +static void on_asr_audio_level(void *context, float level) +{ + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "[%s] ASR Audio level: %.2f dB", EXAMPLE_LOG_TAG, level); +} + +/* TTS回调函数实现 */ +static void on_tts_synthesis_start(void *context, const char *session_id) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS Synthesis started: %s", EXAMPLE_LOG_TAG, session_id); +} + +static void on_tts_synthesis_result(void *context, const tts_synthesis_result_t *result) +{ + if (!result) return; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS Result: %zu bytes audio (seq: %u, final: %s)", + EXAMPLE_LOG_TAG, result->audio_length, result->sequence_number, + result->is_final ? "yes" : "no"); +} + +static void on_tts_synthesis_complete(void *context, const char *session_id) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS Synthesis completed: %s", EXAMPLE_LOG_TAG, session_id); +} + +static void on_tts_playback_start(void *context) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS Playback started", EXAMPLE_LOG_TAG); +} + +static void on_tts_playback_complete(void *context) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS Playback completed", EXAMPLE_LOG_TAG); +} + +static void on_tts_playback_error(void *context, int error_code) +{ + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] TTS Playback error: %d", EXAMPLE_LOG_TAG, error_code); +} + +/* Chat回调函数实现 */ +static void on_chat_start(void *context, const char *session_id) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Chat started: %s", EXAMPLE_LOG_TAG, session_id); +} + +static void on_chat_response(void *context, const chat_response_t *response) +{ + if (!response) return; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Chat Response: '%s' (tokens: %u, time: %ums)", + EXAMPLE_LOG_TAG, response->content ? response->content : "", + response->token_count, response->response_time_ms); +} + +static void on_chat_complete(void *context, const char *session_id) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Chat completed: %s", EXAMPLE_LOG_TAG, session_id); +} + +static void on_chat_streaming(void *context, const char *session_id, const char *partial_content) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Chat Streaming [%s]: %s", + EXAMPLE_LOG_TAG, session_id, partial_content); +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/code/sdk/zhihui/ai/include/ai_service_common.h b/code/sdk/zhihui/ai/include/ai_service_common.h new file mode 100644 index 000000000..ce6eb4ea2 --- /dev/null +++ b/code/sdk/zhihui/ai/include/ai_service_common.h @@ -0,0 +1,271 @@ +/* + * AI服务通用头文件 + * 定义ASR、TTS、Chat服务的通用数据结构和接口 + */ + +#ifndef AI_SERVICE_COMMON_H +#define AI_SERVICE_COMMON_H + +#include +#include +#include +#include +#include +#include "cmsis_os2.h" +#include "net_websocket.h" +#include "net_websocket_type.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* 通用配置 */ +#define AI_MAX_URL_LENGTH 256 +#define AI_MAX_MESSAGE_LENGTH 4096 +#define AI_MAX_RETRY_COUNT 3 +#define AI_CONNECT_TIMEOUT_MS 30000 +#define AI_RECEIVE_TIMEOUT_MS 10000 +#define AI_RECONNECT_INTERVAL_MS 5000 +#define AI_HEARTBEAT_INTERVAL_MS 30000 + +/* 服务状态枚举 */ +typedef enum { + AI_SERVICE_STATE_IDLE = 0, + AI_SERVICE_STATE_CONNECTING, + AI_SERVICE_STATE_CONNECTED, + AI_SERVICE_STATE_DISCONNECTED, + AI_SERVICE_STATE_ERROR, + AI_SERVICE_STATE_RECONNECTING +} ai_service_state_t; + +/* 错误码枚举 */ +typedef enum { + AI_SERVICE_OK = 0, + AI_SERVICE_ERROR_INVALID_PARAM = -1, + AI_SERVICE_ERROR_MEMORY_ALLOC = -2, + AI_SERVICE_ERROR_NETWORK = -3, + AI_SERVICE_ERROR_TIMEOUT = -4, + AI_SERVICE_ERROR_NOT_CONNECTED = -5, + AI_SERVICE_ERROR_ALREADY_CONNECTED = -6, + AI_SERVICE_ERROR_INTERNAL = -7 +} ai_service_error_t; + +/* 通用服务配置结构 */ +typedef struct { + char server_url[AI_MAX_URL_LENGTH]; + uint16_t server_port; + char protocol[64]; + uint32_t connect_timeout_ms; + uint32_t receive_timeout_ms; + uint32_t reconnect_interval_ms; + uint32_t heartbeat_interval_ms; + uint8_t max_retry_count; + bool auto_reconnect; +} ai_service_config_t; + +/* 通用服务统计信息 */ +typedef struct { + uint32_t connect_count; + uint32_t disconnect_count; + uint32_t reconnect_count; + uint32_t send_count; + uint32_t receive_count; + uint32_t error_count; + uint32_t last_connect_time; + uint32_t last_disconnect_time; + uint32_t total_bytes_sent; + uint32_t total_bytes_received; +} ai_service_stats_t; + +/* 通用服务上下文结构 */ +typedef struct { + /* 基本信息 */ + char service_name[32]; + ai_service_state_t state; + ai_service_config_t config; + ai_service_stats_t stats; + + /* WebSocket相关 */ + int ws_client_fd; + bool is_connected; + + /* 线程和同步 */ + osThreadId_t service_thread; + osThreadId_t heartbeat_thread; + osMutexId_t mutex; + osSemaphoreId_t connect_sem; + + /* 重连相关 */ + uint8_t retry_count; + osTimerId_t reconnect_timer; + osTimerId_t heartbeat_timer; + + /* 回调函数 */ + void (*on_connected)(void *context); + void (*on_disconnected)(void *context); + void (*on_message_received)(void *context, const char *data, size_t length); + void (*on_error)(void *context, int error_code, const char *error_msg); + + /* 用户数据 */ + void *user_data; +} ai_service_context_t; + +/* 通用接口函数声明 */ + +/** + * @brief 初始化AI服务上下文 + * @param context 服务上下文 + * @param service_name 服务名称 + * @param config 服务配置 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_service_init(ai_service_context_t *context, + const char *service_name, + const ai_service_config_t *config); + +/** + * @brief 销毁AI服务上下文 + * @param context 服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_service_destroy(ai_service_context_t *context); + +/** + * @brief 启动AI服务 + * @param context 服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_service_start(ai_service_context_t *context); + +/** + * @brief 停止AI服务 + * @param context 服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_service_stop(ai_service_context_t *context); + +/** + * @brief 连接到服务器 + * @param context 服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_service_connect(ai_service_context_t *context); + +/** + * @brief 断开连接 + * @param context 服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_service_disconnect(ai_service_context_t *context); + +/** + * @brief 发送数据 + * @param context 服务上下文 + * @param data 数据内容 + * @param length 数据长度 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_service_send_data(ai_service_context_t *context, + const char *data, size_t length); + +/** + * @brief 发送文本消息 + * @param context 服务上下文 + * @param text 文本内容 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_service_send_text(ai_service_context_t *context, const char *text); + +/** + * @brief 发送二进制数据 + * @param context 服务上下文 + * @param data 二进制数据 + * @param length 数据长度 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_service_send_binary(ai_service_context_t *context, + const char *data, size_t length); + +/** + * @brief 检查连接状态 + * @param context 服务上下文 + * @return true 已连接,false 未连接 + */ +bool ai_service_is_connected(ai_service_context_t *context); + +/** + * @brief 获取服务状态 + * @param context 服务上下文 + * @return 当前服务状态 + */ +ai_service_state_t ai_service_get_state(ai_service_context_t *context); + +/** + * @brief 获取服务统计信息 + * @param context 服务上下文 + * @return 统计信息指针 + */ +const ai_service_stats_t* ai_service_get_stats(ai_service_context_t *context); + +/** + * @brief 设置回调函数 + * @param context 服务上下文 + * @param on_connected 连接成功回调 + * @param on_disconnected 断开连接回调 + * @param on_message_received 消息接收回调 + * @param on_error 错误回调 + */ +void ai_service_set_callbacks(ai_service_context_t *context, + void (*on_connected)(void *context), + void (*on_disconnected)(void *context), + void (*on_message_received)(void *context, const char *data, size_t length), + void (*on_error)(void *context, int error_code, const char *error_msg)); + +/** + * @brief 设置用户数据 + * @param context 服务上下文 + * @param user_data 用户数据指针 + */ +void ai_service_set_user_data(ai_service_context_t *context, void *user_data); + +/** + * @brief 获取用户数据 + * @param context 服务上下文 + * @return 用户数据指针 + */ +void* ai_service_get_user_data(ai_service_context_t *context); + +/* 工具函数 */ + +/** + * @brief 获取当前时间戳(毫秒) + * @return 时间戳 + */ +uint32_t ai_service_get_timestamp(void); + +/** + * @brief 生成UUID字符串 + * @param uuid_str 输出缓冲区 + * @param max_len 缓冲区最大长度 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_service_generate_uuid(char *uuid_str, size_t max_len); + +/** + * @brief 创建JSON消息 + * @param type 消息类型 + * @param data 消息数据 + * @param output 输出缓冲区 + * @param max_len 缓冲区最大长度 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_service_create_json_message(const char *type, + const char *data, + char *output, + size_t max_len); + +#ifdef __cplusplus +} +#endif + +#endif /* AI_SERVICE_COMMON_H */ \ No newline at end of file diff --git a/code/sdk/zhihui/ai/include/ai_service_manager.h b/code/sdk/zhihui/ai/include/ai_service_manager.h new file mode 100644 index 000000000..dbc9966eb --- /dev/null +++ b/code/sdk/zhihui/ai/include/ai_service_manager.h @@ -0,0 +1,323 @@ +/* + * Copyright (c) CompanyNameMagicTag. 2023-2023. All rights reserved. + * Description: AI Service Manager - AI全家桶总入口 + * Author: AI Development Team + * Create: 2024-12-28 + */ + +#ifndef AI_SERVICE_MANAGER_H +#define AI_SERVICE_MANAGER_H + +#include +#include +#include "ai_service_common.h" +#include "asr_server.h" +#include "chat_server.h" +#include "tts_server.h" +#include "ai_media.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ==================== 宏定义 ==================== */ + +#define AI_MANAGER_MAX_SESSION_ID_LEN 64 +#define AI_MANAGER_MAX_TEXT_LEN 4096 +#define AI_MANAGER_MAX_RETRY_COUNT 3 + +/* ==================== 枚举定义 ==================== */ + +/** + * @brief AI服务管理器状态 + */ +typedef enum { + AI_MANAGER_STATE_IDLE = 0, // 空闲状态 + AI_MANAGER_STATE_INITIALIZING, // 初始化中 + AI_MANAGER_STATE_READY, // 就绪状态 + AI_MANAGER_STATE_LISTENING, // 监听中(ASR激活) + AI_MANAGER_STATE_PROCESSING, // 处理中(Chat处理) + AI_MANAGER_STATE_SPEAKING, // 播放中(TTS播放) + AI_MANAGER_STATE_ERROR, // 错误状态 + AI_MANAGER_STATE_SHUTDOWN // 关闭状态 +} ai_manager_state_t; + +/** + * @brief AI交互事件类型 + */ +typedef enum { + AI_EVENT_CONVERSATION_START = 0, // 对话开始 + AI_EVENT_SPEECH_START, // 语音开始 + AI_EVENT_SPEECH_PARTIAL, // 语音识别中间结果 + AI_EVENT_SPEECH_END, // 语音结束 + AI_EVENT_CHAT_RESPONSE_START, // Chat回复开始 + AI_EVENT_CHAT_RESPONSE_PARTIAL, // Chat回复中间结果 + AI_EVENT_CHAT_RESPONSE_END, // Chat回复结束 + AI_EVENT_TTS_START, // TTS开始 + AI_EVENT_TTS_END, // TTS结束 + AI_EVENT_CONVERSATION_END, // 对话结束 + AI_EVENT_ERROR // 错误事件 +} ai_event_type_t; + +/** + * @brief AI服务类型 + */ +typedef enum { + AI_SERVICE_TYPE_ASR = 0, + AI_SERVICE_TYPE_CHAT, + AI_SERVICE_TYPE_TTS, + AI_SERVICE_TYPE_ALL +} ai_service_type_t; + +/* ==================== 结构体定义 ==================== */ + +/** + * @brief AI事件数据结构 + */ +typedef struct { + ai_event_type_t type; + char session_id[AI_MANAGER_MAX_SESSION_ID_LEN]; + char *text; // 文本内容(可选) + uint32_t text_length; // 文本长度 + float confidence; // 置信度(ASR使用) + bool is_final; // 是否为最终结果 + uint32_t timestamp; // 时间戳 + int error_code; // 错误码(错误事件使用) + char *error_message; // 错误信息(错误事件使用) +} ai_event_data_t; + +/** + * @brief AI服务配置 + */ +typedef struct { + /* ASR配置 */ + char asr_server_url[AI_MAX_URL_LENGTH]; + uint16_t asr_server_port; + asr_audio_config_t asr_audio_config; + asr_recognition_config_t asr_recognition_config; + + /* Chat配置 */ + char chat_server_url[AI_MAX_URL_LENGTH]; + uint16_t chat_server_port; + chat_model_config_t chat_model_config; + + /* TTS配置 */ + char tts_server_url[AI_MAX_URL_LENGTH]; + uint16_t tts_server_port; + tts_audio_config_t tts_audio_config; + tts_voice_config_t tts_voice_config; + + /* 通用配置 */ + uint32_t connect_timeout_ms; + uint32_t response_timeout_ms; + bool auto_reconnect; + uint8_t max_retry_count; +} ai_manager_config_t; + +/** + * @brief AI服务统计信息 + */ +typedef struct { + uint32_t conversation_count; // 对话次数 + uint32_t asr_recognition_count; // ASR识别次数 + uint32_t chat_request_count; // Chat请求次数 + uint32_t tts_synthesis_count; // TTS合成次数 + uint32_t total_errors; // 总错误数 + uint32_t last_conversation_time; // 最后对话时间 + float avg_response_time_ms; // 平均响应时间 +} ai_manager_stats_t; + +/** + * @brief AI服务管理器上下文 + */ +typedef struct { + /* 基本信息 */ + ai_manager_state_t state; + ai_manager_config_t config; + ai_manager_stats_t stats; + + /* 服务实例 */ + asr_server_context_t *asr_context; + chat_server_context_t *chat_context; + tts_server_context_t *tts_context; + + /* 当前会话信息 */ + char current_session_id[AI_MANAGER_MAX_SESSION_ID_LEN]; + bool is_conversation_active; + bool is_recording_active; + bool is_playback_active; + + /* 线程和同步 */ + osThreadId_t manager_thread; + osMutexId_t mutex; + osMessageQueueId_t event_queue; + + /* 回调函数 */ + void (*on_event)(void *user_data, const ai_event_data_t *event); + void *user_data; + + /* 内部状态 */ + uint32_t init_timestamp; + bool services_initialized; +} ai_manager_context_t; + +/* ==================== 回调函数类型定义 ==================== */ + +/** + * @brief AI事件回调函数类型 + * @param user_data 用户数据 + * @param event 事件数据 + */ +typedef void (*ai_event_callback_t)(void *user_data, const ai_event_data_t *event); + +/* ==================== 公共接口函数 ==================== */ + +/** + * @brief 初始化AI服务管理器 + * @param context AI管理器上下文 + * @param config AI服务配置 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_manager_init(ai_manager_context_t *context, + const ai_manager_config_t *config); + +/** + * @brief 销毁AI服务管理器 + * @param context AI管理器上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_manager_destroy(ai_manager_context_t *context); + +/** + * @brief 启动AI服务 + * @param context AI管理器上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_manager_start(ai_manager_context_t *context); + +/** + * @brief 停止AI服务 + * @param context AI管理器上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_manager_stop(ai_manager_context_t *context); + +/** + * @brief 开始对话 + * @param context AI管理器上下文 + * @param session_id 会话ID(可选,传NULL自动生成) + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_manager_start_conversation(ai_manager_context_t *context, + const char *session_id); + +/** + * @brief 结束对话 + * @param context AI管理器上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_manager_end_conversation(ai_manager_context_t *context); + +/** + * @brief 开始语音输入 + * @param context AI管理器上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_manager_start_speech_input(ai_manager_context_t *context); + +/** + * @brief 停止语音输入 + * @param context AI管理器上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_manager_stop_speech_input(ai_manager_context_t *context); + +/** + * @brief 发送文本消息到Chat + * @param context AI管理器上下文 + * @param text 文本内容 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_manager_send_text_message(ai_manager_context_t *context, + const char *text); + +/** + * @brief 停止当前TTS播放 + * @param context AI管理器上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t ai_manager_stop_tts_playback(ai_manager_context_t *context); + +/** + * @brief 设置事件回调函数 + * @param context AI管理器上下文 + * @param callback 回调函数 + * @param user_data 用户数据 + */ +void ai_manager_set_event_callback(ai_manager_context_t *context, + ai_event_callback_t callback, + void *user_data); + +/** + * @brief 获取当前状态 + * @param context AI管理器上下文 + * @return 当前状态 + */ +ai_manager_state_t ai_manager_get_state(ai_manager_context_t *context); + +/** + * @brief 获取统计信息 + * @param context AI管理器上下文 + * @return 统计信息指针 + */ +const ai_manager_stats_t* ai_manager_get_stats(ai_manager_context_t *context); + +/** + * @brief 检查服务是否就绪 + * @param context AI管理器上下文 + * @param service_type 服务类型 + * @return true 就绪,false 未就绪 + */ +bool ai_manager_is_service_ready(ai_manager_context_t *context, + ai_service_type_t service_type); + +/* ==================== 工具函数 ==================== */ + +/** + * @brief 创建默认配置 + * @param config 配置结构体指针 + */ +void ai_manager_create_default_config(ai_manager_config_t *config); + +/** + * @brief 生成会话ID + * @param session_id 输出缓冲区 + * @param max_len 缓冲区最大长度 + */ +void ai_manager_generate_session_id(char *session_id, size_t max_len); + +/** + * @brief 状态转换为字符串 + * @param state 状态 + * @return 状态字符串 + */ +const char* ai_manager_state_to_string(ai_manager_state_t state); + +/** + * @brief 事件类型转换为字符串 + * @param event_type 事件类型 + * @return 事件类型字符串 + */ +const char* ai_manager_event_type_to_string(ai_event_type_t event_type); + +/** + * @brief 释放事件数据 + * @param event 事件数据指针 + */ +void ai_manager_free_event_data(ai_event_data_t *event); + +#ifdef __cplusplus +} +#endif + +#endif /* AI_SERVICE_MANAGER_H */ \ No newline at end of file diff --git a/code/sdk/zhihui/ai/include/asr_server.h b/code/sdk/zhihui/ai/include/asr_server.h new file mode 100644 index 000000000..150509a56 --- /dev/null +++ b/code/sdk/zhihui/ai/include/asr_server.h @@ -0,0 +1,303 @@ +/* + * ASR (Automatic Speech Recognition) 服务头文件 + * 负责建立与ASR服务器的WebSocket连接管理,上传录音数据,接收识别结果 + */ + +#ifndef ASR_SERVER_H +#define ASR_SERVER_H + +#include "ai_service_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ASR特定配置 */ +#define ASR_MAX_AUDIO_BUFFER_SIZE 8192 +#define ASR_SAMPLE_RATE_DEFAULT 16000 +#define ASR_CHANNELS_DEFAULT 1 +#define ASR_BITS_PER_SAMPLE_DEFAULT 16 +#define ASR_FRAME_SIZE_MS_DEFAULT 20 + +/* ASR音频格式枚举 */ +typedef enum { + ASR_AUDIO_FORMAT_PCM = 0, + ASR_AUDIO_FORMAT_WAV, + ASR_AUDIO_FORMAT_MP3, + ASR_AUDIO_FORMAT_OPUS +} asr_audio_format_t; + +/* ASR识别模式枚举 */ +typedef enum { + ASR_MODE_CONTINUOUS = 0, // 连续识别 + ASR_MODE_SINGLE_SHOT, // 单次识别 + ASR_MODE_STREAMING // 流式识别 +} asr_recognition_mode_t; + +/* ASR语言枚举 */ +typedef enum { + ASR_LANGUAGE_ZH_CN = 0, // 中文 + ASR_LANGUAGE_EN_US, // 英文 + ASR_LANGUAGE_AUTO // 自动检测 +} asr_language_t; + +/* ASR音频配置 */ +typedef struct { + asr_audio_format_t format; + uint32_t sample_rate; + uint8_t channels; + uint8_t bits_per_sample; + uint16_t frame_size_ms; +} asr_audio_config_t; + +/* ASR识别配置 */ +typedef struct { + asr_recognition_mode_t mode; + asr_language_t language; + bool enable_punctuation; + bool enable_word_timestamp; + bool enable_confidence_score; + uint32_t max_silence_ms; + uint32_t max_speech_ms; +} asr_recognition_config_t; + +/* ASR识别结果 */ +typedef struct { + char *text; // 识别文本 + float confidence; // 置信度 (0.0-1.0) + bool is_final; // 是否为最终结果 + uint32_t start_time_ms; // 开始时间 + uint32_t end_time_ms; // 结束时间 + char *session_id; // 会话ID +} asr_recognition_result_t; + +/* ASR服务上下文 */ +typedef struct { + ai_service_context_t base; // 基础服务上下文 + + /* ASR特定配置 */ + asr_audio_config_t audio_config; + asr_recognition_config_t recognition_config; + + /* 音频缓冲区 */ + char *audio_buffer; + size_t audio_buffer_size; + size_t audio_buffer_used; + osMutexId_t audio_buffer_mutex; + + /* 会话管理 */ + char current_session_id[64]; + bool session_active; + + /* 回调函数 */ + void (*on_recognition_result)(void *context, const asr_recognition_result_t *result); + void (*on_recognition_start)(void *context, const char *session_id); + void (*on_recognition_end)(void *context, const char *session_id); + void (*on_audio_level)(void *context, float level); + + /* 统计信息 */ + uint32_t total_audio_sent; + uint32_t total_results_received; + uint32_t recognition_sessions; +} asr_server_context_t; + +/* ASR服务接口函数 */ + +/** + * @brief 初始化ASR服务 + * @param context ASR服务上下文 + * @param server_url 服务器URL + * @param audio_config 音频配置 + * @param recognition_config 识别配置 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t asr_server_init(asr_server_context_t *context, + const char *server_url, + const asr_audio_config_t *audio_config, + const asr_recognition_config_t *recognition_config); + +/** + * @brief 销毁ASR服务 + * @param context ASR服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t asr_server_destroy(asr_server_context_t *context); + +/** + * @brief 启动ASR服务 + * @param context ASR服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t asr_server_start(asr_server_context_t *context); + +/** + * @brief 停止ASR服务 + * @param context ASR服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t asr_server_stop(asr_server_context_t *context); + +/** + * @brief 开始语音识别会话 + * @param context ASR服务上下文 + * @param session_id 会话ID(可选,为NULL时自动生成) + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t asr_server_start_recognition(asr_server_context_t *context, + const char *session_id); + +/** + * @brief 结束语音识别会话 + * @param context ASR服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t asr_server_stop_recognition(asr_server_context_t *context); + + + +/** + * @brief 发送音频数据(流式) + * @param context ASR服务上下文 + * @param audio_data 音频数据 + * @param data_length 数据长度 + * @param is_last 是否为最后一帧 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t asr_server_send_audio_stream(asr_server_context_t *context, + const char *audio_data, + size_t data_length, + bool is_last); + +/** + * @brief 设置音频配置 + * @param context ASR服务上下文 + * @param audio_config 音频配置 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t asr_server_set_audio_config(asr_server_context_t *context, + const asr_audio_config_t *audio_config); + +/** + * @brief 设置识别配置 + * @param context ASR服务上下文 + * @param recognition_config 识别配置 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t asr_server_set_recognition_config(asr_server_context_t *context, + const asr_recognition_config_t *recognition_config); + +/** + * @brief 获取当前会话ID + * @param context ASR服务上下文 + * @return 会话ID字符串,NULL表示无活动会话 + */ +const char* asr_server_get_current_session_id(asr_server_context_t *context); + +/** + * @brief 检查是否有活动的识别会话 + * @param context ASR服务上下文 + * @return true 有活动会话,false 无活动会话 + */ +bool asr_server_is_recognition_active(asr_server_context_t *context); + +/** + * @brief 设置ASR回调函数 + * @param context ASR服务上下文 + * @param on_recognition_result 识别结果回调 + * @param on_recognition_start 识别开始回调 + * @param on_recognition_end 识别结束回调 + * @param on_audio_level 音频电平回调 + */ +void asr_server_set_callbacks(asr_server_context_t *context, + void (*on_recognition_result)(void *context, const asr_recognition_result_t *result), + void (*on_recognition_start)(void *context, const char *session_id), + void (*on_recognition_end)(void *context, const char *session_id), + void (*on_audio_level)(void *context, float level)); + +/** + * @brief 获取ASR统计信息 + * @param context ASR服务上下文 + * @param total_audio_sent 总发送音频字节数 + * @param total_results_received 总接收结果数 + * @param recognition_sessions 识别会话数 + */ +void asr_server_get_stats(asr_server_context_t *context, + uint32_t *total_audio_sent, + uint32_t *total_results_received, + uint32_t *recognition_sessions); + +/* 工具函数 */ + +/** + * @brief 创建默认音频配置 + * @param config 输出配置 + */ +void asr_server_create_default_audio_config(asr_audio_config_t *config); + +/** + * @brief 创建默认识别配置 + * @param config 输出配置 + */ +void asr_server_create_default_recognition_config(asr_recognition_config_t *config); + +/** + * @brief 释放识别结果内存 + * @param result 识别结果 + */ +void asr_server_free_recognition_result(asr_recognition_result_t *result); + +/** + * @brief 音频格式转字符串 + * @param format 音频格式 + * @return 格式字符串 + */ +const char* asr_server_audio_format_to_string(asr_audio_format_t format); + +/** + * @brief 识别模式转字符串 + * @param mode 识别模式 + * @return 模式字符串 + */ +const char* asr_server_recognition_mode_to_string(asr_recognition_mode_t mode); + +/** + * @brief 语言转字符串 + * @param language 语言 + * @return 语言字符串 + */ +const char* asr_server_language_to_string(asr_language_t language); + +/* ASR API 函数 */ + +/** + * @brief 初始化ASR WebSocket URL + * @param put_buf 输出缓冲区,存储构建的URL + * @return bool true表示成功,false表示失败 + */ +bool asr_api_ws_url_init(char *put_buf); + +/** + * @brief ASR结果分析函数 + * @param buf 接收到的JSON字符串缓冲区 + * @param result 输出的识别结果结构体 + * @return bool true表示解析成功,false表示解析失败 + */ +bool asr_api_result_analysis(char *buf, asr_recognition_result_t *result); + +/** + * @brief 启动ASR(语音识别)服务 + * @param context ASR服务上下文 + */ +void asr_api_start_asr(asr_server_context_t *context); + +/** + * @brief 停止ASR(语音识别)服务 + * @param context ASR服务上下文 + */ +void asr_api_stop_asr(asr_server_context_t *context); + +#ifdef __cplusplus +} +#endif + +#endif /* ASR_SERVER_H */ \ No newline at end of file diff --git a/code/sdk/zhihui/ai/include/bk_websocket.h b/code/sdk/zhihui/ai/include/bk_websocket.h deleted file mode 100644 index e0dd647e3..000000000 --- a/code/sdk/zhihui/ai/include/bk_websocket.h +++ /dev/null @@ -1,93 +0,0 @@ - -#ifndef __BK_WEBSOCKET_H__ -#define __BK_WEBSOCKET_H__ - -// 包含新的WebSocket解析器接口 -#include "websocket_parser_wrapper.h" - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -// 使用websocket_parser_wrapper.h中的枚举值,避免重复定义 -// 为保持向后兼容性,创建类型别名 -typedef ws_opcode_t WebSocketMessageType; - -// 定义向后兼容的常量(如果需要的话) -#define WS_CONT WS_CONTINUATION - -/* WebSocket数据包回调函数类型 */ -typedef void (*on_ws_packet_callback)(uint8_t* data, size_t datalen, WebSocketMessageType type); - -/* WebSocket消息队列系统 */ -#define WS_QUEUE_SIZE 128 -#define WS_MESSAGE_MAX_SIZE 1024 - -typedef struct { - uint8_t *data;//data[WS_MESSAGE_MAX_SIZE]; - uint16_t length; - WebSocketMessageType type; - uint32_t timestamp; - bool in_use; -} ws_message_item_t; - -typedef struct { -#ifdef SUPPORT_WEBSOCKET_TEST - // ws_message_item_t messages[WS_QUEUE_SIZE]; - // uint8_t head; - // uint8_t tail; - // uint8_t count; - // osMutexId_t mutex; - // osSemaphoreId_t semaphore; -#endif - unsigned long queue_id; // osal消息队列ID - bool initialized; -} ws_message_queue_t; - -// WebSocket 客户端结构体 -typedef struct { - ws_message_queue_t *msg_queue; - char host[128]; - char path[128]; - char query[128]; - int port; - - int handshake; - - /* WebSocket解析器 */ - ws_parser_t* parser; - int sockfd; /* socket文件描述符 */ - - /* 数据包回调函数 */ - on_ws_packet_callback packet_callback; -} WebSocketClient; - -// 初始化 WebSocket 客户端 -WebSocketClient *init_websocket_client(const char *url, const char *headers,uint8_t use_dns); - -// 设置WebSocket数据包回调函数 -void ws_set_packet_callback(WebSocketClient *client, on_ws_packet_callback callback); - -// 处理接收到的原始数据 -int ws_process_received_data(WebSocketClient *client, const uint8_t *data, size_t len); - -// 关闭 WebSocket 客户端 -void lwip_close_websocket_client(WebSocketClient *client) ; - -// 接收 WebSocket 消息 -int receive_websocket_message(WebSocketClient *client, unsigned char *buf,int len,WebSocketMessageType *code); -int send_websocket_message(WebSocketClient *client, uint8_t *message, size_t message_len, WebSocketMessageType type) ; - -// WebSocket测试客户端(当SUPPORT_WEBSOCKET_TEST定义时可用) -#ifdef SUPPORT_WEBSOCKET_TEST -extern WebSocketClient *test_client; -#endif - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -// 发送 WebSocket 消息 -// int send_websocket_message(WebSocketClient *client, uint8_t *message, size_t message_len, WebSocketMessageType type) ; - -#endif diff --git a/code/sdk/zhihui/ai/include/chat_server.h b/code/sdk/zhihui/ai/include/chat_server.h new file mode 100644 index 000000000..9672a79ce --- /dev/null +++ b/code/sdk/zhihui/ai/include/chat_server.h @@ -0,0 +1,393 @@ +/* + * Chat 大模型服务头文件 + * 负责建立与Chat大模型服务器的WebSocket连接管理,上传文本,接收回复并转发给TTS + */ + +#ifndef CHAT_SERVER_H +#define CHAT_SERVER_H + +#include "ai_service_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Chat特定配置 */ +#define CHAT_MAX_MESSAGE_LENGTH 4096 +#define CHAT_MAX_CONTEXT_LENGTH 16384 +#define CHAT_MAX_MODEL_NAME_LENGTH 64 +#define CHAT_MAX_HISTORY_COUNT 50 + +/* Chat消息类型枚举 */ +typedef enum { + CHAT_MESSAGE_TYPE_TEXT = 0, + CHAT_MESSAGE_TYPE_IMAGE, + CHAT_MESSAGE_TYPE_AUDIO, + CHAT_MESSAGE_TYPE_VIDEO, + CHAT_MESSAGE_TYPE_SYSTEM +} chat_message_type_t; + + +/* Chat消息角色枚举 */ +typedef enum { + CHAT_ROLE_USER = 0, + CHAT_ROLE_ASSISTANT, + CHAT_ROLE_SYSTEM +} chat_role_t; + +/* Chat模型类型枚举 */ +typedef enum { + CHAT_MODEL_TYPE_GPT35 = 0, + CHAT_MODEL_TYPE_GPT4, + CHAT_MODEL_TYPE_CLAUDE, + CHAT_MODEL_TYPE_LLAMA, + CHAT_MODEL_TYPE_QWEN, + CHAT_MODEL_TYPE_BAICHUAN +} chat_model_type_t; +typedef enum +{ + AI_TYPE_0=0, + AI_TYPE_JIU, //九天 + AI_TYPE_DE, //deepseek + AI_TYPE_DOU,//豆包 + AI_TYPE_WEN,//文心一言 + AI_TYPE_KIMI,//KIMI +}G_AI_TYPE; +/* Chat响应模式枚举 */ +typedef enum { + CHAT_RESPONSE_MODE_COMPLETE = 0, // 完整响应 + CHAT_RESPONSE_MODE_STREAMING // 流式响应 +} chat_response_mode_t; + +/* Chat消息结构 */ +typedef struct { + chat_role_t role; + chat_message_type_t type; + char *content; + size_t content_length; + uint32_t timestamp; + char message_id[64]; +} chat_message_t; + +/* Chat会话历史 */ +typedef struct { + chat_message_t *messages; + size_t message_count; + size_t max_messages; + char session_id[64]; + uint32_t created_time; + uint32_t last_update_time; +} chat_conversation_t; + +/* Chat模型配置 */ +typedef struct { + char model_name[CHAT_MAX_MODEL_NAME_LENGTH]; + chat_model_type_t model_type; + float temperature; // 温度参数 (0.0-2.0) + float top_p; // Top-p参数 (0.0-1.0) + uint32_t max_tokens; // 最大token数 + bool enable_history; // 是否启用历史记录 + uint32_t max_history_count; // 最大历史记录数 +} chat_model_config_t; + +/* Chat请求结构 */ +typedef struct { + char *message; + chat_message_type_t message_type; + char session_id[64]; + chat_response_mode_t response_mode; + chat_model_config_t model_config; + bool include_history; +} chat_request_t; + +/* Chat响应结构 */ +typedef struct { + char *content; + size_t content_length; + char session_id[64]; + char message_id[64]; + bool is_final; + bool is_streaming; + uint32_t token_count; + float confidence; + uint32_t response_time_ms; +} chat_response_t; + +/* Chat统计信息 */ +typedef struct { + uint32_t total_requests; + uint32_t total_responses; + uint32_t total_tokens_sent; + uint32_t total_tokens_received; + uint32_t average_response_time_ms; + uint32_t conversation_count; +} chat_stats_t; + +/* Chat服务上下文 */ +typedef struct { + ai_service_context_t base; // 基础服务上下文 + + /* Chat特定配置 */ + chat_model_config_t model_config; + chat_response_mode_t response_mode; + + /* 会话管理 */ + chat_conversation_t *conversations; + size_t conversation_count; + size_t max_conversations; + char current_session_id[64]; + osMutexId_t conversation_mutex; + + /* TTS接口回调 */ + ai_service_error_t (*tts_send_text)(const char *text, const char *session_id); + + /* 回调函数 */ + void (*on_chat_start)(void *context, const char *session_id); + void (*on_chat_response)(void *context, const chat_response_t *response); + void (*on_chat_complete)(void *context, const char *session_id); + void (*on_chat_streaming)(void *context, const char *session_id, const char *partial_content); + + /* 统计信息 */ + chat_stats_t chat_stats; +} chat_server_context_t; + +/* Chat服务接口函数 */ + +/** + * @brief 初始化Chat服务 + * @param context Chat服务上下文 + * @param server_url 服务器URL + * @param model_config 模型配置 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t chat_server_init(chat_server_context_t *context, + const char *server_url, + const chat_model_config_t *model_config); + +/** + * @brief 销毁Chat服务 + * @param context Chat服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t chat_server_destroy(chat_server_context_t *context); + +/** + * @brief 启动Chat服务 + * @param context Chat服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t chat_server_start(chat_server_context_t *context); + +/** + * @brief 停止Chat服务 + * @param context Chat服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t chat_server_stop(chat_server_context_t *context); + +/** + * @brief 发送聊天消息 + * @param context Chat服务上下文 + * @param message 消息内容 + * @param session_id 会话ID(可选,为NULL时自动生成) + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t chat_server_send_message(chat_server_context_t *context, + const char *message, + const char *session_id); + +/** + * @brief 发送聊天请求 + * @param context Chat服务上下文 + * @param request 聊天请求 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t chat_server_send_request(chat_server_context_t *context, + const chat_request_t *request); + +/** + * @brief 创建新会话 + * @param context Chat服务上下文 + * @param session_id 会话ID(可选,为NULL时自动生成) + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t chat_server_create_session(chat_server_context_t *context, + const char *session_id); + +/** + * @brief 删除会话 + * @param context Chat服务上下文 + * @param session_id 会话ID + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t chat_server_delete_session(chat_server_context_t *context, + const char *session_id); + +/** + * @brief 清空会话历史 + * @param context Chat服务上下文 + * @param session_id 会话ID + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t chat_server_clear_session_history(chat_server_context_t *context, + const char *session_id); + +/** + * @brief 获取会话历史 + * @param context Chat服务上下文 + * @param session_id 会话ID + * @return 会话历史指针,NULL表示会话不存在 + */ +const chat_conversation_t* chat_server_get_session_history(chat_server_context_t *context, + const char *session_id); + +/** + * @brief 设置模型配置 + * @param context Chat服务上下文 + * @param model_config 模型配置 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t chat_server_set_model_config(chat_server_context_t *context, + const chat_model_config_t *model_config); + +/** + * @brief 设置响应模式 + * @param context Chat服务上下文 + * @param response_mode 响应模式 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t chat_server_set_response_mode(chat_server_context_t *context, + chat_response_mode_t response_mode); + +/** + * @brief 设置TTS接口回调 + * @param context Chat服务上下文 + * @param tts_send_text TTS发送文本接口 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t chat_server_set_tts_interface(chat_server_context_t *context, + ai_service_error_t (*tts_send_text)(const char *text, const char *session_id)); + +/** + * @brief 获取当前会话ID + * @param context Chat服务上下文 + * @return 会话ID字符串,NULL表示无活动会话 + */ +const char* chat_server_get_current_session_id(chat_server_context_t *context); + +/** + * @brief 获取会话数量 + * @param context Chat服务上下文 + * @return 会话数量 + */ +size_t chat_server_get_session_count(chat_server_context_t *context); + +/** + * @brief 设置Chat回调函数 + * @param context Chat服务上下文 + * @param on_chat_start 聊天开始回调 + * @param on_chat_response 聊天响应回调 + * @param on_chat_complete 聊天完成回调 + * @param on_chat_streaming 流式响应回调 + */ +void chat_server_set_callbacks(chat_server_context_t *context, + void (*on_chat_start)(void *context, const char *session_id), + void (*on_chat_response)(void *context, const chat_response_t *response), + void (*on_chat_complete)(void *context, const char *session_id), + void (*on_chat_streaming)(void *context, const char *session_id, const char *partial_content)); + +/** + * @brief 获取Chat统计信息 + * @param context Chat服务上下文 + * @return 统计信息指针 + */ +const chat_stats_t* chat_server_get_stats(chat_server_context_t *context); + +/* 会话管理函数 */ + +/** + * @brief 添加消息到会话历史 + * @param conversation 会话对象 + * @param role 消息角色 + * @param content 消息内容 + * @param message_type 消息类型 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t chat_conversation_add_message(chat_conversation_t *conversation, + chat_role_t role, + const char *content, + chat_message_type_t message_type); + +/** + * @brief 获取会话最后一条消息 + * @param conversation 会话对象 + * @return 消息指针,NULL表示无消息 + */ +const chat_message_t* chat_conversation_get_last_message(const chat_conversation_t *conversation); + +/** + * @brief 清空会话消息 + * @param conversation 会话对象 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t chat_conversation_clear_messages(chat_conversation_t *conversation); + +/** + * @brief 释放会话内存 + * @param conversation 会话对象 + */ +void chat_conversation_free(chat_conversation_t *conversation); + +/* 工具函数 */ + +/** + * @brief 创建默认模型配置 + * @param config 输出配置 + */ +void chat_server_create_default_model_config(chat_model_config_t *config); + +/** + * @brief 释放聊天响应内存 + * @param response 聊天响应 + */ +void chat_server_free_response(chat_response_t *response); + +/** + * @brief 释放聊天消息内存 + * @param message 聊天消息 + */ +void chat_server_free_message(chat_message_t *message); + +/** + * @brief 消息类型转字符串 + * @param message_type 消息类型 + * @return 类型字符串 + */ +const char* chat_server_message_type_to_string(chat_message_type_t message_type); + +/** + * @brief 角色转字符串 + * @param role 角色 + * @return 角色字符串 + */ +const char* chat_server_role_to_string(chat_role_t role); + +/** + * @brief 模型类型转字符串 + * @param model_type 模型类型 + * @return 类型字符串 + */ +const char* chat_server_model_type_to_string(chat_model_type_t model_type); + +/** + * @brief 响应模式转字符串 + * @param response_mode 响应模式 + * @return 模式字符串 + */ +const char* chat_server_response_mode_to_string(chat_response_mode_t response_mode); + +#ifdef __cplusplus +} +#endif + +#endif /* CHAT_SERVER_H */ \ No newline at end of file diff --git a/code/sdk/zhihui/ai/include/tts_server.h b/code/sdk/zhihui/ai/include/tts_server.h new file mode 100644 index 000000000..d8b6dbded --- /dev/null +++ b/code/sdk/zhihui/ai/include/tts_server.h @@ -0,0 +1,375 @@ +/* + * TTS (Text-to-Speech) 服务头文件 + * 负责建立与TTS服务器的WebSocket连接管理,上传文本,接收音频流并播放 + */ + +#ifndef TTS_SERVER_H +#define TTS_SERVER_H + +#include "ai_service_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* TTS特定配置 */ +#define TTS_MAX_TEXT_LENGTH 2048 +#define TTS_MAX_AUDIO_BUFFER_SIZE 16384 +#define TTS_MAX_VOICE_NAME_LENGTH 64 +#define TTS_MAX_SSML_LENGTH 4096 + +/* TTS音频格式枚举 */ +typedef enum { + TTS_AUDIO_FORMAT_PCM = 0, + TTS_AUDIO_FORMAT_WAV, + TTS_AUDIO_FORMAT_MP3, + TTS_AUDIO_FORMAT_AAC, + TTS_AUDIO_FORMAT_OGG +} tts_audio_format_t; + +/* TTS语音类型枚举 */ +typedef enum { + TTS_VOICE_TYPE_MALE = 0, + TTS_VOICE_TYPE_FEMALE, + TTS_VOICE_TYPE_CHILD, + TTS_VOICE_TYPE_ELDERLY +} tts_voice_type_t; + +/* TTS语言枚举 */ +typedef enum { + TTS_LANGUAGE_ZH_CN = 0, // 中文 + TTS_LANGUAGE_EN_US, // 英文 + TTS_LANGUAGE_JA_JP, // 日文 + TTS_LANGUAGE_KO_KR // 韩文 +} tts_language_t; + +/* TTS情感枚举 */ +typedef enum { + TTS_EMOTION_NEUTRAL = 0, // 中性 + TTS_EMOTION_HAPPY, // 高兴 + TTS_EMOTION_SAD, // 悲伤 + TTS_EMOTION_ANGRY, // 愤怒 + TTS_EMOTION_EXCITED, // 兴奋 + TTS_EMOTION_CALM // 平静 +} tts_emotion_t; + +/* TTS音频配置 */ +typedef struct { + tts_audio_format_t format; + uint32_t sample_rate; + uint8_t channels; + uint8_t bits_per_sample; + uint32_t bitrate; +} tts_audio_config_t; + +/* TTS语音配置 */ +typedef struct { + char voice_name[TTS_MAX_VOICE_NAME_LENGTH]; + tts_voice_type_t voice_type; + tts_language_t language; + tts_emotion_t emotion; + float speed; // 语速 (0.5-2.0) + float pitch; // 音调 (0.5-2.0) + float volume; // 音量 (0.0-1.0) +} tts_voice_config_t; + +/* TTS合成请求 */ +typedef struct { + char *text; // 文本内容 + char *ssml; // SSML内容(可选) + char session_id[64]; // 会话ID + tts_voice_config_t voice_config; + bool use_ssml; // 是否使用SSML +} tts_synthesis_request_t; + +/* TTS合成结果 */ +typedef struct { + char *audio_data; // 音频数据 + size_t audio_length; // 音频长度 + tts_audio_config_t audio_config; + char session_id[64]; // 会话ID + bool is_final; // 是否为最终数据 + uint32_t sequence_number; // 序列号 +} tts_synthesis_result_t; + +/* TTS播放状态枚举 */ +typedef enum { + TTS_PLAYBACK_STATE_IDLE = 0, + TTS_PLAYBACK_STATE_PLAYING, + TTS_PLAYBACK_STATE_PAUSED, + TTS_PLAYBACK_STATE_STOPPED, + TTS_PLAYBACK_STATE_ERROR +} tts_playback_state_t; + +/* TTS服务上下文 */ +typedef struct { + ai_service_context_t base; // 基础服务上下文 + + /* TTS特定配置 */ + tts_audio_config_t audio_config; + tts_voice_config_t voice_config; + + /* 音频缓冲区 */ + char *audio_buffer; + size_t audio_buffer_size; + size_t audio_buffer_used; + osMutexId_t audio_buffer_mutex; + + /* 播放管理 */ + tts_playback_state_t playback_state; + osThreadId_t playback_thread; + osSemaphoreId_t playback_sem; + + /* 会话管理 */ + char current_session_id[64]; + bool synthesis_active; + uint32_t current_sequence; + + /* 回调函数 */ + void (*on_synthesis_start)(void *context, const char *session_id); + void (*on_synthesis_result)(void *context, const tts_synthesis_result_t *result); + void (*on_synthesis_complete)(void *context, const char *session_id); + void (*on_playback_start)(void *context); + void (*on_playback_complete)(void *context); + void (*on_playback_error)(void *context, int error_code); + + /* 统计信息 */ + uint32_t total_text_sent; + uint32_t total_audio_received; + uint32_t synthesis_sessions; + uint32_t playback_sessions; +} tts_server_context_t; + +/* TTS服务接口函数 */ + +/** + * @brief 初始化TTS服务 + * @param context TTS服务上下文 + * @param server_url 服务器URL + * @param audio_config 音频配置 + * @param voice_config 语音配置 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t tts_server_init(tts_server_context_t *context, + const char *server_url, + const tts_audio_config_t *audio_config, + const tts_voice_config_t *voice_config); + +/** + * @brief 销毁TTS服务 + * @param context TTS服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t tts_server_destroy(tts_server_context_t *context); + +/** + * @brief 启动TTS服务 + * @param context TTS服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t tts_server_start(tts_server_context_t *context); + +/** + * @brief 停止TTS服务 + * @param context TTS服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t tts_server_stop(tts_server_context_t *context); + +/** + * @brief 合成语音(文本) + * @param context TTS服务上下文 + * @param text 文本内容 + * @param session_id 会话ID(可选,为NULL时自动生成) + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t tts_server_synthesize_text(tts_server_context_t *context, + const char *text, + const char *session_id); + +/** + * @brief 合成语音(SSML) + * @param context TTS服务上下文 + * @param ssml SSML内容 + * @param session_id 会话ID(可选,为NULL时自动生成) + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t tts_server_synthesize_ssml(tts_server_context_t *context, + const char *ssml, + const char *session_id); + +/** + * @brief 合成语音(请求结构) + * @param context TTS服务上下文 + * @param request 合成请求 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t tts_server_synthesize_request(tts_server_context_t *context, + const tts_synthesis_request_t *request); + +/** + * @brief 停止当前合成 + * @param context TTS服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t tts_server_stop_synthesis(tts_server_context_t *context); + +/** + * @brief 开始播放音频 + * @param context TTS服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t tts_server_start_playback(tts_server_context_t *context); + +/** + * @brief 暂停播放音频 + * @param context TTS服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t tts_server_pause_playback(tts_server_context_t *context); + +/** + * @brief 恢复播放音频 + * @param context TTS服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t tts_server_resume_playback(tts_server_context_t *context); + +/** + * @brief 停止播放音频 + * @param context TTS服务上下文 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t tts_server_stop_playback(tts_server_context_t *context); + +/** + * @brief 设置音频配置 + * @param context TTS服务上下文 + * @param audio_config 音频配置 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t tts_server_set_audio_config(tts_server_context_t *context, + const tts_audio_config_t *audio_config); + +/** + * @brief 设置语音配置 + * @param context TTS服务上下文 + * @param voice_config 语音配置 + * @return AI_SERVICE_OK 成功,其他值失败 + */ +ai_service_error_t tts_server_set_voice_config(tts_server_context_t *context, + const tts_voice_config_t *voice_config); + +/** + * @brief 获取当前会话ID + * @param context TTS服务上下文 + * @return 会话ID字符串,NULL表示无活动会话 + */ +const char* tts_server_get_current_session_id(tts_server_context_t *context); + +/** + * @brief 检查是否有活动的合成会话 + * @param context TTS服务上下文 + * @return true 有活动会话,false 无活动会话 + */ +bool tts_server_is_synthesis_active(tts_server_context_t *context); + +/** + * @brief 获取播放状态 + * @param context TTS服务上下文 + * @return 播放状态 + */ +tts_playback_state_t tts_server_get_playback_state(tts_server_context_t *context); + +/** + * @brief 设置TTS回调函数 + * @param context TTS服务上下文 + * @param on_synthesis_start 合成开始回调 + * @param on_synthesis_result 合成结果回调 + * @param on_synthesis_complete 合成完成回调 + * @param on_playback_start 播放开始回调 + * @param on_playback_complete 播放完成回调 + * @param on_playback_error 播放错误回调 + */ +void tts_server_set_callbacks(tts_server_context_t *context, + void (*on_synthesis_start)(void *context, const char *session_id), + void (*on_synthesis_result)(void *context, const tts_synthesis_result_t *result), + void (*on_synthesis_complete)(void *context, const char *session_id), + void (*on_playback_start)(void *context), + void (*on_playback_complete)(void *context), + void (*on_playback_error)(void *context, int error_code)); + +/** + * @brief 获取TTS统计信息 + * @param context TTS服务上下文 + * @param total_text_sent 总发送文本字符数 + * @param total_audio_received 总接收音频字节数 + * @param synthesis_sessions 合成会话数 + * @param playback_sessions 播放会话数 + */ +void tts_server_get_stats(tts_server_context_t *context, + uint32_t *total_text_sent, + uint32_t *total_audio_received, + uint32_t *synthesis_sessions, + uint32_t *playback_sessions); + +/* 工具函数 */ + +/** + * @brief 创建默认音频配置 + * @param config 输出配置 + */ +void tts_server_create_default_audio_config(tts_audio_config_t *config); + +/** + * @brief 创建默认语音配置 + * @param config 输出配置 + */ +void tts_server_create_default_voice_config(tts_voice_config_t *config); + +/** + * @brief 释放合成结果内存 + * @param result 合成结果 + */ +void tts_server_free_synthesis_result(tts_synthesis_result_t *result); + +/** + * @brief 音频格式转字符串 + * @param format 音频格式 + * @return 格式字符串 + */ +const char* tts_server_audio_format_to_string(tts_audio_format_t format); + +/** + * @brief 语音类型转字符串 + * @param voice_type 语音类型 + * @return 类型字符串 + */ +const char* tts_server_voice_type_to_string(tts_voice_type_t voice_type); + +/** + * @brief 语言转字符串 + * @param language 语言 + * @return 语言字符串 + */ +const char* tts_server_language_to_string(tts_language_t language); + +/** + * @brief 情感转字符串 + * @param emotion 情感 + * @return 情感字符串 + */ +const char* tts_server_emotion_to_string(tts_emotion_t emotion); + +/** + * @brief 播放状态转字符串 + * @param state 播放状态 + * @return 状态字符串 + */ +const char* tts_server_playback_state_to_string(tts_playback_state_t state); + +#ifdef __cplusplus +} +#endif + +#endif /* TTS_SERVER_H */ \ No newline at end of file diff --git a/code/sdk/zhihui/ai/src/ai_service_common.c b/code/sdk/zhihui/ai/src/ai_service_common.c new file mode 100644 index 000000000..c8f9c6904 --- /dev/null +++ b/code/sdk/zhihui/ai/src/ai_service_common.c @@ -0,0 +1,902 @@ +/* + * AI服务通用功能实现 + * 提供ASR、TTS、Chat服务的通用基础功能 + */ + +#include +#include +#include +#include +#include +#include "ai_service_common.h" +#include "../../../../application/wearable/ohos_deps/wearable_log.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* 内部宏定义 */ +#define AI_SERVICE_LOG_TAG "AI_SERVICE" +#define AI_SERVICE_THREAD_STACK_SIZE 8192 +#define AI_SERVICE_HEARTBEAT_STACK_SIZE 4096 + +/* WebSocket回调函数声明 */ +static void ai_service_on_open(int client); +static void ai_service_on_message(int client, const char *data, size_t length, bool isFinished); +static void ai_service_on_error(int client, struct WebSocketErrorResult errorResult); +static void ai_service_on_close(int client); + +/* 内部函数声明 */ +static void ai_service_main_thread(void *arg); +static void ai_service_heartbeat_thread(void *arg); +static void ai_service_reconnect_timer_callback(void *arg); +static void ai_service_heartbeat_timer_callback(void *arg); +static ai_service_error_t ai_service_internal_connect(ai_service_context_t *context); +static ai_service_error_t ai_service_internal_disconnect(ai_service_context_t *context); +static void ai_service_update_stats_on_connect(ai_service_context_t *context); +static void ai_service_update_stats_on_disconnect(ai_service_context_t *context); +static void ai_service_update_stats_on_send(ai_service_context_t *context, size_t bytes); +static void ai_service_update_stats_on_receive(ai_service_context_t *context, size_t bytes); +static void ai_service_update_stats_on_error(ai_service_context_t *context); + +/* 全局上下文映射表(用于WebSocket回调) */ +#define AI_SERVICE_MAX_CONTEXTS 10 +static ai_service_context_t *g_context_map[AI_SERVICE_MAX_CONTEXTS] = {NULL}; +static osMutexId_t g_context_map_mutex = NULL; + +/* 初始化全局资源 */ +static ai_service_error_t ai_service_init_global_resources(void) +{ + if (g_context_map_mutex == NULL) { + g_context_map_mutex = osMutexNew(NULL); + if (g_context_map_mutex == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create global mutex", AI_SERVICE_LOG_TAG); + return AI_SERVICE_ERROR_INTERNAL; + } + } + return AI_SERVICE_OK; +} + +/* 注册上下文到全局映射表 */ +static ai_service_error_t ai_service_register_context(ai_service_context_t *context) +{ + if (ai_service_init_global_resources() != AI_SERVICE_OK) { + return AI_SERVICE_ERROR_INTERNAL; + } + + osMutexAcquire(g_context_map_mutex, osWaitForever); + + for (int i = 0; i < AI_SERVICE_MAX_CONTEXTS; i++) { + if (g_context_map[i] == NULL) { + g_context_map[i] = context; + context->ws_client_fd = i; // 使用索引作为临时客户端ID + osMutexRelease(g_context_map_mutex); + return AI_SERVICE_OK; + } + } + + osMutexRelease(g_context_map_mutex); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Context map is full", AI_SERVICE_LOG_TAG); + return AI_SERVICE_ERROR_INTERNAL; +} + +/* 从全局映射表注销上下文 */ +static void ai_service_unregister_context(ai_service_context_t *context) +{ + if (g_context_map_mutex == NULL) return; + + osMutexAcquire(g_context_map_mutex, osWaitForever); + + for (int i = 0; i < AI_SERVICE_MAX_CONTEXTS; i++) { + if (g_context_map[i] == context) { + g_context_map[i] = NULL; + break; + } + } + + osMutexRelease(g_context_map_mutex); +} + +/* 根据客户端ID获取上下文 */ +static ai_service_context_t* ai_service_get_context_by_client(int client) +{ + if (g_context_map_mutex == NULL || client < 0 || client >= AI_SERVICE_MAX_CONTEXTS) { + return NULL; + } + + osMutexAcquire(g_context_map_mutex, osWaitForever); + ai_service_context_t *context = g_context_map[client]; + osMutexRelease(g_context_map_mutex); + + return context; +} + +/* 公共接口实现 */ + +ai_service_error_t ai_service_init(ai_service_context_t *context, + const char *service_name, + const ai_service_config_t *config) +{ + if (!context || !service_name || !config) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + // 清零上下文 + memset(context, 0, sizeof(ai_service_context_t)); + + // 复制服务名称 + strncpy(context->service_name, service_name, sizeof(context->service_name) - 1); + + // 复制配置 + memcpy(&context->config, config, sizeof(ai_service_config_t)); + + // 初始化状态 + context->state = AI_SERVICE_STATE_IDLE; + context->is_connected = false; + context->retry_count = 0; + context->ws_client_fd = -1; + + // 创建互斥锁 + context->mutex = osMutexNew(NULL); + if (context->mutex == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create mutex for %s", + AI_SERVICE_LOG_TAG, service_name); + return AI_SERVICE_ERROR_INTERNAL; + } + + // 创建连接信号量 + context->connect_sem = osSemaphoreNew(1, 0, NULL); + if (context->connect_sem == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create semaphore for %s", + AI_SERVICE_LOG_TAG, service_name); + osMutexDelete(context->mutex); + return AI_SERVICE_ERROR_INTERNAL; + } + + // 注册到全局映射表 + ai_service_error_t ret = ai_service_register_context(context); + if (ret != AI_SERVICE_OK) { + osSemaphoreDelete(context->connect_sem); + osMutexDelete(context->mutex); + return ret; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Service %s initialized", + AI_SERVICE_LOG_TAG, service_name); + + return AI_SERVICE_OK; +} + +ai_service_error_t ai_service_destroy(ai_service_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + // 停止服务 + ai_service_stop(context); + + // 从全局映射表注销 + ai_service_unregister_context(context); + + // 删除定时器 + if (context->reconnect_timer) { + osTimerDelete(context->reconnect_timer); + context->reconnect_timer = NULL; + } + + if (context->heartbeat_timer) { + osTimerDelete(context->heartbeat_timer); + context->heartbeat_timer = NULL; + } + + // 删除同步对象 + if (context->connect_sem) { + osSemaphoreDelete(context->connect_sem); + context->connect_sem = NULL; + } + + if (context->mutex) { + osMutexDelete(context->mutex); + context->mutex = NULL; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Service %s destroyed", + AI_SERVICE_LOG_TAG, context->service_name); + + return AI_SERVICE_OK; +} + +ai_service_error_t ai_service_start(ai_service_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + osMutexAcquire(context->mutex, osWaitForever); + + if (context->state != AI_SERVICE_STATE_IDLE) { + osMutexRelease(context->mutex); + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Service %s is not in idle state", + AI_SERVICE_LOG_TAG, context->service_name); + return AI_SERVICE_ERROR_ALREADY_CONNECTED; + } + + // 创建重连定时器 + context->reconnect_timer = osTimerNew(ai_service_reconnect_timer_callback, osTimerOnce, context, NULL); + if (context->reconnect_timer == NULL) { + osMutexRelease(context->mutex); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create reconnect timer for %s", + AI_SERVICE_LOG_TAG, context->service_name); + return AI_SERVICE_ERROR_INTERNAL; + } + + // 创建心跳定时器 + context->heartbeat_timer = osTimerNew(ai_service_heartbeat_timer_callback, osTimerPeriodic, context, NULL); + if (context->heartbeat_timer == NULL) { + osTimerDelete(context->reconnect_timer); + context->reconnect_timer = NULL; + osMutexRelease(context->mutex); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create heartbeat timer for %s", + AI_SERVICE_LOG_TAG, context->service_name); + return AI_SERVICE_ERROR_INTERNAL; + } + + // 创建主线程 + osThreadAttr_t thread_attr = {0}; + thread_attr.name = context->service_name; + thread_attr.stack_size = AI_SERVICE_THREAD_STACK_SIZE; + thread_attr.priority = osPriorityNormal; + // void* p = aligned_alloc(16,AI_SERVICE_THREAD_STACK_SIZE); + // 使用memalign分配栈内存 + void* p = memalign(16,AI_SERVICE_THREAD_STACK_SIZE); + if(p==NULL) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to aligned alloc for %s", + AI_SERVICE_LOG_TAG, context->service_name); + return AI_SERVICE_ERROR_INTERNAL; + } + thread_attr.stack_mem = p; + + context->service_thread = osThreadNew(ai_service_main_thread, context, &thread_attr); + if (context->service_thread == NULL) { + osTimerDelete(context->heartbeat_timer); + osTimerDelete(context->reconnect_timer); + context->heartbeat_timer = NULL; + context->reconnect_timer = NULL; + osMutexRelease(context->mutex); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create main thread for %s", + AI_SERVICE_LOG_TAG, context->service_name); + return AI_SERVICE_ERROR_INTERNAL; + } + + context->state = AI_SERVICE_STATE_CONNECTING; + osMutexRelease(context->mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Service %s started", + AI_SERVICE_LOG_TAG, context->service_name); + + return AI_SERVICE_OK; +} + +ai_service_error_t ai_service_stop(ai_service_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + osMutexAcquire(context->mutex, osWaitForever); + + if (context->state == AI_SERVICE_STATE_IDLE) { + osMutexRelease(context->mutex); + return AI_SERVICE_OK; + } + + context->state = AI_SERVICE_STATE_IDLE; + osMutexRelease(context->mutex); + + // 断开连接 + ai_service_internal_disconnect(context); + + // 停止定时器 + if (context->reconnect_timer) { + osTimerStop(context->reconnect_timer); + } + + if (context->heartbeat_timer) { + osTimerStop(context->heartbeat_timer); + } + + // 等待线程结束 + if (context->service_thread) { + osThreadTerminate(context->service_thread); + context->service_thread = NULL; + } + + if (context->heartbeat_thread) { + osThreadTerminate(context->heartbeat_thread); + context->heartbeat_thread = NULL; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Service %s stopped", + AI_SERVICE_LOG_TAG, context->service_name); + + return AI_SERVICE_OK; +} + +ai_service_error_t ai_service_connect(ai_service_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + return ai_service_internal_connect(context); +} + +ai_service_error_t ai_service_disconnect(ai_service_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + return ai_service_internal_disconnect(context); +} + +ai_service_error_t ai_service_send_data(ai_service_context_t *context, + const char *data, size_t length) +{ + if (!context || !data || length == 0) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + if (!context->is_connected || context->ws_client_fd < 0) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Service %s is not connected", + AI_SERVICE_LOG_TAG, context->service_name); + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + int ret = WebSocketClientSendBinStart(context->ws_client_fd, data, length); + if (ret != WEBSOCKET_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to send data for %s: %d", + AI_SERVICE_LOG_TAG, context->service_name, ret); + ai_service_update_stats_on_error(context); + return AI_SERVICE_ERROR_NETWORK; + } + + ai_service_update_stats_on_send(context, length); + + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "[%s] Sent %zu bytes for %s", + AI_SERVICE_LOG_TAG, length, context->service_name); + + return AI_SERVICE_OK; +} + +ai_service_error_t ai_service_send_text(ai_service_context_t *context, const char *text) +{ + if (!context || !text) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + if (!context->is_connected || context->ws_client_fd < 0) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Service %s is not connected", + AI_SERVICE_LOG_TAG, context->service_name); + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + size_t length = strlen(text); + int ret = WebSocketClientSendText(context->ws_client_fd, text, length); + if (ret != WEBSOCKET_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to send text for %s: %d", + AI_SERVICE_LOG_TAG, context->service_name, ret); + ai_service_update_stats_on_error(context); + return AI_SERVICE_ERROR_NETWORK; + } + + ai_service_update_stats_on_send(context, length); + + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "[%s] Sent text (%zu chars) for %s", + AI_SERVICE_LOG_TAG, length, context->service_name); + + return AI_SERVICE_OK; +} + +ai_service_error_t ai_service_send_binary(ai_service_context_t *context, + const char *data, size_t length) +{ + return ai_service_send_data(context, data, length); +} + +bool ai_service_is_connected(ai_service_context_t *context) +{ + if (!context) { + return false; + } + + return context->is_connected && (context->ws_client_fd >= 0) && + WebSocketClientIsConnected(context->ws_client_fd); +} + +ai_service_state_t ai_service_get_state(ai_service_context_t *context) +{ + if (!context) { + return AI_SERVICE_STATE_ERROR; + } + + return context->state; +} + +const ai_service_stats_t* ai_service_get_stats(ai_service_context_t *context) +{ + if (!context) { + return NULL; + } + + return &context->stats; +} + +void ai_service_set_callbacks(ai_service_context_t *context, + void (*on_connected)(void *context), + void (*on_disconnected)(void *context), + void (*on_message_received)(void *context, const char *data, size_t length), + void (*on_error)(void *context, int error_code, const char *error_msg)) +{ + if (!context) { + return; + } + + context->on_connected = on_connected; + context->on_disconnected = on_disconnected; + context->on_message_received = on_message_received; + context->on_error = on_error; +} + +void ai_service_set_user_data(ai_service_context_t *context, void *user_data) +{ + if (context) { + context->user_data = user_data; + } +} + +void* ai_service_get_user_data(ai_service_context_t *context) +{ + return context ? context->user_data : NULL; +} + +/* 工具函数实现 */ + +uint32_t ai_service_get_timestamp(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (uint32_t)(tv.tv_sec * 1000 + tv.tv_usec / 1000); +} + +ai_service_error_t ai_service_generate_uuid(char *uuid_str, size_t max_len) +{ + if (!uuid_str || max_len < 37) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + // 简单的UUID生成(基于时间戳和随机数) + uint32_t timestamp = ai_service_get_timestamp(); + uint32_t random1 = rand(); + uint32_t random2 = rand(); + + snprintf(uuid_str, max_len, "%08x-%04x-%04x-%04x-%08x%04x", + timestamp, + (random1 >> 16) & 0xFFFF, + random1 & 0xFFFF, + (random2 >> 16) & 0xFFFF, + random2, + timestamp & 0xFFFF); + + return AI_SERVICE_OK; +} + +ai_service_error_t ai_service_create_json_message(const char *type, + const char *data, + char *output, + size_t max_len) +{ + if (!type || !data || !output || max_len == 0) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + char uuid[64]; + ai_service_generate_uuid(uuid, sizeof(uuid)); + uint32_t timestamp = ai_service_get_timestamp(); + + int ret = snprintf(output, max_len, + "{" + "\"id\":\"%s\"," + "\"type\":\"%s\"," + "\"timestamp\":%u," + "\"data\":\"%s\"" + "}", + uuid, type, timestamp, data); + + if (ret >= (int)max_len) { + return AI_SERVICE_ERROR_INVALID_PARAM; // 缓冲区太小 + } + + return AI_SERVICE_OK; +} + +/* 内部函数实现 */ + +static ai_service_error_t ai_service_internal_connect(ai_service_context_t *context) +{ + if (context->is_connected) { + return AI_SERVICE_OK; + } + + // 创建WebSocket客户端 + int client_fd = WebSocketClientConstructor(ai_service_on_open, ai_service_on_message, + ai_service_on_error, ai_service_on_close); + if (client_fd < 0) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create WebSocket client for %s", + AI_SERVICE_LOG_TAG, context->service_name); + return AI_SERVICE_ERROR_NETWORK; + } + + // 设置协议 + if (strlen(context->config.protocol) > 0) { + WebSocketClientSetProtocol(client_fd, context->config.protocol); + } + + // 连接到服务器 + int ret = WebSocketClientConnect(client_fd, context->config.server_url); + if (ret != WEBSOCKET_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to connect to %s: %d", + AI_SERVICE_LOG_TAG, context->config.server_url, ret); + WebSocketClientClose(client_fd); + return AI_SERVICE_ERROR_NETWORK; + } + + context->ws_client_fd = client_fd; + + // 等待连接成功 + osStatus_t status = osSemaphoreAcquire(context->connect_sem, context->config.connect_timeout_ms); + if (status != osOK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Connection timeout for %s", + AI_SERVICE_LOG_TAG, context->service_name); + WebSocketClientClose(client_fd); + context->ws_client_fd = -1; + return AI_SERVICE_ERROR_TIMEOUT; + } + + if (!context->is_connected) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Connection failed for %s", + AI_SERVICE_LOG_TAG, context->service_name); + return AI_SERVICE_ERROR_NETWORK; + } + + // 启动心跳定时器 + if (context->config.heartbeat_interval_ms > 0) { + osTimerStart(context->heartbeat_timer, context->config.heartbeat_interval_ms); + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Connected to %s", + AI_SERVICE_LOG_TAG, context->config.server_url); + + return AI_SERVICE_OK; +} + +static ai_service_error_t ai_service_internal_disconnect(ai_service_context_t *context) +{ + if (!context->is_connected) { + return AI_SERVICE_OK; + } + + // 停止心跳定时器 + if (context->heartbeat_timer) { + osTimerStop(context->heartbeat_timer); + } + + // 关闭WebSocket连接 + if (context->ws_client_fd >= 0) { + WebSocketClientClose(context->ws_client_fd); + context->ws_client_fd = -1; + } + + context->is_connected = false; + ai_service_update_stats_on_disconnect(context); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Disconnected from %s", + AI_SERVICE_LOG_TAG, context->service_name); + + return AI_SERVICE_OK; +} + +/* WebSocket回调函数实现 */ + +static void ai_service_on_open(int client) +{ + ai_service_context_t *context = ai_service_get_context_by_client(client); + if (!context) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Invalid client %d in on_open", + AI_SERVICE_LOG_TAG, client); + return; + } + + context->is_connected = true; + context->state = AI_SERVICE_STATE_CONNECTED; + context->retry_count = 0; + + ai_service_update_stats_on_connect(context); + + // 释放连接信号量 + osSemaphoreRelease(context->connect_sem); + + // 调用用户回调 + if (context->on_connected) { + context->on_connected(context); + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Service %s connected", + AI_SERVICE_LOG_TAG, context->service_name); +} + +static void ai_service_on_message(int client, const char *data, size_t length, bool isFinished) +{ + ai_service_context_t *context = ai_service_get_context_by_client(client); + if (!context) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Invalid client %d in on_message", + AI_SERVICE_LOG_TAG, client); + return; + } + + ai_service_update_stats_on_receive(context, length); + + // 调用用户回调 + if (context->on_message_received) { + context->on_message_received(context, data, length); + } + + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "[%s] Service %s received %zu bytes", + AI_SERVICE_LOG_TAG, context->service_name, length); +} + +static void ai_service_on_error(int client, struct WebSocketErrorResult errorResult) +{ + ai_service_context_t *context = ai_service_get_context_by_client(client); + if (!context) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Invalid client %d in on_error", + AI_SERVICE_LOG_TAG, client); + return; + } + + context->state = AI_SERVICE_STATE_ERROR; + ai_service_update_stats_on_error(context); + + // 调用用户回调 + if (context->on_error) { + context->on_error(context, errorResult.errorCode, errorResult.errorMessage); + } + + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Service %s error: %d - %s", + AI_SERVICE_LOG_TAG, context->service_name, + errorResult.errorCode, errorResult.errorMessage); + + // 触发重连 + if (context->config.auto_reconnect && context->retry_count < context->config.max_retry_count) { + context->state = AI_SERVICE_STATE_RECONNECTING; + osTimerStart(context->reconnect_timer, context->config.reconnect_interval_ms); + } +} + +static void ai_service_on_close(int client) +{ + ai_service_context_t *context = ai_service_get_context_by_client(client); + if (!context) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Invalid client %d in on_close", + AI_SERVICE_LOG_TAG, client); + return; + } + + context->is_connected = false; + context->state = AI_SERVICE_STATE_DISCONNECTED; + context->ws_client_fd = -1; + + ai_service_update_stats_on_disconnect(context); + + // 停止心跳定时器 + if (context->heartbeat_timer) { + osTimerStop(context->heartbeat_timer); + } + + // 调用用户回调 + if (context->on_disconnected) { + context->on_disconnected(context); + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Service %s closed", + AI_SERVICE_LOG_TAG, context->service_name); + + // 触发重连 + if (context->config.auto_reconnect && context->retry_count < context->config.max_retry_count) { + context->state = AI_SERVICE_STATE_RECONNECTING; + osTimerStart(context->reconnect_timer, context->config.reconnect_interval_ms); + } +} + +/* 线程函数实现 */ + +static void ai_service_main_thread(void *arg) +{ + ai_service_context_t *context = (ai_service_context_t *)arg; + if (!context) { + return; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Main thread started for %s", + AI_SERVICE_LOG_TAG, context->service_name); + + // 初始连接 + ai_service_internal_connect(context); + + // 主循环 + while (context->state != AI_SERVICE_STATE_IDLE) { + osDelay(1000); // 1秒检查一次 + + // 检查连接状态 + if (context->is_connected && !WebSocketClientIsConnected(context->ws_client_fd)) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Connection lost for %s", + AI_SERVICE_LOG_TAG, context->service_name); + context->is_connected = false; + context->state = AI_SERVICE_STATE_DISCONNECTED; + + // 触发重连 + if (context->config.auto_reconnect && context->retry_count < context->config.max_retry_count) { + context->state = AI_SERVICE_STATE_RECONNECTING; + osTimerStart(context->reconnect_timer, context->config.reconnect_interval_ms); + } + } + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Main thread stopped for %s", + AI_SERVICE_LOG_TAG, context->service_name); +} + +static void ai_service_heartbeat_thread(void *arg) +{ + ai_service_context_t *context = (ai_service_context_t *)arg; + if (!context) { + return; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Heartbeat thread started for %s", + AI_SERVICE_LOG_TAG, context->service_name); + + while (context->state != AI_SERVICE_STATE_IDLE) { + osDelay(context->config.heartbeat_interval_ms); + + if (context->is_connected) { + // 发送心跳消息 + const char *heartbeat = "{\"type\":\"heartbeat\"}"; + ai_service_send_text(context, heartbeat); + } + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Heartbeat thread stopped for %s", + AI_SERVICE_LOG_TAG, context->service_name); +} + +/* 定时器回调函数实现 */ + +static void ai_service_reconnect_timer_callback(void *arg) +{ + ai_service_context_t *context = (ai_service_context_t *)arg; + if (!context) { + return; + } + + if (context->state != AI_SERVICE_STATE_RECONNECTING) { + return; + } + + context->retry_count++; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Attempting reconnection %d/%d for %s", + AI_SERVICE_LOG_TAG, context->retry_count, + context->config.max_retry_count, context->service_name); + + ai_service_error_t ret = ai_service_internal_connect(context); + if (ret != AI_SERVICE_OK) { + if (context->retry_count < context->config.max_retry_count) { + // 继续重试 + osTimerStart(context->reconnect_timer, context->config.reconnect_interval_ms); + } else { + // 重试次数用完 + context->state = AI_SERVICE_STATE_ERROR; + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Max retry count reached for %s", + AI_SERVICE_LOG_TAG, context->service_name); + } + } +} + +static void ai_service_heartbeat_timer_callback(void *arg) +{ + ai_service_context_t *context = (ai_service_context_t *)arg; + if (!context) { + return; + } + + if (context->is_connected) { + // 发送心跳消息 + const char *heartbeat = "{\"type\":\"heartbeat\"}"; + ai_service_send_text(context, heartbeat); + } +} + +/* 统计信息更新函数 */ + +static void ai_service_update_stats_on_connect(ai_service_context_t *context) +{ + context->stats.connect_count++; + context->stats.last_connect_time = ai_service_get_timestamp(); +} + +static void ai_service_update_stats_on_disconnect(ai_service_context_t *context) +{ + context->stats.disconnect_count++; + context->stats.last_disconnect_time = ai_service_get_timestamp(); +} + +static void ai_service_update_stats_on_send(ai_service_context_t *context, size_t bytes) +{ + context->stats.send_count++; + context->stats.total_bytes_sent += bytes; +} + +static void ai_service_update_stats_on_receive(ai_service_context_t *context, size_t bytes) +{ + context->stats.receive_count++; + context->stats.total_bytes_received += bytes; +} + +static void ai_service_update_stats_on_error(ai_service_context_t *context) +{ + context->stats.error_count++; +} + + + +/** + * @brief 判断字符串是否为JSON语句(20字节内,首个非空字符为'{',且下一个非空字符为'"') + * @param str 字符串指针 + * @param len 字符串长度 + * @return 如果字符串为JSON对象({...}),返回true,否则返回false + */ +bool ai_service_is_json_string(const char *str, size_t len) +{ + if (!str || len == 0) return false; + size_t max_check = len < 20 ? len : 20; + size_t i = 0; + + // 跳过前导空白 + while (i < max_check && (str[i] == ' ' || str[i] == '\t' || str[i] == '\n' || str[i] == '\r')) { + i++; + } + // 检查第一个非空字符是否为'{' + if (i < max_check && str[i] == '{') { + i++; + // 跳过'{'后面的空白 + while (i < max_check && (str[i] == ' ' || str[i] == '\t' || str[i] == '\n' || str[i] == '\r')) { + i++; + } + // 检查下一个非空字符是否为'"' + if (i < max_check && str[i] == '"') { + return true; + } + } + return false; +} + + + + + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/code/sdk/zhihui/ai/src/ai_service_manager.c b/code/sdk/zhihui/ai/src/ai_service_manager.c new file mode 100644 index 000000000..f5cb1c54f --- /dev/null +++ b/code/sdk/zhihui/ai/src/ai_service_manager.c @@ -0,0 +1,1064 @@ +/* + * Copyright (c) CompanyNameMagicTag. 2023-2023. All rights reserved. + * Description: AI Service Manager Implementation - AI全家桶实现 + * Author: AI Development Team + * Create: 2024-12-28 + */ + +#include "ai_service_manager.h" +#include "../../../../application/wearable/ohos_deps/wearable_log.h" +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ==================== 内部宏定义 ==================== */ + +#define AI_MANAGER_LOG_TAG "AI_MANAGER" +#define AI_MANAGER_THREAD_STACK_SIZE 16384 +#define AI_MANAGER_EVENT_QUEUE_SIZE 32 +#define AI_MANAGER_RESPONSE_TIMEOUT_MS 30000 + +/* ==================== 内部函数声明 ==================== */ + +static void ai_manager_main_thread(void *arg); +static ai_service_error_t ai_manager_init_services(ai_manager_context_t *context); +static ai_service_error_t ai_manager_deinit_services(ai_manager_context_t *context); +static void ai_manager_set_state(ai_manager_context_t *context, ai_manager_state_t new_state); +static void ai_manager_handle_asr_event(ai_manager_context_t *context, const asr_recognition_result_t *result); +static void ai_manager_handle_chat_event(ai_manager_context_t *context, const chat_response_t *response); +static void ai_manager_handle_tts_event(ai_manager_context_t *context, const tts_synthesis_result_t *result); +static void ai_manager_send_event(ai_manager_context_t *context, const ai_event_data_t *event); +static uint32_t ai_manager_get_timestamp(void); + +/* ASR回调函数 */ +static void ai_manager_asr_on_result(void *user_data, const asr_recognition_result_t *result); +static void ai_manager_asr_on_error(void *user_data, int error_code, const char *error_msg); + +/* Chat回调函数 */ +static void ai_manager_chat_on_response(void *user_data, const chat_response_t *response); +static void ai_manager_chat_on_error(void *user_data, int error_code, const char *error_msg); + +/* TTS回调函数 */ +static void ai_manager_tts_on_result(void *user_data, const tts_synthesis_result_t *result); +static void ai_manager_tts_on_error(void *user_data, int error_code, const char *error_msg); + +/* 录音回调函数 */ +static void ai_manager_record_callback(uint8_t *data, uint32_t size); + +/* ==================== 全局变量 ==================== */ + +static ai_manager_context_t *g_current_manager = NULL; // 当前管理器实例(用于录音回调) + +/* ==================== 公共接口实现 ==================== */ + +ai_service_error_t ai_manager_init(ai_manager_context_t *context, + const ai_manager_config_t *config) +{ + if (context == NULL || config == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Invalid parameters", AI_MANAGER_LOG_TAG); + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Initializing AI Manager", AI_MANAGER_LOG_TAG); + + // 清零上下文 + memset(context, 0, sizeof(ai_manager_context_t)); + + // 复制配置 + memcpy(&context->config, config, sizeof(ai_manager_config_t)); + + // 设置初始状态 + context->state = AI_MANAGER_STATE_INITIALIZING; + context->init_timestamp = ai_manager_get_timestamp(); + + // 生成初始会话ID + ai_manager_generate_session_id(context->current_session_id, + sizeof(context->current_session_id)); + + // 创建互斥锁 + context->mutex = osMutexNew(NULL); + if (context->mutex == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create mutex", AI_MANAGER_LOG_TAG); + return AI_SERVICE_ERROR_INTERNAL; + } + + // 创建事件队列 + context->event_queue = osMessageQueueNew(AI_MANAGER_EVENT_QUEUE_SIZE, + sizeof(ai_event_data_t), NULL); + if (context->event_queue == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create event queue", AI_MANAGER_LOG_TAG); + osMutexDelete(context->mutex); + return AI_SERVICE_ERROR_INTERNAL; + } + + // 初始化AI媒体服务 + if (ai_media_service_init() != 0) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to init AI media service", AI_MANAGER_LOG_TAG); + osMessageQueueDelete(context->event_queue); + osMutexDelete(context->mutex); + return AI_SERVICE_ERROR_INTERNAL; + } + + // 设置录音回调 + g_current_manager = context; + ai_media_set_record_callback(ai_manager_record_callback); + + // 初始化各个AI服务 + ai_service_error_t ret = ai_manager_init_services(context); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to init AI services", AI_MANAGER_LOG_TAG); + ai_media_service_deinit(); + osMessageQueueDelete(context->event_queue); + osMutexDelete(context->mutex); + return ret; + } + + // 创建主线程 + osThreadAttr_t thread_attr = { + .name = "ai_manager", + .stack_size = AI_MANAGER_THREAD_STACK_SIZE, + .priority = osPriorityNormal + }; + + context->manager_thread = osThreadNew(ai_manager_main_thread, context, &thread_attr); + if (context->manager_thread == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create main thread", AI_MANAGER_LOG_TAG); + ai_manager_deinit_services(context); + ai_media_service_deinit(); + osMessageQueueDelete(context->event_queue); + osMutexDelete(context->mutex); + return AI_SERVICE_ERROR_INTERNAL; + } + + context->services_initialized = true; + ai_manager_set_state(context, AI_MANAGER_STATE_IDLE); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] AI Manager initialized successfully", AI_MANAGER_LOG_TAG); + return AI_SERVICE_OK; +} + +ai_service_error_t ai_manager_destroy(ai_manager_context_t *context) +{ + if (context == NULL) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Destroying AI Manager", AI_MANAGER_LOG_TAG); + + ai_manager_set_state(context, AI_MANAGER_STATE_SHUTDOWN); + + // 停止所有服务 + ai_manager_stop(context); + + // 终止主线程 + if (context->manager_thread != NULL) { + osThreadTerminate(context->manager_thread); + context->manager_thread = NULL; + } + + // 反初始化服务 + ai_manager_deinit_services(context); + + // 反初始化媒体服务 + ai_media_service_deinit(); + + // 清理资源 + if (context->event_queue != NULL) { + osMessageQueueDelete(context->event_queue); + context->event_queue = NULL; + } + + if (context->mutex != NULL) { + osMutexDelete(context->mutex); + context->mutex = NULL; + } + + // 清空全局引用 + if (g_current_manager == context) { + g_current_manager = NULL; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] AI Manager destroyed", AI_MANAGER_LOG_TAG); + return AI_SERVICE_OK; +} + +ai_service_error_t ai_manager_start(ai_manager_context_t *context) +{ + if (context == NULL) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + osMutexAcquire(context->mutex, osWaitForever); + + if (context->state != AI_MANAGER_STATE_IDLE) { + osMutexRelease(context->mutex); + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Manager not in idle state", AI_MANAGER_LOG_TAG); + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Starting AI services", AI_MANAGER_LOG_TAG); + + // 启动ASR服务 + if (asr_server_start(context->asr_context) != AI_SERVICE_OK) { + osMutexRelease(context->mutex); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to start ASR service", AI_MANAGER_LOG_TAG); + return AI_SERVICE_ERROR_INTERNAL; + } + + // 启动Chat服务 + if (chat_server_start(context->chat_context) != AI_SERVICE_OK) { + asr_server_stop(context->asr_context); + osMutexRelease(context->mutex); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to start Chat service", AI_MANAGER_LOG_TAG); + return AI_SERVICE_ERROR_INTERNAL; + } + + // 启动TTS服务 + if (tts_server_start(context->tts_context) != AI_SERVICE_OK) { + chat_server_stop(context->chat_context); + asr_server_stop(context->asr_context); + osMutexRelease(context->mutex); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to start TTS service", AI_MANAGER_LOG_TAG); + return AI_SERVICE_ERROR_INTERNAL; + } + + ai_manager_set_state(context, AI_MANAGER_STATE_READY); + osMutexRelease(context->mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] All AI services started successfully", AI_MANAGER_LOG_TAG); + return AI_SERVICE_OK; +} + +ai_service_error_t ai_manager_stop(ai_manager_context_t *context) +{ + if (context == NULL) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + osMutexAcquire(context->mutex, osWaitForever); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Stopping AI services", AI_MANAGER_LOG_TAG); + + // 结束当前对话 + if (context->is_conversation_active) { + ai_manager_end_conversation(context); + } + + // 停止TTS服务 + if (context->tts_context != NULL) { + tts_server_stop(context->tts_context); + } + + // 停止Chat服务 + if (context->chat_context != NULL) { + chat_server_stop(context->chat_context); + } + + // 停止ASR服务 + if (context->asr_context != NULL) { + asr_server_stop(context->asr_context); + } + + ai_manager_set_state(context, AI_MANAGER_STATE_IDLE); + osMutexRelease(context->mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] All AI services stopped", AI_MANAGER_LOG_TAG); + return AI_SERVICE_OK; +} + +ai_service_error_t ai_manager_start_conversation(ai_manager_context_t *context, + const char *session_id) +{ + if (context == NULL) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + osMutexAcquire(context->mutex, osWaitForever); + + if (context->state != AI_MANAGER_STATE_READY) { + osMutexRelease(context->mutex); + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Manager not ready for conversation", AI_MANAGER_LOG_TAG); + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + if (context->is_conversation_active) { + osMutexRelease(context->mutex); + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Conversation already active", AI_MANAGER_LOG_TAG); + return AI_SERVICE_ERROR_ALREADY_CONNECTED; + } + + // 设置会话ID + if (session_id != NULL) { + strncpy(context->current_session_id, session_id, sizeof(context->current_session_id) - 1); + context->current_session_id[sizeof(context->current_session_id) - 1] = '\0'; + } else { + ai_manager_generate_session_id(context->current_session_id, + sizeof(context->current_session_id)); + } + + context->is_conversation_active = true; + context->stats.conversation_count++; + context->stats.last_conversation_time = ai_manager_get_timestamp(); + + osMutexRelease(context->mutex); + + // 发送对话开始事件 + ai_event_data_t event = { + .type = AI_EVENT_CONVERSATION_START, + .timestamp = ai_manager_get_timestamp(), + .is_final = true + }; + strncpy(event.session_id, context->current_session_id, sizeof(event.session_id) - 1); + ai_manager_send_event(context, &event); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Conversation started: %s", + AI_MANAGER_LOG_TAG, context->current_session_id); + return AI_SERVICE_OK; +} + +ai_service_error_t ai_manager_end_conversation(ai_manager_context_t *context) +{ + if (context == NULL) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + osMutexAcquire(context->mutex, osWaitForever); + + if (!context->is_conversation_active) { + osMutexRelease(context->mutex); + return AI_SERVICE_OK; // 已经结束了 + } + + // 停止录音 + if (context->is_recording_active) { + ai_media_record_stop(); + context->is_recording_active = false; + } + + // 停止播放 + if (context->is_playback_active) { + ai_media_play_stop(); + context->is_playback_active = false; + } + + context->is_conversation_active = false; + ai_manager_set_state(context, AI_MANAGER_STATE_READY); + + osMutexRelease(context->mutex); + + // 发送对话结束事件 + ai_event_data_t event = { + .type = AI_EVENT_CONVERSATION_END, + .timestamp = ai_manager_get_timestamp(), + .is_final = true + }; + strncpy(event.session_id, context->current_session_id, sizeof(event.session_id) - 1); + ai_manager_send_event(context, &event); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Conversation ended: %s", + AI_MANAGER_LOG_TAG, context->current_session_id); + return AI_SERVICE_OK; +} + +ai_service_error_t ai_manager_start_speech_input(ai_manager_context_t *context) +{ + if (context == NULL) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + osMutexAcquire(context->mutex, osWaitForever); + + if (!context->is_conversation_active) { + osMutexRelease(context->mutex); + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] No active conversation", AI_MANAGER_LOG_TAG); + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + if (context->is_recording_active) { + osMutexRelease(context->mutex); + return AI_SERVICE_OK; // 已经在录音了 + } + + // 停止当前TTS播放 + if (context->is_playback_active) { + ai_media_play_stop(); + context->is_playback_active = false; + } + + // 开始ASR识别 + if (asr_server_start_recognition(context->asr_context, context->current_session_id) != AI_SERVICE_OK) { + osMutexRelease(context->mutex); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to start ASR recognition", AI_MANAGER_LOG_TAG); + return AI_SERVICE_ERROR_INTERNAL; + } + + // 开始录音 + ai_media_record_start((uint8_t*)"pcm"); + context->is_recording_active = true; + ai_manager_set_state(context, AI_MANAGER_STATE_LISTENING); + + osMutexRelease(context->mutex); + + // 发送语音开始事件 + ai_event_data_t event = { + .type = AI_EVENT_SPEECH_START, + .timestamp = ai_manager_get_timestamp(), + .is_final = false + }; + strncpy(event.session_id, context->current_session_id, sizeof(event.session_id) - 1); + ai_manager_send_event(context, &event); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Speech input started", AI_MANAGER_LOG_TAG); + return AI_SERVICE_OK; +} + +ai_service_error_t ai_manager_stop_speech_input(ai_manager_context_t *context) +{ + if (context == NULL) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + osMutexAcquire(context->mutex, osWaitForever); + + if (!context->is_recording_active) { + osMutexRelease(context->mutex); + return AI_SERVICE_OK; // 已经停止了 + } + + // 停止录音 + ai_media_record_stop(); + context->is_recording_active = false; + + // 停止ASR识别 + asr_server_stop_recognition(context->asr_context); + + ai_manager_set_state(context, AI_MANAGER_STATE_PROCESSING); + osMutexRelease(context->mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Speech input stopped", AI_MANAGER_LOG_TAG); + return AI_SERVICE_OK; +} + +ai_service_error_t ai_manager_send_text_message(ai_manager_context_t *context, + const char *text) +{ + if (context == NULL || text == NULL) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + osMutexAcquire(context->mutex, osWaitForever); + + if (!context->is_conversation_active) { + osMutexRelease(context->mutex); + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] No active conversation", AI_MANAGER_LOG_TAG); + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + // 发送消息到Chat服务 + chat_message_t message = { + .role = CHAT_ROLE_USER, + .content = (char*)text, + .timestamp = ai_manager_get_timestamp() + }; + + if (chat_server_send_message(context->chat_context, message.content, NULL) != AI_SERVICE_OK) { + osMutexRelease(context->mutex); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to send chat message", AI_MANAGER_LOG_TAG); + return AI_SERVICE_ERROR_INTERNAL; + } + + context->stats.chat_request_count++; + ai_manager_set_state(context, AI_MANAGER_STATE_PROCESSING); + osMutexRelease(context->mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Text message sent: %s", AI_MANAGER_LOG_TAG, text); + return AI_SERVICE_OK; +} + +ai_service_error_t ai_manager_stop_tts_playback(ai_manager_context_t *context) +{ + if (context == NULL) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + osMutexAcquire(context->mutex, osWaitForever); + + if (context->is_playback_active) { + ai_media_play_stop(); + tts_server_stop_synthesis(context->tts_context); + context->is_playback_active = false; + ai_manager_set_state(context, AI_MANAGER_STATE_READY); + } + + osMutexRelease(context->mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS playback stopped", AI_MANAGER_LOG_TAG); + return AI_SERVICE_OK; +} + +void ai_manager_set_event_callback(ai_manager_context_t *context, + ai_event_callback_t callback, + void *user_data) +{ + if (context == NULL) { + return; + } + + osMutexAcquire(context->mutex, osWaitForever); + context->on_event = callback; + context->user_data = user_data; + osMutexRelease(context->mutex); +} + +ai_manager_state_t ai_manager_get_state(ai_manager_context_t *context) +{ + if (context == NULL) { + return AI_MANAGER_STATE_ERROR; + } + + osMutexAcquire(context->mutex, osWaitForever); + ai_manager_state_t state = context->state; + osMutexRelease(context->mutex); + + return state; +} + +const ai_manager_stats_t* ai_manager_get_stats(ai_manager_context_t *context) +{ + if (context == NULL) { + return NULL; + } + + return &context->stats; +} + +bool ai_manager_is_service_ready(ai_manager_context_t *context, + ai_service_type_t service_type) +{ + if (context == NULL) { + return false; + } + + switch (service_type) { + case AI_SERVICE_TYPE_ASR: + return (context->asr_context != NULL && + ai_service_get_state(&context->asr_context->base) == AI_SERVICE_STATE_CONNECTED); + case AI_SERVICE_TYPE_CHAT: + return (context->chat_context != NULL && + ai_service_get_state(&context->chat_context->base) == AI_SERVICE_STATE_CONNECTED); + case AI_SERVICE_TYPE_TTS: + return (context->tts_context != NULL && + ai_service_get_state(&context->tts_context->base) == AI_SERVICE_STATE_CONNECTED); + case AI_SERVICE_TYPE_ALL: + return (ai_manager_is_service_ready(context, AI_SERVICE_TYPE_ASR) && + ai_manager_is_service_ready(context, AI_SERVICE_TYPE_CHAT) && + ai_manager_is_service_ready(context, AI_SERVICE_TYPE_TTS)); + default: + return false; + } +} + +/* ==================== 工具函数实现 ==================== */ + +void ai_manager_create_default_config(ai_manager_config_t *config) +{ + if (config == NULL) { + return; + } + + memset(config, 0, sizeof(ai_manager_config_t)); + + // ASR默认配置 + strcpy(config->asr_server_url, "ws://localhost"); + config->asr_server_port = 8080; + asr_server_create_default_audio_config(&config->asr_audio_config); + asr_server_create_default_recognition_config(&config->asr_recognition_config); + + // Chat默认配置 + strcpy(config->chat_server_url, "ws://localhost"); + config->chat_server_port = 8081; + chat_server_create_default_model_config(&config->chat_model_config); + + // TTS默认配置 + strcpy(config->tts_server_url, "ws://localhost"); + config->tts_server_port = 8082; + tts_server_create_default_audio_config(&config->tts_audio_config); + tts_server_create_default_voice_config(&config->tts_voice_config); + + // 通用配置 + config->connect_timeout_ms = AI_CONNECT_TIMEOUT_MS; + config->response_timeout_ms = AI_MANAGER_RESPONSE_TIMEOUT_MS; + config->auto_reconnect = true; + config->max_retry_count = AI_MANAGER_MAX_RETRY_COUNT; +} + +void ai_manager_generate_session_id(char *session_id, size_t max_len) +{ + if (session_id == NULL || max_len == 0) { + return; + } + + uint32_t timestamp = ai_manager_get_timestamp(); + static uint32_t counter = 0; + + snprintf(session_id, max_len, "ai_session_%u_%u", timestamp, ++counter); +} + +const char* ai_manager_state_to_string(ai_manager_state_t state) +{ + switch (state) { + case AI_MANAGER_STATE_IDLE: return "IDLE"; + case AI_MANAGER_STATE_INITIALIZING: return "INITIALIZING"; + case AI_MANAGER_STATE_READY: return "READY"; + case AI_MANAGER_STATE_LISTENING: return "LISTENING"; + case AI_MANAGER_STATE_PROCESSING: return "PROCESSING"; + case AI_MANAGER_STATE_SPEAKING: return "SPEAKING"; + case AI_MANAGER_STATE_ERROR: return "ERROR"; + case AI_MANAGER_STATE_SHUTDOWN: return "SHUTDOWN"; + default: return "UNKNOWN"; + } +} + +const char* ai_manager_event_type_to_string(ai_event_type_t event_type) +{ + switch (event_type) { + case AI_EVENT_CONVERSATION_START: return "CONVERSATION_START"; + case AI_EVENT_SPEECH_START: return "SPEECH_START"; + case AI_EVENT_SPEECH_PARTIAL: return "SPEECH_PARTIAL"; + case AI_EVENT_SPEECH_END: return "SPEECH_END"; + case AI_EVENT_CHAT_RESPONSE_START: return "CHAT_RESPONSE_START"; + case AI_EVENT_CHAT_RESPONSE_PARTIAL: return "CHAT_RESPONSE_PARTIAL"; + case AI_EVENT_CHAT_RESPONSE_END: return "CHAT_RESPONSE_END"; + case AI_EVENT_TTS_START: return "TTS_START"; + case AI_EVENT_TTS_END: return "TTS_END"; + case AI_EVENT_CONVERSATION_END: return "CONVERSATION_END"; + case AI_EVENT_ERROR: return "ERROR"; + default: return "UNKNOWN"; + } +} + +void ai_manager_free_event_data(ai_event_data_t *event) +{ + if (event == NULL) { + return; + } + + if (event->text != NULL) { + free(event->text); + event->text = NULL; + } + + if (event->error_message != NULL) { + free(event->error_message); + event->error_message = NULL; + } +} + +/* ==================== 内部函数实现 ==================== */ + +static void ai_manager_main_thread(void *arg) +{ + ai_manager_context_t *context = (ai_manager_context_t *)arg; + ai_event_data_t event; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Main thread started", AI_MANAGER_LOG_TAG); + + while (context->state != AI_MANAGER_STATE_SHUTDOWN) { + // 处理事件队列中的事件 + osStatus_t status = osMessageQueueGet(context->event_queue, &event, NULL, 1000); + if (status == osOK) { + // 调用用户回调 + if (context->on_event != NULL) { + context->on_event(context->user_data, &event); + } + + // 清理事件数据 + ai_manager_free_event_data(&event); + } + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Main thread exited", AI_MANAGER_LOG_TAG); +} + +static ai_service_error_t ai_manager_init_services(ai_manager_context_t *context) +{ + // 初始化ASR服务 + context->asr_context = (asr_server_context_t *)malloc(sizeof(asr_server_context_t)); + if (context->asr_context == NULL) { + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + ai_service_config_t asr_config = { + .server_port = context->config.asr_server_port, + .connect_timeout_ms = context->config.connect_timeout_ms, + .receive_timeout_ms = context->config.response_timeout_ms, + .auto_reconnect = context->config.auto_reconnect, + .max_retry_count = context->config.max_retry_count + }; + strcpy(asr_config.server_url, context->config.asr_server_url); + + if (asr_server_init(context->asr_context, + asr_config.server_url, + &context->config.asr_audio_config, + &context->config.asr_recognition_config) != AI_SERVICE_OK) { + free(context->asr_context); + context->asr_context = NULL; + return AI_SERVICE_ERROR_INTERNAL; + } + + // 设置ASR回调 + asr_server_set_callbacks(context->asr_context, + ai_manager_asr_on_result, + NULL, // on_recognition_start + NULL, // on_recognition_end + NULL); // on_audio_level + + // 初始化Chat服务 + context->chat_context = (chat_server_context_t *)malloc(sizeof(chat_server_context_t)); + if (context->chat_context == NULL) { + asr_server_destroy(context->asr_context); + free(context->asr_context); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + ai_service_config_t chat_config = { + .server_port = context->config.chat_server_port, + .connect_timeout_ms = context->config.connect_timeout_ms, + .receive_timeout_ms = context->config.response_timeout_ms, + .auto_reconnect = context->config.auto_reconnect, + .max_retry_count = context->config.max_retry_count + }; + strcpy(chat_config.server_url, context->config.chat_server_url); + + if (chat_server_init(context->chat_context, + chat_config.server_url, + &context->config.chat_model_config) != AI_SERVICE_OK) { + asr_server_destroy(context->asr_context); + free(context->asr_context); + free(context->chat_context); + context->chat_context = NULL; + return AI_SERVICE_ERROR_INTERNAL; + } + + // 设置Chat回调 + chat_server_set_callbacks(context->chat_context, + NULL, // on_chat_start + ai_manager_chat_on_response, + NULL, // on_chat_complete + NULL); // on_chat_streaming + + // 初始化TTS服务 + context->tts_context = (tts_server_context_t *)malloc(sizeof(tts_server_context_t)); + if (context->tts_context == NULL) { + chat_server_destroy(context->chat_context); + free(context->chat_context); + asr_server_destroy(context->asr_context); + free(context->asr_context); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + ai_service_config_t tts_config = { + .server_port = context->config.tts_server_port, + .connect_timeout_ms = context->config.connect_timeout_ms, + .receive_timeout_ms = context->config.response_timeout_ms, + .auto_reconnect = context->config.auto_reconnect, + .max_retry_count = context->config.max_retry_count + }; + strcpy(tts_config.server_url, context->config.tts_server_url); + + if (tts_server_init(context->tts_context, + tts_config.server_url, + &context->config.tts_audio_config, + &context->config.tts_voice_config) != AI_SERVICE_OK) { + chat_server_destroy(context->chat_context); + free(context->chat_context); + asr_server_destroy(context->asr_context); + free(context->asr_context); + free(context->tts_context); + context->tts_context = NULL; + return AI_SERVICE_ERROR_INTERNAL; + } + + // 设置TTS回调 + tts_server_set_callbacks(context->tts_context, + NULL, // on_synthesis_start + ai_manager_tts_on_result, + NULL, // on_synthesis_complete + NULL, // on_playback_start + NULL, // on_playback_complete + NULL); // on_playback_error + + return AI_SERVICE_OK; +} + +static ai_service_error_t ai_manager_deinit_services(ai_manager_context_t *context) +{ + if (context->tts_context != NULL) { + tts_server_destroy(context->tts_context); + free(context->tts_context); + context->tts_context = NULL; + } + + if (context->chat_context != NULL) { + chat_server_destroy(context->chat_context); + free(context->chat_context); + context->chat_context = NULL; + } + + if (context->asr_context != NULL) { + asr_server_destroy(context->asr_context); + free(context->asr_context); + context->asr_context = NULL; + } + + return AI_SERVICE_OK; +} + +static void ai_manager_set_state(ai_manager_context_t *context, ai_manager_state_t new_state) +{ + if (context == NULL) { + return; + } + + ai_manager_state_t old_state = context->state; + context->state = new_state; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] State changed: %s -> %s", + AI_MANAGER_LOG_TAG, + ai_manager_state_to_string(old_state), + ai_manager_state_to_string(new_state)); +} + +static void ai_manager_send_event(ai_manager_context_t *context, const ai_event_data_t *event) +{ + if (context == NULL || event == NULL) { + return; + } + + // 复制事件数据 + ai_event_data_t event_copy = *event; + + // 复制文本内容 + if (event->text != NULL) { + event_copy.text = (char *)malloc(strlen(event->text) + 1); + if (event_copy.text != NULL) { + strcpy(event_copy.text, event->text); + } + } + + // 复制错误信息 + if (event->error_message != NULL) { + event_copy.error_message = (char *)malloc(strlen(event->error_message) + 1); + if (event_copy.error_message != NULL) { + strcpy(event_copy.error_message, event->error_message); + } + } + + // 发送到事件队列 + osStatus_t status = osMessageQueuePut(context->event_queue, &event_copy, 0, 0); + if (status != osOK) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Failed to send event: %s", + AI_MANAGER_LOG_TAG, ai_manager_event_type_to_string(event->type)); + // 清理分配的内存 + ai_manager_free_event_data(&event_copy); + } +} + +static uint32_t ai_manager_get_timestamp(void) +{ + return (uint32_t)(osKernelGetTickCount() * 1000 / osKernelGetTickFreq()); +} + +/* ==================== 回调函数实现 ==================== */ + +static void ai_manager_asr_on_result(void *user_data, const asr_recognition_result_t *result) +{ + ai_manager_context_t *context = (ai_manager_context_t *)user_data; + if (context == NULL || result == NULL) { + return; + } + + context->stats.asr_recognition_count++; + + ai_event_data_t event = { + .type = result->is_final ? AI_EVENT_SPEECH_END : AI_EVENT_SPEECH_PARTIAL, + .text = result->text, + .text_length = result->text ? strlen(result->text) : 0, + .confidence = result->confidence, + .is_final = result->is_final, + .timestamp = ai_manager_get_timestamp() + }; + strncpy(event.session_id, context->current_session_id, sizeof(event.session_id) - 1); + + ai_manager_send_event(context, &event); + + // 如果是最终结果,发送到Chat服务 + if (result->is_final && result->text != NULL && strlen(result->text) > 0) { + ai_manager_send_text_message(context, result->text); + } +} + +static void ai_manager_asr_on_error(void *user_data, int error_code, const char *error_msg) +{ + ai_manager_context_t *context = (ai_manager_context_t *)user_data; + if (context == NULL) { + return; + } + + context->stats.total_errors++; + + ai_event_data_t event = { + .type = AI_EVENT_ERROR, + .error_code = error_code, + .error_message = (char*)error_msg, + .timestamp = ai_manager_get_timestamp(), + .is_final = true + }; + strncpy(event.session_id, context->current_session_id, sizeof(event.session_id) - 1); + + ai_manager_send_event(context, &event); +} + +static void ai_manager_chat_on_response(void *user_data, const chat_response_t *response) +{ + ai_manager_context_t *context = (ai_manager_context_t *)user_data; + if (context == NULL || response == NULL) { + return; + } + + ai_event_type_t event_type; + if (response->is_streaming && !response->is_final) { + event_type = AI_EVENT_CHAT_RESPONSE_PARTIAL; + } else if (response->is_final) { + event_type = AI_EVENT_CHAT_RESPONSE_END; + } else { + event_type = AI_EVENT_CHAT_RESPONSE_START; + ai_manager_set_state(context, AI_MANAGER_STATE_SPEAKING); + } + + ai_event_data_t event = { + .type = event_type, + .text = response->content, + .text_length = response->content ? strlen(response->content) : 0, + .is_final = response->is_final, + .timestamp = ai_manager_get_timestamp() + }; + strncpy(event.session_id, context->current_session_id, sizeof(event.session_id) - 1); + + ai_manager_send_event(context, &event); + + // 如果有内容,发送到TTS服务 + if (response->content != NULL && strlen(response->content) > 0) { + tts_synthesis_request_t tts_request = { + .text = response->content, + .session_id = context->current_session_id + }; + tts_server_synthesize_request(context->tts_context, &tts_request); + } +} + +static void ai_manager_chat_on_error(void *user_data, int error_code, const char *error_msg) +{ + ai_manager_context_t *context = (ai_manager_context_t *)user_data; + if (context == NULL) { + return; + } + + context->stats.total_errors++; + + ai_event_data_t event = { + .type = AI_EVENT_ERROR, + .error_code = error_code, + .error_message = (char*)error_msg, + .timestamp = ai_manager_get_timestamp(), + .is_final = true + }; + strncpy(event.session_id, context->current_session_id, sizeof(event.session_id) - 1); + + ai_manager_send_event(context, &event); +} + +static void ai_manager_tts_on_result(void *user_data, const tts_synthesis_result_t *result) +{ + ai_manager_context_t *context = (ai_manager_context_t *)user_data; + if (context == NULL || result == NULL) { + return; + } + + context->stats.tts_synthesis_count++; + + // 播放音频数据 + if (result->audio_data != NULL && result->audio_length > 0) { + if (!context->is_playback_active) { + ai_media_play_start((uint8_t*)"pcm"); + context->is_playback_active = true; + + ai_event_data_t event = { + .type = AI_EVENT_TTS_START, + .timestamp = ai_manager_get_timestamp(), + .is_final = false + }; + strncpy(event.session_id, context->current_session_id, sizeof(event.session_id) - 1); + ai_manager_send_event(context, &event); + } + + ai_media_play_buffer((uint8_t *)result->audio_data, result->audio_length); + } + + // 如果是最后一个片段 + if (result->is_final) { + ai_event_data_t event = { + .type = AI_EVENT_TTS_END, + .timestamp = ai_manager_get_timestamp(), + .is_final = true + }; + strncpy(event.session_id, context->current_session_id, sizeof(event.session_id) - 1); + ai_manager_send_event(context, &event); + + ai_manager_set_state(context, AI_MANAGER_STATE_READY); + } +} + +static void ai_manager_tts_on_error(void *user_data, int error_code, const char *error_msg) +{ + ai_manager_context_t *context = (ai_manager_context_t *)user_data; + if (context == NULL) { + return; + } + + context->stats.total_errors++; + + ai_event_data_t event = { + .type = AI_EVENT_ERROR, + .error_code = error_code, + .error_message = (char*)error_msg, + .timestamp = ai_manager_get_timestamp(), + .is_final = true + }; + strncpy(event.session_id, context->current_session_id, sizeof(event.session_id) - 1); + + ai_manager_send_event(context, &event); +} + +static void ai_manager_record_callback(uint8_t *data, uint32_t size) +{ + if (g_current_manager == NULL || data == NULL || size == 0) { + return; + } + + ai_manager_context_t *context = g_current_manager; + + // 如果正在录音且ASR服务可用,发送音频数据 + if (context->is_recording_active && context->asr_context != NULL) { + asr_server_send_audio(context->asr_context, (const char*)data, size); + } +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/code/sdk/zhihui/ai/src/asr_server.c b/code/sdk/zhihui/ai/src/asr_server.c new file mode 100644 index 000000000..ee7ff9a88 --- /dev/null +++ b/code/sdk/zhihui/ai/src/asr_server.c @@ -0,0 +1,909 @@ +/* + * ASR (Automatic Speech Recognition) 服务实现 + * 负责建立与ASR服务器的WebSocket连接管理,上传录音数据,接收识别结果 + */ + +#include +#include +#include +#include +#include "asr_server.h" +#include "ai_media.h" // 添加 AI Media 接口 +#include "../../../../application/wearable/ohos_deps/wearable_log.h" +#include "../../../../../open_source/openharmony/third_party/cJSON/cJSON.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* 内部宏定义 */ +#define ASR_LOG_TAG "ASR_SERVER" + +/* + * @brief 正式服务器URL + */ +#define WS_SERVER_URL "ws://amp.raisound.com/asr/ws" +/** + * @brief 测试服务器URL + */ +#define WS_TEST_SERVER_URL "ws://118.145.158.207:22080/asr/ws"; + +/** + * @brief Snicker测试服务器URL + */ +#define WS_SNICKER_TEST_SERVER_URL "ws://110.42.224.129:8910/asr/ws"; + +/* 全局变量 */ +static asr_server_context_t *g_asr_context = NULL; // 全局ASR上下文 +static bool g_audio_upload_enabled = false; // 录音数据上传标识位 +static bool g_recording_service_active = false; // 录音服务状态 +static bool g_websocket_connected = false; // WebSocket连接状态 + +/* 内部函数声明 */ +static void asr_server_on_connected(void *context); +static void asr_server_on_disconnected(void *context); +static void asr_server_on_message_received(void *context, const char *data, size_t length); +static void asr_server_on_error(void *context, int error_code, const char *error_msg); +static ai_service_error_t asr_server_parse_recognition_result(asr_server_context_t *context, + const char *json_data, + asr_recognition_result_t *result); + +/* AI Media 录音回调函数 */ +static void asr_audio_record_callback(uint8_t *data, uint32_t size); + +/* 公共接口实现 */ + +/** + * @brief 初始化ASR WebSocket URL + * @param put_buf 输出缓冲区,存储构建的URL + * @return bool true表示成功,false表示失败 + * @note 从设备令牌构建完整的ASR WebSocket URL + * 格式:ws://amp.raisound.com/asr/ws?dt={token}&platform=aliyun + */ +bool asr_api_ws_url_init(char *put_buf) +{ + char token[64] = {0}; + if (read_dev_token(token)) { + sprintf(put_buf, "ws://amp.raisound.com/asr/ws?dt=%s&platform=aliyun", token); + return true; + } else { + return false; + } +} + +ai_service_error_t asr_server_init(asr_server_context_t *context, + const char *server_url, + const asr_audio_config_t *audio_config, + const asr_recognition_config_t *recognition_config) +{ + if (!context || !server_url || !audio_config || !recognition_config) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + // 清零上下文 + memset(context, 0, sizeof(asr_server_context_t)); + + // 创建基础服务配置 + ai_service_config_t base_config = {0}; + strncpy(base_config.server_url, server_url, sizeof(base_config.server_url) - 1); + base_config.server_port = 80; + strncpy(base_config.protocol, "asr-protocol", sizeof(base_config.protocol) - 1); + base_config.connect_timeout_ms = AI_CONNECT_TIMEOUT_MS; + base_config.receive_timeout_ms = AI_RECEIVE_TIMEOUT_MS; + base_config.reconnect_interval_ms = AI_RECONNECT_INTERVAL_MS; + base_config.heartbeat_interval_ms = AI_HEARTBEAT_INTERVAL_MS; + base_config.max_retry_count = AI_MAX_RETRY_COUNT; + base_config.auto_reconnect = true; + + // 初始化基础服务 + ai_service_error_t ret = ai_service_init(&context->base, "ASR_SERVER", &base_config); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to init base service: %d", ASR_LOG_TAG, ret); + return ret; + } + + // 复制ASR特定配置 + memcpy(&context->audio_config, audio_config, sizeof(asr_audio_config_t)); + memcpy(&context->recognition_config, recognition_config, sizeof(asr_recognition_config_t)); + + // 分配音频缓冲区 + context->audio_buffer_size = ASR_MAX_AUDIO_BUFFER_SIZE; + context->audio_buffer = (char *)malloc(context->audio_buffer_size); + if (!context->audio_buffer) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to allocate audio buffer", ASR_LOG_TAG); + ai_service_destroy(&context->base); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + context->audio_buffer_used = 0; + + // 创建音频缓冲区互斥锁 + context->audio_buffer_mutex = osMutexNew(NULL); + if (!context->audio_buffer_mutex) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create audio buffer mutex", ASR_LOG_TAG); + free(context->audio_buffer); + ai_service_destroy(&context->base); + return AI_SERVICE_ERROR_INTERNAL; + } + + // 初始化会话状态 + context->session_active = false; + memset(context->current_session_id, 0, sizeof(context->current_session_id)); + + // 设置基础服务回调 + ai_service_set_callbacks(&context->base, + asr_server_on_connected, + asr_server_on_disconnected, + asr_server_on_message_received, + asr_server_on_error); + + // 设置用户数据指向ASR上下文 + ai_service_set_user_data(&context->base, context); + + // 设置全局上下文 + g_asr_context = context; + + // 初始化 AI Media 服务 + if (ai_media_service_init() != 0) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to init AI media service", ASR_LOG_TAG); + free(context->audio_buffer); + osMutexDelete(context->audio_buffer_mutex); + ai_service_destroy(&context->base); + return AI_SERVICE_ERROR_INTERNAL; + } + + // 设置录音回调 + ai_media_set_record_callback(asr_audio_record_callback); + + // 启动录音服务(但不开启上传) + ai_media_record_start((uint8_t*)"pcm"); + g_recording_service_active = true; + g_audio_upload_enabled = false; // 初始状态不上传 + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ASR server initialized with recording service", ASR_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t asr_server_destroy(asr_server_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + // 停止服务 + asr_server_stop(context); + + // 停止录音服务 + if (g_recording_service_active) { + g_audio_upload_enabled = false; + ai_media_record_stop(); + g_recording_service_active = false; + } + + // 反初始化 AI Media 服务 + ai_media_service_deinit(); + + // 删除音频缓冲区互斥锁 + if (context->audio_buffer_mutex) { + osMutexDelete(context->audio_buffer_mutex); + context->audio_buffer_mutex = NULL; + } + + // 释放音频缓冲区 + if (context->audio_buffer) { + free(context->audio_buffer); + context->audio_buffer = NULL; + } + + // 销毁基础服务 + ai_service_destroy(&context->base); + + // 清除全局上下文 + g_asr_context = NULL; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ASR server destroyed", ASR_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t asr_server_start(asr_server_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + ai_service_error_t ret = ai_service_start(&context->base); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to start ASR server: %d", ASR_LOG_TAG, ret); + return ret; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ASR server started", ASR_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t asr_server_stop(asr_server_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + // 停止当前识别会话 + if (context->session_active) { + asr_server_stop_recognition(context); + } + + // 关闭录音数据上传 + g_audio_upload_enabled = false; + + ai_service_error_t ret = ai_service_stop(&context->base); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to stop ASR server: %d", ASR_LOG_TAG, ret); + return ret; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ASR server stopped", ASR_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t asr_server_start_recognition(asr_server_context_t *context, + const char *session_id) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + if (!ai_service_is_connected(&context->base)) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] ASR server is not connected", ASR_LOG_TAG); + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + if (context->session_active) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Recognition session is already active", ASR_LOG_TAG); + return AI_SERVICE_ERROR_ALREADY_CONNECTED; + } + + // 生成或使用提供的会话ID + if (session_id && strlen(session_id) > 0) { + strncpy(context->current_session_id, session_id, sizeof(context->current_session_id) - 1); + } else { + ai_service_generate_uuid(context->current_session_id, sizeof(context->current_session_id)); + } + + // 创建并发送开始识别消息 + asr_api_start_asr(context); + + context->session_active = true; + context->recognition_sessions++; + + // 开启录音数据上传 + g_audio_upload_enabled = true; + + // 调用用户回调 + if (context->on_recognition_start) { + context->on_recognition_start(context, context->current_session_id); + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Recognition started, session: %s, audio upload enabled", + ASR_LOG_TAG, context->current_session_id); + + return AI_SERVICE_OK; +} + +ai_service_error_t asr_server_stop_recognition(asr_server_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + if (!context->session_active) { + return AI_SERVICE_OK; + } + + // 关闭录音数据上传 + g_audio_upload_enabled = false; + + // 创建并发送停止识别消息 + asr_api_stop_asr(context); + + // 调用用户回调 + if (context->on_recognition_end) { + context->on_recognition_end(context, context->current_session_id); + } + + context->session_active = false; + memset(context->current_session_id, 0, sizeof(context->current_session_id)); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Recognition stopped, audio upload disabled", ASR_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t asr_server_send_audio_stream(asr_server_context_t *context, + const char *audio_data, + size_t data_length, + bool is_last) +{ + if (!context || !audio_data || data_length == 0) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + if (!context->session_active) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] No active recognition session", ASR_LOG_TAG); + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + if (!ai_service_is_connected(&context->base)) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] ASR server is not connected", ASR_LOG_TAG); + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + // 检查录音数据上传标识位 + if (!g_audio_upload_enabled) { + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "[%s] Audio upload disabled, skipping data", ASR_LOG_TAG); + return AI_SERVICE_OK; + } + + + // 发送音频消息 + if(!asr_api_send_ai_bin(context, (uint8_t *)audio_data, data_length)) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to send audio message", ASR_LOG_TAG); + return AI_SERVICE_ERROR_INTERNAL; + } + + context->total_audio_sent += data_length; + + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "[%s] Sent %zu bytes audio data, level: %.2f", + ASR_LOG_TAG, data_length, audio_level); + + return AI_SERVICE_OK; +} + +ai_service_error_t asr_server_set_audio_config(asr_server_context_t *context, + const asr_audio_config_t *audio_config) +{ + if (!context || !audio_config) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + memcpy(&context->audio_config, audio_config, sizeof(asr_audio_config_t)); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Audio config updated", ASR_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t asr_server_set_recognition_config(asr_server_context_t *context, + const asr_recognition_config_t *recognition_config) +{ + if (!context || !recognition_config) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + memcpy(&context->recognition_config, recognition_config, sizeof(asr_recognition_config_t)); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Recognition config updated", ASR_LOG_TAG); + + return AI_SERVICE_OK; +} + +const char* asr_server_get_current_session_id(asr_server_context_t *context) +{ + if (!context || !context->session_active) { + return NULL; + } + + return context->current_session_id; +} + +bool asr_server_is_recognition_active(asr_server_context_t *context) +{ + return context ? context->session_active : false; +} + +void asr_server_set_callbacks(asr_server_context_t *context, + void (*on_recognition_result)(void *context, const asr_recognition_result_t *result), + void (*on_recognition_start)(void *context, const char *session_id), + void (*on_recognition_end)(void *context, const char *session_id), + void (*on_audio_level)(void *context, float level)) +{ + if (!context) { + return; + } + + context->on_recognition_result = on_recognition_result; + context->on_recognition_start = on_recognition_start; + context->on_recognition_end = on_recognition_end; + context->on_audio_level = on_audio_level; +} + +void asr_server_get_stats(asr_server_context_t *context, + uint32_t *total_audio_sent, + uint32_t *total_results_received, + uint32_t *recognition_sessions) +{ + if (!context) { + return; + } + + if (total_audio_sent) { + *total_audio_sent = context->total_audio_sent; + } + + if (total_results_received) { + *total_results_received = context->total_results_received; + } + + if (recognition_sessions) { + *recognition_sessions = context->recognition_sessions; + } +} + +/* 工具函数实现 */ + +void asr_server_create_default_audio_config(asr_audio_config_t *config) +{ + if (!config) { + return; + } + + config->format = ASR_AUDIO_FORMAT_PCM; + config->sample_rate = ASR_SAMPLE_RATE_DEFAULT; + config->channels = ASR_CHANNELS_DEFAULT; + config->bits_per_sample = ASR_BITS_PER_SAMPLE_DEFAULT; + config->frame_size_ms = ASR_FRAME_SIZE_MS_DEFAULT; +} + +void asr_server_create_default_recognition_config(asr_recognition_config_t *config) +{ + if (!config) { + return; + } + + config->mode = ASR_MODE_CONTINUOUS; + config->language = ASR_LANGUAGE_ZH_CN; + config->enable_punctuation = true; + config->enable_word_timestamp = false; + config->enable_confidence_score = true; + config->max_silence_ms = 3000; + config->max_speech_ms = 60000; +} + +void asr_server_free_recognition_result(asr_recognition_result_t *result) +{ + if (!result) { + return; + } + + if (result->text) { + free(result->text); + result->text = NULL; + } + + if (result->session_id) { + free(result->session_id); + result->session_id = NULL; + } +} + +const char* asr_server_audio_format_to_string(asr_audio_format_t format) +{ + switch (format) { + case ASR_AUDIO_FORMAT_PCM: return "PCM"; + case ASR_AUDIO_FORMAT_WAV: return "WAV"; + case ASR_AUDIO_FORMAT_MP3: return "MP3"; + case ASR_AUDIO_FORMAT_OPUS: return "OPUS"; + default: return "UNKNOWN"; + } +} + +const char* asr_server_recognition_mode_to_string(asr_recognition_mode_t mode) +{ + switch (mode) { + case ASR_MODE_CONTINUOUS: return "CONTINUOUS"; + case ASR_MODE_SINGLE_SHOT: return "SINGLE_SHOT"; + case ASR_MODE_STREAMING: return "STREAMING"; + default: return "UNKNOWN"; + } +} + +const char* asr_server_language_to_string(asr_language_t language) +{ + switch (language) { + case ASR_LANGUAGE_ZH_CN: return "zh-CN"; + case ASR_LANGUAGE_EN_US: return "en-US"; + case ASR_LANGUAGE_AUTO: return "auto"; + default: return "unknown"; + } +} + +/* 内部函数实现 */ + +static void asr_server_on_connected(void *context) +{ + asr_server_context_t *asr_context = (asr_server_context_t *)ai_service_get_user_data((ai_service_context_t *)context); + if (!asr_context) { + return; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ASR server connected", ASR_LOG_TAG); +} + +static void asr_server_on_disconnected(void *context) +{ + asr_server_context_t *asr_context = (asr_server_context_t *)ai_service_get_user_data((ai_service_context_t *)context); + if (!asr_context) { + return; + } + + // WebSocket断连时,关闭录音数据上传标识位,但不停止录音服务 + g_audio_upload_enabled = false; + + // 停止当前识别会话 + if (asr_context->session_active) { + asr_context->session_active = false; + if (asr_context->on_recognition_end) { + asr_context->on_recognition_end(asr_context, asr_context->current_session_id); + } + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ASR server disconnected, audio upload disabled", ASR_LOG_TAG); +} + +static void asr_server_on_message_received(void *context, const char *data, size_t length) +{ + asr_server_context_t *asr_context = (asr_server_context_t *)ai_service_get_user_data((ai_service_context_t *)context); + if (!asr_context || !data || length == 0) { + return; + } + + // 使用 asr_api_result_analysis 分析服务器返回的数据 + char *buffer = (char*)malloc(length + 1); + if (!buffer) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to allocate buffer for message", ASR_LOG_TAG); + return; + } + + memcpy(buffer, data, length); + buffer[length] = '\0'; + + + bool continue_connection = true; + + // 解析识别结果 + asr_recognition_result_t result = {0}; + ai_service_error_t ret = asr_server_parse_recognition_result(asr_context, buffer, &result); + if (ret == AI_SERVICE_OK) { + asr_context->total_results_received++; + + // 调用用户回调 + if (asr_context->on_recognition_result) { + asr_context->on_recognition_result(asr_context, &result); + } + + // 释放结果内存 + asr_server_free_recognition_result(&result); + } + + free(buffer); + + // 如果 asr_api_result_analysis 返回 false,表示需要断开连接 + if (!continue_connection) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ASR analysis indicates disconnection needed", ASR_LOG_TAG); + ai_service_stop(&asr_context->base); + } + + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "[%s] Received message: %.*s", + ASR_LOG_TAG, (int)length, data); +} + +static void asr_server_on_error(void *context, int error_code, const char *error_msg) +{ + asr_server_context_t *asr_context = (asr_server_context_t *)ai_service_get_user_data((ai_service_context_t *)context); + if (!asr_context) { + return; + } + + // 发生错误时关闭录音数据上传 + g_audio_upload_enabled = false; + + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] ASR server error: %d - %s", + ASR_LOG_TAG, error_code, error_msg ? error_msg : "Unknown"); +} + +static ai_service_error_t asr_server_parse_recognition_result(asr_server_context_t *context, + const char *json_data, + asr_recognition_result_t *result) +{ + if (!context || !json_data || !result) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + if (asr_api_result_analysis(json_data,result)) + { + return AI_SERVICE_OK; + } + else + { + return AI_SERVICE_ERROR_INTERNAL; + } + +} + + +/* AI Media 录音回调函数实现 */ +static void asr_audio_record_callback(uint8_t *data, uint32_t size) +{ + if (!g_asr_context || !data || size == 0) { + return; + } + + // 只有在上传标识位开启时才处理录音数据 + if (!g_audio_upload_enabled) { + return; + } + + // 发送录音数据到ASR服务器 + asr_server_send_audio_stream(g_asr_context, (const char*)data, size, false); +} + + + +/****************************************************************************************** */ +/*** + * // 处理事件类型 + if (event_str) { + if (strcmp(event_str, "TranscriptionStarted") == 0) { // 可以对话了,切换到聆听界面 + // 查询结果 + startAudioCaptureTask(); + + bool state = true; + SendMsgToUIService(UI_AI_JUMP_TO_LISTENING_VIEW, 0, &state, sizeof(bool)); + } + else if (strcmp(event_str, "TranscriptionCompleted") == 0) { // asr 结束 + cJSON_Delete(root); + return true; // 返回true asr不断连 + } + else if (strcmp(event_str, "Failed") == 0) { + stop_AudioCaptureProcess_AI(); + cJSON_Delete(root); + return false; + } + } + */ +/** + * @brief ASR结果分析函数 + * @param buf 接收到的JSON字符串缓冲区 + * @param result 输出的识别结果结构体 + * @return bool true表示解析成功,false表示解析失败 + * @note 解析ASR服务器返回的JSON响应,提取识别结果和相关信息 + * + * + */ +/* + +asr result + +{"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"TranscriptionStarted"} +{"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"Changed","result":{"result":"我,","is_end":false,"sentence_id":"","start_time":0,"end_time":0}} +{"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"SentenceEnd","result":{"result":"给我讲讲。","is_end":true,"sentence_id":"","start_time":0,"end_time":0}} +{"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"Changed","result":{"result":"没,","is_end":false,"sentence_id":"","start_time":0,"end_time":0}} +{"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"Changed","result":{"result":"今","is_end":false,"sentence_id":"","start_time":0,"end_time":0}} +{"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"Changed","result":{"result":"今天打算","is_end":false,"sentence_id":"","start_time":0,"end_time":0}} +{"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"Changed","result":{"result":"今天早上有你可能","is_end":false,"sentence_id":"","start_time":0,"end_time":0}} +{"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"Changed","result":{"result":"今天早上我去可能去温","is_end":false,"sentence_id":"","start_time":0,"end_time":0}} +{"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"SentenceEnd","result":{"result":"今天早上有没有什么趣味?","is_end":true,"sentence_id":"","start_time":0,"end_time":0}} +{"status":20000000,"msg":"success","task_id":"ff5809f9a303aa133542df23e171dfc7","event":"TranscriptionCompleted"} + + +{ + "status" : 20000000, + "msg" : "success", + "task_id" : "ff5809f9a303aa133542df23e171dfc7", + "event" : "SentenceEnd", + "result": { + "result" : "今天早上有没有什么趣味?", + "is_end" : true, + "sentence_id" : "", + "start_time" : 0, + "end_time" : 0 + } +} + +*/ + +bool asr_api_result_analysis(char *buf, asr_recognition_result_t *result) +{ + // 参数检查 + if (!buf || !result) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Invalid parameters for asr_api_result_analysis"); + return false; + } + + // 解析JSON响应 + cJSON* root = cJSON_Parse((const char *)buf); + if (root == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "解析失败 %s\n", buf); + return false; + } + + // 初始化解析变量 + char *result_str = NULL; + int start_time_int = 0; + int end_time_int = 0; + bool is_end_bool = false; + char *event_str = NULL; + + // 解析事件类型 + cJSON* event = cJSON_GetObjectItem(root, "event"); + if (event && cJSON_IsString(event)) { + event_str = event->valuestring; + } + + // 解析识别结果 - 修复变量名冲突 + cJSON* result_json = cJSON_GetObjectItem(root, "result"); + if (result_json) { + // 获取识别文本 + cJSON* result_ = cJSON_GetObjectItem(result_json, "result"); + if (result_ && cJSON_IsString(result_)) { + result_str = result_->valuestring; + } + + // 获取开始时间 + cJSON* start_time = cJSON_GetObjectItem(result_json, "start_time"); + if (start_time && cJSON_IsNumber(start_time)) { + start_time_int = start_time->valueint; + } + + // 获取结束时间 + cJSON* end_time = cJSON_GetObjectItem(result_json, "end_time"); + if (end_time && cJSON_IsNumber(end_time)) { + end_time_int = end_time->valueint; + } + + // 获取是否结束标志 + cJSON* is_end = cJSON_GetObjectItem(result_json, "is_end"); + if (is_end && cJSON_IsBool(is_end)) { + is_end_bool = is_end->valueint; + } + } + + // 安全的日志输出,避免空指针 + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"str %s start %d end %d is end %d\n", + result_str ? result_str : "NULL", start_time_int, end_time_int, is_end_bool); + + // 只有在最终结果且有有效文本时才填充 result 结构体 + if (is_end_bool == true) { + + // *** 关键修复:释放之前可能存在的内存,避免内存泄露 *** + if (result->text) { + free(result->text); + result->text = NULL; + } + if (result->session_id) { + free(result->session_id); + result->session_id = NULL; + } + + if(result_str) + { + // 安全分配内存并复制文本 + size_t text_len = strlen(result_str); + result->text = (char *)malloc(text_len + 1); + if (!result->text) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to allocate memory for result text"); + cJSON_Delete(root); + return false; + } + strcpy(result->text, result_str); + } + // 设置其他字段 + result->start_time_ms = start_time_int; + result->end_time_ms = end_time_int; + result->is_final = is_end_bool; + result->confidence = 1.0f; // 默认置信度 + + // 安全分配并复制会话ID(使用事件类型作为会话标识) + if (event_str) { + size_t event_len = strlen(event_str); + result->session_id = (char *)malloc(event_len + 1); + if (!result->session_id) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to allocate memory for session_id"); + // 清理已分配的内存 + free(result->text); + result->text = NULL; + cJSON_Delete(root); + return false; + } + strcpy(result->session_id, event_str); + } + } + + cJSON_Delete(root); + return true; + +} + +/** + * @brief 启动ASR(语音识别)服务 + * @param context ASR服务上下文 + * @note 查询是否可以对话,启动语音识别功能 + */ +void asr_api_start_asr(asr_server_context_t *context) +{ + if (!context) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Invalid context for asr_api_start_asr", ASR_LOG_TAG); + return; + } + + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "查询asr_api_start_asr是否可用\n"); + + // 构建ASR启动转录的JSON消息 + char *jsonString = "{\"header\":{\"event\":\"StartTranscription\",\"trace_id\": \ + \"3fa6b9e6a30b407ea38ad7ae69e6c291\"},\"payload\":{\"format\":\"MP3\",\"sample_rate\":16000}}"; + + // 使用 ai_service_send_text 发送启动转录消息到ASR服务器 + ai_service_error_t ret = ai_service_send_text(&context->base, jsonString); + if (ret == AI_SERVICE_OK) { + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "asr_api_start_asr send message success\n"); + // 开启录音数据上传标识位 + g_audio_upload_enabled = true; + } else { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "asr_api_start_asr send message error: %d\n", ret); + } +} + +/** + * @brief 停止ASR(语音识别)服务 + * @param context ASR服务上下文 + * @note 发送停止转录消息到ASR服务器 + */ +void asr_api_stop_asr(asr_server_context_t *context) +{ + if (!context) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Invalid context for asr_api_stop_asr", ASR_LOG_TAG); + return; + } + + // 关闭录音数据上传标识位 + g_audio_upload_enabled = false; + + // 构建ASR停止转录的JSON消息 + char *jsonString = "{\"header\":{\"event\":\"StopTranscription\",\"trace_id\":\"3fa6b9e6a30b407ea38ad7ae69e6c291\"}}"; + + // 使用 ai_service_send_text 发送停止转录消息到ASR服务器 + ai_service_error_t ret = ai_service_send_text(&context->base, jsonString); + if (ret == AI_SERVICE_OK) { + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "asr_api_stop_asr send message success\n"); + } else { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "asr_api_stop_asr send message error: %d\n", ret); + } +} + + + +bool asr_api_send_ai_bin(asr_server_context_t *context,uint8_t *buf,uint32_t len) +{ + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"send_ai_bin %d\n",len); + // voice_chat_client + ai_service_error_t ret = ai_service_send_binary(&context->base, buf, len); + if(ret == AI_SERVICE_OK) + { + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"send_websocket_message succ 0\n"); + msleep(100); + return true; + } + else + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"send_websocket_message fail 0\n"); + return false; + } +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/code/sdk/zhihui/ai/src/asr_server_enhanced.c b/code/sdk/zhihui/ai/src/asr_server_enhanced.c new file mode 100644 index 000000000..c263c108f --- /dev/null +++ b/code/sdk/zhihui/ai/src/asr_server_enhanced.c @@ -0,0 +1,757 @@ +/* + * Copyright (c) CompanyNameMagicTag. 2023-2023. All rights reserved. + * Description: Enhanced ASR Server Implementation - 语音识别服务增强版 + * Author: AI Development Team + * Create: 2024-12-28 + */ + +#include +#include +#include +#include "asr_server.h" +#include "ai_media.h" +#include "../../../../application/wearable/ohos_deps/wearable_log.h" +#include "../../../../../open_source/openharmony/third_party/cJSON/cJSON.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ==================== 函数声明 ==================== */ +static ai_service_error_t asr_enhanced_stop(void); + +/* ==================== 内部宏定义 ==================== */ + +#define ASR_LOG_TAG "ASR_ENHANCED" +#define ASR_AUDIO_BUFFER_SIZE 4096 +#define ASR_RECOGNITION_TIMEOUT_MS 30000 + +/* ==================== 内部结构体定义 ==================== */ + +/** + * @brief ASR识别状态 + */ +typedef enum { + ASR_RECOGNITION_STATE_IDLE = 0, + ASR_RECOGNITION_STATE_STARTING, + ASR_RECOGNITION_STATE_ACTIVE, + ASR_RECOGNITION_STATE_STOPPING, + ASR_RECOGNITION_STATE_STOPPED +} asr_recognition_state_t; + +/** + * @brief 增强的ASR上下文 + */ +typedef struct { + asr_server_context_t base; // 基础ASR上下文 + + /* 连接管理 */ + bool websocket_connected; // WebSocket连接状态 + bool connection_persistent; // 持久连接标志 + + /* 录音服务管理 */ + bool recording_service_active; // 录音服务状态 + bool recording_service_persistent; // 录音服务持久化标志 + + /* 上传控制 */ + bool upload_control_enabled; // 上传控制开关 + asr_recognition_state_t recognition_state; // 识别状态 + char current_session_id[64]; // 当前会话ID + + /* 消息解析 */ + bool transcription_started; // 转录是否已开始 + osMessageQueueId_t audio_queue; // 音频数据队列 + osThreadId_t upload_thread; // 上传线程 + + /* 事件管理 */ + osTimerId_t recognition_timer; // 识别超时定时器 + + /* 同步控制 */ + osMutexId_t state_mutex; + osSemaphoreId_t upload_sem; + + /* 回调函数 */ + void (*on_transcription_started)(void *user_data, const char *session_id); + void (*on_recognition_partial)(void *user_data, const char *session_id, const char *text, float confidence); + void (*on_recognition_final)(void *user_data, const char *session_id, const char *text, float confidence); + void (*on_transcription_completed)(void *user_data, const char *session_id); + void (*on_error)(void *user_data, int error_code, const char *error_msg); + void *user_data; +} asr_enhanced_context_t; + +/* ==================== 全局变量 ==================== */ + +static asr_enhanced_context_t *g_asr_enhanced_context = NULL; + +/* ==================== 内部函数声明 ==================== */ + +static void asr_enhanced_upload_thread(void *arg); +static void asr_enhanced_recognition_timeout_callback(void *arg); +static void asr_enhanced_record_callback(uint8_t *data, uint32_t size); +static ai_service_error_t asr_enhanced_parse_recognition_message(asr_enhanced_context_t *context, + const char *json_data); +static void asr_enhanced_set_recognition_state(asr_enhanced_context_t *context, + asr_recognition_state_t new_state); +static void asr_enhanced_on_websocket_message(void *context, const char *data, size_t length); +static void asr_enhanced_on_websocket_connected(void *context); +static void asr_enhanced_on_websocket_disconnected(void *context); +static void asr_enhanced_on_websocket_error(void *context, int error_code, const char *error_msg); + +/* ==================== 公共接口实现 ==================== */ + +/** + * @brief 初始化增强ASR服务 + */ +ai_service_error_t asr_enhanced_init(const ai_service_config_t *config, + const asr_audio_config_t *audio_config, + const asr_recognition_config_t *recognition_config) +{ + if (config == NULL || audio_config == NULL || recognition_config == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Invalid parameters", ASR_LOG_TAG); + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + if (g_asr_enhanced_context != NULL) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Already initialized", ASR_LOG_TAG); + return AI_SERVICE_ERROR_ALREADY_CONNECTED; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Initializing enhanced ASR service", ASR_LOG_TAG); + + // 分配上下文内存 + g_asr_enhanced_context = (asr_enhanced_context_t *)malloc(sizeof(asr_enhanced_context_t)); + if (g_asr_enhanced_context == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to allocate context", ASR_LOG_TAG); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + memset(g_asr_enhanced_context, 0, sizeof(asr_enhanced_context_t)); + + // 初始化基础ASR服务 + ai_service_error_t ret = asr_server_init(&g_asr_enhanced_context->base, + config->server_url, + audio_config, + recognition_config); + if (ret != AI_SERVICE_OK) { + free(g_asr_enhanced_context); + g_asr_enhanced_context = NULL; + return ret; + } + + // 复制配置 + memcpy(&g_asr_enhanced_context->base.audio_config, audio_config, sizeof(asr_audio_config_t)); + memcpy(&g_asr_enhanced_context->base.recognition_config, recognition_config, sizeof(asr_recognition_config_t)); + + // 初始化状态 + g_asr_enhanced_context->websocket_connected = false; + g_asr_enhanced_context->connection_persistent = true; + g_asr_enhanced_context->recording_service_active = false; + g_asr_enhanced_context->recording_service_persistent = true; + g_asr_enhanced_context->upload_control_enabled = false; + g_asr_enhanced_context->recognition_state = ASR_RECOGNITION_STATE_IDLE; + g_asr_enhanced_context->transcription_started = false; + + // 创建互斥锁和信号量 + g_asr_enhanced_context->state_mutex = osMutexNew(NULL); + if (g_asr_enhanced_context->state_mutex == NULL) { + asr_server_destroy(&g_asr_enhanced_context->base); + free(g_asr_enhanced_context); + g_asr_enhanced_context = NULL; + return AI_SERVICE_ERROR_INTERNAL; + } + + g_asr_enhanced_context->upload_sem = osSemaphoreNew(1, 0, NULL); + if (g_asr_enhanced_context->upload_sem == NULL) { + osMutexDelete(g_asr_enhanced_context->state_mutex); + asr_server_destroy(&g_asr_enhanced_context->base); + free(g_asr_enhanced_context); + g_asr_enhanced_context = NULL; + return AI_SERVICE_ERROR_INTERNAL; + } + + // 创建音频队列 + g_asr_enhanced_context->audio_queue = osMessageQueueNew(32, ASR_AUDIO_BUFFER_SIZE, NULL); + if (g_asr_enhanced_context->audio_queue == NULL) { + osSemaphoreDelete(g_asr_enhanced_context->upload_sem); + osMutexDelete(g_asr_enhanced_context->state_mutex); + asr_server_destroy(&g_asr_enhanced_context->base); + free(g_asr_enhanced_context); + g_asr_enhanced_context = NULL; + return AI_SERVICE_ERROR_INTERNAL; + } + + // 创建识别超时定时器 + g_asr_enhanced_context->recognition_timer = osTimerNew(asr_enhanced_recognition_timeout_callback, + osTimerOnce, + g_asr_enhanced_context, + NULL); + if (g_asr_enhanced_context->recognition_timer == NULL) { + osMessageQueueDelete(g_asr_enhanced_context->audio_queue); + osSemaphoreDelete(g_asr_enhanced_context->upload_sem); + osMutexDelete(g_asr_enhanced_context->state_mutex); + asr_server_destroy(&g_asr_enhanced_context->base); + free(g_asr_enhanced_context); + g_asr_enhanced_context = NULL; + return AI_SERVICE_ERROR_INTERNAL; + } + + // 创建上传线程 + osThreadAttr_t thread_attr = { + .name = "asr_upload", + .stack_size = 8192, + .priority = osPriorityNormal + }; + + g_asr_enhanced_context->upload_thread = osThreadNew(asr_enhanced_upload_thread, + g_asr_enhanced_context, + &thread_attr); + if (g_asr_enhanced_context->upload_thread == NULL) { + osTimerDelete(g_asr_enhanced_context->recognition_timer); + osMessageQueueDelete(g_asr_enhanced_context->audio_queue); + osSemaphoreDelete(g_asr_enhanced_context->upload_sem); + osMutexDelete(g_asr_enhanced_context->state_mutex); + asr_server_destroy(&g_asr_enhanced_context->base); + free(g_asr_enhanced_context); + g_asr_enhanced_context = NULL; + return AI_SERVICE_ERROR_INTERNAL; + } + + // 设置WebSocket回调 + ai_service_set_callbacks(&g_asr_enhanced_context->base.base, + asr_enhanced_on_websocket_connected, + asr_enhanced_on_websocket_disconnected, + asr_enhanced_on_websocket_message, + asr_enhanced_on_websocket_error); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Enhanced ASR service initialized", ASR_LOG_TAG); + return AI_SERVICE_OK; +} + +/** + * @brief 销毁增强ASR服务 + */ +ai_service_error_t asr_enhanced_destroy(void) +{ + if (g_asr_enhanced_context == NULL) { + return AI_SERVICE_OK; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Destroying enhanced ASR service", ASR_LOG_TAG); + + // 停止服务 + asr_enhanced_stop(); + + // 终止上传线程 + if (g_asr_enhanced_context->upload_thread != NULL) { + osThreadTerminate(g_asr_enhanced_context->upload_thread); + } + + // 清理资源 + if (g_asr_enhanced_context->recognition_timer != NULL) { + osTimerDelete(g_asr_enhanced_context->recognition_timer); + } + + if (g_asr_enhanced_context->audio_queue != NULL) { + osMessageQueueDelete(g_asr_enhanced_context->audio_queue); + } + + if (g_asr_enhanced_context->upload_sem != NULL) { + osSemaphoreDelete(g_asr_enhanced_context->upload_sem); + } + + if (g_asr_enhanced_context->state_mutex != NULL) { + osMutexDelete(g_asr_enhanced_context->state_mutex); + } + + // 销毁基础ASR服务 + asr_server_destroy(&g_asr_enhanced_context->base); + + // 释放上下文 + free(g_asr_enhanced_context); + g_asr_enhanced_context = NULL; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Enhanced ASR service destroyed", ASR_LOG_TAG); + return AI_SERVICE_OK; +} + +/** + * @brief 启动ASR服务 + */ +ai_service_error_t asr_enhanced_start(void) +{ + if (g_asr_enhanced_context == NULL) { + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Starting ASR service", ASR_LOG_TAG); + + // 启动基础ASR服务(建立WebSocket连接) + ai_service_error_t ret = asr_server_start(&g_asr_enhanced_context->base); + if (ret != AI_SERVICE_OK) { + return ret; + } + + osMutexAcquire(g_asr_enhanced_context->state_mutex, osWaitForever); + + // 启动录音服务(如果启用持久化) + if (g_asr_enhanced_context->recording_service_persistent && + !g_asr_enhanced_context->recording_service_active) { + + ai_media_record_start((uint8_t*)"pcm"); + if (1) { // ai_media_record_start returns void, assume success + g_asr_enhanced_context->recording_service_active = true; + ai_media_set_record_callback(asr_enhanced_record_callback); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Recording service started", ASR_LOG_TAG); + } else { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Failed to start recording service", ASR_LOG_TAG); + } + } + + osMutexRelease(g_asr_enhanced_context->state_mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ASR service started", ASR_LOG_TAG); + return AI_SERVICE_OK; +} + +/** + * @brief 停止ASR服务 + */ +ai_service_error_t asr_enhanced_stop(void) +{ + if (g_asr_enhanced_context == NULL) { + return AI_SERVICE_OK; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Stopping ASR service", ASR_LOG_TAG); + + osMutexAcquire(g_asr_enhanced_context->state_mutex, osWaitForever); + + // 停止识别 + if (g_asr_enhanced_context->recognition_state != ASR_RECOGNITION_STATE_IDLE) { + asr_enhanced_set_recognition_state(g_asr_enhanced_context, ASR_RECOGNITION_STATE_STOPPING); + g_asr_enhanced_context->upload_control_enabled = false; + } + + // 停止录音服务(如果不是持久化的) + if (!g_asr_enhanced_context->recording_service_persistent && + g_asr_enhanced_context->recording_service_active) { + ai_media_record_stop(); + g_asr_enhanced_context->recording_service_active = false; + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Recording service stopped", ASR_LOG_TAG); + } + + // 停止超时定时器 + osTimerStop(g_asr_enhanced_context->recognition_timer); + + osMutexRelease(g_asr_enhanced_context->state_mutex); + + // 停止基础ASR服务(如果不是持久连接) + if (!g_asr_enhanced_context->connection_persistent) { + asr_server_stop(&g_asr_enhanced_context->base); + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] ASR service stopped", ASR_LOG_TAG); + return AI_SERVICE_OK; +} + +/** + * @brief 开始识别 + */ +ai_service_error_t asr_enhanced_start_recognition(const char *session_id) +{ + if (g_asr_enhanced_context == NULL || session_id == NULL) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + osMutexAcquire(g_asr_enhanced_context->state_mutex, osWaitForever); + + if (g_asr_enhanced_context->recognition_state != ASR_RECOGNITION_STATE_IDLE) { + osMutexRelease(g_asr_enhanced_context->state_mutex); + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Recognition already active", ASR_LOG_TAG); + return AI_SERVICE_ERROR_ALREADY_CONNECTED; + } + + if (!g_asr_enhanced_context->websocket_connected) { + osMutexRelease(g_asr_enhanced_context->state_mutex); + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] WebSocket not connected", ASR_LOG_TAG); + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + // 设置会话ID + strncpy(g_asr_enhanced_context->current_session_id, session_id, + sizeof(g_asr_enhanced_context->current_session_id) - 1); + g_asr_enhanced_context->current_session_id[sizeof(g_asr_enhanced_context->current_session_id) - 1] = '\0'; + + // 启用上传控制 + g_asr_enhanced_context->upload_control_enabled = true; + g_asr_enhanced_context->transcription_started = false; + asr_enhanced_set_recognition_state(g_asr_enhanced_context, ASR_RECOGNITION_STATE_STARTING); + + // 启动识别超时定时器 + osTimerStart(g_asr_enhanced_context->recognition_timer, ASR_RECOGNITION_TIMEOUT_MS); + + osMutexRelease(g_asr_enhanced_context->state_mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Recognition started: %s", ASR_LOG_TAG, session_id); + return AI_SERVICE_OK; +} + +/** + * @brief 停止识别 + */ +ai_service_error_t asr_enhanced_stop_recognition(void) +{ + if (g_asr_enhanced_context == NULL) { + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + osMutexAcquire(g_asr_enhanced_context->state_mutex, osWaitForever); + + if (g_asr_enhanced_context->recognition_state == ASR_RECOGNITION_STATE_IDLE) { + osMutexRelease(g_asr_enhanced_context->state_mutex); + return AI_SERVICE_OK; + } + + // 禁用上传控制 + g_asr_enhanced_context->upload_control_enabled = false; + asr_enhanced_set_recognition_state(g_asr_enhanced_context, ASR_RECOGNITION_STATE_STOPPING); + + // 停止超时定时器 + osTimerStop(g_asr_enhanced_context->recognition_timer); + + osMutexRelease(g_asr_enhanced_context->state_mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Recognition stopped", ASR_LOG_TAG); + return AI_SERVICE_OK; +} + +/** + * @brief 设置上传控制 + */ +ai_service_error_t asr_enhanced_set_upload_control(bool enabled) +{ + if (g_asr_enhanced_context == NULL) { + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + osMutexAcquire(g_asr_enhanced_context->state_mutex, osWaitForever); + g_asr_enhanced_context->upload_control_enabled = enabled; + osMutexRelease(g_asr_enhanced_context->state_mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Upload control %s", + ASR_LOG_TAG, enabled ? "enabled" : "disabled"); + return AI_SERVICE_OK; +} + +/** + * @brief 设置录音服务持久化 + */ +ai_service_error_t asr_enhanced_set_recording_persistent(bool persistent) +{ + if (g_asr_enhanced_context == NULL) { + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + osMutexAcquire(g_asr_enhanced_context->state_mutex, osWaitForever); + g_asr_enhanced_context->recording_service_persistent = persistent; + osMutexRelease(g_asr_enhanced_context->state_mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Recording service persistent: %s", + ASR_LOG_TAG, persistent ? "enabled" : "disabled"); + return AI_SERVICE_OK; +} + +/** + * @brief 设置连接持久化 + */ +ai_service_error_t asr_enhanced_set_connection_persistent(bool persistent) +{ + if (g_asr_enhanced_context == NULL) { + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + osMutexAcquire(g_asr_enhanced_context->state_mutex, osWaitForever); + g_asr_enhanced_context->connection_persistent = persistent; + osMutexRelease(g_asr_enhanced_context->state_mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Connection persistent: %s", + ASR_LOG_TAG, persistent ? "enabled" : "disabled"); + return AI_SERVICE_OK; +} + +/** + * @brief 设置回调函数 + */ +void asr_enhanced_set_callbacks(void (*on_transcription_started)(void *user_data, const char *session_id), + void (*on_recognition_partial)(void *user_data, const char *session_id, const char *text, float confidence), + void (*on_recognition_final)(void *user_data, const char *session_id, const char *text, float confidence), + void (*on_transcription_completed)(void *user_data, const char *session_id), + void (*on_error)(void *user_data, int error_code, const char *error_msg), + void *user_data) +{ + if (g_asr_enhanced_context == NULL) { + return; + } + + osMutexAcquire(g_asr_enhanced_context->state_mutex, osWaitForever); + g_asr_enhanced_context->on_transcription_started = on_transcription_started; + g_asr_enhanced_context->on_recognition_partial = on_recognition_partial; + g_asr_enhanced_context->on_recognition_final = on_recognition_final; + g_asr_enhanced_context->on_transcription_completed = on_transcription_completed; + g_asr_enhanced_context->on_error = on_error; + g_asr_enhanced_context->user_data = user_data; + osMutexRelease(g_asr_enhanced_context->state_mutex); +} + +/* ==================== 内部函数实现 ==================== */ + +/** + * @brief 上传线程 + */ +static void asr_enhanced_upload_thread(void *arg) +{ + asr_enhanced_context_t *context = (asr_enhanced_context_t *)arg; + uint8_t audio_buffer[ASR_AUDIO_BUFFER_SIZE]; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Upload thread started", ASR_LOG_TAG); + + while (true) { + // 等待上传信号 + osSemaphoreAcquire(context->upload_sem, osWaitForever); + + // 从音频队列获取数据 + osStatus_t status = osMessageQueueGet(context->audio_queue, audio_buffer, NULL, 0); + if (status == osOK) { + osMutexAcquire(context->state_mutex, osWaitForever); + + // 检查上传控制和连接状态 + bool can_upload = context->upload_control_enabled && + context->websocket_connected && + context->transcription_started; + + osMutexRelease(context->state_mutex); + + if (can_upload) { + // 发送音频数据到服务器 + ai_service_send_data(&context->base.base, (const char*)audio_buffer, ASR_AUDIO_BUFFER_SIZE); + } + } + } +} + +/** + * @brief 识别超时回调 + */ +static void asr_enhanced_recognition_timeout_callback(void *arg) +{ + asr_enhanced_context_t *context = (asr_enhanced_context_t *)arg; + + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Recognition timeout", ASR_LOG_TAG); + + osMutexAcquire(context->state_mutex, osWaitForever); + + if (context->recognition_state == ASR_RECOGNITION_STATE_ACTIVE) { + asr_enhanced_set_recognition_state(context, ASR_RECOGNITION_STATE_STOPPED); + context->upload_control_enabled = false; + + // 调用错误回调 + if (context->on_error != NULL) { + context->on_error(context->user_data, AI_SERVICE_ERROR_TIMEOUT, "Recognition timeout"); + } + } + + osMutexRelease(context->state_mutex); +} + +/** + * @brief 录音回调函数 + */ +static void asr_enhanced_record_callback(uint8_t *data, uint32_t size) +{ + if (g_asr_enhanced_context == NULL || data == NULL || size == 0) { + return; + } + + // 如果数据大小超过缓冲区,截断 + uint32_t copy_size = (size > ASR_AUDIO_BUFFER_SIZE) ? ASR_AUDIO_BUFFER_SIZE : size; + + // 发送到音频队列 + osStatus_t status = osMessageQueuePut(g_asr_enhanced_context->audio_queue, data, 0, 0); + if (status == osOK) { + // 通知上传线程 + osSemaphoreRelease(g_asr_enhanced_context->upload_sem); + } +} + +/** + * @brief 解析识别消息 + */ +static ai_service_error_t asr_enhanced_parse_recognition_message(asr_enhanced_context_t *context, + const char *json_data) +{ + cJSON *root = cJSON_Parse(json_data); + if (root == NULL) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + cJSON *event_obj = cJSON_GetObjectItem(root, "event"); + if (event_obj == NULL || !cJSON_IsString(event_obj)) { + cJSON_Delete(root); + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + const char *event = cJSON_GetStringValue(event_obj); + + if (strcmp(event, "TranscriptionStarted") == 0) { + // 转录开始 + osMutexAcquire(context->state_mutex, osWaitForever); + context->transcription_started = true; + asr_enhanced_set_recognition_state(context, ASR_RECOGNITION_STATE_ACTIVE); + osMutexRelease(context->state_mutex); + + // 调用开始回调 + if (context->on_transcription_started != NULL) { + context->on_transcription_started(context->user_data, context->current_session_id); + } + + } else if (strcmp(event, "Changed") == 0) { + // 识别中间结果 + cJSON *result_obj = cJSON_GetObjectItem(root, "result"); + if (result_obj != NULL) { + cJSON *text_obj = cJSON_GetObjectItem(result_obj, "result"); + cJSON *is_end_obj = cJSON_GetObjectItem(result_obj, "is_end"); + + if (text_obj != NULL && cJSON_IsString(text_obj)) { + const char *text = cJSON_GetStringValue(text_obj); + bool is_end = cJSON_IsTrue(is_end_obj); + + // 调用部分结果回调 + if (context->on_recognition_partial != NULL) { + context->on_recognition_partial(context->user_data, + context->current_session_id, + text, 0.0f); + } + } + } + + } else if (strcmp(event, "SentenceEnd") == 0) { + // 句子结束 + cJSON *result_obj = cJSON_GetObjectItem(root, "result"); + if (result_obj != NULL) { + cJSON *text_obj = cJSON_GetObjectItem(result_obj, "result"); + cJSON *is_end_obj = cJSON_GetObjectItem(result_obj, "is_end"); + + if (text_obj != NULL && cJSON_IsString(text_obj) && cJSON_IsTrue(is_end_obj)) { + const char *text = cJSON_GetStringValue(text_obj); + + // 调用最终结果回调 + if (context->on_recognition_final != NULL) { + context->on_recognition_final(context->user_data, + context->current_session_id, + text, 1.0f); + } + } + } + + } else if (strcmp(event, "TranscriptionCompleted") == 0) { + // 转录完成 + osMutexAcquire(context->state_mutex, osWaitForever); + asr_enhanced_set_recognition_state(context, ASR_RECOGNITION_STATE_STOPPED); + context->upload_control_enabled = false; + osTimerStop(context->recognition_timer); + osMutexRelease(context->state_mutex); + + // 调用完成回调 + if (context->on_transcription_completed != NULL) { + context->on_transcription_completed(context->user_data, context->current_session_id); + } + } + + cJSON_Delete(root); + return AI_SERVICE_OK; +} + +/** + * @brief 设置识别状态 + */ +static void asr_enhanced_set_recognition_state(asr_enhanced_context_t *context, + asr_recognition_state_t new_state) +{ + asr_recognition_state_t old_state = context->recognition_state; + context->recognition_state = new_state; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Recognition state changed: %d -> %d", + ASR_LOG_TAG, old_state, new_state); +} + +/* ==================== WebSocket回调函数 ==================== */ + +static void asr_enhanced_on_websocket_connected(void *context) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] WebSocket connected", ASR_LOG_TAG); + + if (g_asr_enhanced_context != NULL) { + osMutexAcquire(g_asr_enhanced_context->state_mutex, osWaitForever); + g_asr_enhanced_context->websocket_connected = true; + osMutexRelease(g_asr_enhanced_context->state_mutex); + } +} + +static void asr_enhanced_on_websocket_disconnected(void *context) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] WebSocket disconnected", ASR_LOG_TAG); + + if (g_asr_enhanced_context != NULL) { + osMutexAcquire(g_asr_enhanced_context->state_mutex, osWaitForever); + g_asr_enhanced_context->websocket_connected = false; + g_asr_enhanced_context->transcription_started = false; + + // 如果是持久连接,尝试重连 + if (g_asr_enhanced_context->connection_persistent) { + ai_service_connect(&g_asr_enhanced_context->base.base); + } + + osMutexRelease(g_asr_enhanced_context->state_mutex); + } +} + +static void asr_enhanced_on_websocket_message(void *context, const char *data, size_t length) +{ + if (g_asr_enhanced_context == NULL || data == NULL || length == 0) { + return; + } + + // 检查是否为JSON数据 + if (data[0] == '{') { + // JSON消息,解析识别结果 + asr_enhanced_parse_recognition_message(g_asr_enhanced_context, data); + } else { + // 二进制数据,暂时忽略 + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "[%s] Received binary data: %zu bytes", ASR_LOG_TAG, length); + } +} + +static void asr_enhanced_on_websocket_error(void *context, int error_code, const char *error_msg) +{ + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] WebSocket error: %d - %s", + ASR_LOG_TAG, error_code, error_msg ? error_msg : "Unknown"); + + if (g_asr_enhanced_context != NULL) { + osMutexAcquire(g_asr_enhanced_context->state_mutex, osWaitForever); + + g_asr_enhanced_context->websocket_connected = false; + asr_enhanced_set_recognition_state(g_asr_enhanced_context, ASR_RECOGNITION_STATE_IDLE); + + // 调用错误回调 + if (g_asr_enhanced_context->on_error != NULL) { + g_asr_enhanced_context->on_error(g_asr_enhanced_context->user_data, + error_code, error_msg); + } + + osMutexRelease(g_asr_enhanced_context->state_mutex); + } +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/code/sdk/zhihui/ai/src/cat1_ai_server.c b/code/sdk/zhihui/ai/src/cat1_ai_server.c index ce34e7be1..9c3468368 100644 --- a/code/sdk/zhihui/ai/src/cat1_ai_server.c +++ b/code/sdk/zhihui/ai/src/cat1_ai_server.c @@ -1,64 +1,90 @@ +/* + * CAT1 AI服务器模块 + * + * 功能描述: + * - 集成ASR(语音识别)、TTS(文本转语音)、Chat(对话)三个AI服务 + * - 基于WebSocket协议与AI服务器进行通信 + * - 支持断线重连、消息队列管理、资源监控等功能 + * - 提供完整的语音对话交互流程 + * + * 主要模块: + * 1. ASR服务:处理语音识别,将音频转换为文本 + * 2. TTS服务:处理文本转语音,将文本转换为音频播放 + * 3. Chat服务:处理对话交互,与大模型进行文本对话 + * + * 作者:智慧AI团队 + * 版本:v2.0 + * 日期:2024-12-28 + */ + +/* ==================== 头文件包含 ==================== */ + /* 标准C库头文件 */ -#include -#include -#include -#include -#include -#include -#include +#include // 标准输入输出 +#include // 内存管理、字符串转换等 +#include // 字符串操作 +#include // UNIX标准定义 +#include // 标准整数类型 +#include // 时间处理 +#include // 系统时间 /* 系统头文件 */ -#include +#include // 延时函数 /* RTOS头文件 */ -#include "cmsis_os2.h" -#include "osal_timer.h" -#include "osal_msgqueue.h" -#include "soc_osal.h" +#include "cmsis_os2.h" // CMSIS-RTOS v2 API +#include "osal_timer.h" // OSAL定时器 +#include "osal_msgqueue.h" // OSAL消息队列 +#include "soc_osal.h" // SOC OSAL接口 /* 网络相关头文件 */ -#include "lwip/ip.h" -#include "lwip/netif.h" -#include "lwip/init.h" -#include "lwip/inet_chksum.h" -#include "lwip/dns.h" -#include "lwip/err.h" -#include "lwip/sockets.h" -#include "lwip/sys.h" -#include "lwip/netdb.h" -#include "lwip/inet.h" -#include "lwip/apps/sntp.h" +#include "lwip/ip.h" // LWIP IP协议 +#include "lwip/netif.h" // LWIP网络接口 +#include "lwip/init.h" // LWIP初始化 +#include "lwip/inet_chksum.h" // LWIP校验和 +#include "lwip/dns.h" // LWIP DNS解析 +#include "lwip/err.h" // LWIP错误码 +#include "lwip/sockets.h" // LWIP套接字 +#include "lwip/sys.h" // LWIP系统接口 +#include "lwip/netdb.h" // LWIP网络数据库 +#include "lwip/inet.h" // LWIP网络地址转换 +#include "lwip/apps/sntp.h" // LWIP SNTP时间同步 /* 第三方库头文件 */ -#include "../../../../../open_source/openharmony/third_party/cJSON/cJSON.h" +#include "../../../../../open_source/openharmony/third_party/cJSON/cJSON.h" // JSON解析库 /* 项目内部头文件 */ -#include "custom_runtime.h" -#include "custom_utils.h" -#include "bleANCS.h" -#include "broadcast_feature.h" -#include "cat1_ctl.h" -#include "voice_call_volte_sample_wrapper.h" -#include "audio_capture_wrapper.h" -#include "cat1_ws.h" -#include "dev_authentication.h" -#include "bk_websocket.h" -#include "thread_init.h" -#include "common_def.h" -#include "los_printf.h" -#include +#include "custom_runtime.h" // 自定义运行时 +#include "custom_utils.h" // 自定义工具函数 +#include "bleANCS.h" // 蓝牙ANCS服务 +#include "broadcast_feature.h" // 广播功能 +#include "cat1_ctl.h" // CAT1控制模块 +#include "voice_call_volte_sample_wrapper.h" // VoLTE语音通话 +#include "audio_capture_wrapper.h" // 音频采集封装 +#include "cat1_ws.h" // CAT1 WebSocket +#include "dev_authentication.h" // 设备认证 +#include "bk_websocket.h" // WebSocket客户端 +#include "thread_init.h" // 线程初始化 +#include "common_def.h" // 通用定义 +#include "los_printf.h" // LiteOS打印 +#include // AI语音模型 /* 日志头文件 */ -#include "../../../../application/wearable/ohos_deps/wearable_log.h" +#include "../../../../application/wearable/ohos_deps/wearable_log.h" // 可穿戴设备日志 #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ -#include "bts_br_gap.h" +#include "bts_br_gap.h" // 蓝牙BR/GAP接口 +/* ==================== 宏定义 ==================== */ -// 内存管理和错误处理优化 +/** + * @brief 安全内存分配宏 + * @param size 要分配的内存大小 + * @return 分配的内存指针,失败时返回NULL并记录错误日志 + */ #define AI_SAFE_MALLOC(size) ({ \ void* ptr = malloc(size); \ if (!ptr) { \ @@ -67,6 +93,11 @@ extern "C" { ptr; \ }) +/** + * @brief 安全内存释放宏 + * @param ptr 要释放的内存指针 + * @note 释放后会将指针设置为NULL,避免重复释放 + */ #define AI_SAFE_FREE(ptr) do { \ if (ptr) { \ free(ptr); \ @@ -74,306 +105,178 @@ extern "C" { } \ } while(0) -// 超时和重试配置 -#define AI_CONNECT_TIMEOUT_MS 30000 // 30秒连接超时 -#define AI_RECEIVE_TIMEOUT_MS 10000 // 10秒接收超时 -#define AI_MAX_RETRY_COUNT 3 // 最大重试次数 -#define AI_QUEUE_CHECK_INTERVAL 1000 // 队列检查间隔(ms) - -// WebSocket超时配置 -#define AI_WS_TCP_CONNECTION_TIMEOUT_MS 15000 // 15秒TCP连接确认超时 -#define AI_WS_HANDSHAKE_TIMEOUT_MS 10000 // 10秒握手等待超时 -#define AI_WS_TCP_CHECK_INTERVAL_MS 100 // TCP状态检查间隔(ms) - -// 资源保护 -static osMutexId_t g_ai_mutex = NULL; -static uint32_t g_ai_memory_usage = 0; -static uint32_t g_ai_max_memory = 1024 * 1024; // 1MB内存限制 - -// 内存使用监控 -static inline bool ai_check_memory_limit(uint32_t size) { - if (g_ai_memory_usage + size > g_ai_max_memory) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Memory limit exceeded: %u + %u > %u", - g_ai_memory_usage, size, g_ai_max_memory); - return false; - } - return true; -} - -static inline void ai_update_memory_usage(int32_t delta) { - if (delta > 0) { - g_ai_memory_usage += delta; - } else { - g_ai_memory_usage = (g_ai_memory_usage > (uint32_t)(-delta)) ? - g_ai_memory_usage + delta : 0; - } -} - -/** - * @brief 处理WebSocket连接失败的统一函数 - * @param client_type 客户端类型字符串 - * @param retry_count 当前重试次数 - * @param max_retries 最大重试次数 - * @return true 如果应该继续重试,false 如果应该停止重试 - */ -static bool handle_websocket_connection_failure(const char* client_type, uint8_t retry_count, uint8_t max_retries) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "%s WebSocket connection failed, retries left: %d", client_type, max_retries - retry_count); - - if (retry_count >= max_retries) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "%s WebSocket connection failed after %d retries, giving up", client_type, max_retries); - return false; // 停止重试 - } - - // 等待一段时间后重试 - msleep(1000); - return true; // 继续重试 -} - -/** - * @brief 检查WebSocket客户端连接状态 - * @param client WebSocket客户端指针 - * @param client_type 客户端类型字符串 - * @return true 如果连接正常,false 如果连接失败 - */ -static bool check_websocket_client_status(WebSocketClient* client, const char* client_type) { - if (client == NULL) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "%s WebSocket client is NULL", client_type); - return false; - } - - if (client->tcp_handle == NULL) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "%s WebSocket TCP handle is NULL", client_type); - return false; - } - - if (client->parser == NULL) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "%s WebSocket parser is NULL", client_type); - return false; - } - - return true; -} - -/** - * @brief 将TCP连接状态转换为字符串 - * @param state TCP连接状态 - * @return 状态字符串 - */ -static const char* tcp_state_to_string(tcp_connection_state_t state) { - switch (state) { - case TCP_STATE_DISCONNECTED: return "DISCONNECTED"; - case TCP_STATE_CONNECTING: return "CONNECTING"; - case TCP_STATE_CONNECTED: return "CONNECTED"; - case TCP_STATE_CLOSING: return "CLOSING"; - case TCP_STATE_ERROR: return "ERROR"; - default: return "UNKNOWN"; - } -} +/* 超时和重试配置 */ +#define AI_CONNECT_TIMEOUT_MS 30000 // WebSocket连接超时时间(30秒) +#define AI_RECEIVE_TIMEOUT_MS 10000 // 消息接收超时时间(10秒) +#define AI_MAX_RETRY_COUNT 3 // 连接失败最大重试次数 +#define AI_QUEUE_CHECK_INTERVAL 1000 // 消息队列状态检查间隔(1秒) + +/* WebSocket连接超时配置 */ +#define AI_WS_TCP_CONNECTION_TIMEOUT_MS 15000 // TCP连接确认超时时间(15秒) +#define AI_WS_HANDSHAKE_TIMEOUT_MS 10000 // WebSocket握手超时时间(10秒) +#define AI_WS_TCP_CHECK_INTERVAL_MS 100 // TCP连接状态检查间隔(100毫秒) + +/* ==================== 全局变量 ==================== */ + +/* 资源保护和监控 */ +static osMutexId_t g_ai_mutex = NULL; // AI模块全局互斥锁 +static uint32_t g_ai_memory_usage = 0; // 当前内存使用量(字节) +static uint32_t g_ai_max_memory = 1024 * 1024; // 最大内存限制(1MB) + + +/* ==================== 外部函数声明 ==================== */ + +extern bool read_dev_token(char *data_token); // 读取设备令牌 + +/* AI系统管理函数 */ +errcode_t ai_system_init(void); // AI系统初始化 +void ai_system_deinit(void); // AI系统反初始化 +void ai_cleanup_resources(void); // 清理AI资源 +uint32_t ai_get_memory_usage(void); // 获取内存使用量 +uint32_t ai_get_memory_usage_percent(void); // 获取内存使用百分比 +bool ai_check_queue_status(void); // 检查消息队列状态 +bool ai_system_health_check(void); // AI系统健康检查 + +/* 外部接口函数声明(已注释的备用接口)*/ +// extern void bluetooth_get_local_addr(unsigned char *addr, int len); // 获取蓝牙地址 +// extern void active_md5_buf(char *input, char *output); // MD5加密 +// extern void uapi_cat1_keep_wakeup(uint8_t state); // CAT1保持唤醒 +// extern void set_AIStreamend(uint8_t state); // 设置AI流结束状态 +// extern void set_clear_queue_stream(uint8_t state); // 清理队列流 +// extern void startAudioCaptureTask(void); // 启动音频采集任务 +// extern void stop_AudioCaptureProcess_AI(void); // 停止AI音频采集处理 +// extern void ble_set_ai_Question_str(char *str); // 设置AI问题字符串 +// extern void ble_set_ai_Answer_str(char *str, uint8_t type); // 设置AI回答字符串 +// extern void SendMsgToUIService(uint32_t msg_id, uint32_t param, void *data, uint32_t len); // 发送消息到UI服务 +// extern int send_msg_data_into_Ai_msg_queue(void *data, uint16_t len, uint16_t type); // 发送数据到AI消息队列 +// extern bool aut_login(void); // 自动登录 +// extern bool get_dev_id(char *device_id); // 获取设备ID + +/* ==================== 全局变量定义 ==================== */ + +/* 定时器相关 */ +static osTimerId_t g_timer_time_sync = NULL; // 时间同步定时器 + +/* TTS相关函数声明 */ +bool uapi_start_tts_single_txt(char *buf); // 启动单次TTS文本转语音 +void uapi_start_tts_single(void); // 启动单次TTS + +/* 服务初始化函数 */ +errcode_t asr_server_init(void); // ASR服务初始化 + +/* 服务状态管理 */ +static uint8_t server_state = 0; // 服务器连接状态标志 +static uint8_t disconnect_server = 0; // 断开服务器连接标志 + +/* WebSocket消息队列 */ +static ws_message_queue_t chat_msg_queue = {0}; // Chat服务消息队列 +static ws_message_queue_t asr_msg_queue = {0}; // ASR服务消息队列 +static ws_message_queue_t tts_msg_queue = {0}; // TTS服务消息队列 +// static ws_message_queue_t voice_msg_queue = {0}; // 语音消息队列(已注释) + +/* ASR相关全局变量 */ +char *asr_recv_buf = NULL; // ASR接收缓冲区(动态分配) +WebSocketClient *asr_client = NULL; // ASR WebSocket客户端 + +/* TTS相关函数和变量 */ +int32_t uapi_tts_paly_init(void); // TTS播放初始化 +int32_t uapi_tts_paly_stop(void); // TTS播放停止 +uint32_t bk_websocket_tts_tick = 0; // TTS WebSocket心跳计数 +uint8_t tts_reconnect = 0; // TTS重连标志 +uint8_t asr_need_stop = 0; // ASR需要停止标志 + +/* 通用接收缓冲区和WebSocket客户端 */ +char recv_buf[1024] = {0}; // 通用接收缓冲区 +WebSocketClient *chat_client = NULL; // Chat WebSocket客户端 +WebSocketClient *tts_client = NULL; // TTS WebSocket客户端 + +/* 心跳和重连管理 */ +static int asr_client_ping = 0; // ASR客户端心跳计数 +static int tts_client_ping = 0; // TTS客户端心跳计数 +static int chat_client_ping = 0; // Chat客户端心跳计数 +static int need_tts_reconnect = 0; // TTS需要重连标志 + +/* ==================== 工具函数实现 ==================== */ /** - * @brief 等待TCP连接建立确认 - * @param client WebSocket客户端指针 - * @param client_type 客户端类型字符串 - * @param timeout_ms 超时时间(毫秒) - * @return true 如果连接成功,false 如果连接失败或超时 + * @brief 设置TTS重连标志 + * @param flag 重连标志(1:需要重连,0:不需要重连) + * @note 用于控制TTS服务的重连行为 */ -static bool wait_for_tcp_connection(WebSocketClient* client, const char* client_type, uint32_t timeout_ms) { - if (!client || !client->tcp_handle) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "%s WebSocket client or TCP handle is NULL", client_type); - return false; - } - - uint32_t start_time = (uint32_t)osKernelGetTickCount(); - uint32_t check_interval = AI_WS_TCP_CHECK_INTERVAL_MS; // 使用宏定义的检查间隔 - - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "%s 等待TCP连接建立确认...", client_type); - - while ((uint32_t)osKernelGetTickCount() - start_time < timeout_ms) { - // 检查TCP连接状态 - tcp_connection_state_t state = tcp_get_connection_state(client->tcp_handle); - - if (state == TCP_STATE_CONNECTED) { - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "%s TCP连接已建立", client_type); - return true; - } - - if (state == TCP_STATE_ERROR || state == TCP_STATE_DISCONNECTED) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "%s TCP连接失败或已断开,状态: %s", client_type, tcp_state_to_string(state)); - return false; - } - - // 如果还在连接中,继续等待 - if (state == TCP_STATE_CONNECTING) { - msleep(check_interval); - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "%s 等待TCP连接... (%d ms)", - client_type, (uint32_t)osKernelGetTickCount() - start_time); - continue; - } - - // 其他状态也继续等待 - msleep(check_interval); - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "%s TCP状态: %s, 等待中... (%d ms)", - client_type, tcp_state_to_string(state), (uint32_t)osKernelGetTickCount() - start_time); - } - - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "%s TCP连接确认超时 (%d ms)", client_type, timeout_ms); - return false; -} - -// 线程安全保护 -#define AI_LOCK() do { \ - if (g_ai_mutex) { \ - osMutexAcquire(g_ai_mutex, osWaitForever); \ - } \ -} while(0) - -#define AI_UNLOCK() do { \ - if (g_ai_mutex) { \ - osMutexRelease(g_ai_mutex); \ - } \ -} while(0) - - - -#define AI_RESULT_STR_LEN 512 - -extern bool read_dev_token(char *data_token); - -errcode_t ai_system_init(void); -void ai_system_deinit(void); -void ai_cleanup_resources(void); -uint32_t ai_get_memory_usage(void); -uint32_t ai_get_memory_usage_percent(void); -bool ai_check_queue_status(void); -bool ai_system_health_check(void); - -// 外部函数声明 -// extern void bluetooth_get_local_addr(unsigned char *addr, int len); -// extern void active_md5_buf(char *input, char *output); -// extern void uapi_cat1_keep_wakeup(uint8_t state); -// extern void set_AIStreamend(uint8_t state); -// extern void set_clear_queue_stream(uint8_t state); -// extern void startAudioCaptureTask(void); -// extern void stop_AudioCaptureProcess_AI(void); -// extern void ble_set_ai_Question_str(char *str); -// extern void ble_set_ai_Answer_str(char *str, uint8_t type); -// extern void SendMsgToUIService(uint32_t msg_id, uint32_t param, void *data, uint32_t len); -// extern int send_msg_data_into_Ai_msg_queue(void *data, uint16_t len, uint16_t type); -// extern bool aut_login(void); -// extern bool get_dev_id(char *device_id); - -static osTimerId_t g_timer_time_sync = NULL; - -bool uapi_start_tts_single_txt(char *buf); - -void uapi_start_tts_single(void); - - -errcode_t asr_server_init(void); - - -static uint8_t server_state=0; - -static uint8_t disconnect_server=0; -// free - - -/* 各线程的消息队列 */ -static ws_message_queue_t chat_msg_queue = {0}; -static ws_message_queue_t asr_msg_queue = {0}; -static ws_message_queue_t tts_msg_queue = {0}; -// static ws_message_queue_t voice_msg_queue = {0}; - - - -char *asr_recv_buf=NULL;//[1024]={0}; -WebSocketClient *asr_client=NULL; - -int32_t uapi_tts_paly_init(void); - -int32_t uapi_tts_paly_stop(void); - -uint32_t bk_websocket_tts_tick=0; - -uint8_t tts_reconnect=0; - -uint8_t asr_need_stop=0; - - -char recv_buf[1024]={0}; - -WebSocketClient *chat_client=NULL; - - -WebSocketClient *tts_client=NULL; - -static int asr_client_ping=0; - -static int tts_client_ping=0; - -static int chat_client_ping=0; - -static int need_tts_reconnect=0; - void set_need_tts_reconnect(uint8_t flag) { - need_tts_reconnect=flag; + need_tts_reconnect = flag; } - -WebSocketClient * get_ws_client_by_sockfd(int sockfd) +/** + * @brief 根据socket文件描述符获取对应的WebSocket客户端 + * @param sockfd socket文件描述符 + * @return WebSocketClient* 对应的WebSocket客户端指针,未找到返回NULL + * @note 用于在回调函数中根据socket描述符识别具体的WebSocket客户端 + */ +WebSocketClient * get_ws_cl ient_by_sockfd(int sockfd) { + // 检查Chat客户端 if (chat_client != NULL && chat_client->sockfd == sockfd) { return chat_client; } + // 检查ASR客户端 else if (asr_client != NULL && asr_client->sockfd == sockfd) { return asr_client; } + // 检查TTS客户端 else if (tts_client != NULL && tts_client->sockfd == sockfd) { return tts_client; } - // 检查WebSocket测试客户端 + // 检查WebSocket测试客户端(条件编译) #ifdef SUPPORT_WEBSOCKET_TEST else if (test_client != NULL && test_client->sockfd == sockfd) { return test_client; } #endif - // 注意:voice_chat_client 被注释掉了,根据用户的修改 + // 注意:voice_chat_client 已被注释,根据用户的修改 // else if (voice_chat_client != NULL && voice_chat_client->sockfd == sockfd) // { // return voice_chat_client; // } - return NULL; + + return NULL; // 未找到对应的客户端 } -errcode_t chat_server_init(void); - -errcode_t tts_server_init(void); +/* 服务初始化函数声明 */ +errcode_t chat_server_init(void); // Chat服务初始化 +errcode_t tts_server_init(void); // TTS服务初始化 -// void tts_server_deinit(void); -uint8 tts_break =0; +// void tts_server_deinit(void); // TTS服务反初始化(已注释) +uint8 tts_break = 0; // TTS中断标志 -/* WebSocket消息队列管理函数 */ +/* ==================== WebSocket消息队列管理函数 ==================== */ /** * @brief 初始化WebSocket消息队列 + * @param queue 消息队列结构体指针 + * @param queue_size 队列大小(0表示使用默认大小) + * @return int 0表示成功,-1表示失败 + * @note 创建OSAL消息队列用于WebSocket消息的异步处理 */ -static int ws_queue_init(ws_message_queue_t* queue,uint32_t queue_size) +static int ws_queue_init(ws_message_queue_t* queue, uint32_t queue_size) { if (!queue) return -1; + // 清零队列结构体 memset(queue, 0, sizeof(ws_message_queue_t)); - /* 创建osal消息队列 */ + /* 确定实际队列大小 */ uint32_t true_queue_size; - if(queue_size!=0) - true_queue_size=queue_size; + if (queue_size != 0) + true_queue_size = queue_size; else - true_queue_size=WS_QUEUE_SIZE; + true_queue_size = WS_QUEUE_SIZE; // 使用默认大小 + /* 创建OSAL消息队列 */ int ret = osal_msg_queue_create("ws_queue", true_queue_size, &queue->queue_id, 0, sizeof(ws_message_item_t)); if (ret != OSAL_SUCCESS) { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to create osal message queue for ws queue"); @@ -387,49 +290,62 @@ static int ws_queue_init(ws_message_queue_t* queue,uint32_t queue_size) /** * @brief 反初始化WebSocket消息队列 + * @param queue 消息队列结构体指针 + * @note 释放队列资源并清零结构体 */ static void ws_queue_deinit(ws_message_queue_t* queue) { if (!queue || !queue->initialized) return; - queue->initialized=false; + + queue->initialized = false; // 标记为未初始化 + // 删除OSAL消息队列 if (queue->queue_id != 0) { osal_msg_queue_delete(queue->queue_id); queue->queue_id = 0; } + // 清零队列结构体 memset(queue, 0, sizeof(ws_message_queue_t)); WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "WebSocket queue deinitialized"); } /** - * @brief 向队列中添加消息 + * @brief 向WebSocket消息队列中添加消息 + * @param queue 消息队列结构体指针 + * @param data 消息数据指针 + * @param length 消息数据长度 + * @param type WebSocket消息类型 + * @return int 0表示成功,-1表示失败 + * @note 消息数据会被复制到动态分配的内存中,调用者无需保持数据有效性 */ static int ws_queue_put(ws_message_queue_t* queue, const uint8_t* data, uint16_t length, WebSocketMessageType type) { + // 参数有效性检查 if (!queue || !queue->initialized || !data || length == 0 || length > WS_MESSAGE_MAX_SIZE) { return -1; } /* 创建消息项 */ ws_message_item_t item; - item.data = (uint8_t*)malloc(length); + item.data = (uint8_t*)malloc(length); // 动态分配内存存储消息数据 if (item.data == NULL) { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to malloc message data"); return -1; } + // 复制消息数据和设置属性 memcpy(item.data, data, length); item.length = length; item.type = type; - item.timestamp = osKernelGetTickCount(); + item.timestamp = osKernelGetTickCount(); // 记录时间戳 item.in_use = true; - /* 将消息写入osal消息队列 */ + /* 将消息写入OSAL消息队列 */ int ret = osal_msg_queue_write_copy(queue->queue_id, &item, sizeof(ws_message_item_t), 0); if (ret != OSAL_SUCCESS) { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to write message to osal queue"); - free(item.data); + free(item.data); // 写入失败时释放内存 return -1; } @@ -437,40 +353,50 @@ static int ws_queue_put(ws_message_queue_t* queue, const uint8_t* data, uint16_t } /** - * @brief 从队列中获取消息 + * @brief 从WebSocket消息队列中获取消息 + * @param queue 消息队列结构体指针 + * @param data 用于存储消息数据的缓冲区 + * @param max_length 缓冲区最大长度 + * @param type 用于返回消息类型的指针 + * @param timeout_ms 超时时间(毫秒) + * @return int 实际复制的数据长度,0表示超时或无消息,-1表示参数错误 + * @note 函数会自动释放消息数据的内存,调用者只需提供接收缓冲区 */ static int ws_queue_get(ws_message_queue_t* queue, uint8_t* data, uint16_t max_length, WebSocketMessageType* type, uint32_t timeout_ms) { + // 参数有效性检查 if (!queue || !queue->initialized || !data || !type) { return -1; } - /* 从osal消息队列读取消息 */ + /* 从OSAL消息队列读取消息 */ ws_message_item_t item; unsigned int buffer_size = sizeof(ws_message_item_t); int ret = osal_msg_queue_read_copy(queue->queue_id, &item, &buffer_size, timeout_ms); if (ret != OSAL_SUCCESS) { - return 0; /* 超时或错误 */ + return 0; /* 超时或错误,返回0表示无消息 */ } /* 检查消息是否有效 */ if (!item.in_use || item.data == NULL) { - return 0; + return 0; // 无效消息 } - /* 复制数据 */ + /* 复制数据到调用者提供的缓冲区 */ uint16_t copy_len = (item.length > max_length) ? max_length : item.length; memcpy(data, item.data, copy_len); - *type = item.type; + *type = item.type; // 返回消息类型 - /* 释放消息数据 */ + /* 释放消息数据内存 */ free(item.data); - return copy_len; + return copy_len; // 返回实际复制的数据长度 } /** * @brief 初始化所有WebSocket消息队列 + * @return int 0表示成功,-1表示失败 + * @note 初始化Chat、ASR、TTS三个服务的消息队列,支持重复初始化(会先清理旧队列) */ static int ws_queues_init_all(void) { @@ -484,11 +410,13 @@ static int ws_queues_init_all(void) ws_queue_deinit(&tts_msg_queue); } - ret |= ws_queue_init(&chat_msg_queue,256); - ret |= ws_queue_init(&asr_msg_queue,0); - ret |= ws_queue_init(&tts_msg_queue,0); - // ret |= ws_queue_init(&voice_msg_queue); + // 初始化各个服务的消息队列 + ret |= ws_queue_init(&chat_msg_queue, 256); // Chat队列使用较大容量 + ret |= ws_queue_init(&asr_msg_queue, 0); // ASR队列使用默认容量 + ret |= ws_queue_init(&tts_msg_queue, 0); // TTS队列使用默认容量 + // ret |= ws_queue_init(&voice_msg_queue); // 语音队列(已注释) + // 检查初始化结果 if (ret != 0) { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to initialize all WebSocket queues"); /* 清理已初始化的队列 */ @@ -503,16 +431,16 @@ static int ws_queues_init_all(void) return 0; } - /** * @brief 反初始化所有WebSocket消息队列 + * @note 清理所有服务的消息队列资源 */ static void ws_queues_deinit_all(void) { ws_queue_deinit(&chat_msg_queue); ws_queue_deinit(&asr_msg_queue); ws_queue_deinit(&tts_msg_queue); - // ws_queue_deinit(&voice_msg_queue); + // ws_queue_deinit(&voice_msg_queue); // 语音队列(已注释) WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "All WebSocket queues deinitialized"); } @@ -565,8 +493,8 @@ void uapi_UI_start_asr(uint8_t type) { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"uapi_UI_start_asr\n"); - memset_s(result_str_asr,AI_RESULT_STR_LEN,0,AI_RESULT_STR_LEN); // uapi_stop_tts(); + memset_s(result_str_asr,AI_RESULT_STR_LEN,0,AI_RESULT_STR_LEN); #ifdef ASR_HODE asr_is_stop=0; @@ -605,12 +533,6 @@ void uapi_UI_start_asr(uint8_t type) } -void uapi_UI_stop_start_asr_timeout(void) -{ - asr_need_stop=1; -} - - void uapi_UI_stop_asr(void) { #ifdef ASR_HODE @@ -635,857 +557,226 @@ void uapi_UI_stop_asr(void) -bool send_ai_txt(uint8_t type,char *msg) -{ - cJSON* dataJSON = cJSON_CreateObject(); - - if (dataJSON == NULL) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"Error creating JSON object\n"); - return false; - } - - - cJSON_AddStringToObject(dataJSON,"type","jiutian-lan"); - - cJSON_AddStringToObject(dataJSON,"content",msg); - - char *jsonString = cJSON_PrintUnformatted(dataJSON); - - if (jsonString == NULL) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"Error printing JSON to string\n"); - cJSON_Delete(dataJSON); - return false; - } - - uint32_t data_len = strlen(jsonString); - cJSON_Delete(dataJSON); - - if (data_len == 0) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"Error: JSON string length is 0\n"); - cJSON_free(jsonString); - return false; - } - - if (send_websocket_message(chat_client,( uint8_t *)jsonString,strlen(jsonString),WS_TEXT)==0) -{ -chat_reconnect=0; - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"send_websocket_message succ 0\n"); - cJSON_free(jsonString); - return true; - } - - else - { - if(chat_client==NULL) - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"chat_client is NULL\n"); - } - cJSON_free(jsonString); - return false; - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"send_websocket_message fail\n"); - } - - -} -// {"type":"middle","content":"我是","task_id":"","reasoning_content":""} -// char content_str_chat[64]={0}; +/* ==================== Chat服务任务配置 ==================== */ -bool chat_result_analysis(char *buf) -{ - cJSON* root = cJSON_Parse((const char *)buf); - if (root == NULL) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"解析失败\n"); - return false; - } - char *type_str=NULL; - char *content_str=NULL; +/** + * @brief Chat服务任务栈大小 + */ +#define CAT1_SERVICE_TASK_SIZE (2048) - cJSON* type = cJSON_GetObjectItem(root, "type"); - if (type && cJSON_IsString(type)) { - - type_str= type->valuestring; - } - cJSON* content = cJSON_GetObjectItem(root, "content"); - if (content && cJSON_IsString(content)) { - content_str = content->valuestring; - } - if(strcmp("middle",type_str)==0) - { - if(strlen(content_str)>0) - { - // memset_s(content_str_chat,64,0,64); - // memcpy_s(content_str_chat,64,content_str,strlen(content_str)); +/** + * @brief Chat服务任务优先级 + */ +#define CAT1_SERVICE_TASK_PRIO 5 +/** + * @brief Chat服务任务配置结构体 + * @note 存储Chat WebSocket服务的任务句柄和配置信息 + */ +app_task_definition_t chat_taskcfg={0}; - char *content_str_chat = (char *)malloc(strlen(content_str)+1); - strcpy(content_str_chat,content_str); - ble_set_ai_Answer_str(content_str_chat,2); +// typedef enum { +// osThreadInactive = 0, ///< Inactive. +// osThreadReady = 1, ///< Ready. +// osThreadRunning = 2, ///< Running. +// osThreadBlocked = 3, ///< Blocked. +// osThreadTerminated = 4, ///< Terminated. +// osThreadError = -1, ///< Error. +// osThreadReserved = 0x7FFFFFFF ///< Prevents enum down-size compiler optimization. +// } osThreadState_t; - // uapi_start_tts_single_txt(content_str_chat); - // static uint8_t test=1; - // if(test) - // { - // uapi_start_tts_single(); - // test=0; - // } - free(content_str_chat); +/** + * @brief 初始化Chat服务器 + * @return errcode_t ERRCODE_SUCC表示成功,ERRCODE_FAIL表示失败 + * @note 创建Chat WebSocket服务任务,检查任务状态和连接状态 + */ +errcode_t chat_server_init(void) +{ + // 获取当前任务状态 + int osThreadGetState_t = osThreadGetState(chat_taskcfg.task_handle); + const char *buf = osThreadGetName(chat_taskcfg.task_handle); + + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "chat osThreadGetState_t %d!\r\n", osThreadGetState_t); + + // 检查任务状态是否允许重新初始化 + if (strcmp("chat_server_init", buf) == 0) { + if (osThreadGetState_t != osThreadReady && + osThreadGetState_t != osThreadError && + osThreadGetState_t != osThreadInactive) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "osThreadGetState is %d!\r\n", osThreadGetState_t); + return ERRCODE_FAIL; } } - if(strcmp("end",type_str)==0) - { - char end_str_chat[128]={0}; - - if(strlen(content_str)>0) - { - memset_s(end_str_chat,128,0,128); - memcpy_s(end_str_chat,128,end_str_chat,strlen(end_str_chat)); - ble_set_ai_Answer_str(end_str_chat,2); - } + // 检查是否已经连接 + if (chat_client || is_bk_websocket_chat) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "cat1_tcp_server_init is connet!\r\n"); + return ERRCODE_FAIL; } + + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "cat1_tcp_server_init !\r\n"); + + // 配置Chat服务任务参数 + chat_taskcfg.create_queue_handle = NULL; + chat_taskcfg.attr.name = "chat_server_init"; + chat_taskcfg.attr.priority = TASK_PRIORITY_APP; + chat_taskcfg.attr.stack_size = 0x2000; // 8KB栈空间 + chat_taskcfg.func = bk_websocket_demo; // 任务入口函数 + chat_taskcfg.use_mem = USE_EXTEND_MEM; // 使用扩展内存 + // 创建Chat服务任务 + errcode_t ret = uapi_create_osThread_custom(&chat_taskcfg); - cJSON_Delete(root); - return true; -} -// &device_id=222&product_id=222 -bool chat_ws_url_init(char * put_buf,uint8_t type) -{ - char token[64]={0}; - if(read_dev_token(token)) - { - if(type_ai==AI_TYPE_JIU) - { - sprintf(put_buf,"ws://amp.raisound.com/chat/ws?dt=%s&platform=chinamobiledevice&bot_type=jiutian-lan",token); - } - else if(type_ai==AI_TYPE_DOU) - { - sprintf(put_buf,"ws://amp.raisound.com/chat/ws?dt=%s&platform=huoshan",token); - } - else if(type_ai==AI_TYPE_WEN) - { - sprintf(put_buf,"ws://amp.raisound.com/chat/ws?dt=%s&platform=baidu",token); - } - else if(type_ai==AI_TYPE_KIMI) - { - sprintf(put_buf,"ws://amp.raisound.com/chat/ws?dt=%s&platform=moonshot",token); - } - else if(type_ai==AI_TYPE_DE) - { - sprintf(put_buf,"ws://amp.raisound.com/chat/ws?dt=%s&platform=huoshan&model=ep-20250205103849-582m7",token); - } - else - { - sprintf(put_buf,"ws://amp.raisound.com/chat/ws?dt=%s&platform=aliyun",token); - } - return true; - - } - else - { - char * Service_url="118.145.158.207:22080"; - - char device_id[128]={0}; - get_dev_id(device_id); - - char * product_id="1927646275585744898"; - - if(type_ai==AI_TYPE_JIU) - { - sprintf(put_buf,"ws://%s/chat/ws?device_id=%s&product_id=%s&platform=chinamobiledevice&bot_type=jiutian-lan",Service_url,device_id,product_id); - } - else if(type_ai==AI_TYPE_DOU) - { - sprintf(put_buf,"ws://%s/chat/ws?platform=huoshan",Service_url); - } - else if(type_ai==AI_TYPE_WEN) - { - sprintf(put_buf,"ws://%s/chat/ws?platform=baidu",Service_url); - } - else if(type_ai==AI_TYPE_KIMI) - { - sprintf(put_buf,"ws://%s/chat/ws?platform=moonshot",Service_url); - } - else if(type_ai==AI_TYPE_DE) - { - sprintf(put_buf,"ws://%s/chat/ws?platform=huoshan&model=ep-20250205103849-582m7",Service_url); - } - else - { - sprintf(put_buf,"ws://%s/chat/ws?platform=aliyun",Service_url); - } - return false; + if (ret != ERRCODE_SUCC) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "create cat1_service_file failed %d!\r\n", ret); + return ERRCODE_FAIL; } - + return ERRCODE_SUCC; } -uint8_t is_bk_websocket_chat=0; -static uint8_t ai_asr_service_init_flag = 0; //asr初始化标志 -static uint8_t ai_chat_service_init_flag = 0; //chat初始化标志 -static uint8_t ai_tts_server_init_flag = 0; //tts初始化标志 -// 获取ASR服务初始化标志 -uint8_t get_ai_asr_service_init_flag(void) -{ - return ai_asr_service_init_flag; -} -// 设置ASR服务初始化标志 -void set_ai_asr_service_init_flag(uint8_t flag) -{ - ai_asr_service_init_flag = flag; -} +/* ==================== ASR(语音识别)服务 ==================== */ -// 获取Chat服务初始化标志 -uint8_t get_ai_chat_service_init_flag(void) -{ - return ai_chat_service_init_flag; -} -// 设置Chat服务初始化标志 -void set_ai_chat_service_init_flag(uint8_t flag) -{ - ai_chat_service_init_flag = flag; -} -// 获取tts服务初始化标志 -uint8_t get_ai_tts_server_init_flag(void) -{ - return ai_tts_server_init_flag; -} -// 设置tts服务初始化标志 -void set_ai_tts_server_init_flag(uint8_t flag) +/** + * @brief 录音测试函数 + * @note 从文件读取录音数据并发送到AI消息队列进行处理 + */ +void record_test(void) { - ai_tts_server_init_flag = flag; -} + char buf[1024] = {0}; + msleep(1000); // 等待1秒 -uint32_t get_bk_websocket_tts_tick(void) -{ - return bk_websocket_tts_tick; -} -void asr_on_ws_packet_callback(uint8_t* data, size_t datalen, WebSocketMessageType type) -{ - if(asr_client !=NULL && asr_client->msg_queue !=NULL) - { - // printf("asr_on_ws_packet_callback %d %s\n",datalen,data); - ws_queue_put(asr_client->msg_queue,data,datalen,type); + // 打开录音文件 + FILE *fp = fopen("/user/aiRecord.bin", "rb+"); + int read_size; + int read_size_num = 0; + uint32_t pack = 1024; // 每次读取1024字节 + + if (fp == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to open aiRecord.bin file"); + return; } + // 循环读取文件内容并发送到消息队列 + do { + memset_s(buf, 1024, 0, 1024); + read_size = fread(buf, 1, pack, fp); + + if (read_size != 0) { + // 将读取的数据发送到AI消息队列 + send_msg_data_into_Ai_msg_queue((void *)buf, (uint16_t)read_size, 0); + read_size_num += read_size; + } + } while (read_size == pack); + + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "record_test total bytes: %d\n", read_size_num); + fclose(fp); } -void tts_on_ws_packet_callback(uint8_t* data, size_t datalen, WebSocketMessageType type) -{ - if(tts_client !=NULL && tts_client->msg_queue !=NULL) - { - // printf("tts_on_ws_packet_callback %d %s\n",datalen,data); - ws_queue_put(tts_client->msg_queue,data,datalen,type); - } -} -void chat_on_ws_packet_callback(uint8_t* data, size_t datalen, WebSocketMessageType type) + +/** + * @brief ASR录音测试函数 + * @note 执行完整的ASR录音测试流程:启动ASR -> 发送录音数据 -> 停止ASR + * + * ASR服务器响应示例: + * 启动响应:{"status":20000000,"msg":"success","task_id":"80228c9f62821ffe3f867e1a5042d887","event":"TranscriptionStarted","result":null} + * 识别结果:{"status":20000000,"msg":"success","task_id":"e8274ed1efb60d8146c076a847d7b71a", + * "event":"SentenceEnd","result":{"result":"你好你好你好你好。","is_end":true, + * "sentence_id":"fcb889fe74a0920efe14939de2fd2b1b","start_time":760,"end_time":2280}} + */ +void asr_record_test(void) { - if(chat_client !=NULL && chat_client->msg_queue !=NULL) - { - ws_queue_put(chat_client->msg_queue,data,datalen,type); - } - + // uapi_start_asr(); // 启动ASR(已注释) + msleep(500); // 等待500ms + record_test(); // 执行录音测试 + msleep(500); // 等待500ms + uapi_stop_asr(); // 停止ASR } /** - * @brief 从队列中接收WebSocket消息(替代原来的receive_websocket_message) + * @brief ASR启动状态标志 + * @note 0表示未启动,1表示已启动 */ -static int receive_websocket_message_from_queue(ws_message_queue_t* queue, uint8_t* buffer, uint16_t buffer_size, WebSocketMessageType* type, uint32_t timeout_ms) -{ - if (!queue || !buffer || !type) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "!queue || !buffer || !type"); - return -1; - } - - return ws_queue_get(queue, buffer, buffer_size, type, timeout_ms); -} -void bk_websocket_demo(void *data) { - uint32_t start_time = (uint32_t)osKernelGetTickCount(); - uint32_t retry_count = 0; - - // 检查互斥锁是否已存在,避免重复创建 - if (!g_ai_mutex) { - g_ai_mutex = osMutexNew(NULL); - if (!g_ai_mutex) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to create AI mutex"); - return; - } - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "AI mutex created in bk_websocket_demo"); - } - - AI_LOCK(); - is_bk_websocket_chat = 1; - AI_UNLOCK(); - - if (chat_msg_queue.initialized ) { - WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "WebSocket queues already initialized, deinitializing first"); - ws_queue_deinit(&chat_msg_queue); - } - ws_queue_init(&chat_msg_queue,256); - char_reconnet: - ws_queue_cleanup(&chat_msg_queue); - - { - char url_buf[248] = {0}; - uint8_t is_raisound_url = 0; - uint8_t client_count = AI_MAX_RETRY_COUNT; - - // 检查内存使用情况 - if (!ai_check_memory_limit(1024)) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Memory limit exceeded, skipping connection"); - goto cleanup_and_exit; - } - - if (chat_ws_url_init(url_buf, type_ai)) { - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "Chat URL: %s", url_buf); - is_raisound_url = 1; - } else { - is_raisound_url = 0; - } - - // 连接重试循环 - while (client_count > 0) { - uint32_t connect_start = (uint32_t)osKernelGetTickCount(); - - if (is_raisound_url) { - chat_client = init_websocket_client(url_buf, NULL, 1); - if (chat_client != NULL) { - chat_client->msg_queue = &chat_msg_queue; - ws_set_packet_callback(chat_client, chat_on_ws_packet_callback); - // chat_client->packet_callback = chat_on_ws_packet_callback; - } - } else { - #ifdef SUPPORT_CHINA_MOBILE_AI_REGULATION - chat_client = init_websocket_client(url_buf, NULL, 0); - if (chat_client != NULL) { - chat_client->msg_queue = &chat_msg_queue; - ws_set_packet_callback(chat_client, chat_on_ws_packet_callback); - // chat_client->packet_callback = chat_on_ws_packet_callback; - } - #else - const char* base_url = "ws://118.145.158.207:22080/chat/ws"; - char full_url[512] = {0}; - - switch (type_ai) { - case AI_TYPE_JIU: - snprintf(full_url, sizeof(full_url), "%s?&platform=chinamobiledevice&bot_type=jiutian-lan", base_url); - break; - case AI_TYPE_DOU: - snprintf(full_url, sizeof(full_url), "%s?&platform=huoshan", base_url); - break; - case AI_TYPE_WEN: - snprintf(full_url, sizeof(full_url), "%s?&platform=baidu", base_url); - break; - case AI_TYPE_KIMI: - snprintf(full_url, sizeof(full_url), "%s?&platform=moonshot", base_url); - break; - case AI_TYPE_DE: - snprintf(full_url, sizeof(full_url), "%s?platform=huoshan&model=ep-20250205103849-582m7", base_url); - break; - default: - snprintf(full_url, sizeof(full_url), "%s?&platform=aliyun", base_url); - break; - } - chat_client = init_websocket_client(full_url, NULL, 0); - if (chat_client != NULL) { - chat_client->msg_queue = &chat_msg_queue; - ws_set_packet_callback(chat_client, chat_on_ws_packet_callback); - // chat_client->packet_callback = chat_on_ws_packet_callback; - } - #endif - } - - if (chat_client == NULL) { - client_count--; - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "WebSocket connection failed, retries left: %d", client_count); - - // 检查超时 - if ((uint32_t)osKernelGetTickCount() - connect_start > AI_CONNECT_TIMEOUT_MS) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Connection timeout"); - break; - } - - msleep(1000); - continue; - } else { - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "WebSocket connection established"); - break; - } - } - - if (chat_client == NULL) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to establish WebSocket connection after %d retries", AI_MAX_RETRY_COUNT); - goto cleanup_and_exit; - } - - // 在握手前先确认TCP连接状态 - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "Chat WebSocket: 开始TCP连接确认..."); - - // 先检查当前连接状态 - tcp_connection_state_t current_state = tcp_get_connection_state(chat_client->tcp_handle); - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "Chat WebSocket: 当前TCP连接状态: %s", tcp_state_to_string(current_state)); - - if (!wait_for_tcp_connection(chat_client, "Chat", AI_WS_TCP_CONNECTION_TIMEOUT_MS)) { // 使用宏定义的TCP连接确认超时 - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Chat WebSocket: TCP连接确认失败,退出线程"); - lwip_close_websocket_client(chat_client); - chat_client = NULL; - goto cleanup_and_exit; - } - - // if(tts_client==NULL) - // { - // tts_server_init(); - // } - // WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "Chat WebSocket: TCP连接确认成功,开始握手等待"); - #if TEST_AI_WS - #else - // 等待WebSocket握手完成,增加超时机制 - uint32_t handshake_start_time = (uint32_t)osKernelGetTickCount(); - const uint32_t handshake_timeout_ms = AI_WS_HANDSHAKE_TIMEOUT_MS; // 使用宏定义的握手超时 - - while (chat_client->handshake == 0) { - // 检查是否超时 - if ((uint32_t)osKernelGetTickCount() - handshake_start_time > handshake_timeout_ms) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "WebSocket握手超时 (%d ms),重新连接", handshake_timeout_ms); - lwip_close_websocket_client(chat_client); - chat_client = NULL; - goto cleanup_and_exit; - } - - msleep(1000); - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "等待WebSocket握手完成... (%d ms)", - (uint32_t)osKernelGetTickCount() - handshake_start_time); - } - - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "WebSocket握手完成,耗时 %d ms", - (uint32_t)osKernelGetTickCount() - handshake_start_time); - #endif - - // 设置socket超时 - // struct timeval timeout; - // timeout.tv_sec = AI_RECEIVE_TIMEOUT_MS / 1000; - // timeout.tv_usec = (AI_RECEIVE_TIMEOUT_MS % 1000) * 1000; - // lwip_setsockopt(chat_client->sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); - // tcp_set_timeout(chat_client->tcp_handle, AI_RECEIVE_TIMEOUT_MS); +uint8_t asr_is_start = 0; - server_state = 0x02 ^ server_state; - - AI_LOCK(); - set_ai_chat_service_init_flag(1); - AI_UNLOCK(); - - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "Chat client initialized successfully"); - - WebSocketMessageType type; - uint32_t last_activity = (uint32_t)osKernelGetTickCount(); - - while (disconnect_server == 0) { - // 检查超时 - if ((uint32_t)osKernelGetTickCount() - last_activity > AI_RECEIVE_TIMEOUT_MS) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Receive timeout, reconnecting..."); - - tcp_connection_state_t current_state = tcp_get_connection_state(chat_client->tcp_handle); - if(current_state!=TCP_STATE_CONNECTED) - { - break; - } - - if(disconnect_server == 0) - { - last_activity = (uint32_t)osKernelGetTickCount(); - continue; - } - break; - } - - memset_s(recv_buf, 1024, 0, 1024); - int len = receive_websocket_message_from_queue(&chat_msg_queue, (unsigned char *)recv_buf, 1024, &type, AI_RECEIVE_TIMEOUT_MS); - if (len < 1024) - { - recv_buf[len] = '\0'; - } - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "bk_websocket_demo len %d\n",len); - if (len == -1 || type == WS_CLOSE) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "WebSocket connection closed"); - break; - } - - if (len > 0) { - last_activity = (uint32_t)osKernelGetTickCount(); - - if (type == WS_PONG) { - chat_client_ping = 0; - } else { - // 检查队列状态 - AI_LOCK(); - if (chat_reconnect == 1) { - AI_UNLOCK(); - break; - } - AI_UNLOCK(); - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "recv_buf %s",recv_buf); - if (len < 1024) - { - recv_buf[len] = '\0'; - } - chat_result_analysis(recv_buf); - } - } - - // 定期检查资源状态 - if (retry_count++ % 100 == 0) { - ai_update_memory_usage(0); // 更新内存使用统计 - } - - msleep(10); // 避免过度占用CPU - } - - AI_LOCK(); - set_ai_chat_service_init_flag(0); - AI_UNLOCK(); - - lwip_close_websocket_client(chat_client); - chat_client = NULL; - - ws_queue_cleanup(&chat_msg_queue); - - if(disconnect_server == 0) - { - chat_reconnect = 0; - retry_count = 0; - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "Reconnecting chat client..."); - goto char_reconnet; - } - - } - -cleanup_and_exit: - AI_LOCK(); - ws_queue_deinit(&chat_msg_queue); - is_bk_websocket_chat = 0; - chat_client_ping = 0; - AI_UNLOCK(); - - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "Chat WebSocket demo ended"); - return; -} - - - - -#define CAT1_SERVICE_TASK_SIZE (2048) -#define CAT1_SERVICE_TASK_PRIO 5 - - -app_task_definition_t chat_taskcfg={0}; - - -// typedef enum { -// osThreadInactive = 0, ///< Inactive. -// osThreadReady = 1, ///< Ready. -// osThreadRunning = 2, ///< Running. -// osThreadBlocked = 3, ///< Blocked. -// osThreadTerminated = 4, ///< Terminated. -// osThreadError = -1, ///< Error. -// osThreadReserved = 0x7FFFFFFF ///< Prevents enum down-size compiler optimization. -// } osThreadState_t; - - -errcode_t chat_server_init(void) -{ - int osThreadGetState_t= osThreadGetState(chat_taskcfg.task_handle); - - const char *buf= osThreadGetName(chat_taskcfg.task_handle); - - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"chat osThreadGetState_t %d!\r\n",osThreadGetState_t); - if(strcmp("chat_server_init",buf)==0) - { - if(osThreadGetState_t!=osThreadReady && osThreadGetState_t!=osThreadError && osThreadGetState_t !=osThreadInactive) - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"osThreadGetState is %d!\r\n",osThreadGetState_t); - return ERRCODE_FAIL; - } - } - - if(chat_client || is_bk_websocket_chat ) - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"cat1_tcp_server_init is connet!\r\n"); - return ERRCODE_FAIL; - } - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"cat1_tcp_server_init !\r\n"); - - chat_taskcfg.create_queue_handle = NULL; - chat_taskcfg.attr.name="chat_server_init"; - chat_taskcfg.attr.priority = TASK_PRIORITY_APP; - chat_taskcfg.attr.stack_size = 0x2000; - chat_taskcfg.func = bk_websocket_demo; - chat_taskcfg.use_mem = USE_EXTEND_MEM; - - - - errcode_t ret=uapi_create_osThread_custom(&chat_taskcfg); - - - if(ret != ERRCODE_SUCC) - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"create cat1_service_file failed %d!\r\n",ret); - return ERRCODE_FAIL; - } - return ERRCODE_SUCC; -} - - -/////////////////////////////////////////////asr/////////////// - -// http://118.145.158.207:22080ws://amp.raisound.com/asr/ws - - - - -//查询是否可以对话 -void uapi_start_asr(void) -{ - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"查询uapi_start_asr是否可用\n"); - - char *jsonString = "{\"header\":{\"event\":\"StartTranscription\",\"trace_id\": \ - \"3fa6b9e6a30b407ea38ad7ae69e6c291\"},\"payload\":{\"format\":\"MP3\",\"sample_rate\":16000}}"; - - if (send_websocket_message(asr_client,( uint8_t *)jsonString,strlen(jsonString),WS_TEXT)==0) - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"send_websocket_message succ 0\n"); - else - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"send_websocket_message erro\n"); - } - -} - -void uapi_start_asr_task(void) -{ - - do_custom_cmd_service_check_msg(CUSTOM_CMD_MSG_AI_SEND_TEST, nullptr, 0); - -} - - - -//停止对话 -void uapi_stop_asr(void) -{ - char *jsonString ="{\"header\":{\"event\":\"StopTranscription\",\"trace_id\":\"3fa6b9e6a30b407ea38ad7ae69e6c291\"}}"; - - if (send_websocket_message(asr_client,( uint8_t *)jsonString,strlen(jsonString),WS_TEXT)==0) - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"send_websocket_message succ 0\n"); - else - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"send_websocket_message erro\n"); - } -} - - - - - -void record_test(void) -{ - char buf[1024]={0}; - msleep(1000); - - FILE *fp=fopen("/user/aiRecord.bin","rb+"); - int read_size; - int read_size_num=0; - uint32_t pack=1024; - - if(fp==NULL) - { - return; - } - do - { - memset_s(buf,1024,0,1024); - read_size=fread(buf,1,pack,fp); - - if(read_size!=0) - { - send_msg_data_into_Ai_msg_queue((void *)buf,(uint16_t)read_size,0); - read_size_num+=read_size; - - } - - } while (read_size==pack); - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"record_test %d\n",read_size_num); - fclose(fp); -} - -// {"status":20000000,"msg":"success","task_id":"80228c9f62821ffe3f867e1a5042d887","event":"TranscriptionStarted","result":null} -// {"status":20000000, -// "msg":"success","task_id":"e8274ed1efb60d8146c076a847d7b71a", -// "event":"SentenceEnd","result":{"result":"你好你好你好你好。", -// "is_end":true,"sentence_id":"fcb889fe74a0920efe14939de2fd2b1b", -// "start_time":760,"end_time":2280}} -void asr_record_test(void) -{ - // uapi_start_asr(); - msleep(500); - record_test(); - msleep(500); - uapi_stop_asr(); -} - - - -uint8_t asr_is_start=0; - -bool aer_result_analysis(char *buf) -{ - cJSON* root = cJSON_Parse((const char *)buf); - if (root == NULL) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"解析失败 %s\n",buf); - return false; - } - char *result_str=NULL; - int start_time_int=0; - int end_time_int=0; - bool is_end_bool=false; - char *event_str=NULL; - - cJSON* event = cJSON_GetObjectItem(root, "event"); - if (event && cJSON_IsString(event)) { - event_str= event->valuestring; - - } - - cJSON* result = cJSON_GetObjectItem(root, "result"); - if (result) - { - cJSON* result_ = cJSON_GetObjectItem(result, "result"); - - if (result_ && cJSON_IsString(result_)) { - result_str= result_->valuestring; - } - cJSON* start_time = cJSON_GetObjectItem(result, "start_time"); - if (start_time && cJSON_IsNumber(start_time)) { - - start_time_int = root->valueint; - } - cJSON* end_time = cJSON_GetObjectItem(result, "end_time"); - if (end_time && cJSON_IsNumber(end_time)) { - end_time_int = end_time->valueint; - } - - cJSON* is_end = cJSON_GetObjectItem(result, "is_end"); - if (is_end && cJSON_IsBool(is_end)) { - is_end_bool = is_end->valueint; - } - } - - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"str %s start %d end %d is end %d\n",result_str,start_time_int,end_time_int,is_end_bool); - - if(is_end_bool==true) - { - - #ifdef ASR_HODE - memcpy_s(&result_str_asr[strlen(result_str_asr)],AI_RESULT_STR_LEN/2,result_str,strlen(result_str)); - - if(asr_is_stop==1 && strlen(result_str_asr)) - { - send_ai_txt(0,result_str_asr); - ble_set_ai_Question_str(result_str_asr); - - memset_s(result_str_asr,AI_RESULT_STR_LEN,0,AI_RESULT_STR_LEN); - } - - - #else - memset_s(result_str_asr,256,0,256); - memcpy_s(result_str_asr,256,result_str,strlen(result_str)); - SendMsgToUIService(UI_AI_JUMP_TO_RESULT_VIEW, 0, nullptr, 0);//先跳转到结果界面 - - ble_set_ai_Question_str(result_str_asr); - // stop_AudioCaptureProcess_AI(); - send_ai_txt(0,result_str_asr); - #endif - - } - if(strcmp(event_str,"TranscriptionStarted")==0) //可以对话了,切换到聆听界面 - { - //查询结果 - startAudioCaptureTask(); - // { - // //上传文件 - // } - - bool state = true; - SendMsgToUIService(UI_AI_JUMP_TO_LISTENING_VIEW, 0, &state, sizeof(bool)); - - } - if(strcmp(event_str,"TranscriptionCompleted")==0) //asr 结束 - { - cJSON_Delete(root); - return true; // 返回true asr不断连 - } - if(strcmp(event_str,"Failed")==0) - { - stop_AudioCaptureProcess_AI(); - cJSON_Delete(root); - return false; - } - - cJSON_Delete(root); - return true; - - -} - -uint8_t is_bk_websocket_asr=0; - -// ws://amp.raisound.com/asr/ws?dt=95fc6dcdf5a3e9a48df535453efd097c19&platform=aliyun +/** + * @brief ASR WebSocket连接状态标志 + * @note 0表示未连接,1表示已连接 + */ +uint8_t is_bk_websocket_asr = 0; -bool asr_ws_url_init(char * put_buf) +/** + * @brief 初始化ASR WebSocket URL + * @param put_buf 输出缓冲区,存储构建的URL + * @return bool true表示成功,false表示失败 + * @note 从设备令牌构建完整的ASR WebSocket URL + * 格式:ws://amp.raisound.com/asr/ws?dt={token}&platform=aliyun + */ +bool asr_ws_url_init(char *put_buf) { - char token[64]={0}; - if(read_dev_token(token)) - { - sprintf(put_buf,"ws://amp.raisound.com/asr/ws?dt=%s&platform=aliyun",token); + char token[64] = {0}; + if (read_dev_token(token)) { + sprintf(put_buf, "ws://amp.raisound.com/asr/ws?dt=%s&platform=aliyun", token); return true; - } - else - { + } else { return false; } } -const char* ws_test_server_url="ws://118.145.158.207:22080/asr/ws"; -const char* ws_snicker_test_server_url="ws://110.42.224.129:8910/asr/ws"; - -int g_test_server_index = 0; //0 正式版本, 1 测试版本, 2 Snicker 服务器测试版本 +/** + * @brief 测试服务器URL + */ +const char* ws_test_server_url = "ws://118.145.158.207:22080/asr/ws"; +/** + * @brief Snicker测试服务器URL + */ +const char* ws_snicker_test_server_url = "ws://110.42.224.129:8910/asr/ws"; +/** + * @brief 测试服务器索引 + * @note 0=正式版本,1=测试版本,2=Snicker服务器测试版本 + */ +int g_test_server_index = 0; -int asr_client_sockfd_last_id=-1; +/** + * @brief ASR客户端上次连接的socket文件描述符 + */ +int asr_client_sockfd_last_id = -1; +/** + * @brief ASR服务器反初始化函数声明 + */ void asr_server_deinit(void); +/** + * @brief ASR WebSocket主任务函数 + * @param data 任务参数(未使用) + * @note ASR服务的主要处理函数,负责WebSocket连接管理、消息接收和处理 + */ void bk_websocket_asr(void *data) { uint32_t start_time = (uint32_t)osKernelGetTickCount(); uint32_t retry_count = 0; WebSocketMessageType type; bool state = true; -if (asr_msg_queue.initialized ) { + + // 检查并初始化ASR消息队列 + if (asr_msg_queue.initialized) { WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "WebSocket queues already initialized, deinitializing first"); ws_queue_deinit(&asr_msg_queue); } - ws_queue_init(&asr_msg_queue,256); + ws_queue_init(&asr_msg_queue, 256); ASR_SATRT: AI_LOCK(); @@ -1802,216 +1093,8 @@ bool tts_ws_url_init(char * put_buf) char tts_task_id_buf[128]={0}; uint8_t tts_is_ready=0; -void uapi_start_tts(void) -{ - char *jsonString = - "{\"header\": \ - { \"event\": \"StartSynthesis\",\ - \"trace_id\": \"\"},\ - \"payload\": \ - {\"voice\":\"xiaoyun\",\ - \"format\":\"mp3\",\ - \"sample_rate\":16000,\ - \"volume\":80,\ - \"speech_rate\":0}\ - }"; - - printf("uapi_start_tts :%s\r\n",jsonString); - - - if (send_websocket_message(tts_client,( uint8_t *)jsonString,strlen(jsonString),WS_TEXT)==0) - { - memset_s(tts_task_id_buf,128,0,128); - tts_is_ready=0; - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"send_websocket_message succ 0\n"); - } - -} - - -void uapi_stop_tts(void) -{ - char *jsonString = - " { \ - \"header\": { \ - \"event\": \"StopSynthesis\", \ - \"trace_id\": \"\" \ - } \ - } "; - - printf("uapi_start_tts : %s\r\n",jsonString); - - if (send_websocket_message(tts_client,( uint8_t *)jsonString,strlen(jsonString),WS_TEXT)==0) - { - tts_is_ready=0; - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"send_websocket_message succ 0\n"); - } - -} - - - -void uapi_start_tts_single(void) -{ - - char *jsonString = - " { \"header\": { \ - \"event\": \"LongTextStream\",\ - \"trace_id\": \"3fa6b9e6a30b407ea38ad7ae69e6c291\" \ - }, \ - \"payload\": { \ - \"voice\":\"xiaoyun\", \ - \"format\":\"pcm\", \ - \"sample_rate\":16000, \ - \"volume\":50, \ - \"speech_rate\":0, \ - \"text\":\"深圳市北科瑞声科技股份有限公司,是由国家重大人才计划特聘专家刘轶博士创办的国内人 \ - 工智能音频领军企业,专注于行业智能音频交互技术,拥有全链条核心自主知识产权,汇集世界一流的研发运营团队。\" \ - } } "; - - printf("uapi_start_tts : %s\r\n",jsonString); - - if (send_websocket_message(tts_client,(uint8_t *)jsonString,strlen(jsonString),WS_TEXT)==0) - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"send_websocket_message succ 0\n"); -} - - -bool uapi_start_tts_single_txt(char *buf) -{ - - #if 0 - if(tts_is_ready!=1) - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"uapi_start_tts_single_txt no tts_is_ready \n"); - return false; - } - char *jsonString = (char *)malloc(strlen(buf)+300);// [512] ={0}; - - sprintf(jsonString, - - "{\"header\" : { \ - \"event\": \"RunSynthesis\", \ - \"trace_id\": \"%s\" \ - }, \ - \"payload\": { \ - \"text\":\"%s\" \ - }}" - - // "{ \"header\": { \ - // \"event\": \"LongTextStream\", \ - // \"trace_id\": \"3fa6b9e6a30b407ea38ad7ae69e6c291\"\ - // },\ - // \"payload\": {\ - // \"voice\":\"xiaoyun\",\ - // \"format\":\"mp3\",\ - // \"sample_rate\":16000,\ - // \"volume\":80,\ - // \"speech_rate\":0,\ - // \"text\":\"%s\"\ - // } }" - - ,tts_task_id_buf,buf); - - printf("uapi_start_tts_single_txt :%s\r\n",jsonString); - - if (send_websocket_message(tts_client,( uint8_t *)jsonString,strlen(jsonString),WS_TEXT)==0) - { - tts_reconnect=0; - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"send_websocket_message succ 0\n"); - free(jsonString); - return true; - } - free(jsonString); - return false; - #else - if (tts_is_ready != 1) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "uapi_start_tts_single_txt no tts_is_ready \n"); - return false; - } - // 创建根JSON对象 - cJSON* dataJSON = cJSON_CreateObject(); - if (dataJSON == NULL) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Error creating root JSON object\n"); - return false; - } - - // 创建header对象并添加字段 - cJSON* header = cJSON_CreateObject(); - if (header == NULL) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Error creating header JSON object\n"); - cJSON_Delete(dataJSON); // 释放已分配的内存 - return false; - } - cJSON_AddStringToObject(header, "event", "RunSynthesis"); - cJSON_AddStringToObject(header, "trace_id", tts_task_id_buf); - - // 创建payload对象并添加字段 - cJSON* payload = cJSON_CreateObject(); - if (payload == NULL) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Error creating payload JSON object\n"); - cJSON_Delete(header); // 释放已分配的内存 - cJSON_Delete(dataJSON); - return false; - } - cJSON_AddStringToObject(payload, "text", buf); - - // 将header和payload添加到根对象dataJSON中 - cJSON_AddItemToObject(dataJSON, "header", header); - cJSON_AddItemToObject(dataJSON, "payload", payload); - - // 将JSON对象转换为字符串 - char* jsonString = cJSON_PrintUnformatted(dataJSON); - if (jsonString == NULL) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Error converting JSON to string\n"); - cJSON_Delete(dataJSON); // 释放JSON对象 - return false; - } - - // 发送WebSocket消息 - bool result = false; - if (send_websocket_message(tts_client, (uint8_t*)jsonString, strlen(jsonString), WS_TEXT) == 0) { - tts_reconnect = 0; - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "send_websocket_message succ 0\n"); - result = true; - } - else - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "send_websocket_message fail -1\n"); - } - - // 释放资源 - cJSON_Delete(dataJSON); // 释放JSON对象(会自动释放header和payload) - free(jsonString); // 释放cJSON_Print生成的字符串 - return result; - #endif -} - -// char *tts_txt_str= - - -void send_tts_txt(char *msg) -{ - char *jsonString = - "{ \ - \"header\": { \ - \"event\": \"RunSynthesis\", \ - \"trace_id\": \"3fa6b9e6a30b407ea38ad7ae69e6c291\"\ - }, \ - \"payload\": { \ - \"text\":\"深圳市北科瑞声科技股份有限公司,是由国家重大人才计划特聘专家刘轶博士创办的国内人工智能音频领军企业,\ - 专注于行业智能音频交互技术,拥有全链条核心自主知识产权,汇集世界一流的研发运营团队。\" \ - }\ - }"; - - - if (send_websocket_message(tts_client,( uint8_t *)jsonString,strlen(jsonString),WS_TEXT)==0) - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"send_websocket_message succ 0\n"); - else - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"send_websocket_message fail 1\n"); -} - // send_msg_data_into_Ai_msg_queue((uint8_t *)tts_recv_buf,len,200); @@ -2059,48 +1142,6 @@ void tts_paly() fclose(fp); } } -// {"header":{"event":"StartedSynthesis","msg":"success","status":20000000,"task_id":"8e8996f9148b66738309602ec3219412"}} -bool tts_analysis(char *buf) -{ - cJSON* root = cJSON_Parse((const char *)buf); - if (root == NULL) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"解析失败\n"); - return false; - } - char *event_str=NULL; - char *task_id_str=NULL; - char *msg_str=NULL; - - - - cJSON* header = cJSON_GetObjectItem(root, "header"); - if(header) - { - cJSON* event = cJSON_GetObjectItem(header, "event"); - if (event && cJSON_IsString(event)) { - event_str= event->valuestring; - } - - cJSON* msg = cJSON_GetObjectItem(header, "msg"); - if (msg && cJSON_IsString(msg)) { - msg_str= msg->valuestring; - - if(strcmp("StartedSynthesis",event_str)==0 && strcmp("success",msg_str)==0) - { - tts_is_ready=1; - } - } - - cJSON* task_id = cJSON_GetObjectItem(header, "task_id"); - - if (task_id && cJSON_IsString(task_id)) - { - memcpy_s(tts_task_id_buf,128,task_id->valuestring,strlen(task_id->valuestring)); - } - } - cJSON_Delete(root); - return true; -} uint8_t is_bk_websocket_tts=0; @@ -2150,22 +1191,7 @@ TTS_SATRT: while (client_count > 0) { uint32_t connect_start = (uint32_t)osKernelGetTickCount(); - if (is_raisound_url == 1) { - tts_client = init_websocket_client(url_buf, NULL, 1); - if (tts_client != NULL) { - tts_client->msg_queue = &tts_msg_queue; - ws_set_packet_callback(tts_client, tts_on_ws_packet_callback); - // tts_client->packet_callback = tts_on_ws_packet_callback; - } - } else { - tts_client = init_websocket_client("ws://118.145.158.207:22080/tts/stream?audio_buffer_size=512", NULL, 0); - if (tts_client != NULL) { - tts_client->msg_queue = &tts_msg_queue; - ws_set_packet_callback(tts_client, tts_on_ws_packet_callback); - // tts_client->packet_callback = tts_on_ws_packet_callback; - } - } - + if (tts_client == NULL) { client_count--; WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "TTS WebSocket connection failed, retries left: %d", client_count); @@ -2520,123 +1546,26 @@ static void time_sync_func(void) if(0==send_websocket_message(chat_client,(uint8_t *)"ping",strlen("ping"),WS_PING)) { chat_client_ping=1; - } - } - } - if(tts_client) - { - if(tts_client_ping) - { - tts_server_deinit(); - lwip_close_websocket_client(tts_client); - tts_client=NULL; - } - else - { - if(0==send_websocket_message(tts_client,(uint8_t *)"ping",strlen("ping"),WS_PING)) - { - tts_client_ping=1; - } - } - } - -} - - -errcode_t voice_chat_server_init(void); - -bool init_ai_task(uint8_t type) -{ - if (is_init_ai) { - // 初始化AI系统 - if (ai_system_init() != ERRCODE_SUCC) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to initialize AI system"); - return false; - } - - // 系统健康检查 - if (!ai_system_health_check()) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "AI system health check failed"); - return false; - } - - disconnect_server = 0; - - // g_timer_time_sync = osTimerNew((osTimerFunc_t)time_sync_func, osTimerPeriodic, NULL, NULL); - // if (g_timer_time_sync == NULL) { - // WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "g_timer_time_sync create failed"); - // ai_cleanup_resources(); - // return false; - // } - - type_ai = type; - - // 初始化TTS播放 - if (uapi_tts_paly_init() != ERRCODE_SUCC) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to initialize TTS playback"); - // ai_cleanup_resources(); - // return false; - } - - // 初始化TTS服务器 - if (tts_server_init() != ERRCODE_SUCC) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to initialize TTS server"); - // ai_cleanup_resources(); - // return false; - } - - // 初始化聊天服务器 - if (chat_server_init() != ERRCODE_SUCC) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to initialize chat server"); - // ai_cleanup_resources(); - // return false; - } - // 初始化ASR服务器 - if (asr_server_init() != ERRCODE_SUCC) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to initialize asr server"); - // ai_cleanup_resources(); - // return false; + } } - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "AI task initialized successfully, type: %d", type); } - - return true; -} - - - - - -bool dinit_ai_task(void) -{ - if (is_init_ai) { - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "Deinitializing AI task..."); - - disconnect_server = 1; - - if (g_timer_time_sync) { - osTimerDelete(g_timer_time_sync); - g_timer_time_sync = NULL; + if(tts_client) + { + if(tts_client_ping) + { + tts_server_deinit(); + lwip_close_websocket_client(tts_client); + tts_client=NULL; } - - // 停止TTS播放 - if (uapi_tts_paly_stop() != ERRCODE_SUCC) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to stop TTS playback"); + else + { + if(0==send_websocket_message(tts_client,(uint8_t *)"ping",strlen("ping"),WS_PING)) + { + tts_client_ping=1; + } } - - // 清理服务器连接 - asr_server_deinit(); - chat_server_deinit(); - tts_server_deinit(); - - // 清理AI系统资源 - ai_cleanup_resources(); - - - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "AI task deinitialized successfully"); } - return true; } // @@ -2660,103 +1589,6 @@ WebSocketClient *voice_chat_client=NULL; uint8_t is_bk_websocket_voice_chat=0; -void send_ai_bin(uint8_t *buf,uint32_t len) -{ - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"send_ai_bin %d\n",len); - // voice_chat_client - - // if (send_websocket_message(asr_client,( uint8_t *)buf,len,WS_BINARY)==0) - if (send_websocket_message(asr_client,( uint8_t *)buf,len,WS_BINARY)==0) - { - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"send_websocket_message succ 0\n"); - msleep(100); - } - else - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"send_websocket_message fail 0\n"); - } - -} - - -typedef enum -{ - AI_STATE_0=0, - Listening, - Responding, - Thinking, - -}AI_VOICE_STATE; - -uint8_t ai_voice_chat_state=0; - - - - -// { -// "event": "start", -// "payload": { -// "device_id": "123123123", // 设备ID -// "user_agent": "AudioClient/1.0", // 浏览器 UA -// "simple_response": false, // true 只返回完整的LLM文本 默认false 返回增量LLM文本 -// // "dialog_attributes": { // 智能体配置 -// // "provider": "coze", // 切换服务驱动 -// // "cfg": { -// // "disable_empty_prompt": true, // 拦截 asr 识别出来的空文本发送到大模型 -// // "custom_variables": { // coze 自定义参数 -// // "name": "小瑞" -// // }, -// // "greeting": { // 问候语 配置 -// // "enabled": true, // 是否开启问候语 -// // "content": "" // 问候语内容 coze 不填写默认使用 coze 平台设置的 -// // }, -// // "bot_option": { -// // "use_workflow": true, // 使用对话流方式 coze 推荐使用对话流 -// // "bot_id":"7462617881076351002", // 使用对话流 bot_id 需要和 workflow_id处于同一个工作空间即可 -// // "workflow_id":"7462621757327196187", // -// // } -// // } -// // }, -// "outbound_attributes": { // TTS输出音频配置 -// "provider": "volcengine", // TTS服务驱动 -// "cfg": { -// "format": "pcm", // 音频输出格式 -// "sample_rate": 16000, // 音频输出采样率 -// "bit_rate": 16, // 比特率 -// "channel": 1, // 声道 1 单声道 -// "volume": 100, // 音量 0-100 -// "speech_rate": 0, // 语速 [-50,100] 默认 0 -// "chunk_interval_ms": 100, // 配置每段音频入队间隔时间 100ms -// "voice": "zh_female_linjianvhai_moon_bigtts" // 音色 -// "voice_source": "private", // 音色来源。目前定义 private 复刻音色。 system 使用系统默认音色,不提交这个参数兼容 system -// } -// }, -// "inbound_attributes": { // ASR输入音频配置 -// "provider": "volcengine", // ASR服务驱动 -// "disable_vad": false, // 开启vad 自动断句 (开启后可以有客户端处理回音消除后持续发送音频由服务器判断是否转写和发送LLM) -// "vad_silence": 400, // 设置vad 静音阈值 (毫秒) 连续静音超过 阈值自动切换状态 -// "interrupt_options": { -// "concatenate_results": true, // 是否在打断后拼接前后的ASR结果 -// "thinking_time_only":true // 是否只在思考时间允许打断 思考时间(ASR结束--->第一帧音频合成) -// "keyword_interrupt": true, // 启用打断词功能 (打断词只有在思考以及之后才能打断) -// "interrupt_keywords": "",// 配置打断词列表 -// }, -// "cfg": { -// "format": "pcm", // 音频输入格式 -// "sample_rate": 16000, // 音频输入采样率 目前只支持 16000 -// "bit_rate": 16, // 比特率 目前只支持 16bit -// "channel": 1 // 声道 目前只支持单声道 -// "enable_punctuation": true, // 开启标点 -// "enable_itn":true, // 开启itn -// "keywords": ["张三"] // 热词 -// } -// }, -// // 注意: 如果走智能体 端侧插件 commands 不传 - -// } -// } - - bool start_ai_voice_chat() { unsigned char deviceId[20] = {0}; @@ -2961,429 +1793,9 @@ bool voice_chat_analysis(char *buf) return true; } -void voice_chat_websocket(void *data) { - uint32_t start_time = (uint32_t)osKernelGetTickCount(); - uint32_t retry_count = 0; - - uapi_cat1_keep_wakeup(1); - - WebSocketMessageType type; - bool state = true; - - AI_LOCK(); - is_bk_websocket_tts = 1; - AI_UNLOCK(); - - uint8_t is_raisound_url = 0; - - { - char url_buf[124] = {0}; - - uint8_t client_count = AI_MAX_RETRY_COUNT; - - // 检查内存使用情况 - if (!ai_check_memory_limit(1024 * 4)) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Memory limit exceeded for voice chat buffer"); - goto voice_chat_cleanup_and_exit; - } - - while (client_count > 0) { - uint32_t connect_start = (uint32_t)osKernelGetTickCount(); - - if (is_raisound_url == 1) { - voice_chat_client = init_websocket_client(url_buf, NULL, 1); - if (voice_chat_client != NULL) { - // voice_chat_client->msg_queue = &voice_msg_queue; - } - } else { - voice_chat_client = init_websocket_client("ws://118.145.158.207:22080/voice-chat/v2/ws?platform=huoshan", NULL, 0); - if (voice_chat_client != NULL) { - // voice_chat_client->msg_queue = &voice_msg_queue; - } - } - - if (voice_chat_client == NULL) { - client_count--; - - // 使用统一的错误处理函数 - if (!handle_websocket_connection_failure("Voice chat", AI_MAX_RETRY_COUNT - client_count, AI_MAX_RETRY_COUNT)) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Voice chat WebSocket connection failed after all retries"); - break; - } - - // 检查超时 - if ((uint32_t)osKernelGetTickCount() - connect_start > AI_CONNECT_TIMEOUT_MS) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Voice chat connection timeout"); - break; - } - - continue; - } else { - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "Voice chat WebSocket connection established"); - break; - } - } - - if (voice_chat_client == NULL) { - goto voice_chat_cleanup_and_exit; - } - - // 安全分配voice chat接收缓冲区 - char* voice_chat_client_buf = (char *)AI_SAFE_MALLOC(1024 * 4); - if (!voice_chat_client_buf) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to allocate voice chat receive buffer"); - goto voice_chat_cleanup_and_exit; - } - ai_update_memory_usage(1024 * 4); - - // 设置socket超时 - // struct timeval timeout; - // timeout.tv_sec = AI_RECEIVE_TIMEOUT_MS / 1000; - // timeout.tv_usec = (AI_RECEIVE_TIMEOUT_MS % 1000) * 1000; - // lwip_setsockopt(voice_chat_client->sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); - - if (!start_ai_voice_chat()) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to start AI voice chat"); - AI_SAFE_FREE(voice_chat_client_buf); - ai_update_memory_usage(-1024 * 4); - goto voice_chat_cleanup_and_exit; - } - - uint32_t last_activity = (uint32_t)osKernelGetTickCount(); - - while (disconnect_server == 0) { - // 检查超时 - if ((uint32_t)osKernelGetTickCount() - last_activity > AI_RECEIVE_TIMEOUT_MS) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Voice chat receive timeout, reconnecting..."); - break; - } - - memset_s(voice_chat_client_buf, 1024 * 4, 0, 1024 * 4); - int len = -1; - // len = receive_websocket_message_from_queue(&voice_msg_queue, (unsigned char *)voice_chat_client_buf, 1024 * 4, &type, AI_RECEIVE_TIMEOUT_MS); - - if (type == WS_CLOSE || len == -1) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Voice chat WebSocket connection closed"); - break; - } - - if (len > 0) { - last_activity = (uint32_t)osKernelGetTickCount(); - - if (type == WS_BINARY) { - if (bk_websocket_tts_tick == 0) { - set_AIStreamend(0); - bk_websocket_tts_tick = (uint32_t)osKernelGetTickCount(); - } - send_msg_data_into_Ai_msg_queue((uint8_t *)voice_chat_client_buf, len, 200); - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "Voice chat binary data received: %d bytes", len); - msleep(1); - } else if (type == WS_TEXT) { - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "Voice chat text data received: %s", voice_chat_client_buf); - voice_chat_analysis(voice_chat_client_buf); - } - } - - // 定期检查资源状态 - if (retry_count++ % 100 == 0) { - ai_update_memory_usage(0); - } - - msleep(1); // 避免过度占用CPU - } - - bk_websocket_tts_tick = 0; - lwip_close_websocket_client(voice_chat_client); - voice_chat_client = NULL; - - // 释放voice_chat_client_buf内存 - AI_SAFE_FREE(voice_chat_client_buf); - ai_update_memory_usage(-1024 * 4); - } - -voice_chat_cleanup_and_exit: - AI_LOCK(); - is_bk_websocket_tts = 0; - AI_UNLOCK(); - - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "Voice chat WebSocket ended"); - return; -} - - -errcode_t voice_chat_server_init(void) -{ - - if(tts_client!=NULL || is_bk_websocket_tts) - { - return ERRCODE_FAIL; - } - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"tts_server_init !\r\n"); - app_task_definition_t taskcfg={0}; - taskcfg.create_queue_handle = NULL; - taskcfg.attr.name="tts_server_init"; - taskcfg.attr.priority = TASK_PRIORITY_APP; - taskcfg.attr.stack_size = 0x2000; - taskcfg.func = voice_chat_websocket; - taskcfg.use_mem = USE_EXTEND_MEM; - - errcode_t ret=uapi_create_osThread_custom(&taskcfg); - - - if(ret != ERRCODE_SUCC) - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"create cat1_service_file failed %d!\r\n",ret); - return ERRCODE_FAIL; - } - return ERRCODE_SUCC; -} - - -// 资源清理函数 -void ai_cleanup_resources(void) { - AI_LOCK(); - - // 首先关闭所有WebSocket连接 - // if (chat_client) { - // lwip_close_websocket_client(chat_client); - // chat_client = NULL; - // } - // if (asr_client) { - // lwip_close_websocket_client(asr_client); - // asr_client = NULL; - // } - // if (tts_client) { - // lwip_close_websocket_client(tts_client); - // tts_client = NULL; - // } - - // 清理WebSocket消息队列 - // ws_queues_deinit_all(); - - // 清理互斥锁 - if (g_ai_mutex) { - osMutexDelete(g_ai_mutex); - g_ai_mutex = NULL; - } - - // 清理定时器 - // if (g_timer_time_sync) { - // osTimerDelete(g_timer_time_sync); - // g_timer_time_sync = NULL; - // } - - // 重置内存使用统计 - g_ai_memory_usage = 0; - - AI_UNLOCK(); - - // 最后清理TCP管理器 - // tcp_simple_manager_deinit(); - - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "AI resources cleaned up"); -} - -// 初始化AI系统 -errcode_t ai_system_init(void) { - // 初始化TCP管理器 - tcp_manager_result_t tcp_result = tcp_simple_manager_init(); - if (tcp_result != TCP_MANAGER_OK) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to initialize TCP manager: %d", tcp_result); - return ERRCODE_FAIL; - } - - // 创建互斥锁(检查是否已存在) - if (!g_ai_mutex) { - g_ai_mutex = osMutexNew(NULL); - if (!g_ai_mutex) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to create AI mutex"); - // tcp_simple_manager_deinit(); // 清理TCP管理器 - return ERRCODE_FAIL; - } - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "AI mutex created in ai_system_init"); - } else { - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "AI mutex already exists, reusing"); - } - - // 初始化内存使用统计 - g_ai_memory_usage = 0; - - // 初始化WebSocket消息队列 - // if (ws_queues_init_all() != 0) { - // WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to initialize all WebSocket queues"); - // if (g_ai_mutex) { - // osMutexDelete(g_ai_mutex); - // g_ai_mutex = NULL; - // } - // tcp_simple_manager_deinit(); // 清理TCP管理器 - // return ERRCODE_FAIL; - // } - - - WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "AI system initialized successfully"); - return ERRCODE_SUCC; -} - -// 获取内存使用情况 -uint32_t ai_get_memory_usage(void) { - return g_ai_memory_usage; -} - -// 获取内存使用百分比 -uint32_t ai_get_memory_usage_percent(void) { - return (g_ai_memory_usage * 100) / g_ai_max_memory; -} - -// 队列状态检查函数 -bool ai_check_queue_status(void) { - // 这里可以添加队列状态检查逻辑 - // 例如检查消息队列是否已满 - return true; -} - -// 系统健康检查 -bool ai_system_health_check(void) { - uint32_t memory_usage = ai_get_memory_usage_percent(); - - if (memory_usage > 90) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Memory usage too high: %u%%", memory_usage); - return false; - } - - if (!ai_check_queue_status()) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Queue status check failed"); - return false; - } - - return true; -} - -bool uapi_get_server_state_is_end(void) -{ - bool ret=true; - - int osThreadGetState_t= osThreadGetState(chat_taskcfg.task_handle); - - const char *buf= osThreadGetName(chat_taskcfg.task_handle); - - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"chat osThreadGetState_t %d!\r\n",osThreadGetState_t); - if(strcmp("chat_server_init",buf)==0) - { - if(osThreadGetState_t!=osThreadReady && osThreadGetState_t!=osThreadError && osThreadGetState_t !=osThreadInactive) - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"osThreadGetState is %d!\r\n",osThreadGetState_t); - ret=false; - } - } - - osThreadGetState_t= osThreadGetState(tts_taskcfg.task_handle); - - buf= osThreadGetName(tts_taskcfg.task_handle); - - if(strcmp("tts_server_init",buf)==0) - { - if(osThreadGetState_t!=osThreadReady && osThreadGetState_t!=osThreadError && osThreadGetState_t !=osThreadInactive) - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"osThreadGetState is %d!\r\n",osThreadGetState_t); - ret=false; - } - } - osThreadGetState_t= osThreadGetState(asr_taskcfg.task_handle); - - buf= osThreadGetName(asr_taskcfg.task_handle); - - if(strcmp("asr_server_init",buf)==0) - { - if(osThreadGetState_t!=osThreadReady && osThreadGetState_t!=osThreadError && osThreadGetState_t !=osThreadInactive) - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"osThreadGetState is %d!\r\n",osThreadGetState_t); - ret=false; - } - } - - return ret; -} - #ifdef __cplusplus } #endif /* __cplusplus */ - -// /** -// * @brief WebSocket数据接收线程 -// * 负责从所有WebSocket连接接收数据并分发到对应队列 -// */ -// static void ws_receiver_thread(void* arg) -// { -// (void)arg; -// WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "WebSocket receiver thread started"); - -// uint8_t temp_buffer[WS_MESSAGE_MAX_SIZE]; -// WebSocketMessageType type; - -// while (disconnect_server == 0) { -// bool any_activity = false; - -// /* 检查chat客户端 */ -// if (chat_client != NULL) { -// int len = receive_websocket_message(chat_client, temp_buffer, sizeof(temp_buffer), &type); -// if (len > 0) { -// any_activity = true; -// if (ws_queue_put(&chat_msg_queue, temp_buffer, len, type) != 0) { -// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to put chat message to queue"); -// } -// } else if (len < 0) { -// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Chat WebSocket connection error"); -// } -// } - -// /* 检查ASR客户端 */ -// if (asr_client != NULL) { -// int len = receive_websocket_message(asr_client, temp_buffer, sizeof(temp_buffer), &type); -// if (len > 0) { -// any_activity = true; -// if (ws_queue_put(&asr_msg_queue, temp_buffer, len, type) != 0) { -// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to put ASR message to queue"); -// } -// } else if (len < 0) { -// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ASR WebSocket connection error"); -// } -// } - -// /* 检查TTS客户端 */ -// if (tts_client != NULL) { -// int len = receive_websocket_message(tts_client, temp_buffer, sizeof(temp_buffer), &type); -// if (len > 0) { -// any_activity = true; -// if (ws_queue_put(&tts_msg_queue, temp_buffer, len, type) != 0) { -// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to put TTS message to queue"); -// } -// } else if (len < 0) { -// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "TTS WebSocket connection error"); -// } -// } - -// /* 检查voice客户端 */ -// if (voice_chat_client != NULL) { -// int len = receive_websocket_message(voice_chat_client, temp_buffer, sizeof(temp_buffer), &type); -// if (len > 0) { -// any_activity = true; -// if (ws_queue_put(&voice_msg_queue, temp_buffer, len, type) != 0) { -// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to put voice message to queue"); -// } -// } else if (len < 0) { -// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Voice WebSocket connection error"); -// } -// } - -// /* 如果没有任何活动,短暂休眠避免CPU占用过高 */ -// if (!any_activity) { -// osDelay(10); /* 10ms */ -// } -// } - -// WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "WebSocket receiver thread exited"); -// } - diff --git a/code/sdk/zhihui/ai/src/cat1_ws.c b/code/sdk/zhihui/ai/src/cat1_ws.c deleted file mode 100644 index a4aea11f2..000000000 --- a/code/sdk/zhihui/ai/src/cat1_ws.c +++ /dev/null @@ -1,2135 +0,0 @@ -/* 标准C库头文件 */ -#include -#include -#include -#include -#include -#include -#include - -/* 系统头文件 */ -#include - -/* RTOS头文件 */ -#include "cmsis_os2.h" -#include "osal_timer.h" - -/* 网络相关头文件 - 只使用LWIP,避免与系统头文件冲突 */ -#include "lwip/ip.h" -#include "lwip/netif.h" -#include "lwip/init.h" -#include "lwip/inet_chksum.h" -#include "lwip/dns.h" -#include "lwip/err.h" -#include "lwip/sockets.h" -#include "lwip/sys.h" -#include "lwip/netdb.h" -#include "lwip/inet.h" -#include "lwip/apps/sntp.h" - -/* 第三方库头文件 */ -#include "../../../../../open_source/openharmony/third_party/cJSON/cJSON.h" - -/* 项目内部头文件 */ -#include "custom_runtime.h" -#include "custom_utils.h" -#include "bleANCS.h" -#include "broadcast_feature.h" -#include "cat1_ctl.h" -#include "voice_call_volte_sample_wrapper.h" -#include "audio_capture_wrapper.h" -#include "cat1_ws.h" -// #include "dev_authentication.h" -// #include "tcp_simple_manager.h" -#include "websocket_parser_wrapper.h" -#include "bk_websocket.h" - -/* 蓝牙相关头文件 */ -// #include "bletest.h" -// #include "cat1_tcpServer.h" -// #include -// #include - -/* 加密相关头文件 */ -#ifdef CONFIG_CRYPTO_MBEDTLS -#ifdef CONFIG_CRYPTO_MBEDTLS_2 -#include "mbedtls/net.h" -#else -#include "mbedtls/net_sockets.h" -#endif -#include -#include -#include -#include -#include -#endif - -#include "mbedtls/base64.h" -#include "mbedtls/sha1.h" - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -#define TEST_AI_WS 1 - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#define BUFFER_SIZE 1024 -#define WEBSOCKET_KEY_LEN 24 -#define WEBSOCKET_GUID "1234567890123456" - -/* WebSocket日志控制宏 */ -#define WS_LOG_ERROR(fmt, ...) printf("[WS_ERROR] " fmt "\n", ##__VA_ARGS__) -#define WS_LOG_WARNING(fmt, ...) printf("[WS_WARN] " fmt "\n", ##__VA_ARGS__) -#define WS_LOG_INFO(fmt, ...) printf("[WS_INFO] " fmt "\n", ##__VA_ARGS__) -#define WS_LOG_DEBUG(fmt, ...) printf("[WS_DEBUG] " fmt "\n", ##__VA_ARGS__) - -/* WebSocket服务管理器配置 */ -#define WS_MAX_INSTANCES 8 -#define WS_RX_QUEUE_SIZE 64 -#define WS_TX_QUEUE_SIZE 64 -#define WS_MAX_PAYLOAD_SIZE 4096 -#define WS_THREAD_STACK_SIZE 4096 -#define WS_THREAD_PRIORITY 5 - -/* WebSocket实例状态 */ -typedef enum { - WS_INSTANCE_IDLE = 0, - WS_INSTANCE_CONNECTING, - WS_INSTANCE_CONNECTED, - WS_INSTANCE_HANDSHAKING, - WS_INSTANCE_READY, - WS_INSTANCE_ERROR, - WS_INSTANCE_CLOSING, - WS_INSTANCE_CLOSED -} ws_instance_state_t; - -/* WebSocket消息队列项 */ -typedef struct { - uint8_t *data; - size_t length; - WebSocketMessageType type; - uint32_t timestamp; - bool in_use; -} ws_queue_item_t; - -/* WebSocket消息队列 */ -typedef struct { - ws_queue_item_t items[WS_RX_QUEUE_SIZE]; - uint16_t head; - uint16_t tail; - uint16_t count; - osal_mutex_t mutex; - osal_sem_t semaphore; - bool initialized; -} ws_message_queue_t; - -/* WebSocket实例结构体 */ -typedef struct { - uint8_t instance_id; - ws_instance_state_t state; - WebSocketClient *client; - ws_message_queue_t rx_queue; - ws_message_queue_t tx_queue; - osal_thread_t rx_thread; - osal_thread_t tx_thread; - bool rx_thread_running; - bool tx_thread_running; - char instance_name[32]; - uint32_t created_time; - uint32_t last_activity; - uint32_t rx_packets; - uint32_t tx_packets; - uint32_t rx_bytes; - uint32_t tx_bytes; - uint32_t errors; - - /* 回调函数 */ - ws_on_connect_callback on_connect; - ws_on_disconnect_callback on_disconnect; - ws_on_error_callback on_error; - ws_on_message_callback on_message; -} ws_instance_t; - -/* WebSocket服务管理器 */ -typedef struct { - ws_instance_t instances[WS_MAX_INSTANCES]; - uint8_t active_instances; - uint8_t next_instance_id; - osal_mutex_t manager_mutex; - bool initialized; - uint32_t total_rx_packets; - uint32_t total_tx_packets; - uint32_t total_rx_bytes; - uint32_t total_tx_bytes; - uint32_t total_errors; -} WebSocketServerManager; - -/* 全局WebSocket服务管理器 */ -static WebSocketServerManager g_ws_manager = {0}; - -/* 前向声明 */ -static int ws_instance_init(ws_instance_t *instance, uint8_t id, const char *name); -static void ws_instance_deinit(ws_instance_t *instance); -static int ws_instance_connect(ws_instance_t *instance, const char *url, const char *headers, uint8_t use_dns); -static void ws_instance_disconnect(ws_instance_t *instance); -static int ws_instance_send_data(ws_instance_t *instance, uint8_t *data, size_t length, WebSocketMessageType type); -static void ws_instance_rx_thread(void *arg); -static void ws_instance_tx_thread(void *arg); -static int ws_queue_init(ws_message_queue_t *queue, const char *name); -static void ws_queue_deinit(ws_message_queue_t *queue); -static int ws_queue_enqueue(ws_message_queue_t *queue, uint8_t *data, size_t length, WebSocketMessageType type); -static int ws_queue_dequeue(ws_message_queue_t *queue, uint8_t *data, size_t *length, WebSocketMessageType *type); -static void ws_queue_clear(ws_message_queue_t *queue); - - -void * aic_zalloc(unsigned int len) -{ - return malloc(len); -} - - - -// 定义一个结构体来存储解析后的URL部分 -typedef struct { - char *protocol; - char *host; - int port; - char *path; - char *query; - int error; - char *error_message; -} BK_URL; - -// 自定义的 strdup 函数,添加错误处理 -char* bk_strdup(const char* str) { - if (str == NULL) { - return NULL; - } - size_t len = strlen(str) + 1; - char* new_str = (char*)aic_zalloc(len); - if (new_str == NULL) { - return NULL; - } - return (char*)memcpy(new_str, str, len); -} - -// 设置错误信息 -void set_url_error(BK_URL *url, const char *error_msg) { - url->error = 1; - url->error_message = bk_strdup(error_msg); - if (!url->error_message) { - // 若分配失败,输出提示信息 - WS_LOG_ERROR("Memory allocation failed for error message."); - } - -} - -// 解析URL的函数 -BK_URL parse_url(const char *url) { - WS_LOG_WARNING("parse_url url=%s", url); - BK_URL result = {NULL, NULL, 0, NULL, NULL, 0, NULL}; - char *temp_url_t = bk_strdup(url); - char *temp_url = temp_url_t; - if (!temp_url) { - set_url_error(&result, "Failed to allocate memory for temp_url"); - return result; - } - - // 解析协议 - char *colon = strchr(temp_url, ':'); - if (!colon) { - set_url_error(&result, "Invalid URL: No protocol specified"); - free(temp_url_t); - return result; - } - *colon = '\0'; - result.protocol = bk_strdup(temp_url); - if (!result.protocol) { - set_url_error(&result, "Failed to allocate memory for protocol"); - free(temp_url_t); - return result; - } - temp_url = colon + 3; // 跳过 "://" - - // 解析主机和端口 - char *slash = strchr(temp_url, '/'); - if (!slash) { - set_url_error(&result, "Invalid URL: No path specified"); - free(temp_url_t); - return result; - } - *slash = '\0'; - char *at = strchr(temp_url, '@'); - if (at) { - temp_url = at + 1; // 跳过用户名和密码部分 - } - char *colon_port = strchr(temp_url, ':'); - if (colon_port) { - *colon_port = '\0'; - result.host = bk_strdup(temp_url); - if (!result.host) { - set_url_error(&result, "Failed to allocate memory for host"); - free(temp_url_t); - return result; - } - result.port = atoi(colon_port + 1); - if (!result.port) { - set_url_error(&result, "Invalid port number"); - free(temp_url_t); - return result; - } - } else { - result.host = bk_strdup(temp_url); - if (!result.host) { - set_url_error(&result, "Failed to allocate memory for host"); - free(temp_url_t); - return result; - } - if (!strcmp(result.protocol, "http") || !strcmp(result.protocol, "ws")) { - result.port = 80; - } else if (!strcmp(result.protocol, "https")|| !strcmp(result.protocol, "wss")) { - result.port = 443; - } - } - temp_url = slash+1; - //printf("parse_url slash=%s\n",temp_url); - // 解析路径和查询字符串 - char *question = strchr(temp_url, '?'); - if (question) { - *question = '\0'; - result.path = bk_strdup(temp_url); - if (!result.path) { - set_url_error(&result, "Failed to allocate memory for path"); - free(temp_url_t); - return result; - } - result.query = bk_strdup(question + 1); - if (!result.query) { - set_url_error(&result, "Failed to allocate memory for query"); - free(temp_url_t); - return result; - } - } else { - result.path = bk_strdup(temp_url); - if (!result.path) { - set_url_error(&result, "Failed to allocate memory for path"); - free(temp_url_t); - return result; - } - result.query = NULL; - } - - free(temp_url_t); - - return result; -} - -// 释放URL结构体中分配的内存 -void free_url(BK_URL url) { - - // 释放已分配的内存 - - if (url.protocol) { - free(url.protocol); - url.protocol = NULL; - } - if (url.host) { - free(url.host); - url.host = NULL; - } - if (url.path) { - free(url.path); - url.path = NULL; - } - if (url.query) { - free(url.query); - url.query = NULL; - } - if (url.error_message) { - free(url.error_message); - url.error_message = NULL; - } - -} - -// // 仿写 htonll 函数 -static uint64_t htonll(uint64_t hostlonglong) { - // 检查主机是否为大端字节序 - static const union { - uint32_t i; - char c[4]; - } test = {0x01000000}; - if (test.c[0] == 0x01) { - // 主机是大端字节序,不需要转换 - return hostlonglong; - } - - // 将 64 位整数拆分为两个 32 位整数 - uint32_t high = (hostlonglong >> 32) & 0xFFFFFFFF; - uint32_t low = hostlonglong & 0xFFFFFFFF; - - // 分别转换两个 32 位整数的字节序 - high = htonl(high); - low = htonl(low); - - // 合并转换后的两个 32 位整数 - return ((uint64_t)low << 32) | high; -} -// // 仿写 ntohll 函数 -static uint64_t ntohll(uint64_t netlonglong) { - // 将 64 位整数拆分为两个 32 位整数 - uint32_t high = (netlonglong >> 32) & 0xFFFFFFFF; - uint32_t low = netlonglong & 0xFFFFFFFF; - - // 分别转换两个 32 位整数的字节序 - high = ntohl(high); - low = ntohl(low); - - // 合并转换后的两个 32 位整数 - return ((uint64_t)high << 32) | low; -} -// 生成随机的 WebSocket 密钥 -void generate_websocket_key(char *key) { - static const char charset[] = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - for (int i = 0; i < WEBSOCKET_KEY_LEN - 1; ++i) { - key[i] = charset[rand() % (sizeof(charset) - 1)]; - } - key[WEBSOCKET_KEY_LEN] = '\0'; - - unsigned char encoded[WEBSOCKET_KEY_LEN * 2]; - size_t olen; - mbedtls_base64_encode(encoded, sizeof(encoded), &olen, (const unsigned char *)key, strlen(key)); - WS_LOG_WARNING("generate_websocket_key %s %d", encoded, olen); - - if(olen>=(WEBSOCKET_KEY_LEN)) - olen=WEBSOCKET_KEY_LEN; - - strncpy(key, (const char *)encoded, olen); - - key[olen] = '\0'; -} - -// 生成 WebSocket 握手响应密钥 -void generate_accept_key(const char *key, char *accept_key) { - char combined[128]={0}; - strcpy(combined, key); - strcat(combined, WEBSOCKET_GUID); - - unsigned char sha1_result[20]={0}; - mbedtls_sha1((const unsigned char *)combined, strlen(combined), sha1_result); - - unsigned char encoded[64]={0}; - size_t olen; - mbedtls_base64_encode(encoded, sizeof(encoded), &olen, sha1_result, 16);//sizeof(sha1_result)); - strncpy(accept_key, (const char *)encoded, olen); - accept_key[olen] = '\0'; -} - -// 发送 WebSocket 握手请求 -/** - * @brief 发送 WebSocket 握手请求 - * @param client 客户端指针 - * @param headers 头信息 - * @return 成功返回0,失败返回-1 - */ -#if (TEST_AI_WS==0) -int send_handshake(WebSocketClient *client,const char *headers,char *response,int response_len) { - char key[WEBSOCKET_KEY_LEN+1]; - WS_LOG_INFO("send_handshake1"); - - if(client==NULL|| response==NULL || response_len<=0) - { - WS_LOG_ERROR("send_handshake client or response is NULL , or response_len <= 0"); - return -1; - } - - generate_websocket_key(key); - char accept_key[64]; - int ret = 0; - generate_accept_key(key, accept_key); - WS_LOG_INFO("send_handshake2"); - - char *request =( char *)aic_zalloc(BUFFER_SIZE); - - if(request==NULL) - { - WS_LOG_ERROR("request aic_zalloc error"); - return -1; - } - - memset(request,0,BUFFER_SIZE); - - if(headers!= NULL) - { - snprintf(request, BUFFER_SIZE, - "GET /%s?%s HTTP/1.1\r\n" - "Host: %s:%d\r\n" - "Upgrade: websocket\r\n" - "Connection: Upgrade\r\n" - "Sec-WebSocket-Key: %s\r\n" - "Sec-WebSocket-Version: 13\r\n" - "Origin: http://%s:%d\r\n" - "%s\r\n" - "\r\n", - client->path,client->query ,client->host,client->port, accept_key,client->host,client->port,headers); - }else - { - snprintf(request, BUFFER_SIZE, - "GET /%s?%s HTTP/1.1\r\n" - "Host: %s:%d\r\n" - "Upgrade: websocket\r\n" - "Connection: Upgrade\r\n" - "Sec-WebSocket-Key: %s\r\n" - "Sec-WebSocket-Version: 13\r\n" - "Origin: http://%s:%d\r\n" - "\r\n", - client->path,client->query , client->host,client->port, accept_key,client->host,client->port); - } - - WS_LOG_WARNING("send_handshake request:\r\n%s", request); - - // 发送WebSocket握手请求并等待响应 - WS_LOG_INFO("发送WebSocket握手请求..."); - // 发送握手请求 - if (lwip_send(client->sockfd, request, strlen(request), 0) < 0) { - WS_LOG_ERROR("WebSocket握手发送失败"); - return -1; - } - - // 接收握手响应 - ret = lwip_recv(client->sockfd, response, response_len, 0); - - if (ret < 0) { - WS_LOG_ERROR("WebSocket握手发送失败: %d", ret); - ret = -1; - } else { - WS_LOG_INFO("WebSocket握手发送成功,接收到 %d 字节响应", ret); - - // 检查响应内容 - if (ret > 0 && response[0] != '\0') { - WS_LOG_WARNING("WebSocket %s:\r\n", client->path); - WS_LOG_WARNING("WebSocket握手响应:\r\n%s", response); - - // 检查是否包含"HTTP/1.1 101" - if (strstr(response, "HTTP/1.1 101") != NULL) { - WS_LOG_INFO("WebSocket握手成功!"); - client->handshake = 1; - } else { - WS_LOG_ERROR("WebSocket握手失败:响应不是101状态码"); - ret = -1; - } - } else { - WS_LOG_ERROR("WebSocket握手失败:无响应数据"); - ret = -1; - } - } - - free(request); - WS_LOG_INFO("send_handshake3 返回: %d", ret); - return ret; -} -#else - -int send_handshake(WebSocketClient *client,const char *headers) { - char key[WEBSOCKET_KEY_LEN+1]; - printf("send_handshake1"); - generate_websocket_key(key); - char accept_key[64]; - int ret = 0; - generate_accept_key(key, accept_key); - printf("send_handshake2"); - char *request =( char *)aic_zalloc(BUFFER_SIZE); - if(request==NULL) - { - printf("request aic_zalloc erro"); - return -1; - } - - - if(headers!= NULL) - { - snprintf(request, BUFFER_SIZE, - "GET /%s?%s HTTP/1.1\r\n" - "Host: %s:%d\r\n" - "Upgrade: websocket\r\n" - "Connection: Upgrade\r\n" - "Sec-WebSocket-Key: %s\r\n" - "Sec-WebSocket-Version: 13\r\n" - "Origin: http://%s:%d\r\n" - "%s\r\n" - "\r\n", - client->path,client->query ,client->host,client->port, accept_key,client->host,client->port,headers); - }else - { - snprintf(request, BUFFER_SIZE, - "GET /%s?%s HTTP/1.1\r\n" - "Host: %s:%d\r\n" - "Upgrade: websocket\r\n" - "Connection: Upgrade\r\n" - "Sec-WebSocket-Key: %s\r\n" - "Sec-WebSocket-Version: 13\r\n" - "Origin: http://%s:%d\r\n" - "\r\n", - client->path,client->query , client->host,client->port, accept_key,client->host,client->port); - } - - printf("send_handshake request:\r\n%s",request); - if (lwip_send(client->sockfd, request, strlen(request), 0) < 0) { - printf("lwip_send handshake failed"); - ret = -1; - } - - free(request); - printf("send_handshake3 %d",ret); - return ret; -} -#endif - - -// 发送 WebSocket 消息 -int send_websocket_message(WebSocketClient *client, uint8_t *message, size_t message_len, WebSocketMessageType type) { - - int ret_tcp_send_data=0; - if(client==NULL||message==NULL ) - { - WS_LOG_ERROR("send_websocket_message client or message is NULL %s %s", client, message); - return -1; - } - - // 检查连接状态 - if (client->sockfd < 0) { - WS_LOG_ERROR("send_websocket_message: socket is invalid"); - return -1; - } - - if (client->handshake == 0) { - WS_LOG_ERROR("send_websocket_message: WebSocket handshake not completed"); - return -1; - } - uint8_t* frame=( uint8_t *)aic_zalloc(message_len + 50); - int offset = 0; - int ret = -1; - if(!frame) - { - WS_LOG_ERROR("send_websocket_message zalloc error"); - return -1; - } - // 第一个字节:FIN = 1, 设置 opcode - frame[offset++] = 0x80 | type; - - // 第二个字节:设置掩码位和负载长度 - if (message_len < 126) { - - frame[offset++] = 0x80 | message_len; - - } - else if (message_len < 65536) - { - frame[offset++] = 0x80 | 126; - uint16_t len = htons((uint16_t)message_len); - memcpy(frame + offset, &len, 2); - offset += 2; - } else { - frame[offset++] = 0x80 | 127; - uint64_t len = htonll(message_len); - memcpy(frame + offset, &len, 8); - offset += 8; - } - - // 生成掩码密钥 - unsigned char masking_key[4]; - for (int i = 0; i < 4; ++i) { - masking_key[i] = (unsigned char)rand(); - } - memcpy(frame + offset, masking_key, 4); - offset += 4; - - // 掩码处理消息 - for (size_t i = 0; i < message_len; ++i) { - frame[offset + i] = message[i] ^ masking_key[i % 4]; - } - offset += message_len; - - // 发送帧 - WS_LOG_INFO("offset %d", offset); - - // 再次检查socket有效性 - if (client->sockfd < 0) { - WS_LOG_ERROR("send_websocket_message: socket became invalid before send"); - ret = -1; - goto send_websocket_message_ERROR; - } - - - - ret_tcp_send_data = lwip_send(client->sockfd, frame, offset, 0); - if (ret_tcp_send_data < 0) { - WS_LOG_ERROR("lwip_send message failed %d", ret_tcp_send_data); - ret = -1; - goto send_websocket_message_ERROR; - } - ret = 0; - -send_websocket_message_ERROR: - msleep(message_len/1000+1); - free(frame); - return ret; -} - -void _tcp_on_connect_cb(void* handle, bool success) -{ - WS_LOG_INFO("tcp_on_connect_cb %p %d", handle, success); - #if TEST_AI_WS - return; - #else - if (success) { - // 连接成功,查找对应的WebSocket客户端 - extern WebSocketClient * get_ws_client_by_sockfd(int sockfd); - WebSocketClient * client = get_ws_client_by_sockfd((int)(intptr_t)handle); - if (client != NULL) { - WS_LOG_INFO("连接成功,开始WebSocket握手"); - - // 等待连接稳定后再发送握手 - WS_LOG_INFO("等待TCP连接稳定..."); - msleep(100); // 等待100ms让连接稳定 - - // 发送WebSocket握手请求 - char *buffer = (char *)aic_zalloc(BUFFER_SIZE); - if (buffer != NULL) { - int retry_count = 0; - const int max_retries = 3; - - while (retry_count < max_retries) { - WS_LOG_INFO("WebSocket握手尝试 #%d...", retry_count + 1); - - int bytes_received = send_handshake(client, NULL, buffer, BUFFER_SIZE); - if (bytes_received < 0) { - retry_count++; - if (retry_count < max_retries) { - WS_LOG_INFO("WebSocket握手失败,等待重试..."); - msleep(500); // 等待500ms后重试 - continue; - } else { - WS_LOG_INFO("WebSocket握手失败,已达到最大重试次数"); - // 安全关闭连接 - if (client->sockfd >= 0) { - lwip_close(client->sockfd); - client->sockfd = -1; - } - client->handshake = 0; - return; - } - } else { - WS_LOG_INFO("WebSocket握手发送成功"); - client->handshake = 1; - break; - } - } - - free(buffer); - } - } else { - WS_LOG_INFO("未找到对应的WebSocket客户端"); - } - - } else { - WS_LOG_INFO("TCP连接失败"); - } - #endif -} -void _tcp_on_disconnect_cb(void* handle) -{ - WS_LOG_INFO("tcp_on_disconnect_cb %d", handle); - - // 查找对应的WebSocket客户端并清理状态 - extern WebSocketClient * get_ws_client_by_sockfd(int sockfd); - WebSocketClient * client = get_ws_client_by_sockfd((int)(intptr_t)handle); - if(client != NULL) - { - WS_LOG_INFO("Cleaning up WebSocket client for disconnected handle %d", handle); - - if (client->packet_callback) { - client->packet_callback((uint8_t*)("close"), strlen("close"), (WebSocketMessageType)WS_CLOSE); - } - - // 清理客户端状态 - client->handshake = 0; - client->sockfd = -1; - - // 重置解析器状态 - if (client->parser) { - ws_parser_reset(client->parser); - } - - - } -} -void _tcp_on_data_received_cb(void* handle, uint8_t* data, uint16_t datalen) -{ - // WS_LOG_INFO("tcp_on_data_received_cb 0x%0X %d", handle, datalen); - - // 参数验证 - if (data == NULL || datalen == 0) { - WS_LOG_ERROR("Invalid data parameters: data=%p, datalen=%d", data, datalen); - return; - } - - extern WebSocketClient * get_ws_client_by_sockfd(int sockfd); - WebSocketClient * client = get_ws_client_by_sockfd((int)(intptr_t)handle); - if(client==NULL) - { - WS_LOG_ERROR("get_ws_client failed for handle 0x%0X", handle); - return; - } - - // 检查客户端状态 - if (client->sockfd < 0 || client->parser == NULL) { - WS_LOG_ERROR("Client in invalid state: sockfd=%d, parser=%p", client->sockfd, client->parser); - return; - } - - // 使用WebSocket解析器处理数据 - int processed = ws_process_received_data(client, data, datalen); - if (processed < 0) { - WS_LOG_ERROR("WebSocket packet processing failed"); - // 解析失败时重置解析器,避免状态不一致 - if (client->parser) { - ws_parser_reset(client->parser); - } - return; - } - - // WS_LOG_WARNING("Processed %d bytes %s %s of WebSocket data", processed, client->host, client->path); -} -#if TEST_AI_WS - -// struct cat1_ws_init_t -// { -// osSemaphoreId_t dns_done_sem; -// ip_addr_t ip; -// }; -// static void dns_found(const char* hostname, const ip_addr_t *ipaddr, void *arg) -// { -// struct cat1_ws_init_t * ip_sem=(struct cat1_ws_init_t *)arg; - -// printf("hostname %s ipaddr%d \r\n",hostname,ipaddr->u_addr.ip4); -// memcpy(&(ip_sem->ip),ipaddr,sizeof(ipaddr)); -// (void)osSemaphoreRelease(ip_sem->dns_done_sem); -// } - - -// 初始化 WebSocket 客户端 -WebSocketClient *init_websocket_client(const char *url, const char *headers,uint8_t use_dns) { - //printf("init_websocket_client 1\n"); - char *buffer = NULL; - WebSocketClient *client =NULL; - BK_URL parsed_url = {0}; - struct sockaddr_in server_addr; - ssize_t bytes_received; - struct in_addr in_addr_t={0}; - ip_addr_t ip; - // struct cat1_ws_init_t ip_sem={0}; - int socket_opt = 1; - int sockfd = -1; - - err_t ret=0; - - parsed_url = parse_url(url);//解析url - - if (parsed_url.error) { - printf("init_websocket_client Error: %s", parsed_url.error_message); - goto client_ERROR; - } else { - printf("Protocol: %s", parsed_url.protocol); - printf("Host: %s\n", parsed_url.host); - printf("Port: %d\n", parsed_url.port); - printf("Path: %s\n", parsed_url.path); - printf("Query: %s\n", parsed_url.query); - - } - client = (WebSocketClient *)aic_zalloc(sizeof(WebSocketClient)) ; - if(!client) - { - printf("init_websocket_client alloc error"); - goto client_ERROR; - } - - - - sockfd = lwip_socket(AF_INET, SOCK_STREAM, 0); - client->sockfd=sockfd; - - if (sockfd < 0) { - - printf("socket creation failed %d errno: %d\r\n",sockfd,errno); - goto client_ERROR; - } - lwip_setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (const void*)&socket_opt, sizeof(socket_opt)); - lwip_setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,(const void*)&socket_opt, sizeof(socket_opt)); - // printf("init_websocket_client 3\n"); - if(use_dns) - { - // ip_sem.dns_done_sem= osSemaphoreNew(1, 0, NULL); - // if(ip_sem.dns_done_sem==0) - // { - // goto client_ERROR; - // } - - // ret =dns_gethostbyname(parsed_url.host,&ip,dns_found,&ip_sem); - // // if(ERR_OK==ret) - // { - // osSemaphoreAcquire(ip_sem.dns_done_sem, 1000*30); - // } - // osSemaphoreDelete(ip_sem.dns_done_sem); - // printf("dns_gethostbyname %d\r\n",ip_sem.ip.u_addr.ip4); - // memcpy(&server_addr.sin_addr.s_addr, &ip_sem.ip.u_addr.ip4, 4); - - } - else - { - inet_pton(AF_INET, parsed_url.host, &server_addr.sin_addr); - } - - - - server_addr.sin_family = AF_INET; - - server_addr.sin_port = htons(parsed_url.port); - - - // - - if (lwip_connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { - printf("init_websocket_client connection failed\r\n"); - msleep(100); - lwip_close(sockfd); - - goto client_ERROR; - - } - printf("socket creation succ\r\n"); - - - strcpy(client->host, parsed_url.host); - strcpy(client->path, parsed_url.path); - strcpy(client->query, parsed_url.query); - client->port = parsed_url.port; - - - if (send_handshake(client,headers) < 0) { //发送头 - printf("socket send_handshake client_ERROR"); - - lwip_close(sockfd); - goto client_ERROR; - } - - buffer=(char *)aic_zalloc(BUFFER_SIZE); - - if(buffer==NULL) - { - printf("aic_zalloc(BUFFER_SIZE);null "); - goto client_ERROR; - } - printf("lwip_recv: ??? "); - - bytes_received = lwip_recv(sockfd, buffer, BUFFER_SIZE - 1, 0); - - printf("lwip_recv; "); - - if (bytes_received > 0) { - buffer[bytes_received] = '\0'; - printf("Received handshake response:\n%s\n", buffer); - } - - - - if(strstr(buffer,"HTTP/1.1 101") == NULL) - { - - goto client_ERROR; - } - printf("init_websocket_client end 1"); - - - // 直接使用socket,不需要TCP管理器 - client->sockfd = sockfd; - strcpy(client->host, parsed_url.host); - strcpy(client->path, parsed_url.path); - strcpy(client->query, parsed_url.query); - client->port = parsed_url.port; - - client->parser = ws_parser_init(); - - free(buffer); - free_url(parsed_url); - - client->handshake=1; - - if (!client->parser) { - WS_LOG_ERROR("init_websocket_client Error: Failed to initialize WebSocket parser"); - lwip_close(sockfd); - goto client_ERROR; - } - - return client; -client_ERROR: - WS_LOG_ERROR("init_websocket_client Error: client_ERROR"); - if(buffer) { - free(buffer); - buffer=NULL; - } - if(client){ - free(client); - client = NULL; - } - free_url(parsed_url); - - return NULL; -} -#else -// 初始化 WebSocket 客户端 -WebSocketClient *init_websocket_client(const char *url, const char *headers,uint8_t use_dns) { - //printf("init_websocket_client 1\n"); - WebSocketClient *client = NULL; - BK_URL parsed_url = {0}; - int socket_opt2 = 1; - err_t ret = 0; - - if (!url) { - WS_LOG_ERROR("init_websocket_client Error: URL is NULL"); - return NULL; - } - - parsed_url = parse_url(url);//解析url - - if (parsed_url.error) { - WS_LOG_ERROR("init_websocket_client Error: %s", parsed_url.error_message); - goto client_ERROR; - } else { - WS_LOG_INFO("Protocol: %s", parsed_url.protocol); - WS_LOG_INFO("Host: %s", parsed_url.host); - WS_LOG_INFO("Port: %d", parsed_url.port); - WS_LOG_INFO("Path: %s", parsed_url.path); - WS_LOG_INFO("Query: %s", parsed_url.query); - } - - client = (WebSocketClient *)aic_zalloc(sizeof(WebSocketClient)) ; - memset_s(client,sizeof(WebSocketClient),0,sizeof(WebSocketClient)); - if(!client) - { - WS_LOG_ERROR("init_websocket_client alloc error: Failed to allocate memory for WebSocketClient"); - goto client_ERROR; - } - - // 创建socket并连接 - int sockfd = lwip_socket(AF_INET, SOCK_STREAM, 0); - if (sockfd < 0) { - WS_LOG_ERROR("init_websocket_client Error: socket creation failed"); - goto client_ERROR; - } - - // 设置socket选项 - int socket_opt3 = 1; - lwip_setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (const void*)&socket_opt3, sizeof(socket_opt3)); - lwip_setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void*)&socket_opt3, sizeof(socket_opt3)); - - // 解析主机地址 - struct sockaddr_in server_addr; - memset(&server_addr, 0, sizeof(server_addr)); - server_addr.sin_family = AF_INET; - server_addr.sin_port = htons(parsed_url.port); - - if (use_dns) { - // 使用DNS解析 - struct hostent *host = lwip_gethostbyname(parsed_url.host); - if (host == NULL) { - WS_LOG_ERROR("init_websocket_client Error: DNS resolution failed for %s", parsed_url.host); - lwip_close(sockfd); - goto client_ERROR; - } - server_addr.sin_addr.s_addr = *(uint32_t*)host->h_addr_list[0]; - } else { - // 直接使用IP地址 - server_addr.sin_addr.s_addr = lwip_inet_addr(parsed_url.host); - } - - // 连接服务器 - if (lwip_connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { - WS_LOG_ERROR("init_websocket_client Error: connection failed for %s:%d", parsed_url.host, parsed_url.port); - lwip_close(sockfd); - goto client_ERROR; - } - - client->sockfd = sockfd; - WS_LOG_INFO("socket creation and connection success"); - - strcpy(client->host, parsed_url.host); - strcpy(client->path, parsed_url.path); - strcpy(client->query, parsed_url.query); - client->port = parsed_url.port; - - /* 初始化WebSocket解析器 */ - client->parser = ws_parser_init(); - if (!client->parser) { - WS_LOG_ERROR("init_websocket_client Error: Failed to initialize WebSocket parser"); - goto client_ERROR; - } - - /* 握手将在连接成功回调中发送,这里不再立即发送 */ - WS_LOG_INFO("WebSocket客户端初始化完成,等待TCP连接建立"); - -// if(strstr(buffer,"HTTP/1.1 101") == NULL) - // { - - // goto client_ERROR; - // } - // printf("init_websocket_client end 1"); - // free(buffer); - free_url(parsed_url); - - WS_LOG_INFO("init_websocket_client end 2"); - return client; - -client_ERROR: - WS_LOG_ERROR("init_websocket_client Error: Initialization failed, cleaning up..."); - if(client){ - free(client); - client = NULL; - } - free_url(parsed_url); - - return NULL; -} -#endif - -// 关闭 WebSocket 客户端 -void lwip_close_websocket_client(WebSocketClient *client) { - if(client) - { - //移植建议: 此处调用 tcp_manager_close 进行关闭, 不必使用 lwip_close - - WS_LOG_INFO("lwip_close_websocket_client %s %s", client->host, client->path); - - // 先关闭TCP连接,避免数据继续流入 - if (client->sockfd >= 0) { - lwip_close(client->sockfd); - client->sockfd = -1; - } - - // 释放WebSocket解析器 - if (client->parser) { - ws_parser_destroy(client->parser); - client->parser = NULL; - } - - // 清理客户端状态 - client->handshake = 0; - client->packet_callback = NULL; - - free(client); - } -} - -// 旧的WebSocket解析器函数已移除,现在使用新的解析器 - -/** - * @brief 设置WebSocket数据包回调函数 - */ -void ws_set_packet_callback(WebSocketClient *client, on_ws_packet_callback callback) -{ - if (!client) return; - - client->packet_callback = callback; -} - -/** - * @brief 处理接收到的原始数据 - */ -int ws_process_received_data(WebSocketClient *client, const uint8_t *data, size_t len) -{ - if (!client || !data || len == 0 || !client->parser) { - WS_LOG_ERROR("ws_process_received_data: invalid parameters - client=%p, data=%p, len=%zu, parser=%p", - client, data, len, client->parser); - return -1; - } - - // ========== 新增:打印收到的所有原始数据 ========== - // printf("\n=== WebSocket Raw Data Received ===\n"); - // printf("Client: %s:%s\n", client->host, client->path); - // WS_LOG_WARNING("Total bytes: %zu", len); - // printf("Raw data (hex): "); - // // 打印所有数据的十六进制表示 - // for (size_t i = 0; i < len; i++) { - // printf("%02X ", data[i]); - // // 每16字节换行,提高可读性 - // if ((i + 1) % 16 == 0) { - // printf("\n "); - // } - // } - // printf("\n"); - - // // 打印ASCII表示(可打印字符) - // printf("Raw data (ASCII): "); - // for (size_t i = 0; i < len; i++) { - // if (data[i] >= 32 && data[i] <= 126) { - // printf("%c", data[i]); - // } else if (data[i] == '\r') { - // printf("\\r"); - // } else if (data[i] == '\n') { - // printf("\\n"); - // } else if (data[i] == '\t') { - // printf("\\t"); - // } else { - // printf("."); - // } - // } - // printf("\n"); - - // 打印数据统计信息 - // printf("Data analysis:\n"); - // printf(" - Total bytes: %zu\n", len); - // printf(" - First byte: 0x%02X\n", len > 0 ? data[0] : 0); - // printf(" - Last byte: 0x%02X\n", len > 0 ? data[len-1] : 0); - - // 检查是否可能是WebSocket帧头 - // if (len >= 2) { - // uint8_t first_byte = data[0]; - // uint8_t second_byte = data[1]; - // uint8_t fin = (first_byte >> 7) & 0x1; - // uint8_t rsv = (first_byte >> 4) & 0x7; - // uint8_t opcode = first_byte & 0x0F; - // uint8_t mask = (second_byte >> 7) & 0x1; - // uint8_t payload_len = second_byte & 0x7F; - - // printf(" - Potential WebSocket frame:\n"); - // printf(" * FIN: %d\n", fin); - // printf(" * RSV: %d%d%d\n", (rsv >> 2) & 1, (rsv >> 1) & 1, rsv & 1); - // printf(" * Opcode: 0x%02X\n", opcode); - // printf(" * Mask: %d\n", mask); - // printf(" * Payload length: %d\n", payload_len); - - // // 验证opcode是否有效 - // bool valid_opcode = ((opcode <= 0x2) || (opcode >= 0x8 && opcode <= 0xA)); - // printf(" * Valid opcode: %s\n", valid_opcode ? "YES" : "NO"); - - // // 验证RSV位 - // bool valid_rsv = (rsv == 0); - // printf(" * Valid RSV: %s\n", valid_rsv ? "YES" : "NO"); - // } - // WS_LOG_WARNING("====================================="); - - ws_parser_t* parser = client->parser; - int total_processed = 0; - const uint8_t* current_data = data; - size_t remaining_len = len; - - WS_LOG_DEBUG("ws_process_received_data: processing %zu bytes for client %s:%s", - len, client->host, client->path); - - // 循环处理数据,支持连续帧 - size_t original_len = remaining_len; - size_t loop_count = 0; - const size_t max_loops = original_len + 10; // 防止无限循环 - - while (remaining_len > 0 && loop_count < max_loops) { - loop_count++; - WS_LOG_DEBUG("ws_process_received_data: processing remaining %d bytes, current offset: %zu, loop %d", - remaining_len, current_data - data, loop_count); - - // 在解析前打印当前解析器状态 - WS_LOG_DEBUG("ws_process_received_data: parser state before parsing:"); - // ws_parser_debug_state(parser); - - // 使用新的解析器处理当前数据段 - int result = ws_parse_stream(parser, current_data, remaining_len); - - if(result <= 0) - { - WS_LOG_ERROR("ws_process_received_data: parsing error, reset parser,find next frame. Remaining: %zu bytes", remaining_len); - ws_parser_reset(parser); - // 不要立即返回,尝试跳过错误数据继续处理 - current_data++; - remaining_len--; - continue; - } - - // 更新处理进度 - total_processed += result; - current_data += result; - remaining_len -= result; - - // 在解析后打印解析器状态 - WS_LOG_DEBUG("ws_process_received_data: parser state after parsing:"); - // ws_parser_debug_state(parser); - ws_parse_state_t ws_stat = ws_parser_get_state(parser); - - if (ws_stat == WS_STATE_COMPLETE) { - // 解析完成一个完整的WebSocket帧 - const ws_header_t* header = ws_get_header(parser); - const uint8_t* payload = ws_get_payload(parser); - size_t payload_len = ws_get_payload_length(parser); - - WS_LOG_DEBUG("ws_process_received_data: frame complete, total bytes: %d, payload bytes: %zu %s", - result, payload_len,client->path); - - if (header && payload) { - WS_LOG_DEBUG("WebSocket frame received: opcode=0x%02X, fin=%d, payload_len=%llu", - header->opcode, header->fin, header->payload_len); - // printf("payload = %s\n",payload); - - // 打印载荷数据的前几个字节(用于调试) - // if (payload_len > 0) { - // printf("Payload data (first 32 bytes): "); - // for (size_t i = 0; i < payload_len && i < 32; i++) { - // printf("%02X ", payload[i]); - // } - // if (payload_len > 32) printf("..."); - // printf("\n"); - // } - - // 根据操作码处理不同类型的消息 - switch (header->opcode) { - case WS_PING: - WS_LOG_WARNING("Received WebSocket PING"); - // TODO: 自动回复PONG - send_websocket_message(client,( uint8_t *)"PONG",strlen("PONG"),WS_PONG); - break; - - case WS_PONG: - WS_LOG_WARNING("Received WebSocket PONG"); - break; - - case WS_CLOSE: - WS_LOG_WARNING("Received WebSocket CLOSE"); - // 调用回调函数处理关闭帧 - if (client->packet_callback) { - client->packet_callback((uint8_t*)payload, payload_len, (WebSocketMessageType)WS_CLOSE); - } - break; - - case WS_TEXT: - case WS_BINARY: - case WS_CONTINUATION: - // 调用回调函数处理数据包 - if (client->packet_callback) { - WebSocketMessageType ws_type = (header->opcode == WS_TEXT) ? (WebSocketMessageType)WS_TEXT : - (header->opcode == WS_BINARY) ? (WebSocketMessageType)WS_BINARY : (WebSocketMessageType)WS_CONT; - client->packet_callback((uint8_t*)payload, payload_len, ws_type); - } - - break; - - default: - WS_LOG_DEBUG("Unknown WebSocket opcode: 0x%02X", header->opcode); - break; - } - - // 重要:在数据处理完成后再重置解析器状态 - // 这样可以确保数据被正确处理后再清理状态 - WS_LOG_DEBUG("ws_process_received_data: data processing complete, resetting parser"); - ws_parser_reset(parser); - - - - WS_LOG_DEBUG("ws_process_received_data: frame processed, moving to next frame. Remaining: %d bytes", remaining_len); - - } else { - WS_LOG_DEBUG("ws_process_received_data: invalid payload data - header=%p, payload=%p, payload_len=%zu", - header, payload, payload_len); - return -1; - } - - } else if (ws_stat == WS_STATE_ERROR) { - // 解析错误,重置parser - ws_parser_reset(parser); - - WS_LOG_ERROR("ws_process_received_data: ws_stat == WS_STATE_ERROR parsing error, find next frame. Remaining: %zu bytes", remaining_len); - - } else { - // 数据不完整,继续等待更多数据 - WS_LOG_DEBUG("ws_process_received_data: frame incomplete, waiting for more data"); - } - } - - // 检查是否达到循环限制 - if (loop_count >= max_loops) { - WS_LOG_ERROR("ws_process_received_data: reached maximum loop count %zu, forcing exit", max_loops); - ws_parser_reset(parser); - } - - WS_LOG_DEBUG("ws_process_received_data: total processed: %d bytes", total_processed); - return total_processed; -} - -// 多余的函数已移除,现在只使用一个统一的解析器 - -/* ========== WebSocket 多实例服务管理器实现 ========== */ - -/** - * 初始化 WebSocket 服务管理器 - */ -int ws_manager_init(void) -{ - if (g_ws_manager.initialized) { - WS_LOG_WARNING("WebSocket manager already initialized"); - return 0; - } - - memset(&g_ws_manager, 0, sizeof(WebSocketServerManager)); - - // 创建管理器互斥锁 - if (osal_mutex_create(&g_ws_manager.manager_mutex) != OSAL_SUCCESS) { - WS_LOG_ERROR("Failed to create manager mutex"); - return -1; - } - - g_ws_manager.initialized = true; - g_ws_manager.next_instance_id = 1; - - WS_LOG_INFO("WebSocket manager initialized successfully"); - return 0; -} - -/** - * 销毁 WebSocket 服务管理器 - */ -void ws_manager_deinit(void) -{ - if (!g_ws_manager.initialized) { - return; - } - - osal_mutex_lock(&g_ws_manager.manager_mutex); - - // 关闭所有活跃实例 - for (int i = 0; i < WS_MAX_INSTANCES; i++) { - if (g_ws_manager.instances[i].state != WS_INSTANCE_IDLE) { - ws_instance_deinit(&g_ws_manager.instances[i]); - } - } - - g_ws_manager.initialized = false; - osal_mutex_unlock(&g_ws_manager.manager_mutex); - osal_mutex_destroy(&g_ws_manager.manager_mutex); - - WS_LOG_INFO("WebSocket manager deinitialized"); -} - -/** - * 创建新的 WebSocket 实例 - */ -int ws_manager_create_instance(const char *name, const char *url, const char *headers, uint8_t use_dns) -{ - if (!g_ws_manager.initialized) { - if (ws_manager_init() != 0) { - return -1; - } - } - - osal_mutex_lock(&g_ws_manager.manager_mutex); - - // 查找空闲实例槽 - int instance_idx = -1; - for (int i = 0; i < WS_MAX_INSTANCES; i++) { - if (g_ws_manager.instances[i].state == WS_INSTANCE_IDLE) { - instance_idx = i; - break; - } - } - - if (instance_idx == -1) { - WS_LOG_ERROR("No available instance slots (max: %d)", WS_MAX_INSTANCES); - osal_mutex_unlock(&g_ws_manager.manager_mutex); - return -1; - } - - ws_instance_t *instance = &g_ws_manager.instances[instance_idx]; - uint8_t instance_id = g_ws_manager.next_instance_id++; - - // 初始化实例 - if (ws_instance_init(instance, instance_id, name) != 0) { - WS_LOG_ERROR("Failed to initialize instance %d", instance_id); - osal_mutex_unlock(&g_ws_manager.manager_mutex); - return -1; - } - - // 连接到服务器 - if (ws_instance_connect(instance, url, headers, use_dns) != 0) { - WS_LOG_ERROR("Failed to connect instance %d to %s", instance_id, url); - ws_instance_deinit(instance); - osal_mutex_unlock(&g_ws_manager.manager_mutex); - return -1; - } - - g_ws_manager.active_instances++; - osal_mutex_unlock(&g_ws_manager.manager_mutex); - - WS_LOG_INFO("Created WebSocket instance %d (%s) connected to %s", instance_id, name, url); - return instance_id; -} - -/** - * 销毁 WebSocket 实例 - */ -int ws_manager_destroy_instance(int instance_id) -{ - if (!g_ws_manager.initialized) { - return -1; - } - - osal_mutex_lock(&g_ws_manager.manager_mutex); - - ws_instance_t *instance = NULL; - for (int i = 0; i < WS_MAX_INSTANCES; i++) { - if (g_ws_manager.instances[i].instance_id == instance_id && - g_ws_manager.instances[i].state != WS_INSTANCE_IDLE) { - instance = &g_ws_manager.instances[i]; - break; - } - } - - if (!instance) { - WS_LOG_ERROR("Instance %d not found", instance_id); - osal_mutex_unlock(&g_ws_manager.manager_mutex); - return -1; - } - - ws_instance_deinit(instance); - g_ws_manager.active_instances--; - - osal_mutex_unlock(&g_ws_manager.manager_mutex); - - WS_LOG_INFO("Destroyed WebSocket instance %d", instance_id); - return 0; -} - -/** - * 发送数据到指定实例 - */ -int ws_manager_send_data(int instance_id, uint8_t *data, size_t length, WebSocketMessageType type) -{ - if (!g_ws_manager.initialized) { - return -1; - } - - osal_mutex_lock(&g_ws_manager.manager_mutex); - - ws_instance_t *instance = NULL; - for (int i = 0; i < WS_MAX_INSTANCES; i++) { - if (g_ws_manager.instances[i].instance_id == instance_id && - g_ws_manager.instances[i].state == WS_INSTANCE_READY) { - instance = &g_ws_manager.instances[i]; - break; - } - } - - if (!instance) { - WS_LOG_ERROR("Instance %d not found or not ready", instance_id); - osal_mutex_unlock(&g_ws_manager.manager_mutex); - return -1; - } - - int result = ws_instance_send_data(instance, data, length, type); - osal_mutex_unlock(&g_ws_manager.manager_mutex); - - return result; -} - -/** - * 获取实例状态 - */ -ws_instance_state_t ws_manager_get_instance_state(int instance_id) -{ - if (!g_ws_manager.initialized) { - return WS_INSTANCE_ERROR; - } - - osal_mutex_lock(&g_ws_manager.manager_mutex); - - for (int i = 0; i < WS_MAX_INSTANCES; i++) { - if (g_ws_manager.instances[i].instance_id == instance_id) { - ws_instance_state_t state = g_ws_manager.instances[i].state; - osal_mutex_unlock(&g_ws_manager.manager_mutex); - return state; - } - } - - osal_mutex_unlock(&g_ws_manager.manager_mutex); - return WS_INSTANCE_ERROR; -} - -/** - * 注册 WebSocket 实例回调函数 - */ -int ws_manager_register_callbacks(int instance_id, - ws_on_connect_callback on_connect, - ws_on_disconnect_callback on_disconnect, - ws_on_error_callback on_error, - ws_on_message_callback on_message) -{ - if (!g_ws_manager.initialized) { - return -1; - } - - osal_mutex_lock(&g_ws_manager.manager_mutex); - - ws_instance_t *instance = NULL; - for (int i = 0; i < WS_MAX_INSTANCES; i++) { - if (g_ws_manager.instances[i].instance_id == instance_id && - g_ws_manager.instances[i].state != WS_INSTANCE_IDLE) { - instance = &g_ws_manager.instances[i]; - break; - } - } - - if (!instance) { - WS_LOG_ERROR("Instance %d not found for callback registration", instance_id); - osal_mutex_unlock(&g_ws_manager.manager_mutex); - return -1; - } - - // 注册回调函数 - instance->on_connect = on_connect; - instance->on_disconnect = on_disconnect; - instance->on_error = on_error; - instance->on_message = on_message; - - osal_mutex_unlock(&g_ws_manager.manager_mutex); - - WS_LOG_INFO("Callbacks registered for instance %d", instance_id); - return 0; -} - -/** - * WebSocket 数据包回调包装函数 - */ -static void ws_instance_packet_callback_wrapper(uint8_t *data, size_t length, WebSocketMessageType type) -{ - // 查找对应的实例 - // 注意:这里需要通过某种方式识别是哪个实例的回调 - // 由于 packet_callback 不提供实例信息,我们需要另一种方法 - - // 临时解决方案:遍历所有实例,找到匹配的客户端 - if (!g_ws_manager.initialized) { - return; - } - - osal_mutex_lock(&g_ws_manager.manager_mutex); - - for (int i = 0; i < WS_MAX_INSTANCES; i++) { - ws_instance_t *instance = &g_ws_manager.instances[i]; - if (instance->state == WS_INSTANCE_READY && instance->client) { - // 这里需要一个更好的方法来识别是哪个实例 - // 暂时使用这种方式,后续可以优化 - if (instance->on_message) { - instance->on_message(instance->instance_id, data, length, type); - } - break; // 假设只有一个实例会匹配 - } - } - - osal_mutex_unlock(&g_ws_manager.manager_mutex); -} - -/** - * 初始化 WebSocket 实例 - */ -static int ws_instance_init(ws_instance_t *instance, uint8_t id, const char *name) -{ - if (!instance || !name) { - return -1; - } - - memset(instance, 0, sizeof(ws_instance_t)); - - instance->instance_id = id; - instance->state = WS_INSTANCE_IDLE; - strncpy(instance->instance_name, name, sizeof(instance->instance_name) - 1); - instance->created_time = osal_get_current_time(); - instance->last_activity = instance->created_time; - - // 初始化接收队列 - if (ws_queue_init(&instance->rx_queue, "rx") != 0) { - WS_LOG_ERROR("Failed to initialize RX queue for instance %d", id); - return -1; - } - - // 初始化发送队列 - if (ws_queue_init(&instance->tx_queue, "tx") != 0) { - WS_LOG_ERROR("Failed to initialize TX queue for instance %d", id); - ws_queue_deinit(&instance->rx_queue); - return -1; - } - - WS_LOG_DEBUG("Instance %d (%s) initialized", id, name); - return 0; -} - -/** - * 销毁 WebSocket 实例 - */ -static void ws_instance_deinit(ws_instance_t *instance) -{ - if (!instance || instance->state == WS_INSTANCE_IDLE) { - return; - } - - WS_LOG_INFO("Deinitializing instance %d (%s)", instance->instance_id, instance->instance_name); - - // 停止线程 - instance->rx_thread_running = false; - instance->tx_thread_running = false; - - // 等待线程退出 - if (instance->rx_thread) { - osal_thread_join(instance->rx_thread, NULL); - instance->rx_thread = NULL; - } - - if (instance->tx_thread) { - osal_thread_join(instance->tx_thread, NULL); - instance->tx_thread = NULL; - } - - // 断开连接 - ws_instance_disconnect(instance); - - // 清理队列 - ws_queue_deinit(&instance->rx_queue); - ws_queue_deinit(&instance->tx_queue); - - // 重置状态 - instance->state = WS_INSTANCE_IDLE; - instance->instance_id = 0; - memset(instance->instance_name, 0, sizeof(instance->instance_name)); -} - -/** - * 连接 WebSocket 实例到服务器 - */ -static int ws_instance_connect(ws_instance_t *instance, const char *url, const char *headers, uint8_t use_dns) -{ - if (!instance || !url) { - return -1; - } - - instance->state = WS_INSTANCE_CONNECTING; - - // 创建 WebSocket 客户端 - instance->client = init_websocket_client(url, headers, use_dns); - if (!instance->client) { - WS_LOG_ERROR("Failed to create WebSocket client for instance %d", instance->instance_id); - instance->state = WS_INSTANCE_ERROR; - return -1; - } - - // 设置数据包回调(暂时使用包装函数) - ws_set_packet_callback(instance->client, ws_instance_packet_callback_wrapper); - - instance->state = WS_INSTANCE_CONNECTED; - - // 创建接收线程 - if (osal_thread_create(&instance->rx_thread, "ws_rx", ws_instance_rx_thread, instance, - WS_THREAD_STACK_SIZE, WS_THREAD_PRIORITY) != OSAL_SUCCESS) { - WS_LOG_ERROR("Failed to create RX thread for instance %d", instance->instance_id); - lwip_close_websocket_client(instance->client); - instance->client = NULL; - instance->state = WS_INSTANCE_ERROR; - return -1; - } - - // 创建发送线程 - if (osal_thread_create(&instance->tx_thread, "ws_tx", ws_instance_tx_thread, instance, - WS_THREAD_STACK_SIZE, WS_THREAD_PRIORITY) != OSAL_SUCCESS) { - WS_LOG_ERROR("Failed to create TX thread for instance %d", instance->instance_id); - instance->rx_thread_running = false; - osal_thread_join(instance->rx_thread, NULL); - lwip_close_websocket_client(instance->client); - instance->client = NULL; - instance->state = WS_INSTANCE_ERROR; - return -1; - } - - instance->rx_thread_running = true; - instance->tx_thread_running = true; - instance->state = WS_INSTANCE_READY; - instance->last_activity = osal_get_current_time(); - - // 调用连接成功回调 - if (instance->on_connect) { - instance->on_connect(instance->instance_id); - } - - WS_LOG_INFO("Instance %d connected and ready", instance->instance_id); - return 0; -} - -/** - * 断开 WebSocket 实例连接 - */ -static void ws_instance_disconnect(ws_instance_t *instance) -{ - if (!instance || !instance->client) { - return; - } - - instance->state = WS_INSTANCE_CLOSING; - - // 关闭 WebSocket 客户端 - lwip_close_websocket_client(instance->client); - instance->client = NULL; - - instance->state = WS_INSTANCE_CLOSED; - - // 调用断开连接回调 - if (instance->on_disconnect) { - instance->on_disconnect(instance->instance_id); - } - - WS_LOG_INFO("Instance %d disconnected", instance->instance_id); -} - -/** - * 发送数据到 WebSocket 实例 - */ -static int ws_instance_send_data(ws_instance_t *instance, uint8_t *data, size_t length, WebSocketMessageType type) -{ - if (!instance || !data || length == 0 || instance->state != WS_INSTANCE_READY) { - return -1; - } - - // 将数据加入发送队列 - int result = ws_queue_enqueue(&instance->tx_queue, data, length, type); - if (result == 0) { - instance->last_activity = osal_get_current_time(); - } - - return result; -} - -/* ========== WebSocket 实例线程函数 ========== */ - -/** - * WebSocket 实例接收线程 - */ -static void ws_instance_rx_thread(void *arg) -{ - ws_instance_t *instance = (ws_instance_t *)arg; - uint8_t buffer[WS_MAX_PAYLOAD_SIZE]; - - WS_LOG_INFO("RX thread started for instance %d", instance->instance_id); - - while (instance->rx_thread_running && instance->client && instance->client->sockfd >= 0) { - // 非阻塞接收数据 - int received = lwip_recv(instance->client->sockfd, buffer, sizeof(buffer), MSG_DONTWAIT); - - if (received > 0) { - // 处理接收到的数据 - int processed = ws_process_received_data(instance->client, buffer, received); - if (processed > 0) { - instance->rx_packets++; - instance->rx_bytes += received; - instance->last_activity = osal_get_current_time(); - } else if (processed < 0) { - instance->errors++; - WS_LOG_ERROR("Error processing received data in instance %d", instance->instance_id); - } - } else if (received == 0) { - // 连接关闭 - WS_LOG_INFO("Connection closed for instance %d", instance->instance_id); - break; - } else { - // 没有数据或错误 - if (errno != EAGAIN && errno != EWOULDBLOCK) { - WS_LOG_ERROR("Receive error in instance %d: %d", instance->instance_id, errno); - instance->errors++; - break; - } - } - - // 短暂休眠,避免占用过多CPU - osal_msleep(1); - } - - instance->rx_thread_running = false; - WS_LOG_INFO("RX thread stopped for instance %d", instance->instance_id); -} - -/** - * WebSocket 实例发送线程 - */ -static void ws_instance_tx_thread(void *arg) -{ - ws_instance_t *instance = (ws_instance_t *)arg; - uint8_t buffer[WS_MAX_PAYLOAD_SIZE]; - size_t length; - WebSocketMessageType type; - - WS_LOG_INFO("TX thread started for instance %d", instance->instance_id); - - while (instance->tx_thread_running && instance->client && instance->client->sockfd >= 0) { - // 从发送队列获取数据 - if (ws_queue_dequeue(&instance->tx_queue, buffer, &length, &type) == 0) { - // 发送数据 - int sent = send_websocket_message(instance->client, buffer, length, type); - if (sent > 0) { - instance->tx_packets++; - instance->tx_bytes += sent; - instance->last_activity = osal_get_current_time(); - } else { - instance->errors++; - WS_LOG_ERROR("Error sending data in instance %d", instance->instance_id); - } - } else { - // 队列为空,短暂休眠 - osal_msleep(10); - } - } - - instance->tx_thread_running = false; - WS_LOG_INFO("TX thread stopped for instance %d", instance->instance_id); -} - -/* ========== WebSocket 消息队列函数 ========== */ - -/** - * 初始化消息队列 - */ -static int ws_queue_init(ws_message_queue_t *queue, const char *name) -{ - if (!queue || !name) { - return -1; - } - - memset(queue, 0, sizeof(ws_message_queue_t)); - - // 创建互斥锁 - if (osal_mutex_create(&queue->mutex) != OSAL_SUCCESS) { - WS_LOG_ERROR("Failed to create mutex for %s queue", name); - return -1; - } - - // 创建信号量 - if (osal_sem_create(&queue->semaphore, 0, WS_RX_QUEUE_SIZE) != OSAL_SUCCESS) { - WS_LOG_ERROR("Failed to create semaphore for %s queue", name); - osal_mutex_destroy(&queue->mutex); - return -1; - } - - queue->initialized = true; - return 0; -} - -/** - * 销毁消息队列 - */ -static void ws_queue_deinit(ws_message_queue_t *queue) -{ - if (!queue || !queue->initialized) { - return; - } - - ws_queue_clear(queue); - - osal_mutex_destroy(&queue->mutex); - osal_sem_destroy(&queue->semaphore); - - queue->initialized = false; -} - -/** - * 向队列添加消息 - */ -static int ws_queue_enqueue(ws_message_queue_t *queue, uint8_t *data, size_t length, WebSocketMessageType type) -{ - if (!queue || !queue->initialized || !data || length == 0 || length > WS_MAX_PAYLOAD_SIZE) { - return -1; - } - - osal_mutex_lock(&queue->mutex); - - // 检查队列是否已满 - if (queue->count >= WS_RX_QUEUE_SIZE) { - WS_LOG_WARNING("Queue is full, dropping message"); - osal_mutex_unlock(&queue->mutex); - return -1; - } - - // 分配内存并复制数据 - ws_queue_item_t *item = &queue->items[queue->tail]; - item->data = malloc(length); - if (!item->data) { - WS_LOG_ERROR("Failed to allocate memory for queue item"); - osal_mutex_unlock(&queue->mutex); - return -1; - } - - memcpy(item->data, data, length); - item->length = length; - item->type = type; - item->timestamp = osal_get_current_time(); - item->in_use = true; - - queue->tail = (queue->tail + 1) % WS_RX_QUEUE_SIZE; - queue->count++; - - osal_mutex_unlock(&queue->mutex); - - // 通知等待的线程 - osal_sem_post(&queue->semaphore); - - return 0; -} - -/** - * 从队列获取消息 - */ -static int ws_queue_dequeue(ws_message_queue_t *queue, uint8_t *data, size_t *length, WebSocketMessageType *type) -{ - if (!queue || !queue->initialized || !data || !length || !type) { - return -1; - } - - // 等待信号量(非阻塞) - if (osal_sem_trywait(&queue->semaphore) != OSAL_SUCCESS) { - return -1; // 队列为空 - } - - osal_mutex_lock(&queue->mutex); - - if (queue->count == 0) { - osal_mutex_unlock(&queue->mutex); - return -1; - } - - ws_queue_item_t *item = &queue->items[queue->head]; - if (!item->in_use || !item->data) { - osal_mutex_unlock(&queue->mutex); - return -1; - } - - // 复制数据 - if (item->length <= WS_MAX_PAYLOAD_SIZE) { - memcpy(data, item->data, item->length); - *length = item->length; - *type = item->type; - } else { - WS_LOG_ERROR("Queue item too large: %zu bytes", item->length); - osal_mutex_unlock(&queue->mutex); - return -1; - } - - // 释放内存 - free(item->data); - item->data = NULL; - item->in_use = false; - - queue->head = (queue->head + 1) % WS_RX_QUEUE_SIZE; - queue->count--; - - osal_mutex_unlock(&queue->mutex); - - return 0; -} - -/** - * 清空队列 - */ -static void ws_queue_clear(ws_message_queue_t *queue) -{ - if (!queue || !queue->initialized) { - return; - } - - osal_mutex_lock(&queue->mutex); - - for (int i = 0; i < WS_RX_QUEUE_SIZE; i++) { - if (queue->items[i].in_use && queue->items[i].data) { - free(queue->items[i].data); - queue->items[i].data = NULL; - queue->items[i].in_use = false; - } - } - - queue->head = 0; - queue->tail = 0; - queue->count = 0; - - osal_mutex_unlock(&queue->mutex); -} - -/* ========== WebSocket 多实例管理器使用示例 ========== */ - -/* -使用示例: - -// 1. 初始化管理器 -ws_manager_init(); - -// 2. 定义回调函数 -void on_connect(int instance_id) { - WS_LOG_INFO("Instance %d connected", instance_id); -} - -void on_disconnect(int instance_id) { - WS_LOG_INFO("Instance %d disconnected", instance_id); -} - -void on_error(int instance_id, int error_code) { - WS_LOG_ERROR("Instance %d error: %d", instance_id, error_code); -} - -void on_message(int instance_id, uint8_t *data, size_t length, WebSocketMessageType type) { - WS_LOG_INFO("Instance %d received message: %.*s", instance_id, (int)length, data); -} - -// 3. 创建 WebSocket 实例 -int chat_instance = ws_manager_create_instance("chat", "ws://example.com:8080/chat", NULL, 1); -int asr_instance = ws_manager_create_instance("asr", "ws://example.com:8080/asr", NULL, 1); -int tts_instance = ws_manager_create_instance("tts", "ws://example.com:8080/tts", NULL, 1); - -// 4. 注册回调函数 -ws_manager_register_callbacks(chat_instance, on_connect, on_disconnect, on_error, on_message); -ws_manager_register_callbacks(asr_instance, on_connect, on_disconnect, on_error, on_message); -ws_manager_register_callbacks(tts_instance, on_connect, on_disconnect, on_error, on_message); - -// 5. 发送数据 -uint8_t message[] = "Hello WebSocket!"; -ws_manager_send_data(chat_instance, message, strlen((char*)message), WS_TEXT); - -// 6. 检查状态 -ws_instance_state_t state = ws_manager_get_instance_state(chat_instance); -if (state == WS_INSTANCE_READY) { - // 实例已准备就绪 -} - -// 7. 销毁实例 -ws_manager_destroy_instance(chat_instance); -ws_manager_destroy_instance(asr_instance); -ws_manager_destroy_instance(tts_instance); - -// 8. 销毁管理器 -ws_manager_deinit(); - -特性说明: -- 支持最多 8 个并发 WebSocket 实例 -- 每个实例有独立的接收和发送队列(64个消息) -- 非阻塞的队列操作,避免线程阻塞 -- 支持回调函数,便于处理连接、断开、错误和消息事件 -- 线程安全的实例管理 -- 自动处理连接状态和错误恢复 -- 支持 DNS 解析和直接 IP 连接 -- 统计信息跟踪(数据包数、字节数、错误数) - -注意事项: -- 每个实例会创建两个线程(接收和发送) -- 消息队列使用动态内存分配,注意内存管理 -- 回调函数在实例线程中执行,避免长时间阻塞操作 -- 实例 ID 从 1 开始递增分配 -- 管理器会自动初始化,但建议显式调用 ws_manager_init() -*/ \ No newline at end of file diff --git a/code/sdk/zhihui/ai/src/chat_server.c b/code/sdk/zhihui/ai/src/chat_server.c new file mode 100644 index 000000000..713ce694f --- /dev/null +++ b/code/sdk/zhihui/ai/src/chat_server.c @@ -0,0 +1,1132 @@ +/* + * Chat 大模型服务实现 + * 负责建立与Chat大模型服务器的WebSocket连接管理,上传文本,接收回复并转发给TTS + */ + +#include +#include +#include +#include "chat_server.h" +#include "../../../../application/wearable/ohos_deps/wearable_log.h" +#include "../../../../../open_source/openharmony/third_party/cJSON/cJSON.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* 内部宏定义 */ +#define CHAT_LOG_TAG "CHAT_SERVER" +#define CHAT_MAX_CONVERSATIONS 10 + +/* 内部函数声明 */ +static void chat_server_on_connected(void *context); +static void chat_server_on_disconnected(void *context); +static void chat_server_on_message_received(void *context, const char *data, size_t length); +static void chat_server_on_error(void *context, int error_code, const char *error_msg); +static ai_service_error_t chat_server_parse_response(chat_server_context_t *context, + const char *json_data, + chat_response_t *response); +static ai_service_error_t chat_server_create_request_message(chat_server_context_t *context, + const chat_request_t *request, + char *output, size_t max_len); +static chat_conversation_t* chat_server_find_conversation(chat_server_context_t *context, + const char *session_id); +static chat_conversation_t* chat_server_create_conversation(chat_server_context_t *context, + const char *session_id); +static ai_service_error_t chat_server_add_message_to_conversation(chat_server_context_t *context, + const char *session_id, + chat_role_t role, + const char *content); + +/* 公共接口实现 */ + +ai_service_error_t chat_server_init(chat_server_context_t *context, + const char *server_url, + const chat_model_config_t *model_config) +{ + if (!context || !server_url || !model_config) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + // 清零上下文 + memset(context, 0, sizeof(chat_server_context_t)); + + // 创建基础服务配置 + ai_service_config_t base_config = {0}; + strncpy(base_config.server_url, server_url, sizeof(base_config.server_url) - 1); + base_config.server_port = 80; + strncpy(base_config.protocol, "chat-protocol", sizeof(base_config.protocol) - 1); + base_config.connect_timeout_ms = AI_CONNECT_TIMEOUT_MS; + base_config.receive_timeout_ms = AI_RECEIVE_TIMEOUT_MS; + base_config.reconnect_interval_ms = AI_RECONNECT_INTERVAL_MS; + base_config.heartbeat_interval_ms = AI_HEARTBEAT_INTERVAL_MS; + base_config.max_retry_count = AI_MAX_RETRY_COUNT; + base_config.auto_reconnect = true; + + // 初始化基础服务 + ai_service_error_t ret = ai_service_init(&context->base, "CHAT_SERVER", &base_config); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to init base service: %d", CHAT_LOG_TAG, ret); + return ret; + } + + // 复制Chat特定配置 + memcpy(&context->model_config, model_config, sizeof(chat_model_config_t)); + context->response_mode = CHAT_RESPONSE_MODE_COMPLETE; + + // 初始化会话管理 + context->max_conversations = CHAT_MAX_CONVERSATIONS; + context->conversations = (chat_conversation_t *)malloc(sizeof(chat_conversation_t) * context->max_conversations); + if (!context->conversations) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to allocate conversations", CHAT_LOG_TAG); + ai_service_destroy(&context->base); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + // 初始化所有会话 + for (size_t i = 0; i < context->max_conversations; i++) { + memset(&context->conversations[i], 0, sizeof(chat_conversation_t)); + } + + context->conversation_count = 0; + + // 创建会话互斥锁 + context->conversation_mutex = osMutexNew(NULL); + if (!context->conversation_mutex) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create conversation mutex", CHAT_LOG_TAG); + free(context->conversations); + ai_service_destroy(&context->base); + return AI_SERVICE_ERROR_INTERNAL; + } + + // 初始化统计信息 + memset(&context->chat_stats, 0, sizeof(chat_stats_t)); + + // 设置基础服务回调 + ai_service_set_callbacks(&context->base, + chat_server_on_connected, + chat_server_on_disconnected, + chat_server_on_message_received, + chat_server_on_error); + + // 设置用户数据指向Chat上下文 + ai_service_set_user_data(&context->base, context); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Chat server initialized", CHAT_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t chat_server_destroy(chat_server_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + // 停止服务 + chat_server_stop(context); + + // 清理所有会话 + if (context->conversations) { + for (size_t i = 0; i < context->max_conversations; i++) { + chat_conversation_free(&context->conversations[i]); + } + free(context->conversations); + context->conversations = NULL; + } + + // 删除会话互斥锁 + if (context->conversation_mutex) { + osMutexDelete(context->conversation_mutex); + context->conversation_mutex = NULL; + } + + // 销毁基础服务 + ai_service_destroy(&context->base); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Chat server destroyed", CHAT_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t chat_server_start(chat_server_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + ai_service_error_t ret = ai_service_start(&context->base); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to start Chat server: %d", CHAT_LOG_TAG, ret); + return ret; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Chat server started", CHAT_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t chat_server_stop(chat_server_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + ai_service_error_t ret = ai_service_stop(&context->base); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to stop Chat server: %d", CHAT_LOG_TAG, ret); + return ret; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Chat server stopped", CHAT_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t chat_server_send_message(chat_server_context_t *context, + const char *message, + const char *session_id) +{ + if (!context || !message) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + chat_request_t request = {0}; + request.message = (char *)message; + request.message_type = CHAT_MESSAGE_TYPE_TEXT; + request.response_mode = context->response_mode; + request.model_config = context->model_config; + request.include_history = context->model_config.enable_history; + + if (session_id && strlen(session_id) > 0) { + strncpy(request.session_id, session_id, sizeof(request.session_id) - 1); + } else { + ai_service_generate_uuid(request.session_id, sizeof(request.session_id)); + } + + return chat_server_send_request(context, &request); +} + +ai_service_error_t chat_server_send_request(chat_server_context_t *context, + const chat_request_t *request) +{ + if (!context || !request) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + if (!ai_service_is_connected(&context->base)) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Chat server is not connected", CHAT_LOG_TAG); + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + // 添加用户消息到会话历史 + if (request->include_history) { + chat_server_add_message_to_conversation(context, request->session_id, + CHAT_ROLE_USER, request->message); + } + + // 创建请求消息 + char *chat_message = (char *)malloc(CHAT_MAX_MESSAGE_LENGTH + CHAT_MAX_CONTEXT_LENGTH + 1024); + if (!chat_message) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to allocate chat message buffer", CHAT_LOG_TAG); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + ai_service_error_t ret = chat_server_create_request_message(context, request, + chat_message, + CHAT_MAX_MESSAGE_LENGTH + CHAT_MAX_CONTEXT_LENGTH + 1024); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create chat message: %d", CHAT_LOG_TAG, ret); + free(chat_message); + return ret; + } + + // 发送请求消息 + ret = ai_service_send_text(&context->base, chat_message); + free(chat_message); + + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to send chat message: %d", CHAT_LOG_TAG, ret); + return ret; + } + + // 更新当前会话ID + strncpy(context->current_session_id, request->session_id, sizeof(context->current_session_id) - 1); + + // 更新统计信息 + context->chat_stats.total_requests++; + if (request->message) { + context->chat_stats.total_tokens_sent += strlen(request->message); // 简化的token计算 + } + + // 调用用户回调 + if (context->on_chat_start) { + context->on_chat_start(context, request->session_id); + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Chat request sent, session: %s", + CHAT_LOG_TAG, request->session_id); + + return AI_SERVICE_OK; +} + +ai_service_error_t chat_server_create_session(chat_server_context_t *context, + const char *session_id) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + char actual_session_id[64]; + if (session_id && strlen(session_id) > 0) { + strncpy(actual_session_id, session_id, sizeof(actual_session_id) - 1); + } else { + ai_service_generate_uuid(actual_session_id, sizeof(actual_session_id)); + } + + chat_conversation_t *conversation = chat_server_create_conversation(context, actual_session_id); + if (!conversation) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create conversation", CHAT_LOG_TAG); + return AI_SERVICE_ERROR_INTERNAL; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Session created: %s", CHAT_LOG_TAG, actual_session_id); + + return AI_SERVICE_OK; +} + +ai_service_error_t chat_server_delete_session(chat_server_context_t *context, + const char *session_id) +{ + if (!context || !session_id) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + osMutexAcquire(context->conversation_mutex, osWaitForever); + + for (size_t i = 0; i < context->max_conversations; i++) { + if (strlen(context->conversations[i].session_id) > 0 && + strcmp(context->conversations[i].session_id, session_id) == 0) { + + chat_conversation_free(&context->conversations[i]); + memset(&context->conversations[i], 0, sizeof(chat_conversation_t)); + context->conversation_count--; + + osMutexRelease(context->conversation_mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Session deleted: %s", CHAT_LOG_TAG, session_id); + return AI_SERVICE_OK; + } + } + + osMutexRelease(context->conversation_mutex); + + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Session not found: %s", CHAT_LOG_TAG, session_id); + return AI_SERVICE_ERROR_INVALID_PARAM; +} + +ai_service_error_t chat_server_clear_session_history(chat_server_context_t *context, + const char *session_id) +{ + if (!context || !session_id) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + chat_conversation_t *conversation = chat_server_find_conversation(context, session_id); + if (!conversation) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Session not found: %s", CHAT_LOG_TAG, session_id); + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + ai_service_error_t ret = chat_conversation_clear_messages(conversation); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to clear session history: %d", CHAT_LOG_TAG, ret); + return ret; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Session history cleared: %s", CHAT_LOG_TAG, session_id); + + return AI_SERVICE_OK; +} + +const chat_conversation_t* chat_server_get_session_history(chat_server_context_t *context, + const char *session_id) +{ + if (!context || !session_id) { + return NULL; + } + + return chat_server_find_conversation(context, session_id); +} + +ai_service_error_t chat_server_set_model_config(chat_server_context_t *context, + const chat_model_config_t *model_config) +{ + if (!context || !model_config) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + memcpy(&context->model_config, model_config, sizeof(chat_model_config_t)); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Model config updated", CHAT_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t chat_server_set_response_mode(chat_server_context_t *context, + chat_response_mode_t response_mode) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + context->response_mode = response_mode; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Response mode updated: %s", + CHAT_LOG_TAG, chat_server_response_mode_to_string(response_mode)); + + return AI_SERVICE_OK; +} + +ai_service_error_t chat_server_set_tts_interface(chat_server_context_t *context, + ai_service_error_t (*tts_send_text)(const char *text, const char *session_id)) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + context->tts_send_text = tts_send_text; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS interface set", CHAT_LOG_TAG); + + return AI_SERVICE_OK; +} + +const char* chat_server_get_current_session_id(chat_server_context_t *context) +{ + if (!context || strlen(context->current_session_id) == 0) { + return NULL; + } + + return context->current_session_id; +} + +size_t chat_server_get_session_count(chat_server_context_t *context) +{ + return context ? context->conversation_count : 0; +} + +void chat_server_set_callbacks(chat_server_context_t *context, + void (*on_chat_start)(void *context, const char *session_id), + void (*on_chat_response)(void *context, const chat_response_t *response), + void (*on_chat_complete)(void *context, const char *session_id), + void (*on_chat_streaming)(void *context, const char *session_id, const char *partial_content)) +{ + if (!context) { + return; + } + + context->on_chat_start = on_chat_start; + context->on_chat_response = on_chat_response; + context->on_chat_complete = on_chat_complete; + context->on_chat_streaming = on_chat_streaming; +} + +const chat_stats_t* chat_server_get_stats(chat_server_context_t *context) +{ + return context ? &context->chat_stats : NULL; +} + +/* 会话管理函数实现 */ + +ai_service_error_t chat_conversation_add_message(chat_conversation_t *conversation, + chat_role_t role, + const char *content, + chat_message_type_t message_type) +{ + if (!conversation || !content) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + // 检查是否需要扩展消息数组 + if (conversation->message_count >= conversation->max_messages) { + size_t new_max = conversation->max_messages == 0 ? 10 : conversation->max_messages * 2; + chat_message_t *new_messages = (chat_message_t *)realloc(conversation->messages, + sizeof(chat_message_t) * new_max); + if (!new_messages) { + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + conversation->messages = new_messages; + conversation->max_messages = new_max; + } + + // 添加新消息 + chat_message_t *message = &conversation->messages[conversation->message_count]; + memset(message, 0, sizeof(chat_message_t)); + + message->role = role; + message->type = message_type; + message->content = strdup(content); + message->content_length = strlen(content); + message->timestamp = ai_service_get_timestamp(); + + // 生成消息ID + ai_service_generate_uuid(message->message_id, sizeof(message->message_id)); + + conversation->message_count++; + conversation->last_update_time = ai_service_get_timestamp(); + + return AI_SERVICE_OK; +} + +const chat_message_t* chat_conversation_get_last_message(const chat_conversation_t *conversation) +{ + if (!conversation || conversation->message_count == 0) { + return NULL; + } + + return &conversation->messages[conversation->message_count - 1]; +} + +ai_service_error_t chat_conversation_clear_messages(chat_conversation_t *conversation) +{ + if (!conversation) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + // 释放所有消息内容 + for (size_t i = 0; i < conversation->message_count; i++) { + if (conversation->messages[i].content) { + free(conversation->messages[i].content); + conversation->messages[i].content = NULL; + } + } + + conversation->message_count = 0; + conversation->last_update_time = ai_service_get_timestamp(); + + return AI_SERVICE_OK; +} + +void chat_conversation_free(chat_conversation_t *conversation) +{ + if (!conversation) { + return; + } + + // 清空消息 + chat_conversation_clear_messages(conversation); + + // 释放消息数组 + if (conversation->messages) { + free(conversation->messages); + conversation->messages = NULL; + } + + conversation->max_messages = 0; +} + +/* 工具函数实现 */ + +void chat_server_create_default_model_config(chat_model_config_t *config) +{ + if (!config) { + return; + } + + strncpy(config->model_name, "gpt-3.5-turbo", sizeof(config->model_name) - 1); + config->model_type = CHAT_MODEL_TYPE_GPT35; + config->temperature = 0.7f; + config->top_p = 0.9f; + config->max_tokens = 2048; + config->enable_history = true; + config->max_history_count = CHAT_MAX_HISTORY_COUNT; +} + +void chat_server_free_response(chat_response_t *response) +{ + if (!response) { + return; + } + + if (response->content) { + free(response->content); + response->content = NULL; + } +} + +void chat_server_free_message(chat_message_t *message) +{ + if (!message) { + return; + } + + if (message->content) { + free(message->content); + message->content = NULL; + } +} + +const char* chat_server_message_type_to_string(chat_message_type_t message_type) +{ + switch (message_type) { + case CHAT_MESSAGE_TYPE_TEXT: return "TEXT"; + case CHAT_MESSAGE_TYPE_IMAGE: return "IMAGE"; + case CHAT_MESSAGE_TYPE_AUDIO: return "AUDIO"; + case CHAT_MESSAGE_TYPE_VIDEO: return "VIDEO"; + case CHAT_MESSAGE_TYPE_SYSTEM: return "SYSTEM"; + default: return "UNKNOWN"; + } +} + +const char* chat_server_role_to_string(chat_role_t role) +{ + switch (role) { + case CHAT_ROLE_USER: return "user"; + case CHAT_ROLE_ASSISTANT: return "assistant"; + case CHAT_ROLE_SYSTEM: return "system"; + default: return "unknown"; + } +} + +const char* chat_server_model_type_to_string(chat_model_type_t model_type) +{ + switch (model_type) { + case CHAT_MODEL_TYPE_GPT35: return "GPT-3.5"; + case CHAT_MODEL_TYPE_GPT4: return "GPT-4"; + case CHAT_MODEL_TYPE_CLAUDE: return "Claude"; + case CHAT_MODEL_TYPE_LLAMA: return "LLaMA"; + case CHAT_MODEL_TYPE_QWEN: return "Qwen"; + case CHAT_MODEL_TYPE_BAICHUAN: return "Baichuan"; + default: return "UNKNOWN"; + } +} + +const char* chat_server_response_mode_to_string(chat_response_mode_t response_mode) +{ + switch (response_mode) { + case CHAT_RESPONSE_MODE_COMPLETE: return "COMPLETE"; + case CHAT_RESPONSE_MODE_STREAMING: return "STREAMING"; + default: return "UNKNOWN"; + } +} + +/* 内部函数实现 */ + +static void chat_server_on_connected(void *context) +{ + chat_server_context_t *chat_context = (chat_server_context_t *)ai_service_get_user_data((ai_service_context_t *)context); + if (!chat_context) { + return; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Chat server connected", CHAT_LOG_TAG); +} + +static void chat_server_on_disconnected(void *context) +{ + chat_server_context_t *chat_context = (chat_server_context_t *)ai_service_get_user_data((ai_service_context_t *)context); + if (!chat_context) { + return; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Chat server disconnected", CHAT_LOG_TAG); +} + +static void chat_server_on_message_received(void *context, const char *data, size_t length) +{ + chat_server_context_t *chat_context = (chat_server_context_t *)ai_service_get_user_data((ai_service_context_t *)context); + if (!chat_context || !data || length == 0) { + return; + } + + // 解析聊天响应 + chat_response_t response = {0}; + ai_service_error_t ret = chat_server_parse_response(chat_context, data, &response); + if (ret == AI_SERVICE_OK) { + // 更新统计信息 + chat_context->chat_stats.total_responses++; + chat_context->chat_stats.total_tokens_received += response.token_count; + + // 更新平均响应时间 + if (chat_context->chat_stats.total_responses > 0) { + chat_context->chat_stats.average_response_time_ms = + (chat_context->chat_stats.average_response_time_ms * (chat_context->chat_stats.total_responses - 1) + + response.response_time_ms) / chat_context->chat_stats.total_responses; + } + + // 添加助手消息到会话历史 + if (chat_context->model_config.enable_history && response.content) { + chat_server_add_message_to_conversation(chat_context, response.session_id, + CHAT_ROLE_ASSISTANT, response.content); + } + + // 调用用户回调 + if (response.is_streaming && chat_context->on_chat_streaming) { + chat_context->on_chat_streaming(chat_context, response.session_id, response.content); + } else if (chat_context->on_chat_response) { + chat_context->on_chat_response(chat_context, &response); + } + + // 如果是最终响应,调用完成回调 + if (response.is_final && chat_context->on_chat_complete) { + chat_context->on_chat_complete(chat_context, response.session_id); + } + + // 转发给TTS服务 + if (chat_context->tts_send_text && response.content && response.is_final) { + ai_service_error_t tts_ret = chat_context->tts_send_text(response.content, response.session_id); + if (tts_ret != AI_SERVICE_OK) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Failed to send to TTS: %d", CHAT_LOG_TAG, tts_ret); + } + } + + // 释放响应内存 + chat_server_free_response(&response); + } + + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "[%s] Received message: %.*s", + CHAT_LOG_TAG, (int)length, data); +} + +static void chat_server_on_error(void *context, int error_code, const char *error_msg) +{ + chat_server_context_t *chat_context = (chat_server_context_t *)ai_service_get_user_data((ai_service_context_t *)context); + if (!chat_context) { + return; + } + + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Chat server error: %d - %s", + CHAT_LOG_TAG, error_code, error_msg ? error_msg : "Unknown"); +} + +static ai_service_error_t chat_server_parse_response(chat_server_context_t *context, + const char *json_data, + chat_response_t *response) +{ + if (!context || !json_data || !response) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + cJSON *json = cJSON_Parse(json_data); + if (!json) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to parse JSON", CHAT_LOG_TAG); + return AI_SERVICE_ERROR_INTERNAL; + } + + // 解析内容 + cJSON *content_item = cJSON_GetObjectItem(json, "content"); + if (content_item && cJSON_IsString(content_item)) { + const char *content = cJSON_GetStringValue(content_item); + if (content) { + response->content = strdup(content); + response->content_length = strlen(content); + } + } + + // 解析会话ID + cJSON *session_id_item = cJSON_GetObjectItem(json, "session_id"); + if (session_id_item && cJSON_IsString(session_id_item)) { + const char *session_id = cJSON_GetStringValue(session_id_item); + if (session_id) { + strncpy(response->session_id, session_id, sizeof(response->session_id) - 1); + } + } + + // 解析消息ID + cJSON *message_id_item = cJSON_GetObjectItem(json, "message_id"); + if (message_id_item && cJSON_IsString(message_id_item)) { + const char *message_id = cJSON_GetStringValue(message_id_item); + if (message_id) { + strncpy(response->message_id, message_id, sizeof(response->message_id) - 1); + } + } + + // 解析是否为最终结果 + cJSON *is_final_item = cJSON_GetObjectItem(json, "is_final"); + if (is_final_item && cJSON_IsBool(is_final_item)) { + response->is_final = cJSON_IsTrue(is_final_item); + } + + // 解析是否为流式响应 + cJSON *is_streaming_item = cJSON_GetObjectItem(json, "is_streaming"); + if (is_streaming_item && cJSON_IsBool(is_streaming_item)) { + response->is_streaming = cJSON_IsTrue(is_streaming_item); + } + + // 解析token数量 + cJSON *token_count_item = cJSON_GetObjectItem(json, "token_count"); + if (token_count_item && cJSON_IsNumber(token_count_item)) { + response->token_count = (uint32_t)cJSON_GetNumberValue(token_count_item); + } + + // 解析置信度 + cJSON *confidence_item = cJSON_GetObjectItem(json, "confidence"); + if (confidence_item && cJSON_IsNumber(confidence_item)) { + response->confidence = (float)cJSON_GetNumberValue(confidence_item); + } + + // 解析响应时间 + cJSON *response_time_item = cJSON_GetObjectItem(json, "response_time_ms"); + if (response_time_item && cJSON_IsNumber(response_time_item)) { + response->response_time_ms = (uint32_t)cJSON_GetNumberValue(response_time_item); + } + + cJSON_Delete(json); + + return AI_SERVICE_OK; +} + +static ai_service_error_t chat_server_create_request_message(chat_server_context_t *context, + const chat_request_t *request, + char *output, size_t max_len) +{ + if (!context || !request || !output || max_len == 0) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + cJSON *json = cJSON_CreateObject(); + if (!json) { + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + cJSON_AddStringToObject(json, "type", "chat_request"); + cJSON_AddStringToObject(json, "session_id", request->session_id); + cJSON_AddStringToObject(json, "message", request->message); + cJSON_AddStringToObject(json, "message_type", chat_server_message_type_to_string(request->message_type)); + cJSON_AddStringToObject(json, "response_mode", chat_server_response_mode_to_string(request->response_mode)); + + // 添加模型配置 + cJSON *model_config = cJSON_CreateObject(); + cJSON_AddStringToObject(model_config, "model_name", request->model_config.model_name); + cJSON_AddStringToObject(model_config, "model_type", chat_server_model_type_to_string(request->model_config.model_type)); + cJSON_AddNumberToObject(model_config, "temperature", request->model_config.temperature); + cJSON_AddNumberToObject(model_config, "top_p", request->model_config.top_p); + cJSON_AddNumberToObject(model_config, "max_tokens", request->model_config.max_tokens); + cJSON_AddBoolToObject(model_config, "enable_history", request->model_config.enable_history); + cJSON_AddNumberToObject(model_config, "max_history_count", request->model_config.max_history_count); + cJSON_AddItemToObject(json, "model_config", model_config); + + // 添加会话历史(如果启用) + if (request->include_history) { + chat_conversation_t *conversation = chat_server_find_conversation(context, request->session_id); + if (conversation && conversation->message_count > 0) { + cJSON *history = cJSON_CreateArray(); + + size_t start_index = 0; + if (conversation->message_count > request->model_config.max_history_count) { + start_index = conversation->message_count - request->model_config.max_history_count; + } + + for (size_t i = start_index; i < conversation->message_count; i++) { + cJSON *msg = cJSON_CreateObject(); + cJSON_AddStringToObject(msg, "role", chat_server_role_to_string(conversation->messages[i].role)); + cJSON_AddStringToObject(msg, "content", conversation->messages[i].content); + cJSON_AddNumberToObject(msg, "timestamp", conversation->messages[i].timestamp); + cJSON_AddItemToArray(history, msg); + } + + cJSON_AddItemToObject(json, "history", history); + } + } + + char *json_string = cJSON_Print(json); + if (!json_string) { + cJSON_Delete(json); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + if (strlen(json_string) >= max_len) { + free(json_string); + cJSON_Delete(json); + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + strcpy(output, json_string); + + free(json_string); + cJSON_Delete(json); + + return AI_SERVICE_OK; +} + +static chat_conversation_t* chat_server_find_conversation(chat_server_context_t *context, + const char *session_id) +{ + if (!context || !session_id) { + return NULL; + } + + osMutexAcquire(context->conversation_mutex, osWaitForever); + + for (size_t i = 0; i < context->max_conversations; i++) { + if (strlen(context->conversations[i].session_id) > 0 && + strcmp(context->conversations[i].session_id, session_id) == 0) { + osMutexRelease(context->conversation_mutex); + return &context->conversations[i]; + } + } + + osMutexRelease(context->conversation_mutex); + return NULL; +} + +static chat_conversation_t* chat_server_create_conversation(chat_server_context_t *context, + const char *session_id) +{ + if (!context || !session_id) { + return NULL; + } + + osMutexAcquire(context->conversation_mutex, osWaitForever); + + // 查找空闲的会话槽 + for (size_t i = 0; i < context->max_conversations; i++) { + if (strlen(context->conversations[i].session_id) == 0) { + strncpy(context->conversations[i].session_id, session_id, + sizeof(context->conversations[i].session_id) - 1); + context->conversations[i].created_time = ai_service_get_timestamp(); + context->conversations[i].last_update_time = context->conversations[i].created_time; + context->conversation_count++; + context->chat_stats.conversation_count++; + + osMutexRelease(context->conversation_mutex); + return &context->conversations[i]; + } + } + + osMutexRelease(context->conversation_mutex); + + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] No free conversation slots", CHAT_LOG_TAG); + return NULL; +} + +static ai_service_error_t chat_server_add_message_to_conversation(chat_server_context_t *context, + const char *session_id, + chat_role_t role, + const char *content) +{ + if (!context || !session_id || !content) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + chat_conversation_t *conversation = chat_server_find_conversation(context, session_id); + if (!conversation) { + conversation = chat_server_create_conversation(context, session_id); + if (!conversation) { + return AI_SERVICE_ERROR_INTERNAL; + } + } + + return chat_conversation_add_message(conversation, role, content, CHAT_MESSAGE_TYPE_TEXT); +} + + + +/****************************************************************************************************** */ + + + +// {"type":"middle","content":"我是","task_id":"","reasoning_content":""} +// char content_str_chat[64]={0}; +/* + +chart result + +{"type":"start","content":null,"task_id":"","reasoning_content":""} +{"type":"middle","content":"请","task_id":"","reasoning_content":""} +{"type":"middle","content":"你","task_id":"","reasoning_content":""} +{"type":"middle","content":"明确","task_id":"","reasoning_content":""} +{"type":"middle","content":"一下","task_id":"","reasoning_content":""} +{"type":"middle","content":"问题","task_id":"","reasoning_content":""} +{"type":"end","content":null,"task_id":"","reasoning_content":""} + +*/ +bool chat_api_result_analysis(char *buf) +{ + cJSON* root = cJSON_Parse((const char *)buf); + if (root == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"解析失败\n"); + return false; + } + char *type_str=NULL; + char *content_str=NULL; + + cJSON* type = cJSON_GetObjectItem(root, "type"); + if (type && cJSON_IsString(type)) { + + type_str= type->valuestring; + } + cJSON* content = cJSON_GetObjectItem(root, "content"); + if (content && cJSON_IsString(content)) { + content_str = content->valuestring; + } + if(strcmp("middle",type_str)==0) + { + if(strlen(content_str)>0) + { + // memset_s(content_str_chat,64,0,64); + // memcpy_s(content_str_chat,64,content_str,strlen(content_str)); + + + char *content_str_chat = (char *)malloc(strlen(content_str)+1); + strcpy(content_str_chat,content_str); + ble_set_ai_Answer_str(content_str_chat,2); + + + // uapi_start_tts_single_txt(content_str_chat); + // static uint8_t test=1; + // if(test) + // { + // uapi_start_tts_single(); + // test=0; + + // } + free(content_str_chat); + } + } + if(strcmp("end",type_str)==0) + { + char end_str_chat[128]={0}; + + if(strlen(content_str)>0) + { + memset_s(end_str_chat,128,0,128); + memcpy_s(end_str_chat,128,end_str_chat,strlen(end_str_chat)); + ble_set_ai_Answer_str(end_str_chat,2); + } + + } + + + cJSON_Delete(root); + return true; +} +// &device_id=222&product_id=222 +bool chat_api_ws_url_init(char * put_buf,uint8_t type) +{ + char token[64]={0}; + if(read_dev_token(token)) + { + if(type_ai==AI_TYPE_JIU) + { + sprintf(put_buf,"ws://amp.raisound.com/chat/ws?dt=%s&platform=chinamobiledevice&bot_type=jiutian-lan",token); + } + else if(type_ai==AI_TYPE_DOU) + { + sprintf(put_buf,"ws://amp.raisound.com/chat/ws?dt=%s&platform=huoshan",token); + } + else if(type_ai==AI_TYPE_WEN) + { + sprintf(put_buf,"ws://amp.raisound.com/chat/ws?dt=%s&platform=baidu",token); + } + else if(type_ai==AI_TYPE_KIMI) + { + sprintf(put_buf,"ws://amp.raisound.com/chat/ws?dt=%s&platform=moonshot",token); + } + else if(type_ai==AI_TYPE_DE) + { + sprintf(put_buf,"ws://amp.raisound.com/chat/ws?dt=%s&platform=huoshan&model=ep-20250205103849-582m7",token); + } + else + { + sprintf(put_buf,"ws://amp.raisound.com/chat/ws?dt=%s&platform=aliyun",token); + } + return true; + + } + else + { + char * Service_url="118.145.158.207:22080"; + + char device_id[128]={0}; + get_dev_id(device_id); + + char * product_id="1927646275585744898"; + + if(type_ai==AI_TYPE_JIU) + { + sprintf(put_buf,"ws://%s/chat/ws?device_id=%s&product_id=%s&platform=chinamobiledevice&bot_type=jiutian-lan",Service_url,device_id,product_id); + } + else if(type_ai==AI_TYPE_DOU) + { + sprintf(put_buf,"ws://%s/chat/ws?platform=huoshan",Service_url); + } + else if(type_ai==AI_TYPE_WEN) + { + sprintf(put_buf,"ws://%s/chat/ws?platform=baidu",Service_url); + } + else if(type_ai==AI_TYPE_KIMI) + { + sprintf(put_buf,"ws://%s/chat/ws?platform=moonshot",Service_url); + } + else if(type_ai==AI_TYPE_DE) + { + sprintf(put_buf,"ws://%s/chat/ws?platform=huoshan&model=ep-20250205103849-582m7",Service_url); + } + else + { + sprintf(put_buf,"ws://%s/chat/ws?platform=aliyun",Service_url); + } + return false; + } + + +} + +bool chat_api_send_ai_txt(uint8_t type, char *msg) +{ + cJSON *dataJSON = cJSON_CreateObject(); + + if (dataJSON == NULL) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Error creating JSON object\n"); + return false; + } + + cJSON_AddStringToObject(dataJSON, "type", "jiutian-lan"); + + cJSON_AddStringToObject(dataJSON, "content", msg); + + char *jsonString = cJSON_PrintUnformatted(dataJSON); + + if (jsonString == NULL) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Error printing JSON to string\n"); + cJSON_Delete(dataJSON); + return false; + } + + uint32_t data_len = strlen(jsonString); + cJSON_Delete(dataJSON); + + if (data_len == 0) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Error: JSON string length is 0\n"); + cJSON_free(jsonString); + return false; + } + + if (send_websocket_message(chat_client, (uint8_t *)jsonString, strlen(jsonString), WS_TEXT) == 0) + { + chat_reconnect = 0; + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "send_websocket_message succ 0\n"); + cJSON_free(jsonString); + return true; + } + + else + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "send_websocket_message fail\n"); + if (chat_client == NULL) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "chat_client is NULL\n"); + } + cJSON_free(jsonString); + return false; + } +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/code/sdk/zhihui/ai/src/chat_server_enhanced.c b/code/sdk/zhihui/ai/src/chat_server_enhanced.c new file mode 100644 index 000000000..40b9fb48f --- /dev/null +++ b/code/sdk/zhihui/ai/src/chat_server_enhanced.c @@ -0,0 +1,820 @@ +/* + * Copyright (c) CompanyNameMagicTag. 2023-2023. All rights reserved. + * Description: Enhanced Chat Server Implementation - 对话服务增强版 + * Author: AI Development Team + * Create: 2024-12-28 + */ + +#include +#include +#include +#include "chat_server.h" +#include "../../../../application/wearable/ohos_deps/wearable_log.h" +#include "../../../../../open_source/openharmony/third_party/cJSON/cJSON.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ==================== 函数声明 ==================== */ +static ai_service_error_t chat_enhanced_stop(void); + +/* ==================== 内部宏定义 ==================== */ + +#define CHAT_LOG_TAG "CHAT_ENHANCED" +#define CHAT_MAX_HISTORY_SIZE 50 +#define CHAT_MAX_RESPONSE_BUFFER 8192 +#define CHAT_INTERRUPT_TIMEOUT_MS 5000 + +/* ==================== 内部结构体定义 ==================== */ + +/** + * @brief Chat对话状态 + */ +typedef enum { + CHAT_STATE_IDLE = 0, + CHAT_STATE_SENDING, + CHAT_STATE_RECEIVING, + CHAT_STATE_INTERRUPTED +} chat_conversation_state_t; + +/** + * @brief Chat消息历史记录 + */ +typedef struct chat_history_node { + chat_message_t message; + struct chat_history_node *next; +} chat_history_node_t; + +/** + * @brief 增强的Chat上下文 + */ +typedef struct { + chat_server_context_t base; // 基础Chat上下文 + + /* 对话管理 */ + chat_conversation_state_t state; + char current_session_id[64]; + chat_history_node_t *history_head; + uint32_t history_count; + + /* 连续对话支持 */ + bool continuous_mode; + uint32_t last_message_time; + osTimerId_t timeout_timer; + + /* 打断机制 */ + bool interrupt_requested; + osThreadId_t response_thread; + + /* 响应缓冲 */ + char response_buffer[CHAT_MAX_RESPONSE_BUFFER]; + size_t response_length; + bool response_complete; + + /* 同步控制 */ + osMutexId_t state_mutex; + osSemaphoreId_t response_sem; + + /* 回调函数 */ + void (*on_response_start)(void *user_data, const char *session_id); + void (*on_response_partial)(void *user_data, const char *session_id, const char *content); + void (*on_response_end)(void *user_data, const char *session_id, const char *full_content); + void (*on_error)(void *user_data, int error_code, const char *error_msg); + void *user_data; +} chat_enhanced_context_t; + +/* ==================== 全局变量 ==================== */ + +static chat_enhanced_context_t *g_chat_enhanced_context = NULL; + +/* ==================== 内部函数声明 ==================== */ + +static void chat_enhanced_response_thread(void *arg); +static void chat_enhanced_timeout_callback(void *arg); +static ai_service_error_t chat_enhanced_send_message_internal(chat_enhanced_context_t *context, + const chat_message_t *message); +static ai_service_error_t chat_enhanced_parse_response(chat_enhanced_context_t *context, + const char *json_data); +static void chat_enhanced_add_to_history(chat_enhanced_context_t *context, + const chat_message_t *message); +static void chat_enhanced_clear_history(chat_enhanced_context_t *context); +static void chat_enhanced_set_state(chat_enhanced_context_t *context, + chat_conversation_state_t new_state); +static void chat_enhanced_on_websocket_message(void *context, const char *data, size_t length); +static void chat_enhanced_on_websocket_connected(void *context); +static void chat_enhanced_on_websocket_disconnected(void *context); +static void chat_enhanced_on_websocket_error(void *context, int error_code, const char *error_msg); + +/* ==================== 公共接口实现 ==================== */ + +/** + * @brief 初始化增强Chat服务 + */ +ai_service_error_t chat_enhanced_init(const ai_service_config_t *config, + const chat_model_config_t *model_config) +{ + if (config == NULL || model_config == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Invalid parameters", CHAT_LOG_TAG); + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + if (g_chat_enhanced_context != NULL) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Already initialized", CHAT_LOG_TAG); + return AI_SERVICE_ERROR_ALREADY_CONNECTED; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Initializing enhanced Chat service", CHAT_LOG_TAG); + + // 分配上下文内存 + g_chat_enhanced_context = (chat_enhanced_context_t *)malloc(sizeof(chat_enhanced_context_t)); + if (g_chat_enhanced_context == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to allocate context", CHAT_LOG_TAG); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + memset(g_chat_enhanced_context, 0, sizeof(chat_enhanced_context_t)); + + // 初始化基础Chat服务 + ai_service_error_t ret = chat_server_init(&g_chat_enhanced_context->base, + config->server_url, + model_config); + if (ret != AI_SERVICE_OK) { + free(g_chat_enhanced_context); + g_chat_enhanced_context = NULL; + return ret; + } + + // 复制模型配置 + memcpy(&g_chat_enhanced_context->base.model_config, model_config, sizeof(chat_model_config_t)); + + // 初始化状态 + g_chat_enhanced_context->state = CHAT_STATE_IDLE; + g_chat_enhanced_context->continuous_mode = true; + g_chat_enhanced_context->history_head = NULL; + g_chat_enhanced_context->history_count = 0; + + // 创建互斥锁和信号量 + g_chat_enhanced_context->state_mutex = osMutexNew(NULL); + if (g_chat_enhanced_context->state_mutex == NULL) { + chat_server_destroy(&g_chat_enhanced_context->base); + free(g_chat_enhanced_context); + g_chat_enhanced_context = NULL; + return AI_SERVICE_ERROR_INTERNAL; + } + + g_chat_enhanced_context->response_sem = osSemaphoreNew(1, 0, NULL); + if (g_chat_enhanced_context->response_sem == NULL) { + osMutexDelete(g_chat_enhanced_context->state_mutex); + chat_server_destroy(&g_chat_enhanced_context->base); + free(g_chat_enhanced_context); + g_chat_enhanced_context = NULL; + return AI_SERVICE_ERROR_INTERNAL; + } + + // 创建超时定时器 + g_chat_enhanced_context->timeout_timer = osTimerNew(chat_enhanced_timeout_callback, + osTimerOnce, + g_chat_enhanced_context, + NULL); + if (g_chat_enhanced_context->timeout_timer == NULL) { + osSemaphoreDelete(g_chat_enhanced_context->response_sem); + osMutexDelete(g_chat_enhanced_context->state_mutex); + chat_server_destroy(&g_chat_enhanced_context->base); + free(g_chat_enhanced_context); + g_chat_enhanced_context = NULL; + return AI_SERVICE_ERROR_INTERNAL; + } + + // 创建响应处理线程 + osThreadAttr_t thread_attr = { + .name = "chat_response", + .stack_size = 8192, + .priority = osPriorityNormal + }; + + g_chat_enhanced_context->response_thread = osThreadNew(chat_enhanced_response_thread, + g_chat_enhanced_context, + &thread_attr); + if (g_chat_enhanced_context->response_thread == NULL) { + osTimerDelete(g_chat_enhanced_context->timeout_timer); + osSemaphoreDelete(g_chat_enhanced_context->response_sem); + osMutexDelete(g_chat_enhanced_context->state_mutex); + chat_server_destroy(&g_chat_enhanced_context->base); + free(g_chat_enhanced_context); + g_chat_enhanced_context = NULL; + return AI_SERVICE_ERROR_INTERNAL; + } + + // 设置WebSocket回调 + ai_service_set_callbacks(&g_chat_enhanced_context->base.base, + chat_enhanced_on_websocket_connected, + chat_enhanced_on_websocket_disconnected, + chat_enhanced_on_websocket_message, + chat_enhanced_on_websocket_error); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Enhanced Chat service initialized", CHAT_LOG_TAG); + return AI_SERVICE_OK; +} + +/** + * @brief 销毁增强Chat服务 + */ +ai_service_error_t chat_enhanced_destroy(void) +{ + if (g_chat_enhanced_context == NULL) { + return AI_SERVICE_OK; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Destroying enhanced Chat service", CHAT_LOG_TAG); + + // 停止服务 + chat_enhanced_stop(); + + // 终止响应线程 + if (g_chat_enhanced_context->response_thread != NULL) { + osThreadTerminate(g_chat_enhanced_context->response_thread); + } + + // 清理历史记录 + chat_enhanced_clear_history(g_chat_enhanced_context); + + // 清理资源 + if (g_chat_enhanced_context->timeout_timer != NULL) { + osTimerDelete(g_chat_enhanced_context->timeout_timer); + } + + if (g_chat_enhanced_context->response_sem != NULL) { + osSemaphoreDelete(g_chat_enhanced_context->response_sem); + } + + if (g_chat_enhanced_context->state_mutex != NULL) { + osMutexDelete(g_chat_enhanced_context->state_mutex); + } + + // 销毁基础Chat服务 + chat_server_destroy(&g_chat_enhanced_context->base); + + // 释放上下文 + free(g_chat_enhanced_context); + g_chat_enhanced_context = NULL; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Enhanced Chat service destroyed", CHAT_LOG_TAG); + return AI_SERVICE_OK; +} + +/** + * @brief 启动Chat服务 + */ +ai_service_error_t chat_enhanced_start(void) +{ + if (g_chat_enhanced_context == NULL) { + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Starting Chat service", CHAT_LOG_TAG); + + // 启动基础Chat服务 + ai_service_error_t ret = chat_server_start(&g_chat_enhanced_context->base); + if (ret != AI_SERVICE_OK) { + return ret; + } + + chat_enhanced_set_state(g_chat_enhanced_context, CHAT_STATE_IDLE); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Chat service started", CHAT_LOG_TAG); + return AI_SERVICE_OK; +} + +/** + * @brief 停止Chat服务 + */ +ai_service_error_t chat_enhanced_stop(void) +{ + if (g_chat_enhanced_context == NULL) { + return AI_SERVICE_OK; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Stopping Chat service", CHAT_LOG_TAG); + + osMutexAcquire(g_chat_enhanced_context->state_mutex, osWaitForever); + + // 如果正在接收响应,请求打断 + if (g_chat_enhanced_context->state == CHAT_STATE_RECEIVING) { + g_chat_enhanced_context->interrupt_requested = true; + chat_enhanced_set_state(g_chat_enhanced_context, CHAT_STATE_INTERRUPTED); + } + + // 停止超时定时器 + osTimerStop(g_chat_enhanced_context->timeout_timer); + + osMutexRelease(g_chat_enhanced_context->state_mutex); + + // 停止基础Chat服务(这会断开WebSocket连接) + chat_server_stop(&g_chat_enhanced_context->base); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Chat service stopped", CHAT_LOG_TAG); + return AI_SERVICE_OK; +} + +/** + * @brief 创建新会话 + */ +ai_service_error_t chat_enhanced_create_session(const char *session_id) +{ + if (g_chat_enhanced_context == NULL || session_id == NULL) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + osMutexAcquire(g_chat_enhanced_context->state_mutex, osWaitForever); + + // 清理旧的历史记录 + chat_enhanced_clear_history(g_chat_enhanced_context); + + // 设置新的会话ID + strncpy(g_chat_enhanced_context->current_session_id, session_id, + sizeof(g_chat_enhanced_context->current_session_id) - 1); + g_chat_enhanced_context->current_session_id[sizeof(g_chat_enhanced_context->current_session_id) - 1] = '\0'; + + osMutexRelease(g_chat_enhanced_context->state_mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Session created: %s", CHAT_LOG_TAG, session_id); + return AI_SERVICE_OK; +} + +/** + * @brief 发送消息 + */ +ai_service_error_t chat_enhanced_send_message(const char *content, bool wait_response) +{ + if (g_chat_enhanced_context == NULL || content == NULL) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + osMutexAcquire(g_chat_enhanced_context->state_mutex, osWaitForever); + + if (g_chat_enhanced_context->state != CHAT_STATE_IDLE) { + osMutexRelease(g_chat_enhanced_context->state_mutex); + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Chat not idle, current state: %d", + CHAT_LOG_TAG, g_chat_enhanced_context->state); + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + // 创建用户消息 + chat_message_t message = { + .role = CHAT_ROLE_USER, + .content = (char*)content, + .timestamp = osKernelGetTickCount() + }; + + // 添加到历史记录 + chat_enhanced_add_to_history(g_chat_enhanced_context, &message); + + // 发送消息 + ai_service_error_t ret = chat_enhanced_send_message_internal(g_chat_enhanced_context, &message); + if (ret != AI_SERVICE_OK) { + osMutexRelease(g_chat_enhanced_context->state_mutex); + return ret; + } + + chat_enhanced_set_state(g_chat_enhanced_context, CHAT_STATE_SENDING); + g_chat_enhanced_context->last_message_time = osKernelGetTickCount(); + g_chat_enhanced_context->interrupt_requested = false; + g_chat_enhanced_context->response_complete = false; + g_chat_enhanced_context->response_length = 0; + + osMutexRelease(g_chat_enhanced_context->state_mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Message sent: %s", CHAT_LOG_TAG, content); + + // 如果需要等待响应 + if (wait_response) { + osStatus_t status = osSemaphoreAcquire(g_chat_enhanced_context->response_sem, + CHAT_INTERRUPT_TIMEOUT_MS); + if (status != osOK) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Response timeout", CHAT_LOG_TAG); + return AI_SERVICE_ERROR_TIMEOUT; + } + } + + return AI_SERVICE_OK; +} + +/** + * @brief 请求打断当前对话 + */ +ai_service_error_t chat_enhanced_interrupt_conversation(void) +{ + if (g_chat_enhanced_context == NULL) { + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + osMutexAcquire(g_chat_enhanced_context->state_mutex, osWaitForever); + + if (g_chat_enhanced_context->state == CHAT_STATE_RECEIVING) { + g_chat_enhanced_context->interrupt_requested = true; + chat_enhanced_set_state(g_chat_enhanced_context, CHAT_STATE_INTERRUPTED); + + // 断开WebSocket连接来实现打断 + ai_service_disconnect(&g_chat_enhanced_context->base.base); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Conversation interrupted", CHAT_LOG_TAG); + } + + osMutexRelease(g_chat_enhanced_context->state_mutex); + return AI_SERVICE_OK; +} + +/** + * @brief 设置连续对话模式 + */ +ai_service_error_t chat_enhanced_set_continuous_mode(bool enabled) +{ + if (g_chat_enhanced_context == NULL) { + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + osMutexAcquire(g_chat_enhanced_context->state_mutex, osWaitForever); + g_chat_enhanced_context->continuous_mode = enabled; + osMutexRelease(g_chat_enhanced_context->state_mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Continuous mode %s", + CHAT_LOG_TAG, enabled ? "enabled" : "disabled"); + return AI_SERVICE_OK; +} + +/** + * @brief 获取对话历史 + */ +const chat_message_t* chat_enhanced_get_history(uint32_t *count) +{ + if (g_chat_enhanced_context == NULL || count == NULL) { + return NULL; + } + + osMutexAcquire(g_chat_enhanced_context->state_mutex, osWaitForever); + *count = g_chat_enhanced_context->history_count; + + // 这里简化处理,实际应用中可能需要返回数组 + chat_history_node_t *node = g_chat_enhanced_context->history_head; + osMutexRelease(g_chat_enhanced_context->state_mutex); + + return node ? &node->message : NULL; +} + +/** + * @brief 清除对话历史 + */ +ai_service_error_t chat_enhanced_clear_session_history(void) +{ + if (g_chat_enhanced_context == NULL) { + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + osMutexAcquire(g_chat_enhanced_context->state_mutex, osWaitForever); + chat_enhanced_clear_history(g_chat_enhanced_context); + osMutexRelease(g_chat_enhanced_context->state_mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Session history cleared", CHAT_LOG_TAG); + return AI_SERVICE_OK; +} + +/** + * @brief 设置回调函数 + */ +void chat_enhanced_set_callbacks(void (*on_response_start)(void *user_data, const char *session_id), + void (*on_response_partial)(void *user_data, const char *session_id, const char *content), + void (*on_response_end)(void *user_data, const char *session_id, const char *full_content), + void (*on_error)(void *user_data, int error_code, const char *error_msg), + void *user_data) +{ + if (g_chat_enhanced_context == NULL) { + return; + } + + osMutexAcquire(g_chat_enhanced_context->state_mutex, osWaitForever); + g_chat_enhanced_context->on_response_start = on_response_start; + g_chat_enhanced_context->on_response_partial = on_response_partial; + g_chat_enhanced_context->on_response_end = on_response_end; + g_chat_enhanced_context->on_error = on_error; + g_chat_enhanced_context->user_data = user_data; + osMutexRelease(g_chat_enhanced_context->state_mutex); +} + +/* ==================== 内部函数实现 ==================== */ + +/** + * @brief 响应处理线程 + */ +static void chat_enhanced_response_thread(void *arg) +{ + chat_enhanced_context_t *context = (chat_enhanced_context_t *)arg; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Response thread started", CHAT_LOG_TAG); + + while (true) { + // 等待响应信号 + osSemaphoreAcquire(context->response_sem, osWaitForever); + + osMutexAcquire(context->state_mutex, osWaitForever); + + if (context->response_complete && !context->interrupt_requested) { + // 创建助手消息并添加到历史 + chat_message_t assistant_message = { + .role = CHAT_ROLE_ASSISTANT, + .content = context->response_buffer, + .timestamp = osKernelGetTickCount() + }; + + chat_enhanced_add_to_history(context, &assistant_message); + + // 调用完成回调 + if (context->on_response_end != NULL) { + context->on_response_end(context->user_data, + context->current_session_id, + context->response_buffer); + } + + chat_enhanced_set_state(context, CHAT_STATE_IDLE); + } + + osMutexRelease(context->state_mutex); + } +} + +/** + * @brief 超时回调 + */ +static void chat_enhanced_timeout_callback(void *arg) +{ + chat_enhanced_context_t *context = (chat_enhanced_context_t *)arg; + + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Response timeout", CHAT_LOG_TAG); + + osMutexAcquire(context->state_mutex, osWaitForever); + + if (context->state == CHAT_STATE_RECEIVING) { + context->interrupt_requested = true; + chat_enhanced_set_state(context, CHAT_STATE_INTERRUPTED); + + // 调用错误回调 + if (context->on_error != NULL) { + context->on_error(context->user_data, AI_SERVICE_ERROR_TIMEOUT, "Response timeout"); + } + } + + osMutexRelease(context->state_mutex); +} + +/** + * @brief 发送消息内部实现 + */ +static ai_service_error_t chat_enhanced_send_message_internal(chat_enhanced_context_t *context, + const chat_message_t *message) +{ + // 构建包含历史记录的完整消息 + cJSON *root = cJSON_CreateObject(); + cJSON *messages = cJSON_CreateArray(); + + if (root == NULL || messages == NULL) { + if (root) cJSON_Delete(root); + if (messages) cJSON_Delete(messages); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + // 添加历史消息 + chat_history_node_t *node = context->history_head; + while (node != NULL) { + cJSON *msg_obj = cJSON_CreateObject(); + if (msg_obj != NULL) { + cJSON_AddStringToObject(msg_obj, "role", + node->message.role == CHAT_ROLE_USER ? "user" : "assistant"); + cJSON_AddStringToObject(msg_obj, "content", node->message.content); + cJSON_AddItemToArray(messages, msg_obj); + } + node = node->next; + } + + // 设置消息数组 + cJSON_AddItemToObject(root, "messages", messages); + + // 添加模型配置 + cJSON_AddStringToObject(root, "model", context->base.model_config.model_name); + cJSON_AddNumberToObject(root, "temperature", context->base.model_config.temperature); + cJSON_AddNumberToObject(root, "max_tokens", context->base.model_config.max_tokens); + cJSON_AddBoolToObject(root, "stream", true); + + char *json_string = cJSON_Print(root); + cJSON_Delete(root); + + if (json_string == NULL) { + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + // 发送消息 + ai_service_error_t ret = ai_service_send_data(&context->base.base, json_string, strlen(json_string)); + free(json_string); + + return ret; +} + +/** + * @brief 解析响应 + */ +static ai_service_error_t chat_enhanced_parse_response(chat_enhanced_context_t *context, + const char *json_data) +{ + cJSON *root = cJSON_Parse(json_data); + if (root == NULL) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + cJSON *type_obj = cJSON_GetObjectItem(root, "type"); + if (type_obj == NULL || !cJSON_IsString(type_obj)) { + cJSON_Delete(root); + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + const char *type = cJSON_GetStringValue(type_obj); + cJSON *content_obj = cJSON_GetObjectItem(root, "content"); + const char *content = cJSON_IsString(content_obj) ? cJSON_GetStringValue(content_obj) : NULL; + + if (strcmp(type, "start") == 0) { + // 响应开始 + chat_enhanced_set_state(context, CHAT_STATE_RECEIVING); + context->response_length = 0; + context->response_buffer[0] = '\0'; + + // 启动超时定时器 + osTimerStart(context->timeout_timer, CHAT_INTERRUPT_TIMEOUT_MS); + + // 调用开始回调 + if (context->on_response_start != NULL) { + context->on_response_start(context->user_data, context->current_session_id); + } + + } else if (strcmp(type, "middle") == 0 && content != NULL) { + // 响应中间部分 + if (context->state == CHAT_STATE_RECEIVING && !context->interrupt_requested) { + // 追加内容到缓冲区 + size_t content_len = strlen(content); + if (context->response_length + content_len < CHAT_MAX_RESPONSE_BUFFER - 1) { + strcat(context->response_buffer, content); + context->response_length += content_len; + + // 调用部分回调 + if (context->on_response_partial != NULL) { + context->on_response_partial(context->user_data, + context->current_session_id, + content); + } + } + } + + } else if (strcmp(type, "end") == 0) { + // 响应结束 + osTimerStop(context->timeout_timer); + context->response_complete = true; + + // 通知响应线程 + osSemaphoreRelease(context->response_sem); + } + + cJSON_Delete(root); + return AI_SERVICE_OK; +} + +/** + * @brief 添加消息到历史记录 + */ +static void chat_enhanced_add_to_history(chat_enhanced_context_t *context, + const chat_message_t *message) +{ + if (context->history_count >= CHAT_MAX_HISTORY_SIZE) { + // 删除最旧的消息 + chat_history_node_t *old_tail = context->history_head; + if (old_tail != NULL) { + while (old_tail->next && old_tail->next->next) { + old_tail = old_tail->next; + } + if (old_tail->next) { + free(old_tail->next->message.content); + free(old_tail->next); + old_tail->next = NULL; + context->history_count--; + } + } + } + + // 创建新节点 + chat_history_node_t *new_node = (chat_history_node_t *)malloc(sizeof(chat_history_node_t)); + if (new_node == NULL) { + return; + } + + // 复制消息内容 + new_node->message = *message; + new_node->message.content = (char *)malloc(strlen(message->content) + 1); + if (new_node->message.content == NULL) { + free(new_node); + return; + } + strcpy(new_node->message.content, message->content); + + // 插入到头部 + new_node->next = context->history_head; + context->history_head = new_node; + context->history_count++; +} + +/** + * @brief 清除历史记录 + */ +static void chat_enhanced_clear_history(chat_enhanced_context_t *context) +{ + chat_history_node_t *node = context->history_head; + while (node != NULL) { + chat_history_node_t *next = node->next; + free(node->message.content); + free(node); + node = next; + } + + context->history_head = NULL; + context->history_count = 0; +} + +/** + * @brief 设置对话状态 + */ +static void chat_enhanced_set_state(chat_enhanced_context_t *context, + chat_conversation_state_t new_state) +{ + chat_conversation_state_t old_state = context->state; + context->state = new_state; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] State changed: %d -> %d", + CHAT_LOG_TAG, old_state, new_state); +} + +/* ==================== WebSocket回调函数 ==================== */ + +static void chat_enhanced_on_websocket_connected(void *context) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] WebSocket connected", CHAT_LOG_TAG); +} + +static void chat_enhanced_on_websocket_disconnected(void *context) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] WebSocket disconnected", CHAT_LOG_TAG); + + if (g_chat_enhanced_context != NULL) { + osMutexAcquire(g_chat_enhanced_context->state_mutex, osWaitForever); + + // 如果是因为打断而断开,重新连接 + if (g_chat_enhanced_context->state == CHAT_STATE_INTERRUPTED) { + chat_enhanced_set_state(g_chat_enhanced_context, CHAT_STATE_IDLE); + + // 如果启用了连续模式,自动重连 + if (g_chat_enhanced_context->continuous_mode) { + ai_service_connect(&g_chat_enhanced_context->base.base); + } + } + + osMutexRelease(g_chat_enhanced_context->state_mutex); + } +} + +static void chat_enhanced_on_websocket_message(void *context, const char *data, size_t length) +{ + if (g_chat_enhanced_context == NULL || data == NULL || length == 0) { + return; + } + + // 解析响应消息 + chat_enhanced_parse_response(g_chat_enhanced_context, data); +} + +static void chat_enhanced_on_websocket_error(void *context, int error_code, const char *error_msg) +{ + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] WebSocket error: %d - %s", + CHAT_LOG_TAG, error_code, error_msg ? error_msg : "Unknown"); + + if (g_chat_enhanced_context != NULL) { + osMutexAcquire(g_chat_enhanced_context->state_mutex, osWaitForever); + + chat_enhanced_set_state(g_chat_enhanced_context, CHAT_STATE_IDLE); + + // 调用错误回调 + if (g_chat_enhanced_context->on_error != NULL) { + g_chat_enhanced_context->on_error(g_chat_enhanced_context->user_data, + error_code, error_msg); + } + + osMutexRelease(g_chat_enhanced_context->state_mutex); + } +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/code/sdk/zhihui/ai/src/tts_server.c b/code/sdk/zhihui/ai/src/tts_server.c new file mode 100644 index 000000000..09336b4ee --- /dev/null +++ b/code/sdk/zhihui/ai/src/tts_server.c @@ -0,0 +1,1278 @@ +/* + * TTS (Text-to-Speech) 服务实现 + * 负责建立与TTS服务器的WebSocket连接管理,上传文本,接收音频流并播放 + */ + +#include +#include +#include +#include "tts_server.h" +#include "../../../../application/wearable/ohos_deps/wearable_log.h" +#include "../../../../../open_source/openharmony/third_party/cJSON/cJSON.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* 内部宏定义 */ +#define TTS_LOG_TAG "TTS_SERVER" +#define TTS_PLAYBACK_THREAD_STACK_SIZE 4096 + +/* 内部函数声明 */ +static void tts_server_on_connected(void *context); +static void tts_server_on_disconnected(void *context); +static void tts_server_on_message_received(void *context, const char *data, size_t length); +static void tts_server_on_error(void *context, int error_code, const char *error_msg); +static void tts_server_playback_thread(void *arg); +static ai_service_error_t tts_server_parse_synthesis_result(tts_server_context_t *context, + const char *json_data, + tts_synthesis_result_t *result); +static ai_service_error_t tts_server_create_synthesis_message(tts_server_context_t *context, + const tts_synthesis_request_t *request, + char *output, size_t max_len); +static ai_service_error_t tts_server_create_stop_message(tts_server_context_t *context, + char *output, size_t max_len); +static ai_service_error_t tts_server_play_audio_data(tts_server_context_t *context, + const char *audio_data, size_t data_length); +static ai_service_error_t tts_server_decode_audio_data(const char *encoded_data, + char **decoded_data, size_t *decoded_length); + +/* 公共接口实现 */ + +ai_service_error_t tts_server_init(tts_server_context_t *context, + const char *server_url, + const tts_audio_config_t *audio_config, + const tts_voice_config_t *voice_config) +{ + if (!context || !server_url || !audio_config || !voice_config) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + // 清零上下文 + memset(context, 0, sizeof(tts_server_context_t)); + + // 创建基础服务配置 + ai_service_config_t base_config = {0}; + strncpy(base_config.server_url, server_url, sizeof(base_config.server_url) - 1); + base_config.server_port = 80; + strncpy(base_config.protocol, "tts-protocol", sizeof(base_config.protocol) - 1); + base_config.connect_timeout_ms = AI_CONNECT_TIMEOUT_MS; + base_config.receive_timeout_ms = AI_RECEIVE_TIMEOUT_MS; + base_config.reconnect_interval_ms = AI_RECONNECT_INTERVAL_MS; + base_config.heartbeat_interval_ms = AI_HEARTBEAT_INTERVAL_MS; + base_config.max_retry_count = AI_MAX_RETRY_COUNT; + base_config.auto_reconnect = true; + + // 初始化基础服务 + ai_service_error_t ret = ai_service_init(&context->base, "TTS_SERVER", &base_config); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to init base service: %d", TTS_LOG_TAG, ret); + return ret; + } + + // 复制TTS特定配置 + memcpy(&context->audio_config, audio_config, sizeof(tts_audio_config_t)); + memcpy(&context->voice_config, voice_config, sizeof(tts_voice_config_t)); + + // 分配音频缓冲区 + context->audio_buffer_size = TTS_MAX_AUDIO_BUFFER_SIZE; + context->audio_buffer = (char *)malloc(context->audio_buffer_size); + if (!context->audio_buffer) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to allocate audio buffer", TTS_LOG_TAG); + ai_service_destroy(&context->base); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + context->audio_buffer_used = 0; + + // 创建音频缓冲区互斥锁 + context->audio_buffer_mutex = osMutexNew(NULL); + if (!context->audio_buffer_mutex) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create audio buffer mutex", TTS_LOG_TAG); + free(context->audio_buffer); + ai_service_destroy(&context->base); + return AI_SERVICE_ERROR_INTERNAL; + } + + // 创建播放信号量 + context->playback_sem = osSemaphoreNew(1, 0, NULL); + if (!context->playback_sem) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create playback semaphore", TTS_LOG_TAG); + osMutexDelete(context->audio_buffer_mutex); + free(context->audio_buffer); + ai_service_destroy(&context->base); + return AI_SERVICE_ERROR_INTERNAL; + } + + // 初始化播放状态 + context->playback_state = TTS_PLAYBACK_STATE_IDLE; + context->synthesis_active = false; + context->current_sequence = 0; + memset(context->current_session_id, 0, sizeof(context->current_session_id)); + + // 设置基础服务回调 + ai_service_set_callbacks(&context->base, + tts_server_on_connected, + tts_server_on_disconnected, + tts_server_on_message_received, + tts_server_on_error); + + // 设置用户数据指向TTS上下文 + ai_service_set_user_data(&context->base, context); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS server initialized", TTS_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t tts_server_destroy(tts_server_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + // 停止服务 + tts_server_stop(context); + + // 删除播放信号量 + if (context->playback_sem) { + osSemaphoreDelete(context->playback_sem); + context->playback_sem = NULL; + } + + // 删除音频缓冲区互斥锁 + if (context->audio_buffer_mutex) { + osMutexDelete(context->audio_buffer_mutex); + context->audio_buffer_mutex = NULL; + } + + // 释放音频缓冲区 + if (context->audio_buffer) { + free(context->audio_buffer); + context->audio_buffer = NULL; + } + + // 销毁基础服务 + ai_service_destroy(&context->base); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS server destroyed", TTS_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t tts_server_start(tts_server_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + ai_service_error_t ret = ai_service_start(&context->base); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to start TTS server: %d", TTS_LOG_TAG, ret); + return ret; + } + + // 创建播放线程 + osThreadAttr_t thread_attr = {0}; + thread_attr.name = "TTS_Playback"; + thread_attr.stack_size = TTS_PLAYBACK_THREAD_STACK_SIZE; + thread_attr.priority = osPriorityNormal; + + context->playback_thread = osThreadNew(tts_server_playback_thread, context, &thread_attr); + if (!context->playback_thread) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create playback thread", TTS_LOG_TAG); + ai_service_stop(&context->base); + return AI_SERVICE_ERROR_INTERNAL; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS server started", TTS_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t tts_server_stop(tts_server_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + // 停止当前合成 + if (context->synthesis_active) { + tts_server_stop_synthesis(context); + } + + // 停止播放 + tts_server_stop_playback(context); + + // 等待播放线程结束 + if (context->playback_thread) { + osThreadTerminate(context->playback_thread); + context->playback_thread = NULL; + } + + ai_service_error_t ret = ai_service_stop(&context->base); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to stop TTS server: %d", TTS_LOG_TAG, ret); + return ret; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS server stopped", TTS_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t tts_server_synthesize_text(tts_server_context_t *context, + const char *text, + const char *session_id) +{ + if (!context || !text) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + tts_synthesis_request_t request = {0}; + request.text = (char *)text; + request.use_ssml = false; + request.voice_config = context->voice_config; + + if (session_id && strlen(session_id) > 0) { + strncpy(request.session_id, session_id, sizeof(request.session_id) - 1); + } else { + ai_service_generate_uuid(request.session_id, sizeof(request.session_id)); + } + + return tts_server_synthesize_request(context, &request); +} + +ai_service_error_t tts_server_synthesize_ssml(tts_server_context_t *context, + const char *ssml, + const char *session_id) +{ + if (!context || !ssml) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + tts_synthesis_request_t request = {0}; + request.ssml = (char *)ssml; + request.use_ssml = true; + request.voice_config = context->voice_config; + + if (session_id && strlen(session_id) > 0) { + strncpy(request.session_id, session_id, sizeof(request.session_id) - 1); + } else { + ai_service_generate_uuid(request.session_id, sizeof(request.session_id)); + } + + return tts_server_synthesize_request(context, &request); +} + +ai_service_error_t tts_server_synthesize_request(tts_server_context_t *context, + const tts_synthesis_request_t *request) +{ + if (!context || !request) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + if (!ai_service_is_connected(&context->base)) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] TTS server is not connected", TTS_LOG_TAG); + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + if (context->synthesis_active) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Synthesis is already active", TTS_LOG_TAG); + return AI_SERVICE_ERROR_ALREADY_CONNECTED; + } + + // 复制会话ID + strncpy(context->current_session_id, request->session_id, sizeof(context->current_session_id) - 1); + + // 创建合成消息 + char *synthesis_message = (char *)malloc(TTS_MAX_TEXT_LENGTH + TTS_MAX_SSML_LENGTH + 1024); + if (!synthesis_message) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to allocate synthesis message buffer", TTS_LOG_TAG); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + ai_service_error_t ret = tts_server_create_synthesis_message(context, request, + synthesis_message, + TTS_MAX_TEXT_LENGTH + TTS_MAX_SSML_LENGTH + 1024); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create synthesis message: %d", TTS_LOG_TAG, ret); + free(synthesis_message); + return ret; + } + + // 发送合成消息 + ret = ai_service_send_text(&context->base, synthesis_message); + free(synthesis_message); + + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to send synthesis message: %d", TTS_LOG_TAG, ret); + return ret; + } + + context->synthesis_active = true; + context->current_sequence = 0; + context->synthesis_sessions++; + + // 统计发送的文本长度 + if (request->use_ssml && request->ssml) { + context->total_text_sent += strlen(request->ssml); + } else if (request->text) { + context->total_text_sent += strlen(request->text); + } + + // 调用用户回调 + if (context->on_synthesis_start) { + context->on_synthesis_start(context, context->current_session_id); + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Synthesis started, session: %s", + TTS_LOG_TAG, context->current_session_id); + + return AI_SERVICE_OK; +} + +ai_service_error_t tts_server_stop_synthesis(tts_server_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + if (!context->synthesis_active) { + return AI_SERVICE_OK; + } + + // 创建停止合成消息 + char stop_message[512]; + ai_service_error_t ret = tts_server_create_stop_message(context, stop_message, sizeof(stop_message)); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to create stop message: %d", TTS_LOG_TAG, ret); + } else { + // 发送停止消息 + ret = ai_service_send_text(&context->base, stop_message); + if (ret != AI_SERVICE_OK) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to send stop message: %d", TTS_LOG_TAG, ret); + } + } + + // 调用用户回调 + if (context->on_synthesis_complete) { + context->on_synthesis_complete(context, context->current_session_id); + } + + context->synthesis_active = false; + memset(context->current_session_id, 0, sizeof(context->current_session_id)); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Synthesis stopped", TTS_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t tts_server_start_playback(tts_server_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + if (context->playback_state == TTS_PLAYBACK_STATE_PLAYING) { + return AI_SERVICE_OK; + } + + context->playback_state = TTS_PLAYBACK_STATE_PLAYING; + context->playback_sessions++; + + // 通知播放线程开始播放 + osSemaphoreRelease(context->playback_sem); + + // 调用用户回调 + if (context->on_playback_start) { + context->on_playback_start(context); + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Playback started", TTS_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t tts_server_pause_playback(tts_server_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + if (context->playback_state != TTS_PLAYBACK_STATE_PLAYING) { + return AI_SERVICE_OK; + } + + context->playback_state = TTS_PLAYBACK_STATE_PAUSED; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Playback paused", TTS_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t tts_server_resume_playback(tts_server_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + if (context->playback_state != TTS_PLAYBACK_STATE_PAUSED) { + return AI_SERVICE_OK; + } + + context->playback_state = TTS_PLAYBACK_STATE_PLAYING; + + // 通知播放线程恢复播放 + osSemaphoreRelease(context->playback_sem); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Playback resumed", TTS_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t tts_server_stop_playback(tts_server_context_t *context) +{ + if (!context) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + if (context->playback_state == TTS_PLAYBACK_STATE_IDLE || + context->playback_state == TTS_PLAYBACK_STATE_STOPPED) { + return AI_SERVICE_OK; + } + + context->playback_state = TTS_PLAYBACK_STATE_STOPPED; + + // 清空音频缓冲区 + osMutexAcquire(context->audio_buffer_mutex, osWaitForever); + context->audio_buffer_used = 0; + osMutexRelease(context->audio_buffer_mutex); + + // 调用用户回调 + if (context->on_playback_complete) { + context->on_playback_complete(context); + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Playback stopped", TTS_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t tts_server_set_audio_config(tts_server_context_t *context, + const tts_audio_config_t *audio_config) +{ + if (!context || !audio_config) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + memcpy(&context->audio_config, audio_config, sizeof(tts_audio_config_t)); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Audio config updated", TTS_LOG_TAG); + + return AI_SERVICE_OK; +} + +ai_service_error_t tts_server_set_voice_config(tts_server_context_t *context, + const tts_voice_config_t *voice_config) +{ + if (!context || !voice_config) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + memcpy(&context->voice_config, voice_config, sizeof(tts_voice_config_t)); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Voice config updated", TTS_LOG_TAG); + + return AI_SERVICE_OK; +} + +const char* tts_server_get_current_session_id(tts_server_context_t *context) +{ + if (!context || !context->synthesis_active) { + return NULL; + } + + return context->current_session_id; +} + +bool tts_server_is_synthesis_active(tts_server_context_t *context) +{ + return context ? context->synthesis_active : false; +} + +tts_playback_state_t tts_server_get_playback_state(tts_server_context_t *context) +{ + return context ? context->playback_state : TTS_PLAYBACK_STATE_ERROR; +} + +void tts_server_set_callbacks(tts_server_context_t *context, + void (*on_synthesis_start)(void *context, const char *session_id), + void (*on_synthesis_result)(void *context, const tts_synthesis_result_t *result), + void (*on_synthesis_complete)(void *context, const char *session_id), + void (*on_playback_start)(void *context), + void (*on_playback_complete)(void *context), + void (*on_playback_error)(void *context, int error_code)) +{ + if (!context) { + return; + } + + context->on_synthesis_start = on_synthesis_start; + context->on_synthesis_result = on_synthesis_result; + context->on_synthesis_complete = on_synthesis_complete; + context->on_playback_start = on_playback_start; + context->on_playback_complete = on_playback_complete; + context->on_playback_error = on_playback_error; +} + +void tts_server_get_stats(tts_server_context_t *context, + uint32_t *total_text_sent, + uint32_t *total_audio_received, + uint32_t *synthesis_sessions, + uint32_t *playback_sessions) +{ + if (!context) { + return; + } + + if (total_text_sent) { + *total_text_sent = context->total_text_sent; + } + + if (total_audio_received) { + *total_audio_received = context->total_audio_received; + } + + if (synthesis_sessions) { + *synthesis_sessions = context->synthesis_sessions; + } + + if (playback_sessions) { + *playback_sessions = context->playback_sessions; + } +} + +/* 工具函数实现 */ + +void tts_server_create_default_audio_config(tts_audio_config_t *config) +{ + if (!config) { + return; + } + + config->format = TTS_AUDIO_FORMAT_PCM; + config->sample_rate = 16000; + config->channels = 1; + config->bits_per_sample = 16; + config->bitrate = 128000; +} + +void tts_server_create_default_voice_config(tts_voice_config_t *config) +{ + if (!config) { + return; + } + + strncpy(config->voice_name, "default", sizeof(config->voice_name) - 1); + config->voice_type = TTS_VOICE_TYPE_FEMALE; + config->language = TTS_LANGUAGE_ZH_CN; + config->emotion = TTS_EMOTION_NEUTRAL; + config->speed = 1.0f; + config->pitch = 1.0f; + config->volume = 0.8f; +} + +void tts_server_free_synthesis_result(tts_synthesis_result_t *result) +{ + if (!result) { + return; + } + + if (result->audio_data) { + free(result->audio_data); + result->audio_data = NULL; + } +} + +const char* tts_server_audio_format_to_string(tts_audio_format_t format) +{ + switch (format) { + case TTS_AUDIO_FORMAT_PCM: return "PCM"; + case TTS_AUDIO_FORMAT_WAV: return "WAV"; + case TTS_AUDIO_FORMAT_MP3: return "MP3"; + case TTS_AUDIO_FORMAT_AAC: return "AAC"; + case TTS_AUDIO_FORMAT_OGG: return "OGG"; + default: return "UNKNOWN"; + } +} + +const char* tts_server_voice_type_to_string(tts_voice_type_t voice_type) +{ + switch (voice_type) { + case TTS_VOICE_TYPE_MALE: return "MALE"; + case TTS_VOICE_TYPE_FEMALE: return "FEMALE"; + case TTS_VOICE_TYPE_CHILD: return "CHILD"; + case TTS_VOICE_TYPE_ELDERLY: return "ELDERLY"; + default: return "UNKNOWN"; + } +} + +const char* tts_server_language_to_string(tts_language_t language) +{ + switch (language) { + case TTS_LANGUAGE_ZH_CN: return "zh-CN"; + case TTS_LANGUAGE_EN_US: return "en-US"; + case TTS_LANGUAGE_JA_JP: return "ja-JP"; + case TTS_LANGUAGE_KO_KR: return "ko-KR"; + default: return "unknown"; + } +} + +const char* tts_server_emotion_to_string(tts_emotion_t emotion) +{ + switch (emotion) { + case TTS_EMOTION_NEUTRAL: return "NEUTRAL"; + case TTS_EMOTION_HAPPY: return "HAPPY"; + case TTS_EMOTION_SAD: return "SAD"; + case TTS_EMOTION_ANGRY: return "ANGRY"; + case TTS_EMOTION_EXCITED: return "EXCITED"; + case TTS_EMOTION_CALM: return "CALM"; + default: return "UNKNOWN"; + } +} + +const char* tts_server_playback_state_to_string(tts_playback_state_t state) +{ + switch (state) { + case TTS_PLAYBACK_STATE_IDLE: return "IDLE"; + case TTS_PLAYBACK_STATE_PLAYING: return "PLAYING"; + case TTS_PLAYBACK_STATE_PAUSED: return "PAUSED"; + case TTS_PLAYBACK_STATE_STOPPED: return "STOPPED"; + case TTS_PLAYBACK_STATE_ERROR: return "ERROR"; + default: return "UNKNOWN"; + } +} + +/* 内部函数实现 */ + +static void tts_server_on_connected(void *context) +{ + tts_server_context_t *tts_context = (tts_server_context_t *)ai_service_get_user_data((ai_service_context_t *)context); + if (!tts_context) { + return; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS server connected", TTS_LOG_TAG); +} + +static void tts_server_on_disconnected(void *context) +{ + tts_server_context_t *tts_context = (tts_server_context_t *)ai_service_get_user_data((ai_service_context_t *)context); + if (!tts_context) { + return; + } + + // 停止当前合成 + if (tts_context->synthesis_active) { + tts_context->synthesis_active = false; + if (tts_context->on_synthesis_complete) { + tts_context->on_synthesis_complete(tts_context, tts_context->current_session_id); + } + } + + // 停止播放 + tts_server_stop_playback(tts_context); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS server disconnected", TTS_LOG_TAG); +} + +static void tts_server_on_message_received(void *context, const char *data, size_t length) +{ + tts_server_context_t *tts_context = (tts_server_context_t *)ai_service_get_user_data((ai_service_context_t *)context); + if (!tts_context || !data || length == 0) { + return; + } + + // 解析合成结果 + tts_synthesis_result_t result = {0}; + ai_service_error_t ret = tts_server_parse_synthesis_result(tts_context, data, &result); + if (ret == AI_SERVICE_OK) { + tts_context->total_audio_received += result.audio_length; + + // 调用用户回调 + if (tts_context->on_synthesis_result) { + tts_context->on_synthesis_result(tts_context, &result); + } + + // 播放音频数据 + if (result.audio_data && result.audio_length > 0) { + tts_server_play_audio_data(tts_context, result.audio_data, result.audio_length); + } + + // 检查是否为最终结果 + if (result.is_final) { + tts_context->synthesis_active = false; + if (tts_context->on_synthesis_complete) { + tts_context->on_synthesis_complete(tts_context, result.session_id); + } + } + + // 释放结果内存 + tts_server_free_synthesis_result(&result); + } + + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "[%s] Received message: %.*s", + TTS_LOG_TAG, (int)length, data); +} + +static void tts_server_on_error(void *context, int error_code, const char *error_msg) +{ + tts_server_context_t *tts_context = (tts_server_context_t *)ai_service_get_user_data((ai_service_context_t *)context); + if (!tts_context) { + return; + } + + // 调用用户回调 + if (tts_context->on_playback_error) { + tts_context->on_playback_error(tts_context, error_code); + } + + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] TTS server error: %d - %s", + TTS_LOG_TAG, error_code, error_msg ? error_msg : "Unknown"); +} + +static void tts_server_playback_thread(void *arg) +{ + tts_server_context_t *context = (tts_server_context_t *)arg; + if (!context) { + return; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Playback thread started", TTS_LOG_TAG); + + while (ai_service_get_state(&context->base) != AI_SERVICE_STATE_IDLE) { + // 等待播放信号 + osStatus_t status = osSemaphoreAcquire(context->playback_sem, 1000); + if (status != osOK) { + continue; + } + + // 播放音频缓冲区中的数据 + osMutexAcquire(context->audio_buffer_mutex, osWaitForever); + + if (context->audio_buffer_used > 0 && context->playback_state == TTS_PLAYBACK_STATE_PLAYING) { + // 这里应该调用实际的音频播放接口 + // 为了演示,我们只是简单地清空缓冲区 + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "[%s] Playing %zu bytes of audio", + TTS_LOG_TAG, context->audio_buffer_used); + + context->audio_buffer_used = 0; + + // 播放完成后检查是否还有更多数据 + if (context->audio_buffer_used == 0 && !context->synthesis_active) { + context->playback_state = TTS_PLAYBACK_STATE_IDLE; + if (context->on_playback_complete) { + context->on_playback_complete(context); + } + } + } + + osMutexRelease(context->audio_buffer_mutex); + + osDelay(10); // 短暂延迟 + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Playback thread stopped", TTS_LOG_TAG); +} + +static ai_service_error_t tts_server_parse_synthesis_result(tts_server_context_t *context, + const char *json_data, + tts_synthesis_result_t *result) +{ + if (!context || !json_data || !result) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + cJSON *json = cJSON_Parse(json_data); + if (!json) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to parse JSON", TTS_LOG_TAG); + return AI_SERVICE_ERROR_INTERNAL; + } + + // 解析音频数据 + cJSON *audio_data_item = cJSON_GetObjectItem(json, "audio_data"); + if (audio_data_item && cJSON_IsString(audio_data_item)) { + const char *encoded_data = cJSON_GetStringValue(audio_data_item); + if (encoded_data) { + ai_service_error_t ret = tts_server_decode_audio_data(encoded_data, + &result->audio_data, + &result->audio_length); + if (ret != AI_SERVICE_OK) { + cJSON_Delete(json); + return ret; + } + } + } + + // 解析会话ID + cJSON *session_id_item = cJSON_GetObjectItem(json, "session_id"); + if (session_id_item && cJSON_IsString(session_id_item)) { + const char *session_id = cJSON_GetStringValue(session_id_item); + if (session_id) { + strncpy(result->session_id, session_id, sizeof(result->session_id) - 1); + } + } + + // 解析是否为最终结果 + cJSON *is_final_item = cJSON_GetObjectItem(json, "is_final"); + if (is_final_item && cJSON_IsBool(is_final_item)) { + result->is_final = cJSON_IsTrue(is_final_item); + } + + // 解析序列号 + cJSON *sequence_item = cJSON_GetObjectItem(json, "sequence_number"); + if (sequence_item && cJSON_IsNumber(sequence_item)) { + result->sequence_number = (uint32_t)cJSON_GetNumberValue(sequence_item); + } + + cJSON_Delete(json); + + return AI_SERVICE_OK; +} + +static ai_service_error_t tts_server_create_synthesis_message(tts_server_context_t *context, + const tts_synthesis_request_t *request, + char *output, size_t max_len) +{ + if (!context || !request || !output || max_len == 0) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + cJSON *json = cJSON_CreateObject(); + if (!json) { + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + cJSON_AddStringToObject(json, "type", "synthesize"); + cJSON_AddStringToObject(json, "session_id", request->session_id); + + if (request->use_ssml && request->ssml) { + cJSON_AddStringToObject(json, "ssml", request->ssml); + cJSON_AddBoolToObject(json, "use_ssml", true); + } else if (request->text) { + cJSON_AddStringToObject(json, "text", request->text); + cJSON_AddBoolToObject(json, "use_ssml", false); + } + + // 添加音频配置 + cJSON *audio_config = cJSON_CreateObject(); + cJSON_AddStringToObject(audio_config, "format", tts_server_audio_format_to_string(context->audio_config.format)); + cJSON_AddNumberToObject(audio_config, "sample_rate", context->audio_config.sample_rate); + cJSON_AddNumberToObject(audio_config, "channels", context->audio_config.channels); + cJSON_AddNumberToObject(audio_config, "bits_per_sample", context->audio_config.bits_per_sample); + cJSON_AddNumberToObject(audio_config, "bitrate", context->audio_config.bitrate); + cJSON_AddItemToObject(json, "audio_config", audio_config); + + // 添加语音配置 + cJSON *voice_config = cJSON_CreateObject(); + cJSON_AddStringToObject(voice_config, "voice_name", request->voice_config.voice_name); + cJSON_AddStringToObject(voice_config, "voice_type", tts_server_voice_type_to_string(request->voice_config.voice_type)); + cJSON_AddStringToObject(voice_config, "language", tts_server_language_to_string(request->voice_config.language)); + cJSON_AddStringToObject(voice_config, "emotion", tts_server_emotion_to_string(request->voice_config.emotion)); + cJSON_AddNumberToObject(voice_config, "speed", request->voice_config.speed); + cJSON_AddNumberToObject(voice_config, "pitch", request->voice_config.pitch); + cJSON_AddNumberToObject(voice_config, "volume", request->voice_config.volume); + cJSON_AddItemToObject(json, "voice_config", voice_config); + + char *json_string = cJSON_Print(json); + if (!json_string) { + cJSON_Delete(json); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + if (strlen(json_string) >= max_len) { + free(json_string); + cJSON_Delete(json); + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + strcpy(output, json_string); + + free(json_string); + cJSON_Delete(json); + + return AI_SERVICE_OK; +} + +static ai_service_error_t tts_server_create_stop_message(tts_server_context_t *context, + char *output, size_t max_len) +{ + if (!context || !output || max_len == 0) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + cJSON *json = cJSON_CreateObject(); + if (!json) { + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + cJSON_AddStringToObject(json, "type", "stop_synthesis"); + cJSON_AddStringToObject(json, "session_id", context->current_session_id); + cJSON_AddNumberToObject(json, "timestamp", ai_service_get_timestamp()); + + char *json_string = cJSON_Print(json); + if (!json_string) { + cJSON_Delete(json); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + if (strlen(json_string) >= max_len) { + free(json_string); + cJSON_Delete(json); + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + strcpy(output, json_string); + + free(json_string); + cJSON_Delete(json); + + return AI_SERVICE_OK; +} + +static ai_service_error_t tts_server_play_audio_data(tts_server_context_t *context, + const char *audio_data, size_t data_length) +{ + if (!context || !audio_data || data_length == 0) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + osMutexAcquire(context->audio_buffer_mutex, osWaitForever); + + // 检查缓冲区空间 + if (context->audio_buffer_used + data_length > context->audio_buffer_size) { + // 缓冲区不足,扩展或清空 + if (data_length > context->audio_buffer_size) { + // 数据太大,重新分配缓冲区 + char *new_buffer = (char *)realloc(context->audio_buffer, data_length * 2); + if (!new_buffer) { + osMutexRelease(context->audio_buffer_mutex); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + context->audio_buffer = new_buffer; + context->audio_buffer_size = data_length * 2; + context->audio_buffer_used = 0; + } else { + // 清空缓冲区为新数据腾出空间 + context->audio_buffer_used = 0; + } + } + + // 复制音频数据到缓冲区 + memcpy(context->audio_buffer + context->audio_buffer_used, audio_data, data_length); + context->audio_buffer_used += data_length; + + osMutexRelease(context->audio_buffer_mutex); + + // 如果当前没有播放,开始播放 + if (context->playback_state == TTS_PLAYBACK_STATE_IDLE) { + tts_server_start_playback(context); + } else if (context->playback_state == TTS_PLAYBACK_STATE_PLAYING) { + // 通知播放线程有新数据 + osSemaphoreRelease(context->playback_sem); + } + + return AI_SERVICE_OK; +} + +static ai_service_error_t tts_server_decode_audio_data(const char *encoded_data, + char **decoded_data, size_t *decoded_length) +{ + if (!encoded_data || !decoded_data || !decoded_length) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + // 简单的十六进制解码(与ASR服务中的编码对应) + size_t encoded_len = strlen(encoded_data); + if (encoded_len % 2 != 0) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + size_t decoded_len = encoded_len / 2; + char *decoded = (char *)malloc(decoded_len); + if (!decoded) { + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + for (size_t i = 0; i < decoded_len; i++) { + unsigned int byte_val; + if (sscanf(&encoded_data[i * 2], "%2x", &byte_val) != 1) { + free(decoded); + return AI_SERVICE_ERROR_INVALID_PARAM; + } + decoded[i] = (char)byte_val; + } + + *decoded_data = decoded; + *decoded_length = decoded_len; + + return AI_SERVICE_OK; +} +/********************************************************************************************************************* */ + +/* + * @brief 启动TTS服务 + * @note 通过自定义命令消息启动TTS任务 + */ +void tts_api_start_tts(void) +{ + char *jsonString = + "{\"header\": \ + { \"event\": \"StartSynthesis\",\ + \"trace_id\": \"\"},\ + \"payload\": \ + {\"voice\":\"xiaoyun\",\ + \"format\":\"mp3\",\ + \"sample_rate\":16000,\ + \"volume\":80,\ + \"speech_rate\":0}\ + }"; + + printf("uapi_start_tts :%s\r\n",jsonString); + + + if (send_websocket_message(tts_client,( uint8_t *)jsonString,strlen(jsonString),WS_TEXT)==0) + { + memset_s(tts_task_id_buf,128,0,128); + tts_is_ready=0; + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"send_websocket_message succ 0\n"); + } + +} + +/* + * @brief 停止TTS服务 + * @note 通过自定义命令消息停止TTS任务 + */ +void tts_api_stop_tts(void) +{ + char *jsonString = + " { \ + \"header\": { \ + \"event\": \"StopSynthesis\", \ + \"trace_id\": \"\" \ + } \ + } "; + + printf("uapi_start_tts : %s\r\n",jsonString); + + if (send_websocket_message(tts_client,( uint8_t *)jsonString,strlen(jsonString),WS_TEXT)==0) + { + tts_is_ready=0; + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"send_websocket_message succ 0\n"); + } + +} + +/* + * @brief 初始化TTS服务URL + * @note 根据设备token初始化TTS服务URL + */ +bool tts_api_ws_url_init(char * put_buf) +{ + // 测试用例 + // tts_client = init_websocket_client("ws://118.145.158.207:22080/tts/stream?audio_buffer_size=512", NULL, 0); + + char token[64]={0}; + if(read_dev_token(token)) + { + // /tts/stream?audio_buffer_size=1024 + sprintf(put_buf,"ws://amp.raisound.com/tts/stream?dt=%s&platform=aliyun&noCache=1",token); + return true; + } + else + { + return false; + } + +} + +/* + * @brief 启动TTS单次任务 + * @note 通过自定义命令消息启动TTS单次任务 测试用例 + */ +void tts_api_start_tts_single(void) +{ + + char *jsonString = + " { \"header\": { \ + \"event\": \"LongTextStream\",\ + \"trace_id\": \"3fa6b9e6a30b407ea38ad7ae69e6c291\" \ + }, \ + \"payload\": { \ + \"voice\":\"xiaoyun\", \ + \"format\":\"pcm\", \ + \"sample_rate\":16000, \ + \"volume\":50, \ + \"speech_rate\":0, \ + \"text\":\"深圳市北科瑞声科技股份有限公司,是由国家重大人才计划特聘专家刘轶博士创办的国内人 \ + 工智能音频领军企业,专注于行业智能音频交互技术,拥有全链条核心自主知识产权,汇集世界一流的研发运营团队。\" \ + } } "; + + printf("uapi_start_tts : %s\r\n",jsonString); + + if (send_websocket_message(tts_client,(uint8_t *)jsonString,strlen(jsonString),WS_TEXT)==0) + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"send_websocket_message succ 0\n"); +} + +/* + * @brief 启动TTS单次任务 + * @param buf 文本内容 + * @note 通过自定义命令消息启动TTS单次任务 + */ +bool tts_api_start_tts_single_txt(char *buf) +{ + + + if (tts_is_ready != 1) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "uapi_start_tts_single_txt no tts_is_ready \n"); + return false; + } + // 创建根JSON对象 + cJSON* dataJSON = cJSON_CreateObject(); + if (dataJSON == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Error creating root JSON object\n"); + return false; + } + + // 创建header对象并添加字段 + cJSON* header = cJSON_CreateObject(); + if (header == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Error creating header JSON object\n"); + cJSON_Delete(dataJSON); // 释放已分配的内存 + return false; + } + cJSON_AddStringToObject(header, "event", "RunSynthesis"); + cJSON_AddStringToObject(header, "trace_id", tts_task_id_buf); + + // 创建payload对象并添加字段 + cJSON* payload = cJSON_CreateObject(); + if (payload == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Error creating payload JSON object\n"); + cJSON_Delete(header); // 释放已分配的内存 + cJSON_Delete(dataJSON); + return false; + } + cJSON_AddStringToObject(payload, "text", buf); + + // 将header和payload添加到根对象dataJSON中 + cJSON_AddItemToObject(dataJSON, "header", header); + cJSON_AddItemToObject(dataJSON, "payload", payload); + + // 将JSON对象转换为字符串 + char* jsonString = cJSON_PrintUnformatted(dataJSON); + if (jsonString == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Error converting JSON to string\n"); + cJSON_Delete(dataJSON); // 释放JSON对象 + return false; + } + + // 发送WebSocket消息 + bool result = false; + if (send_websocket_message(tts_client, (uint8_t*)jsonString, strlen(jsonString), WS_TEXT) == 0) { + tts_reconnect = 0; + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "send_websocket_message succ 0\n"); + result = true; + } + else + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "send_websocket_message fail -1\n"); + } + + // 释放资源 + cJSON_Delete(dataJSON); // 释放JSON对象(会自动释放header和payload) + free(jsonString); // 释放cJSON_Print生成的字符串 + return result; +} + +// char *tts_txt_str= + + +/* + * @brief 发送TTS文本 + * @param msg 文本内容 + * @note 通过自定义命令消息启动TTS单次任务 测试用例 + */ +void tts_api_send_tts_txt(char *msg) +{ + char *jsonString = + "{ \ + \"header\": { \ + \"event\": \"RunSynthesis\", \ + \"trace_id\": \"3fa6b9e6a30b407ea38ad7ae69e6c291\"\ + }, \ + \"payload\": { \ + \"text\":\"深圳市北科瑞声科技股份有限公司,是由国家重大人才计划特聘专家刘轶博士创办的国内人工智能音频领军企业,\ + 专注于行业智能音频交互技术,拥有全链条核心自主知识产权,汇集世界一流的研发运营团队。\" \ + }\ + }"; + + + if (send_websocket_message(tts_client,( uint8_t *)jsonString,strlen(jsonString),WS_TEXT)==0) + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"send_websocket_message succ 0\n"); + else + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"send_websocket_message fail 1\n"); +} + +// {"header":{"event":"StartedSynthesis","msg":"success","status":20000000,"task_id":"8e8996f9148b66738309602ec3219412"}} +/* + * @brief 解析TTS响应 + * @param buf 响应内容 + * @note 解析TTS响应 + */ +bool tts_api_msg_analysis(char *buf) +{ + cJSON* root = cJSON_Parse((const char *)buf); + if (root == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"解析失败\n"); + return false; + } + char *event_str=NULL; + char *task_id_str=NULL; + char *msg_str=NULL; + + + + cJSON* header = cJSON_GetObjectItem(root, "header"); + if(header) + { + cJSON* event = cJSON_GetObjectItem(header, "event"); + if (event && cJSON_IsString(event)) { + event_str= event->valuestring; + } + + cJSON* msg = cJSON_GetObjectItem(header, "msg"); + if (msg && cJSON_IsString(msg)) { + msg_str= msg->valuestring; + + if(strcmp("StartedSynthesis",event_str)==0 && strcmp("success",msg_str)==0) + { + tts_is_ready=1; + } + } + + cJSON* task_id = cJSON_GetObjectItem(header, "task_id"); + + if (task_id && cJSON_IsString(task_id)) + { + memcpy_s(tts_task_id_buf,128,task_id->valuestring,strlen(task_id->valuestring)); + } + } + cJSON_Delete(root); + return true; +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/code/sdk/zhihui/ai/src/tts_server_enhanced.c b/code/sdk/zhihui/ai/src/tts_server_enhanced.c new file mode 100644 index 000000000..96e3a45bc --- /dev/null +++ b/code/sdk/zhihui/ai/src/tts_server_enhanced.c @@ -0,0 +1,799 @@ +/* + * Copyright (c) CompanyNameMagicTag. 2023-2023. All rights reserved. + * Description: Enhanced TTS Server Implementation - 双向流式合成 + * Author: AI Development Team + * Create: 2024-12-28 + */ + +#include +#include +#include +#include "tts_server.h" +#include "ai_media.h" +#include "../../../../application/wearable/ohos_deps/wearable_log.h" +#include "../../../../../open_source/openharmony/third_party/cJSON/cJSON.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ==================== 函数声明 ==================== */ +static ai_service_error_t tts_enhanced_stop(void); + +/* ==================== 内部宏定义 ==================== */ + +#define TTS_LOG_TAG "TTS_ENHANCED" +#define TTS_MAX_TEXT_CHUNK_SIZE 512 +#define TTS_TRACE_ID_LENGTH 32 +#define TTS_PLAYBACK_BUFFER_SIZE 8192 + +/* ==================== 内部结构体定义 ==================== */ + +/** + * @brief TTS流式合成状态 + */ +typedef enum { + TTS_STREAM_STATE_IDLE = 0, + TTS_STREAM_STATE_STARTING, + TTS_STREAM_STATE_RUNNING, + TTS_STREAM_STATE_STOPPING, + TTS_STREAM_STATE_STOPPED +} tts_stream_state_t; + +/** + * @brief TTS文本块结构 + */ +typedef struct { + char *text; + size_t length; + bool is_final; +} tts_text_chunk_t; + +/** + * @brief 增强的TTS上下文 + */ +typedef struct { + tts_server_context_t base; // 基础TTS上下文 + + /* 流式合成相关 */ + tts_stream_state_t stream_state; + char trace_id[TTS_TRACE_ID_LENGTH]; + osMessageQueueId_t text_queue; // 文本队列 + osThreadId_t synthesis_thread; // 合成线程 + + /* 播放服务管理 */ + bool playback_service_active; // 播放服务状态 + bool playback_control_enabled; // 播放控制开关 + osMessageQueueId_t audio_queue; // 音频数据队列 + osThreadId_t playback_thread; // 播放线程 + + /* 同步控制 */ + osMutexId_t stream_mutex; + osSemaphoreId_t synthesis_sem; + + /* 统计信息 */ + uint32_t total_text_chunks; + uint32_t total_audio_chunks; + uint32_t synthesis_start_time; +} tts_enhanced_context_t; + +/* ==================== 全局变量 ==================== */ + +static tts_enhanced_context_t *g_tts_enhanced_context = NULL; + +/* ==================== 内部函数声明 ==================== */ + +static void tts_enhanced_synthesis_thread(void *arg); +static void tts_enhanced_playback_thread(void *arg); +static ai_service_error_t tts_enhanced_send_start_synthesis(tts_enhanced_context_t *context); +static ai_service_error_t tts_enhanced_send_run_synthesis(tts_enhanced_context_t *context, const char *text); +static ai_service_error_t tts_enhanced_send_stop_synthesis(tts_enhanced_context_t *context); +static ai_service_error_t tts_enhanced_parse_synthesis_event(tts_enhanced_context_t *context, + const char *json_data); +static void tts_enhanced_generate_trace_id(char *trace_id, size_t max_len); +static void tts_enhanced_on_websocket_message(void *context, const char *data, size_t length); +static void tts_enhanced_on_websocket_connected(void *context); +static void tts_enhanced_on_websocket_disconnected(void *context); +static void tts_enhanced_on_websocket_error(void *context, int error_code, const char *error_msg); + +/* ==================== 公共接口实现 ==================== */ + +/** + * @brief 初始化增强TTS服务 + */ +ai_service_error_t tts_enhanced_init(const ai_service_config_t *config, + const tts_audio_config_t *audio_config, + const tts_voice_config_t *voice_config) +{ + if (config == NULL || audio_config == NULL || voice_config == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Invalid parameters", TTS_LOG_TAG); + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + if (g_tts_enhanced_context != NULL) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Already initialized", TTS_LOG_TAG); + return AI_SERVICE_ERROR_ALREADY_CONNECTED; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Initializing enhanced TTS service", TTS_LOG_TAG); + + // 分配上下文内存 + g_tts_enhanced_context = (tts_enhanced_context_t *)malloc(sizeof(tts_enhanced_context_t)); + if (g_tts_enhanced_context == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] Failed to allocate context", TTS_LOG_TAG); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + memset(g_tts_enhanced_context, 0, sizeof(tts_enhanced_context_t)); + + // 初始化基础TTS服务 + ai_service_error_t ret = tts_server_init(&g_tts_enhanced_context->base, + config->server_url, + audio_config, + voice_config); + if (ret != AI_SERVICE_OK) { + free(g_tts_enhanced_context); + g_tts_enhanced_context = NULL; + return ret; + } + + // 复制配置 + memcpy(&g_tts_enhanced_context->base.audio_config, audio_config, sizeof(tts_audio_config_t)); + memcpy(&g_tts_enhanced_context->base.voice_config, voice_config, sizeof(tts_voice_config_t)); + + // 初始化流式合成状态 + g_tts_enhanced_context->stream_state = TTS_STREAM_STATE_IDLE; + tts_enhanced_generate_trace_id(g_tts_enhanced_context->trace_id, + sizeof(g_tts_enhanced_context->trace_id)); + + // 创建互斥锁和信号量 + g_tts_enhanced_context->stream_mutex = osMutexNew(NULL); + if (g_tts_enhanced_context->stream_mutex == NULL) { + tts_server_destroy(&g_tts_enhanced_context->base); + free(g_tts_enhanced_context); + g_tts_enhanced_context = NULL; + return AI_SERVICE_ERROR_INTERNAL; + } + + g_tts_enhanced_context->synthesis_sem = osSemaphoreNew(1, 0, NULL); + if (g_tts_enhanced_context->synthesis_sem == NULL) { + osMutexDelete(g_tts_enhanced_context->stream_mutex); + tts_server_destroy(&g_tts_enhanced_context->base); + free(g_tts_enhanced_context); + g_tts_enhanced_context = NULL; + return AI_SERVICE_ERROR_INTERNAL; + } + + // 创建文本队列 + g_tts_enhanced_context->text_queue = osMessageQueueNew(32, sizeof(tts_text_chunk_t), NULL); + if (g_tts_enhanced_context->text_queue == NULL) { + osSemaphoreDelete(g_tts_enhanced_context->synthesis_sem); + osMutexDelete(g_tts_enhanced_context->stream_mutex); + tts_server_destroy(&g_tts_enhanced_context->base); + free(g_tts_enhanced_context); + g_tts_enhanced_context = NULL; + return AI_SERVICE_ERROR_INTERNAL; + } + + // 创建音频队列 + g_tts_enhanced_context->audio_queue = osMessageQueueNew(16, TTS_PLAYBACK_BUFFER_SIZE, NULL); + if (g_tts_enhanced_context->audio_queue == NULL) { + osMessageQueueDelete(g_tts_enhanced_context->text_queue); + osSemaphoreDelete(g_tts_enhanced_context->synthesis_sem); + osMutexDelete(g_tts_enhanced_context->stream_mutex); + tts_server_destroy(&g_tts_enhanced_context->base); + free(g_tts_enhanced_context); + g_tts_enhanced_context = NULL; + return AI_SERVICE_ERROR_INTERNAL; + } + + // 创建合成线程 + osThreadAttr_t synthesis_attr = { + .name = "tts_synthesis", + .stack_size = 8192, + .priority = osPriorityNormal + }; + + g_tts_enhanced_context->synthesis_thread = osThreadNew(tts_enhanced_synthesis_thread, + g_tts_enhanced_context, + &synthesis_attr); + if (g_tts_enhanced_context->synthesis_thread == NULL) { + osMessageQueueDelete(g_tts_enhanced_context->audio_queue); + osMessageQueueDelete(g_tts_enhanced_context->text_queue); + osSemaphoreDelete(g_tts_enhanced_context->synthesis_sem); + osMutexDelete(g_tts_enhanced_context->stream_mutex); + tts_server_destroy(&g_tts_enhanced_context->base); + free(g_tts_enhanced_context); + g_tts_enhanced_context = NULL; + return AI_SERVICE_ERROR_INTERNAL; + } + + // 创建播放线程 + osThreadAttr_t playback_attr = { + .name = "tts_playback", + .stack_size = 4096, + .priority = osPriorityHigh + }; + + g_tts_enhanced_context->playback_thread = osThreadNew(tts_enhanced_playback_thread, + g_tts_enhanced_context, + &playback_attr); + if (g_tts_enhanced_context->playback_thread == NULL) { + osThreadTerminate(g_tts_enhanced_context->synthesis_thread); + osMessageQueueDelete(g_tts_enhanced_context->audio_queue); + osMessageQueueDelete(g_tts_enhanced_context->text_queue); + osSemaphoreDelete(g_tts_enhanced_context->synthesis_sem); + osMutexDelete(g_tts_enhanced_context->stream_mutex); + tts_server_destroy(&g_tts_enhanced_context->base); + free(g_tts_enhanced_context); + g_tts_enhanced_context = NULL; + return AI_SERVICE_ERROR_INTERNAL; + } + + // 设置WebSocket回调 + ai_service_set_callbacks(&g_tts_enhanced_context->base.base, + tts_enhanced_on_websocket_connected, + tts_enhanced_on_websocket_disconnected, + tts_enhanced_on_websocket_message, + tts_enhanced_on_websocket_error); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Enhanced TTS service initialized", TTS_LOG_TAG); + return AI_SERVICE_OK; +} + +/** + * @brief 销毁增强TTS服务 + */ +ai_service_error_t tts_enhanced_destroy(void) +{ + if (g_tts_enhanced_context == NULL) { + return AI_SERVICE_OK; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Destroying enhanced TTS service", TTS_LOG_TAG); + + // 停止服务 + tts_enhanced_stop(); + + // 终止线程 + if (g_tts_enhanced_context->synthesis_thread != NULL) { + osThreadTerminate(g_tts_enhanced_context->synthesis_thread); + } + + if (g_tts_enhanced_context->playback_thread != NULL) { + osThreadTerminate(g_tts_enhanced_context->playback_thread); + } + + // 清理资源 + if (g_tts_enhanced_context->audio_queue != NULL) { + osMessageQueueDelete(g_tts_enhanced_context->audio_queue); + } + + if (g_tts_enhanced_context->text_queue != NULL) { + osMessageQueueDelete(g_tts_enhanced_context->text_queue); + } + + if (g_tts_enhanced_context->synthesis_sem != NULL) { + osSemaphoreDelete(g_tts_enhanced_context->synthesis_sem); + } + + if (g_tts_enhanced_context->stream_mutex != NULL) { + osMutexDelete(g_tts_enhanced_context->stream_mutex); + } + + // 销毁基础TTS服务 + tts_server_destroy(&g_tts_enhanced_context->base); + + // 释放上下文 + free(g_tts_enhanced_context); + g_tts_enhanced_context = NULL; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Enhanced TTS service destroyed", TTS_LOG_TAG); + return AI_SERVICE_OK; +} + +/** + * @brief 启动TTS服务 + */ +ai_service_error_t tts_enhanced_start(void) +{ + if (g_tts_enhanced_context == NULL) { + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Starting TTS service", TTS_LOG_TAG); + + // 启动基础TTS服务 + ai_service_error_t ret = tts_server_start(&g_tts_enhanced_context->base); + if (ret != AI_SERVICE_OK) { + return ret; + } + + // 启动播放服务 + ai_media_play_start((uint8_t*)"pcm"); + if (1) { // ai_media_play_start returns void, assume success + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Media playback started", TTS_LOG_TAG); + } else { + g_tts_enhanced_context->playback_service_active = true; + } + + g_tts_enhanced_context->playback_control_enabled = true; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS service started", TTS_LOG_TAG); + return AI_SERVICE_OK; +} + +/** + * @brief 停止TTS服务 + */ +ai_service_error_t tts_enhanced_stop(void) +{ + if (g_tts_enhanced_context == NULL) { + return AI_SERVICE_OK; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Stopping TTS service", TTS_LOG_TAG); + + osMutexAcquire(g_tts_enhanced_context->stream_mutex, osWaitForever); + + // 停止流式合成 + if (g_tts_enhanced_context->stream_state == TTS_STREAM_STATE_RUNNING) { + tts_enhanced_send_stop_synthesis(g_tts_enhanced_context); + g_tts_enhanced_context->stream_state = TTS_STREAM_STATE_STOPPING; + } + + // 禁用播放控制 + g_tts_enhanced_context->playback_control_enabled = false; + + // 停止播放服务 + if (g_tts_enhanced_context->playback_service_active) { + ai_media_play_stop(); + g_tts_enhanced_context->playback_service_active = false; + } + + osMutexRelease(g_tts_enhanced_context->stream_mutex); + + // 停止基础TTS服务 + tts_server_stop(&g_tts_enhanced_context->base); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] TTS service stopped", TTS_LOG_TAG); + return AI_SERVICE_OK; +} + +/** + * @brief 开始流式合成 + */ +ai_service_error_t tts_enhanced_start_synthesis(const char *session_id) +{ + if (g_tts_enhanced_context == NULL) { + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + osMutexAcquire(g_tts_enhanced_context->stream_mutex, osWaitForever); + + if (g_tts_enhanced_context->stream_state != TTS_STREAM_STATE_IDLE) { + osMutexRelease(g_tts_enhanced_context->stream_mutex); + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Synthesis already active", TTS_LOG_TAG); + return AI_SERVICE_ERROR_ALREADY_CONNECTED; + } + + // 生成新的trace_id + tts_enhanced_generate_trace_id(g_tts_enhanced_context->trace_id, + sizeof(g_tts_enhanced_context->trace_id)); + + // 发送StartSynthesis消息 + ai_service_error_t ret = tts_enhanced_send_start_synthesis(g_tts_enhanced_context); + if (ret != AI_SERVICE_OK) { + osMutexRelease(g_tts_enhanced_context->stream_mutex); + return ret; + } + + g_tts_enhanced_context->stream_state = TTS_STREAM_STATE_STARTING; + g_tts_enhanced_context->synthesis_start_time = osKernelGetTickCount(); + g_tts_enhanced_context->total_text_chunks = 0; + g_tts_enhanced_context->total_audio_chunks = 0; + + osMutexRelease(g_tts_enhanced_context->stream_mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Synthesis started: %s", TTS_LOG_TAG, session_id); + return AI_SERVICE_OK; +} + +/** + * @brief 发送文本进行合成 + */ +ai_service_error_t tts_enhanced_send_text(const char *text, bool is_final) +{ + if (g_tts_enhanced_context == NULL || text == NULL) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + osMutexAcquire(g_tts_enhanced_context->stream_mutex, osWaitForever); + + if (g_tts_enhanced_context->stream_state != TTS_STREAM_STATE_RUNNING && + g_tts_enhanced_context->stream_state != TTS_STREAM_STATE_STARTING) { + osMutexRelease(g_tts_enhanced_context->stream_mutex); + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Synthesis not active", TTS_LOG_TAG); + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + // 创建文本块 + tts_text_chunk_t chunk = { + .text = (char *)malloc(strlen(text) + 1), + .length = strlen(text), + .is_final = is_final + }; + + if (chunk.text == NULL) { + osMutexRelease(g_tts_enhanced_context->stream_mutex); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + strcpy(chunk.text, text); + + // 发送到文本队列 + osStatus_t status = osMessageQueuePut(g_tts_enhanced_context->text_queue, &chunk, 0, 0); + if (status != osOK) { + free(chunk.text); + osMutexRelease(g_tts_enhanced_context->stream_mutex); + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "[%s] Text queue full", TTS_LOG_TAG); + return AI_SERVICE_ERROR_INTERNAL; + } + + g_tts_enhanced_context->total_text_chunks++; + osMutexRelease(g_tts_enhanced_context->stream_mutex); + + // 通知合成线程 + osSemaphoreRelease(g_tts_enhanced_context->synthesis_sem); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Text sent: %s (final: %d)", TTS_LOG_TAG, text, is_final); + return AI_SERVICE_OK; +} + +/** + * @brief 停止流式合成 + */ +ai_service_error_t tts_enhanced_stop_synthesis(void) +{ + if (g_tts_enhanced_context == NULL) { + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + osMutexAcquire(g_tts_enhanced_context->stream_mutex, osWaitForever); + + if (g_tts_enhanced_context->stream_state == TTS_STREAM_STATE_IDLE || + g_tts_enhanced_context->stream_state == TTS_STREAM_STATE_STOPPED) { + osMutexRelease(g_tts_enhanced_context->stream_mutex); + return AI_SERVICE_OK; + } + + // 发送StopSynthesis消息 + tts_enhanced_send_stop_synthesis(g_tts_enhanced_context); + g_tts_enhanced_context->stream_state = TTS_STREAM_STATE_STOPPING; + + osMutexRelease(g_tts_enhanced_context->stream_mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Synthesis stopped", TTS_LOG_TAG); + return AI_SERVICE_OK; +} + +/** + * @brief 启用/禁用播放控制 + */ +ai_service_error_t tts_enhanced_set_playback_control(bool enabled) +{ + if (g_tts_enhanced_context == NULL) { + return AI_SERVICE_ERROR_NOT_CONNECTED; + } + + osMutexAcquire(g_tts_enhanced_context->stream_mutex, osWaitForever); + g_tts_enhanced_context->playback_control_enabled = enabled; + osMutexRelease(g_tts_enhanced_context->stream_mutex); + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Playback control %s", + TTS_LOG_TAG, enabled ? "enabled" : "disabled"); + return AI_SERVICE_OK; +} + +/* ==================== 内部函数实现 ==================== */ + +/** + * @brief 合成线程 + */ +static void tts_enhanced_synthesis_thread(void *arg) +{ + tts_enhanced_context_t *context = (tts_enhanced_context_t *)arg; + tts_text_chunk_t chunk; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Synthesis thread started", TTS_LOG_TAG); + + while (true) { + // 等待文本数据 + osSemaphoreAcquire(context->synthesis_sem, osWaitForever); + + // 从队列获取文本块 + osStatus_t status = osMessageQueueGet(context->text_queue, &chunk, NULL, 0); + if (status == osOK) { + // 发送RunSynthesis消息 + tts_enhanced_send_run_synthesis(context, chunk.text); + + // 如果是最后一块,发送停止消息 + if (chunk.is_final) { + osDelay(100); // 等待一下再发送停止消息 + tts_enhanced_send_stop_synthesis(context); + } + + // 释放文本内存 + free(chunk.text); + } + } +} + +/** + * @brief 播放线程 + */ +static void tts_enhanced_playback_thread(void *arg) +{ + tts_enhanced_context_t *context = (tts_enhanced_context_t *)arg; + uint8_t audio_buffer[TTS_PLAYBACK_BUFFER_SIZE]; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Playback thread started", TTS_LOG_TAG); + + while (true) { + // 从音频队列获取数据 + osStatus_t status = osMessageQueueGet(context->audio_queue, audio_buffer, NULL, 1000); + if (status == osOK) { + // 检查播放控制 + osMutexAcquire(context->stream_mutex, osWaitForever); + bool can_play = context->playback_control_enabled && context->playback_service_active; + osMutexRelease(context->stream_mutex); + + if (can_play) { + // 播放音频数据 + ai_media_play_buffer(audio_buffer, TTS_PLAYBACK_BUFFER_SIZE); + } + } + } +} + +/** + * @brief 发送StartSynthesis消息 + */ +static ai_service_error_t tts_enhanced_send_start_synthesis(tts_enhanced_context_t *context) +{ + cJSON *root = cJSON_CreateObject(); + cJSON *header = cJSON_CreateObject(); + cJSON *payload = cJSON_CreateObject(); + + if (root == NULL || header == NULL || payload == NULL) { + if (root) cJSON_Delete(root); + if (header) cJSON_Delete(header); + if (payload) cJSON_Delete(payload); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + // 构建header + cJSON_AddStringToObject(header, "event", "StartSynthesis"); + cJSON_AddStringToObject(header, "trace_id", context->trace_id); + + // 构建payload + cJSON_AddStringToObject(payload, "voice", + tts_server_voice_type_to_string(context->base.voice_config.voice_type)); + cJSON_AddStringToObject(payload, "format", "WAV"); + cJSON_AddNumberToObject(payload, "sample_rate", context->base.audio_config.sample_rate); + cJSON_AddNumberToObject(payload, "volume", context->base.voice_config.volume); + cJSON_AddNumberToObject(payload, "speech_rate", context->base.voice_config.speed); + + // 组装消息 + cJSON_AddItemToObject(root, "header", header); + cJSON_AddItemToObject(root, "payload", payload); + + char *json_string = cJSON_Print(root); + cJSON_Delete(root); + + if (json_string == NULL) { + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + // 发送消息 + ai_service_error_t ret = ai_service_send_data(&context->base.base, json_string, strlen(json_string)); + free(json_string); + + return ret; +} + +/** + * @brief 发送RunSynthesis消息 + */ +static ai_service_error_t tts_enhanced_send_run_synthesis(tts_enhanced_context_t *context, const char *text) +{ + cJSON *root = cJSON_CreateObject(); + cJSON *header = cJSON_CreateObject(); + cJSON *payload = cJSON_CreateObject(); + + if (root == NULL || header == NULL || payload == NULL) { + if (root) cJSON_Delete(root); + if (header) cJSON_Delete(header); + if (payload) cJSON_Delete(payload); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + // 构建header + cJSON_AddStringToObject(header, "event", "RunSynthesis"); + cJSON_AddStringToObject(header, "trace_id", context->trace_id); + + // 构建payload + cJSON_AddStringToObject(payload, "text", text); + + // 组装消息 + cJSON_AddItemToObject(root, "header", header); + cJSON_AddItemToObject(root, "payload", payload); + + char *json_string = cJSON_Print(root); + cJSON_Delete(root); + + if (json_string == NULL) { + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + // 发送消息 + ai_service_error_t ret = ai_service_send_data(&context->base.base, json_string, strlen(json_string)); + free(json_string); + + return ret; +} + +/** + * @brief 发送StopSynthesis消息 + */ +static ai_service_error_t tts_enhanced_send_stop_synthesis(tts_enhanced_context_t *context) +{ + cJSON *root = cJSON_CreateObject(); + cJSON *header = cJSON_CreateObject(); + + if (root == NULL || header == NULL) { + if (root) cJSON_Delete(root); + if (header) cJSON_Delete(header); + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + // 构建header + cJSON_AddStringToObject(header, "event", "StopSynthesis"); + cJSON_AddStringToObject(header, "trace_id", context->trace_id); + + // 组装消息 + cJSON_AddItemToObject(root, "header", header); + + char *json_string = cJSON_Print(root); + cJSON_Delete(root); + + if (json_string == NULL) { + return AI_SERVICE_ERROR_MEMORY_ALLOC; + } + + // 发送消息 + ai_service_error_t ret = ai_service_send_data(&context->base.base, json_string, strlen(json_string)); + free(json_string); + + return ret; +} + +/** + * @brief 解析合成事件 + */ +static ai_service_error_t tts_enhanced_parse_synthesis_event(tts_enhanced_context_t *context, + const char *json_data) +{ + cJSON *root = cJSON_Parse(json_data); + if (root == NULL) { + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + cJSON *header = cJSON_GetObjectItem(root, "header"); + if (header == NULL) { + cJSON_Delete(root); + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + cJSON *event = cJSON_GetObjectItem(header, "event"); + if (event == NULL || !cJSON_IsString(event)) { + cJSON_Delete(root); + return AI_SERVICE_ERROR_INVALID_PARAM; + } + + const char *event_str = cJSON_GetStringValue(event); + + if (strcmp(event_str, "SentenceBegin") == 0) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Sentence begin", TTS_LOG_TAG); + + } else if (strcmp(event_str, "SentenceSynthesis") == 0) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Sentence synthesis", TTS_LOG_TAG); + context->total_audio_chunks++; + + } else if (strcmp(event_str, "SentenceEnd") == 0) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Sentence end", TTS_LOG_TAG); + + } else if (strcmp(event_str, "EndSynthesis") == 0) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] Synthesis completed", TTS_LOG_TAG); + + osMutexAcquire(context->stream_mutex, osWaitForever); + context->stream_state = TTS_STREAM_STATE_IDLE; + osMutexRelease(context->stream_mutex); + } + + cJSON_Delete(root); + return AI_SERVICE_OK; +} + +/** + * @brief 生成trace_id + */ +static void tts_enhanced_generate_trace_id(char *trace_id, size_t max_len) +{ + if (trace_id == NULL || max_len == 0) { + return; + } + + uint32_t timestamp = osKernelGetTickCount(); + static uint32_t counter = 0; + + snprintf(trace_id, max_len, "%08x%08x%08x%08x", + timestamp, ++counter, rand(), rand()); +} + +/* ==================== WebSocket回调函数 ==================== */ + +static void tts_enhanced_on_websocket_connected(void *context) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] WebSocket connected", TTS_LOG_TAG); +} + +static void tts_enhanced_on_websocket_disconnected(void *context) +{ + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "[%s] WebSocket disconnected", TTS_LOG_TAG); + + if (g_tts_enhanced_context != NULL) { + osMutexAcquire(g_tts_enhanced_context->stream_mutex, osWaitForever); + g_tts_enhanced_context->stream_state = TTS_STREAM_STATE_IDLE; + osMutexRelease(g_tts_enhanced_context->stream_mutex); + } +} + +static void tts_enhanced_on_websocket_message(void *context, const char *data, size_t length) +{ + if (g_tts_enhanced_context == NULL || data == NULL || length == 0) { + return; + } + + // 检查是否为JSON数据 + if (data[0] == '{') { + // JSON消息,解析事件 + tts_enhanced_parse_synthesis_event(g_tts_enhanced_context, data); + } else { + // 二进制音频数据 + osMutexAcquire(g_tts_enhanced_context->stream_mutex, osWaitForever); + bool can_queue = g_tts_enhanced_context->playback_control_enabled; + osMutexRelease(g_tts_enhanced_context->stream_mutex); + + if (can_queue && length <= TTS_PLAYBACK_BUFFER_SIZE) { + // 发送到音频队列 + osMessageQueuePut(g_tts_enhanced_context->audio_queue, data, 0, 0); + } + } +} + +static void tts_enhanced_on_websocket_error(void *context, int error_code, const char *error_msg) +{ + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[%s] WebSocket error: %d - %s", + TTS_LOG_TAG, error_code, error_msg ? error_msg : "Unknown"); + + if (g_tts_enhanced_context != NULL) { + osMutexAcquire(g_tts_enhanced_context->stream_mutex, osWaitForever); + g_tts_enhanced_context->stream_state = TTS_STREAM_STATE_IDLE; + osMutexRelease(g_tts_enhanced_context->stream_mutex); + } +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file