--- /dev/null
+#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);
+}
* 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