mqtt修改
authorhcj <1224667075@qq.com>
Fri, 3 Apr 2026 11:10:36 +0000 (19:10 +0800)
committerhcj <1224667075@qq.com>
Fri, 3 Apr 2026 11:10:36 +0000 (19:10 +0800)
code/sdk/zhihui/MQTT/MQTT_server_mian.c
code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt
code/sdk/zhihui/leiniao/imsl_ln/LN_MQTT/ln_TCP.c [new file with mode: 0644]
code/sdk/zhihui/leiniao/imsl_ln/LN_MQTT/ln_UDP.c
code/sdk/zhihui/leiniao/imsl_ln/LN_MQTT/ln_data_decode.c [new file with mode: 0644]
code/sdk/zhihui/leiniao/imsl_ln/LN_MQTT/ln_data_decode.h [new file with mode: 0644]
code/sdk/zhihui/leiniao/imsl_ln/LN_MQTT/ln_mqtt_api.c
code/sdk/zhihui/leiniao/imsl_ln/LN_MQTT/ln_mqtt_api.h
code/sdk/zhihui/leiniao/imsl_ln/LN_MQTT/ln_mqtt_api_at.c

index 9a9bb95..8a5d8c4 100644 (file)
@@ -4,6 +4,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <stdint.h>
+#include <stdbool.h>
 #include <linux/delay.h>
 #include "uapi_Mqtt_api.h"
 
@@ -65,6 +66,12 @@ typedef struct {
     } msg;
 } mqtt_queue_msg_t;
 
+/* 主线程用于检测变化的上一次快照:连接结果码 + 连接阶段(合并原 g_mqtt_last_connection_status 与 last_connection_state) */
+typedef struct {
+    volatile int last_status; /* 对应 g_mqtt_connection_status,-1 未初始化 */
+    int last_state;           /* 对应 g_mqtt_connection_state(MQTT_STATE_*) */
+} mqtt_connection_last_t;
+
 /* 全局变量 */
 static volatile int g_mqtt_thread_running = 0;
 static uint32_t g_mqtt_send_thread_handle = 0;
@@ -73,7 +80,7 @@ static osMessageQueueId_t g_mqtt_send_queue = NULL;  // 发送队列(发送消
 static osMessageQueueId_t g_mqtt_recv_queue = NULL;  // 接收队列(接收消息,由主线程处理)
 static volatile int g_mqtt_connection_state = MQTT_STATE_DISCONNECTED;
 static volatile int g_mqtt_connection_status = -1;  // 连接状态码(-1表示未初始化,0表示连接成功,其他值表示错误码)
-static volatile int g_mqtt_last_connection_status = -1;  // 上一次的连接状态码,用于检测状态变化
+static mqtt_connection_last_t g_mqtt_conn_last = { -1, MQTT_STATE_DISCONNECTED };
 static uint32_t g_mqtt_reconnect_count = 0;
 static uint32_t g_mqtt_last_connect_attempt = 0;
 static mqtt_call_backs_t g_mqtt_callbacks = {0};  // MQTT回调函数结构体
@@ -156,6 +163,10 @@ static void mqtt_connection_status_cb(void *arg, int status)
     } else {
         g_mqtt_connection_state = MQTT_STATE_DISCONNECTED;
         MQTT_INFO("MQTT disconnected, status: %d\n", status);
+        if (g_mqtt_thread_running == 0) {
+            printf("mqtt_connection_status_cb g_mqtt_thread_running == 0\n");
+            g_mqtt_conn_last.last_status = -1;
+        }
     }
 }
 
