增加更多播放事件雷鸟
authorhcj <1224667075@qq.com>
Tue, 14 Oct 2025 07:05:43 +0000 (15:05 +0800)
committerhcj <1224667075@qq.com>
Tue, 14 Oct 2025 07:05:43 +0000 (15:05 +0800)
code/sdk/zhihui/leiniao/imsl_ln/ln_api.h
code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c

index b914290..7e2cf53 100644 (file)
@@ -336,8 +336,10 @@ typedef enum {
     WS_CMD_RANDOM_STORY_RESPONSE,    /* 随机故事响应 (RandomStoryResponse) */
     WS_CMD_CONNECTED,                /* WebSocket连接成功 */
     WS_CMD_DISCONNECTED,             /* WebSocket断连 */
+    WS_CMD_AUDIO_PLAYBACK_START,     /* 音频播放开始 */
     WS_CMD_AUDIO_PLAYBACK_END,       /* 音频播放结束 */
     WS_CMD_AUDIO_RECORDING_START,    /* 开始录音上传 */
+    WS_CMD_SENTENCE_PLAYBACK_COMPLETE, /* 单句语音播放完成 */
     WS_CMD_MAX                       /* 最大命令 */
 } ws_command_t;
 
@@ -355,6 +357,8 @@ typedef struct {
     char text_content[512]; // 文本内容
     bool finished;           // 是否完成
     char chat_id[64];        // 聊天ID
+
+    uint32_t audio_duration;
 } ws_data_msg_t;
 
 /* 消息处理回调函数类型 */
index 4783508..a9d9e30 100644 (file)
@@ -11,7 +11,7 @@
 #include "cmsis_os2.h"
 #include "mbedtls/base64.h"
 #include "ai_media.h" // Added for AI_RegisterWriteAudioDataCallback
-
+#include "audio_base_type.h"
 // 功能开关宏定义
 #define ENABLE_BINARY_DATA_INTERRUPT_CHECK 1  // 启用二进制数据间隔检查
 
@@ -113,7 +113,10 @@ static void ln_ws_base64_cache_reset(void);
 static void ln_ws_process_base64_data(const char *base64_data, size_t length);
 static void ln_ws_process_incremental_base64_data(const char *new_data, size_t new_length);
 static void ln_ws_incomplete_callback(const char *raw_data, size_t length, bool isBinary);
-
+static void clear_audio_duration_buffer(void);
+static void record_audio_duration(uint32_t duration);
+static bool check_playback_completion_by_duration(void);
+static bool check_playback_completion_by_timeout(void);
 
 uint16_t send_audio=0;
 
@@ -152,6 +155,20 @@ static uint32_t g_last_audio_write_tick = 0;  // 最后一次音频写入的tick
 static bool g_audio_playback_active = false;  // 音频播放是否活跃
 static uint32_t g_audio_timeout_ticks = 1500; // 音频超时tick数(默认1.5秒)
 static uint32_t g_last_binary_data_tick = 0;  // 最后一次接收二进制数据的tick
+static uint32_t g_audio_playback_start_tick = 0;  // 音频播放开始时间tick
+
+// 音频时长记录循环数组
+#define AUDIO_DURATION_BUFFER_SIZE 50
+
+// 音频时长记录结构体
+typedef struct {
+    uint32_t duration;    // 音频时长(毫秒)
+    bool is_valid;        // 是否有效
+} audio_duration_record_t;
+
+static audio_duration_record_t g_audio_duration_buffer[AUDIO_DURATION_BUFFER_SIZE];
+static uint32_t g_audio_duration_index = 0;
+static uint32_t g_audio_duration_count = 0;
 
 // 函数声明
 static void generate_chat_id(char *chat_id, size_t size);
@@ -283,16 +300,11 @@ static int ln_ws_heartbeat_callback(void)
         
         LN_WS_DEBUG("Heartbeat callback: audio playback active, elapsed ticks: %u\n", elapsed_ticks);
         
