From f3f951deb49091341e8fa0c0f985f468098b3a00 Mon Sep 17 00:00:00 2001 From: hcj <1224667075@qq.com> Date: Mon, 29 Sep 2025 16:20:54 +0800 Subject: [PATCH] =?utf8?q?=E5=A2=9E=E5=8A=A0=E6=89=93=E6=96=AD=E6=8E=A5?= =?utf8?q?=E5=8F=A3=EF=BC=8C=E5=BC=80=E5=90=AF=E6=96=B0=E5=AF=B9=E8=AF=9D?= =?utf8?q?=E5=B0=86=E8=87=AA=E5=8A=A8=E6=89=93=E6=96=AD=E4=B8=8A=E4=B8=80?= =?utf8?q?=E6=AC=A1=E5=AF=B9=E8=AF=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../wrapper/audio_manager_c_wrapper.cpp | 10 + .../sample/wrapper/audio_manager_c_wrapper.h | 3 + .../zhihui/ai/media/ai_media_service_ext.cpp | 36 +++- .../zhihui/leiniao/imsl_ln/leiniao_api_at.c | 30 +++ code/sdk/zhihui/leiniao/imsl_ln/ln_api.h | 1 + .../zhihui/leiniao/imsl_ln/ln_api_websocket.c | 182 +++++++++++++++++- 6 files changed, 243 insertions(+), 19 deletions(-) diff --git a/code/sdk/middleware/services/media/foundation/sample/wrapper/audio_manager_c_wrapper.cpp b/code/sdk/middleware/services/media/foundation/sample/wrapper/audio_manager_c_wrapper.cpp index 6c00d37f4..397deea97 100644 --- a/code/sdk/middleware/services/media/foundation/sample/wrapper/audio_manager_c_wrapper.cpp +++ b/code/sdk/middleware/services/media/foundation/sample/wrapper/audio_manager_c_wrapper.cpp @@ -77,6 +77,7 @@ public: void OnInterrupt(int32_t type, int32_t hint) override { + printf("OnInterrupt xxxxxxxxxxxxxxxxxxxxxxxxxx\r\n"); ALOGI("type:%d hint:%d", type, hint); } }; @@ -276,6 +277,15 @@ int32_t AudioStreamOutInit(AudioRendererConfig renderConfig) ALOGD("success"); return AUDIO_SUCCESS; } +// int32_t GetTimestamp(struct timespec &timeStamp) const; + +int32_t AudioStreamGetTimestamp(struct timespec *timeStamp) +{ + g_streamOut->GetTimestamp(*timeStamp); + return AUDIO_SUCCESS; +} + + int32_t AudioStreamOutSetMute(bool mute) { diff --git a/code/sdk/middleware/services/media/foundation/sample/wrapper/audio_manager_c_wrapper.h b/code/sdk/middleware/services/media/foundation/sample/wrapper/audio_manager_c_wrapper.h index 9a70782f5..0b7935a15 100644 --- a/code/sdk/middleware/services/media/foundation/sample/wrapper/audio_manager_c_wrapper.h +++ b/code/sdk/middleware/services/media/foundation/sample/wrapper/audio_manager_c_wrapper.h @@ -9,6 +9,7 @@ #define AUDIO_MANAGER_C_WRAPPER_H #include "audio_base_type.h" +#include #ifdef __cplusplus extern "C" { @@ -62,6 +63,8 @@ int32_t AudioStreamOutDeinit(void); int32_t AudioStreamOutFlush(void); +int32_t AudioStreamGetTimestamp(struct timespec *timeStamp); + /** * @brief Sets audio effect algorithm parameters. * 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 dfda41791..03fa4e301 100644 --- a/code/sdk/zhihui/ai/media/ai_media_service_ext.cpp +++ b/code/sdk/zhihui/ai/media/ai_media_service_ext.cpp @@ -1051,13 +1051,15 @@ static int32_t RunAIVoice(AIVoiceContext *context) if (status == osOK) { // 成功接收到队列数据 - MEDIA_INFO_LOG("RunAIVoice: received queue data: size=%u, timestamp=%u", msg.size, msg.timestamp); + // MEDIA_INFO_LOG("RunAIVoice: received queue data: size=%u, timestamp=%u", msg.size, msg.timestamp); // 处理接收到的音频数据 if (msg.data != nullptr && msg.size > 0) { // 写入音频数据到输出流 AI_WriteAudioData(msg.data,msg.size); - + // struct timespec ts; + // AudioStreamGetTimestamp(&ts); + // printf("AudioStreamGetTimestamp %d\r\n",ts.tv_sec); // 释放消息数据内存 free(msg.data); } @@ -1071,15 +1073,15 @@ static int32_t RunAIVoice(AIVoiceContext *context) 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); + // 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); + // 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); // 如果队列写入失败,释放内存 @@ -1573,9 +1575,23 @@ int32_t AI_WriteAudioData(const uint8_t* data, uint32_t size) MEDIA_ERR_LOG("Invalid audio data parameters"); return -1; } + + const int32_t MAX_RETRY_COUNT = 5; // 最大重试次数 + int32_t retry_count = 0; + AI_WRITE: if(g_aiVoiceContext->interruptFlag) - return -1; + { + g_aiVoiceContext->interruptFlag=false; + return -1; + } + + // 检查重试次数 + if (retry_count >= MAX_RETRY_COUNT) { + MEDIA_ERR_LOG("Failed to write audio data after %d retries, giving up", MAX_RETRY_COUNT); + return -1; + } + // 获取全局互斥锁(自动初始化) MediaMutexHandle mutex = GetGlobalMutex(); if (mutex == nullptr) { @@ -1596,8 +1612,8 @@ int32_t AI_WriteAudioData(const uint8_t* data, uint32_t size) int32_t ret = AudioStreamOutStreamWrite(data, size); usleep(50*size); if (ret != 0) { - // MEDIA_ERR_LOG("Failed to write audio data to output stream, ret=%d", ret); - // return -1; + retry_count++; + MEDIA_ERR_LOG("Failed to write audio data to output stream, ret=%d, retry %d/%d", ret, retry_count, MAX_RETRY_COUNT); usleep(1000*500); goto AI_WRITE; } @@ -1910,11 +1926,11 @@ int32_t AI_ForceInterrupt(void) AI_ClearFileCache(); // 立即暂停和清空输出流 - PauseAudioStreams(); + // PauseAudioStreams(); ClearAudioStreams(); // 立即恢复输出流 - ResetAudioStreams(); + // ResetAudioStreams(); MEDIA_INFO_LOG("AI voice force interrupt completed"); 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 e7ab7d3ff..66aade480 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c @@ -867,6 +867,35 @@ static uint32_t ln_ws_send_random_story_at(uint8_t *para, uint32_t paraLen, char return ERRCODE_SUCC; } +/* AT^LN_WS_SEND_VOICE_CHAT_INTERRUPT - 发送语音聊天打断请求 */ +static uint32_t ln_ws_send_voice_chat_interrupt_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + VAD_UNUSED(strCommonCmd); + + const char *reason = "用户主动打断"; // 默认原因 + + // 解析参数 + 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) { + reason = (const char *)argv[0]; + } + } + + printf("[LN][WS_SEND_VOICE_CHAT_INTERRUPT] Sending voice chat interrupt with reason: %s\n", reason); + + int ret = ln_ws_send_voice_chat_interrupt(reason); + if (ret != 0) + { + printf("[LN][WS_SEND_VOICE_CHAT_INTERRUPT] failed: %d\n", ret); + return ERRCODE_FAIL; + } + printf("[LN][WS_SEND_VOICE_CHAT_INTERRUPT] success\n"); + return ERRCODE_SUCC; +} + /* AT^LN_WS_SLEEP */ static uint32_t ln_ws_sleep_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { @@ -1847,6 +1876,7 @@ static const at_cmd_table_t g_ln_api_cmd[] = { {(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"}, {(char *)"AT^LN_WS_SEND_RANDOM_STORY", ln_ws_send_random_story_at, (char *)"发送随机故事请求: AT^LN_WS_SEND_RANDOM_STORY[=storyTheme]"}, + {(char *)"AT^LN_WS_SEND_VOICE_CHAT_INTERRUPT", ln_ws_send_voice_chat_interrupt_at, (char *)"发送语音聊天打断请求: AT^LN_WS_SEND_VOICE_CHAT_INTERRUPT[=reason]"}, /* 音频播放 */ {(char *)"AT^LN_STORY_PLAY_AT", ln_audio_play_at, (char *)"播放音频: AT^LN_STORY_PLAY_AT"}, diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h index 2323e1ccc..0036dfbcf 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h @@ -353,6 +353,7 @@ int ln_ws_send_audio_data(const uint8_t *audio_data, uint32_t audio_size, bool f int ln_ws_send_random_story_request(const char *story_theme); int ln_ws_start_audio_chat(ln_audio_chat_command_t command_type); int ln_ws_end_audio_chat(void); +int ln_ws_send_voice_chat_interrupt(const char *reason); const char* ln_ws_get_current_chat_id(void); int ln_ws_stop(void); // int ln_ws_force_reconnect(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 063a5bd87..dab65924e 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c @@ -228,7 +228,7 @@ int ln_ws_send_ping(void) printf("[LN][WS] Sent ping: %s\n", json_string); // 清理内存 - free(json_string); + cJSON_free(json_string); cJSON_Delete(json); return ret; @@ -251,9 +251,17 @@ static audio_chat_context_t g_audio_chat_ctx = {0}; // 音频聊天结束标志 static bool g_audio_chat_ending = false; + +// 当前对话匹配标志 +static bool g_chat_id_matched = false; // 当前对话ID是否匹配 + // 函数声明 static void generate_chat_id(char *chat_id, size_t size); static void generate_message_id(char *message_id, size_t size, const char *chat_id, uint32_t counter); +static void reset_chat_id_match(void); +static void set_chat_id_matched(void); +static bool is_chat_id_matched(void); +static bool is_active_chat_response(const char *chat_id, const char *response_id); // 生成聊天ID static void generate_chat_id(char *chat_id, size_t size) @@ -274,6 +282,41 @@ static void generate_message_id(char *message_id, size_t size, const char *chat_ snprintf(message_id, size, "%s_msg_%u", chat_id, counter); } + +// 重置对话ID匹配标志 +static void reset_chat_id_match(void) +{ + g_chat_id_matched = false; + printf("[LN][WS] Reset chat ID match flag\n"); +} + +// 设置对话ID匹配标志 +static void set_chat_id_matched(void) +{ + g_chat_id_matched = true; + printf("[LN][WS] Set chat ID matched\n"); +} + +// 检查对话ID是否匹配 +static bool is_chat_id_matched(void) +{ + return g_chat_id_matched; +} + +// 检查是否为活跃对话的回复 +static bool is_active_chat_response(const char *chat_id, const char *response_id) +{ + if (!chat_id || !response_id) return false; + + // 检查对话ID是否匹配当前活跃对话 + if (!g_audio_chat_ctx.is_active || strcmp(g_audio_chat_ctx.chat_id, chat_id) != 0) { + return false; + } + + // 检查对话ID是否匹配 + return is_chat_id_matched(); +} + // 发送音频数据到WebSocket int ln_ws_send_audio_data(const uint8_t *audio_data, uint32_t audio_size, bool finished) { @@ -299,6 +342,8 @@ int ln_ws_send_audio_data(const uint8_t *audio_data, uint32_t audio_size, bool f 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; + // 重置对话ID匹配标志 + reset_chat_id_match(); printf("[LN][WS] New chat started: %s\n", g_audio_chat_ctx.chat_id); } @@ -392,7 +437,7 @@ int ln_ws_send_audio_data(const uint8_t *audio_data, uint32_t audio_size, bool f g_audio_chat_ctx.chat_id, message_id, audio_size, finished ? "true" : "false"); // 清理内存 - free(json_string); + cJSON_free(json_string); free(audio_base64); cJSON_Delete(json); @@ -400,6 +445,8 @@ int ln_ws_send_audio_data(const uint8_t *audio_data, uint32_t audio_size, bool f if (finished) { g_audio_chat_ctx.is_active = false; printf("[LN][WS] Chat ended: %s\n", g_audio_chat_ctx.chat_id); + // 重置对话ID匹配标志 + reset_chat_id_match(); } return ret; @@ -418,7 +465,9 @@ int ln_ws_start_audio_chat(ln_audio_chat_command_t command_type) printf("[LN][WS] Audio chat already active: %s\n", g_audio_chat_ctx.chat_id); return 0; } - // AI_ForceInterrupt(); + ln_ws_send_voice_chat_interrupt("新对话开始"); + + AI_ForceInterrupt(); // 设置命令类型 g_audio_chat_command_type = command_type; @@ -427,6 +476,9 @@ int ln_ws_start_audio_chat(ln_audio_chat_command_t command_type) g_audio_chat_ctx.message_counter = 0; g_audio_chat_ctx.is_active = true; + // 重置对话ID匹配标志 + reset_chat_id_match(); + const char *command_name = (command_type == LN_AUDIO_CHAT_VOICE_CHAT_REQUEST) ? "VoiceChatRequest" : "ChooseStoryRequest"; @@ -487,6 +539,11 @@ static void ai_binary_message_handler(const uint8_t *raw_data, uint32_t timestam { printf("[AI] Received binary message at %u, length: %d bytes\n", timestamp, data_len); + if (!is_chat_id_matched()) { + printf("[AI] Chat ID not matched\n"); + return; + } + if (!raw_data || data_len == 0) { printf("[AI] Invalid binary data parameters\n"); return; @@ -669,14 +726,13 @@ int ln_ws_send_random_story_request(const char *story_theme) printf("[LN][WS] Cannot send random story request, connection state: %d\n", g_ws_connected); return -1; } + ln_ws_send_voice_chat_interrupt("故事请求开始"); // 生成聊天ID(使用当前对话ID) - char chat_id[128]; - generate_chat_id(chat_id, sizeof(chat_id)); - + generate_chat_id(g_audio_chat_ctx.chat_id, sizeof(g_audio_chat_ctx.chat_id)); // 生成消息ID char message_id[128]; - generate_message_id(message_id, sizeof(message_id), chat_id, 1); + generate_message_id(message_id, sizeof(message_id), g_audio_chat_ctx.chat_id, 1); // 构建JSON消息 cJSON *json = cJSON_CreateObject(); @@ -699,7 +755,7 @@ int ln_ws_send_random_story_request(const char *story_theme) return -1; } - cJSON_AddStringToObject(data, "chatId", chat_id); + cJSON_AddStringToObject(data, "chatId", g_audio_chat_ctx.chat_id); cJSON_AddStringToObject(data, "storyTheme", story_theme); cJSON_AddItemToObject(json, "data", data); @@ -717,7 +773,7 @@ int ln_ws_send_random_story_request(const char *story_theme) int ret = uapi_ws_send_text(json_string); // 清理资源 - free(json_string); + cJSON_free(json_string); cJSON_Delete(json); if (ret == 0) { @@ -729,6 +785,74 @@ int ln_ws_send_random_story_request(const char *story_theme) return ret; } +/* 发送语音聊天打断请求 */ +int ln_ws_send_voice_chat_interrupt(const char *reason) +{ + if (!reason) { + printf("[LN][WS] Invalid reason parameter\n"); + return -1; + } + + if (g_ws_connected != WS_STATE_CONNECTED) { + printf("[LN][WS] Cannot send voice chat interrupt, connection state: %d\n", g_ws_connected); + return -1; + } + + // 生成消息ID + char message_id[128]; + generate_message_id(message_id, sizeof(message_id), g_audio_chat_ctx.chat_id, g_audio_chat_ctx.message_counter + 1); + + // 构建JSON消息 + cJSON *json = cJSON_CreateObject(); + if (!json) { + printf("[LN][WS] Failed to create JSON object\n"); + return -1; + } + + // 添加基本字段 + cJSON_AddStringToObject(json, "deviceId", uapi_ln_get_device_id()); + cJSON_AddNumberToObject(json, "timestamp", (double)time(NULL)); + cJSON_AddStringToObject(json, "messageId", message_id); + cJSON_AddStringToObject(json, "command", "VoiceChatInterrupt"); + + // 添加data字段 + cJSON *data = cJSON_CreateObject(); + if (!data) { + printf("[LN][WS] Failed to create data JSON object\n"); + cJSON_Delete(json); + return -1; + } + + cJSON_AddStringToObject(data, "chatId", g_audio_chat_ctx.chat_id); + cJSON_AddStringToObject(data, "reason", reason); + cJSON_AddItemToObject(json, "data", data); + + // 转换为字符串 + char *json_string = cJSON_Print(json); + if (!json_string) { + printf("[LN][WS] Failed to convert JSON to string\n"); + cJSON_Delete(json); + return -1; + } + + printf("[LN][WS] Sending VoiceChatInterrupt: %s\n", json_string); + + // 发送消息 + int ret = uapi_ws_send_text(json_string); + + // 清理资源 + cJSON_free(json_string); + cJSON_Delete(json); + + if (ret == 0) { + printf("[LN][WS] VoiceChatInterrupt sent successfully\n"); + } else { + printf("[LN][WS] Failed to send VoiceChatInterrupt\n"); + } + + return ret; +} + /* 停止:关闭心跳并关闭连接 */ int ln_ws_stop(void) @@ -920,6 +1044,7 @@ static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_ms if (message_id && cJSON_IsString(message_id) && message_id->valuestring) { strncpy(data_msg->message_id, message_id->valuestring, sizeof(data_msg->message_id) - 1); data_msg->message_id[sizeof(data_msg->message_id) - 1] = '\0'; + } else { printf("[LN][WS] Missing or invalid messageId in message\n"); cJSON_Delete(root); @@ -958,6 +1083,15 @@ static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_ms 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'; + + // 检查对话ID是否匹配当前活跃对话 + if (strcmp(g_audio_chat_ctx.chat_id, data_msg->chat_id) == 0) { + set_chat_id_matched(); + } else { + printf("[LN][WS] VoiceChatResponse from non-active chat %s, ignoring\n", data_msg->chat_id); + cJSON_Delete(root); + return -1; // 返回错误,不处理此消息 + } } // 解析data.data字段 @@ -998,6 +1132,15 @@ static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_ms 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'; + + // 检查对话ID是否匹配当前活跃对话 + if (strcmp(g_audio_chat_ctx.chat_id, data_msg->chat_id) == 0) { + set_chat_id_matched(); + } else { + printf("[LN][WS] ChooseStoryResponse from non-active chat %s, ignoring\n", data_msg->chat_id); + cJSON_Delete(root); + return -1; // 返回错误,不处理此消息 + } } // 解析data.data字段 @@ -1038,6 +1181,15 @@ static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_ms 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'; + + // 检查对话ID是否匹配当前活跃对话 + if (strcmp(g_audio_chat_ctx.chat_id, data_msg->chat_id) == 0) { + set_chat_id_matched(); + } else { + printf("[LN][WS] RandomStoryResponse from non-active chat %s, ignoring\n", data_msg->chat_id); + cJSON_Delete(root); + return -1; // 返回错误,不处理此消息 + } } // 解析data.data字段 @@ -1521,6 +1673,18 @@ static void ln_ws_process_incremental_base64_data(const char *new_data, size_t n } +// { +// "deviceId": "DEVICE_001", +// "timestamp": 1640995204, +// "messageId": "msg_interrupt_001", +// "command": "VoiceChatInterrupt", +// "data": { +// "chatId": "chat_12345", +// "reason": "用户主动打断" +// } +// } + + // { // "deviceId": "DEVICE_001", // "timestamp": 1640995200, -- 2.17.1