@@ -300,14 +311,10 @@ static void mqtt_main_thread(void *arg)
 {
     (void)arg;
     mqtt_queue_msg_t *msg = NULL;
-    int last_connection_state = MQTT_STATE_DISCONNECTED;
     
     MQTT_INFO("Main thread started\n");
     
-    // 初始化上一次的连接状态码
-    g_mqtt_last_connection_status = g_mqtt_connection_status;
-    
-    // 尝试首次连接(mqtt_try_connect内部会初始化)
+    /* 尝试首次连接(mqtt_try_connect内部会初始化) */
     mqtt_try_connect();
     
     while (g_mqtt_thread_running) {
@@ -321,19 +328,11 @@ static void mqtt_main_thread(void *arg)
                     // 处理接收到的消息
                     if (msg->msg.recv_msg.data != NULL && msg->msg.recv_msg.data_len > 0) {
                         // 打印消息内容(限制长度)
-                        MQTT_DEBUG("Message content: ");
-                        uint16_t print_len = msg->msg.recv_msg.data_len > 100 ? 100 : msg->msg.recv_msg.data_len;
-                        for (uint16_t i = 0; i < print_len; i++) {
-                            uint8_t c = msg->msg.recv_msg.data[i];
-                            if (c >= 32 && c < 127) {
-                                printf("%c", c);
-                            } else {
-                                printf(".");
-                            }
-                        }
-                        if (msg->msg.recv_msg.data_len > 100) {
-                            printf("...");
+                        printf("Message content: ");
+                        for (int i = 0; i < msg->msg.recv_msg.data_len; i++) {
+                            printf("%c", msg->msg.recv_msg.data[i]);
                         }
+                        // printf("%s", msg->msg.recv_msg.data);
                         printf("\n");
                         
                         // 调用用户注册的消息回调(在队列读取时触发)
@@ -375,28 +374,29 @@ static void mqtt_main_thread(void *arg)
             }
         }
         
-        // 检测连接状态码变化(在主线程循环中检测并触发用户回调)
-        if (g_mqtt_connection_status != g_mqtt_last_connection_status) {
-            int current_status = g_mqtt_connection_status;
-            int last_status = g_mqtt_last_connection_status;
+        /* 检测连接结果码 / 连接阶段是否相对上次快照变化(统一维护 g_mqtt_conn_last) */
+        {
+            int cur_status = (int)g_mqtt_connection_status;
+            int cur_state = (int)g_mqtt_connection_state;
+            bool status_changed = (cur_status != g_mqtt_conn_last.last_status);
+            bool state_changed = (cur_state != g_mqtt_conn_last.last_state);
             
+            if (status_changed) {
             MQTT_DEBUG("Connection status changed in main thread: %d -> %d\n", 
-                   last_status, current_status);
-            
-            // 更新上一次的状态码
-            g_mqtt_last_connection_status = current_status;
-            
-            // 调用用户注册的连接回调(在主线程循环中触发)
+                           g_mqtt_conn_last.last_status, cur_status);
+                g_mqtt_conn_last.last_status = cur_status;
+                printf("mqtt_main_thread current_status:%d\n", cur_status);
             if (g_mqtt_callbacks.connect_callback != NULL) {
-                g_mqtt_callbacks.connect_callback(g_mqtt_callbacks.user_arg, current_status);
+                    g_mqtt_callbacks.connect_callback(g_mqtt_callbacks.user_arg, cur_status);
+                } else {
+                    MQTT_DEBUG("No connect callback registered\n");
+                }
             }
-        }
-        
-        // 检测连接状态变化(用于日志输出)
-        if (g_mqtt_connection_state != last_connection_state) {
+            if (state_changed) {
             MQTT_DEBUG("Connection state changed: %d -> %d\n", 
-                   last_connection_state, g_mqtt_connection_state);
-            last_connection_state = g_mqtt_connection_state;
+                           g_mqtt_conn_last.last_state, cur_state);
+                g_mqtt_conn_last.last_state = cur_state;
+            }
         }
         
         // 连接管理:检查连接状态并处理重连
@@ -590,6 +590,8 @@ void uapi_mqtt_main_stop(void)
     // 断开MQTT连接
     if (g_mqtt_connection_state == MQTT_STATE_CONNECTED) {
         uapi_mqtt_disconnect();
+        g_mqtt_conn_last.last_status = -1;
+        g_mqtt_conn_last.last_state = MQTT_STATE_DISCONNECTED;
     }
     
     // 发送停止消息到发送队列(主线程和发送线程都会处理)
@@ -641,6 +643,10 @@ void uapi_mqtt_main_stop(void)
     g_mqtt_main_thread_handle = 0;
     g_mqtt_send_thread_handle = 0;
     g_mqtt_connection_state = MQTT_STATE_DISCONNECTED;
+    g_mqtt_connection_status = -1;
+    g_mqtt_conn_last.last_status = -1;
+    g_mqtt_conn_last.last_state = MQTT_STATE_DISCONNECTED;
+
     
     // 清空回调函数结构体
     memset(&g_mqtt_callbacks, 0, sizeof(mqtt_call_backs_t));
index f8e1243..8cd23ff 100644 (file)
@@ -36,6 +36,8 @@ if((DEFINED SUPPORT_LN_MQTT_SERVICE AND SUPPORT_LN_MQTT_SERVICE) OR ("SUPPORT_LN
         ${CMAKE_CURRENT_SOURCE_DIR}/LN_MQTT/ln_mqtt_api_at.c
         ${CMAKE_CURRENT_SOURCE_DIR}/LN_MQTT/ln_OTA.c
         ${CMAKE_CURRENT_SOURCE_DIR}/LN_MQTT/ln_UDP.c
+        ${CMAKE_CURRENT_SOURCE_DIR}/LN_MQTT/ln_data_decode.c
+        ${CMAKE_CURRENT_SOURCE_DIR}/LN_MQTT/ln_TCP.c
     )
 endif()
 
diff --git a/code/sdk/zhihui/leiniao/imsl_ln/LN_MQTT/ln_TCP.c b/code/sdk/zhihui/leiniao/imsl_ln/LN_MQTT/ln_TCP.c
new file mode 100644 (file)
index 0000000..69b6e0f
--- /dev/null
@@ -0,0 +1,837 @@
+#include "ln_mqtt_api.h"
+#include "ln_api.h"
+#include "custom_utils.h"
+#include "cmsis_os2.h"
+#include "soc_osal.h"
+#include "lwip/sockets.h"
+#include "lwip/netdb.h"
+#include "lwip/inet.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <errno.h>
+#include "opus_frame_to_pcm.h"
+#include "ln_data_decode.h"
+#include "cat1_server.h"
+
+#define TCP_RECV_BUFFER_SIZE 4096
+#define AUDIO_SEND_QUEUE_SIZE 32
+#define AUDIO_SEND_MAX_DATA_SIZE 0x800
+#define TCP_FRAME_HEADER_SIZE 2
+#define UDP_PACKET_HEADER_SIZE 16
+#define LN_CAT1_BRIDGE_HOST "172.16.1.2"
+#define LN_CAT1_BRIDGE_TCP_PORT 5001    
+#ifndef LN_CAT1_BRIDGE_READY_WAIT_MS
+#define LN_CAT1_BRIDGE_READY_WAIT_MS 8000U
+#endif
+#ifndef LN_CAT1_BRIDGE_TCP_READY_WAIT_MS
+#define LN_CAT1_BRIDGE_TCP_READY_WAIT_MS 8000U
+#endif
+
+/* 为 1 时 ln_tcp_create_socket_fd_only 走 cat1 UDP 桥 + 双信号量;为 0 时仅直连 TCP(默认停掉桥接流程,可用 AT 测全路径) */
+#ifndef LN_TCP_CAT1_BRIDGE_IN_FD_ONLY
+#define LN_TCP_CAT1_BRIDGE_IN_FD_ONLY 1
+#endif
+
+#define AUDIO_SEND_PCM_SAMPLE_RATE  16000
+#define AUDIO_SEND_PCM_CHANNELS     1
+#define AUDIO_SEND_PCM_BITRATE      64000
+#define AUDIO_SEND_PCM_APPLICATION  2049
+#define AUDIO_SEND_PCM_FRAME_MS     60
+#define AUDIO_SEND_PCM_FRAME_BYTES ((uint32_t)((AUDIO_SEND_PCM_SAMPLE_RATE) * (AUDIO_SEND_PCM_FRAME_MS) / 1000 * (AUDIO_SEND_PCM_CHANNELS) * 2))
+#define AUDIO_SEND_PCM_FRAME_SAMPLES ((int)((AUDIO_SEND_PCM_SAMPLE_RATE) * (AUDIO_SEND_PCM_FRAME_MS) / 1000))
+#define AUDIO_SEND_PCM_CACHE_ALLOC_FRAMES 100
+#define AUDIO_SEND_PCM_CACHE_ALLOC_SIZE   ((AUDIO_SEND_PCM_CACHE_ALLOC_FRAMES) * (AUDIO_SEND_PCM_FRAME_BYTES) + (AUDIO_SEND_MAX_DATA_SIZE))
+
+typedef struct {
+    uint8_t *data;
+    uint32_t size;
+    uint8_t encoding;
+} audio_send_msg_t;
+
+typedef struct {
+    uint8_t type;
+    uint8_t flags;
+    uint16_t payload_len;
+    uint32_t ssrc;
+    uint32_t timestamp;
+    uint32_t sequence;
+    uint8_t *payload;
+    uint16_t payload_size;
+} ln_tcp_packet_t;
+
+static int g_tcp_socket = -1;
+/** Set on last TCP bridge/socket failure so strerror() is not stale after lwip_close() or unrelated errno. */
+static int g_ln_tcp_last_errno = 0;
+static volatile bool g_tcp_thread_running = false;
+static uint32_t g_tcp_thread_handle = 0;
+
+static unsigned long g_audio_send_queue = 0xFFFFFFFF;
+static volatile bool g_audio_send_thread_running = false;
+static uint32_t g_audio_send_thread_handle = 0;
+
+static uint8_t *g_pcm_send_cache = NULL;
+static uint32_t g_pcm_send_cache_capacity = 0;
+static uint32_t g_pcm_send_cache_size = 0;
+static uint32_t g_tcp_ssrc = 0;
+static uint32_t g_tcp_sequence = 0;
+
+extern int ln_udp_handle_decrypted_payload(uint32_t sequence, uint8_t *decrypted_data, uint16_t payload_len);
+
+static void create_audio_send_queue(void);
+static void ln_tcp_recv_thread(void *arg);
+static void ln_tcp_audio_send_thread(void *arg);
+static int ln_tcp_send_audio_frame(int sock, const uint8_t *audio_data, uint32_t audio_size);
+static int ln_tcp_unpack_packet(const uint8_t *data, uint16_t data_len, ln_tcp_packet_t *packet);
+static void ln_tcp_free_packet(ln_tcp_packet_t *packet);
+static int ln_tcp_process_received_data(const uint8_t *data, uint16_t data_len);
+static int ln_tcp_pack_audio_data(const uint8_t *audio_data, uint16_t audio_len,
+                                  uint8_t *buffer, uint16_t buffer_size, uint16_t *out_len);
+int ln_tcp_create_socket(void);
+void ln_tcp_close_socket(void);
+int ln_tcp_get_socket(void);
+bool ln_tcp_is_socket_created(void);
+int ln_tcp_audio_send_init(void);
+void ln_tcp_audio_send_deinit(void);
+int ln_tcp_audio_send_enqueue(const uint8_t *data, uint32_t size, uint8_t encoding);
+
+static int ln_tcp_send_all(int sock, const uint8_t *buf, uint16_t len)
+{
+    uint16_t sent = 0;
+    while (sent < len) {
+    // printf("ln_tcp_send_all sent %d, len %d\n", sent, len);
+        ssize_t ret = lwip_send(sock, buf + sent, len - sent, 0);
+        if (ret <= 0) {
+            return -1;
+        }
+        sent += (uint16_t)ret;
+    }
+    return 0;
+}
+
+static int ln_tcp_send_packet(int sock, const uint8_t *payload, uint16_t payload_len)
+{
+    uint8_t hdr[TCP_FRAME_HEADER_SIZE];
+    hdr[0] = (uint8_t)((payload_len >> 8) & 0xFF);
+    hdr[1] = (uint8_t)(payload_len & 0xFF);
+
+    if (ln_tcp_send_all(sock, hdr, (uint16_t)sizeof(hdr)) != 0) {
+        return -1;
+    }
+    if (ln_tcp_send_all(sock, payload, payload_len) != 0) {
+        return -1;
+    }
+
+    // printf("ln_tcp_send_packet sent %d\n", payload_len);
+    msleep(10);
+    return 0;
+}
+
+static int ln_tcp_connect_to_modem_bridge(int fd)
+{
+    int opt = 1;
+    if (lwip_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) {
+        LN_MQTT_WARN("ln_tcp SO_REUSEADDR: %s\n", strerror(errno));
+    }
+    if (lwip_setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt)) != 0) {
+        LN_MQTT_WARN("ln_tcp SO_KEEPALIVE: %s\n", strerror(errno));
+    }
+
+    struct sockaddr_in server_addr;
+    memset(&server_addr, 0, sizeof(server_addr));
+    server_addr.sin_family = AF_INET;
+    server_addr.sin_port = lwip_htons(LN_CAT1_BRIDGE_TCP_PORT);
+    server_addr.sin_addr.s_addr = inet_addr(LN_CAT1_BRIDGE_HOST);
+
+    for (int i = 0; i < 5; i++) {
+        if (lwip_connect(fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == 0) {
+            printf("ln_tcp_connect_to_modem_bridge connect ok\n");
+            g_ln_tcp_last_errno = 0;
+            return 0;
+        }
+        g_ln_tcp_last_errno = errno;
+        printf("ln_tcp_connect_to_modem_bridge attempt %d errno=%d (%s)\n",
+               i, g_ln_tcp_last_errno, strerror(g_ln_tcp_last_errno));
+        osDelay(200);
+    }
+    printf("ln_tcp_connect_to_modem_bridge connect failed (last errno=%d %s)\n",
+           g_ln_tcp_last_errno, strerror(g_ln_tcp_last_errno));
+    return -1;
+}
+
+/**
+ * 完整流程:cat1 UDP 桥启动 + 双信号量等待 + TCP 连接 + tcp ready
+ * 供 AT^LN_TCP_CAT1_BRIDGE_TEST 或 LN_TCP_CAT1_BRIDGE_IN_FD_ONLY==1 使用
+ */
+static int ln_tcp_cat1_full_bridge_connect(void)
+{
+    ln_mqtt_udp_info_t udp_info;
+    if (ln_mqtt_api_get_udp_info(&udp_info) != 0 || !udp_info.valid) {
+        g_ln_tcp_last_errno = 0;
+        return -1;
+    }
+
+    if (cat1_udp_bridge_start(udp_info.server, udp_info.port) != ERRCODE_SUCC) {
+        LN_MQTT_WARN("cat1 bridge start cmd send failed\n");
+    }
+    osDelay(100);
+
+    if (cat1_udp_bridge_wait_ready(LN_CAT1_BRIDGE_READY_WAIT_MS) != ERRCODE_SUCC) {
+        LN_MQTT_WARN("cat1 bridge ready timeout (wait %ums)\n", (unsigned)LN_CAT1_BRIDGE_READY_WAIT_MS);
+        g_ln_tcp_last_errno = 0;
+        return -1;
+    }
+
+    printf("cat1 bridge ready ok\n");
+
+    int fd = lwip_socket(AF_INET, SOCK_STREAM, 0);
+    if (fd < 0) {
+        g_ln_tcp_last_errno = errno;
+        LN_MQTT_ERROR("lwip_socket failed: errno=%d (%s)\n", g_ln_tcp_last_errno, strerror(g_ln_tcp_last_errno));
+        return -1;
+    }
+
+    if (ln_tcp_connect_to_modem_bridge(fd) != 0) {
+        /* g_ln_tcp_last_errno already set; lwip_close may clobber errno */
+        lwip_close(fd);
+        return -1;
+    }
+
+    printf("ln_tcp_create_socket_fd_only fd %d\n", fd);
+    printf("ln_tcp_create_socket_fd_only ok\n");
+
+    return fd;
+}
+
+/**
+ * 仅连接 LN_CAT1_BRIDGE_HOST:LN_CAT1_BRIDGE_TCP_PORT,不经过 cat1 UDP 桥(需模组侧已就绪)
+ */
+static int ln_tcp_direct_tcp_connect(void)
+{
+    ln_mqtt_udp_info_t udp_info;
+    if (ln_mqtt_api_get_udp_info(&udp_info) != 0 || !udp_info.valid) {
+        LN_MQTT_WARN("TCP endpoint info unavailable\n");
+        g_ln_tcp_last_errno = 0;
+        return -1;
+    }
+    (void)udp_info;
+
+    LN_MQTT_INFO("ln_tcp direct TCP %s:%u (no cat1 bridge)\n", LN_CAT1_BRIDGE_HOST, (unsigned)LN_CAT1_BRIDGE_TCP_PORT);
+
+    int fd = lwip_socket(AF_INET, SOCK_STREAM, 0);
+    if (fd < 0) {
+        g_ln_tcp_last_errno = errno;
+        return -1;
+    }
+
+    if (ln_tcp_connect_to_modem_bridge(fd) != 0) {
+        lwip_close(fd);
+        return -1;
+    }
+
+    printf("ln_tcp direct ok fd=%d\n", fd);
+    return fd;
+}
+
+static int ln_tcp_create_socket_fd_only(void)
+{
+
+    return ln_tcp_cat1_full_bridge_connect();
+
+}
+
+/** AT 测试:与业务一致,经 ln_tcp_pack_audio_data(加密)+ ln_tcp_send_packet(2 字节 TCP 帧头) */
+static int ln_tcp_at_send_test_frames(int sock)
+{
+    uint8_t test_pl[16];
+    for (unsigned i = 0; i < sizeof(test_pl); i++) {
+        test_pl[i] = (uint8_t)(0xA0U + i);
+    }
+
+    uint8_t send_buf[UDP_PACKET_HEADER_SIZE + AUDIO_SEND_MAX_DATA_SIZE];
+    uint16_t out_len = 0;
+
+    if (ln_tcp_pack_audio_data(test_pl, (uint16_t)sizeof(test_pl), send_buf, (uint16_t)sizeof(send_buf), &out_len) != 0) {
+        LN_MQTT_WARN("AT test: ln_tcp_pack_audio_data failed (check UDP key/nonce)\n");
+        return -1;
+    }
+    if (ln_tcp_send_packet(sock, send_buf, out_len) != 0) {
+        LN_MQTT_WARN("AT test: ln_tcp_send_packet frame1 failed\n");
+        return -1;
+    }
+    printf("+LN_TCP_CAT1_BRIDGE_TEST: sent frame1 enc_payload=%u tcp_bytes=%u\n",
+           (unsigned)sizeof(test_pl), (unsigned)(TCP_FRAME_HEADER_SIZE + out_len));
+
+    if (ln_tcp_pack_audio_data(test_pl, (uint16_t)sizeof(test_pl), send_buf, (uint16_t)sizeof(send_buf), &out_len) != 0) {
+        return -1;
+    }
+    if (ln_tcp_send_packet(sock, send_buf, out_len) != 0) {
+        LN_MQTT_WARN("AT test: ln_tcp_send_packet frame2 failed\n");
+        return -1;
+    }
+    printf("+LN_TCP_CAT1_BRIDGE_TEST: sent frame2 tcp_bytes=%u\n", (unsigned)(TCP_FRAME_HEADER_SIZE + out_len));
+    return 0;
+}
+
+static void ln_tcp_at_try_recv_log(int sock)
+{
+    uint8_t rbuf[256];
+    fd_set rfds;
+    struct timeval tv;
+
+    FD_ZERO(&rfds);
+    FD_SET(sock, &rfds);
+    tv.tv_sec = 0;
+    tv.tv_usec = 300000;
+
+    if (lwip_select(sock + 1, &rfds, NULL, NULL, &tv) > 0 && FD_ISSET(sock, &rfds)) {
+        ssize_t n = lwip_recv(sock, rbuf, sizeof(rbuf), 0);
+        if (n > 0) {
+            printf("+LN_TCP_CAT1_BRIDGE_TEST: recv %d bytes (optional echo from bridge)\n", (int)n);
+        } else {
+            printf("+LN_TCP_CAT1_BRIDGE_TEST: recv closed or err\n");
+        }
+    } else {
+        printf("+LN_TCP_CAT1_BRIDGE_TEST: recv idle 300ms (normal if no echo)\n");
+    }
+}
+
+int ln_tcp_at_test_cat1_full_bridge(void)
+{
+    printf("[LN_TCP_AT] AT^LN_TCP_CAT1_BRIDGE_TEST: bridge + encrypt send + recv probe\n");
+    int fd = ln_tcp_cat1_full_bridge_connect();
+    if (fd < 0) {
+        printf("+LN_TCP_CAT1_BRIDGE_TEST: FAIL connect\n");
+        return -1;
+    }
+    printf("+LN_TCP_CAT1_BRIDGE_TEST: connect OK fd=%d\n", fd);
+
+    if (ln_tcp_at_send_test_frames(fd) != 0) {
+        printf("+LN_TCP_CAT1_BRIDGE_TEST: FAIL send\n");
+        lwip_close(fd);
+        return -1;
+    }
+
+    ln_tcp_at_try_recv_log(fd);
+
+    printf("+LN_TCP_CAT1_BRIDGE_TEST: OK (closing fd)\n");
+    lwip_close(fd);
+    return 0;
+}
+
+static int ln_tcp_unpack_packet(const uint8_t *data, uint16_t data_len, ln_tcp_packet_t *packet)
+{
+    if (!data || !packet || data_len < UDP_PACKET_HEADER_SIZE) {
+        return -1;
+    }
+
+    memset(packet, 0, sizeof(ln_tcp_packet_t));
+    const uint8_t *ptr = data;
+    packet->type = ptr[0];
+    packet->flags = ptr[1];
+    packet->payload_len = lwip_ntohs(*(uint16_t *)(ptr + 2));
+    packet->ssrc = lwip_ntohl(*(uint32_t *)(ptr + 4));
+    packet->timestamp = lwip_ntohl(*(uint32_t *)(ptr + 8));
+    packet->sequence = lwip_ntohl(*(uint32_t *)(ptr + 12));
+
+    if (packet->payload_len > data_len - UDP_PACKET_HEADER_SIZE) {
+        return -1;
+    }
+
+    packet->payload_size = packet->payload_len;
+    if (packet->payload_len > 0) {
+        packet->payload = (uint8_t *)malloc(packet->payload_len);
+        if (packet->payload == NULL) {
+            return -1;
+        }
+        memcpy(packet->payload, data + UDP_PACKET_HEADER_SIZE, packet->payload_len);
+    }
+    return 0;
+}
+
+static void ln_tcp_free_packet(ln_tcp_packet_t *packet)
+{
+    if (packet != NULL && packet->payload != NULL) {
+        free(packet->payload);
+        packet->payload = NULL;
+    }
+}
+
+static int ln_tcp_process_received_data(const uint8_t *data, uint16_t data_len)
+{
+    ln_tcp_packet_t packet;
+    memset(&packet, 0, sizeof(packet));
+
+    if (ln_tcp_unpack_packet(data, data_len, &packet) != 0) {
+        LN_MQTT_ERROR("TCP unpack packet failed\n");
+        return -1;
+    }
+
+    if (packet.payload_len > 0 && packet.payload != NULL) {
+        ln_mqtt_udp_info_t udp_info;
+        if (ln_mqtt_api_get_udp_info(&udp_info) != 0 || !udp_info.valid) {
+            ln_tcp_free_packet(&packet);
+            return -1;
+        }
+
+        uint8_t *decrypted_data = (uint8_t *)malloc(packet.payload_len);
+        if (decrypted_data == NULL) {
+            ln_tcp_free_packet(&packet);
+            return -1;
+        }
+
+        if (ln_udp_decrypt_audio(udp_info.key, udp_info.nonce, packet.payload_len,
+                                 packet.timestamp, packet.sequence, packet.payload,
+                                 packet.payload_len, decrypted_data) != 0) {
+            free(decrypted_data);
+            ln_tcp_free_packet(&packet);
+            return -1;
+        }
+
+        (void)ln_udp_handle_decrypted_payload(packet.sequence, decrypted_data, packet.payload_len);
+    }
+
+    ln_tcp_free_packet(&packet);
+    return 0;
+}
+
+static int ln_tcp_pack_audio_data(const uint8_t *audio_data, uint16_t audio_len,
+                                  uint8_t *buffer, uint16_t buffer_size, uint16_t *out_len)
+{
+    if (!audio_data || !buffer || !out_len || audio_len == 0) {
+        return -1;
+    }
+    if (buffer_size < (uint16_t)(UDP_PACKET_HEADER_SIZE + audio_len)) {
+        return -1;
+    }
+
+    ln_mqtt_udp_info_t udp_info;
+    if (ln_mqtt_api_get_udp_info(&udp_info) != 0 || !udp_info.valid) {
+        return -1;
+    }
+
+    if (g_tcp_ssrc == 0) {
+        if (ln_data_nonce_to_ssrc(udp_info.nonce, &g_tcp_ssrc) != 0) {
+            g_tcp_ssrc = 0x12345678;
+        }
+    }
+
+    uint32_t sequence = g_tcp_sequence++;
+    uint32_t timestamp = (uint32_t)osKernelGetTickCount();
+    uint8_t *encrypted_payload = buffer + UDP_PACKET_HEADER_SIZE;
+
+    if (ln_udp_encrypt_audio(udp_info.key, udp_info.nonce, audio_len, timestamp, sequence,
+                             audio_data, audio_len, encrypted_payload) != 0) {
+        return -1;
+    }
+
+    buffer[0] = 0x01;
+    buffer[1] = 0x00;
+    *(uint16_t *)(buffer + 2) = lwip_htons(audio_len);
+    *(uint32_t *)(buffer + 4) = lwip_htonl(g_tcp_ssrc);
+    *(uint32_t *)(buffer + 8) = lwip_htonl(timestamp);
+    *(uint32_t *)(buffer + 12) = lwip_htonl(sequence);
+
+    *out_len = (uint16_t)(UDP_PACKET_HEADER_SIZE + audio_len);
+    return 0;
+}
+
+static void ln_tcp_recv_thread(void *arg)
+{
+    (void)arg;
+    uint8_t* recv_buf=malloc(TCP_RECV_BUFFER_SIZE);
+    uint8_t* frame_buf=malloc(TCP_RECV_BUFFER_SIZE * 2);
+    if(recv_buf == NULL || frame_buf == NULL)
+    {
+        LN_MQTT_ERROR("TCP receive thread malloc failed\n");
+        return;
+    }
+    memset(recv_buf, 0, TCP_RECV_BUFFER_SIZE);
+    memset(frame_buf, 0, TCP_RECV_BUFFER_SIZE * 2);
+    uint32_t frame_len = 0;
+
+    LN_MQTT_INFO("TCP receive thread started\n");
+
+    while (g_tcp_thread_running && g_tcp_socket >= 0) {
+        fd_set read_fds;
+        struct timeval timeout;
+        FD_ZERO(&read_fds);
+        FD_SET(g_tcp_socket, &read_fds);
+        timeout.tv_sec = 1;
+        timeout.tv_usec = 0;
+
+        int select_ret = lwip_select(g_tcp_socket + 1, &read_fds, NULL, NULL, &timeout);
+        if (select_ret > 0 && FD_ISSET(g_tcp_socket, &read_fds)) {
+            ssize_t recv_len = lwip_recv(g_tcp_socket, recv_buf,TCP_RECV_BUFFER_SIZE, 0);
+            if (recv_len > 0) {
+                if ((frame_len + (uint32_t)recv_len) > sizeof(frame_buf)) {
+                    frame_len = 0;
+                    LN_MQTT_WARN("TCP frame buffer overflow, reset parser\n");
+                    continue;
+                }
+
+                memcpy(frame_buf + frame_len, recv_buf, (size_t)recv_len);
+                frame_len += (uint32_t)recv_len;
+
+                while (frame_len >= TCP_FRAME_HEADER_SIZE) {
+                    uint16_t payload_len = (uint16_t)((frame_buf[0] << 8) | frame_buf[1]);
+                    if (payload_len == 0 || payload_len > (uint16_t)(TCP_RECV_BUFFER_SIZE - TCP_FRAME_HEADER_SIZE)) {
+                        LN_MQTT_WARN("Invalid TCP payload_len=%u, reset parser\n", payload_len);
+                        frame_len = 0;
+                        break;
+                    }
+                    if (frame_len < (uint32_t)(TCP_FRAME_HEADER_SIZE + payload_len)) {
+                        break;
+                    }
+
+                    (void)ln_tcp_process_received_data(frame_buf + TCP_FRAME_HEADER_SIZE, payload_len);
+
+                    frame_len -= (uint32_t)(TCP_FRAME_HEADER_SIZE + payload_len);
+                    if (frame_len > 0) {
+                        memmove(frame_buf, frame_buf + TCP_FRAME_HEADER_SIZE + payload_len, frame_len);
+                    }
+                }
+            } else if (recv_len == 0) {
+                LN_MQTT_WARN("TCP peer closed connection\n");
+                break;
+            } else {
+                if (errno != EAGAIN && errno != EWOULDBLOCK) {
+                    LN_MQTT_ERROR("TCP recv failed: %s\n", strerror(errno));
+                    break;
+                }
+            }
+        } else if (select_ret < 0 && errno != EINTR) {
+            LN_MQTT_ERROR("TCP select failed: %s\n", strerror(errno));
+            break;
+        }
+    }
+
+    g_tcp_thread_running = false;
+    free(recv_buf);
+    free(frame_buf);
+    LN_MQTT_INFO("TCP receive thread exited\n");
+}
+
+
+
+
+int ln_tcp_create_socket(void)
+{
+    ln_mqtt_udp_info_t udp_info;
+    if (ln_mqtt_api_get_udp_info(&udp_info) != 0 || !udp_info.valid) {
+        LN_MQTT_WARN("TCP endpoint info unavailable\n");
+        return -1;
+    }
+
+    if (g_tcp_socket >= 0) {
+        ln_tcp_close_socket();
+    }
+
+    LN_MQTT_INFO("Creating TCP socket for server: %s:%d\n", udp_info.server, udp_info.port);
+    g_ln_tcp_last_errno = 0;
+    g_tcp_socket = ln_tcp_create_socket_fd_only();
+    if (g_tcp_socket < 0) {
+        if (g_ln_tcp_last_errno != 0) {
+            LN_MQTT_ERROR("TCP bridge connect failed: %s (errno=%d)\n",
+                          strerror(g_ln_tcp_last_errno), g_ln_tcp_last_errno);
+        } else {
+            LN_MQTT_ERROR("TCP bridge connect failed (check UDP info / cat1 bridge ready timeout)\n");
+        }
+        return -1;
+    }
+    printf("g_tcp_socket %d\n", g_tcp_socket);
+
+    if (!g_tcp_thread_running) {
+        app_task_definition_ext_t recv_task = {
+            .attr = {
+                .name = "ln_tcp_recv",
+                .stack_size = 0x6000,
+                .priority = osPriorityNormal
+            },
+            .func = ln_tcp_recv_thread,
+            .task_handle = &g_tcp_thread_handle,
+            .create_queue_handle = NULL,
+            .use_mem = USE_EXTEND_MEM,
+            .inst_args = NULL
+        };
+
+        g_tcp_thread_running = true;
+        errcode_t ret = uapi_create_osThread_custom_with_args(&recv_task);
+        if (ret != ERRCODE_SUCC) {
+            g_tcp_thread_running = false;
+            lwip_close(g_tcp_socket);
+            g_tcp_socket = -1;
+            LN_MQTT_ERROR("Failed to create TCP recv thread: %d\n", ret);
+            return -1;
+        }
+    }
+
+    LN_MQTT_INFO("TCP socket connected, fd=%d\n", g_tcp_socket);
+    return 0;
+}
+
+void ln_tcp_close_socket(void)
+{
+    if (g_tcp_thread_running) {
+        g_tcp_thread_running = false;
+        uint32_t timeout = osKernelGetTickCount() + osKernelGetTickFreq() * 2;
+        while (g_tcp_thread_handle != 0 && osKernelGetTickCount() < timeout) {
+            osDelay(10);
+        }
+        g_tcp_thread_handle = 0;
+    }
+
+    if (g_tcp_socket >= 0) {
+        lwip_close(g_tcp_socket);
+        g_tcp_socket = -1;
+    }
+
+    (void)cat1_udp_bridge_stop();
+}
+
+int ln_tcp_get_socket(void)
+{
+    return g_tcp_socket;
+}
+
+bool ln_tcp_is_socket_created(void)
+{
+    return (g_tcp_socket >= 0);
+}
+
+static void create_audio_send_queue(void)
+{
+    if (g_audio_send_queue != 0xFFFFFFFF) {
+        return;
+    }
+    if (osal_msg_queue_create("ln_tcp_audio_send_queue", AUDIO_SEND_QUEUE_SIZE, &g_audio_send_queue,
+                              0, sizeof(audio_send_msg_t)) != ERRCODE_SUCC) {
+        g_audio_send_queue = 0xFFFFFFFF;
+        LN_MQTT_ERROR("Failed to create TCP audio send queue\n");
+    }
+}
+
+static int ln_tcp_send_audio_frame(int sock, const uint8_t *audio_data, uint32_t audio_size)
+{
+    if (audio_data == NULL || audio_size == 0 || audio_size > AUDIO_SEND_MAX_DATA_SIZE) {
+        return -1;
+    }
+
+    uint8_t send_buf[16 + AUDIO_SEND_MAX_DATA_SIZE];
+    uint16_t out_len = 0;
+    if (ln_tcp_pack_audio_data(audio_data, (uint16_t)audio_size, send_buf,
+                               (uint16_t)sizeof(send_buf), &out_len) != 0) {
+        return -1;
+    }
+
+    if (ln_tcp_send_packet(sock, send_buf, out_len) != 0) {
+        LN_MQTT_ERROR("TCP send packet failed\n");
+        return -1;
+    }
+    return 0;
+}
+
+static void ln_tcp_audio_send_thread(void *arg)
+{
+    (void)arg;
+    audio_send_msg_t msg;
+    uint32_t msgsize = sizeof(audio_send_msg_t);
+
+    LN_MQTT_INFO("TCP audio send thread started\n");
+
+    while (g_audio_send_thread_running) {
+        int32_t ret = osal_msg_queue_read_copy(g_audio_send_queue, (void *)&msg, &msgsize,
+                                               OSAL_MSGQ_WAIT_FOREVER);
+        if (ret != OSAL_SUCCESS) {
+            continue;
+        }
+        // printf("ln_tcp_audio_send_thread msgsize %d\n", msgsize);
+
+        if (msg.size == 0 || msg.size > AUDIO_SEND_MAX_DATA_SIZE || msg.data == NULL) {
+            if (msg.data != NULL) {
+                free(msg.data);
+            }
+            msgsize = sizeof(audio_send_msg_t);
+            continue;
+        }
+
+        if (!ln_tcp_is_socket_created()) {
+            free(msg.data);
+            msgsize = sizeof(audio_send_msg_t);
+            continue;
+        }
+
+        int sock = ln_tcp_get_socket();
+        if (sock < 0) {
+            free(msg.data);
+            msgsize = sizeof(audio_send_msg_t);
+            continue;
+        }
+
+        if (msg.encoding == AUDIO_SEND_ENCODING_PCM) {
+            if (g_pcm_send_cache == NULL) {
+                free(msg.data);
+                msgsize = sizeof(audio_send_msg_t);
+                continue;
+            }
+            while (g_pcm_send_cache_size + msg.size > g_pcm_send_cache_capacity &&
+                   g_pcm_send_cache_size >= AUDIO_SEND_PCM_FRAME_BYTES) {
+                g_pcm_send_cache_size -= AUDIO_SEND_PCM_FRAME_BYTES;
+                memmove(g_pcm_send_cache, g_pcm_send_cache + AUDIO_SEND_PCM_FRAME_BYTES, g_pcm_send_cache_size);
+            }
+            if (g_pcm_send_cache_size + msg.size > g_pcm_send_cache_capacity) {
+                free(msg.data);
+                msgsize = sizeof(audio_send_msg_t);
+                continue;
+            }
+
+            memcpy(g_pcm_send_cache + g_pcm_send_cache_size, msg.data, msg.size);
+            g_pcm_send_cache_size += msg.size;
+            free(msg.data);
+            msg.data = NULL;
+
+            uint8_t opus_buf[1500];
+            while (g_pcm_send_cache_size >= AUDIO_SEND_PCM_FRAME_BYTES) {
+                int encoded_len = 0;
+                if (opus_pcm_to_frame_encode_packet(
+                        (const short *)g_pcm_send_cache, AUDIO_SEND_PCM_FRAME_SAMPLES,
+                        AUDIO_SEND_PCM_SAMPLE_RATE, AUDIO_SEND_PCM_CHANNELS,
+                        AUDIO_SEND_PCM_BITRATE, AUDIO_SEND_PCM_APPLICATION,
+                        opus_buf, (int)sizeof(opus_buf), &encoded_len) != 0) {
+                    break;
+                }
+                if (ln_tcp_send_audio_frame(sock, opus_buf, (uint32_t)encoded_len) != 0) {
+                    break;
+                }
+                g_pcm_send_cache_size -= AUDIO_SEND_PCM_FRAME_BYTES;
+                memmove(g_pcm_send_cache, g_pcm_send_cache + AUDIO_SEND_PCM_FRAME_BYTES, g_pcm_send_cache_size);
+            }
+            msgsize = sizeof(audio_send_msg_t);
+            continue;
+        }
+
+        (void)ln_tcp_send_audio_frame(sock, msg.data, msg.size);
+        free(msg.data);
+        msg.data = NULL;
+        msgsize = sizeof(audio_send_msg_t);
+    }
+
+    g_audio_send_thread_running = false;
+    LN_MQTT_INFO("TCP audio send thread exited\n");
+}
+
+int ln_tcp_audio_send_init(void)
+{
+    if (g_pcm_send_cache == NULL) {
+        g_pcm_send_cache = (uint8_t *)malloc(AUDIO_SEND_PCM_CACHE_ALLOC_SIZE);
+        if (g_pcm_send_cache == NULL) {
+            return -1;
+        }
+        g_pcm_send_cache_capacity = AUDIO_SEND_PCM_CACHE_ALLOC_SIZE;
+        g_pcm_send_cache_size = 0;
+    }
+
+    create_audio_send_queue();
+    if (g_audio_send_queue == 0xFFFFFFFF) {
+        return -1;
+    }
+
+    if (!g_audio_send_thread_running) {
+        app_task_definition_ext_t send_task = {
+            .attr = {
+                .name = "ln_tcp_audio_send",
+                .stack_size = 0x4000,
+                .priority = osPriorityNormal
+            },
+            .func = ln_tcp_audio_send_thread,
+            .task_handle = &g_audio_send_thread_handle,
+            .create_queue_handle = NULL,
+            .use_mem = USE_EXTEND_MEM,
+            .inst_args = NULL
+        };
+
+        g_audio_send_thread_running = true;
+        errcode_t ret = uapi_create_osThread_custom_with_args(&send_task);
+        if (ret != ERRCODE_SUCC) {
+            g_audio_send_thread_running = false;
+            return -1;
+        }
+    }
+    return 0;
+}
+
+void ln_tcp_audio_send_deinit(void)
+{
+    g_pcm_send_cache_size = 0;
+    g_pcm_send_cache_capacity = 0;
+    if (g_pcm_send_cache != NULL) {
+        free(g_pcm_send_cache);
+        g_pcm_send_cache = NULL;
+    }
+
+    if (g_audio_send_thread_running) {
+        g_audio_send_thread_running = false;
+        audio_send_msg_t wake_msg = {0};
+        osal_msg_queue_write_copy(g_audio_send_queue, (void *)&wake_msg, sizeof(audio_send_msg_t), 0);
+
+        uint32_t timeout = osKernelGetTickCount() + osKernelGetTickFreq() * 2;
+        while (g_audio_send_thread_handle != 0 && osKernelGetTickCount() < timeout) {
+            osDelay(10);
+        }
+        g_audio_send_thread_handle = 0;
+    }
+
+    if (g_audio_send_queue != 0xFFFFFFFF) {
+        audio_send_msg_t msg;
+        uint32_t msgsize = sizeof(audio_send_msg_t);
+        while (osal_msg_queue_read_copy(g_audio_send_queue, (void *)&msg, &msgsize, 0) == OSAL_SUCCESS) {
+            if (msg.data != NULL) {
+                free(msg.data);
+            }
+            msgsize = sizeof(audio_send_msg_t);
+        }
+        osal_msg_queue_delete(g_audio_send_queue);
+        g_audio_send_queue = 0xFFFFFFFF;
+    }
+}
+
+int ln_tcp_audio_send_enqueue(const uint8_t *data, uint32_t size, uint8_t encoding)
+{
+    if (!data || size == 0 || size > AUDIO_SEND_MAX_DATA_SIZE) {
+        return -1;
+    }
+    if (g_audio_send_queue == 0xFFFFFFFF) {
+        return -1;
+    }
+
+    uint8_t *data_copy = (uint8_t *)malloc(size);
+    if (data_copy == NULL) {
+        return -1;
+    }
+    memcpy(data_copy, data, size);
+
+    audio_send_msg_t msg;
+    msg.data = data_copy;
+    msg.size = size;
+    msg.encoding = (encoding != AUDIO_SEND_ENCODING_PCM) ? AUDIO_SEND_ENCODING_OPUS : AUDIO_SEND_ENCODING_PCM;
+
+    // printf("ln_tcp_audio_send_enqueue size %d\n", size);
+
+    int32_t ret = osal_msg_queue_write_copy(g_audio_send_queue, (void *)&msg, sizeof(audio_send_msg_t), 0);
+    if (ret != OSAL_SUCCESS) {
+        free(data_copy);
+        return -1;
+    }
+    return 0;
+}
+
+int ln_tcp_send_test_frames(void)
+{
+    int sock = ln_tcp_get_socket();
+    if (sock < 0) {
+        return -1;
+    }
+    return ln_tcp_send_packet(sock, (uint8_t *)"test", 4);
+}
index 8d33a13..dba93b7 100644 (file)
  * Copyright (c) Zhihui Technologies Co., Ltd. 2025-2025. All rights reserved.
  */
 
-#include "ln_mqtt_api.h"
-#include "ln_api.h"
-#include "custom_utils.h"
-#include "cmsis_os2.h"
-#include "soc_osal.h"
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <errno.h>
-#include "lwip/sockets.h"
-#include "lwip/netdb.h"
-#include "lwip/inet.h"
-#include "mbedtls/cipher.h"
-#include "mbedtls/aes.h"
-#include <time.h>
-#include "opus_frame_to_pcm.h"
-#include "ai_media.h"
-
-/* OGG 封装相关前向声明(不透明句柄,避免依赖 ogg.h) */
-/* OGG 上下文句柄(不透明指针,实际类型在 ai_media_service_mqtt.cpp 中定义) */
-typedef void* MqttOggContextHandle;
-
-/* OGG 封装接口声明(使用不透明句柄) */
-extern void* MqttOggGetContext(void);
-extern int32_t MqttOggEncapsulationInitHandle(MqttOggContextHandle oggCtxHandle, FILE *pfd, uint32_t sampleRate, uint32_t channelCount);
-extern int32_t MqttOggEncapsulationWriteHandle(MqttOggContextHandle oggCtxHandle, const uint8_t *buffer, uint32_t bufferLen, FILE *pfd, bool isEos);
-extern int32_t MqttOggEncapsulationFinishHandle(MqttOggContextHandle oggCtxHandle, FILE *pfd);
-
-/* MQTT 播放接口声明 */
-typedef void (*MqttPlayerCallback)(int32_t event, void* userData);
-extern int32_t MQTT_PlayAudioFile(const char* filePath, MqttPlayerCallback callback, void* userData);
-
-/* UDP接收缓冲区大小 */
-#define UDP_RECV_BUFFER_SIZE 4096
-
-/* UDP数据包头部大小 */
-#define UDP_PACKET_HEADER_SIZE 16  // 1+1+2+4+4+4 = 16 bytes
-
-/* UDP数据包结构体 */
-typedef struct {
-    uint8_t type;               // 类型 (1 byte)
-    uint8_t flags;              // 标志 (1 byte)
-    uint16_t payload_len;       // 负载长度 (2 bytes, 网络字节序)
-    uint32_t ssrc;              // SSRC (4 bytes, 网络字节序)
-    uint32_t timestamp;         // 时间戳 (4 bytes, 网络字节序)
-    uint32_t sequence;          // 序列号 (4 bytes, 网络字节序)
-    uint8_t *payload;           // 加密负载数据指针
-    uint16_t payload_size;      // 负载数据大小
-} ln_udp_packet_t;
-
-/* UDP套接字全局变量 */
-static int g_udp_socket = -1;
-static volatile bool g_udp_thread_running = false;
-static uint32_t g_udp_thread_handle = 0;
-
-/* UDP数据包序列号和SSRC管理 */
-static uint32_t g_udp_ssrc = 0;          // SSRC标识
-static uint32_t g_udp_sequence = 0;      // 序列号(自动递增)
-
-/* 音频数据发送队列和线程 */
-#define AUDIO_SEND_QUEUE_SIZE 32  // 队列大小
-
-/* OGG 封装相关全局变量 */
-#define OGG_PACKETS_PER_FILE 50  // 每50个数据包打包成一个文件
-#define OGG_PACKET_CACHE_SIZE 32  // 数据包缓存大小(用于处理乱序)
-#define OGG_OPUS_ENCODE_HEADER_SIZE 8  // Opus 编码头大小(前8字节)
-#define ENABLE_OPUS_RAW_DEBUG 1  // 启用 Opus 原始数据保存调试(1=启用,0=禁用)
-
-static MqttOggContextHandle g_ogg_ctx_handle = NULL;  // OGG 上下文句柄(不透明指针)
-static FILE *g_ogg_file = NULL;      // 当前 OGG 文件句柄
-static uint32_t g_ogg_packet_count = 0; // 当前文件的数据包计数
-static uint32_t g_ogg_file_index = 0;   // 文件序号
-static bool g_ogg_initialized = false;  // OGG 上下文是否已初始化
-static char g_completed_ogg_filename[128] = {0};  // 已完成的 OGG 文件名(用于播放)
-
-/* Opus 原始数据调试相关全局变量 */
-static FILE *g_opus_debug_file = NULL;      // Opus 调试文件句柄
-static uint32_t g_opus_debug_packet_count = 0; // Opus 调试文件的数据包计数
-static uint32_t g_opus_debug_file_index = 0;   // Opus 调试文件序号
-#define OPUS_DEBUG_PCM_BUF_SAMPLES 5760   // 单帧解码 PCM 缓冲(120ms@48kHz)
-static short g_opus_debug_pcm_buf[OPUS_DEBUG_PCM_BUF_SAMPLES];
-static FILE *g_opus_debug_pcm_file = NULL;    // 解码 PCM 测试输出文件(追加)
-
-/* 数据包缓存结构(用于处理乱序接收) */
-typedef struct {
-    uint32_t sequence;      // 序列号
-    uint8_t *data;          // 数据指针(动态分配)
-    uint32_t data_size;     // 数据大小
-    bool valid;             // 是否有效
-} ogg_packet_cache_t;
-
-static ogg_packet_cache_t g_ogg_packet_cache[OGG_PACKET_CACHE_SIZE] = {0};  // 数据包缓存
-static uint32_t g_ogg_expected_sequence = 0;  // 期望的下一个序列号(0表示未初始化)
-static uint32_t g_ogg_out_of_order_count = 0;  // 连续错序计数器(用于检测丢包)
-#define OGG_MAX_OUT_OF_ORDER_COUNT 5  // 最大连续错序次数,超过后跳过一包
-#define AUDIO_SEND_MAX_DATA_SIZE 0x800  // 最大音频数据大小(与 LN_MQTT_RECORD_MAX_FRAME 一致)
-
-/* 音频编码类型:入队时 data 的格式 */
-#define AUDIO_SEND_ENCODING_OPUS 0  // 已是 Opus,直接发送
-#define AUDIO_SEND_ENCODING_PCM  1  // PCM,发送前用 opus_pcm_to_frame_encode_packet 编码为 Opus
-
-/* PCM 编码默认参数(encoding==PCM 时使用):输入为 16kHz 单声道、16-bit 小端 */
-#define AUDIO_SEND_PCM_SAMPLE_RATE  16000  /* 采样率 16kHz */
-#define AUDIO_SEND_PCM_CHANNELS     1      /* 单声道 */
-#define AUDIO_SEND_PCM_BITRATE     64000
-#define AUDIO_SEND_PCM_APPLICATION 2049  /* Opus: OPUS_APPLICATION_AUDIO,必须为 2048/2049/2051 之一 */
-#define AUDIO_SEND_PCM_FRAME_MS    60  // 每帧 60ms,切割固定长度再编码
-/* 60ms 对应字节数:sample_rate * frame_ms/1000 * channels * 2(bytes/sample) */
-#define AUDIO_SEND_PCM_FRAME_BYTES ((uint32_t)((AUDIO_SEND_PCM_SAMPLE_RATE) * (AUDIO_SEND_PCM_FRAME_MS) / 1000 * (AUDIO_SEND_PCM_CHANNELS) * 2))
-#define AUDIO_SEND_PCM_FRAME_SAMPLES ((int)((AUDIO_SEND_PCM_SAMPLE_RATE) * (AUDIO_SEND_PCM_FRAME_MS) / 1000))
-/* 动态分配容量:帧数可调以增大缓存(约 6s @ 60ms/帧) */
-#define AUDIO_SEND_PCM_CACHE_ALLOC_FRAMES 100
-#define AUDIO_SEND_PCM_CACHE_ALLOC_SIZE   ((AUDIO_SEND_PCM_CACHE_ALLOC_FRAMES) * (AUDIO_SEND_PCM_FRAME_BYTES) + (AUDIO_SEND_MAX_DATA_SIZE))
-
-/* PCM 发送缓存:动态分配,新数据追加,满 60ms 切割编码,余量保留 */
-static uint8_t *g_pcm_send_cache = NULL;
-static uint32_t g_pcm_send_cache_capacity = 0;  /* 已分配容量(字节) */
-static uint32_t g_pcm_send_cache_size = 0;      /* 当前有效数据长度 */
-
-/* 音频数据消息结构体 */
-typedef struct {
-    uint8_t *data;    // 音频数据指针(动态分配)
-    uint32_t size;    // 数据大小(字节)
-    uint8_t  encoding; // 0=Opus,1=PCM(发送前编码为 Opus)
-} audio_send_msg_t;
-
-static unsigned long g_audio_send_queue = 0xFFFFFFFF;  // 消息队列句柄
-static volatile bool g_audio_send_thread_running = false;
-static uint32_t g_audio_send_thread_handle = 0;
-
-/* 前向声明 */
-static void ln_udp_recv_thread(void *arg);
-static int ln_udp_process_received_data(const uint8_t *data, uint16_t data_len);
-int ln_udp_unpack_packet(const uint8_t *data, uint16_t data_len, ln_udp_packet_t *packet);
-void ln_udp_free_packet(ln_udp_packet_t *packet);
-void ln_udp_close_socket(void);
-int ln_udp_decrypt_audio(const char *key_hex, const char *base_nonce_hex,
-                         uint16_t payload_len, uint32_t timestamp, uint32_t sequence,
-                         const uint8_t *encrypted_data, uint16_t encrypted_len, uint8_t *decrypted_data);
-static void ln_udp_audio_send_thread(void *arg);
-static void create_audio_send_queue(void);
-/* OGG 封装相关函数声明 */
-static int ln_udp_ogg_handle_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size);
-static void ln_udp_ogg_cache_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size);
-static int ln_udp_ogg_write_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size);
-static void ln_udp_ogg_flush_cache(void);
-static void ln_udp_ogg_complete_current_file(void);
-static int ln_udp_ogg_create_new_file(void);
-
-#if USE_DUAL_STREAM_INTERFACE
-/* 双流接口相关函数声明 */
-static int ln_udp_dual_stream_handle_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size);
-static int ln_udp_dual_stream_decode_and_send(uint32_t sequence, const uint8_t *data, uint32_t data_size);
-static void ln_udp_dual_stream_flush_cache(void);
-#endif /* USE_DUAL_STREAM_INTERFACE */
-/* Opus 原始数据调试相关函数声明 */
-static int ln_udp_opus_debug_create_new_file(void);
-static void ln_udp_opus_debug_complete_current_file(void);
-static int ln_udp_opus_debug_write_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size);
-/* Opus 数据包解析函数声明 */
-static int ln_udp_parse_opus_packet(const uint8_t *data, uint32_t data_size, 
-                                    const uint8_t **opus_data, uint32_t *opus_data_size);
-
-
-
-/**
- * @brief 处理接收到的UDP数据(解包、解密)
- * @param data 原始接收数据
- * @param data_len 数据长度
- * @return 0 成功,-1 失败
- */
-static int ln_udp_process_received_data(const uint8_t *data, uint16_t data_len)
-{
-    ln_udp_packet_t packet;
-    memset(&packet, 0, sizeof(packet));
-    
-    // 1. 解包数据包
-    if (ln_udp_unpack_packet(data, data_len, &packet) != 0) {
-        LN_MQTT_ERROR("Failed to unpack packet\n");
-        return -1;
-    }
-    
-    LN_MQTT_DEBUG("Packet unpacked: type=%d, flags=%d, ssrc=%u, timestamp=%u, sequence=%u, payload_len=%d\n",
-           packet.type, packet.flags, packet.ssrc, packet.timestamp, packet.sequence, packet.payload_len);
-    
-    // 2. 获取UDP信息(用于解密)
-    ln_mqtt_udp_info_t udp_info;
-    if (ln_mqtt_api_get_udp_info(&udp_info) != 0 || !udp_info.valid) {
-        LN_MQTT_WARN("UDP info not available for decryption\n");
-        ln_udp_free_packet(&packet);
-        return -1;
-    }
-    
-    // 3. 解密负载数据
-    if (packet.payload_len > 0 && packet.payload) {
-        // 分配解密数据缓冲区
-        uint8_t *decrypted_data = (uint8_t *)malloc(packet.payload_len);
-        if (!decrypted_data) {
-            LN_MQTT_ERROR("Failed to allocate memory for decrypted data\n");
-            ln_udp_free_packet(&packet);
-            return -1;
-        }
-        
-        // 执行解密(nonce在函数内部构造)
-        if (ln_udp_decrypt_audio(udp_info.key, udp_info.nonce, packet.payload_len, 
-                                 packet.timestamp, packet.sequence,
-                                 packet.payload, packet.payload_len, decrypted_data) != 0) {
-            LN_MQTT_ERROR("Failed to decrypt payload data\n");
-            free(decrypted_data);
-            ln_udp_free_packet(&packet);
-            return -1;
-        }
-        LN_MQTT_DEBUG("decrypted_data: ");
-
-        LN_MQTT_DEBUG("\n");
-        
-        LN_MQTT_DEBUG("Payload decrypted successfully, size=%d bytes\n", packet.payload_len);
-        
-        LN_MQTT_DEBUG("packet.sequence: %u\n", packet.sequence);
-
-#if USE_DUAL_STREAM_INTERFACE
-        // 使用双流接口时,不进行 OGG 封装,但需要保持包顺序处理
-        // 注意:decrypted_data 的所有权会转移给处理函数,不需要在这里释放
-        if (ln_udp_dual_stream_handle_packet(packet.sequence, decrypted_data, packet.payload_len) != 0) {
-            LN_MQTT_ERROR("Failed to handle dual stream packet (sequence=%u)\n", packet.sequence);
-            free(decrypted_data);  // 处理失败时才释放
-        }
-#else
-        // OGG 封装处理:使用独立函数处理,支持乱序数据包缓存和排序
-        // 注意:decrypted_data 的所有权会转移给处理函数,不需要在这里释放
-        if (ln_udp_ogg_handle_packet(packet.sequence, decrypted_data, packet.payload_len) != 0) {
-            LN_MQTT_ERROR("Failed to handle OGG packet (sequence=%u)\n", packet.sequence);
-            free(decrypted_data);  // 处理失败时才释放
-        }
-#endif /* USE_DUAL_STREAM_INTERFACE */
-    } else {
-        LN_MQTT_DEBUG("Packet has no payload data\n");
-    }
-    
-    // 4. 释放数据包
-    ln_udp_free_packet(&packet);
-    
-    return 0;
-}
-
-/**
- * @brief 解包UDP数据包
- * @param data 原始数据缓冲区
- * @param data_len 数据长度
- * @param packet 输出的数据包结构体指针(payload字段需要外部释放)
- * @return 0 成功,-1 失败
- */
-int ln_udp_unpack_packet(const uint8_t *data, uint16_t data_len, ln_udp_packet_t *packet)
-{
-    if (!data || !packet || data_len < UDP_PACKET_HEADER_SIZE) {
-        LN_MQTT_WARN("Invalid parameters or data too short for unpack\n");
-        return -1;
-    }
-    
-    memset(packet, 0, sizeof(ln_udp_packet_t));
-    
-    // 解析头部字段(网络字节序)
-    const uint8_t *ptr = data;
-    packet->type = ptr[0];
-    packet->flags = ptr[1];
-    packet->payload_len = lwip_ntohs(*(uint16_t *)(ptr + 2));
-    packet->ssrc = lwip_ntohl(*(uint32_t *)(ptr + 4));
-    packet->timestamp = lwip_ntohl(*(uint32_t *)(ptr + 8));
-    packet->sequence = lwip_ntohl(*(uint32_t *)(ptr + 12));
-    
-    // 验证负载长度
-    uint16_t expected_len = UDP_PACKET_HEADER_SIZE + packet->payload_len;
-    if (data_len < expected_len) {
-        LN_MQTT_WARN("Data length mismatch: expected %d, got %d\n", expected_len, data_len);
-        return -1;
-    }
-    
-    // 分配并复制负载数据
-    if (packet->payload_len > 0) {
-        packet->payload = (uint8_t *)malloc(packet->payload_len);
-        if (!packet->payload) {
-            LN_MQTT_ERROR("Failed to allocate memory for payload\n");
-            return -1;
-        }
-        memcpy(packet->payload, ptr + UDP_PACKET_HEADER_SIZE, packet->payload_len);
-        packet->payload_size = packet->payload_len;
-    } else {
-        packet->payload = NULL;
-        packet->payload_size = 0;
-    }
-    
-    return 0;
-}
-
-/**
- * @brief 封包UDP数据包
- * @param packet 数据包结构体指针
- * @param buffer 输出缓冲区指针(由调用者分配)
- * @param buffer_size 缓冲区大小
- * @param out_len 输出的实际数据长度
- * @return 0 成功,-1 失败
- */
-int ln_udp_pack_packet(const ln_udp_packet_t *packet, uint8_t *buffer, uint16_t buffer_size, uint16_t *out_len)
-{
-    if (!packet || !buffer || !out_len) {
-        LN_MQTT_WARN("Invalid parameters for pack\n");
-        return -1;
-    }
-    
-    // 计算所需缓冲区大小
-    uint16_t total_len = UDP_PACKET_HEADER_SIZE + packet->payload_len;
-    if (buffer_size < total_len) {
-        LN_MQTT_WARN("Buffer too small: need %d, have %d\n", total_len, buffer_size);
-        return -1;
-    }
-    
-    // 打包头部字段(转换为网络字节序)
-    uint8_t *ptr = buffer;
-    ptr[0] = packet->type;
-    ptr[1] = packet->flags;
-    *(uint16_t *)(ptr + 2) = lwip_htons(packet->payload_len);
-    *(uint32_t *)(ptr + 4) = lwip_htonl(packet->ssrc);
-    *(uint32_t *)(ptr + 8) = lwip_htonl(packet->timestamp);
-    *(uint32_t *)(ptr + 12) = lwip_htonl(packet->sequence);
-    
-    // 复制负载数据
-    if (packet->payload_len > 0 && packet->payload) {
-        memcpy(ptr + UDP_PACKET_HEADER_SIZE, packet->payload, packet->payload_len);
-    }
-    
-    *out_len = total_len;
-    return 0;
-}
-
-/**
- * @brief 释放数据包结构体的负载数据
- * @param packet 数据包结构体指针
- */
-void ln_udp_free_packet(ln_udp_packet_t *packet)
-{
-    if (packet && packet->payload) {
-        free(packet->payload);
-        packet->payload = NULL;
-        packet->payload_size = 0;
-    }
-}
-
-// ==================== OGG 封装处理函数(支持乱序数据包缓存和排序)====================
-
-/**
- * @brief 清空数据包缓存
- */
-static void ln_udp_ogg_clear_cache(void)
-{
-    for (int i = 0; i < OGG_PACKET_CACHE_SIZE; i++) {
-        if (g_ogg_packet_cache[i].valid && g_ogg_packet_cache[i].data != NULL) {
-            free(g_ogg_packet_cache[i].data);
-            g_ogg_packet_cache[i].data = NULL;
-        }
-        g_ogg_packet_cache[i].valid = false;
-        g_ogg_packet_cache[i].sequence = 0;
-        g_ogg_packet_cache[i].data_size = 0;
-    }
-}
-
-/**
- * @brief 将数据包缓存(用于处理乱序接收)
- * @param sequence 序列号
- * @param data 数据指针(会被复制,调用者可以释放)
- * @param data_size 数据大小
- */
-static void ln_udp_ogg_cache_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size)
-{
-    // 查找空闲的缓存槽
-    int empty_slot = -1;
-    for (int i = 0; i < OGG_PACKET_CACHE_SIZE; i++) {
-        if (!g_ogg_packet_cache[i].valid) {
-            empty_slot = i;
-            break;
-        }
-    }
-    
-    if (empty_slot < 0) {
-        LN_MQTT_WARN("OGG cache full, dropping packet sequence=%u\n", sequence);
-        return;
-    }
-    
-    // 分配内存并复制数据
-    uint8_t *cached_data = (uint8_t *)malloc(data_size);
-    if (cached_data == NULL) {
-        LN_MQTT_ERROR("Failed to allocate memory for cached packet sequence=%u\n", sequence);
-        return;
-    }
-    
-    memcpy(cached_data, data, data_size);
-    
-    // 保存到缓存
-    g_ogg_packet_cache[empty_slot].sequence = sequence;
-    g_ogg_packet_cache[empty_slot].data = cached_data;
-    g_ogg_packet_cache[empty_slot].data_size = data_size;
-    g_ogg_packet_cache[empty_slot].valid = true;
-    
-    LN_MQTT_DEBUG("Cached packet sequence=%u (slot=%d)\n", sequence, empty_slot);
-}
-
-/**
- * @brief 创建新的 Opus 调试文件(直接保存 Opus 原始数据)
- * @return 0 成功,-1 失败
- */
-static int ln_udp_opus_debug_create_new_file(void)
-{
-#if ENABLE_OPUS_RAW_DEBUG
-    // 创建新文件(使用时间戳和序号命名)
-    time_t now = time(NULL);
-    struct tm *tm_info = localtime(&now);
-    char filename[128];
-    snprintf(filename, sizeof(filename), "/user/udp_opus_raw_%04d%02d%02d_%02d%02d%02d_%03u.opus",
-             tm_info->tm_year + 1900, tm_info->tm_mon + 1, tm_info->tm_mday,
-             tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, g_opus_debug_file_index);
-    
-    g_opus_debug_file = fopen(filename, "wb");
-    if (g_opus_debug_file == NULL) {
-        LN_MQTT_ERROR("Failed to open Opus debug file: %s\n", filename);
-        return -1;
-    }
-    
-    g_opus_debug_packet_count = 0;
-    g_opus_debug_file_index++;
-    LN_MQTT_DEBUG("Created new Opus debug file: %s (index: %u)\n", filename, g_opus_debug_file_index - 1);
-    
-    return 0;
-#else
-    return -1;
-#endif
-}
-
-/**
- * @brief 完成当前 Opus 调试文件
- */
-static void ln_udp_opus_debug_complete_current_file(void)
-{
-#if ENABLE_OPUS_RAW_DEBUG
-    if (g_opus_debug_file == NULL) {
-        return;
-    }
-    
-    fclose(g_opus_debug_file);
-    g_opus_debug_file = NULL;
-    g_opus_debug_packet_count = 0;
-    LN_MQTT_DEBUG("Completed Opus debug file #%u\n", g_opus_debug_file_index - 1);
-#endif
-}
-
-/**
- * @brief 解析 Opus 数据包,找到实际 Opus 编码数据的起始位置和大小
- * @param data 完整的 Opus 数据包(包含 TOC 字节和可能的帧长度表)
- * @param data_size 数据包总大小
- * @param opus_data 输出的实际 Opus 数据起始指针
- * @param opus_data_size 输出的实际 Opus 数据大小
- * @return 0 成功,-1 失败
- * @note 根据 RFC 6716 Opus 编码标准解析
- */
-static int ln_udp_parse_opus_packet(const uint8_t *data, uint32_t data_size, 
-                                    const uint8_t **opus_data, uint32_t *opus_data_size)
-{
-    if (!data || data_size == 0 || !opus_data || !opus_data_size) {
-        LN_MQTT_WARN("Invalid parameters for Opus packet parsing\n");
-        return -1;
-    }
-    
-    // 至少需要 TOC 字节
-    if (data_size < 1) {
-        LN_MQTT_WARN("Opus packet too small (size=%u)\n", data_size);
-        return -1;
-    }
-    
-    const uint8_t *ptr = data;
-    uint32_t remaining = data_size;
-    
-    // 1. 读取 TOC 字节
-    uint8_t toc = ptr[0];
-    ptr++;
-    remaining--;
-    
-    // 2. 解析帧计数编码(bit 3-7)
-    uint8_t frame_count_code = (toc >> 3) & 0x1F;  // bit 3-7
-    uint8_t frame_count = 0;
-    uint32_t header_size = 1;  // TOC 字节
-    
-    // 3. 判断是单帧还是多帧
-    if (frame_count_code < 16) {
-        // 单帧:帧计数 = 编码值(0表示1帧,1-15表示对应帧数)
-        if (frame_count_code == 0) {
-            frame_count = 1;  // 编码值0表示单帧
-        } else {
-            frame_count = frame_count_code;
-        }
-    } else {
-        // 多帧:需要读取帧计数字节
-        if (remaining < 1) {
-            LN_MQTT_WARN("Opus packet too small for frame count byte\n");
-            return -1;
-        }
-        frame_count = frame_count_code - 16 + 1 + ptr[0];
-        ptr++;
-        remaining--;
-        header_size++;
-    }
-    
-    // 4. 如果是多帧,需要读取帧长度表(但不跳过数据)
-    if (frame_count > 1) {
-        // 多帧数据包需要帧长度表
-        // 每个帧长度使用可变长度编码(1-2字节)
-        // 如果长度 < 252,使用1字节;否则使用2字节(0xFC + 实际长度)
-        // 注意:我们只读取长度值,不跳过帧数据,因为所有帧数据是连续存储的
-        for (uint8_t i = 0; i < frame_count - 1; i++) {
-            if (remaining < 1) {
-                LN_MQTT_WARN("Opus packet too small for frame length table\n");
-                return -1;
-            }
-            
-            if (ptr[0] < 252) {
-                // 1字节编码
-                ptr++;
-                remaining--;
-                header_size++;
-            } else {
-                // 2字节编码:0xFC + 实际长度(大端序)
-                if (remaining < 2) {
-                    LN_MQTT_WARN("Opus packet too small for 2-byte frame length\n");
-                    return -1;
-                }
-                ptr += 2;
-                remaining -= 2;
-                header_size += 2;
-            }
-        }
-    }
-    
-    // 5. 现在 header_size 包含了所有头部信息(TOC + 帧计数 + 帧长度表)
-    // 从 header_size 之后的所有数据都是 Opus 编码数据(包含所有帧的数据)
-    if (header_size >= data_size) {
-        LN_MQTT_WARN("Opus header size (%u) >= total size (%u)\n", header_size, data_size);
-        return -1;
-    }
-    
-    *opus_data = data + header_size;
-    *opus_data_size = data_size - header_size;
-    
-    LN_MQTT_DEBUG("Opus parsed: frame_count=%d, header_size=%u, opus_data_size=%u\n", 
-           frame_count, header_size, *opus_data_size);
-    
-    return 0;
-}
-
-/**
- * @brief 写入 Opus 原始数据到调试文件(不进行 OGG 封装)
- * @param sequence 序列号
- * @param data 数据指针(包含 Opus TOC 字节和可能的帧长度表)
- * @param data_size 数据大小
- * @return 0 成功,-1 失败
- */
-static int ln_udp_opus_debug_write_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size)
-{
-#if ENABLE_OPUS_RAW_DEBUG
-    // 检查是否需要创建新文件
-    if (g_opus_debug_file == NULL || g_opus_debug_packet_count >= OGG_PACKETS_PER_FILE) {
-        // 完成当前文件
-        if (g_opus_debug_file != NULL) {
-            ln_udp_opus_debug_complete_current_file();
-        }
-        
-        // 创建新文件
-        if (ln_udp_opus_debug_create_new_file() != 0) {
-            return -1;
-        }
-    }
-    LN_MQTT_DEBUG("opus debug data: ");
-    for (int i = 0; i < data_size; i++) {
-        LN_MQTT_DEBUG("%02X ", data[i]);
-    }
-    LN_MQTT_DEBUG("\n");
-    
-    // 解析 Opus 数据包(用于验证和日志输出,但不影响保存)
-    const uint8_t *opus_data = NULL;
-    uint32_t opus_data_size = 0;
-    int parse_ret = ln_udp_parse_opus_packet(data, data_size, &opus_data, &opus_data_size);
-    if (parse_ret == 0) {
-        LN_MQTT_DEBUG("Opus debug: Parsed packet sequence=%u, total_size=%u, opus_data_size=%u\n", 
-               sequence, data_size, opus_data_size);
-    } else {
-        LN_MQTT_WARN("Opus debug: Parse warning (sequence=%u, size=%u), but saving raw data anyway\n", 
-               sequence, data_size);
-    }
-
-    /* 调用 opus 解码接口测试:完整包 data/data_size 送入解码 */
-    {
-        int pcm_samples = 0;
-        int dec_ret = opus_frame_to_pcm_decode_packet(
-            data, (int)data_size,
-            16000, 1,
-            g_opus_debug_pcm_buf, OPUS_DEBUG_PCM_BUF_SAMPLES,
-            &pcm_samples);
-        if (dec_ret == 0) {
-            LN_MQTT_DEBUG("Opus decode test OK sequence=%u pcm_samples=%d\n", sequence, pcm_samples);
-            if (g_opus_debug_pcm_file == NULL) {
-                g_opus_debug_pcm_file = fopen("user/udp_opus_debug.pcm", "ab");
-                if (g_opus_debug_pcm_file != NULL) {
-                    LN_MQTT_DEBUG("Opus decode PCM output: udp_opus_debug.pcm (append)\n");
-                }
-            }
-            if (g_opus_debug_pcm_file != NULL && pcm_samples > 0) {
-                size_t n = fwrite(g_opus_debug_pcm_buf, sizeof(short), (size_t)pcm_samples, g_opus_debug_pcm_file);
-                if (n != (size_t)pcm_samples) {
-                    LN_MQTT_DEBUG("Opus decode PCM write short: %zu/%d\n", n, pcm_samples);
-                }
-                fflush(g_opus_debug_pcm_file);
-            }
-            fclose(g_opus_debug_pcm_file);
-            g_opus_debug_pcm_file = NULL;
-        } else {
-            LN_MQTT_ERROR("Opus decode test FAIL sequence=%u\n", sequence);
-        }
-    }
-    
-    // 保存完整的原始数据(包含 TOC 字节和帧长度表)
-    size_t written = fwrite(data, 1, data_size, g_opus_debug_file);
-    if (written != data_size) {
-        LN_MQTT_ERROR("Opus debug: Failed to write data (sequence=%u, written=%zu, expected=%u)\n", 
-               sequence, written, data_size);
-        return -1;
-    }
-    
-    fflush(g_opus_debug_file);  // 立即刷新到文件
-    
-    g_opus_debug_packet_count++;
-    LN_MQTT_DEBUG("Opus debug: Written packet sequence=%u (%u/%u, raw_size=%u bytes)\n", 
-           sequence, g_opus_debug_packet_count, OGG_PACKETS_PER_FILE, data_size);
-    
-    return 0;
-#else
-    return 0;  // 未启用时直接返回成功
-#endif
-}
-
-/**
- * @brief 写入数据包到 OGG 文件
- * @param sequence 序列号
- * @param data 数据指针
- * @param data_size 数据大小
- * @return 0 成功,-1 失败
- */
-static int ln_udp_ogg_write_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size)
-{
-    // 调试模式:同时保存 Opus 原始数据
-#if ENABLE_OPUS_RAW_DEBUG
-    ln_udp_opus_debug_write_packet(sequence, data, data_size);
-#endif
-    
-    // 检查是否需要创建新文件
-    if (!g_ogg_initialized || g_ogg_packet_count >= OGG_PACKETS_PER_FILE) {
-        // 完成当前文件
-        if (g_ogg_initialized) {
-            ln_udp_ogg_complete_current_file();
-        }
-        
-        // 创建新文件
-        if (ln_udp_ogg_create_new_file() != 0) {
-            return -1;
-        }
-    }
-    
-    // 写入数据包
-    if (g_ogg_ctx_handle == NULL) {
-        LN_MQTT_ERROR("OGG context handle is null\n");
-        return -1;
-    }
-    if (MqttOggEncapsulationWriteHandle(g_ogg_ctx_handle, data, data_size, g_ogg_file, false) != 0) {
-        LN_MQTT_ERROR("Failed to write OGG data (sequence=%u)\n", sequence);
-        return -1;
-    }
-    
-    g_ogg_packet_count++;
-    LN_MQTT_DEBUG("Written packet sequence=%u (%u/%u to OGG file)\n", 
-           sequence, g_ogg_packet_count, OGG_PACKETS_PER_FILE);
-    
-    return 0;
-}
-
-/**
- * @brief 刷新缓存:尝试按顺序写入缓存中的数据包
- */
-static void ln_udp_ogg_flush_cache(void)
-{
-    bool found = true;
-    while (found) {
-        found = false;
-        // 查找期望的下一个序列号
-        for (int i = 0; i < OGG_PACKET_CACHE_SIZE; i++) {
-            if (g_ogg_packet_cache[i].valid && 
-                g_ogg_packet_cache[i].sequence == g_ogg_expected_sequence) {
-                // 找到期望的包,写入
-                if (ln_udp_ogg_write_packet(g_ogg_packet_cache[i].sequence,
-                                            g_ogg_packet_cache[i].data,
-                                            g_ogg_packet_cache[i].data_size) == 0) {
-                    // 更新期望的序列号
-                    g_ogg_expected_sequence++;
-                    // 释放缓存
-                    free(g_ogg_packet_cache[i].data);
-                    g_ogg_packet_cache[i].data = NULL;
-                    g_ogg_packet_cache[i].valid = false;
-                    g_ogg_packet_cache[i].sequence = 0;
-                    g_ogg_packet_cache[i].data_size = 0;
-                    found = true;
-                    LN_MQTT_DEBUG("Flushed cached packet sequence=%u\n", 
-                           g_ogg_expected_sequence - 1);
-                    break;  // 重新开始查找
-                } else {
-                    // 写入失败,保留在缓存中
-                    break;
-                }
-            }
-        }
-    }
-}
-
-/**
- * @brief 完成当前 OGG 文件并播放
- */
-static void ln_udp_ogg_complete_current_file(void)
-{
-    if (!g_ogg_initialized || g_ogg_file == NULL) {
-        return;
-    }
-    
-    // 写入结束标志并完成封装
-    if (g_ogg_ctx_handle != NULL) {
-        uint8_t empty_data[8] = {0};  // 至少需要8字节的编码头
-        MqttOggEncapsulationWriteHandle(g_ogg_ctx_handle, empty_data, 8, g_ogg_file, true);
-        MqttOggEncapsulationFinishHandle(g_ogg_ctx_handle, g_ogg_file);
-    }
-    fclose(g_ogg_file);
-    g_ogg_file = NULL;
-    g_ogg_initialized = false;
-    
-    LN_MQTT_INFO("Completed OGG file #%u with %u packets: %s\n", 
-           g_ogg_file_index - 1, g_ogg_packet_count, g_completed_ogg_filename);
-    
-    // 使用 MQTT 播放接口播放完成的文件
-    if (g_completed_ogg_filename[0] != '\0') {
-        int32_t play_ret = MQTT_PlayAudioFile(g_completed_ogg_filename, NULL, NULL);
-        if (play_ret == 0) {
-            LN_MQTT_INFO("Started playing OGG file: %s\n", g_completed_ogg_filename);
-        } else {
-            LN_MQTT_ERROR("Failed to play OGG file: %s (ret=%d)\n", 
-                   g_completed_ogg_filename, play_ret);
-        }
-        g_completed_ogg_filename[0] = '\0';
-    }
-}
-
-/**
- * @brief 创建新的 OGG 文件
- * @return 0 成功,-1 失败
- */
-static int ln_udp_ogg_create_new_file(void)
-{
-    // 创建新文件(使用时间戳和序号命名)
-    time_t now = time(NULL);
-    struct tm *tm_info = localtime(&now);
-    char filename[128];
-    snprintf(filename, sizeof(filename), "/user/udp_audio_%04d%02d%02d_%02d%02d%02d_%03u.ogg",
-             tm_info->tm_year + 1900, tm_info->tm_mon + 1, tm_info->tm_mday,
-             tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, g_ogg_file_index);
-    
-    // 保存文件名(用于后续播放)
-    strncpy(g_completed_ogg_filename, filename, sizeof(g_completed_ogg_filename) - 1);
-    g_completed_ogg_filename[sizeof(g_completed_ogg_filename) - 1] = '\0';
-    
-    g_ogg_file = fopen(filename, "wb");
-    if (g_ogg_file == NULL) {
-        LN_MQTT_ERROR("Failed to open OGG file: %s\n", filename);
-        g_completed_ogg_filename[0] = '\0';
-        return -1;
-    }
-    
-    // 获取 OGG 上下文句柄(如果尚未获取)
-    if (g_ogg_ctx_handle == NULL) {
-        g_ogg_ctx_handle = MqttOggGetContext();
-        if (g_ogg_ctx_handle == NULL) {
-            LN_MQTT_ERROR("Failed to get OGG context handle\n");
-            fclose(g_ogg_file);
-            g_ogg_file = NULL;
-            g_completed_ogg_filename[0] = '\0';
-            return -1;
-        }
-    }
-    
-    // 初始化 OGG 上下文
-    if (MqttOggEncapsulationInitHandle(g_ogg_ctx_handle, g_ogg_file, 16000, 1) != 0) {
-        LN_MQTT_ERROR("Failed to initialize OGG context\n");
-        fclose(g_ogg_file);
-        g_ogg_file = NULL;
-        g_completed_ogg_filename[0] = '\0';
-        return -1;
-    }
-    
-    g_ogg_initialized = true;
-    g_ogg_packet_count = 0;
-    g_ogg_file_index++;
-    LN_MQTT_DEBUG("Created new OGG file: %s (index: %u)\n", filename, g_ogg_file_index - 1);
-    
-    return 0;
-}
-
-/**
- * @brief 处理 OGG 数据包(支持乱序接收和缓存)
- * @param sequence 数据包序列号
- * @param data 解密后的数据(所有权转移,函数内部负责释放)
- * @param data_size 数据大小
- * @return 0 成功,-1 失败
- * @note 此函数会接管 data 的所有权,调用者不应再释放 data
- */
-static int ln_udp_ogg_handle_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size)
-{
-    // 初始化期望序列号(首次接收)
-    if (g_ogg_expected_sequence == 0) {
-        g_ogg_expected_sequence = sequence;
-        g_ogg_out_of_order_count = 0;  // 重置错序计数器
-        LN_MQTT_DEBUG("Initialized expected sequence to %u\n", sequence);
-    }
-    
-    // 检查是否是期望的下一个包
-    if (sequence == g_ogg_expected_sequence) {
-        // 是期望的包,直接写入
-        if (ln_udp_ogg_write_packet(sequence, data, data_size) == 0) {
-            g_ogg_expected_sequence++;
-            g_ogg_out_of_order_count = 0;  // 重置错序计数器(收到期望的包)
-            free((void *)data);  // 释放数据(所有权已转移)
-            
-            // 尝试刷新缓存中的后续包
-            ln_udp_ogg_flush_cache();
-            return 0;
-        } else {
-            // 写入失败,缓存这个包
-            ln_udp_ogg_cache_packet(sequence, data, data_size);
-            free((void *)data);
-            return -1;
-        }
-    } else if (sequence > g_ogg_expected_sequence) {
-        // 是未来的包,缓存起来
-        g_ogg_out_of_order_count++;  // 增加错序计数器
-        LN_MQTT_DEBUG("Received future packet sequence=%u (expected=%u), caching (out_of_order_count=%u)\n", 
-               sequence, g_ogg_expected_sequence, g_ogg_out_of_order_count);
-        
-        // 如果连续错序超过阈值,跳过期望的序列号(避免丢包时一直无法正常输出)
-        if (g_ogg_out_of_order_count > OGG_MAX_OUT_OF_ORDER_COUNT) {
-            uint32_t old_expected = g_ogg_expected_sequence;
-            LN_MQTT_DEBUG("Out of order count exceeded %d, skipping sequence %u\n", 
-                   OGG_MAX_OUT_OF_ORDER_COUNT, g_ogg_expected_sequence);
-            
-            // 1. 先将期望序号往后加一(跳过丢失的包)
-            g_ogg_expected_sequence++;
-            g_ogg_out_of_order_count = 0;  // 重置计数器
-            
-            // 2. 处理缓存中的包(尝试写入缓存中所有能写入的连续包)
-            ln_udp_ogg_flush_cache();
-            
-            // 3. 再尝试写入当前包
-            if (sequence == g_ogg_expected_sequence) {
-                // 当前包正好是期望的包,直接写入
-                if (ln_udp_ogg_write_packet(sequence, data, data_size) == 0) {
-                    g_ogg_expected_sequence++;
-                    free((void *)data);
-                    // 继续刷新缓存中的后续包
-                    ln_udp_ogg_flush_cache();
-                    LN_MQTT_DEBUG("Successfully wrote skipped packet sequence=%u (old_expected=%u)\n", 
-                           sequence, old_expected);
-                    return 0;
-                } else {
-                    // 写入失败,缓存这个包
-                    ln_udp_ogg_cache_packet(sequence, data, data_size);
-                    free((void *)data);
-                    return -1;
-                }
-            } else if (sequence > g_ogg_expected_sequence) {
-                // 当前包仍然是未来的包,缓存起来
-                LN_MQTT_DEBUG("Current packet sequence=%u still ahead of expected=%u after skip, caching\n", 
-                       sequence, g_ogg_expected_sequence);
-                ln_udp_ogg_cache_packet(sequence, data, data_size);
-                free((void *)data);
-                return 0;
-            } else {
-                // 当前包已经是过去的包(在刷新缓存时已处理),丢弃
-                LN_MQTT_DEBUG("Current packet sequence=%u is now old (expected=%u), dropping\n", 
-                       sequence, g_ogg_expected_sequence);
-                free((void *)data);
-                return 0;
-            }
-        } else {
-            // 正常缓存未来包
-            ln_udp_ogg_cache_packet(sequence, data, data_size);
-            free((void *)data);
-            return 0;
-        }
-    } else {
-        // 是过去的包(重复或乱序),丢弃
-        LN_MQTT_DEBUG("Received old/duplicate packet sequence=%u (expected=%u), dropping\n", 
-               sequence, g_ogg_expected_sequence);
-        free((void *)data);
-        return 0;
-    }
-}
-
-#if USE_DUAL_STREAM_INTERFACE
-// ==================== 双流接口处理函数(支持乱序数据包缓存和排序)====================
-
-/**
- * @brief 解码 Opus 数据包并送入 AI_SendAudioDataToQueue
- * @param sequence 序列号
- * @param data 数据指针
- * @param data_size 数据大小
- * @return 0 成功,-1 失败
- */
-static int ln_udp_dual_stream_decode_and_send(uint32_t sequence, const uint8_t *data, uint32_t data_size)
-{
-    int pcm_samples = 0;
-    int dec_ret = opus_frame_to_pcm_decode_packet(
-        data, (int)data_size,
-        16000, 1,
-        g_opus_debug_pcm_buf, OPUS_DEBUG_PCM_BUF_SAMPLES,
-        &pcm_samples);
-    
-    if (dec_ret == 0 && pcm_samples > 0) {
-        // 解码成功,将 PCM 数据送入 AI_SendAudioDataToQueue
-        uint32_t pcm_data_size = (uint32_t)(pcm_samples * sizeof(short));
-        int32_t send_ret = AI_SendAudioDataToQueue((const uint8_t *)g_opus_debug_pcm_buf, pcm_data_size);
-        if (send_ret == 0) {
-            LN_MQTT_DEBUG("Dual stream: Decoded and sent to queue: sequence=%u, pcm_samples=%d\n", 
-                   sequence, pcm_samples);
-            return 0;
-        } else {
-            LN_MQTT_ERROR("Dual stream: Failed to send decoded PCM to queue: sequence=%u, ret=%d\n", 
-                   sequence, (int)send_ret);
-            return -1;
-        }
-    } else {
-        LN_MQTT_ERROR("Dual stream: Opus decode failed: sequence=%u, ret=%d\n", 
-               sequence, dec_ret);
-        return -1;
-    }
-}
-
-/**
- * @brief 刷新双流数据包缓存(按顺序处理缓存中的包)
- */
-static void ln_udp_dual_stream_flush_cache(void)
-{
-    bool found = true;
-    while (found) {
-        found = false;
-        // 查找期望序列号的包
-        for (int i = 0; i < OGG_PACKET_CACHE_SIZE; i++) {
-            if (g_ogg_packet_cache[i].valid && 
-                g_ogg_packet_cache[i].sequence == g_ogg_expected_sequence) {
-                // 找到期望的包,解码并发送
-                if (ln_udp_dual_stream_decode_and_send(g_ogg_packet_cache[i].sequence,
-                                                        g_ogg_packet_cache[i].data,
-                                                        g_ogg_packet_cache[i].data_size) == 0) {
-                    g_ogg_expected_sequence++;
-                    // 释放缓存数据
-                    free(g_ogg_packet_cache[i].data);
-                    g_ogg_packet_cache[i].data = NULL;
-                    g_ogg_packet_cache[i].valid = false;
-                    g_ogg_packet_cache[i].sequence = 0;
-                    g_ogg_packet_cache[i].data_size = 0;
-                    found = true;  // 继续查找下一个期望的包
-                    LN_MQTT_DEBUG("Dual stream: Flushed cached packet sequence=%u\n", 
-                           g_ogg_expected_sequence - 1);
-                    break;  // 重新开始查找
-                } else {
-                    // 解码失败,停止刷新
-                    found = false;
-                    break;
-                }
-            }
-        }
-    }
-}
-
-/**
- * @brief 处理双流数据包(支持乱序接收和缓存)
- * @param sequence 数据包序列号
- * @param data 解密后的数据(所有权转移,函数内部负责释放)
- * @param data_size 数据大小
- * @return 0 成功,-1 失败
- * @note 此函数会接管 data 的所有权,调用者不应再释放 data
- */
-static int ln_udp_dual_stream_handle_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size)
-{
-    // 初始化期望序列号(首次接收)
-    if (g_ogg_expected_sequence == 0) {
-        g_ogg_expected_sequence = sequence;
-        g_ogg_out_of_order_count = 0;  // 重置错序计数器
-        LN_MQTT_DEBUG("Dual stream: Initialized expected sequence to %u\n", sequence);
-    }
-    
-    // 检查是否是期望的下一个包
-    if (sequence == g_ogg_expected_sequence) {
-        // 是期望的包,直接解码并发送
-        if (ln_udp_dual_stream_decode_and_send(sequence, data, data_size) == 0) {
-            g_ogg_expected_sequence++;
-            g_ogg_out_of_order_count = 0;  // 重置错序计数器(收到期望的包)
-            free((void *)data);  // 释放数据(所有权已转移)
-            
-            // 尝试刷新缓存中的后续包
-            ln_udp_dual_stream_flush_cache();
-            return 0;
-        } else {
-            // 解码失败,缓存这个包
-            ln_udp_ogg_cache_packet(sequence, data, data_size);
-            free((void *)data);
-            return -1;
-        }
-    } else if (sequence > g_ogg_expected_sequence) {
-        // 是未来的包,缓存起来
-        g_ogg_out_of_order_count++;  // 增加错序计数器
-        LN_MQTT_DEBUG("Dual stream: Received future packet sequence=%u (expected=%u), caching (out_of_order_count=%u)\n", 
-               sequence, g_ogg_expected_sequence, g_ogg_out_of_order_count);
-        
-        // 如果连续错序超过阈值,跳过期望的序列号(避免丢包时一直无法正常输出)
-        if (g_ogg_out_of_order_count > OGG_MAX_OUT_OF_ORDER_COUNT) {
-            uint32_t old_expected = g_ogg_expected_sequence;
-            LN_MQTT_DEBUG("Dual stream: Out of order count exceeded %d, skipping sequence %u\n", 
-                   OGG_MAX_OUT_OF_ORDER_COUNT, g_ogg_expected_sequence);
-            
-            // 1. 先将期望序号往后加一(跳过丢失的包)
-            g_ogg_expected_sequence++;
-            g_ogg_out_of_order_count = 0;  // 重置计数器
-            
-            // 2. 处理缓存中的包(尝试解码并发送缓存中所有能处理的连续包)
-            ln_udp_dual_stream_flush_cache();
-            
-            // 3. 再尝试处理当前包
-            if (sequence == g_ogg_expected_sequence) {
-                // 当前包正好是期望的包,直接解码并发送
-                if (ln_udp_dual_stream_decode_and_send(sequence, data, data_size) == 0) {
-                    g_ogg_expected_sequence++;
-                    free((void *)data);
-                    // 继续刷新缓存中的后续包
-                    ln_udp_dual_stream_flush_cache();
-                    LN_MQTT_DEBUG("Dual stream: Successfully decoded and sent skipped packet sequence=%u (old_expected=%u)\n", 
-                           sequence, old_expected);
-                    return 0;
-                } else {
-                    // 解码失败,缓存这个包
-                    ln_udp_ogg_cache_packet(sequence, data, data_size);
-                    free((void *)data);
-                    return -1;
-                }
-            } else if (sequence > g_ogg_expected_sequence) {
-                // 当前包仍然是未来的包,缓存起来
-                LN_MQTT_DEBUG("Dual stream: Current packet sequence=%u still ahead of expected=%u after skip, caching\n", 
-                       sequence, g_ogg_expected_sequence);
-                ln_udp_ogg_cache_packet(sequence, data, data_size);
-                free((void *)data);
-                return 0;
-            } else {
-                // 当前包已经是过去的包(在刷新缓存时已处理),丢弃
-                LN_MQTT_DEBUG("Dual stream: Current packet sequence=%u is now old (expected=%u), dropping\n", 
-                       sequence, g_ogg_expected_sequence);
-                free((void *)data);
-                return 0;
-            }
-        } else {
-            // 正常缓存未来包
-            ln_udp_ogg_cache_packet(sequence, data, data_size);
-            free((void *)data);
-            return 0;
-        }
-    } else {
-        // 是过去的包(重复或乱序),丢弃
-        LN_MQTT_DEBUG("Dual stream: Received old/duplicate packet sequence=%u (expected=%u), dropping\n", 
-               sequence, g_ogg_expected_sequence);
-        free((void *)data);
-        return 0;
-    }
-}
-#endif /* USE_DUAL_STREAM_INTERFACE */
-
-/* 接收线程内固定时间间隔(秒)后重新创建 socket,0 表示不按时间重建 */
-#define UDP_RECV_RECREATE_INTERVAL_SEC 0
-
-/**
- * @brief 仅创建并配置 UDP socket(不启动线程),供创建与接收线程内重建复用
- * @return socket fd,失败返回 -1
- */
-static int ln_udp_create_socket_fd_only(void)
-{
-    ln_mqtt_udp_info_t udp_info;
-    if (ln_mqtt_api_get_udp_info(&udp_info) != 0 || !udp_info.valid) {
-        return -1;
-    }
-    int fd = lwip_socket(AF_INET, SOCK_DGRAM, 0);
-    if (fd < 0) {
-        return -1;
-    }
-    int opt = 1;
-    lwip_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
-    return fd;
-}
-
-/**
- * @brief UDP接收线程
- */
-static void ln_udp_recv_thread(void *arg)
-{
-    (void)arg;
-    uint8_t recv_buffer[UDP_RECV_BUFFER_SIZE];
-    struct sockaddr_in server_addr;
-    socklen_t addr_len = sizeof(server_addr);
-    uint32_t last_recreate_tick = 0;
-    uint32_t recreate_interval_ticks = 0;
-
-    LN_MQTT_INFO("Receive thread started\n");
-
-    // 获取服务器地址信息
-    ln_mqtt_udp_info_t udp_info;
-    if (ln_mqtt_api_get_udp_info(&udp_info) != 0 || !udp_info.valid) {
-        LN_MQTT_WARN("UDP info not available in receive thread\n");
-        g_udp_thread_running = false;
-        return;
-    }
-
-    // 设置服务器地址
-    memset(&server_addr, 0, sizeof(server_addr));
-    server_addr.sin_family = AF_INET;
-    server_addr.sin_port = lwip_htons(udp_info.port);
-    server_addr.sin_addr.s_addr = inet_addr(udp_info.server);
-
-    if (UDP_RECV_RECREATE_INTERVAL_SEC > 0) {
-        recreate_interval_ticks = (uint32_t)osKernelGetTickFreq() * UDP_RECV_RECREATE_INTERVAL_SEC;
-        last_recreate_tick = osKernelGetTickCount();
-    }
-
-    while (g_udp_thread_running && g_udp_socket >= 0) {
-                LN_MQTT_DEBUG("g_udp_thread_running %d g_udp_socket %d\r\n", g_udp_thread_running, g_udp_socket);
-        // 固定时间间隔到达则重新创建 socket
-        if (UDP_RECV_RECREATE_INTERVAL_SEC > 0 && recreate_interval_ticks > 0) {
-            uint32_t now = osKernelGetTickCount();
-            if ((now - last_recreate_tick) >= recreate_interval_ticks) {
-                LN_MQTT_INFO("Recreate interval reached (%u s), recreating socket\n", (unsigned)UDP_RECV_RECREATE_INTERVAL_SEC);
-                lwip_close(g_udp_socket);
-                g_udp_socket = ln_udp_create_socket_fd_only();
-                if (g_udp_socket < 0) {
-                    LN_MQTT_ERROR("Failed to recreate socket: %s\n", strerror(errno));
-                    break;
-                }
-                /* 重新获取 UDP 地址信息(服务端地址/端口可能变化) */
-                if (ln_mqtt_api_get_udp_info(&udp_info) != 0 || !udp_info.valid) {
-                    LN_MQTT_WARN("UDP info not available after recreate, keep last address\n");
-                } else {
-                    memset(&server_addr, 0, sizeof(server_addr));
-                    server_addr.sin_family = AF_INET;
-                    server_addr.sin_port = lwip_htons(udp_info.port);
-                    server_addr.sin_addr.s_addr = inet_addr(udp_info.server);
-                    addr_len = sizeof(server_addr);
-                }
-                last_recreate_tick = osKernelGetTickCount();
-            }
-        }
-
-        // 接收UDP数据(阻塞,超时1秒)
-        fd_set read_fds;
-        struct timeval timeout;
-
-        FD_ZERO(&read_fds);
-        FD_SET(g_udp_socket, &read_fds);
-        timeout.tv_sec = 1;
-        timeout.tv_usec = 0;
-
-        int select_ret = lwip_select(g_udp_socket + 1, &read_fds, NULL, NULL, &timeout);
-        if (select_ret > 0 && FD_ISSET(g_udp_socket, &read_fds)) {
-            // 有数据可读
-            ssize_t recv_len = lwip_recvfrom(g_udp_socket, recv_buffer, sizeof(recv_buffer) - 1, 0,
-                                             (struct sockaddr *)&server_addr, &addr_len);
-
-            if (recv_len > 0) {
-                LN_MQTT_DEBUG("Received %d bytes\r\n",
-                       (int)recv_len
-                    );
-
-                // 处理接收到的数据(解包、解密)
-                ln_udp_process_received_data(recv_buffer, (uint16_t)recv_len);
-
-            } else if (recv_len == 0) {
-                LN_MQTT_INFO("Connection closed by peer\n");
-                break;
-            } else {
-                if (errno != EAGAIN && errno != EWOULDBLOCK) {
-                    LN_MQTT_ERROR("Recvfrom error: %s\n", strerror(errno));
-                }
-            }
-        } else if (select_ret < 0) {
-            LN_MQTT_DEBUG("select_ret %d\r\n", select_ret);
-            if (errno != EINTR) {
-                LN_MQTT_ERROR("Select error: %s\n", strerror(errno));
-                break;
-            }
-        }
-        // select_ret == 0 表示超时,继续循环
-    }
-
-    LN_MQTT_INFO("Receive thread exited\n");
-    g_udp_thread_running = false;
-}
-
-/**
- * @brief 创建UDP套接字
- * @return 0 成功,-1 失败
- */
-int ln_udp_create_socket(void)
-{
-    // 获取UDP信息
-    ln_mqtt_udp_info_t udp_info;
-    if (ln_mqtt_api_get_udp_info(&udp_info) != 0) {
-        LN_MQTT_WARN("UDP info not available, cannot create socket\n");
-        return -1;
-    }
-    
-    if (!udp_info.valid) {
-        LN_MQTT_WARN("UDP info invalid, cannot create socket\n");
-        return -1;
-    }
-    
-    LN_MQTT_INFO("Creating UDP socket for server: %s:%d\n", udp_info.server, udp_info.port);
-
-    // 如果socket已存在,先关闭
-    if (g_udp_socket >= 0) {
-        LN_MQTT_DEBUG("Closing existing UDP socket\n");
-        ln_udp_close_socket();
-    }
-
-    // 创建UDP套接字(复用仅创建 fd 的逻辑)
-    g_udp_socket = ln_udp_create_socket_fd_only();
-    if (g_udp_socket < 0) {
-        LN_MQTT_ERROR("Failed to create UDP socket: %s\n", strerror(errno));
-        return -1;
-    }
-
-    LN_MQTT_INFO("UDP socket created successfully, fd=%d\n", g_udp_socket);
-    
-    // 创建接收线程
-    if (!g_udp_thread_running) {
-        app_task_definition_ext_t recv_task = {
-            .attr = {
-                .name = "ln_udp_recv",
-                .stack_size = 0x8000,
-                .priority = osPriorityNormal
-            },
-            .func = ln_udp_recv_thread,
-            .task_handle = &g_udp_thread_handle,
-            .create_queue_handle = NULL,
-            .use_mem = USE_EXTEND_MEM,
-            .inst_args = NULL
-        };
-        
-        g_udp_thread_running = true;
-        errcode_t ret = uapi_create_osThread_custom_with_args(&recv_task);
-        if (ret != ERRCODE_SUCC) {
-            LN_MQTT_ERROR("Failed to create receive thread: %d\n", ret);
-            g_udp_thread_running = false;
-            lwip_close(g_udp_socket);
-            g_udp_socket = -1;
-            return -1;
-        }
-        LN_MQTT_INFO("Receive thread created successfully\n");
-    }
-    
-    return 0;
-}
-
-/**
- * @brief 关闭UDP套接字
- */
-void ln_udp_close_socket(void)
-{
-    // 完成并关闭 OGG 文件
-    if (g_ogg_initialized) {
-        ln_udp_ogg_complete_current_file();
-    }
-    
-    // 完成并关闭 Opus 调试文件
-#if ENABLE_OPUS_RAW_DEBUG
-    if (g_opus_debug_file != NULL) {
-        ln_udp_opus_debug_complete_current_file();
-    }
-#endif
-    
-    // 停止接收线程
-    if (g_udp_thread_running) {
-        LN_MQTT_INFO("Stopping receive thread\n");
-        g_udp_thread_running = false;
-        
-        // 等待线程退出(最多等待2秒)
-        uint32_t timeout = osKernelGetTickCount() + osKernelGetTickFreq() * 2;
-        while (g_udp_thread_handle != 0 && osKernelGetTickCount() < timeout) {
-            osDelay(10);
-        }
-        
-        if (g_udp_thread_handle != 0) {
-            LN_MQTT_WARN("Receive thread did not exit in time\n");
-        }
-        
-        g_udp_thread_handle = 0;
-    }
-    
-    // 关闭套接字
-    if (g_udp_socket >= 0) {
-        LN_MQTT_DEBUG("Closing UDP socket, fd=%d\n", g_udp_socket);
-        lwip_close(g_udp_socket);
-        g_udp_socket = -1;
-    }
-}
-
-/**
- * @brief 获取UDP套接字文件描述符
- * @return socket文件描述符,-1表示未创建
- */
-int ln_udp_get_socket(void)
+ #include "ln_mqtt_api.h"
+ #include "ln_api.h"
+ #include "custom_utils.h"
+ #include "cmsis_os2.h"
+ #include "soc_osal.h"
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include <stdbool.h>
+ #include <stdint.h>
+ #include <errno.h>
+ #include "lwip/sockets.h"
+ #include "lwip/netdb.h"
+ #include "lwip/inet.h"
+ #include <time.h>
+ #include "opus_frame_to_pcm.h"
+ #include "ai_media.h"
+ #include "ln_data_decode.h"
+ 
+ /* OGG 封装相关前向声明(不透明句柄,避免依赖 ogg.h) */
+ /* OGG 上下文句柄(不透明指针,实际类型在 ai_media_service_mqtt.cpp 中定义) */
+ typedef void* MqttOggContextHandle;
+ 
+ /* OGG 封装接口声明(使用不透明句柄) */
+ extern void* MqttOggGetContext(void);
+ extern int32_t MqttOggEncapsulationInitHandle(MqttOggContextHandle oggCtxHandle, FILE *pfd, uint32_t sampleRate, uint32_t channelCount);
+ extern int32_t MqttOggEncapsulationWriteHandle(MqttOggContextHandle oggCtxHandle, const uint8_t *buffer, uint32_t bufferLen, FILE *pfd, bool isEos);
+ extern int32_t MqttOggEncapsulationFinishHandle(MqttOggContextHandle oggCtxHandle, FILE *pfd);
+ 
+ /* MQTT 播放接口声明 */
+ typedef void (*MqttPlayerCallback)(int32_t event, void* userData);
+ extern int32_t MQTT_PlayAudioFile(const char* filePath, MqttPlayerCallback callback, void* userData);
+ 
+ /* UDP接收缓冲区大小 */
+ #define UDP_RECV_BUFFER_SIZE 4096
+ 
+ /* UDP数据包头部大小 */
+ #define UDP_PACKET_HEADER_SIZE 16  // 1+1+2+4+4+4 = 16 bytes
+ 
+ /* UDP数据包结构体 */
+ typedef struct {
+     uint8_t type;               // 类型 (1 byte)
+     uint8_t flags;              // 标志 (1 byte)
+     uint16_t payload_len;       // 负载长度 (2 bytes, 网络字节序)
+     uint32_t ssrc;              // SSRC (4 bytes, 网络字节序)
+     uint32_t timestamp;         // 时间戳 (4 bytes, 网络字节序)
+     uint32_t sequence;          // 序列号 (4 bytes, 网络字节序)
+     uint8_t *payload;           // 加密负载数据指针
+     uint16_t payload_size;      // 负载数据大小
+ } ln_udp_packet_t;
+ 
+ /* UDP套接字全局变量 */
+ static int g_udp_socket = -1;
+ static volatile bool g_udp_thread_running = false;
+ static uint32_t g_udp_thread_handle = 0;
+ 
+ /* UDP数据包序列号和SSRC管理 */
+uint32_t g_udp_ssrc = 0;          // SSRC标识
+uint32_t g_udp_sequence = 0;      // 序列号(自动递增)
+
+void set_udp_sequence(uint32_t sequence)
 {
-    return g_udp_socket;
+    g_udp_sequence = sequence;
 }
 