-        // 如果超过超时时间没有音频写入,认为播放结束
-        if (elapsed_ticks >= g_audio_timeout_ticks) {
-            LN_WS_INFO("Audio playback timeout detected, triggering end event\n");
-            g_audio_playback_active = false;
-            
-            // 触发播放结束事件
-            if (g_ws_message_callback != NULL) {
-                g_ws_message_callback(NULL, WS_CMD_AUDIO_PLAYBACK_END);
-            }
-        }
+        // 优先使用基于音频时长的精确判断
+        check_playback_completion_by_duration();
+ 
+        // 使用基于超时的兜底机制
+        check_playback_completion_by_timeout();
     }
     
     if (heartbeat_counter >= 30) { // 30秒 = 30次 * 1秒
@@ -350,6 +362,7 @@ int ln_ws_send_ping(void)
 }
 
 
+
 // 生成聊天ID
 static void generate_chat_id(char *chat_id, size_t size)
 {
@@ -411,6 +424,18 @@ static void ln_ws_audio_write_callback(uint32_t size)
     
     // 更新最后一次音频写入tick
     g_last_audio_write_tick = osKernelGetTickCount();
+    
+    // 如果音频播放从非活跃状态转为活跃状态,记录开始播放时间
+    if (!g_audio_playback_active) {
+        g_audio_playback_start_tick = g_last_audio_write_tick;
+        LN_WS_INFO("Audio playback started at tick: %u\n", g_audio_playback_start_tick);
+        
+        // 触发音频播放开始事件
+        if (g_ws_message_callback != NULL) {
+            g_ws_message_callback(NULL, WS_CMD_AUDIO_PLAYBACK_START);
+        }
+    }
+    
     g_audio_playback_active = true;
 }
 
