#include "ai_connection_manager.h"
#include "ai_memory_pool.h"
#include "ai_services.h"
+#include "ai_media.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#else
#define AI_AT_DEMO_ASR_URL "ws://118.145.158.207:22080/asr/ws"
#define AI_AT_DEMO_CHAT_URL "ws://118.145.158.207:22080/chat/ws"
-#define AI_AT_DEMO_TTS_URL "ws://118.145.158.207:22080/tts/ws"
+#define AI_AT_DEMO_TTS_URL "ws://118.145.158.207:22080/tts/stream"
#endif
#define AI_AT_DEMO_AUDIO_SIZE 1024
#define AI_AT_DEMO_CHAT_MESSAGE "你好,我是AI助手AT命令测试程序"
} ai_at_test_stats_t;
static ai_at_test_stats_t g_test_stats = {0};
+static uint32_t get_timestamp_ms(void);
+
+/* ================================ 媒体测试相关 ================================ */
+
+/* 媒体测试状态 */
+typedef struct {
+ bool is_testing;
+ uint32_t start_time;
+ uint32_t test_duration; // 测试持续时间(秒)
+ uint32_t record_count; // 录音数据包计数
+ uint32_t play_count; // 播放数据包计数
+} ai_media_test_context_t;
+
+static ai_media_test_context_t g_media_test_ctx = {0};
+
+/**
+ * @brief 媒体测试录音回调函数 - 边录边放
+ */
+static void media_test_record_callback(uint8_t *data, uint32_t size)
+{
+ if (!g_media_test_ctx.is_testing) {
+ return;
+ }
+
+ g_media_test_ctx.record_count++;
+
+ // 检查播放状态,如果播放正在进行,直接播放录音数据
+ if (ai_media_get_play_status()) {
+ ai_media_play_buffer(data, size);
+ g_media_test_ctx.play_count++;
+
+ // 每100个数据包打印一次状态
+ if (g_media_test_ctx.record_count % 100 == 0) {
+ printf("[AI_MEDIA_TEST] 录音包: %u, 播放包: %u\n",
+ g_media_test_ctx.record_count, g_media_test_ctx.play_count);
+ }
+ }
+
+ // 检查测试时间是否到达
+ uint32_t current_time = get_timestamp_ms();
+ if (current_time - g_media_test_ctx.start_time >= g_media_test_ctx.test_duration * 1000) {
+ printf("[AI_MEDIA_TEST] 测试时间到达,准备停止测试\n");
+ g_media_test_ctx.is_testing = false;
+ }
+}
/* ================================ 内部函数 ================================ */
const ai_memory_stats_t* mem_stats = ai_memory_pool_get_global_stats();
printf("[AI_AT] 全局状态: %s\n", state_names[global_state]);
+
+ // 显示各服务的详细连接状态
+ ai_connection_state_t asr_state = ai_connection_manager_get_connection_state(AI_SERVICE_ASR);
+ ai_connection_state_t chat_state = ai_connection_manager_get_connection_state(AI_SERVICE_CHAT);
+ ai_connection_state_t tts_state = ai_connection_manager_get_connection_state(AI_SERVICE_TTS);
+
+ const char* conn_state_names[] = {"断开", "连接中", "已连接", "错误"};
+ printf("[AI_AT] 服务状态:\n");
+ printf("[AI_AT] ASR: %s (%d)\n", conn_state_names[asr_state], asr_state);
+ printf("[AI_AT] Chat: %s (%d)\n", conn_state_names[chat_state], chat_state);
+ printf("[AI_AT] TTS: %s (%d)\n", conn_state_names[tts_state], tts_state);
+
printf("[AI_AT] 内存使用: %u/%u 字节 (%.1f%%)\n",
mem_stats->used_size, mem_stats->total_size,
(float)mem_stats->used_size * 100.0f / mem_stats->total_size);
return ERRCODE_FAIL;
}
- // 生成并发送模拟音频数据
- uint8_t *audio_data = malloc(AI_AT_DEMO_AUDIO_SIZE);
- if (audio_data == NULL) {
- printf("[AI_AT] 音频缓冲区分配失败\n");
- ai_asr_stop(&g_ai_services_context);
- g_test_stats.tests_failed++;
- return ERRCODE_FAIL;
- }
-
- // 生成模拟音频数据(更真实的模式)
- for (int i = 0; i < AI_AT_DEMO_AUDIO_SIZE; i++) {
- audio_data[i] = (uint8_t)(i % 256);
- }
-
- printf("[AI_AT] 发送音频数据: %d 字节\n", AI_AT_DEMO_AUDIO_SIZE);
- ret = ai_asr_send_audio(&g_ai_services_context, audio_data, AI_AT_DEMO_AUDIO_SIZE);
- free(audio_data);
-
- if (ret != AI_OK) {
- printf("[AI_AT] 音频数据发送失败: %d\n", ret);
- ai_asr_stop(&g_ai_services_context);
- g_test_stats.tests_failed++;
- return ERRCODE_FAIL;
- }
+
// 等待指定时间
printf("[AI_AT] 等待ASR处理...\n");
}
}
+/**
+ * @brief AT^AIMEDIATEST - 媒体服务测试(边录边放)
+ * @param para 参数:测试持续时间(秒),默认10秒
+ * @param paraLen 参数长度
+ * @param strCommonCmd 通用命令字符串
+ * @return ERRCODE_SUCC 成功,ERRCODE_FAIL 失败
+ */
+static uint32_t at_ai_media_test(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ UNUSED(strCommonCmd);
+
+ printf("[AI_AT] 执行 AT^AIMEDIATEST - 媒体服务测试(边录边放)\n");
+
+ // 检查是否已经在测试中
+ if (g_media_test_ctx.is_testing) {
+ printf("[AI_AT] 媒体测试已在进行中,请先停止当前测试\n");
+ return ERRCODE_FAIL;
+ }
+
+ // 解析测试持续时间参数
+ uint32_t test_duration = 10; // 默认10秒
+ if (para != NULL && paraLen > 0) {
+ uint8_t argv[AI_AT_MAX_ARGC][AI_AT_MAX_ARGV_LEN] = { 0 };
+ uint32_t argc = 0;
+
+ int ret = ConvertCmdToParam(para, paraLen, argv, &argc);
+ if (ret == AI_AT_SUCCESS && argc > 0) {
+ int duration = atoi((char*)argv[0]);
+ if (duration > 0 && duration <= 300) { // 最大5分钟
+ test_duration = (uint32_t)duration;
+ } else {
+ printf("[AI_AT] 无效的测试时长: %d (有效范围: 1-300秒)\n", duration);
+ return ERRCODE_FAIL;
+ }
+ }
+ }
+
+ printf("[AI_AT] 开始媒体测试,持续时间: %u秒\n", test_duration);
+
+ // 初始化媒体测试上下文
+ memset(&g_media_test_ctx, 0, sizeof(g_media_test_ctx));
+ g_media_test_ctx.is_testing = true;
+ g_media_test_ctx.start_time = get_timestamp_ms();
+ g_media_test_ctx.test_duration = test_duration;
+
+ // 初始化媒体服务
+ int32_t init_ret = ai_media_service_init();
+ if (init_ret != 0) {
+ printf("[AI_AT] 媒体服务初始化失败: %d\n", init_ret);
+ g_media_test_ctx.is_testing = false;
+ return ERRCODE_FAIL;
+ }
+
+ // 设置录音回调
+ ai_media_set_record_callback(media_test_record_callback);
+
+ // 启动播放服务
+ printf("[AI_AT] 启动音频播放服务...\n");
+ uint8_t play_format[] = "mp3";
+ ai_media_play_start(play_format);
+
+ // 检查播放状态
+ if (!ai_media_get_play_status()) {
+ printf("[AI_AT] 播放服务启动失败\n");
+ ai_media_service_deinit();
+ g_media_test_ctx.is_testing = false;
+ return ERRCODE_FAIL;
+ }
+
+ // 启动录音服务
+ printf("[AI_AT] 启动音频录音服务...\n");
+ uint8_t record_format[] = "mp3";
+ ai_media_record_start(record_format);
+
+ // 检查录音状态
+ if (!ai_media_get_record_status()) {
+ printf("[AI_AT] 录音服务启动失败\n");
+ ai_media_play_stop();
+ ai_media_service_deinit();
+ g_media_test_ctx.is_testing = false;
+ return ERRCODE_FAIL;
+ }
+
+ printf("[AI_AT] 媒体测试已启动!录音数据将实时播放\n");
+ printf("[AI_AT] 测试将在%u秒后自动停止\n", test_duration);
+
+ // 等待测试完成
+ uint32_t check_interval = 1000; // 每秒检查一次
+ uint32_t elapsed_time = 0;
+
+ while (g_media_test_ctx.is_testing && elapsed_time < test_duration * 1000) {
+#ifndef __TEST_MODE__
+ osDelay(check_interval);
+#else
+ usleep(check_interval * 1000); // 测试模式下使用usleep
+#endif
+ elapsed_time += check_interval;
+
+ // 每5秒打印一次状态
+ if (elapsed_time % 5000 == 0) {
+ printf("[AI_AT] 测试进行中... 已录音包: %u, 已播放包: %u, 剩余时间: %u秒\n",
+ g_media_test_ctx.record_count, g_media_test_ctx.play_count,
+ (test_duration * 1000 - elapsed_time) / 1000);
+ }
+ }
+
+ // 停止测试
+ printf("[AI_AT] 停止媒体测试...\n");
+ g_media_test_ctx.is_testing = false;
+
+ // 停止录音和播放
+ ai_media_record_stop();
+ ai_media_play_stop();
+
+ // 反初始化媒体服务
+ ai_media_service_deinit();
+
+ // 打印测试结果
+ printf("[AI_AT] 媒体测试完成!\n");
+ printf("[AI_AT] 测试时长: %u秒\n", test_duration);
+ printf("[AI_AT] 录音数据包: %u\n", g_media_test_ctx.record_count);
+ printf("[AI_AT] 播放数据包: %u\n", g_media_test_ctx.play_count);
+ printf("[AI_AT] 数据传输率: %.2f包/秒\n",
+ (float)g_media_test_ctx.record_count / test_duration);
+
+ return ERRCODE_SUCC;
+}
+
+/**
+ * @brief AT^AIMEDIASTOP - 停止媒体服务测试
+ * @param para 参数(未使用)
+ * @param paraLen 参数长度
+ * @param strCommonCmd 通用命令字符串
+ * @return ERRCODE_SUCC 成功,ERRCODE_FAIL 失败
+ */
+static uint32_t at_ai_media_stop(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ UNUSED(para);
+ UNUSED(paraLen);
+ UNUSED(strCommonCmd);
+
+ printf("[AI_AT] 执行 AT^AIMEDIASTOP - 停止媒体服务测试\n");
+
+ if (!g_media_test_ctx.is_testing) {
+ printf("[AI_AT] 当前没有进行中的媒体测试\n");
+ return ERRCODE_SUCC;
+ }
+
+ // 停止测试
+ printf("[AI_AT] 强制停止媒体测试...\n");
+ g_media_test_ctx.is_testing = false;
+
+ // 停止录音和播放
+ ai_media_record_stop();
+ ai_media_play_stop();
+
+ // 反初始化媒体服务
+ ai_media_service_deinit();
+
+ printf("[AI_AT] 媒体测试已停止\n");
+ printf("[AI_AT] 录音数据包: %u\n", g_media_test_ctx.record_count);
+ printf("[AI_AT] 播放数据包: %u\n", g_media_test_ctx.play_count);
+
+ return ERRCODE_SUCC;
+}
+
/* ================================ AT命令表定义 ================================ */
static const at_cmd_table_t g_ai_cmd[] = {
{ (char *)"AT^AISTATS", at_ai_stats, (char *)"Show test statistics" },
{ (char *)"AT^AICLEANUP", at_ai_cleanup, (char *)"Cleanup AI services" },
{ (char *)"AT^AIRECONNECT", at_ai_reconnect, (char *)"service_type (0=ASR,1=Chat,2=TTS)" },
+ { (char *)"AT^AIMEDIATEST", at_ai_media_test, (char *)"duration_seconds (default=10, max=300)" },
+ { (char *)"AT^AIMEDIASTOP", at_ai_media_stop, (char *)"Stop media test" },
};
/* ================================ 公共接口 ================================ */
result = at_ai_performance_test(NULL, 0, NULL);
printf(" 结果: %s\n", result == ERRCODE_SUCC ? "✅ 成功" : "❌ 失败");
- printf("\n9. 最终统计和清理\n");
+ printf("\n9. 测试 AT^AIMEDIATEST=5 - 媒体服务测试(边录边放5秒)\n");
+ uint8_t media_para[] = "5";
+ result = at_ai_media_test(media_para, sizeof(media_para) - 1, NULL);
+ printf(" 结果: %s\n", result == ERRCODE_SUCC ? "✅ 成功" : "❌ 失败");
+
+ printf("\n10. 最终统计和清理\n");
result = at_ai_stats(NULL, 0, NULL);
printf(" 统计结果: %s\n", result == ERRCODE_SUCC ? "✅ 成功" : "❌ 失败");
- printf("\n10. 测试 AT^AICLEANUP - 清理AI服务\n");
+ printf("\n11. 测试 AT^AICLEANUP - 清理AI服务\n");
result = at_ai_cleanup(NULL, 0, NULL);
printf(" 结果: %s\n", result == ERRCODE_SUCC ? "✅ 成功" : "❌ 失败");
/* WebSocket连接计数器和限制 */
static int g_active_websocket_count = 0;
-#define MAX_CONCURRENT_WEBSOCKETS 4 // 最大并发WebSocket连接数
+#define MAX_CONCURRENT_WEBSOCKETS 3 // 最大并发WebSocket连接数
/* 服务名称映射表 */
static const char* g_service_names[AI_SERVICE_COUNT] = {
g_connection_manager.metrics.last_connect_timestamp = start_time;
- /* 并发启动所有连接线程 */
+ /* 串行启动所有连接,确保按顺序成功连接 */
AI_LOGI("[DEBUG] AI_SERVICE_COUNT = %d", AI_SERVICE_COUNT);
+ AI_LOGI("Starting services in serial mode to avoid connection conflicts");
for (int i = 0; i < AI_SERVICE_COUNT; i++) {
AI_LOGI("[DEBUG] Creating thread for service %d (%s)...", i, g_service_names[i]);
AI_LOGI("[DEBUG] Thread created successfully for service %s", g_service_names[i]);
#ifndef __TEST_MODE__
- /* 给线程一些时间启动,避免竞态条件 */
- osDelay(100); // 100ms延迟
+ /* 等待当前服务连接成功后再启动下一个 */
+ AI_LOGI("Waiting for %s service to connect before starting next service...", g_service_names[i]);
+
+ // 等待连接建立,最多等待15秒(增加超时时间)
+ bool connected = false;
+ uint32_t wait_start = get_current_timestamp();
+
+ for (int wait_count = 0; wait_count < 150; wait_count++) {
+ osDelay(100); // 每100ms检查一次
+
+ ai_connection_state_t state = g_connection_manager.connections[i].state;
+ if (state == AI_CONN_STATE_CONNECTED) {
+ connected = true;
+ uint32_t connect_duration = get_current_timestamp() - wait_start;
+ AI_LOGI("Service %s connected successfully in %u ms", g_service_names[i], connect_duration);
+ break;
+ } else if (state == AI_CONN_STATE_ERROR) {
+ AI_LOGE("Service %s connection failed with error state", g_service_names[i]);
+ break;
+ }
+
+ // 每2秒打印一次状态,减少日志噪音
+ if (wait_count % 20 == 0 && wait_count > 0) {
+ uint32_t elapsed = get_current_timestamp() - wait_start;
+ AI_LOGI("Waiting for %s connection... (state: %d, elapsed: %u ms, attempt: %d/150)",
+ g_service_names[i], state, elapsed, wait_count + 1);
+ }
+ }
+
+ if (!connected) {
+ uint32_t total_wait = get_current_timestamp() - wait_start;
+ AI_LOGE("Service %s failed to connect within %u ms timeout", g_service_names[i], total_wait);
+ // 继续尝试下一个服务,而不是完全失败
+ } else {
+ // 连接成功后等待1秒,确保连接稳定并避免资源竞争
+ AI_LOGI("Service %s connection established, waiting 1s before next service...", g_service_names[i]);
+ osDelay(1000);
+ }
+#else
+ /* 测试模式下简单延时 */
+ usleep(100000); // 100ms
#endif
}
AI_LOGI("[DEBUG] Connection thread started for service %s", g_service_names[service_type]);
- while (g_connection_manager.is_running) {
+ /* 只尝试连接一次,连接成功后线程退出 */
+ bool connection_attempted = false;
+
+ while (g_connection_manager.is_running && !connection_attempted) {
if (conn->state == AI_CONN_STATE_DISCONNECTED) {
/* 尝试连接 */
+ connection_attempted = true; // 标记已尝试连接
uint32_t connect_start = get_current_timestamp();
ai_error_t ret = websocket_connect(conn);
AI_LOGI("Service %s connected in %u ms", g_service_names[service_type], connect_time);
- /* 更新连接状态矩阵 */
- switch (service_type) {
- case AI_SERVICE_ASR:
- g_connection_manager.connection_matrix.asr_connected = 1;
- break;
- case AI_SERVICE_CHAT:
- g_connection_manager.connection_matrix.chat_connected = 1;
- break;
- case AI_SERVICE_TTS:
- g_connection_manager.connection_matrix.tts_connected = 1;
- break;
- case AI_SERVICE_COUNT:
- /* 不应该到达这里 */
- break;
- case AI_SERVICE_GLOBAL:
- /* 全局服务不需要更新连接矩阵 */
- break;
- }
-
- update_global_state();
+ /* 注意:连接状态矩阵将在websocket_on_connected回调中更新 */
#ifndef __TEST_MODE__
/* 释放就绪信号量 */
/* 发送连接建立事件 */
send_event(AI_EVENT_CONNECTION_ESTABLISHED, service_type, NULL);
+
+ /* 连接成功,线程任务完成,退出循环 */
+ AI_LOGI("Connection thread for service %s completed successfully, exiting", g_service_names[service_type]);
+ break;
} else {
conn->retry_count++;
g_connection_manager.metrics.retry_count[service_type]++;
/* 使用新的清理函数替换原有的清理逻辑 */
cleanup_websocket_connection(conn);
+ /* 确保连接状态矩阵正确反映失败状态 */
+ switch (service_type) {
+ case AI_SERVICE_ASR:
+ g_connection_manager.connection_matrix.asr_connected = 0;
+ break;
+ case AI_SERVICE_CHAT:
+ g_connection_manager.connection_matrix.chat_connected = 0;
+ break;
+ case AI_SERVICE_TTS:
+ g_connection_manager.connection_matrix.tts_connected = 0;
+ break;
+ case AI_SERVICE_COUNT:
+ case AI_SERVICE_GLOBAL:
+ break;
+ }
+
/* 为了安全,限制重试次数为1次 */
if (conn->retry_count >= 1) {
AI_LOGW("Service %s connection disabled for safety after %d attempts",
/* 设置为永久断开状态,不再重试 */
conn->state = AI_CONN_STATE_ERROR;
+ /* 更新全局状态 */
+ update_global_state();
+
/* 发送连接失败事件 */
send_event(AI_EVENT_ERROR_OCCURRED, service_type, &ret);
+
+ /* 连接失败,线程任务完成,退出循环 */
+ AI_LOGI("Connection thread for service %s failed after retries, exiting", g_service_names[service_type]);
break;
}
- /* 等待重试间隔 */
+ /* 等待重试间隔,然后继续尝试 */
#ifndef __TEST_MODE__
osDelay(g_connection_manager.config.retry_interval_ms);
#endif
+ /* 重置connection_attempted以允许重试 */
+ connection_attempted = false;
}
} else {
- /* 连接已建立,等待断开或错误 */
-#ifndef __TEST_MODE__
- osDelay(1000); // 1秒检查一次
-#endif
+ /* 连接已建立,线程任务完成,退出循环 */
+ AI_LOGI("Connection thread for service %s found already connected, exiting", g_service_names[service_type]);
+ break;
}
}
ai_connection_info_t *conn_info = find_connection_by_websocket(client);
if (conn_info == NULL) {
AI_LOGE("Cannot find connection info for WebSocket client %d", client);
- return;
+
+ /* 尝试恢复:检查是否有处于CONNECTING状态的连接 */
+ for (int i = 0; i < AI_SERVICE_COUNT; i++) {
+ if (g_connection_manager.connections[i].state == AI_CONN_STATE_CONNECTING) {
+ AI_LOGW("Found CONNECTING service %s, assuming this is the connected one", g_service_names[i]);
+ conn_info = &g_connection_manager.connections[i];
+ conn_info->websocket = client; // 更新正确的WebSocket ID
+ break;
+ }
+ }
+
+ if (conn_info == NULL) {
+ AI_LOGE("No CONNECTING service found, cannot recover");
+ return;
+ }
}
/* 验证服务类型有效性 */
conn_info->state = AI_CONN_STATE_CONNECTED;
conn_info->last_activity_time = get_current_timestamp();
+ /* 更新连接状态矩阵 */
+ switch (conn_info->service_type) {
+ case AI_SERVICE_ASR:
+ g_connection_manager.connection_matrix.asr_connected = 1;
+ break;
+ case AI_SERVICE_CHAT:
+ g_connection_manager.connection_matrix.chat_connected = 1;
+ break;
+ case AI_SERVICE_TTS:
+ g_connection_manager.connection_matrix.tts_connected = 1;
+ break;
+ case AI_SERVICE_COUNT:
+ case AI_SERVICE_GLOBAL:
+ /* 不需要更新连接矩阵 */
+ break;
+ }
+
+ /* 更新全局状态 */
+ update_global_state();
+
#ifndef __TEST_MODE__
if (conn_info->mutex != NULL) {
osMutexRelease(conn_info->mutex);