-/**
- * @brief 检查UDP套接字是否已创建
- * @return true 已创建,false 未创建
- */
-bool ln_udp_is_socket_created(void)
+void set_udp_ssrc(uint32_t ssrc)
 {
-    return (g_udp_socket >= 0);
+    g_udp_ssrc = ssrc;
 }
-
-// +------+-------+-------------+----------+------------+----------+-------------------+
-// | Type | Flags | Payload Len |   SSRC   | Timestamp  | Sequence | Encrypted Payload |
-// +------+-------+-------------+----------+------------+----------+-------------------+
-// |1 byte|1 byte |   2 bytes   | 4 bytes  |  4 bytes   | 4 bytes  |      N bytes      |
-// +------+-------+-------------+----------+------------+----------+-------------------+
-
-
-// import struct
-// from Crypto.Cipher import AES
-// from Crypto.Util import Counter
-
-// def encrypt_audio(key_hex: str, nonce_hex: str, timestamp: int, sequence: int, audio_data: bytes) -> bytes:
-//     """
-//     加密音频数据
-    
-//     Args:
-//         key_hex: 32字符十六进制密钥
-//         nonce_hex: 32字符十六进制nonce
-//         timestamp: 数据包时间戳
-//         sequence: 数据包序列号
-//         audio_data: 原始Opus音频数据
-    
-//     Returns:
-//         加密后的音频数据
-//     """
-//     key = bytes.fromhex(key_hex)
-//     nonce = bytes.fromhex(nonce_hex)
-    
-//     # 构建计数器初始值:结合timestamp和sequence
-//     counter_init = int.from_bytes(nonce[:12] + struct.pack('>I', sequence), 'big')
-//     ctr = Counter.new(128, initial_value=counter_init)
-    
-//     cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
-//     return cipher.encrypt(audio_data)
-
-// def decrypt_audio(key_hex: str, nonce_hex: str, timestamp: int, sequence: int, encrypted_data: bytes) -> bytes:
-//     """解密音频数据(与加密相同,CTR模式对称)"""
-//     return encrypt_audio(key_hex, nonce_hex, timestamp, sequence, encrypted_data)
-
-/**
- * @brief 将十六进制字符串转换为字节数组
- * @param hex_str 十六进制字符串(如 "01020304")
- * @param output 输出的字节数组
- * @param output_size 输出缓冲区大小
- * @return 转换的字节数,-1表示失败
- */
-static int hex_string_to_bytes(const char *hex_str, uint8_t *output, size_t output_size)
-{
-    if (!hex_str || !output) {
-        return -1;
-    }
-    
-    size_t hex_len = strlen(hex_str);
-    if (hex_len % 2 != 0 || hex_len / 2 > output_size) {
-        LN_MQTT_WARN("Invalid hex string length: %zu\n", hex_len);
-        return -1;
-    }
-    
-    for (size_t i = 0; i < hex_len; i += 2) {
-        char hex_byte[3] = {hex_str[i], hex_str[i + 1], '\0'};
-        char *endptr;
-        unsigned long val = strtoul(hex_byte, &endptr, 16);
-        if (*endptr != '\0' || val > 255) {
-            LN_MQTT_WARN("Invalid hex character at position %zu\n", i);
-            return -1;
-        }
-        output[i / 2] = (uint8_t)val;
-    }
-    
-    return (int)(hex_len / 2);
-}
-
-/**
- * @brief 加密音频数据(AES-128-CTR模式)
- * @param key_hex 32字符十六进制密钥
- * @param base_nonce_hex 32字符十六进制基础nonce(从hello消息中获取的原始nonce)
- * @param payload_len 负载长度(2字节,用于构造nonce)
- * @param timestamp 时间戳(4字节,用于构造nonce)
- * @param sequence 数据包序列号(4字节,用于构造nonce)
- * @param audio_data 原始音频数据
- * @param audio_len 音频数据长度
- * @param encrypted_data 输出的加密数据缓冲区(由调用者分配,大小至少为audio_len)
- * @return 0 成功,-1 失败
- */
-int ln_udp_encrypt_audio(const char *key_hex, const char *base_nonce_hex, 
-                         uint16_t payload_len, uint32_t timestamp, uint32_t sequence,
-                         const uint8_t *audio_data, uint16_t audio_len, uint8_t *encrypted_data)
-{
-    if (!key_hex || !base_nonce_hex || !audio_data || !encrypted_data || audio_len == 0) {
-        LN_MQTT_WARN("Invalid parameters for encryption\n");
-        return -1;
-    }
-    
-    // 转换密钥(32字符十六进制 = 16字节)
-    uint8_t key[16];
-    if (hex_string_to_bytes(key_hex, key, sizeof(key)) != 16) {
-        LN_MQTT_ERROR("Failed to convert key from hex\n");
-        return -1;
-    }
-    
-    // 转换基础nonce(32字符十六进制 = 16字节)
-    uint8_t base_nonce[16];
-    if (hex_string_to_bytes(base_nonce_hex, base_nonce, sizeof(base_nonce)) != 16) {
-        LN_MQTT_ERROR("Failed to convert base nonce from hex\n");
-        return -1;
-    }
-    
-    // 构造nonce:参考代码 std::string nonce(aes_nonce_);
-    // 然后修改特定位置:*(uint16_t*)&nonce[2] = htons(payload.size());
-    //                    *(uint32_t*)&nonce[8] = htonl(timestamp);
-    //                    *(uint32_t*)&nonce[12] = htonl(sequence);
-    uint8_t nonce_counter[16];
-    memcpy(nonce_counter, base_nonce, 16);  // 复制基础nonce
-    *(uint16_t*)&nonce_counter[2] = lwip_htons(payload_len);   // 位置2:payload长度(2字节)
-    *(uint32_t*)&nonce_counter[8] = lwip_htonl(timestamp);      // 位置8:时间戳(4字节)
-    *(uint32_t*)&nonce_counter[12] = lwip_htonl(sequence);     // 位置12:序列号(4字节)
-    
-    // 初始化AES上下文
-    mbedtls_aes_context aes_ctx;
-    mbedtls_aes_init(&aes_ctx);
-    
-    // 设置AES-128密钥(128位 = 16字节)
-    if (mbedtls_aes_setkey_enc(&aes_ctx, key, 128) != 0) {
-        LN_MQTT_ERROR("Failed to set AES key\n");
-        mbedtls_aes_free(&aes_ctx);
-        return -1;
-    }
-    
-    // 使用AES-CTR模式加密(直接使用 mbedtls_aes_crypt_ctr)
-    size_t nc_off = 0;  // nonce counter offset(CTR模式内部使用)
-    uint8_t stream_block[16] = {0};  // stream block(CTR模式内部使用)
-
-    LN_MQTT_DEBUG("nonce_counter: ");
-    for (int i = 0; i < 16; i++) {
-        LN_MQTT_DEBUG("%02x ", nonce_counter[i]);
-    }
-    LN_MQTT_DEBUG("\n");
-    
-    if (mbedtls_aes_crypt_ctr(&aes_ctx, audio_len, &nc_off, nonce_counter, stream_block,
-                               audio_data, encrypted_data) != 0) {
-        LN_MQTT_ERROR("Failed to encrypt audio data with AES-CTR\n");
-        mbedtls_aes_free(&aes_ctx);
-        return -1;
-    }
-    
-    mbedtls_aes_free(&aes_ctx);
-    
-    return 0;
-}
-
-/**
- * @brief 解密音频数据(AES-128-CTR模式,与加密相同)
- * @param key_hex 32字符十六进制密钥
- * @param nonce_hex 32字符十六进制nonce
- * @param sequence 数据包序列号
- * @param encrypted_data 加密的音频数据
- * @param encrypted_len 加密数据长度
- * @param decrypted_data 输出的解密数据缓冲区(由调用者分配,大小至少为encrypted_len)
- * @return 0 成功,-1 失败
- */
-/**
- * @brief 解密音频数据(AES-128-CTR模式,与加密相同)
- * @param key_hex 32字符十六进制密钥
- * @param base_nonce_hex 32字符十六进制基础nonce(从hello消息中获取的原始nonce)
- * @param payload_len 负载长度(2字节,用于构造nonce)
- * @param timestamp 时间戳(4字节,用于构造nonce)
- * @param sequence 数据包序列号(4字节,用于构造nonce)
- * @param encrypted_data 加密的音频数据
- * @param encrypted_len 加密数据长度
- * @param decrypted_data 输出的解密数据缓冲区(由调用者分配,大小至少为encrypted_len)
- * @return 0 成功,-1 失败
- */
-int ln_udp_decrypt_audio(const char *key_hex, const char *base_nonce_hex,
-                         uint16_t payload_len, uint32_t timestamp, uint32_t sequence,
-                         const uint8_t *encrypted_data, uint16_t encrypted_len, uint8_t *decrypted_data)
-{
-    // CTR模式是对称的,解密和加密使用相同的操作
-    return ln_udp_encrypt_audio(key_hex, base_nonce_hex, payload_len, timestamp, sequence,
-                                 encrypted_data, encrypted_len, decrypted_data);
-}
-
-/**
- * @brief 完整的数据打包函数(加密+打包)
- * @param audio_data 原始音频数据
- * @param audio_len 音频数据长度
- * @param buffer 输出缓冲区(由调用者分配,大小至少为 UDP_PACKET_HEADER_SIZE + audio_len)
- * @param buffer_size 缓冲区大小
- * @param out_len 输出的实际数据长度
- * @return 0 成功,-1 失败
- * @note 函数内部自动处理type、flags、ssrc、timestamp、sequence等字段
- */
-int ln_udp_pack_audio_data(const uint8_t *audio_data, uint16_t audio_len,
-                           uint8_t *buffer, uint16_t buffer_size, uint16_t *out_len)
-{
-    if (!audio_data || audio_len == 0 || !buffer || !out_len) {
-        LN_MQTT_WARN("Invalid parameters for pack audio data\n");
-        return -1;
-    }
-    
-    // 计算所需缓冲区大小
-    uint16_t total_len = UDP_PACKET_HEADER_SIZE + audio_len;
-    if (buffer_size < total_len) {
-        LN_MQTT_WARN("Buffer too small: need %d, have %d\n", total_len, buffer_size);
-        return -1;
-    }
-    
-    // 1. 获取UDP信息(用于加密)
-    ln_mqtt_udp_info_t udp_info;
-    if (ln_mqtt_api_get_udp_info(&udp_info) != 0 || !udp_info.valid) {
-        LN_MQTT_WARN("UDP info not available for encryption\n");
-        return -1;
-    }
-    // 01 00 00 00 1d 38 db 20 69 70 a0 da 00000000
-    // 2. 初始化SSRC(如果未初始化,从 nonce 字节数组的索引 4-8 提取,只取 4 个字节)
-    if (g_udp_ssrc == 0) {
-        // 先将 nonce_hex 转换为字节数组
-        uint8_t nonce_bytes[16];  // nonce 是 32 个十六进制字符 = 16 字节
-        if (hex_string_to_bytes(udp_info.nonce, nonce_bytes, sizeof(nonce_bytes)) == 16) {
-            // 从字节数组中取索引 4-7(4 个字节)作为 SSRC(大端序)
-            g_udp_ssrc = ((uint32_t)nonce_bytes[4] << 24) |
-                         ((uint32_t)nonce_bytes[5] << 16) |
-                         ((uint32_t)nonce_bytes[6] << 8) |
-                         ((uint32_t)nonce_bytes[7]);
-            LN_MQTT_DEBUG("SSRC initialized from nonce bytes[4-7]: 0x%02X%02X%02X%02X -> 0x%08X\n",
-                   nonce_bytes[4], nonce_bytes[5], nonce_bytes[6], nonce_bytes[7], g_udp_ssrc);
-        } else {
-            LN_MQTT_WARN("Failed to convert nonce to bytes, using default SSRC\n");
-            g_udp_ssrc = 0x12345678;
-        }
-    }
-    
-    // 3. 自动递增序列号
-    uint32_t sequence = g_udp_sequence++;
-    
-    // 4. 获取时间戳(使用系统时钟)
-    uint32_t timestamp = (uint32_t)osKernelGetTickCount();  // 使用系统tick作为时间戳
-    
-    // 5. 设置数据包类型和标志(使用默认值)
-    uint8_t type = 0x01;   // 默认类型
-    uint8_t flags = 0;  // 默认标志
-    
-    // 6. 加密音频数据(nonce在函数内部构造)
-    uint8_t *encrypted_payload = buffer + UDP_PACKET_HEADER_SIZE;
-    if (ln_udp_encrypt_audio(udp_info.key, udp_info.nonce, audio_len, timestamp, sequence,
-                             audio_data, audio_len, encrypted_payload) != 0) {
-        LN_MQTT_ERROR("Failed to encrypt audio data\n");
-        return -1;
-    }
-    
-    // 7. 打包数据包头部
-    uint8_t *ptr = buffer;
-    ptr[0] = type;
-    ptr[1] = flags;
-    *(uint16_t *)(ptr + 2) = lwip_htons(audio_len);  // payload_len(加密后的长度等于原始长度)
-    *(uint32_t *)(ptr + 4) = lwip_htonl(g_udp_ssrc);
-    *(uint32_t *)(ptr + 8) = lwip_htonl(timestamp);
-    *(uint32_t *)(ptr + 12) = lwip_htonl(sequence);
-    
-    // 加密的payload已经在步骤6中写入buffer + UDP_PACKET_HEADER_SIZE位置
-    
-    *out_len = total_len;
-
-    LN_MQTT_DEBUG("buffer: ");
-    for (int i = 0; i < 16; i++) {
-        LN_MQTT_DEBUG("%02x ", buffer[i]);
-    }
-    LN_MQTT_DEBUG("\n");
-    
-    LN_MQTT_DEBUG("Audio data packed: type=%d, flags=%d, ssrc=0x%08X, timestamp=%u, sequence=%u, payload_len=%d, total_len=%d\n",
-           type, flags, g_udp_ssrc, timestamp, sequence, audio_len, total_len);
-    
-    return 0;
-}
-
-/**
- * @brief 创建音频发送队列
- */
-static void create_audio_send_queue(void)
-{
-    if (g_audio_send_queue != 0xFFFFFFFF) {
-        // 队列已创建
-        return;
-    }
-    
-    if (osal_msg_queue_create("ln_udp_audio_send_queue", AUDIO_SEND_QUEUE_SIZE, &g_audio_send_queue,
-                              0, sizeof(audio_send_msg_t)) != ERRCODE_SUCC) {
-        LN_MQTT_ERROR("Failed to create audio send queue\n");
-        g_audio_send_queue = 0xFFFFFFFF;
-    } else {
-        LN_MQTT_INFO("Audio send queue created successfully\n");
-    }
-}
-
-/**
- * @brief 发送单帧音频数据(打包/加密 + 发送)
- * @note 复用原有 ln_udp_pack_audio_data 加密逻辑
- * @return 0 成功,-1 失败
- */
-static int ln_udp_send_audio_frame(int sock, const uint8_t *audio_data, uint32_t audio_size)
-{
-    if (audio_data == NULL || audio_size == 0 || audio_size > AUDIO_SEND_MAX_DATA_SIZE) {
-        return -1;
-    }
-
-    // 打包(加密+UDP头)
-    uint8_t send_buf[UDP_PACKET_HEADER_SIZE + AUDIO_SEND_MAX_DATA_SIZE];
-    uint16_t out_len = 0;
-    if (ln_udp_pack_audio_data(audio_data, (uint16_t)audio_size, send_buf,
-                               (uint16_t)sizeof(send_buf), &out_len) != 0) {
-        LN_MQTT_ERROR("Failed to pack audio frame\n");
-        return -1;
-    }
-
-    // 获取UDP信息(用于发送)
-    ln_mqtt_udp_info_t udp_info;
-    if (ln_mqtt_api_get_udp_info(&udp_info) != 0 || !udp_info.valid) {
-        LN_MQTT_WARN("UDP info not available, drop audio frame\n");
-        return -1;
-    }
-
-    // 发送到服务器
-    struct sockaddr_in server_addr;
-    memset(&server_addr, 0, sizeof(server_addr));
-    server_addr.sin_family = AF_INET;
-    server_addr.sin_port = lwip_htons(udp_info.port);
-    server_addr.sin_addr.s_addr = inet_addr(udp_info.server);
-
-    ssize_t sent = lwip_sendto(sock, send_buf, out_len, 0,
-                               (struct sockaddr *)&server_addr, sizeof(server_addr));
-    if (sent < 0) {
-        LN_MQTT_ERROR("UDP sendto failed: %s\n", strerror(errno));
-        return -1;
-    }
-
-    return 0;
-}
-
-/**
- * @brief 音频数据发送线程
- */
-static void ln_udp_audio_send_thread(void *arg)
-{
-    (void)arg;
-    audio_send_msg_t msg;
-    uint32_t msgsize = sizeof(audio_send_msg_t);
-    
-    LN_MQTT_INFO("Audio send thread started\n");
-    
-    while (g_audio_send_thread_running) {
-        // 从队列读取音频数据(阻塞等待)
-        int32_t ret = osal_msg_queue_read_copy(g_audio_send_queue, (void *)&msg, &msgsize,
-                                               OSAL_MSGQ_WAIT_FOREVER);
-        if (ret != OSAL_SUCCESS) {
-            LN_MQTT_ERROR("Failed to read from audio send queue: %d\n", ret);
-            continue;
-        }
-        
-        // 检查数据大小和数据指针
-        if (msg.size == 0 || msg.size > AUDIO_SEND_MAX_DATA_SIZE || msg.data == NULL) {
-            LN_MQTT_WARN("Invalid audio data: size=%u, data=%p\n", msg.size, msg.data);
-            // 释放内存(如果存在)
-            if (msg.data != NULL) {
-                free(msg.data);
-            }
-            continue;
-        }
-        
-        // 确保 UDP socket 已创建
-        if (!ln_udp_is_socket_created()) {
-            LN_MQTT_WARN("UDP socket not ready, drop audio frame\n");
-            free(msg.data);
-            continue;
-        }
-        
-        int sock = ln_udp_get_socket();
-        if (sock < 0) {
-            LN_MQTT_WARN("UDP socket invalid, drop audio frame\n");
-            free(msg.data);
-            continue;
-        }
-        
-        const uint8_t *send_data = msg.data;
-        uint32_t send_size = msg.size;
-        if (msg.encoding == AUDIO_SEND_ENCODING_PCM) {
-            /* PCM:追加到缓存,按 60ms 切割编码,余量保留 */
-            if (g_pcm_send_cache == NULL) {
-                LN_MQTT_WARN("PCM cache not allocated, drop %u bytes\n", msg.size);
-                free(msg.data);
-                continue;
-            }
-            while (g_pcm_send_cache_size + msg.size > g_pcm_send_cache_capacity &&
-                   g_pcm_send_cache_size >= AUDIO_SEND_PCM_FRAME_BYTES) {
-                /* 缓存满时丢弃最旧一帧,避免持续溢出 */
-                g_pcm_send_cache_size -= AUDIO_SEND_PCM_FRAME_BYTES;
-                memmove(g_pcm_send_cache, g_pcm_send_cache + AUDIO_SEND_PCM_FRAME_BYTES, g_pcm_send_cache_size);
-            }
-            if (g_pcm_send_cache_size + msg.size > g_pcm_send_cache_capacity) {
-                LN_MQTT_WARN("PCM cache full, drop %u bytes (cache=%u, cap=%u)\n", msg.size, g_pcm_send_cache_size, g_pcm_send_cache_capacity);
-                free(msg.data);
-                continue;
-            }
-            memcpy(g_pcm_send_cache + g_pcm_send_cache_size, msg.data, msg.size);
-            g_pcm_send_cache_size += msg.size;
-            free(msg.data);
-            msg.data = NULL;
-
-            /* 每次取 60ms 固定长度编码并发送,多余数据留在缓存 */
-            uint8_t opus_buf[1500];
-
-            while (g_pcm_send_cache_size >= AUDIO_SEND_PCM_FRAME_BYTES) {
-                int encoded_len = 0;
-                LN_MQTT_DEBUG("PCM send cache size: %d\n", g_pcm_send_cache_size);
-                for (int i = 0; i < AUDIO_SEND_PCM_FRAME_BYTES; i++) {
-                    LN_MQTT_DEBUG("%02x ", g_pcm_send_cache[i]);
-                }
-                LN_MQTT_DEBUG("\n");
-                
-                if (opus_pcm_to_frame_encode_packet(
-                        (const short *)g_pcm_send_cache, AUDIO_SEND_PCM_FRAME_SAMPLES,
-                        AUDIO_SEND_PCM_SAMPLE_RATE, AUDIO_SEND_PCM_CHANNELS,
-                        AUDIO_SEND_PCM_BITRATE, AUDIO_SEND_PCM_APPLICATION,
-                        opus_buf, (int)sizeof(opus_buf), &encoded_len) != 0) {
-                    LN_MQTT_WARN("PCM to Opus encode failed, drop 60ms frame\n");
-                    break;
-                }
-                LN_MQTT_DEBUG("Opus encoded len: %d\n", encoded_len); 
-                LN_MQTT_DEBUG("Opus encoded data: ");
-                for (int i = 0; i < encoded_len; i++) {
-                    LN_MQTT_DEBUG("%02x ", opus_buf[i]);
-                }
-                LN_MQTT_DEBUG("\n");
-                if (ln_udp_send_audio_frame(sock, opus_buf, (uint32_t)encoded_len) != 0) {
-                    break;
-                }
-                /* 移除已编码的一帧,保留余量 */
-                g_pcm_send_cache_size -= AUDIO_SEND_PCM_FRAME_BYTES;
-                memmove(g_pcm_send_cache, g_pcm_send_cache + AUDIO_SEND_PCM_FRAME_BYTES, g_pcm_send_cache_size);
-            }
-            continue;
-        }
-        
-        (void)send_data;
-        if (ln_udp_send_audio_frame(sock, send_data, send_size) != 0) {
-            free(msg.data);
-            continue;
-        }
-        
-        // 释放音频数据内存
-        free(msg.data);
-        msg.data = NULL;
-    }
-    
-    LN_MQTT_INFO("Audio send thread exited\n");
-    g_audio_send_thread_running = false;
-}
-
-/**
- * @brief 初始化音频发送队列和线程
- * @return 0 成功,-1 失败
- */
-int ln_udp_audio_send_init(void)
-{
-    // 动态分配 PCM 发送缓存(若未分配或需扩容可在此统一申请)
-    if (g_pcm_send_cache == NULL) {
-        g_pcm_send_cache = (uint8_t *)malloc(AUDIO_SEND_PCM_CACHE_ALLOC_SIZE);
-        if (g_pcm_send_cache == NULL) {
-            LN_MQTT_ERROR("Failed to allocate PCM send cache (%u bytes)\n", (unsigned)AUDIO_SEND_PCM_CACHE_ALLOC_SIZE);
-            return -1;
-        }
-        g_pcm_send_cache_capacity = AUDIO_SEND_PCM_CACHE_ALLOC_SIZE;
-        g_pcm_send_cache_size = 0;
-        LN_MQTT_DEBUG("PCM send cache allocated: %u bytes\n", g_pcm_send_cache_capacity);
-    }
-
-    // 创建消息队列
-    create_audio_send_queue();
-    if (g_audio_send_queue == 0xFFFFFFFF) {
-        LN_MQTT_ERROR("Failed to create audio send queue\n");
-        if (g_pcm_send_cache != NULL) {
-            free(g_pcm_send_cache);
-            g_pcm_send_cache = NULL;
-            g_pcm_send_cache_capacity = 0;
-        }
-        return -1;
-    }
-    
-    // 创建发送线程(如果尚未创建)
-    if (!g_audio_send_thread_running) {
-        app_task_definition_ext_t send_task = {
-            .attr = {
-                .name = "ln_udp_audio_send",
-                .stack_size = 0x4000,
-                .priority = osPriorityNormal
-            },
-            .func = ln_udp_audio_send_thread,
-            .task_handle = &g_audio_send_thread_handle,
-            .create_queue_handle = NULL,
-            .use_mem = USE_EXTEND_MEM,
-            .inst_args = NULL
-        };
-        
-        g_audio_send_thread_running = true;
-        errcode_t ret = uapi_create_osThread_custom_with_args(&send_task);
-        if (ret != ERRCODE_SUCC) {
-            LN_MQTT_ERROR("Failed to create audio send thread: %d\n", ret);
-            g_audio_send_thread_running = false;
-            return -1;
-        }
-        LN_MQTT_INFO("Audio send thread created successfully\n");
-    }
-    
-    return 0;
-}
-
-/**
- * @brief 停止音频发送线程
- */
-void ln_udp_audio_send_deinit(void)
-{
-    g_pcm_send_cache_size = 0;
-    g_pcm_send_cache_capacity = 0;
-    if (g_pcm_send_cache != NULL) {
-        free(g_pcm_send_cache);
-        g_pcm_send_cache = NULL;
-    }
-    // 停止发送线程
-    if (g_audio_send_thread_running) {
-        LN_MQTT_INFO("Stopping audio send thread\n");
-        g_audio_send_thread_running = false;
-        
-        // 发送一个空消息唤醒线程(如果队列为空)
-        // 注意:data 为 NULL,size 为 0,线程会检查并释放
-        audio_send_msg_t wake_msg = {0};
-        wake_msg.data = NULL;
-        wake_msg.size = 0;
-        wake_msg.encoding = AUDIO_SEND_ENCODING_OPUS;
-        osal_msg_queue_write_copy(g_audio_send_queue, (void *)&wake_msg, sizeof(audio_send_msg_t), 0);
-        
-        // 等待线程退出(最多等待2秒)
-        uint32_t timeout = osKernelGetTickCount() + osKernelGetTickFreq() * 2;
-        while (g_audio_send_thread_handle != 0 && osKernelGetTickCount() < timeout) {
-            osDelay(10);
-        }
-        
-        if (g_audio_send_thread_handle != 0) {
-            LN_MQTT_WARN("Audio send thread did not exit in time\n");
-        }
-        
-        g_audio_send_thread_handle = 0;
-    }
-    
-    // 清空队列中剩余的消息(释放内存)
-    if (g_audio_send_queue != 0xFFFFFFFF) {
-        audio_send_msg_t msg;
-        uint32_t msgsize = sizeof(audio_send_msg_t);
-        while (osal_msg_queue_read_copy(g_audio_send_queue, (void *)&msg, &msgsize, 0) == OSAL_SUCCESS) {
-            if (msg.data != NULL) {
-                free(msg.data);
-            }
-            msgsize = sizeof(audio_send_msg_t);
-        }
-    }
-    
-    // 删除消息队列
-    if (g_audio_send_queue != 0xFFFFFFFF) {
-        osal_msg_queue_delete(g_audio_send_queue);
-        g_audio_send_queue = 0xFFFFFFFF;
-        LN_MQTT_DEBUG("Audio send queue deleted\n");
-    }
-}
-
-/**
- * @brief 将音频数据放入发送队列(供录音回调函数调用)
- * @param data 音频数据指针
- * @param size 音频数据大小
- * @return 0 成功,-1 失败
- * @note 函数内部会动态分配内存并复制数据,发送线程负责释放内存
- * @param encoding AUDIO_SEND_ENCODING_OPUS(0)=已是 Opus;AUDIO_SEND_ENCODING_PCM(1)=PCM,发送前编码为 Opus
- */
-int ln_udp_audio_send_enqueue(const uint8_t *data, uint32_t size, uint8_t encoding)
-{
-    if (!data || size == 0 || size > AUDIO_SEND_MAX_DATA_SIZE) {
-        LN_MQTT_WARN("Invalid parameters for audio send enqueue\n");
-        return -1;
-    }
-    
-    if (g_audio_send_queue == 0xFFFFFFFF) {
-        LN_MQTT_WARN("Audio send queue not initialized\n");
-        return -1;
-    }
-    
-    // 动态分配内存并复制数据
-    uint8_t *data_copy = (uint8_t *)malloc(size);
-    if (data_copy == NULL) {
-        LN_MQTT_ERROR("Failed to allocate memory for audio data\n");
-        return -1;
-    }
-    memcpy(data_copy, data, size);
-    
-    // 构造消息
-    audio_send_msg_t msg;
-    msg.data = data_copy;
-    msg.size = size;
-    msg.encoding = (encoding != AUDIO_SEND_ENCODING_PCM) ? AUDIO_SEND_ENCODING_OPUS : AUDIO_SEND_ENCODING_PCM;
-    
-    // 写入队列(非阻塞,如果队列满则丢弃并释放内存)
-    int32_t ret = osal_msg_queue_write_copy(g_audio_send_queue, (void *)&msg, sizeof(audio_send_msg_t), 0);
-    if (ret != OSAL_SUCCESS) {
-        LN_MQTT_WARN("Failed to enqueue audio data: %d (queue may be full)\n", ret);
-        free(data_copy);  // 队列满时释放内存
-        return -1;
-    }
-    
-    return 0;
-}
\ No newline at end of file
+ 
+ /* 音频数据发送队列和线程 */
+ #define AUDIO_SEND_QUEUE_SIZE 32  // 队列大小
+ 
+ /* OGG 封装相关全局变量 */
+ #define OGG_PACKETS_PER_FILE 50  // 每50个数据包打包成一个文件
+ #define OGG_PACKET_CACHE_SIZE 32  // 数据包缓存大小(用于处理乱序)
+ #define OGG_OPUS_ENCODE_HEADER_SIZE 8  // Opus 编码头大小(前8字节)
+ #define ENABLE_OPUS_RAW_DEBUG 1  // 启用 Opus 原始数据保存调试(1=启用,0=禁用)
+ 
+ static MqttOggContextHandle g_ogg_ctx_handle = NULL;  // OGG 上下文句柄(不透明指针)
+ static FILE *g_ogg_file = NULL;      // 当前 OGG 文件句柄
+ static uint32_t g_ogg_packet_count = 0; // 当前文件的数据包计数
+ static uint32_t g_ogg_file_index = 0;   // 文件序号
+ static bool g_ogg_initialized = false;  // OGG 上下文是否已初始化
+ static char g_completed_ogg_filename[128] = {0};  // 已完成的 OGG 文件名(用于播放)
+ 
+ /* Opus 原始数据调试相关全局变量 */
+ static FILE *g_opus_debug_file = NULL;      // Opus 调试文件句柄
+ static uint32_t g_opus_debug_packet_count = 0; // Opus 调试文件的数据包计数
+ static uint32_t g_opus_debug_file_index = 0;   // Opus 调试文件序号
+ #define OPUS_DEBUG_PCM_BUF_SAMPLES 5760   // 单帧解码 PCM 缓冲(120ms@48kHz)
+ static short g_opus_debug_pcm_buf[OPUS_DEBUG_PCM_BUF_SAMPLES];
+ static FILE *g_opus_debug_pcm_file = NULL;    // 解码 PCM 测试输出文件(追加)
+ 
+ /* 数据包缓存结构(用于处理乱序接收) */
+ typedef struct {
+     uint32_t sequence;      // 序列号
+     uint8_t *data;          // 数据指针(动态分配)
+     uint32_t data_size;     // 数据大小
+     bool valid;             // 是否有效
+ } ogg_packet_cache_t;
+ 
+ static ogg_packet_cache_t g_ogg_packet_cache[OGG_PACKET_CACHE_SIZE] = {0};  // 数据包缓存
+ static uint32_t g_ogg_expected_sequence = 0;  // 期望的下一个序列号(0表示未初始化)
+ static uint32_t g_ogg_out_of_order_count = 0;  // 连续错序计数器(用于检测丢包)
+ #define OGG_MAX_OUT_OF_ORDER_COUNT 5  // 最大连续错序次数,超过后跳过一包
+ #define AUDIO_SEND_MAX_DATA_SIZE 0x800  // 最大音频数据大小(与 LN_MQTT_RECORD_MAX_FRAME 一致)
+ 
+ /* 音频编码类型:入队时 data 的格式 */
+ #define AUDIO_SEND_ENCODING_OPUS 0  // 已是 Opus,直接发送
+ #define AUDIO_SEND_ENCODING_PCM  1  // PCM,发送前用 opus_pcm_to_frame_encode_packet 编码为 Opus
+ 
+ /* PCM 编码默认参数(encoding==PCM 时使用):输入为 16kHz 单声道、16-bit 小端 */
+ #define AUDIO_SEND_PCM_SAMPLE_RATE  16000  /* 采样率 16kHz */
+ #define AUDIO_SEND_PCM_CHANNELS     1      /* 单声道 */
+ #define AUDIO_SEND_PCM_BITRATE     64000
+ #define AUDIO_SEND_PCM_APPLICATION 2049  /* Opus: OPUS_APPLICATION_AUDIO,必须为 2048/2049/2051 之一 */
+ #define AUDIO_SEND_PCM_FRAME_MS    60  // 每帧 60ms,切割固定长度再编码
+ /* 60ms 对应字节数:sample_rate * frame_ms/1000 * channels * 2(bytes/sample) */
+ #define AUDIO_SEND_PCM_FRAME_BYTES ((uint32_t)((AUDIO_SEND_PCM_SAMPLE_RATE) * (AUDIO_SEND_PCM_FRAME_MS) / 1000 * (AUDIO_SEND_PCM_CHANNELS) * 2))
+ #define AUDIO_SEND_PCM_FRAME_SAMPLES ((int)((AUDIO_SEND_PCM_SAMPLE_RATE) * (AUDIO_SEND_PCM_FRAME_MS) / 1000))
+ /* 动态分配容量:帧数可调以增大缓存(约 6s @ 60ms/帧) */
+ #define AUDIO_SEND_PCM_CACHE_ALLOC_FRAMES 100
+ #define AUDIO_SEND_PCM_CACHE_ALLOC_SIZE   ((AUDIO_SEND_PCM_CACHE_ALLOC_FRAMES) * (AUDIO_SEND_PCM_FRAME_BYTES) + (AUDIO_SEND_MAX_DATA_SIZE))
+ 
+ /* PCM 发送缓存:动态分配,新数据追加,满 60ms 切割编码,余量保留 */
+ static uint8_t *g_pcm_send_cache = NULL;
+ static uint32_t g_pcm_send_cache_capacity = 0;  /* 已分配容量(字节) */
+ static uint32_t g_pcm_send_cache_size = 0;      /* 当前有效数据长度 */
+ 
+ /* 音频数据消息结构体 */
+ typedef struct {
+     uint8_t *data;    // 音频数据指针(动态分配)
+     uint32_t size;    // 数据大小(字节)
+     uint8_t  encoding; // 0=Opus,1=PCM(发送前编码为 Opus)
+ } audio_send_msg_t;
+ 
+ static unsigned long g_audio_send_queue = 0xFFFFFFFF;  // 消息队列句柄
+ static volatile bool g_audio_send_thread_running = false;
+ static uint32_t g_audio_send_thread_handle = 0;
+ 
+ /* 前向声明 */
+ static void ln_udp_recv_thread(void *arg);
+ int ln_udp_process_received_data(const uint8_t *data, uint16_t data_len);
+ int ln_udp_handle_decrypted_payload(uint32_t sequence, uint8_t *decrypted_data, uint16_t payload_len);
+ int ln_udp_unpack_packet(const uint8_t *data, uint16_t data_len, ln_udp_packet_t *packet);
+ void ln_udp_free_packet(ln_udp_packet_t *packet);
+ void ln_udp_close_socket(void);
+ static void ln_udp_audio_send_thread(void *arg);
+ static void create_audio_send_queue(void);
+ /* OGG 封装相关函数声明 */
+ static int ln_udp_ogg_handle_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size);
+ static void ln_udp_ogg_cache_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size);
+ static int ln_udp_ogg_write_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size);
+ static void ln_udp_ogg_flush_cache(void);
+ static void ln_udp_ogg_complete_current_file(void);
+ static int ln_udp_ogg_create_new_file(void);
+ 
+ #if USE_DUAL_STREAM_INTERFACE
+ /* 双流接口相关函数声明 */
+ static int ln_udp_dual_stream_handle_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size);
+ static int ln_udp_dual_stream_decode_and_send(uint32_t sequence, const uint8_t *data, uint32_t data_size);
+ static bool ln_udp_dual_stream_flush_cache(void);
+ #endif /* USE_DUAL_STREAM_INTERFACE */
+ /* Opus 原始数据调试相关函数声明 */
+ static int ln_udp_opus_debug_create_new_file(void);
+ static void ln_udp_opus_debug_complete_current_file(void);
+ static int ln_udp_opus_debug_write_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size);
+ /* Opus 数据包解析函数声明 */
+ static int ln_udp_parse_opus_packet(const uint8_t *data, uint32_t data_size, 
+                                     const uint8_t **opus_data, uint32_t *opus_data_size);
+ 
+ 
+ 
+ /**
+  * @brief 处理已解密的负载数据(供 UDP/TCP 共享)
+  * @param sequence 数据包序列号
+  * @param decrypted_data 已解密数据(所有权转移给本函数)
+  * @param payload_len 数据长度
+  * @return 0 成功,-1 失败
+  */
+ int ln_udp_handle_decrypted_payload(uint32_t sequence, uint8_t *decrypted_data, uint16_t payload_len)
+ {
+     if (decrypted_data == NULL || payload_len == 0) {
+         return -1;
+     }
+ 
+ #if USE_DUAL_STREAM_INTERFACE
+     if (ln_udp_dual_stream_handle_packet(sequence, decrypted_data, payload_len) != 0) {
+         LN_MQTT_ERROR("Failed to handle dual stream packet (sequence=%u)\n", sequence);
+         free(decrypted_data);
+         return -1;
+     }
+ #else
+     if (ln_udp_ogg_handle_packet(sequence, decrypted_data, payload_len) != 0) {
+         LN_MQTT_ERROR("Failed to handle OGG packet (sequence=%u)\n", sequence);
+         free(decrypted_data);
+         return -1;
+     }
+ #endif
+ 
+     return 0;
+ }
+ 
+ /**
+  * @brief 处理接收到的UDP数据(解包、解密)
+  * @param data 原始接收数据
+  * @param data_len 数据长度
+  * @return 0 成功,-1 失败
+  */
+ int ln_udp_process_received_data(const uint8_t *data, uint16_t data_len)
+ {
+     ln_udp_packet_t packet;
+     memset(&packet, 0, sizeof(packet));
+     
+     // 1. 解包数据包
+     if (ln_udp_unpack_packet(data, data_len, &packet) != 0) {
+         LN_MQTT_ERROR("Failed to unpack packet\n");
+         return -1;
+     }
+     
+     LN_MQTT_DEBUG("Packet unpacked: type=%d, flags=%d, ssrc=%u, timestamp=%u, sequence=%u, payload_len=%d\n",
+            packet.type, packet.flags, packet.ssrc, packet.timestamp, packet.sequence, packet.payload_len);
+     
+     // 2. 获取UDP信息(用于解密)
+     ln_mqtt_udp_info_t udp_info;
+     if (ln_mqtt_api_get_udp_info(&udp_info) != 0 || !udp_info.valid) {
+         LN_MQTT_WARN("UDP info not available for decryption\n");
+         ln_udp_free_packet(&packet);
+         return -1;
+     }
+     
+     // 3. 解密负载数据
+     if (packet.payload_len > 0 && packet.payload) {
+         // 分配解密数据缓冲区
+         uint8_t *decrypted_data = (uint8_t *)malloc(packet.payload_len);
+         if (!decrypted_data) {
+             LN_MQTT_ERROR("Failed to allocate memory for decrypted data\n");
+             ln_udp_free_packet(&packet);
+             return -1;
+         }
+         
+         // 执行解密(nonce在函数内部构造)
+         if (ln_udp_decrypt_audio(udp_info.key, udp_info.nonce, packet.payload_len, 
+                                  packet.timestamp, packet.sequence,
+                                  packet.payload, packet.payload_len, decrypted_data) != 0) {
+             LN_MQTT_ERROR("Failed to decrypt payload data\n");
+             free(decrypted_data);
+             ln_udp_free_packet(&packet);
+             return -1;
+         }
+         LN_MQTT_DEBUG("decrypted_data: ");
+ 
+         LN_MQTT_DEBUG("\n");
+         
+         LN_MQTT_DEBUG("Payload decrypted successfully, size=%d bytes\n", packet.payload_len);
+         
+         LN_MQTT_DEBUG("packet.sequence: %u\n", packet.sequence);
+ 
+         (void)ln_udp_handle_decrypted_payload(packet.sequence, decrypted_data, packet.payload_len);
+     } else {
+         LN_MQTT_DEBUG("Packet has no payload data\n");
+     }
+     
+     // 4. 释放数据包
+     ln_udp_free_packet(&packet);
+     
+     return 0;
+ }
+ 
+ /**
+  * @brief 解包UDP数据包
+  * @param data 原始数据缓冲区
+  * @param data_len 数据长度
+  * @param packet 输出的数据包结构体指针(payload字段需要外部释放)
+  * @return 0 成功,-1 失败
+  */
+ int ln_udp_unpack_packet(const uint8_t *data, uint16_t data_len, ln_udp_packet_t *packet)
+ {
+     if (!data || !packet || data_len < UDP_PACKET_HEADER_SIZE) {
+         LN_MQTT_WARN("Invalid parameters or data too short for unpack\n");
+         return -1;
+     }
+     
+     memset(packet, 0, sizeof(ln_udp_packet_t));
+     
+     // 解析头部字段(网络字节序)
+     const uint8_t *ptr = data;
+     packet->type = ptr[0];
+     packet->flags = ptr[1];
+     packet->payload_len = lwip_ntohs(*(uint16_t *)(ptr + 2));
+     packet->ssrc = lwip_ntohl(*(uint32_t *)(ptr + 4));
+     packet->timestamp = lwip_ntohl(*(uint32_t *)(ptr + 8));
+     packet->sequence = lwip_ntohl(*(uint32_t *)(ptr + 12));
+     
+     // 验证负载长度
+     uint16_t expected_len = UDP_PACKET_HEADER_SIZE + packet->payload_len;
+     if (data_len < expected_len) {
+         LN_MQTT_WARN("Data length mismatch: expected %d, got %d\n", expected_len, data_len);
+         return -1;
+     }
+     
+     // 分配并复制负载数据
+     if (packet->payload_len > 0) {
+         packet->payload = (uint8_t *)malloc(packet->payload_len);
+         if (!packet->payload) {
+             LN_MQTT_ERROR("Failed to allocate memory for payload\n");
+             return -1;
+         }
+         memcpy(packet->payload, ptr + UDP_PACKET_HEADER_SIZE, packet->payload_len);
+         packet->payload_size = packet->payload_len;
+     } else {
+         packet->payload = NULL;
+         packet->payload_size = 0;
+     }
+     
+     return 0;
+ }
+ 
+ /**
+  * @brief 封包UDP数据包
+  * @param packet 数据包结构体指针
+  * @param buffer 输出缓冲区指针(由调用者分配)
+  * @param buffer_size 缓冲区大小
+  * @param out_len 输出的实际数据长度
+  * @return 0 成功,-1 失败
+  */
+ int ln_udp_pack_packet(const ln_udp_packet_t *packet, uint8_t *buffer, uint16_t buffer_size, uint16_t *out_len)
+ {
+     if (!packet || !buffer || !out_len) {
+         LN_MQTT_WARN("Invalid parameters for pack\n");
+         return -1;
+     }
+     
+     // 计算所需缓冲区大小
+     uint16_t total_len = UDP_PACKET_HEADER_SIZE + packet->payload_len;
+     if (buffer_size < total_len) {
+         LN_MQTT_WARN("Buffer too small: need %d, have %d\n", total_len, buffer_size);
+         return -1;
+     }
+     
+     // 打包头部字段(转换为网络字节序)
+     uint8_t *ptr = buffer;
+     ptr[0] = packet->type;
+     ptr[1] = packet->flags;
+     *(uint16_t *)(ptr + 2) = lwip_htons(packet->payload_len);
+     *(uint32_t *)(ptr + 4) = lwip_htonl(packet->ssrc);
+     *(uint32_t *)(ptr + 8) = lwip_htonl(packet->timestamp);
+     *(uint32_t *)(ptr + 12) = lwip_htonl(packet->sequence);
+     
+     // 复制负载数据
+     if (packet->payload_len > 0 && packet->payload) {
+         memcpy(ptr + UDP_PACKET_HEADER_SIZE, packet->payload, packet->payload_len);
+     }
+     
+     *out_len = total_len;
+     return 0;
+ }
+ 
+ /**
+  * @brief 释放数据包结构体的负载数据
+  * @param packet 数据包结构体指针
+  */
+ void ln_udp_free_packet(ln_udp_packet_t *packet)
+ {
+     if (packet && packet->payload) {
+         free(packet->payload);
+         packet->payload = NULL;
+         packet->payload_size = 0;
+     }
+ }
+ 
+ // ==================== OGG 封装处理函数(支持乱序数据包缓存和排序)====================
+ 
+ /**
+  * @brief 清空数据包缓存
+  */
+ static void ln_udp_ogg_clear_cache(void)
+ {
+     for (int i = 0; i < OGG_PACKET_CACHE_SIZE; i++) {
+         if (g_ogg_packet_cache[i].valid && g_ogg_packet_cache[i].data != NULL) {
+             free(g_ogg_packet_cache[i].data);
+             g_ogg_packet_cache[i].data = NULL;
+         }
+         g_ogg_packet_cache[i].valid = false;
+         g_ogg_packet_cache[i].sequence = 0;
+         g_ogg_packet_cache[i].data_size = 0;
+     }
+ }
+ 
+ /**
+  * @brief 将数据包缓存(用于处理乱序接收)
+  * @param sequence 序列号
+  * @param data 数据指针(会被复制,调用者可以释放)
+  * @param data_size 数据大小
+  */
+ static void ln_udp_ogg_cache_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size)
+ {
+     // 查找空闲的缓存槽
+     int empty_slot = -1;
+     for (int i = 0; i < OGG_PACKET_CACHE_SIZE; i++) {
+         if (!g_ogg_packet_cache[i].valid) {
+             empty_slot = i;
+             break;
+         }
+     }
+     
+     if (empty_slot < 0) {
+         LN_MQTT_WARN("OGG cache full, dropping packet sequence=%u\n", sequence);
+         ln_udp_ogg_clear_cache();
+         return;
+     }
+     
+     // 分配内存并复制数据
+     uint8_t *cached_data = (uint8_t *)malloc(data_size);
+     if (cached_data == NULL) {
+         LN_MQTT_ERROR("Failed to allocate memory for cached packet sequence=%u\n", sequence);
+         return;
+     }
+     
+     memcpy(cached_data, data, data_size);
+     
+     // 保存到缓存
+     g_ogg_packet_cache[empty_slot].sequence = sequence;
+     g_ogg_packet_cache[empty_slot].data = cached_data;
+     g_ogg_packet_cache[empty_slot].data_size = data_size;
+     g_ogg_packet_cache[empty_slot].valid = true;
+     
+     LN_MQTT_DEBUG("Cached packet sequence=%u (slot=%d)\n", sequence, empty_slot);
+ }
+ 
+ /**
+  * @brief 创建新的 Opus 调试文件(直接保存 Opus 原始数据)
+  * @return 0 成功,-1 失败
+  */
+ static int ln_udp_opus_debug_create_new_file(void)
+ {
+ #if ENABLE_OPUS_RAW_DEBUG
+     // 创建新文件(使用时间戳和序号命名)
+     time_t now = time(NULL);
+     struct tm *tm_info = localtime(&now);
+     char filename[128];
+     snprintf(filename, sizeof(filename), "/user/udp_opus_raw_%04d%02d%02d_%02d%02d%02d_%03u.opus",
+              tm_info->tm_year + 1900, tm_info->tm_mon + 1, tm_info->tm_mday,
+              tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, g_opus_debug_file_index);
+     
+     g_opus_debug_file = fopen(filename, "wb");
+     if (g_opus_debug_file == NULL) {
+         LN_MQTT_ERROR("Failed to open Opus debug file: %s\n", filename);
+         return -1;
+     }
+     
+     g_opus_debug_packet_count = 0;
+     g_opus_debug_file_index++;
+     LN_MQTT_DEBUG("Created new Opus debug file: %s (index: %u)\n", filename, g_opus_debug_file_index - 1);
+     
+     return 0;
+ #else
+     return -1;
+ #endif
+ }
+ 
+ /**
+  * @brief 完成当前 Opus 调试文件
+  */
+ static void ln_udp_opus_debug_complete_current_file(void)
+ {
+ #if ENABLE_OPUS_RAW_DEBUG
+     if (g_opus_debug_file == NULL) {
+         return;
+     }
+     
+     fclose(g_opus_debug_file);
+     g_opus_debug_file = NULL;
+     g_opus_debug_packet_count = 0;
+     LN_MQTT_DEBUG("Completed Opus debug file #%u\n", g_opus_debug_file_index - 1);
+ #endif
+ }
+ 
+ /**
+  * @brief 解析 Opus 数据包,找到实际 Opus 编码数据的起始位置和大小
+  * @param data 完整的 Opus 数据包(包含 TOC 字节和可能的帧长度表)
+  * @param data_size 数据包总大小
+  * @param opus_data 输出的实际 Opus 数据起始指针
+  * @param opus_data_size 输出的实际 Opus 数据大小
+  * @return 0 成功,-1 失败
+  * @note 根据 RFC 6716 Opus 编码标准解析
+  */
+ static int ln_udp_parse_opus_packet(const uint8_t *data, uint32_t data_size, 
+                                     const uint8_t **opus_data, uint32_t *opus_data_size)
+ {
+     if (!data || data_size == 0 || !opus_data || !opus_data_size) {
+         LN_MQTT_WARN("Invalid parameters for Opus packet parsing\n");
+         return -1;
+     }
+     
+     // 至少需要 TOC 字节
+     if (data_size < 1) {
+         LN_MQTT_WARN("Opus packet too small (size=%u)\n", data_size);
+         return -1;
+     }
+     
+     const uint8_t *ptr = data;
+     uint32_t remaining = data_size;
+     
+     // 1. 读取 TOC 字节
+     uint8_t toc = ptr[0];
+     ptr++;
+     remaining--;
+     
+     // 2. 解析帧计数编码(bit 3-7)
+     uint8_t frame_count_code = (toc >> 3) & 0x1F;  // bit 3-7
+     uint8_t frame_count = 0;
+     uint32_t header_size = 1;  // TOC 字节
+     
+     // 3. 判断是单帧还是多帧
+     if (frame_count_code < 16) {
+         // 单帧:帧计数 = 编码值(0表示1帧,1-15表示对应帧数)
+         if (frame_count_code == 0) {
+             frame_count = 1;  // 编码值0表示单帧
+         } else {
+             frame_count = frame_count_code;
+         }
+     } else {
+         // 多帧:需要读取帧计数字节
+         if (remaining < 1) {
+             LN_MQTT_WARN("Opus packet too small for frame count byte\n");
+             return -1;
+         }
+         frame_count = frame_count_code - 16 + 1 + ptr[0];
+         ptr++;
+         remaining--;
+         header_size++;
+     }
+     
+     // 4. 如果是多帧,需要读取帧长度表(但不跳过数据)
+     if (frame_count > 1) {
+         // 多帧数据包需要帧长度表
+         // 每个帧长度使用可变长度编码(1-2字节)
+         // 如果长度 < 252,使用1字节;否则使用2字节(0xFC + 实际长度)
+         // 注意:我们只读取长度值,不跳过帧数据,因为所有帧数据是连续存储的
+         for (uint8_t i = 0; i < frame_count - 1; i++) {
+             if (remaining < 1) {
+                 LN_MQTT_WARN("Opus packet too small for frame length table\n");
+                 return -1;
+             }
+             
+             if (ptr[0] < 252) {
+                 // 1字节编码
+                 ptr++;
+                 remaining--;
+                 header_size++;
+             } else {
+                 // 2字节编码:0xFC + 实际长度(大端序)
+                 if (remaining < 2) {
+                     LN_MQTT_WARN("Opus packet too small for 2-byte frame length\n");
+                     return -1;
+                 }
+                 ptr += 2;
+                 remaining -= 2;
+                 header_size += 2;
+             }
+         }
+     }
+     
+     // 5. 现在 header_size 包含了所有头部信息(TOC + 帧计数 + 帧长度表)
+     // 从 header_size 之后的所有数据都是 Opus 编码数据(包含所有帧的数据)
+     if (header_size >= data_size) {
+         LN_MQTT_WARN("Opus header size (%u) >= total size (%u)\n", header_size, data_size);
+         return -1;
+     }
+     
+     *opus_data = data + header_size;
+     *opus_data_size = data_size - header_size;
+     
+     LN_MQTT_DEBUG("Opus parsed: frame_count=%d, header_size=%u, opus_data_size=%u\n", 
+            frame_count, header_size, *opus_data_size);
+     
+     return 0;
+ }
+ 
+ /**
+  * @brief 写入 Opus 原始数据到调试文件(不进行 OGG 封装)
+  * @param sequence 序列号
+  * @param data 数据指针(包含 Opus TOC 字节和可能的帧长度表)
+  * @param data_size 数据大小
+  * @return 0 成功,-1 失败
+  */
+ static int ln_udp_opus_debug_write_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size)
+ {
+ #if ENABLE_OPUS_RAW_DEBUG
+     // 检查是否需要创建新文件
+     if (g_opus_debug_file == NULL || g_opus_debug_packet_count >= OGG_PACKETS_PER_FILE) {
+         // 完成当前文件
+         if (g_opus_debug_file != NULL) {
+             ln_udp_opus_debug_complete_current_file();
+         }
+         
+         // 创建新文件
+         if (ln_udp_opus_debug_create_new_file() != 0) {
+             return -1;
+         }
+     }
+     LN_MQTT_DEBUG("opus debug data: ");
+     for (int i = 0; i < data_size; i++) {
+         LN_MQTT_DEBUG("%02X ", data[i]);
+     }
+     LN_MQTT_DEBUG("\n");
+     
+     // 解析 Opus 数据包(用于验证和日志输出,但不影响保存)
+     const uint8_t *opus_data = NULL;
+     uint32_t opus_data_size = 0;
+     int parse_ret = ln_udp_parse_opus_packet(data, data_size, &opus_data, &opus_data_size);
+     if (parse_ret == 0) {
+         LN_MQTT_DEBUG("Opus debug: Parsed packet sequence=%u, total_size=%u, opus_data_size=%u\n", 
+                sequence, data_size, opus_data_size);
+     } else {
+         LN_MQTT_WARN("Opus debug: Parse warning (sequence=%u, size=%u), but saving raw data anyway\n", 
+                sequence, data_size);
+     }
+ 
+     /* 调用 opus 解码接口测试:完整包 data/data_size 送入解码 */
+     {
+         int pcm_samples = 0;
+         int dec_ret = opus_frame_to_pcm_decode_packet(
+             data, (int)data_size,
+             16000, 1,
+             g_opus_debug_pcm_buf, OPUS_DEBUG_PCM_BUF_SAMPLES,
+             &pcm_samples);
+         if (dec_ret == 0) {
+             LN_MQTT_DEBUG("Opus decode test OK sequence=%u pcm_samples=%d\n", sequence, pcm_samples);
+             if (g_opus_debug_pcm_file == NULL) {
+                 g_opus_debug_pcm_file = fopen("user/udp_opus_debug.pcm", "ab");
+                 if (g_opus_debug_pcm_file != NULL) {
+                     LN_MQTT_DEBUG("Opus decode PCM output: udp_opus_debug.pcm (append)\n");
+                 }
+             }
+             if (g_opus_debug_pcm_file != NULL && pcm_samples > 0) {
+                 size_t n = fwrite(g_opus_debug_pcm_buf, sizeof(short), (size_t)pcm_samples, g_opus_debug_pcm_file);
+                 if (n != (size_t)pcm_samples) {
+                     LN_MQTT_DEBUG("Opus decode PCM write short: %zu/%d\n", n, pcm_samples);
+                 }
+                 fflush(g_opus_debug_pcm_file);
+             }
+             fclose(g_opus_debug_pcm_file);
+             g_opus_debug_pcm_file = NULL;
+         } else {
+             LN_MQTT_ERROR("Opus decode test FAIL sequence=%u\n", sequence);
+         }
+     }
+     
+     // 保存完整的原始数据(包含 TOC 字节和帧长度表)
+     size_t written = fwrite(data, 1, data_size, g_opus_debug_file);
+     if (written != data_size) {
+         LN_MQTT_ERROR("Opus debug: Failed to write data (sequence=%u, written=%zu, expected=%u)\n", 
+                sequence, written, data_size);
+         return -1;
+     }
+     
+     fflush(g_opus_debug_file);  // 立即刷新到文件
+     
+     g_opus_debug_packet_count++;
+     LN_MQTT_DEBUG("Opus debug: Written packet sequence=%u (%u/%u, raw_size=%u bytes)\n", 
+            sequence, g_opus_debug_packet_count, OGG_PACKETS_PER_FILE, data_size);
+     
+     return 0;
+ #else
+     return 0;  // 未启用时直接返回成功
+ #endif
+ }
+ 
+ /**
+  * @brief 写入数据包到 OGG 文件
+  * @param sequence 序列号
+  * @param data 数据指针
+  * @param data_size 数据大小
+  * @return 0 成功,-1 失败
+  */
+ static int ln_udp_ogg_write_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size)
+ {
+     // 调试模式:同时保存 Opus 原始数据
+ #if ENABLE_OPUS_RAW_DEBUG
+     ln_udp_opus_debug_write_packet(sequence, data, data_size);
+ #endif
+     
+     // 检查是否需要创建新文件
+     if (!g_ogg_initialized || g_ogg_packet_count >= OGG_PACKETS_PER_FILE) {
+         // 完成当前文件
+         if (g_ogg_initialized) {
+             ln_udp_ogg_complete_current_file();
+         }
+         
+         // 创建新文件
+         if (ln_udp_ogg_create_new_file() != 0) {
+             return -1;
+         }
+     }
+     
+     // 写入数据包
+     if (g_ogg_ctx_handle == NULL) {
+         LN_MQTT_ERROR("OGG context handle is null\n");
+         return -1;
+     }
+     if (MqttOggEncapsulationWriteHandle(g_ogg_ctx_handle, data, data_size, g_ogg_file, false) != 0) {
+         LN_MQTT_ERROR("Failed to write OGG data (sequence=%u)\n", sequence);
+         return -1;
+     }
+     
+     g_ogg_packet_count++;
+     LN_MQTT_DEBUG("Written packet sequence=%u (%u/%u to OGG file)\n", 
+            sequence, g_ogg_packet_count, OGG_PACKETS_PER_FILE);
+     
+     return 0;
+ }
+ 
+ /**
+  * @brief 刷新缓存:尝试按顺序写入缓存中的数据包
+  */
+ static void ln_udp_ogg_flush_cache(void)
+ {
+     bool found = true;
+     while (found) {
+         found = false;
+         // 查找期望的下一个序列号
+         for (int i = 0; i < OGG_PACKET_CACHE_SIZE; i++) {
+             if (g_ogg_packet_cache[i].valid && 
+                 g_ogg_packet_cache[i].sequence == g_ogg_expected_sequence) {
+                 // 找到期望的包,写入
+                 if (ln_udp_ogg_write_packet(g_ogg_packet_cache[i].sequence,
+                                             g_ogg_packet_cache[i].data,
+                                             g_ogg_packet_cache[i].data_size) == 0) {
+                     // 更新期望的序列号
+                     g_ogg_expected_sequence++;
+                     // 释放缓存
+                     free(g_ogg_packet_cache[i].data);
+                     g_ogg_packet_cache[i].data = NULL;
+                     g_ogg_packet_cache[i].valid = false;
+                     g_ogg_packet_cache[i].sequence = 0;
+                     g_ogg_packet_cache[i].data_size = 0;
+                     found = true;
+                     LN_MQTT_DEBUG("Flushed cached packet sequence=%u\n", 
+                            g_ogg_expected_sequence - 1);
+                     break;  // 重新开始查找
+                 } else {
+                     // 写入失败,保留在缓存中
+                     break;
+                 }
+             }
+         }
+     }
+ }
+ 
+ /**
+  * @brief 完成当前 OGG 文件并播放
+  */
+ static void ln_udp_ogg_complete_current_file(void)
+ {
+     if (!g_ogg_initialized || g_ogg_file == NULL) {
+         return;
+     }
+     
+     // 写入结束标志并完成封装
+     if (g_ogg_ctx_handle != NULL) {
+         uint8_t empty_data[8] = {0};  // 至少需要8字节的编码头
+         MqttOggEncapsulationWriteHandle(g_ogg_ctx_handle, empty_data, 8, g_ogg_file, true);
+         MqttOggEncapsulationFinishHandle(g_ogg_ctx_handle, g_ogg_file);
+     }
+     fclose(g_ogg_file);
+     g_ogg_file = NULL;
+     g_ogg_initialized = false;
+     
+     LN_MQTT_INFO("Completed OGG file #%u with %u packets: %s\n", 
+            g_ogg_file_index - 1, g_ogg_packet_count, g_completed_ogg_filename);
+     
+     // 使用 MQTT 播放接口播放完成的文件
+     if (g_completed_ogg_filename[0] != '\0') {
+         int32_t play_ret = MQTT_PlayAudioFile(g_completed_ogg_filename, NULL, NULL);
+         if (play_ret == 0) {
+             LN_MQTT_INFO("Started playing OGG file: %s\n", g_completed_ogg_filename);
+         } else {
+             LN_MQTT_ERROR("Failed to play OGG file: %s (ret=%d)\n", 
+                    g_completed_ogg_filename, play_ret);
+         }
+         g_completed_ogg_filename[0] = '\0';
+     }
+ }
+ 
+ /**
+  * @brief 创建新的 OGG 文件
+  * @return 0 成功,-1 失败
+  */
+ static int ln_udp_ogg_create_new_file(void)
+ {
+     // 创建新文件(使用时间戳和序号命名)
+     time_t now = time(NULL);
+     struct tm *tm_info = localtime(&now);
+     char filename[128];
+     snprintf(filename, sizeof(filename), "/user/udp_audio_%04d%02d%02d_%02d%02d%02d_%03u.ogg",
+              tm_info->tm_year + 1900, tm_info->tm_mon + 1, tm_info->tm_mday,
+              tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, g_ogg_file_index);
+     
+     // 保存文件名(用于后续播放)
+     strncpy(g_completed_ogg_filename, filename, sizeof(g_completed_ogg_filename) - 1);
+     g_completed_ogg_filename[sizeof(g_completed_ogg_filename) - 1] = '\0';
+     
+     g_ogg_file = fopen(filename, "wb");
+     if (g_ogg_file == NULL) {
+         LN_MQTT_ERROR("Failed to open OGG file: %s\n", filename);
+         g_completed_ogg_filename[0] = '\0';
+         return -1;
+     }
+     
+     // 获取 OGG 上下文句柄(如果尚未获取)
+     if (g_ogg_ctx_handle == NULL) {
+         g_ogg_ctx_handle = MqttOggGetContext();
+         if (g_ogg_ctx_handle == NULL) {
+             LN_MQTT_ERROR("Failed to get OGG context handle\n");
+             fclose(g_ogg_file);
+             g_ogg_file = NULL;
+             g_completed_ogg_filename[0] = '\0';
+             return -1;
+         }
+     }
+     
+     // 初始化 OGG 上下文
+     if (MqttOggEncapsulationInitHandle(g_ogg_ctx_handle, g_ogg_file, 16000, 1) != 0) {
+         LN_MQTT_ERROR("Failed to initialize OGG context\n");
+         fclose(g_ogg_file);
+         g_ogg_file = NULL;
+         g_completed_ogg_filename[0] = '\0';
+         return -1;
+     }
+     
+     g_ogg_initialized = true;
+     g_ogg_packet_count = 0;
+     g_ogg_file_index++;
+     LN_MQTT_DEBUG("Created new OGG file: %s (index: %u)\n", filename, g_ogg_file_index - 1);
+     
+     return 0;
+ }
+ 
+ /**
+  * @brief 处理 OGG 数据包(支持乱序接收和缓存)
+  * @param sequence 数据包序列号
+  * @param data 解密后的数据(所有权转移,函数内部负责释放)
+  * @param data_size 数据大小
+  * @return 0 成功,-1 失败
+  * @note 此函数会接管 data 的所有权,调用者不应再释放 data
+  */
+ static int ln_udp_ogg_handle_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size)
+ {
+     // 初始化期望序列号(首次接收)
+     if (g_ogg_expected_sequence == 0) {
+         g_ogg_expected_sequence = sequence;
+         g_ogg_out_of_order_count = 0;  // 重置错序计数器
+         LN_MQTT_DEBUG("Initialized expected sequence to %u\n", sequence);
+     }
+     
+     // 检查是否是期望的下一个包
+     if (sequence == g_ogg_expected_sequence) {
+         // 是期望的包,直接写入
+         if (ln_udp_ogg_write_packet(sequence, data, data_size) == 0) {
+             g_ogg_expected_sequence++;
+             g_ogg_out_of_order_count = 0;  // 重置错序计数器(收到期望的包)
+             free((void *)data);  // 释放数据(所有权已转移)
+             
+             // 尝试刷新缓存中的后续包
+             ln_udp_ogg_flush_cache();
+             return 0;
+         } else {
+             // 写入失败,缓存这个包
+             ln_udp_ogg_cache_packet(sequence, data, data_size);
+             free((void *)data);
+             return -1;
+         }
+     } else if (sequence > g_ogg_expected_sequence) {
+         // 是未来的包,缓存起来
+         g_ogg_out_of_order_count++;  // 增加错序计数器
+         LN_MQTT_WARN("Received future packet sequence=%u (expected=%u), caching (out_of_order_count=%u)\n", 
+                sequence, g_ogg_expected_sequence, g_ogg_out_of_order_count);
+         
+         // 如果连续错序超过阈值,跳过期望的序列号(避免丢包时一直无法正常输出)
+         if (g_ogg_out_of_order_count > OGG_MAX_OUT_OF_ORDER_COUNT) {
+             uint32_t old_expected = g_ogg_expected_sequence;
+             LN_MQTT_DEBUG("Out of order count exceeded %d, skipping sequence %u\n", 
+                    OGG_MAX_OUT_OF_ORDER_COUNT, g_ogg_expected_sequence);
+             
+             // 1. 先将期望序号往后加一(跳过丢失的包)
+             g_ogg_expected_sequence++;
+             g_ogg_out_of_order_count = 0;  // 重置计数器
+             
+             // 2. 处理缓存中的包(尝试写入缓存中所有能写入的连续包)
+             ln_udp_ogg_flush_cache();
+             
+             // 3. 再尝试写入当前包
+             if (sequence == g_ogg_expected_sequence) {
+                 // 当前包正好是期望的包,直接写入
+                 if (ln_udp_ogg_write_packet(sequence, data, data_size) == 0) {
+                     g_ogg_expected_sequence++;
+                     free((void *)data);
+                     // 继续刷新缓存中的后续包
+                     ln_udp_ogg_flush_cache();
+                     LN_MQTT_DEBUG("Successfully wrote skipped packet sequence=%u (old_expected=%u)\n", 
+                            sequence, old_expected);
+                     return 0;
+                 } else {
+                     // 写入失败,缓存这个包
+                     ln_udp_ogg_cache_packet(sequence, data, data_size);
+                     free((void *)data);
+                     return -1;
+                 }
+             } else if (sequence > g_ogg_expected_sequence) {
+                 // 当前包仍然是未来的包,缓存起来
+                 LN_MQTT_DEBUG("Current packet sequence=%u still ahead of expected=%u after skip, caching\n", 
+                        sequence, g_ogg_expected_sequence);
+                 ln_udp_ogg_cache_packet(sequence, data, data_size);
+                 free((void *)data);
+                 return 0;
+             } else {
+                 // 当前包已经是过去的包(在刷新缓存时已处理),丢弃
+                 LN_MQTT_DEBUG("Current packet sequence=%u is now old (expected=%u), dropping\n", 
+                        sequence, g_ogg_expected_sequence);
+                 free((void *)data);
+                 return 0;
+             }
+         } else {
+             // 正常缓存未来包
+             ln_udp_ogg_cache_packet(sequence, data, data_size);
+             free((void *)data);
+             return 0;
+         }
+     } else {
+         // 是过去的包(重复或乱序),丢弃
+         LN_MQTT_DEBUG("Received old/duplicate packet sequence=%u (expected=%u), dropping\n", 
+                sequence, g_ogg_expected_sequence);
+         free((void *)data);
+         return 0;
+     }
+ }
+ 
+ #if USE_DUAL_STREAM_INTERFACE
+ // ==================== 双流接口处理函数(支持乱序数据包缓存和排序)====================
+ 
+ /**
+  * @brief 解码 Opus 数据包并送入 AI_SendAudioDataToQueue
+  * @param sequence 序列号
+  * @param data 数据指针
+  * @param data_size 数据大小
+  * @return 0 成功,-1 失败
+  */
+ static int ln_udp_dual_stream_decode_and_send(uint32_t sequence, const uint8_t *data, uint32_t data_size)
+ {
+     int pcm_samples = 0;
+     int dec_ret = opus_frame_to_pcm_decode_packet(
+         data, (int)data_size,
+         16000, 1,
+         g_opus_debug_pcm_buf, OPUS_DEBUG_PCM_BUF_SAMPLES,
+         &pcm_samples);
+     
+     if (dec_ret == 0 && pcm_samples > 0) {
+         // 解码成功,将 PCM 数据送入 AI_SendAudioDataToQueue
+         uint32_t pcm_data_size = (uint32_t)(pcm_samples * sizeof(short));
+         int32_t send_ret = AI_SendAudioDataToQueue((const uint8_t *)g_opus_debug_pcm_buf, pcm_data_size);
+         if (send_ret == 0) {
+             LN_MQTT_DEBUG("Dual stream: Decoded and sent to queue: sequence=%u, pcm_samples=%d\n", 
+                    sequence, pcm_samples);
+             return 0;
+         } else {
+             LN_MQTT_ERROR("Dual stream: Failed to send decoded PCM to queue: sequence=%u, ret=%d\n", 
+                    sequence, (int)send_ret);
+             return -1;
+         }
+     } else {
+         LN_MQTT_ERROR("Dual stream: Opus decode failed: sequence=%u, ret=%d\n", 
+                sequence, dec_ret);
+         return -1;
+     }
+ }
+ 
+ /**
+  * @brief 刷新双流数据包缓存(按顺序处理缓存中的包)
+  */
+ static bool ln_udp_dual_stream_flush_cache(void)
+ {
+     bool found = true;
+     while (found) {
+         found = false;
+         // 查找期望序列号的包
+         for (int i = 0; i < OGG_PACKET_CACHE_SIZE; i++) {
+             if (g_ogg_packet_cache[i].valid && 
+                 g_ogg_packet_cache[i].sequence == g_ogg_expected_sequence) {
+                 // 找到期望的包,解码并发送
+                 if (ln_udp_dual_stream_decode_and_send(g_ogg_packet_cache[i].sequence,
+                                                         g_ogg_packet_cache[i].data,
+                                                         g_ogg_packet_cache[i].data_size) == 0) {
+                     g_ogg_expected_sequence++;
+                     // 释放缓存数据
+                     free(g_ogg_packet_cache[i].data);
+                     g_ogg_packet_cache[i].data = NULL;
+                     g_ogg_packet_cache[i].valid = false;
+                     g_ogg_packet_cache[i].sequence = 0;
+                     g_ogg_packet_cache[i].data_size = 0;
+                     found = true;  // 继续查找下一个期望的包
+                     LN_MQTT_DEBUG("Dual stream: Flushed cached packet sequence=%u\n", 
+                            g_ogg_expected_sequence - 1);
+                     break;  // 重新开始查找
+                 } else {
+                     // 解码失败,停止刷新
+                     found = false;
+                     break;
+                 }
+             }
+         }
+     }
+     return found;
+ }
+ 
+ /**
+  * @brief 处理双流数据包(支持乱序接收和缓存)
+  * @param sequence 数据包序列号
+  * @param data 解密后的数据(所有权转移,函数内部负责释放)
+  * @param data_size 数据大小
+  * @return 0 成功,-1 失败
+  * @note 此函数会接管 data 的所有权,调用者不应再释放 data
+  */
+ static int ln_udp_dual_stream_handle_packet(uint32_t sequence, const uint8_t *data, uint32_t data_size)
+ {
+     // 初始化期望序列号(首次接收)
+     if (g_ogg_expected_sequence == 0) {
+         g_ogg_expected_sequence = sequence;
+         g_ogg_out_of_order_count = 0;  // 重置错序计数器
+         LN_MQTT_DEBUG("Dual stream: Initialized expected sequence to %u\n", sequence);
+     }
+     
+     // 检查是否是期望的下一个包
+     if (sequence == g_ogg_expected_sequence) {
+         // 是期望的包,直接解码并发送
+         if (ln_udp_dual_stream_decode_and_send(sequence, data, data_size) == 0) {
+             g_ogg_expected_sequence++;
+             g_ogg_out_of_order_count = 0;  // 重置错序计数器(收到期望的包)
+             free((void *)data);  // 释放数据(所有权已转移)
+             
+             // 尝试刷新缓存中的后续包
+             ln_udp_dual_stream_flush_cache();
+             return 0;
+         } else {
+             // 解码失败,缓存这个包
+             ln_udp_ogg_cache_packet(sequence, data, data_size);
+             free((void *)data);
+             return -1;
+         }
+     } else if (sequence > g_ogg_expected_sequence) {
+         // 是未来的包,缓存起来
+         g_ogg_out_of_order_count++;  // 增加错序计数器
+         LN_MQTT_DEBUG("Dual stream: Received future packet sequence=%u (expected=%u), caching (out_of_order_count=%u)\n", 
+                sequence, g_ogg_expected_sequence, g_ogg_out_of_order_count);
+         
+         // 如果连续错序超过阈值,跳过期望的序列号(避免丢包时一直无法正常输出)
+         if (g_ogg_out_of_order_count > OGG_MAX_OUT_OF_ORDER_COUNT) {
+             uint32_t old_expected = g_ogg_expected_sequence;
+             LN_MQTT_WARN("Dual stream: Out of order count exceeded %d, skipping sequence %u\n", 
+                    OGG_MAX_OUT_OF_ORDER_COUNT, g_ogg_expected_sequence);
+             
+             // 1. 先将期望序号往后加一(跳过丢失的包)
+             g_ogg_expected_sequence++;
+             
+             // 2. 处理缓存中的包(尝试解码并发送缓存中所有能处理的连续包)
+             bool found = ln_udp_dual_stream_flush_cache();
+             if (found) {
+                 g_ogg_out_of_order_count = 0;  // 重置计数器
+                 LN_MQTT_DEBUG("Dual stream: Successfully flushed cache\n");
+             } else {
+                 LN_MQTT_WARN("Dual stream: Failed to flush cache\n");
+             }
+             
+             // 3. 再尝试处理当前包
+             if (sequence == g_ogg_expected_sequence) {
+                 // 当前包正好是期望的包,直接解码并发送
+                 if (ln_udp_dual_stream_decode_and_send(sequence, data, data_size) == 0) {
+                     g_ogg_expected_sequence++;
+                     free((void *)data);
+                     // 继续刷新缓存中的后续包
+                     ln_udp_dual_stream_flush_cache();
+                     LN_MQTT_DEBUG("Dual stream: Successfully decoded and sent skipped packet sequence=%u (old_expected=%u)\n", 
+                            sequence, old_expected);
+                     return 0;
+                 } else {
+                     // 解码失败,缓存这个包
+                     ln_udp_ogg_cache_packet(sequence, data, data_size);
+                     free((void *)data);
+                     return -1;
+                 }
+             } else if (sequence > g_ogg_expected_sequence) {
+                 // 当前包仍然是未来的包,缓存起来
+                 LN_MQTT_DEBUG("Dual stream: Current packet sequence=%u still ahead of expected=%u after skip, caching\n", 
+                        sequence, g_ogg_expected_sequence);
+                 ln_udp_ogg_cache_packet(sequence, data, data_size);
+                 free((void *)data);
+                 return 0;
+             } else {
+                 // 当前包已经是过去的包(在刷新缓存时已处理),丢弃
+                 LN_MQTT_DEBUG("Dual stream: Current packet sequence=%u is now old (expected=%u), dropping\n", 
+                        sequence, g_ogg_expected_sequence);
+                 free((void *)data);
+                 return 0;
+             }
+         } else {
+             // 正常缓存未来包
+             ln_udp_ogg_cache_packet(sequence, data, data_size);
+             free((void *)data);
+             return 0;
+         }
+     } else {
+         // 是过去的包(重复或乱序),丢弃
+         LN_MQTT_DEBUG("Dual stream: Received old/duplicate packet sequence=%u (expected=%u), dropping\n", 
+                sequence, g_ogg_expected_sequence);
+         free((void *)data);
+         return 0;
+     }
+ }
+ #endif /* USE_DUAL_STREAM_INTERFACE */
+ 
+ /* 接收线程内固定时间间隔(秒)后重新创建 socket,0 表示不按时间重建 */
+ #define UDP_RECV_RECREATE_INTERVAL_SEC 0
+ /* UDP 客户端本地端口绑定策略
+  * - UDP_CLIENT_LOCAL_BIND_PORT > 0: 绑定到指定本地端口
+  * - 否则且 UDP_CLIENT_BIND_TO_SERVER_PORT=1: 绑定到 hello 下发的 udp.port
+  * - 否则: 绑定到 0(由系统分配临时端口)
+  */
+ #ifndef UDP_CLIENT_BIND_TO_SERVER_PORT
+ #define UDP_CLIENT_BIND_TO_SERVER_PORT 1
+ #endif
+ #ifndef UDP_CLIENT_LOCAL_BIND_PORT
+ #define UDP_CLIENT_LOCAL_BIND_PORT 50001
+ #endif
+ 
+ /**
+  * @brief 仅创建并配置 UDP socket(不启动线程),供创建与接收线程内重建复用
+  * @return socket fd,失败返回 -1
+  */
+ static int ln_udp_create_socket_fd_only(void)
+ {
+     ln_mqtt_udp_info_t udp_info;
+     if (ln_mqtt_api_get_udp_info(&udp_info) != 0 || !udp_info.valid) {
+         return -1;
+     }
+     int fd = lwip_socket(AF_INET, SOCK_DGRAM, 0);
+     if (fd < 0) {
+         return -1;
+     }
+     int opt = 1;
+     lwip_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+ 
+     /* UDP 客户端显式绑定本地端口 */
+     uint16_t local_bind_port = 0;
+ #if UDP_CLIENT_LOCAL_BIND_PORT > 0
+     local_bind_port = (uint16_t)UDP_CLIENT_LOCAL_BIND_PORT;
+ #elif UDP_CLIENT_BIND_TO_SERVER_PORT
+     local_bind_port = udp_info.port;
+ #else
+     local_bind_port = 0;
+ #endif
+     struct sockaddr_in local_addr;
+     memset(&local_addr, 0, sizeof(local_addr));
+     local_addr.sin_family = AF_INET;
+     local_addr.sin_addr.s_addr = lwip_htonl(INADDR_ANY);
+     local_addr.sin_port = lwip_htons(local_bind_port);
+ 
+     if (lwip_bind(fd, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) {
+         LN_MQTT_ERROR("Failed to bind UDP local port %u: %s\n", local_bind_port, strerror(errno));
+         lwip_close(fd);
+         return -1;
+     }
+     LN_MQTT_INFO("UDP local bind success, port=%u\n", local_bind_port);
+     return fd;
+ }
+ 
+ /**
+  * @brief UDP接收线程
+  */
+ static void ln_udp_recv_thread(void *arg)
+ {
+     (void)arg;
+     uint8_t recv_buffer[UDP_RECV_BUFFER_SIZE];
+     struct sockaddr_in server_addr;
+     socklen_t addr_len = sizeof(server_addr);
+     uint32_t last_recreate_tick = 0;
+     uint32_t recreate_interval_ticks = 0;
+ 
+     LN_MQTT_INFO("Receive thread started\n");
+ 
+     // 获取服务器地址信息
+     ln_mqtt_udp_info_t udp_info;
+     if (ln_mqtt_api_get_udp_info(&udp_info) != 0 || !udp_info.valid) {
+         LN_MQTT_WARN("UDP info not available in receive thread\n");
+         g_udp_thread_running = false;
+         return;
+     }
+ 
+     // 设置服务器地址
+     memset(&server_addr, 0, sizeof(server_addr));
+     server_addr.sin_family = AF_INET;
+     server_addr.sin_port = lwip_htons(udp_info.port);
+     server_addr.sin_addr.s_addr = inet_addr(udp_info.server);
+ 
+     if (UDP_RECV_RECREATE_INTERVAL_SEC > 0) {
+         recreate_interval_ticks = (uint32_t)osKernelGetTickFreq() * UDP_RECV_RECREATE_INTERVAL_SEC;
+         last_recreate_tick = osKernelGetTickCount();
+     }
+ 
+     while (g_udp_thread_running && g_udp_socket >= 0) {
+                 LN_MQTT_DEBUG("g_udp_thread_running %d g_udp_socket %d\r\n", g_udp_thread_running, g_udp_socket);
+         // 固定时间间隔到达则重新创建 socket
+         if (UDP_RECV_RECREATE_INTERVAL_SEC > 0 && recreate_interval_ticks > 0) {
+             uint32_t now = osKernelGetTickCount();
+             if ((now - last_recreate_tick) >= recreate_interval_ticks) {
+                 LN_MQTT_INFO("Recreate interval reached (%u s), recreating socket\n", (unsigned)UDP_RECV_RECREATE_INTERVAL_SEC);
+                 lwip_close(g_udp_socket);
+                 g_udp_socket = ln_udp_create_socket_fd_only();
+                 if (g_udp_socket < 0) {
+                     LN_MQTT_ERROR("Failed to recreate socket: %s\n", strerror(errno));
+                     break;
+                 }
+                 /* 重新获取 UDP 地址信息(服务端地址/端口可能变化) */
+                 if (ln_mqtt_api_get_udp_info(&udp_info) != 0 || !udp_info.valid) {
+                     LN_MQTT_WARN("UDP info not available after recreate, keep last address\n");
+                 } else {
+                     memset(&server_addr, 0, sizeof(server_addr));
+                     server_addr.sin_family = AF_INET;
+                     server_addr.sin_port = lwip_htons(udp_info.port);
+                     server_addr.sin_addr.s_addr = inet_addr(udp_info.server);
+                     addr_len = sizeof(server_addr);
+                 }
+                 last_recreate_tick = osKernelGetTickCount();
+             }
+         }
+ 
+         // 接收UDP数据(阻塞,超时1秒)
+         fd_set read_fds;
+         struct timeval timeout;
+ 
+         FD_ZERO(&read_fds);
+         FD_SET(g_udp_socket, &read_fds);
+         timeout.tv_sec = 1;
+         timeout.tv_usec = 0;
+ 
+         int select_ret = lwip_select(g_udp_socket + 1, &read_fds, NULL, NULL, &timeout);
+         if (select_ret > 0 && FD_ISSET(g_udp_socket, &read_fds)) {
+             // 有数据可读
+             ssize_t recv_len = lwip_recvfrom(g_udp_socket, recv_buffer, sizeof(recv_buffer) - 1, 0,
+                                              (struct sockaddr *)&server_addr, &addr_len);
+ 
+             if (recv_len > 0) {
+                 LN_MQTT_DEBUG("Received %d bytes\r\n",
+                        (int)recv_len
+                     );
+ 
+                 // 处理接收到的数据(解包、解密)
+                 ln_udp_process_received_data(recv_buffer, (uint16_t)recv_len);
+ 
+             } else if (recv_len == 0) {
+                 LN_MQTT_INFO("Connection closed by peer\n");
+                 break;
+             } else {
+                 if (errno != EAGAIN && errno != EWOULDBLOCK) {
+                     LN_MQTT_ERROR("Recvfrom error: %s\n", strerror(errno));
+                 }
+             }
+         } else if (select_ret < 0) {
+             LN_MQTT_DEBUG("select_ret %d\r\n", select_ret);
+             if (errno != EINTR) {
+                 LN_MQTT_ERROR("Select error: %s\n", strerror(errno));
+                 break;
+             }
+         }
+         // select_ret == 0 表示超时,继续循环
+     }
+ 
+     LN_MQTT_INFO("Receive thread exited\n");
+     g_udp_thread_running = false;
+ }
+ 
+ /**
+  * @brief 创建UDP套接字
+  * @return 0 成功,-1 失败
+  */
+ int ln_udp_create_socket(void)
+ {
+     // 获取UDP信息
+     ln_mqtt_udp_info_t udp_info;
+     if (ln_mqtt_api_get_udp_info(&udp_info) != 0) {
+         LN_MQTT_WARN("UDP info not available, cannot create socket\n");
+         return -1;
+     }
+     
+     if (!udp_info.valid) {
+         LN_MQTT_WARN("UDP info invalid, cannot create socket\n");
+         return -1;
+     }
+     
+     LN_MQTT_INFO("Creating UDP socket for server: %s:%d\n", udp_info.server, udp_info.port);
+ 
+     // 如果socket已存在,先关闭
+     if (g_udp_socket >= 0) {
+         LN_MQTT_DEBUG("Closing existing UDP socket\n");
+         ln_udp_close_socket();
+     }
+ 
+     // 创建UDP套接字(复用仅创建 fd 的逻辑)
+     g_udp_socket = ln_udp_create_socket_fd_only();
+     if (g_udp_socket < 0) {
+         LN_MQTT_ERROR("Failed to create UDP socket: %s\n", strerror(errno));
+         return -1;
+     }
+ 
+     LN_MQTT_INFO("UDP socket created successfully, fd=%d\n", g_udp_socket);
+     
+     // 创建接收线程
+     if (!g_udp_thread_running) {
+         app_task_definition_ext_t recv_task = {
+             .attr = {
+                 .name = "ln_udp_recv",
+                 .stack_size = 0x8000,
+                 .priority = osPriorityNormal
+             },
+             .func = ln_udp_recv_thread,
+             .task_handle = &g_udp_thread_handle,
+             .create_queue_handle = NULL,
+             .use_mem = USE_EXTEND_MEM,
+             .inst_args = NULL
+         };
+         
+         g_udp_thread_running = true;
+         errcode_t ret = uapi_create_osThread_custom_with_args(&recv_task);
+         if (ret != ERRCODE_SUCC) {
+             LN_MQTT_ERROR("Failed to create receive thread: %d\n", ret);
+             g_udp_thread_running = false;
+             lwip_close(g_udp_socket);
+             g_udp_socket = -1;
+             return -1;
+         }
+         LN_MQTT_INFO("Receive thread created successfully\n");
+     }
+     
+     return 0;
+ }
+ 
+ /**
+  * @brief 关闭UDP套接字
+  */
+ void ln_udp_close_socket(void)
+ {
+     // 完成并关闭 OGG 文件
+     if (g_ogg_initialized) {
+         ln_udp_ogg_complete_current_file();
+     }
+     
+     // 完成并关闭 Opus 调试文件
+ #if ENABLE_OPUS_RAW_DEBUG
+     if (g_opus_debug_file != NULL) {
+         ln_udp_opus_debug_complete_current_file();
+     }
+ #endif
+     
+     // 停止接收线程
+     if (g_udp_thread_running) {
+         LN_MQTT_INFO("Stopping receive thread\n");
+         g_udp_thread_running = false;
+         
+         // 等待线程退出(最多等待2秒)
+         uint32_t timeout = osKernelGetTickCount() + osKernelGetTickFreq() * 2;
+         while (g_udp_thread_handle != 0 && osKernelGetTickCount() < timeout) {
+             osDelay(10);
+         }
+         
+         if (g_udp_thread_handle != 0) {
+             LN_MQTT_WARN("Receive thread did not exit in time\n");
+         }
+         
+         g_udp_thread_handle = 0;
+     }
+     
+     // 关闭套接字
+     if (g_udp_socket >= 0) {
+         LN_MQTT_DEBUG("Closing UDP socket, fd=%d\n", g_udp_socket);
+         lwip_close(g_udp_socket);
+         g_udp_socket = -1;
+     }
+ }
+ 
+ /**
+  * @brief 获取UDP套接字文件描述符
+  * @return socket文件描述符,-1表示未创建
+  */
+ int ln_udp_get_socket(void)
+ {
+     return g_udp_socket;
+ }
+ 
+ /**
+  * @brief 检查UDP套接字是否已创建
+  * @return true 已创建,false 未创建
+  */
+ bool ln_udp_is_socket_created(void)
+ {
+     return (g_udp_socket >= 0);
+ }
+ 
+ // +------+-------+-------------+----------+------------+----------+-------------------+
+ // | Type | Flags | Payload Len |   SSRC   | Timestamp  | Sequence | Encrypted Payload |
+ // +------+-------+-------------+----------+------------+----------+-------------------+
+ // |1 byte|1 byte |   2 bytes   | 4 bytes  |  4 bytes   | 4 bytes  |      N bytes      |
+ // +------+-------+-------------+----------+------------+----------+-------------------+
+ 
+ 
+ // import struct
+ // from Crypto.Cipher import AES
+ // from Crypto.Util import Counter
+ 
+ // def encrypt_audio(key_hex: str, nonce_hex: str, timestamp: int, sequence: int, audio_data: bytes) -> bytes:
+ //     """
+ //     加密音频数据
+     
+ //     Args:
+ //         key_hex: 32字符十六进制密钥
+ //         nonce_hex: 32字符十六进制nonce
+ //         timestamp: 数据包时间戳
+ //         sequence: 数据包序列号
+ //         audio_data: 原始Opus音频数据
+     
+ //     Returns:
+ //         加密后的音频数据
+ //     """
+ //     key = bytes.fromhex(key_hex)
+ //     nonce = bytes.fromhex(nonce_hex)
+     
+ //     # 构建计数器初始值:结合timestamp和sequence
+ //     counter_init = int.from_bytes(nonce[:12] + struct.pack('>I', sequence), 'big')
+ //     ctr = Counter.new(128, initial_value=counter_init)
+     
+ //     cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
+ //     return cipher.encrypt(audio_data)
+ 
+ // def decrypt_audio(key_hex: str, nonce_hex: str, timestamp: int, sequence: int, encrypted_data: bytes) -> bytes:
+ //     """解密音频数据(与加密相同,CTR模式对称)"""
+ //     return encrypt_audio(key_hex, nonce_hex, timestamp, sequence, encrypted_data)
+ 
+ /**
+  * @brief 完整的数据打包函数(加密+打包)
+  * @param audio_data 原始音频数据
+  * @param audio_len 音频数据长度
+  * @param buffer 输出缓冲区(由调用者分配,大小至少为 UDP_PACKET_HEADER_SIZE + audio_len)
+  * @param buffer_size 缓冲区大小
+  * @param out_len 输出的实际数据长度
+  * @return 0 成功,-1 失败
+  * @note 函数内部自动处理type、flags、ssrc、timestamp、sequence等字段
+  */
+ int ln_udp_pack_audio_data(const uint8_t *audio_data, uint16_t audio_len,
+                            uint8_t *buffer, uint16_t buffer_size, uint16_t *out_len)
+ {
+     if (!audio_data || audio_len == 0 || !buffer || !out_len) {
+         LN_MQTT_WARN("Invalid parameters for pack audio data\n");
+         return -1;
+     }
+     
+     // 计算所需缓冲区大小
+     uint16_t total_len = UDP_PACKET_HEADER_SIZE + audio_len;
+     if (buffer_size < total_len) {
+         LN_MQTT_WARN("Buffer too small: need %d, have %d\n", total_len, buffer_size);
+         return -1;
+     }
+     
+     // 1. 获取UDP信息(用于加密)
+     ln_mqtt_udp_info_t udp_info;
+     if (ln_mqtt_api_get_udp_info(&udp_info) != 0 || !udp_info.valid) {
+         LN_MQTT_WARN("UDP info not available for encryption\n");
+         return -1;
+     }
+     // 01 00 00 00 1d 38 db 20 69 70 a0 da 00000000
+     // 2. 初始化SSRC(如果未初始化,从 nonce 字节数组的索引 4-8 提取,只取 4 个字节)
+     if (g_udp_ssrc == 0) {
+         if (ln_data_nonce_to_ssrc(udp_info.nonce, &g_udp_ssrc) == 0) {
+             LN_MQTT_DEBUG("SSRC initialized from nonce bytes[4-7], ssrc=0x%08X\n", g_udp_ssrc);
+         } else {
+             LN_MQTT_WARN("Failed to convert nonce to bytes, using default SSRC\n");
+             g_udp_ssrc = 0x12345678;
+         }
+     }
+     
+     // 3. 自动递增序列号
+     uint32_t sequence = g_udp_sequence++;
+     
+     // 4. 获取时间戳(使用系统时钟)
+     uint32_t timestamp = (uint32_t)osKernelGetTickCount();  // 使用系统tick作为时间戳
+     
+     // 5. 设置数据包类型和标志(使用默认值)
+     uint8_t type = 0x01;   // 默认类型
+     uint8_t flags = 0;  // 默认标志
+     
+     // 6. 加密音频数据(nonce在函数内部构造)
+     uint8_t *encrypted_payload = buffer + UDP_PACKET_HEADER_SIZE;
+     if (ln_udp_encrypt_audio(udp_info.key, udp_info.nonce, audio_len, timestamp, sequence,
+                              audio_data, audio_len, encrypted_payload) != 0) {
+         LN_MQTT_ERROR("Failed to encrypt audio data\n");
+         return -1;
+     }
+     
+     // 7. 打包数据包头部
+     uint8_t *ptr = buffer;
+     ptr[0] = type;
+     ptr[1] = flags;
+     *(uint16_t *)(ptr + 2) = lwip_htons(audio_len);  // payload_len(加密后的长度等于原始长度)
+     *(uint32_t *)(ptr + 4) = lwip_htonl(g_udp_ssrc);
+     *(uint32_t *)(ptr + 8) = lwip_htonl(timestamp);
+     *(uint32_t *)(ptr + 12) = lwip_htonl(sequence);
+     
+     // 加密的payload已经在步骤6中写入buffer + UDP_PACKET_HEADER_SIZE位置
+     
+     *out_len = total_len;
+ 
+     LN_MQTT_DEBUG("buffer: ");
+     for (int i = 0; i < 16; i++) {
+         LN_MQTT_DEBUG("%02x ", buffer[i]);
+     }
+     LN_MQTT_DEBUG("\n");
+     
+     LN_MQTT_DEBUG("Audio data packed: type=%d, flags=%d, ssrc=0x%08X, timestamp=%u, sequence=%u, payload_len=%d, total_len=%d\n",
+            type, flags, g_udp_ssrc, timestamp, sequence, audio_len, total_len);
+     
+     return 0;
+ }
+ 
+ /**
+  * @brief 创建音频发送队列
+  */
+ static void create_audio_send_queue(void)
+ {
+     if (g_audio_send_queue != 0xFFFFFFFF) {
+         // 队列已创建
+         return;
+     }
+     
+     if (osal_msg_queue_create("ln_udp_audio_send_queue", AUDIO_SEND_QUEUE_SIZE, &g_audio_send_queue,
+                               0, sizeof(audio_send_msg_t)) != ERRCODE_SUCC) {
+         LN_MQTT_ERROR("Failed to create audio send queue\n");
+         g_audio_send_queue = 0xFFFFFFFF;
+     } else {
+         LN_MQTT_INFO("Audio send queue created successfully\n");
+     }
+ }
+ 
+ /**
+  * @brief 发送单帧音频数据(打包/加密 + 发送)
+  * @note 复用原有 ln_udp_pack_audio_data 加密逻辑
+  * @return 0 成功,-1 失败
+  */
+ static int ln_udp_send_audio_frame(int sock, const uint8_t *audio_data, uint32_t audio_size)
+ {
+     if (audio_data == NULL || audio_size == 0 || audio_size > AUDIO_SEND_MAX_DATA_SIZE) {
+         return -1;
+     }
+ 
+     // 打包(加密+UDP头)
+     uint8_t send_buf[UDP_PACKET_HEADER_SIZE + AUDIO_SEND_MAX_DATA_SIZE];
+     uint16_t out_len = 0;
+     if (ln_udp_pack_audio_data(audio_data, (uint16_t)audio_size, send_buf,
+                                (uint16_t)sizeof(send_buf), &out_len) != 0) {
+         LN_MQTT_ERROR("Failed to pack audio frame\n");
+         return -1;
+     }
+ 
+     // 获取UDP信息(用于发送)
+     ln_mqtt_udp_info_t udp_info;
+     if (ln_mqtt_api_get_udp_info(&udp_info) != 0 || !udp_info.valid) {
+         LN_MQTT_WARN("UDP info not available, drop audio frame\n");
+         return -1;
+     }
+ 
+     // 发送到服务器
+     struct sockaddr_in server_addr;
+     memset(&server_addr, 0, sizeof(server_addr));
+     server_addr.sin_family = AF_INET;
+     server_addr.sin_port = lwip_htons(udp_info.port);
+     server_addr.sin_addr.s_addr = inet_addr(udp_info.server);
+ 
+     ssize_t sent = lwip_sendto(sock, send_buf, out_len, 0,
+                                (struct sockaddr *)&server_addr, sizeof(server_addr));
+     if (sent < 0) {
+         LN_MQTT_ERROR("UDP sendto failed: %s\n", strerror(errno));
+         return -1;
+     }
+ 
+     return 0;
+ }
+ 
+ /**
+  * @brief 音频数据发送线程
+  */
+ static void ln_udp_audio_send_thread(void *arg)
+ {
+     (void)arg;
+     audio_send_msg_t msg;
+     uint32_t msgsize = sizeof(audio_send_msg_t);
+     
+     LN_MQTT_INFO("Audio send thread started\n");
+     
+     while (g_audio_send_thread_running) {
+         // 从队列读取音频数据(阻塞等待)
+         int32_t ret = osal_msg_queue_read_copy(g_audio_send_queue, (void *)&msg, &msgsize,
+                                                OSAL_MSGQ_WAIT_FOREVER);
+         if (ret != OSAL_SUCCESS) {
+             LN_MQTT_ERROR("Failed to read from audio send queue: %d\n", ret);
+             continue;
+         }
+         
+         // 检查数据大小和数据指针
+         if (msg.size == 0 || msg.size > AUDIO_SEND_MAX_DATA_SIZE || msg.data == NULL) {
+             LN_MQTT_WARN("Invalid audio data: size=%u, data=%p\n", msg.size, msg.data);
+             // 释放内存(如果存在)
+             if (msg.data != NULL) {
+                 free(msg.data);
+             }
+             continue;
+         }
+         
+         // 确保 UDP socket 已创建
+         if (!ln_udp_is_socket_created()) {
+             LN_MQTT_WARN("UDP socket not ready, drop audio frame\n");
+             free(msg.data);
+             continue;
+         }
+         
+         int sock = ln_udp_get_socket();
+         if (sock < 0) {
+             LN_MQTT_WARN("UDP socket invalid, drop audio frame\n");
+             free(msg.data);
+             continue;
+         }
+         
+         const uint8_t *send_data = msg.data;
+         uint32_t send_size = msg.size;
+         if (msg.encoding == AUDIO_SEND_ENCODING_PCM) {
+             /* PCM:追加到缓存,按 60ms 切割编码,余量保留 */
+             if (g_pcm_send_cache == NULL) {
+                 LN_MQTT_WARN("PCM cache not allocated, drop %u bytes\n", msg.size);
+                 free(msg.data);
+                 continue;
+             }
+             while (g_pcm_send_cache_size + msg.size > g_pcm_send_cache_capacity &&
+                    g_pcm_send_cache_size >= AUDIO_SEND_PCM_FRAME_BYTES) {
+                 /* 缓存满时丢弃最旧一帧,避免持续溢出 */
+                 g_pcm_send_cache_size -= AUDIO_SEND_PCM_FRAME_BYTES;
+                 memmove(g_pcm_send_cache, g_pcm_send_cache + AUDIO_SEND_PCM_FRAME_BYTES, g_pcm_send_cache_size);
+             }
+             if (g_pcm_send_cache_size + msg.size > g_pcm_send_cache_capacity) {
+                 LN_MQTT_WARN("PCM cache full, drop %u bytes (cache=%u, cap=%u)\n", msg.size, g_pcm_send_cache_size, g_pcm_send_cache_capacity);
+                 free(msg.data);
+                 continue;
+             }
+             memcpy(g_pcm_send_cache + g_pcm_send_cache_size, msg.data, msg.size);
+             g_pcm_send_cache_size += msg.size;
+             free(msg.data);
+             msg.data = NULL;
+ 
+             /* 每次取 60ms 固定长度编码并发送,多余数据留在缓存 */
+             uint8_t opus_buf[1500];
+ 
+             while (g_pcm_send_cache_size >= AUDIO_SEND_PCM_FRAME_BYTES) {
+                 int encoded_len = 0;
+                 LN_MQTT_DEBUG("PCM send cache size: %d\n", g_pcm_send_cache_size);
+                 for (int i = 0; i < AUDIO_SEND_PCM_FRAME_BYTES; i++) {
+                     LN_MQTT_DEBUG("%02x ", g_pcm_send_cache[i]);
+                 }
+                 LN_MQTT_DEBUG("\n");
+                 
+                 if (opus_pcm_to_frame_encode_packet(
+                         (const short *)g_pcm_send_cache, AUDIO_SEND_PCM_FRAME_SAMPLES,
+                         AUDIO_SEND_PCM_SAMPLE_RATE, AUDIO_SEND_PCM_CHANNELS,
+                         AUDIO_SEND_PCM_BITRATE, AUDIO_SEND_PCM_APPLICATION,
+                         opus_buf, (int)sizeof(opus_buf), &encoded_len) != 0) {
+                     LN_MQTT_WARN("PCM to Opus encode failed, drop 60ms frame\n");
+                     break;
+                 }
+                 LN_MQTT_DEBUG("Opus encoded len: %d\n", encoded_len); 
+                 LN_MQTT_DEBUG("Opus encoded data: ");
+                 for (int i = 0; i < encoded_len; i++) {
+                     LN_MQTT_DEBUG("%02x ", opus_buf[i]);
+                 }
+                 LN_MQTT_DEBUG("\n");
+                 if (ln_udp_send_audio_frame(sock, opus_buf, (uint32_t)encoded_len) != 0) {
+                     break;
+                 }
+                 /* 移除已编码的一帧,保留余量 */
+                 g_pcm_send_cache_size -= AUDIO_SEND_PCM_FRAME_BYTES;
+                 memmove(g_pcm_send_cache, g_pcm_send_cache + AUDIO_SEND_PCM_FRAME_BYTES, g_pcm_send_cache_size);
+             }
+             continue;
+         }
+         
+         (void)send_data;
+         if (ln_udp_send_audio_frame(sock, send_data, send_size) != 0) {
+             free(msg.data);
+             continue;
+         }
+         
+         // 释放音频数据内存
+         free(msg.data);
+         msg.data = NULL;
+     }
+     
+     LN_MQTT_INFO("Audio send thread exited\n");
+     g_audio_send_thread_running = false;
+ }
+ 
+ /**
+  * @brief 初始化音频发送队列和线程
+  * @return 0 成功,-1 失败
+  */
+ int ln_udp_audio_send_init(void)
+ {
+     // 动态分配 PCM 发送缓存(若未分配或需扩容可在此统一申请)
+     if (g_pcm_send_cache == NULL) {
+         g_pcm_send_cache = (uint8_t *)malloc(AUDIO_SEND_PCM_CACHE_ALLOC_SIZE);
+         if (g_pcm_send_cache == NULL) {
+             LN_MQTT_ERROR("Failed to allocate PCM send cache (%u bytes)\n", (unsigned)AUDIO_SEND_PCM_CACHE_ALLOC_SIZE);
+             return -1;
+         }
+         g_pcm_send_cache_capacity = AUDIO_SEND_PCM_CACHE_ALLOC_SIZE;
+         g_pcm_send_cache_size = 0;
+         LN_MQTT_DEBUG("PCM send cache allocated: %u bytes\n", g_pcm_send_cache_capacity);
+     }
+ 
+     // 创建消息队列
+     create_audio_send_queue();
+     if (g_audio_send_queue == 0xFFFFFFFF) {
+         LN_MQTT_ERROR("Failed to create audio send queue\n");
+         if (g_pcm_send_cache != NULL) {
+             free(g_pcm_send_cache);
+             g_pcm_send_cache = NULL;
+             g_pcm_send_cache_capacity = 0;
+         }
+         return -1;
+     }
+     
+     // 创建发送线程(如果尚未创建)
+     if (!g_audio_send_thread_running) {
+         app_task_definition_ext_t send_task = {
+             .attr = {
+                 .name = "ln_udp_audio_send",
+                 .stack_size = 0x4000,
+                 .priority = osPriorityNormal
+             },
+             .func = ln_udp_audio_send_thread,
+             .task_handle = &g_audio_send_thread_handle,
+             .create_queue_handle = NULL,
+             .use_mem = USE_EXTEND_MEM,
+             .inst_args = NULL
+         };
+         
+         g_audio_send_thread_running = true;
+         errcode_t ret = uapi_create_osThread_custom_with_args(&send_task);
+         if (ret != ERRCODE_SUCC) {
+             LN_MQTT_ERROR("Failed to create audio send thread: %d\n", ret);
+             g_audio_send_thread_running = false;
+             return -1;
+         }
+         LN_MQTT_INFO("Audio send thread created successfully\n");
+     }
+     
+     return 0;
+ }
+ 
+ /**
+  * @brief 停止音频发送线程
+  */
+ void ln_udp_audio_send_deinit(void)
+ {
+     g_pcm_send_cache_size = 0;
+     g_pcm_send_cache_capacity = 0;
+     if (g_pcm_send_cache != NULL) {
+         free(g_pcm_send_cache);
+         g_pcm_send_cache = NULL;
+     }
+     // 停止发送线程
+     if (g_audio_send_thread_running) {
+         LN_MQTT_INFO("Stopping audio send thread\n");
+         g_audio_send_thread_running = false;
+         
+         // 发送一个空消息唤醒线程(如果队列为空)
+         // 注意:data 为 NULL,size 为 0,线程会检查并释放
+         audio_send_msg_t wake_msg = {0};
+         wake_msg.data = NULL;
+         wake_msg.size = 0;
+         wake_msg.encoding = AUDIO_SEND_ENCODING_OPUS;
+         osal_msg_queue_write_copy(g_audio_send_queue, (void *)&wake_msg, sizeof(audio_send_msg_t), 0);
+         
+         // 等待线程退出(最多等待2秒)
+         uint32_t timeout = osKernelGetTickCount() + osKernelGetTickFreq() * 2;
+         while (g_audio_send_thread_handle != 0 && osKernelGetTickCount() < timeout) {
+             osDelay(10);
+         }
+         
+         if (g_audio_send_thread_handle != 0) {
+             LN_MQTT_WARN("Audio send thread did not exit in time\n");
+         }
+         
+         g_audio_send_thread_handle = 0;
+     }
+     
+     // 清空队列中剩余的消息(释放内存)
+     if (g_audio_send_queue != 0xFFFFFFFF) {
+         audio_send_msg_t msg;
+         uint32_t msgsize = sizeof(audio_send_msg_t);
+         while (osal_msg_queue_read_copy(g_audio_send_queue, (void *)&msg, &msgsize, 0) == OSAL_SUCCESS) {
+             if (msg.data != NULL) {
+                 free(msg.data);
+             }
+             msgsize = sizeof(audio_send_msg_t);
+         }
+     }
+     
+     // 删除消息队列
+     if (g_audio_send_queue != 0xFFFFFFFF) {
+         osal_msg_queue_delete(g_audio_send_queue);
+         g_audio_send_queue = 0xFFFFFFFF;
+         LN_MQTT_DEBUG("Audio send queue deleted\n");
+     }
+ }
+ 
+ /**
+  * @brief 将音频数据放入发送队列(供录音回调函数调用)
+  * @param data 音频数据指针
+  * @param size 音频数据大小
+  * @return 0 成功,-1 失败
+  * @note 函数内部会动态分配内存并复制数据,发送线程负责释放内存
+  * @param encoding AUDIO_SEND_ENCODING_OPUS(0)=已是 Opus;AUDIO_SEND_ENCODING_PCM(1)=PCM,发送前编码为 Opus
+  */
+ int ln_udp_audio_send_enqueue(const uint8_t *data, uint32_t size, uint8_t encoding)
+ {
+     if (!data || size == 0 || size > AUDIO_SEND_MAX_DATA_SIZE) {
+         LN_MQTT_WARN("Invalid parameters for audio send enqueue\n");
+         return -1;
+     }
+     
+     if (g_audio_send_queue == 0xFFFFFFFF) {
+         LN_MQTT_WARN("Audio send queue not initialized\n");
+         return -1;
+     }
+     
+     // 动态分配内存并复制数据
+     uint8_t *data_copy = (uint8_t *)malloc(size);
+     if (data_copy == NULL) {
+         LN_MQTT_ERROR("Failed to allocate memory for audio data\n");
+         return -1;
+     }
+     memcpy(data_copy, data, size);
+     
+     // 构造消息
+     audio_send_msg_t msg;
+     msg.data = data_copy;
+     msg.size = size;
+     msg.encoding = (encoding != AUDIO_SEND_ENCODING_PCM) ? AUDIO_SEND_ENCODING_OPUS : AUDIO_SEND_ENCODING_PCM;
+     
+     // 写入队列(非阻塞,如果队列满则丢弃并释放内存)
+     int32_t ret = osal_msg_queue_write_copy(g_audio_send_queue, (void *)&msg, sizeof(audio_send_msg_t), 0);
+     if (ret != OSAL_SUCCESS) {
+         LN_MQTT_WARN("Failed to enqueue audio data: %d (queue may be full)\n", ret);
+         free(data_copy);  // 队列满时释放内存
+         return -1;
+     }
+     
+     return 0;
+ }
\ No newline at end of file
diff --git a/code/sdk/zhihui/leiniao/imsl_ln/LN_MQTT/ln_data_decode.c b/code/sdk/zhihui/leiniao/imsl_ln/LN_MQTT/ln_data_decode.c
new file mode 100644 (file)
index 0000000..876fdfd
--- /dev/null
@@ -0,0 +1,127 @@
+/*\r
+ * Copyright (c) Zhihui Technologies Co., Ltd. 2025-2025. All rights reserved.\r
+ */\r
+\r
+#include "ln_data_decode.h"\r
+#include "ln_mqtt_api.h"\r
+#include "lwip/inet.h"\r
+#include "mbedtls/cipher.h"\r
+#include "mbedtls/aes.h"\r
+#include <stdlib.h>\r
+#include <string.h>\r
+\r
+/**\r
+ * @brief 将十六进制字符串转换为字节数组\r
+ * @param hex_str 十六进制字符串(如 "01020304")\r
+ * @param output 输出的字节数组\r
+ * @param output_size 输出缓冲区大小\r
+ * @return 转换的字节数,-1表示失败\r
+ */\r
+static int hex_string_to_bytes(const char *hex_str, uint8_t *output, size_t output_size)\r
+{\r
+    if (!hex_str || !output) {\r
+        return -1;\r
+    }\r
+\r
+    size_t hex_len = strlen(hex_str);\r
+    if (hex_len % 2 != 0 || hex_len / 2 > output_size) {\r
+        LN_MQTT_WARN("Invalid hex string length: %zu\n", hex_len);\r
+        return -1;\r
+    }\r
+\r
+    for (size_t i = 0; i < hex_len; i += 2) {\r
+        char hex_byte[3] = {hex_str[i], hex_str[i + 1], '\0'};\r
+        char *endptr;\r
+        unsigned long val = strtoul(hex_byte, &endptr, 16);\r
+        if (*endptr != '\0' || val > 255) {\r
+            LN_MQTT_WARN("Invalid hex character at position %zu\n", i);\r
+            return -1;\r
+        }\r
+        output[i / 2] = (uint8_t)val;\r
+    }\r
+\r
+    return (int)(hex_len / 2);\r
+}\r
+\r
+int ln_data_nonce_to_ssrc(const char *nonce_hex, uint32_t *out_ssrc)\r
+{\r
+    if (!nonce_hex || !out_ssrc) {\r
+        return -1;\r
+    }\r
+\r
+    uint8_t nonce_bytes[16];\r
+    if (hex_string_to_bytes(nonce_hex, nonce_bytes, sizeof(nonce_bytes)) != 16) {\r
+        return -1;\r
+    }\r
+\r
+    *out_ssrc = ((uint32_t)nonce_bytes[4] << 24) |\r
+                ((uint32_t)nonce_bytes[5] << 16) |\r
+                ((uint32_t)nonce_bytes[6] << 8) |\r
+                ((uint32_t)nonce_bytes[7]);\r
+    return 0;\r
+}\r
+\r
+int ln_udp_encrypt_audio(const char *key_hex, const char *base_nonce_hex,\r
+                         uint16_t payload_len, uint32_t timestamp, uint32_t sequence,\r
+                         const uint8_t *audio_data, uint16_t audio_len, uint8_t *encrypted_data)\r
+{\r
+    if (!key_hex || !base_nonce_hex || !audio_data || !encrypted_data || audio_len == 0) {\r
+        LN_MQTT_WARN("Invalid parameters for encryption\n");\r
+        return -1;\r
+    }\r
+\r
+    uint8_t key[16];\r
+    if (hex_string_to_bytes(key_hex, key, sizeof(key)) != 16) {\r
+        LN_MQTT_ERROR("Failed to convert key from hex\n");\r
+        return -1;\r
+    }\r
+\r
+    uint8_t base_nonce[16];\r
+    if (hex_string_to_bytes(base_nonce_hex, base_nonce, sizeof(base_nonce)) != 16) {\r
+        LN_MQTT_ERROR("Failed to convert base nonce from hex\n");\r
+        return -1;\r
+    }\r
+\r
+    uint8_t nonce_counter[16];\r
+    memcpy(nonce_counter, base_nonce, 16);\r
+    *(uint16_t *)&nonce_counter[2] = lwip_htons(payload_len);\r
+    *(uint32_t *)&nonce_counter[8] = lwip_htonl(timestamp);\r
+    *(uint32_t *)&nonce_counter[12] = lwip_htonl(sequence);\r
+\r
+    printf("nonce_counter: ");\r
+    for (int i = 0; i < 16; i++) {\r
+        printf("%02x ", nonce_counter[i]);\r
+    }\r
+    printf("\n");\r
+\r
+    mbedtls_aes_context aes_ctx;\r
+    mbedtls_aes_init(&aes_ctx);\r
+\r
+    if (mbedtls_aes_setkey_enc(&aes_ctx, key, 128) != 0) {\r
+        LN_MQTT_ERROR("Failed to set AES key\n");\r
+        mbedtls_aes_free(&aes_ctx);\r
+        return -1;\r
+    }\r
+\r
+    size_t nc_off = 0;\r
+    uint8_t stream_block[16] = {0};\r
+\r
+    if (mbedtls_aes_crypt_ctr(&aes_ctx, audio_len, &nc_off, nonce_counter, stream_block,\r
+                              audio_data, encrypted_data) != 0) {\r
+        LN_MQTT_ERROR("Failed to encrypt audio data with AES-CTR\n");\r
+        mbedtls_aes_free(&aes_ctx);\r
+        return -1;\r
+    }\r
+\r
+    mbedtls_aes_free(&aes_ctx);\r
+    return 0;\r
+}\r
+\r
+int ln_udp_decrypt_audio(const char *key_hex, const char *base_nonce_hex,\r
+                         uint16_t payload_len, uint32_t timestamp, uint32_t sequence,\r
+                         const uint8_t *encrypted_data, uint16_t encrypted_len, uint8_t *decrypted_data)\r
+{\r
+    return ln_udp_encrypt_audio(key_hex, base_nonce_hex, payload_len, timestamp, sequence,\r
+                                encrypted_data, encrypted_len, decrypted_data);\r
+}\r
+\r
diff --git a/code/sdk/zhihui/leiniao/imsl_ln/LN_MQTT/ln_data_decode.h b/code/sdk/zhihui/leiniao/imsl_ln/LN_MQTT/ln_data_decode.h
new file mode 100644 (file)
index 0000000..b4600e3
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef LN_DATA_DECODE_H
+#define LN_DATA_DECODE_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief 从 nonce 十六进制字符串提取 SSRC(取字节 4~7,大端)
+ * @param nonce_hex 32 字符十六进制字符串
+ * @param out_ssrc 输出 SSRC
+ * @return 0 成功,-1 失败
+ */
+int ln_data_nonce_to_ssrc(const char *nonce_hex, uint32_t *out_ssrc);
+
+/**
+ * @brief 加密音频数据(AES-128-CTR 模式)
+ */
+int ln_udp_encrypt_audio(const char *key_hex, const char *base_nonce_hex,
+                         uint16_t payload_len, uint32_t timestamp, uint32_t sequence,
+                         const uint8_t *audio_data, uint16_t audio_len, uint8_t *encrypted_data);
+
+/**
+ * @brief 解密音频数据(AES-128-CTR 模式,与加密相同)
+ */
+int ln_udp_decrypt_audio(const char *key_hex, const char *base_nonce_hex,
+                         uint16_t payload_len, uint32_t timestamp, uint32_t sequence,
+                         const uint8_t *encrypted_data, uint16_t encrypted_len, uint8_t *decrypted_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LN_DATA_DECODE_H */
+
index 61db245..2d4b55d 100644 (file)
 #include "lwip/inet.h"
 #include "lwip/netdb.h"
 #include <time.h>
+#include "cmsis_os2.h"
 #if defined(ENABLE_VAD_SERVER) && ENABLE_VAD_SERVER
 #include "vad_server.h"
 #include "errcode.h"
-#include "cmsis_os2.h"
 #endif
 
 #define LN_MQTT_SEND_TOPIC "device-server-enhanced"
@@ -37,7 +37,7 @@ void ln_mqtt_set_log_level(uint8_t level)
 
 /* 测试性录音数据保存到文件:1=启用,0=禁用。启用后每次开始录音创建新文件,停止时关闭。 */
 #ifndef LN_MQTT_RECORD_SAVE_TO_FILE_TEST
-#define LN_MQTT_RECORD_SAVE_TO_FILE_TEST 0
+#define LN_MQTT_RECORD_SAVE_TO_FILE_TEST 1
 #endif
 
 /* MQTT API 全局状态 */
@@ -57,8 +57,18 @@ static volatile bool g_mqtt_audio_initialized = false;
 /* 消息处理回调函数(参考 ln_ws_start,可为 NULL) */
 static ws_message_callback_t_ln g_mqtt_message_callback = NULL;
 
+/* 保活:上次有数据交互的 tick(收到或发送 MQTT 消息时更新) */
+#define LN_MQTT_KEEPALIVE_INTERVAL_MS  (30 * 1000)
+static uint32_t g_last_mqtt_activity_tick = 0;
+static osTimerId_t g_keepalive_timer_id = NULL;
+
 int ln_mqtt_api_send_hello(void);
 
+#if USE_DUAL_STREAM_INTERFACE
+static int ln_mqtt_send_listen_message_only(bool state_start);
+static void ln_mqtt_keepalive_timer_cb(void *arg);
+#endif
+
 // --- MQTT 录音(来自 ai_media_service_mqtt.cpp,无对应头文件时在此声明) ---
 typedef void (*MqttRecordDataCallback)(const uint8_t* data, uint32_t size, void* userData);
 typedef struct {
@@ -72,6 +82,7 @@ extern int32_t MQTT_StopRecord(void);
 
 // --- LN UDP(定义在 ln_UPD.c)---
 extern int ln_udp_create_socket(void);
+extern void ln_udp_close_socket(void);
 extern bool ln_udp_is_socket_created(void);
 extern int ln_udp_get_socket(void);
 extern int ln_udp_pack_audio_data(const uint8_t *audio_data, uint16_t audio_len,
@@ -80,6 +91,39 @@ extern int ln_udp_audio_send_init(void);
 extern void ln_udp_audio_send_deinit(void);
 extern int ln_udp_audio_send_enqueue(const uint8_t *data, uint32_t size, uint8_t encoding);
 
+// --- LN TCP(定义在 ln_TCP.c)---
+extern int ln_tcp_create_socket(void);
+extern void ln_tcp_close_socket(void);
+extern bool ln_tcp_is_socket_created(void);
+extern int ln_tcp_get_socket(void);
+extern int ln_tcp_audio_send_init(void);
+extern void ln_tcp_audio_send_deinit(void);
+extern int ln_tcp_audio_send_enqueue(const uint8_t *data, uint32_t size, uint8_t encoding);
+
+/* 传输层选择:默认启用 TCP,可通过编译宏切回 UDP */
+#ifndef USE_LN_TCP_SERVICE
+#define USE_LN_TCP_SERVICE 0
+#endif
+
+/* UDP 只创建一次模式:1=开启时断链不关闭 UDP 套接字,仅在 ln_mqtt_api_stop_service 退出时关闭;0=断链即关闭。仅在使用 UDP 传输时生效 */
+#ifndef LN_MQTT_UDP_SOCKET_CREATE_ONCE
+#define LN_MQTT_UDP_SOCKET_CREATE_ONCE 1
+#endif
+
+#if USE_LN_TCP_SERVICE
+#define LN_TRANSPORT_CREATE_SOCKET       ln_tcp_create_socket
+#define LN_TRANSPORT_CLOSE_SOCKET        ln_tcp_close_socket
+#define LN_TRANSPORT_AUDIO_SEND_INIT     ln_tcp_audio_send_init
+#define LN_TRANSPORT_AUDIO_SEND_DEINIT   ln_tcp_audio_send_deinit
+#define LN_TRANSPORT_AUDIO_SEND_ENQUEUE  ln_tcp_audio_send_enqueue
+#else
+#define LN_TRANSPORT_CREATE_SOCKET       ln_udp_create_socket
+#define LN_TRANSPORT_CLOSE_SOCKET        ln_udp_close_socket
+#define LN_TRANSPORT_AUDIO_SEND_INIT     ln_udp_audio_send_init
+#define LN_TRANSPORT_AUDIO_SEND_DEINIT   ln_udp_audio_send_deinit
+#define LN_TRANSPORT_AUDIO_SEND_ENQUEUE  ln_udp_audio_send_enqueue
+#endif
+
 #define LN_UDP_PACKET_HEADER_SIZE 16
 #define LN_MQTT_RECORD_MAX_FRAME   0x800
 #define LN_MQTT_RECORD_BATCH_SIZE  2  // 每5包数据合并后入队
@@ -429,7 +473,7 @@ static void ln_mqtt_record_data_cb(const uint8_t* data, uint32_t size, void* use
                g_record_cache.current_size, size, g_record_cache.buffer_size);
         // 如果缓存已满,强制刷新当前缓存
         if (g_record_cache.current_size > 0) {
-            if (ln_udp_audio_send_enqueue(g_record_cache.buffer, g_record_cache.current_size, AUDIO_SEND_ENCODING_OPUS) != 0) {
+            if (LN_TRANSPORT_AUDIO_SEND_ENQUEUE(g_record_cache.buffer, g_record_cache.current_size, AUDIO_SEND_ENCODING_OPUS) != 0) {
                 LN_MQTT_ERROR("Failed to enqueue cached audio data\n");
             }
             g_record_cache.current_size = 0;
@@ -444,7 +488,7 @@ static void ln_mqtt_record_data_cb(const uint8_t* data, uint32_t size, void* use
 
     // 如果累积到5包,将缓存数据入队
     if (g_record_cache.packet_count >= LN_MQTT_RECORD_BATCH_SIZE) {
-        if (ln_udp_audio_send_enqueue(g_record_cache.buffer, g_record_cache.current_size, AUDIO_SEND_ENCODING_OPUS) != 0)
+        if (LN_TRANSPORT_AUDIO_SEND_ENQUEUE(g_record_cache.buffer, g_record_cache.current_size, AUDIO_SEND_ENCODING_OPUS) != 0)
          {
             LN_MQTT_ERROR("Failed to enqueue batched audio data, drop frame\n");
         } else {
@@ -456,7 +500,7 @@ static void ln_mqtt_record_data_cb(const uint8_t* data, uint32_t size, void* use
         g_record_cache.packet_count = 0;
     }
 #else
-    if (ln_udp_audio_send_enqueue(data, size, AUDIO_SEND_ENCODING_OPUS) != 0) {
+    if (LN_TRANSPORT_AUDIO_SEND_ENQUEUE(data, size, AUDIO_SEND_ENCODING_OPUS) != 0) {
         LN_MQTT_ERROR("Failed to enqueue audio data, drop frame\n");
     } else {
         LN_MQTT_DEBUG("Enqueued %u bytes of audio data\n", size);
@@ -524,10 +568,16 @@ static int ln_mqtt_parse_message_by_type(const uint8_t *data, uint16_t len)
     int ret = -1;
     if (strcmp(type_value, "hello") == 0) {
         ret = ln_mqtt_handle_hello_message(json_str);
-        if(ret==0)
-        {
-            extern int ln_udp_create_socket(void);
-            ln_udp_create_socket();
+        if (ret == 0) {
+#if !USE_LN_TCP_SERVICE && LN_MQTT_UDP_SOCKET_CREATE_ONCE
+            if (!ln_udp_is_socket_created()) {
+                LN_TRANSPORT_CREATE_SOCKET();
+            } else {
+                LN_MQTT_DEBUG("UDP socket create-once: already created, skip\n");
+            }
+#else
+            LN_TRANSPORT_CREATE_SOCKET();
+#endif
         }
     } else {
         LN_MQTT_DEBUG("Unknown message type: %s, skipping\n", type_value);
@@ -674,11 +724,15 @@ static int ln_mqtt_handle_hello_message(const char *data)
 int ln_mqtt_api_auto_subscribe_p2p(void)
 {
     char client_id[256] = {0};
+    
     if (uapi_mqtt_get_client_id(client_id, sizeof(client_id)) != 0) {
         LN_MQTT_WARN("client_id not available for auto-subscribe\n");
         return -1;
     }
     
+    printf("ln_mqtt_api_auto_subscribe_p2p client_id %s\n", client_id);
+    
+    
     char p2p_topic[256] = {0};
     int ret = snprintf(p2p_topic, sizeof(p2p_topic), "devices/p2p/%s", client_id);
     if (ret <= 0 || ret >= (int)sizeof(p2p_topic)) {
@@ -720,8 +774,14 @@ static void ln_mqtt_internal_connect_cb(void *arg, int status)
         if (g_mqtt_message_callback != NULL) {
             g_mqtt_message_callback(NULL, WS_CMD_DISCONNECTED);
         }
-        extern void ln_udp_close_socket(void);
-        ln_udp_close_socket();
+#if !USE_LN_TCP_SERVICE && LN_MQTT_UDP_SOCKET_CREATE_ONCE
+        /* UDP 只创建一次模式:断链不关闭套接字,仅在 stop_service 时关闭 */
+        LN_MQTT_DEBUG("UDP socket create-once: disconnect, keep socket\n");
+#else
+        LN_TRANSPORT_CLOSE_SOCKET();
+#endif
+        set_udp_sequence(0);
+        set_udp_ssrc(0);
     }
 }
 
@@ -732,6 +792,8 @@ static void ln_mqtt_internal_message_cb(void *arg, const char *topic, const uint
 {
     (void)arg;
     
+    g_last_mqtt_activity_tick = osKernelGetTickCount();
+    
     LN_MQTT_DEBUG("Message received - Topic: %s, Length: %d\n", topic ? topic : "NULL", len);
     
     // 统一解析消息,根据type字段分发处理
@@ -825,7 +887,7 @@ int ln_mqtt_api_start_service(ws_message_callback_t_ln callback)
     }
     
     // 初始化音频发送队列和线程
-    if (ln_udp_audio_send_init() != 0) {
+    if (LN_TRANSPORT_AUDIO_SEND_INIT() != 0) {
         LN_MQTT_WARN("Failed to init audio send queue/thread\n");
         // 不影响服务启动,继续执行
     }
@@ -835,6 +897,9 @@ int ln_mqtt_api_start_service(ws_message_callback_t_ln callback)
 
     g_ln_mqtt_service_started = true;
     LN_MQTT_INFO("MQTT service started successfully\n");
+
+    ret = uapi_pm_request_mode(PM_ID_SPI, PM_MODE_PERFORMACE);
+    printf("uapi_pm_request_mode ret : %X\n",ret);
     
     return 0;
 }
@@ -849,16 +914,16 @@ void ln_mqtt_api_stop_service(void)
     }
     
     LN_MQTT_INFO("Stopping MQTT service\n");
-    
+
     // 停止音频发送线程
-    ln_udp_audio_send_deinit();
+    LN_TRANSPORT_AUDIO_SEND_DEINIT();
     
     // 释放缓存缓冲区(如果存在)
     if (g_record_cache.buffer != NULL) {
         // 如果有剩余数据,先刷新
         if (g_record_cache.current_size > 0) {
             LN_MQTT_DEBUG("Flushing remaining cached audio data before service stop\n");
-            if (ln_udp_audio_send_enqueue(g_record_cache.buffer, g_record_cache.current_size, AUDIO_SEND_ENCODING_OPUS) != 0) {
+            if (LN_TRANSPORT_AUDIO_SEND_ENQUEUE(g_record_cache.buffer, g_record_cache.current_size, AUDIO_SEND_ENCODING_OPUS) != 0) {
                 LN_MQTT_ERROR("Failed to enqueue remaining cached audio data\n");
             }
         }
@@ -881,8 +946,19 @@ void ln_mqtt_api_stop_service(void)
     // 停止MQTT主线程(会自动断开连接和清理资源)
     uapi_mqtt_main_stop();
     
+#if !USE_LN_TCP_SERVICE && LN_MQTT_UDP_SOCKET_CREATE_ONCE
+    /* UDP 只创建一次模式:退出时关闭套接字 */
+    LN_TRANSPORT_CLOSE_SOCKET();
+#endif
+
+    int ret = uapi_pm_request_mode(PM_ID_SPI, PM_MODE_NORMAL);
+    printf("uapi_pm_request_mode ret : %X\n",ret);
+
     g_ln_mqtt_service_started = false;
-    
+
+    set_udp_sequence(0);
+    set_udp_ssrc(0);
+
     LN_MQTT_INFO("MQTT service stopped\n");
 }
 
@@ -1075,6 +1151,7 @@ int ln_mqtt_api_send_hello(void)
     
     if (ret == 0) {
         LN_MQTT_INFO("Hello message queued for sending successfully\n");
+        g_last_mqtt_activity_tick = osKernelGetTickCount();
         return 0;
     } else {
         LN_MQTT_ERROR("Failed to queue hello message: %d\n", ret);
@@ -1092,6 +1169,30 @@ int ln_mqtt_api_send_hello(void)
  *   "mode": "auto"
  * }
  */
+static int ln_mqtt_send_listen_message_only(bool state_start)
+{
+    if (!uapi_mqtt_is_connected() || !g_ln_mqtt_session_id_valid || g_ln_mqtt_session_id[0] == '\0') {
+        return -1;
+    }
+    cJSON *root = cJSON_CreateObject();
+    if (!root) return -1;
+    cJSON_AddStringToObject(root, "session_id", g_ln_mqtt_session_id);
+    cJSON_AddStringToObject(root, "type", "listen");
+    cJSON_AddStringToObject(root, "state", state_start ? "start" : "stop");
+    cJSON_AddStringToObject(root, "mode", "auto");
+    char *json_str = cJSON_Print(root);
+    cJSON_Delete(root);
+    if (!json_str) return -1;
+    char publish_topic[128] = {0};
+    const char *use_topic = (uapi_mqtt_get_publish_topic(publish_topic, sizeof(publish_topic)) == 0)
+        ? publish_topic : LN_MQTT_SEND_TOPIC;
+    uint16_t msg_len = (uint16_t)strlen(json_str);
+    int ret = uapi_mqtt_main_send(use_topic, json_str, msg_len, 1, 0);
+    cJSON_free(json_str);
+    return ret;
+}
+
+
 int ln_mqtt_api_send_listen_state(bool start)
 {
     if (!uapi_mqtt_is_connected()) {
@@ -1136,8 +1237,8 @@ int ln_mqtt_api_send_listen_state(bool start)
         use_topic = LN_MQTT_SEND_TOPIC;  // 使用默认主题
     }
 
-    LN_MQTT_DEBUG("Publishing listen message to topic: %s\n", use_topic);
-    LN_MQTT_DEBUG("Message: %s\n", json_str);
+    LN_MQTT_ERROR("Publishing listen message to topic: %s\n", use_topic);
+    LN_MQTT_ERROR("Message: %s\n", json_str);
 
     uint16_t msg_len = (uint16_t)strlen(json_str);
     int ret = uapi_mqtt_main_send(use_topic, json_str, msg_len, 1, 0);
@@ -1151,6 +1252,7 @@ int ln_mqtt_api_send_listen_state(bool start)
     }
 
     LN_MQTT_INFO("Listen message queued for sending successfully\n");
+    g_last_mqtt_activity_tick = osKernelGetTickCount();
 
     // 发送完成后控制录音:start->开始录音,stop->停止录音
     if (start) {
@@ -1193,7 +1295,7 @@ int ln_mqtt_api_send_listen_state(bool start)
         if (g_record_cache.buffer != NULL && g_record_cache.current_size > 0) {
             LN_MQTT_DEBUG("Flushing remaining cached audio data: %u bytes, %u packets\n",
                    g_record_cache.current_size, g_record_cache.packet_count);
-            if (ln_udp_audio_send_enqueue(g_record_cache.buffer, g_record_cache.current_size, AUDIO_SEND_ENCODING_OPUS) != 0) {
+            if (LN_TRANSPORT_AUDIO_SEND_ENQUEUE(g_record_cache.buffer, g_record_cache.current_size, AUDIO_SEND_ENCODING_OPUS) != 0) {
                 LN_MQTT_ERROR("Failed to enqueue remaining cached audio data\n");
             }
             g_record_cache.current_size = 0;
index adaa199..56317fa 100644 (file)
@@ -147,6 +147,61 @@ void ln_udp_audio_send_deinit(void);
  */
 int ln_udp_audio_send_enqueue(const uint8_t *data, uint32_t size, uint8_t encoding);
 
+/**
+ * @brief 创建 TCP 套接字并连接到服务端(在 ln_TCP.c 中实现)
+ * @return 0 成功,-1 失败
+ */
+int ln_tcp_create_socket(void);
+
+/**
+ * @brief 关闭 TCP 套接字
+ */
+void ln_tcp_close_socket(void);
+
+/**
+ * @brief 获取 TCP 套接字文件描述符
+ * @return socket 文件描述符,-1 表示未创建
+ */
+int ln_tcp_get_socket(void);
+
+/**
+ * @brief 检查 TCP 套接字是否已创建
+ * @return true 已创建,false 未创建
+ */
+bool ln_tcp_is_socket_created(void);
+
+/**
+ * @brief 初始化 TCP 音频发送队列和线程(在 ln_TCP.c 中实现)
+ * @return 0 成功,-1 失败
+ */
+int ln_tcp_audio_send_init(void);
+
+/**
+ * @brief 停止 TCP 音频发送线程
+ */
+void ln_tcp_audio_send_deinit(void);
+
+/**
+ * @brief 将音频数据放入 TCP 发送队列(供录音回调函数调用)
+ * @param data 音频数据指针
+ * @param size 音频数据大小(字节)
+ * @param encoding AUDIO_SEND_ENCODING_OPUS(0)=已是 Opus;AUDIO_SEND_ENCODING_PCM(1)=PCM,发送前编码为 Opus
+ * @return 0 成功,-1 失败
+ */
+int ln_tcp_audio_send_enqueue(const uint8_t *data, uint32_t size, uint8_t encoding);
+
+/**
+ * @brief AT^LN_TCP_CAT1_BRIDGE_TEST:cat1 桥 + TCP 连接后,按业务路径发送两帧加密音频包并短时 recv 探测
+ * @return 0 成功,-1 失败
+ */
+int ln_tcp_at_test_cat1_full_bridge(void);
+
+
+/**
+ * @brief 发送测试帧
+ * @return 0 成功,-1 失败
+ */
+int ln_tcp_send_test_frames(void);
 
 #ifdef __cplusplus
 }
index 862a372..fb7aea3 100644 (file)
@@ -193,6 +193,30 @@ static uint32_t ln_ota_send_device_info_at(uint8_t *para, uint32_t paraLen, char
     }\r
 }\r
 \r
+/* 模组 cat1↔UDP 桥全路径自测:AT^LN_TCP_CAT1_BRIDGE_TEST */\r
+static uint32_t ln_tcp_cat1_bridge_test_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd)\r
+{\r
+    (void)para;\r
+    (void)paraLen;\r
+    (void)strCommonCmd;\r
+\r
+    if (ln_tcp_at_test_cat1_full_bridge() == 0) {\r
+        return ERRCODE_SUCC;\r
+    }\r
+    return ERRCODE_FAIL;\r
+}\r
+\r
+/* 发送测试帧:AT^LN_TCP_SEND_TEST_FRAMES */\r
+static uint32_t ln_tcp_send_test_frames_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd)\r
+{\r
+    (void)para;\r
+    (void)paraLen;\r
+    (void)strCommonCmd;\r
+    if (ln_tcp_send_test_frames() == 0) {\r
+        return ERRCODE_SUCC;\r
+    }\r
+    return ERRCODE_FAIL;\r
+}\r
 \r
 static const at_cmd_table_t g_ln_mqtt_at_cmds[] = {\r
     {(char *)"AT^LN_MQTT_LISTEN", ln_mqtt_listen_at, (char *)"LN MQTT listen start/stop: AT^LN_MQTT_LISTEN=1/0"},\r
@@ -200,6 +224,10 @@ static const at_cmd_table_t g_ln_mqtt_at_cmds[] = {
     {(char *)"AT^LN_MQTT_START_SERVICE", ln_mqtt_start_service_at, (char *)"LN MQTT start service: AT^LN_MQTT_START_SERVICE"},\r
     {(char *)"AT^LN_MQTT_STOP_SERVICE",  ln_mqtt_stop_service_at,  (char *)"LN MQTT stop service: AT^LN_MQTT_STOP_SERVICE"},\r
      {(char *)"AT^LN_OTA_SEND_DEVICE_INFO",  ln_ota_send_device_info_at,  (char *)"ln_ota_send_device_info_at: AT^ln_ota_send_device_info_json"},\r
+    {(char *)"AT^LN_TCP_CAT1_BRIDGE_TEST", ln_tcp_cat1_bridge_test_at,\r
+     (char *)"cat1桥+TCP+加密发送两帧+recv探测: AT^LN_TCP_CAT1_BRIDGE_TEST"},\r
+    {(char *)"AT^LN_TCP_SEND_TEST_FRAMES", ln_tcp_send_test_frames_at,\r
+     (char *)"TCP发送测试帧: AT^LN_TCP_SEND_TEST_FRAMES"},\r
 };\r
 \r
 at_cmd_table_t *get_ln_mqtt_at_cmd_table(uint32_t *cmd_count)\r