@@ -551,7 +576,7 @@ int ln_ws_send_audio_data(const uint8_t *audio_data, uint32_t audio_size, bool f
     // 发送JSON字符串
     int ret = uapi_ws_send_text(json_string);
     
-    LN_WS_ERROR("Sent audio data: chatId=%s, messageId=%s, size=%u, finished=%s\n", 
+    LN_WS_DEBUG("Sent audio data: chatId=%s, messageId=%s, size=%u, finished=%s\n", 
            g_audio_chat_ctx.chat_id, message_id, audio_size, finished ? "true" : "false");
     
     // 清理内存
@@ -570,6 +595,7 @@ int ln_ws_send_audio_data(const uint8_t *audio_data, uint32_t audio_size, bool f
     return ret;
 }
 
+
 // 开始新的音频聊天
 int ln_ws_start_audio_chat(ln_audio_chat_command_t command_type)
 {
@@ -594,6 +620,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;
     
+    // 清空音频时长数组,避免不同对话间的数据混淆
+    clear_audio_duration_buffer();
+    
     // 重置对话ID匹配标志
     reset_chat_id_match();
     
@@ -825,8 +854,21 @@ int ln_ws_start(ws_message_callback_t_ln callback, uint32_t audio_timeout_ticks)
         g_ws_running = 0;
         return ret;
     }
+
+     AIVoiceAudioParams audioParams_in = {
+        .audioFormat = MP3,
+        .channelCount = 1,
+        .sampleRate = 16000
+    };
+    
+    AIVoiceAudioParams audioParams_out = {
+        .audioFormat = MP3,
+        .channelCount = 1,
+        .sampleRate = 8000
+    };
+
     
-    old_Start_AIVoice(AIVoiceDataCallback_ln,NULL,NULL,NULL,false);
+    old_Start_AIVoice(AIVoiceDataCallback_ln,NULL,&audioParams_in,&audioParams_out,false);
 
     // 注册音频数据写入回调
     AI_RegisterWriteAudioDataCallback(ln_ws_audio_write_callback);
@@ -919,6 +961,7 @@ int ln_ws_send_random_story_request(const char *story_theme)
     return ret;
 }
 
+
 /* 发送语音聊天打断请求 */
 int ln_ws_send_voice_chat_interrupt(const char *reason)
 {
@@ -999,6 +1042,7 @@ int ln_ws_send_voice_chat_interrupt(const char *reason)
 }
 
 
+
 /* 停止:关闭心跳并关闭连接 */
 int ln_ws_stop(void)
 {
@@ -1016,9 +1060,15 @@ int ln_ws_stop(void)
     // 清理音频缓存
     ln_ws_audio_buffer_cleanup();
     
+    // 重置音频播放相关状态
+    g_audio_playback_active = false;
+    g_audio_playback_start_tick = 0;
+    g_last_audio_write_tick = 0;
+    
     return ret;
 }
 
+
 /* 获取当前连接状态 */
 int ln_ws_get_connection_state(void)
 {
@@ -1155,6 +1205,8 @@ static ws_command_t ln_ws_parse_command_string(const char *command_str)
 //       "errorCode": null
 //     }
 //   }
+
+// {"deviceId":"860132063148589","timestamp":1760408698,"messageId":"1edfb1fc9d904ceb8ca5579241aee1ee","command":"VoiceChatResponse","data":{"finished":false,"chatId":"chat_1760408691_1125307275","data":{"audioDuration":1583,"audioUrl":"http://ambybox-api.test.leiniao.com/ambybox-device-api/file/933515348189_20251014102458_1760408698051.mp3","textContent":"有什么想聊聊的吗?","intention":"None","npcEmotion":"winking"}}}
 static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_msg)
 {
     if (!json_str || !data_msg) {
@@ -1260,11 +1312,19 @@ static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_ms
                     strncpy(data_msg->text_content, text_content->valuestring, sizeof(data_msg->text_content) - 1);
                     data_msg->text_content[sizeof(data_msg->text_content) - 1] = '\0';
                 }
+                
+                // 解析audioDuration字段
+                cJSON *audio_duration = cJSON_GetObjectItem(data_data, "audioDuration");
+                if (audio_duration && cJSON_IsNumber(audio_duration)) {
+                    data_msg->audio_duration = (uint32_t)audio_duration->valuedouble;
+                    // 记录音频时长
+                    record_audio_duration(data_msg->audio_duration);
+                }
             }
         }
         
-        LN_WS_DEBUG("VoiceChatResponse parsed: finished=%s, chatId=%s, audioUrl=%s, textContent=%s\n",
-               data_msg->finished ? "true" : "false", data_msg->chat_id, data_msg->audio_url, data_msg->text_content);
+        LN_WS_DEBUG("VoiceChatResponse parsed: finished=%s, chatId=%s, audioUrl=%s, textContent=%s, audioDuration=%u\n",
+               data_msg->finished ? "true" : "false", data_msg->chat_id, data_msg->audio_url, data_msg->text_content, data_msg->audio_duration);
     }
     
     // 解析ChooseStoryResponse特定字段
@@ -1305,6 +1365,13 @@ static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_ms
                     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';
                 }
+
+                cJSON *audio_duration = cJSON_GetObjectItem(data_data, "audioDuration");
+                if (audio_duration && cJSON_IsNumber(audio_duration)) {
+                    data_msg->audio_duration = (uint32_t)audio_duration->valuedouble;
+                    // 记录音频时长
+                    record_audio_duration(data_msg->audio_duration);
+                    }
             }
         }
         
@@ -1360,6 +1427,13 @@ static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_ms
                     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';
                 }
+
+                cJSON *audio_duration = cJSON_GetObjectItem(data_data, "audioDuration");
+                if (audio_duration && cJSON_IsNumber(audio_duration)) {
+                    data_msg->audio_duration = (uint32_t)audio_duration->valuedouble;
+                    // 记录音频时长
+                    record_audio_duration(data_msg->audio_duration);
+                }
             }
         }
         
