From: hcj <1224667075@qq.com> Date: Sun, 28 Sep 2025 06:14:44 +0000 (+0800) Subject: 增加ai对话文件缓存,优化音频处理逻辑,ln ws增加新的回调事件 X-Git-Tag: V1.0.9_20251128_194606_LN~250^2~42 X-Git-Url: http://git.snicker.com.cn/css/static/gitweb.css?a=commitdiff_plain;h=93e6f32ac3f04e34e6c95d0ee9aebba138b4fd1d;p=mirrors%2FFD230.git 增加ai对话文件缓存,优化音频处理逻辑,ln ws增加新的回调事件 --- diff --git a/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/brandy-native-js-ln/libwebsocket.a b/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/brandy-native-js-ln/libwebsocket.a index 93b5e6f80..8de57b289 100644 Binary files a/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/brandy-native-js-ln/libwebsocket.a and b/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/brandy-native-js-ln/libwebsocket.a differ diff --git a/code/sdk/zhihui/ai/media/ai_media_service_ext.cpp b/code/sdk/zhihui/ai/media/ai_media_service_ext.cpp index 455c1db2c..bd77bbd4e 100644 --- a/code/sdk/zhihui/ai/media/ai_media_service_ext.cpp +++ b/code/sdk/zhihui/ai/media/ai_media_service_ext.cpp @@ -17,6 +17,9 @@ #include #include #include +#include +#include +#include #include "securec.h" #include "audio_manager.h" @@ -67,6 +70,7 @@ const uint32_t DEFAULT_CHANNEL_COUNT = 1; const uint32_t DEFAULT_SAMPLE_RATE = SAMPLERATE_16000; static MediaMutexHandle GetGlobalMutex(void); +int32_t AI_GetQueueStatus(uint32_t* queueSize, uint32_t* queueCapacity); // AI语音音频参数结构体IVoiceAudioParams; @@ -126,6 +130,12 @@ typedef struct { static MediaMutexHandle g_aiVoiceContextMutex = nullptr; static AIVoiceContext *g_aiVoiceContext = nullptr; +// 文件缓存相关变量 +static const char* AI_AUDIO_CACHE_FILE = "/user/ai_audio_cache.dat"; +static const size_t AI_AUDIO_CHUNK_SIZE = 1024; // 固定音频块大小:1KB +static MediaMutexHandle g_fileCacheMutex = nullptr; +static long g_fileCacheOffset = 0; // 文件读取偏移量 + // OGG相关内存管理函数 static void MediaMemFree(void **src) { @@ -154,6 +164,198 @@ static void *MediaMemCalloc(const size_t bytesNum) return tmp; } +// 文件缓存操作辅助函数 +/** + * @brief 初始化文件缓存互斥锁 + */ +static void AI_InitFileCacheMutex() +{ + if (g_fileCacheMutex == nullptr) { + g_fileCacheMutex = MediaMutexCreate(nullptr); + if (g_fileCacheMutex == nullptr) { + MEDIA_ERR_LOG("Failed to create file cache mutex"); + } + } +} + +/** + * @brief 将音频数据写入文件缓存 + * @param msg 音频消息 + * @return 0 成功,-1 失败 + */ +static int32_t AI_WriteToFileCache(const AIVoiceQueueMessage& msg) +{ + AI_InitFileCacheMutex(); + if (g_fileCacheMutex == nullptr) { + return -1; + } + + MediaMutexLock(g_fileCacheMutex); + + // 写入文件 + FILE *file = fopen(AI_AUDIO_CACHE_FILE, "ab"); + if (file != NULL) { + // 写入音频数据 + size_t written = fwrite(msg.data, 1, msg.size, file); + if (written != msg.size) { + MEDIA_ERR_LOG("Failed to write audio data to file cache"); + fclose(file); + MediaMutexUnLock(g_fileCacheMutex); + return -1; + } + + fclose(file); + MEDIA_INFO_LOG("Successfully wrote %u bytes to file cache", msg.size); + MediaMutexUnLock(g_fileCacheMutex); + return 0; + } else { + MEDIA_ERR_LOG("Failed to open file cache for writing"); + MediaMutexUnLock(g_fileCacheMutex); + return -1; + } +} + +/** + * @brief 从文件缓存读取音频数据 + * @param msg 输出的音频消息 + * @return 0 成功,-1 失败 + */ +static int32_t AI_ReadFromFileCache(AIVoiceQueueMessage& msg) +{ + AI_InitFileCacheMutex(); + if (g_fileCacheMutex == nullptr) { + return -1; + } + + MediaMutexLock(g_fileCacheMutex); + + // 从文件读取 + FILE *file = fopen(AI_AUDIO_CACHE_FILE, "rb"); + if (file != NULL) { + // 设置文件读取位置到当前偏移量 + if (fseek(file, g_fileCacheOffset, SEEK_SET) != 0) { + MEDIA_ERR_LOG("Failed to seek to offset %ld in file cache", g_fileCacheOffset); + fclose(file); + MediaMutexUnLock(g_fileCacheMutex); + return -1; + } + + // 分配固定大小的内存 + msg.data = static_cast(malloc(AI_AUDIO_CHUNK_SIZE)); + if (msg.data == nullptr) { + MEDIA_ERR_LOG("Failed to allocate memory for cached audio data"); + fclose(file); + MediaMutexUnLock(g_fileCacheMutex); + return -1; + } + + // 读取固定大小的音频数据 + size_t read_count = fread(msg.data, 1, AI_AUDIO_CHUNK_SIZE, file); + if (read_count == 0) { + // 没有数据可读,检查是否到达文件末尾 + fseek(file, 0, SEEK_END); + long file_size = ftell(file); + + if (g_fileCacheOffset >= file_size) { + // 已到达文件末尾,清理文件内容并重置偏移量 + MEDIA_INFO_LOG("Reached end of file cache (offset: %ld, file size: %ld), clearing cache", + g_fileCacheOffset, file_size); + fclose(file); + + // 清理文件内容 + if (remove(AI_AUDIO_CACHE_FILE) == 0) { + MEDIA_INFO_LOG("File cache cleared successfully"); + } else { + MEDIA_ERR_LOG("Failed to clear file cache"); + } + + // 重置偏移量 + g_fileCacheOffset = 0; + + free(msg.data); + msg.data = nullptr; + MediaMutexUnLock(g_fileCacheMutex); + return -1; + } else { + MEDIA_ERR_LOG("No data available in file cache at offset %ld", g_fileCacheOffset); + free(msg.data); + msg.data = nullptr; + fclose(file); + MediaMutexUnLock(g_fileCacheMutex); + return -1; + } + } + + // 更新偏移量 + g_fileCacheOffset += read_count; + + // 设置实际读取的数据大小 + msg.size = read_count; + + fclose(file); + MEDIA_INFO_LOG("Read %u bytes from file cache at offset %ld", msg.size, g_fileCacheOffset - read_count); + MediaMutexUnLock(g_fileCacheMutex); + return 0; + } else { + MEDIA_ERR_LOG("Failed to open file cache for reading"); + MediaMutexUnLock(g_fileCacheMutex); + return -1; + } +} + +/** + * @brief 检查文件缓存是否有数据 + * @return true 有数据,false 无数据 + */ +static bool AI_HasFileCacheData() +{ + AI_InitFileCacheMutex(); + if (g_fileCacheMutex == nullptr) { + return false; + } + + MediaMutexLock(g_fileCacheMutex); + + // 检查文件是否存在且不为空 + FILE *file = fopen(AI_AUDIO_CACHE_FILE, "rb"); + bool hasData = false; + if (file != NULL) { + fseek(file, 0, SEEK_END); + long fileSize = ftell(file); + + // 检查文件大小是否大于0,且偏移量小于文件大小(还有数据可读) + hasData = (fileSize > 0) && (g_fileCacheOffset < fileSize); + + fclose(file); + } + + MediaMutexUnLock(g_fileCacheMutex); + return hasData; +} + +/** + * @brief 清理文件缓存 + */ +static void AI_ClearFileCache() +{ + AI_InitFileCacheMutex(); + if (g_fileCacheMutex == nullptr) { + return; + } + + MediaMutexLock(g_fileCacheMutex); + + // 重置文件偏移量 + g_fileCacheOffset = 0; + + // 删除文件 + if (remove(AI_AUDIO_CACHE_FILE) == 0) { + MEDIA_INFO_LOG("File cache cleared successfully"); + } + + MediaMutexUnLock(g_fileCacheMutex); +} + // OGG头部初始化函数 static int32_t AudioCapturerOggHeaderInit(CaptureOggContext *oggCtx, uint32_t channelCount, uint32_t sampleRate) { @@ -864,6 +1066,30 @@ static int32_t RunAIVoice(AIVoiceContext *context) if (context->queueMsgCount > 0) { context->queueMsgCount--; } + + // 检查并处理文件缓存中的数据(每次只处理一个) + if (AI_HasFileCacheData()) { + AIVoiceQueueMessage cachedMsg; + if (AI_ReadFromFileCache(cachedMsg) == 0) { + MEDIA_INFO_LOG("RunAIVoice: restoring cached data to queue: size=%u, timestamp=%u", + cachedMsg.size, cachedMsg.timestamp); + + // 将缓存的数据重新写入队列 + if (cachedMsg.data != nullptr && cachedMsg.size > 0) { + osStatus_t queueStatus = osMessageQueuePut(context->inputQueue, &cachedMsg, 0, 0); + if (queueStatus == osOK) { + context->queueMsgCount++; + MEDIA_INFO_LOG("Successfully restored %u bytes from cache to queue", cachedMsg.size); + } else { + MEDIA_ERR_LOG("Failed to restore cached data to queue: %d", queueStatus); + // 如果队列写入失败,释放内存 + free(cachedMsg.data); + } + } + } else { + MEDIA_ERR_LOG("Failed to read from file cache"); + } + } } else if (status == osErrorTimeout) { // 超时,继续循环检查停止标志 continue; @@ -1125,7 +1351,7 @@ int32_t old_Start_AIVoice(AIVoiceDataCallback dataCallback, void* userData, task_cfg.create_queue_handle = CreateAIVoiceQueue; // 设置队列创建函数 task_cfg.attr.name = "AIVoiceThread"; - task_cfg.attr.priority = (osPriority_t)16; // 使用合适的优先级 + task_cfg.attr.priority = (osPriority_t)24; // 使用合适的优先级 task_cfg.attr.stack_size = 0x2000; // 8KB栈空间 task_cfg.func = AIVoiceThread; task_cfg.use_mem = USE_EXTEND_MEM; @@ -1147,6 +1373,9 @@ int32_t old_Start_AIVoice(AIVoiceDataCallback dataCallback, void* userData, g_aiVoiceContext->threadName[sizeof(g_aiVoiceContext->threadName) - 1] = '\0'; // 确保字符串结束 MEDIA_INFO_LOG("AI voice thread created successfully with name: %s", g_aiVoiceContext->threadName); + + AI_ClearFileCache(); + return 0; EXIT: // 清理已分配的资源 @@ -1253,6 +1482,9 @@ int32_t StopAIVoice(void) // 首先清理AI语音资源(包括队列) DeinitAIVoiceResources(context); + // 清理文件缓存 + AI_ClearFileCache(); + // 清理线程同步资源 if (context->cond != nullptr) { (void)MediaThreadCondDestroy(&context->cond); @@ -1409,6 +1641,74 @@ int32_t AI_SendAudioDataToQueue(const uint8_t* data, uint32_t size) MEDIA_ERR_LOG("Invalid audio data parameters"); return -1; } + uint32_t queueSize, queueCapacity; + AI_GetQueueStatus(&queueSize, &queueCapacity); + // 检查是否有文件缓存数据需要优先处理 + bool hasFileCacheData = AI_HasFileCacheData(); + + if(queueSize>=queueCapacity) + { + MEDIA_INFO_LOG("Queue is full, writing to file cache instead"); + + // 分配内存并复制数据 + uint8_t *dataCopy = (uint8_t*)malloc(size); + if (dataCopy == nullptr) { + MEDIA_ERR_LOG("Failed to allocate memory for audio data"); + return -1; + } + + memcpy(dataCopy, data, size); + + // 创建队列消息 + AIVoiceQueueMessage msg; + msg.data = dataCopy; + msg.size = size; + msg.timestamp = (uint32_t)time(NULL); + + // 写入文件缓存 + int32_t result = AI_WriteToFileCache(msg); + if (result == 0) { + MEDIA_INFO_LOG("Successfully wrote %u bytes to file cache", size); + return 0; + } else { + MEDIA_ERR_LOG("Failed to write to file cache"); + free(dataCopy); + return -1; + } + } + + // 如果队列有空闲但存在文件缓存数据,优先处理缓存数据 + if (hasFileCacheData) { + MEDIA_INFO_LOG("Queue has space but file cache exists, writing new data to cache to maintain order"); + + // 分配内存并复制数据 + uint8_t *dataCopy = (uint8_t*)malloc(size); + if (dataCopy == nullptr) { + MEDIA_ERR_LOG("Failed to allocate memory for audio data"); + return -1; + } + + memcpy(dataCopy, data, size); + + // 创建队列消息 + AIVoiceQueueMessage msg; + msg.data = dataCopy; + msg.size = size; + msg.timestamp = (uint32_t)time(NULL); + + // 写入文件缓存以保持顺序 + int32_t result = AI_WriteToFileCache(msg); + if (result == 0) { + MEDIA_INFO_LOG("Successfully wrote %u bytes to file cache to maintain order", size); + return 0; + } else { + MEDIA_ERR_LOG("Failed to write to file cache for order maintenance"); + free(dataCopy); + return -1; + } + } + + MEDIA_ERR_LOG("AI_GetQueueStatus %d,%d\r\n",queueSize,queueCapacity); MediaMutexHandle mutex = GetGlobalMutex(); if (mutex == nullptr) { diff --git a/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c b/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c index bcc7c7139..02b99ec38 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c @@ -755,11 +755,38 @@ static uint32_t ln_ws_close_status_at(uint8_t *para, uint32_t paraLen, char *str /* AT^LN_WS_START_AUDIO_CHAT - 开始音频聊天 */ static uint32_t ln_ws_start_audio_chat_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); VAD_UNUSED(strCommonCmd); - printf("[LN][WS_START_AUDIO_CHAT]\n"); - int ret = ln_ws_start_audio_chat(); + + ln_audio_chat_command_t command_type = LN_AUDIO_CHAT_VOICE_CHAT_REQUEST; // 默认值 + + // 解析参数 + if (para != NULL && paraLen > 0) { + uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0}; + uint32_t argc = 0; + + if (ConvertCmdToParam(para, paraLen, argv, &argc) == VAD_SUCCESS && argc >= 1) { + const char *param = (const char *)argv[0]; + + if (strcmp(param, "0") == 0 || strcmp(param, "VoiceChatRequest") == 0) { + command_type = LN_AUDIO_CHAT_VOICE_CHAT_REQUEST; + } else if (strcmp(param, "1") == 0 || strcmp(param, "ChooseStoryRequest") == 0) { + command_type = LN_AUDIO_CHAT_CHOOSE_STORY_REQUEST; + } else { + printf("[LN][WS_START_AUDIO_CHAT] Invalid parameter: %s\n", param); + printf("Usage: AT^LN_WS_START_AUDIO_CHAT[=0/1 or VoiceChatRequest/ChooseStoryRequest]\n"); + printf(" 0 or VoiceChatRequest: 语音聊天请求 (默认)\n"); + printf(" 1 or ChooseStoryRequest: 选择故事请求\n"); + return ERRCODE_FAIL; + } + } + } + + const char *command_name = (command_type == LN_AUDIO_CHAT_VOICE_CHAT_REQUEST) ? + "VoiceChatRequest" : "ChooseStoryRequest"; + + printf("[LN][WS_START_AUDIO_CHAT] Starting with command: %s\n", command_name); + + int ret = ln_ws_start_audio_chat(command_type); if (ret != 0) { printf("[LN][WS_START_AUDIO_CHAT] failed: %d\n", ret); @@ -1727,7 +1754,7 @@ static const at_cmd_table_t g_ln_api_cmd[] = { {(char *)"AT^LN_WS_SLEEP", ln_ws_sleep_at, (char *)"进入休眠状态: AT^LN_WS_SLEEP"}, {(char *)"AT^LN_WS_WAKEUP", ln_ws_wakeup_at, (char *)"退出休眠状态: AT^LN_WS_WAKEUP"}, {(char *)"AT^LN_WS_CLOSE_STATUS", ln_ws_close_status_at, (char *)"查询WebSocket关闭状态: AT^LN_WS_CLOSE_STATUS"}, - {(char *)"AT^LN_WS_START_AUDIO_CHAT", ln_ws_start_audio_chat_at, (char *)"开始音频聊天: AT^LN_WS_START_AUDIO_CHAT"}, + {(char *)"AT^LN_WS_START_AUDIO_CHAT", ln_ws_start_audio_chat_at, (char *)"开始音频聊天: AT^LN_WS_START_AUDIO_CHAT[=0/1]"}, {(char *)"AT^LN_WS_END_AUDIO_CHAT", ln_ws_end_audio_chat_at, (char *)"结束音频聊天: AT^LN_WS_END_AUDIO_CHAT"}, /* 音频播放 */ diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c index 010d26fe0..c1f035382 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c @@ -78,6 +78,8 @@ static int ln_write_string_file(const char *file_path, const char *str) /* 获取设备id: 优先读本地缓存文件 /user/userdata/leiniao/device_id.bin*/ char *uapi_ln_get_device_id(void) { + return "DEVICE_PC_TEST_001"; + if (s_cached_device_id && s_cached_device_id[0]) { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "device_id cached: %s", s_cached_device_id); return s_cached_device_id; @@ -176,6 +178,7 @@ char *uapi_ln_get_device_version(void) /* 获取设备token: 优先读本地缓存,判断过期,否则刷新 */ char *uapi_ln_get_device_token(void) { + return "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJXWVNaIiwiZGV2aWNlTW9kZWwiOiJhYm15Ym94XzEiLCJkZXZpY2VWZXJzaW9uIjoiMS4wLjAiLCJkZXZpY2VJZCI6IkRFVklDRV9QQ19URVNUXzAwMSIsImV4cCI6MTc2MTQ2MjQ5Mn0.Oeyu8BDopDTvtAcdNpzz46U2w1UwzEzqcvPwD3Dp08Y"; /* 若内存已有且非占位,直接返回 */ if (g_device_token && strcmp(g_device_token, "") != 0) { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"uapi_ln_get_device_token: cached token=%s", g_device_token); @@ -1293,7 +1296,7 @@ static void ln_http_server_send_task(void const *arg) { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[LN_HTTP_SERVER_SEND_TASK] 获取雷鸟设备Token成功\n"); set_is_ln_get_device_token_success(1); - ret = ln_ws_start(ln_ws_process_message); + // ret = ln_ws_start(ln_ws_process_message); if (ret != 0) { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[LN_HTTP_SERVER_SEND_TASK] 启动WebSocket失败: %d\n", ret); diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h index 11195b407..3c975368a 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h @@ -333,6 +333,9 @@ typedef enum { WS_CMD_PING, /* 心跳 (Ping) */ WS_CMD_PONG, /* 心跳回应 (Pong) */ WS_CMD_VOICE_CHAT_RESPONSE, /* 语音聊天响应 (VoiceChatResponse) */ + WS_CMD_CHOOSE_STORY_RESPONSE, /* 选择故事响应 (ChooseStoryResponse) */ + WS_CMD_CONNECTED, /* WebSocket连接成功 */ + WS_CMD_DISCONNECTED, /* WebSocket断连 */ WS_CMD_MAX /* 最大命令 */ } ws_command_t; @@ -347,7 +350,7 @@ typedef struct { // VoiceChatResponse 特定字段 char audio_url[512]; // 音频URL - char text_content[1024]; // 文本内容 + char text_content[512]; // 文本内容 bool finished; // 是否完成 char chat_id[64]; // 聊天ID } ws_data_msg_t; @@ -359,11 +362,17 @@ typedef void (*ws_message_callback_t_ln)(const ws_data_msg_t *data_msg, ws_comma void ln_ws_process_message(const ws_data_msg_t *data_msg, ws_command_t type); /* -------------------------------------------------------------------------- */ +/* 音频聊天命令类型枚举 */ +typedef enum { + LN_AUDIO_CHAT_VOICE_CHAT_REQUEST = 0, /* 语音聊天请求 */ + LN_AUDIO_CHAT_CHOOSE_STORY_REQUEST = 1 /* 选择故事请求 */ +} ln_audio_chat_command_t; + /* WebSocket接口函数 */ int ln_ws_start(ws_message_callback_t_ln callback); int ln_ws_send_text(const char *text); int ln_ws_send_audio_data(const uint8_t *audio_data, uint32_t audio_size, bool finished); -int ln_ws_start_audio_chat(void); +int ln_ws_start_audio_chat(ln_audio_chat_command_t command_type); int ln_ws_end_audio_chat(void); const char* ln_ws_get_current_chat_id(void); int ln_ws_stop(void); diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c index efb7c0b00..3bb39a8b9 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c @@ -42,6 +42,9 @@ static size_t g_base64_cache_size = 0; /* 缓存大小 */ static size_t g_base64_cache_capacity = 0; /* 缓存容量 */ static bool g_base64_in_progress = false; /* 是否正在进行Base64数据接收 */ +/* 音频聊天命令类型 */ +static ln_audio_chat_command_t g_audio_chat_command_type = LN_AUDIO_CHAT_VOICE_CHAT_REQUEST; + /* 回调函数结构体 */ static ws_call_backs_t g_ws_callbacks = {0}; @@ -126,16 +129,27 @@ static void ln_ws_event_callback(ws_event_type_t event_type) case WS_EVENT_CONNECTED: printf("[LN][WS] Event: Connected\n"); g_ws_connected = WS_STATE_CONNECTED; + + // 触发连接成功事件 + if (g_ws_message_callback != NULL) { + g_ws_message_callback(NULL, WS_CMD_CONNECTED); + } break; case WS_EVENT_DISCONNECTED: printf("[LN][WS] Event: Disconnected\n"); - g_ws_connected = WS_STATE_DISCONNECTED; - /* 断开后增加重连计数,由监督线程在下一次连接时携带 */ - if (g_ws_running) { - if (g_ws_reconnect_count < 0xFFFFFFFFu) { - g_ws_reconnect_count++; - } - } + g_ws_connected = WS_STATE_DISCONNECTED; + + // 触发断连事件 + if (g_ws_message_callback != NULL) { + g_ws_message_callback(NULL, WS_CMD_DISCONNECTED); + } + + /* 断开后增加重连计数,由监督线程在下一次连接时携带 */ + if (g_ws_running) { + if (g_ws_reconnect_count < 0xFFFFFFFFu) { + g_ws_reconnect_count++; + } + } break; default: printf("[LN][WS] Event: Unknown event type %d\n", event_type); @@ -341,7 +355,11 @@ int ln_ws_send_audio_data(const uint8_t *audio_data, uint32_t audio_size, bool f cJSON_AddStringToObject(json, "deviceId", uapi_ln_get_device_id()); cJSON_AddNumberToObject(json, "timestamp", (double)timestamp); cJSON_AddStringToObject(json, "messageId", message_id); - cJSON_AddStringToObject(json, "command", "VoiceChatRequest"); + + // 根据命令类型设置不同的command + const char *command_str = (g_audio_chat_command_type == LN_AUDIO_CHAT_VOICE_CHAT_REQUEST) ? + "VoiceChatRequest" : "ChooseStoryRequest"; + cJSON_AddStringToObject(json, "command", command_str); // 创建data对象 cJSON *data_obj = cJSON_CreateObject(); @@ -388,18 +406,25 @@ int ln_ws_send_audio_data(const uint8_t *audio_data, uint32_t audio_size, bool f } // 开始新的音频聊天 -int ln_ws_start_audio_chat(void) +int ln_ws_start_audio_chat(ln_audio_chat_command_t command_type) { if (g_audio_chat_ctx.is_active) { printf("[LN][WS] Audio chat already active: %s\n", g_audio_chat_ctx.chat_id); return 0; } + // 设置命令类型 + g_audio_chat_command_type = command_type; + generate_chat_id(g_audio_chat_ctx.chat_id, sizeof(g_audio_chat_ctx.chat_id)); g_audio_chat_ctx.message_counter = 0; g_audio_chat_ctx.is_active = true; - printf("[LN][WS] Audio chat started: %s\n", g_audio_chat_ctx.chat_id); + const char *command_name = (command_type == LN_AUDIO_CHAT_VOICE_CHAT_REQUEST) ? + "VoiceChatRequest" : "ChooseStoryRequest"; + + printf("[LN][WS] Audio chat started: %s (command: %s)\n", + g_audio_chat_ctx.chat_id, command_name); return 0; } @@ -451,7 +476,7 @@ static void AIVoiceDataCallback_ln(const uint8_t* data, uint32_t size, void* use // "audioBase64": -static void ai_binary_message_handler(const uint8_t *raw_data, uint32_t timestamp, uint16_t data_len) +static void ai_binary_message_handler(const uint8_t *raw_data, uint32_t timestamp, uint32_t data_len) { printf("[AI] Received binary message at %u, length: %d bytes\n", timestamp, data_len); @@ -483,8 +508,6 @@ static void ai_binary_message_handler(const uint8_t *raw_data, uint32_t timestam current_pos += current_chunk_size; remaining -= current_chunk_size; - // 添加小延迟避免过快发送 - usleep(1000); // 1ms延迟 } printf("[AI] All %d chunks sent successfully\n", chunk_count); @@ -719,6 +742,8 @@ static ws_command_t ln_ws_parse_command_string(const char *command_str) return WS_CMD_PONG; } else if (strcmp(command_str, "VoiceChatResponse") == 0) { return WS_CMD_VOICE_CHAT_RESPONSE; + } else if (strcmp(command_str, "ChooseStoryResponse") == 0) { + return WS_CMD_CHOOSE_STORY_RESPONSE; } else { return WS_CMD_UNKNOWN; } @@ -730,7 +755,23 @@ static ws_command_t ln_ws_parse_command_string(const char *command_str) * @param data_msg 输出的消息结构体 * @return 0成功,-1失败 */ - +// { +// "deviceId": "DEVICE_001", +// "timestamp": 1640995203, +// "messageId": "story_response_001", +// "command": "ChooseStoryResponse", +// "data": { +// "chatId": "story_12345", +// "finished": false, +// "data": { +// "storyTheme": "童话故事", +// "storyContent": "从前有一个可爱的小女孩", +// "storyAudioUrl": "https://example.com/story/audio/chunk_001.mp3", +// "storyDuration": null +// }, +// "errorCode": null +// } +// } // {"deviceId":"DEVICE_PC_TEST_001","timestamp":1758880220, // "messageId":"d63a0670a3854d709cb95720e1e89f83","command":"VoiceChatResponse","data": // {"finished":false,"chatId":"chat_1758880210_2087035594", @@ -838,6 +879,46 @@ static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_ms data_msg->finished ? "true" : "false", data_msg->chat_id, data_msg->audio_url, data_msg->text_content); } + // 解析ChooseStoryResponse特定字段 + if (data_msg->command == WS_CMD_CHOOSE_STORY_RESPONSE) { + cJSON *data_obj = cJSON_GetObjectItem(root, "data"); + if (data_obj && cJSON_IsObject(data_obj)) { + // 解析finished字段 + cJSON *finished = cJSON_GetObjectItem(data_obj, "finished"); + if (finished && cJSON_IsBool(finished)) { + data_msg->finished = cJSON_IsTrue(finished); + } + + // 解析chatId字段 + cJSON *chat_id = cJSON_GetObjectItem(data_obj, "chatId"); + if (chat_id && cJSON_IsString(chat_id) && chat_id->valuestring) { + strncpy(data_msg->chat_id, chat_id->valuestring, sizeof(data_msg->chat_id) - 1); + data_msg->chat_id[sizeof(data_msg->chat_id) - 1] = '\0'; + } + + // 解析data.data字段 + cJSON *data_data = cJSON_GetObjectItem(data_obj, "data"); + if (data_data && cJSON_IsObject(data_data)) { + // 解析storyTheme字段,存储到text_content中 + cJSON *story_theme = cJSON_GetObjectItem(data_data, "storyTheme"); + if (story_theme && cJSON_IsString(story_theme) && story_theme->valuestring) { + strncpy(data_msg->text_content, story_theme->valuestring, sizeof(data_msg->text_content) - 1); + data_msg->text_content[sizeof(data_msg->text_content) - 1] = '\0'; + } + + // 解析storyAudioUrl字段,存储到audio_url中 + cJSON *story_audio_url = cJSON_GetObjectItem(data_data, "storyAudioUrl"); + if (story_audio_url && cJSON_IsString(story_audio_url) && story_audio_url->valuestring) { + strncpy(data_msg->audio_url, story_audio_url->valuestring, sizeof(data_msg->audio_url) - 1); + data_msg->audio_url[sizeof(data_msg->audio_url) - 1] = '\0'; + } + } + } + + printf("[LN][WS] ChooseStoryResponse parsed: finished=%s, chatId=%s, storyTheme=%s, storyAudioUrl=%s\n", + data_msg->finished ? "true" : "false", data_msg->chat_id, data_msg->text_content, data_msg->audio_url); + } + // 保存原始JSON字符串 data_msg->raw_json = (char*)malloc(strlen(json_str) + 1); if (data_msg->raw_json) { @@ -877,42 +958,73 @@ static void ln_ws_free_data_message(ws_data_msg_t *data_msg) */ static void ln_ws_default_message_handler(const ws_data_msg_t *data_msg, ws_command_t type) { - if (!data_msg) return; - - printf("[LN][WS] Default handler: Processing %s message (type: %d)\n", data_msg->command_str, type); + printf("[LN][WS] Default handler: Processing message (type: %d)\n", type); switch (type) { + case WS_CMD_CONNECTED: + printf("[LN][WS] Default: Processing WebSocket Connected event\n"); + break; + case WS_CMD_DISCONNECTED: + printf("[LN][WS] Default: Processing WebSocket Disconnected event\n"); + break; case WS_CMD_PONG: - printf("[LN][WS] Default: Processing Pong message\n"); - // 更新心跳状态、记录响应时间等 + if (data_msg) { + printf("[LN][WS] Default: Processing Pong message\n"); + // 更新心跳状态、记录响应时间等 + } break; case WS_CMD_CHARACTER_CHANGE: - printf("[LN][WS] Default: Processing CharacterChange message\n"); + if (data_msg) { + printf("[LN][WS] Default: Processing CharacterChange message\n"); + } break; case WS_CMD_SLEEP_MODE_START: - printf("[LN][WS] Default: Processing SleepModeStart message\n"); + if (data_msg) { + printf("[LN][WS] Default: Processing SleepModeStart message\n"); + } break; case WS_CMD_SLEEP_MODE_END: - printf("[LN][WS] Default: Processing SleepModeEnd message\n"); + if (data_msg) { + printf("[LN][WS] Default: Processing SleepModeEnd message\n"); + } break; case WS_CMD_DEVICE_BIND: - printf("[LN][WS] Default: Processing DeviceBind message\n"); + if (data_msg) { + printf("[LN][WS] Default: Processing DeviceBind message\n"); + } break; case WS_CMD_DEVICE_UNBIND: - printf("[LN][WS] Default: Processing DeviceUnbind message\n"); + if (data_msg) { + printf("[LN][WS] Default: Processing DeviceUnbind message\n"); + } break; case WS_CMD_SCHEDULE_REMINDER: - printf("[LN][WS] Default: Processing ScheduleReminder message\n"); + if (data_msg) { + printf("[LN][WS] Default: Processing ScheduleReminder message\n"); + } break; case WS_CMD_SCHEDULE_REMINDER_CFG: - printf("[LN][WS] Default: Processing ScheduleReminderConfig message\n"); + if (data_msg) { + printf("[LN][WS] Default: Processing ScheduleReminderConfig message\n"); + } break; case WS_CMD_VOICE_CHAT_RESPONSE: - printf("[LN][WS] Default: Processing VoiceChatResponse message\n"); - printf("[LN][WS] VoiceChatResponse details: finished=%s, chatId=%s\n", - data_msg->finished ? "true" : "false", data_msg->chat_id); - printf("[LN][WS] Audio URL: %s\n", data_msg->audio_url); - printf("[LN][WS] Text Content: %s\n", data_msg->text_content); + if (data_msg) { + printf("[LN][WS] Default: Processing VoiceChatResponse message\n"); + printf("[LN][WS] VoiceChatResponse details: finished=%s, chatId=%s\n", + data_msg->finished ? "true" : "false", data_msg->chat_id); + printf("[LN][WS] Audio URL: %s\n", data_msg->audio_url); + printf("[LN][WS] Text Content: %s\n", data_msg->text_content); + } + break; + case WS_CMD_CHOOSE_STORY_RESPONSE: + if (data_msg) { + printf("[LN][WS] Default: Processing ChooseStoryResponse message\n"); + printf("[LN][WS] ChooseStoryResponse details: finished=%s, chatId=%s\n", + data_msg->finished ? "true" : "false", data_msg->chat_id); + printf("[LN][WS] Story Theme: %s\n", data_msg->text_content); + printf("[LN][WS] Story Audio URL: %s\n", data_msg->audio_url); + } break; default: printf("[LN][WS] Default: Processing unknown command: %d\n", type); diff --git a/code/sdk/zhihui/websocket/os_ws/haisi_websocket.c b/code/sdk/zhihui/websocket/os_ws/haisi_websocket.c index decd7adb8..5be52ada6 100644 --- a/code/sdk/zhihui/websocket/os_ws/haisi_websocket.c +++ b/code/sdk/zhihui/websocket/os_ws/haisi_websocket.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "cJSON.h" // Added for JSON parsing #include "custom_utils.h" #include "soc_osal.h" @@ -40,7 +41,7 @@ typedef enum { typedef struct { uint8_t msg_type; - uint16_t data_len; + uint32_t data_len; uint32_t timestamp; // 收到消息的时间戳 bool isBinary; // 消息是否为二进制数据 char *raw_data; // 动态分配的原始消息数据 @@ -56,6 +57,10 @@ static osMessageQueueId_t g_ws_msg_queue = NULL; static ws_call_backs_t g_ws_callbacks = {0}; /* WebSocket回调函数结构体 */ static volatile ws_close_status_t g_ws_close_status = WS_CLOSE_NOT_EXECUTED; /* WebSocket关闭状态标志位 */ +/* 连接超时检测相关变量 */ +static time_t g_ws_connect_start_time = 0; /* 连接开始时间 */ +static volatile int g_ws_connect_timeout_occurred = 0; /* 超时标志 */ + static void uapi_ws_build_url(char *out, size_t out_size); static void uapi_ws_default_url_handler(char *url, size_t url_size, char *header_buf, size_t header_size); int uapi_ws_send_ping(void); @@ -64,6 +69,11 @@ static ws_close_status_t uapi_ws_get_close_status(void); static void uapi_ws_reset_close_status(void); const char* uapi_ws_get_connection_state_string(void); +/* 连接超时检测相关函数 */ +static void uapi_ws_start_connect_timer(void); +static int uapi_ws_is_connect_timeout(void); +static void uapi_ws_reset_connect_timer(void); + /* 消息内存管理函数 */ static ws_msg_info_t* ws_msg_create(uint16_t data_len) { @@ -114,6 +124,7 @@ static void uapi_ws_on_open(int client) printf("WebSocketClientConstructor uapi_ws_on_open\r\n"); g_ws_connected = WS_STATE_CONNECTED; uapi_ws_reset_close_status(); // 连接成功时重置关闭状态 + uapi_ws_reset_connect_timer(); // 连接成功时重置超时计时器 printf("[LN][WS] 连接状态: 连接成功, 关闭状态已重置为 NOT_EXECUTED (%d)\n", g_ws_close_status); } @@ -132,34 +143,37 @@ static void uapi_ws_on_message(int client, const char *data, size_t length, bool if (isFinished) { // 数据完整,创建消息结构体 - ws_msg_info_t *msg = ws_msg_create(length); - if (msg == NULL) { - printf("[LN][WS] Failed to create message structure\n"); - return; - } - - msg->msg_type = WS_MSG_TYPE_RAW_MESSAGE; - msg->isBinary = isBinary; // 设置二进制标志 - - // 记录收到消息的时间戳 - uapi_get_timestampe_ss(&msg->timestamp); - - // 复制原始数据 - memcpy(msg->raw_data, data, length); - msg->raw_data[length] = '\0'; + if(isBinary) { + uint32_t timestamp; + uapi_get_timestampe_ss(×tamp); if (g_ws_callbacks.binary_callback != NULL) { - g_ws_callbacks.binary_callback((const uint8_t*)msg->raw_data, msg->timestamp, msg->data_len); + g_ws_callbacks.binary_callback(data, timestamp, length); } else { printf("[LN][WS] No binary message callback registered\n"); } // 二进制消息处理完后释放内存 - ws_msg_free(msg); } else { + ws_msg_info_t *msg = ws_msg_create(length); + if (msg == NULL) { + printf("[LN][WS] Failed to create message structure\n"); + return; + } + + msg->msg_type = WS_MSG_TYPE_RAW_MESSAGE; + msg->isBinary = isBinary; // 设置二进制标志 + + // 记录收到消息的时间戳 + uapi_get_timestampe_ss(&msg->timestamp); + + // 复制原始数据 + memcpy(msg->raw_data, data, length); + msg->raw_data[length] = '\0'; + osStatus_t ret = osMessageQueuePut(g_ws_msg_queue, &msg, 0, 0); if (ret == osOK) { printf("[LN][WS] Raw message queued successfully, length: %d, timestamp: %u\n", msg->data_len, msg->timestamp); @@ -256,6 +270,7 @@ static void uapi_ws_main_thread(void *arg) if (g_ws_client_fd >= 0) { printf("[LN][WS] WebSocketClientClose\n"); uapi_ws_close_with_status(g_ws_client_fd); + g_ws_client_fd=-1; printf("[LN][WS] WebSocketClientClose end\n"); } g_ws_connected = WS_STATE_DISCONNECTED; @@ -343,6 +358,9 @@ static void uapi_ws_main_thread(void *arg) g_ws_callbacks.event_callback(WS_EVENT_CONNECTING); } + // 启动连接超时计时器 + uapi_ws_start_connect_timer(); + g_ws_client_fd = WebSocketClientConstructor(uapi_ws_on_open, uapi_ws_on_message, uapi_ws_on_error, uapi_ws_on_close); if (g_ws_client_fd < 0) { printf("[LN][WS] constructor failed: %d\n", g_ws_client_fd); @@ -380,6 +398,7 @@ static void uapi_ws_main_thread(void *arg) printf("[LN][WS] connect failed: %d\n", ret); g_ws_connected = WS_STATE_DISCONNECTED; printf("[LN][WS] 连接状态: 未连接(连接失败)\n"); + uapi_ws_reset_connect_timer(); // 连接失败时重置超时计时器 (void)uapi_ws_close_with_status(g_ws_client_fd); g_ws_client_fd = -1; /* 指数退避*/ @@ -398,6 +417,31 @@ static void uapi_ws_main_thread(void *arg) printf("[LN][WS] 连接请求已发送,等待握手完成...\n"); } + // 连接超时检测:在连接中状态时检查是否超时 + if (g_ws_connected == WS_STATE_CONNECTING) { + if (uapi_ws_is_connect_timeout()) { + printf("[LN][WS] Connection timeout detected, forcing disconnect and reconnect\n"); + + // 强制关闭连接 + if (g_ws_client_fd >= 0) { + uapi_ws_close_with_status(g_ws_client_fd); + g_ws_close_status = WS_CLOSE_NOT_EXECUTED; + g_ws_client_fd = -1; + } + + // 重置连接状态 + g_ws_connected = WS_STATE_DISCONNECTED; + uapi_ws_reset_connect_timer(); + + // 增加重连计数 + if (g_ws_reconnect_count < 0xFFFFFFFFu) { + g_ws_reconnect_count++; + } + + printf("[LN][WS] Connection timeout handled, will retry connection\n"); + } + } + // 心跳部分:由回调函数处理时间判断和心跳发送 if (g_ws_connected == WS_STATE_CONNECTED && g_ws_client_fd >= 0) { // 使用心跳回调函数处理,包括时间判断 @@ -866,5 +910,54 @@ int uapi_ws_exit_sleep(void) return 0; } +/* ============================== 连接超时检测函数实现 ============================== */ + +/** + * @brief 开始连接超时计时器 + */ +static void uapi_ws_start_connect_timer(void) +{ + g_ws_connect_start_time = time(NULL); + g_ws_connect_timeout_occurred = 0; + printf("[LN][WS] Connect timer started, timeout: %d seconds\n", WS_CONNECT_TIMEOUT_SECONDS); +} +/** + * @brief 检查连接是否超时 + * @return 1表示超时,0表示未超时 + */ +static int uapi_ws_is_connect_timeout(void) +{ +#if WS_CONNECT_TIMEOUT_ENABLED + if (g_ws_connect_start_time == 0) { + return 0; /* 计时器未启动 */ + } + + time_t current_time = time(NULL); + time_t elapsed = current_time - g_ws_connect_start_time; + + if (elapsed >= WS_CONNECT_TIMEOUT_SECONDS) { + if (!g_ws_connect_timeout_occurred) { + g_ws_connect_timeout_occurred = 1; + printf("[LN][WS] Connect timeout after %ld seconds (limit: %d seconds)\n", + elapsed, WS_CONNECT_TIMEOUT_SECONDS); + } + return 1; + } + + return 0; +#else + return 0; /* 超时检测被禁用 */ +#endif +} + +/** + * @brief 重置连接超时计时器 + */ +static void uapi_ws_reset_connect_timer(void) +{ + g_ws_connect_start_time = 0; + g_ws_connect_timeout_occurred = 0; + printf("[LN][WS] Connect timer reset\n"); +} diff --git a/code/sdk/zhihui/websocket/os_ws/haisi_websocket.h b/code/sdk/zhihui/websocket/os_ws/haisi_websocket.h index d2ac635a6..8c077a280 100644 --- a/code/sdk/zhihui/websocket/os_ws/haisi_websocket.h +++ b/code/sdk/zhihui/websocket/os_ws/haisi_websocket.h @@ -6,6 +6,15 @@ #include #include +/* WebSocket连接超时配置 */ +#ifndef WS_CONNECT_TIMEOUT_SECONDS +#define WS_CONNECT_TIMEOUT_SECONDS 30 /* 连接超时时间(秒),默认30秒 */ +#endif + +#ifndef WS_CONNECT_TIMEOUT_ENABLED +#define WS_CONNECT_TIMEOUT_ENABLED 1 /* 是否启用连接超时检测,默认启用 */ +#endif + #ifdef __cplusplus extern "C" { #endif @@ -15,7 +24,7 @@ extern "C" { typedef void (*ws_message_callback_t)(const char *raw_data, uint32_t timestamp); /* 二进制消息处理回调函数类型 */ -typedef void (*ws_binary_message_callback_t)(const uint8_t *raw_data, uint32_t timestamp, uint16_t data_len); +typedef void (*ws_binary_message_callback_t)(const uint8_t *raw_data, uint32_t timestamp, uint32_t data_len); /* 数据不完整消息处理回调函数类型 */ typedef void (*ws_incomplete_message_callback_t)(const char *raw_data, size_t length, bool isBinary);