增加ai对话文件缓存,优化音频处理逻辑,ln ws增加新的回调事件
authorhcj <1224667075@qq.com>
Sun, 28 Sep 2025 06:14:44 +0000 (14:14 +0800)
committerhcj <1224667075@qq.com>
Sun, 28 Sep 2025 06:14:44 +0000 (14:14 +0800)
code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/brandy-native-js-ln/libwebsocket.a
code/sdk/zhihui/ai/media/ai_media_service_ext.cpp
code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c
code/sdk/zhihui/leiniao/imsl_ln/ln_api.c
code/sdk/zhihui/leiniao/imsl_ln/ln_api.h
code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c
code/sdk/zhihui/websocket/os_ws/haisi_websocket.c
code/sdk/zhihui/websocket/os_ws/haisi_websocket.h

index 93b5e6f..8de57b2 100644 (file)
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
index 455c1db..bd77bbd 100644 (file)
@@ -17,6 +17,9 @@
 #include <iostream>
 #include <unistd.h>
 #include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <queue>
 #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<uint8_t*>(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) {
index bcc7c71..02b99ec 100644 (file)
@@ -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"},
     
     /* 音频播放 */
index 010d26f..c1f0353 100644 (file)
@@ -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, "<null>") != 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);
index 11195b4..3c97536 100644 (file)
@@ -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);
index efb7c0b..3bb39a8 100644 (file)
@@ -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);
index decd7ad..5be52ad 100644 (file)
@@ -2,6 +2,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
+#include <time.h>
 #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(&timestamp);
             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");
+}
 
index d2ac635..8c077a2 100644 (file)
@@ -6,6 +6,15 @@
 #include <stdint.h>
 #include <stddef.h>
 
+/* 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);