@@ -1416,9 +1490,15 @@ static void ln_ws_default_message_handler(const ws_data_msg_t *data_msg, ws_comm
         case WS_CMD_DISCONNECTED:
             LN_WS_DEBUG("Default: Processing WebSocket Disconnected event\n");
             break;
+        case WS_CMD_AUDIO_PLAYBACK_START:
+            LN_WS_DEBUG("Default: Processing Audio Playback Start event\n");
+            break;
         case WS_CMD_AUDIO_PLAYBACK_END:
             LN_WS_DEBUG("Default: Processing Audio Playback End event\n");
             break;
+        case WS_CMD_SENTENCE_PLAYBACK_COMPLETE:
+            LN_WS_DEBUG("Default: Processing Sentence Playback Complete event\n");
+            break;
         case WS_CMD_AUDIO_RECORDING_START:
             LN_WS_DEBUG("Default: Processing Audio Recording Start event\n");
             break;
@@ -1552,6 +1632,7 @@ int ln_ws_enter_sleep(void)
     return ret;
 }
 
+
 /**
  * @brief 退出休眠状态(公共接口)
  * @return 0成功,-1失败
@@ -1575,6 +1656,7 @@ int ln_ws_exit_sleep(void)
     return ret;
 }
 
+
 /* ============================ Base64缓存管理函数 ============================ */
 
 /**
@@ -2318,4 +2400,158 @@ static int ln_ws_send_audio_data_direct(const uint8_t *audio_data, uint32_t audi
     }
     
     return ret;
-}
\ No newline at end of file
+}
+
+
+// 清空音频时长数组(新对话开始时调用)
+static void clear_audio_duration_buffer(void)
+{
+    // 重置所有记录为无效
+    for (uint32_t i = 0; i < AUDIO_DURATION_BUFFER_SIZE; i++) {
+        g_audio_duration_buffer[i].duration = 0;
+        g_audio_duration_buffer[i].is_valid = false;
+    }
+    
+    // 重置索引和计数
+    g_audio_duration_index = 0;
+    g_audio_duration_count = 0;
+    
+    LN_WS_INFO("Audio duration buffer cleared for new conversation\n");
+}
+
+// 记录音频时长到循环数组
+static void record_audio_duration(uint32_t duration)
+{
+    // 判断时长是否有效:大于0且小于10分钟(600000毫秒)
+    bool is_valid = (duration > 0 && duration < 600000);
+    
+    // 获取当前记录位置
+    audio_duration_record_t *record = &g_audio_duration_buffer[g_audio_duration_index];
+    
+    // 填充记录数据
+    record->duration = duration;
+    record->is_valid = is_valid;  // 新记录默认为有效(如果时长合理)
+    
+    // 更新索引和计数
+    g_audio_duration_index = (g_audio_duration_index + 1) % AUDIO_DURATION_BUFFER_SIZE;
+    if (g_audio_duration_count < AUDIO_DURATION_BUFFER_SIZE) {
+        g_audio_duration_count++;
+    }
+}
+
+// 获取最早且有效的音频时长
+static uint32_t get_oldest_valid_audio_duration(void)
+{
+    if (g_audio_duration_count == 0) {
+        return 0;
+    }
+    
+    // 从最早的记录开始查找有效数据
+    uint32_t oldest_index = (g_audio_duration_index >= g_audio_duration_count) ? 
+                           (g_audio_duration_index - g_audio_duration_count) : 
+                           (AUDIO_DURATION_BUFFER_SIZE - (g_audio_duration_count - g_audio_duration_index));
+    
+    // 查找最早的有效记录
+    for (uint32_t i = 0; i < g_audio_duration_count; i++) {
+        uint32_t current_index = (oldest_index + i) % AUDIO_DURATION_BUFFER_SIZE;
+        audio_duration_record_t *record = &g_audio_duration_buffer[current_index];
+        
+        if (record->is_valid) {
+            return record->duration;
+        }
+    }
+    
+    return 0; // 没有找到有效的音频时长
+}
+
+// 设置最早且有效的音频时长记录为无效(播放完成后调用)
+static void mark_oldest_valid_audio_duration_invalid(void)
+{
+    if (g_audio_duration_count == 0) {
+        return;
+    }
+    
+    // 从最早的记录开始查找有效数据
+    uint32_t oldest_index = (g_audio_duration_index >= g_audio_duration_count) ? 
+                           (g_audio_duration_index - g_audio_duration_count) : 
+                           (AUDIO_DURATION_BUFFER_SIZE - (g_audio_duration_count - g_audio_duration_index));
+    
+    // 查找最早的有效记录
+    for (uint32_t i = 0; i < g_audio_duration_count; i++) {
+        uint32_t current_index = (oldest_index + i) % AUDIO_DURATION_BUFFER_SIZE;
+        audio_duration_record_t *record = &g_audio_duration_buffer[current_index];
+        
+        if (record->is_valid) {
+            // 找到最早的有效记录,设置为无效
+            record->is_valid = false;
+            LN_WS_DEBUG("Marked oldest valid audio duration record as invalid (duration: %u ms)\n", record->duration);
+            return;
+        }
+    }
+    
+    LN_WS_DEBUG("No valid audio duration record found to mark as invalid\n");
+}
+
+// ===== 播放完成判断函数 =====
+
+// 基于音频时长的精确判断
+static bool check_playback_completion_by_duration(void)
+{
+    uint32_t oldest_audio_duration_ms = get_oldest_valid_audio_duration();
+    
+    if (oldest_audio_duration_ms == 0) {
+        return false; // 没有有效的音频时长数据
+    }
+    
+    uint32_t current_tick = osKernelGetTickCount();
+    uint32_t expected_duration_ticks = oldest_audio_duration_ms;
+    uint32_t playback_elapsed_ticks = current_tick - g_audio_playback_start_tick;
+    
+    LN_WS_DEBUG("Duration-based check: expected %u ticks, elapsed %u ticks\n", 
+               expected_duration_ticks, playback_elapsed_ticks);
+    
+    // 如果播放时间已经超过预期时长,认为播放完成
+    if (playback_elapsed_ticks >= expected_duration_ticks) {
+        LN_WS_INFO("Audio playback completed based on duration: %u ms\n", oldest_audio_duration_ms);
+        
+        // 标记最早且有效的音频时长记录为无效(已播放完成)
+        mark_oldest_valid_audio_duration_invalid();
+        g_audio_playback_start_tick = g_last_audio_write_tick;  // 重置播放开始时间
+        
+        // 触发单句语音播放完成事件
+        if (g_ws_message_callback != NULL) {
+            g_ws_message_callback(NULL, WS_CMD_SENTENCE_PLAYBACK_COMPLETE);
+        }
+        
+        return true; // 播放已完成
+    }
+    
+    return false; // 播放未完成
+}
+
+// 基于超时的兜底机制判断
+static bool check_playback_completion_by_timeout(void)
+{
+    uint32_t current_tick = osKernelGetTickCount();
+    uint32_t elapsed_ticks = current_tick - g_last_audio_write_tick;
+    
+    if (elapsed_ticks >= g_audio_timeout_ticks) {
+        LN_WS_INFO("Audio playback timeout detected, elapsed: %u ticks\n", elapsed_ticks);
+        
+        // 计算播放总时长
+        uint32_t playback_duration = current_tick - g_audio_playback_start_tick;
+        LN_WS_INFO("Audio playback ended by timeout, total duration: %u ticks\n", playback_duration);
+        
+        g_audio_playback_active = false;
+        g_audio_playback_start_tick = 0;  // 重置播放开始时间
+        
+        // 触发播放结束事件
+        if (g_ws_message_callback != NULL) {
+            g_ws_message_callback(NULL, WS_CMD_AUDIO_PLAYBACK_END);
+        }
+        
+        return true; // 播放已完成
+    }
+    
+    return false; // 播放未完成
+}