#include "cmsis_os2.h"
#include "mbedtls/base64.h"
#include "ai_media.h" // Added for AI_RegisterWriteAudioDataCallback
-
+#include "audio_base_type.h"
// 功能开关宏定义
#define ENABLE_BINARY_DATA_INTERRUPT_CHECK 1 // 启用二进制数据间隔检查
static void ln_ws_process_base64_data(const char *base64_data, size_t length);
static void ln_ws_process_incremental_base64_data(const char *new_data, size_t new_length);
static void ln_ws_incomplete_callback(const char *raw_data, size_t length, bool isBinary);
-
+static void clear_audio_duration_buffer(void);
+static void record_audio_duration(uint32_t duration);
+static bool check_playback_completion_by_duration(void);
+static bool check_playback_completion_by_timeout(void);
uint16_t send_audio=0;
static bool g_audio_playback_active = false; // 音频播放是否活跃
static uint32_t g_audio_timeout_ticks = 1500; // 音频超时tick数(默认1.5秒)
static uint32_t g_last_binary_data_tick = 0; // 最后一次接收二进制数据的tick
+static uint32_t g_audio_playback_start_tick = 0; // 音频播放开始时间tick
+
+// 音频时长记录循环数组
+#define AUDIO_DURATION_BUFFER_SIZE 50
+
+// 音频时长记录结构体
+typedef struct {
+ uint32_t duration; // 音频时长(毫秒)
+ bool is_valid; // 是否有效
+} audio_duration_record_t;
+
+static audio_duration_record_t g_audio_duration_buffer[AUDIO_DURATION_BUFFER_SIZE];
+static uint32_t g_audio_duration_index = 0;
+static uint32_t g_audio_duration_count = 0;
// 函数声明
static void generate_chat_id(char *chat_id, size_t size);
LN_WS_DEBUG("Heartbeat callback: audio playback active, elapsed ticks: %u\n", elapsed_ticks);
- // 如果超过超时时间没有音频写入,认为播放结束
- if (elapsed_ticks >= g_audio_timeout_ticks) {
- LN_WS_INFO("Audio playback timeout detected, triggering end event\n");
- g_audio_playback_active = false;
-
- // 触发播放结束事件
- if (g_ws_message_callback != NULL) {
- g_ws_message_callback(NULL, WS_CMD_AUDIO_PLAYBACK_END);
- }
- }
+ // 优先使用基于音频时长的精确判断
+ check_playback_completion_by_duration();
+
+ // 使用基于超时的兜底机制
+ check_playback_completion_by_timeout();
}
if (heartbeat_counter >= 30) { // 30秒 = 30次 * 1秒
}
+
// 生成聊天ID
static void generate_chat_id(char *chat_id, size_t size)
{
// 更新最后一次音频写入tick
g_last_audio_write_tick = osKernelGetTickCount();
+
+ // 如果音频播放从非活跃状态转为活跃状态,记录开始播放时间
+ if (!g_audio_playback_active) {
+ g_audio_playback_start_tick = g_last_audio_write_tick;
+ LN_WS_INFO("Audio playback started at tick: %u\n", g_audio_playback_start_tick);
+
+ // 触发音频播放开始事件
+ if (g_ws_message_callback != NULL) {
+ g_ws_message_callback(NULL, WS_CMD_AUDIO_PLAYBACK_START);
+ }
+ }
+
g_audio_playback_active = true;
}
// 发送JSON字符串
int ret = uapi_ws_send_text(json_string);
- LN_WS_ERROR("Sent audio data: chatId=%s, messageId=%s, size=%u, finished=%s\n",
+ LN_WS_DEBUG("Sent audio data: chatId=%s, messageId=%s, size=%u, finished=%s\n",
g_audio_chat_ctx.chat_id, message_id, audio_size, finished ? "true" : "false");
// 清理内存
return ret;
}
+
// 开始新的音频聊天
int ln_ws_start_audio_chat(ln_audio_chat_command_t command_type)
{
g_audio_chat_ctx.message_counter = 0;
g_audio_chat_ctx.is_active = true;
+ // 清空音频时长数组,避免不同对话间的数据混淆
+ clear_audio_duration_buffer();
+
// 重置对话ID匹配标志
reset_chat_id_match();
g_ws_running = 0;
return ret;
}
+
+ AIVoiceAudioParams audioParams_in = {
+ .audioFormat = MP3,
+ .channelCount = 1,
+ .sampleRate = 16000
+ };
+
+ AIVoiceAudioParams audioParams_out = {
+ .audioFormat = MP3,
+ .channelCount = 1,
+ .sampleRate = 8000
+ };
+
- old_Start_AIVoice(AIVoiceDataCallback_ln,NULL,NULL,NULL,false);
+ old_Start_AIVoice(AIVoiceDataCallback_ln,NULL,&audioParams_in,&audioParams_out,false);
// 注册音频数据写入回调
AI_RegisterWriteAudioDataCallback(ln_ws_audio_write_callback);
return ret;
}
+
/* 发送语音聊天打断请求 */
int ln_ws_send_voice_chat_interrupt(const char *reason)
{
}
+
/* 停止:关闭心跳并关闭连接 */
int ln_ws_stop(void)
{
// 清理音频缓存
ln_ws_audio_buffer_cleanup();
+ // 重置音频播放相关状态
+ g_audio_playback_active = false;
+ g_audio_playback_start_tick = 0;
+ g_last_audio_write_tick = 0;
+
return ret;
}
+
/* 获取当前连接状态 */
int ln_ws_get_connection_state(void)
{
// "errorCode": null
// }
// }
+
+// {"deviceId":"860132063148589","timestamp":1760408698,"messageId":"1edfb1fc9d904ceb8ca5579241aee1ee","command":"VoiceChatResponse","data":{"finished":false,"chatId":"chat_1760408691_1125307275","data":{"audioDuration":1583,"audioUrl":"http://ambybox-api.test.leiniao.com/ambybox-device-api/file/933515348189_20251014102458_1760408698051.mp3","textContent":"有什么想聊聊的吗?","intention":"None","npcEmotion":"winking"}}}
static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_msg)
{
if (!json_str || !data_msg) {
strncpy(data_msg->text_content, text_content->valuestring, sizeof(data_msg->text_content) - 1);
data_msg->text_content[sizeof(data_msg->text_content) - 1] = '\0';
}
+
+ // 解析audioDuration字段
+ cJSON *audio_duration = cJSON_GetObjectItem(data_data, "audioDuration");
+ if (audio_duration && cJSON_IsNumber(audio_duration)) {
+ data_msg->audio_duration = (uint32_t)audio_duration->valuedouble;
+ // 记录音频时长
+ record_audio_duration(data_msg->audio_duration);
+ }
}
}
- LN_WS_DEBUG("VoiceChatResponse parsed: finished=%s, chatId=%s, audioUrl=%s, textContent=%s\n",
- data_msg->finished ? "true" : "false", data_msg->chat_id, data_msg->audio_url, data_msg->text_content);
+ LN_WS_DEBUG("VoiceChatResponse parsed: finished=%s, chatId=%s, audioUrl=%s, textContent=%s, audioDuration=%u\n",
+ data_msg->finished ? "true" : "false", data_msg->chat_id, data_msg->audio_url, data_msg->text_content, data_msg->audio_duration);
}
// 解析ChooseStoryResponse特定字段
strncpy(data_msg->audio_url, story_audio_url->valuestring, sizeof(data_msg->audio_url) - 1);
data_msg->audio_url[sizeof(data_msg->audio_url) - 1] = '\0';
}
+
+ cJSON *audio_duration = cJSON_GetObjectItem(data_data, "audioDuration");
+ if (audio_duration && cJSON_IsNumber(audio_duration)) {
+ data_msg->audio_duration = (uint32_t)audio_duration->valuedouble;
+ // 记录音频时长
+ record_audio_duration(data_msg->audio_duration);
+ }
}
}
strncpy(data_msg->audio_url, story_audio_url->valuestring, sizeof(data_msg->audio_url) - 1);
data_msg->audio_url[sizeof(data_msg->audio_url) - 1] = '\0';
}
+
+ cJSON *audio_duration = cJSON_GetObjectItem(data_data, "audioDuration");
+ if (audio_duration && cJSON_IsNumber(audio_duration)) {
+ data_msg->audio_duration = (uint32_t)audio_duration->valuedouble;
+ // 记录音频时长
+ record_audio_duration(data_msg->audio_duration);
+ }
}
}
case WS_CMD_DISCONNECTED:
LN_WS_DEBUG("Default: Processing WebSocket Disconnected event\n");
break;
+ case WS_CMD_AUDIO_PLAYBACK_START:
+ LN_WS_DEBUG("Default: Processing Audio Playback Start event\n");
+ break;
case WS_CMD_AUDIO_PLAYBACK_END:
LN_WS_DEBUG("Default: Processing Audio Playback End event\n");
break;
+ case WS_CMD_SENTENCE_PLAYBACK_COMPLETE:
+ LN_WS_DEBUG("Default: Processing Sentence Playback Complete event\n");
+ break;
case WS_CMD_AUDIO_RECORDING_START:
LN_WS_DEBUG("Default: Processing Audio Recording Start event\n");
break;
return ret;
}
+
/**
* @brief 退出休眠状态(公共接口)
* @return 0成功,-1失败
return ret;
}
+
/* ============================ Base64缓存管理函数 ============================ */
/**
}
return ret;
-}
\ No newline at end of file
+}
+
+
+// 清空音频时长数组(新对话开始时调用)
+static void clear_audio_duration_buffer(void)
+{
+ // 重置所有记录为无效
+ for (uint32_t i = 0; i < AUDIO_DURATION_BUFFER_SIZE; i++) {
+ g_audio_duration_buffer[i].duration = 0;
+ g_audio_duration_buffer[i].is_valid = false;
+ }
+
+ // 重置索引和计数
+ g_audio_duration_index = 0;
+ g_audio_duration_count = 0;
+
+ LN_WS_INFO("Audio duration buffer cleared for new conversation\n");
+}
+
+// 记录音频时长到循环数组
+static void record_audio_duration(uint32_t duration)
+{
+ // 判断时长是否有效:大于0且小于10分钟(600000毫秒)
+ bool is_valid = (duration > 0 && duration < 600000);
+
+ // 获取当前记录位置
+ audio_duration_record_t *record = &g_audio_duration_buffer[g_audio_duration_index];
+
+ // 填充记录数据
+ record->duration = duration;
+ record->is_valid = is_valid; // 新记录默认为有效(如果时长合理)
+
+ // 更新索引和计数
+ g_audio_duration_index = (g_audio_duration_index + 1) % AUDIO_DURATION_BUFFER_SIZE;
+ if (g_audio_duration_count < AUDIO_DURATION_BUFFER_SIZE) {
+ g_audio_duration_count++;
+ }
+}
+
+// 获取最早且有效的音频时长
+static uint32_t get_oldest_valid_audio_duration(void)
+{
+ if (g_audio_duration_count == 0) {
+ return 0;
+ }
+
+ // 从最早的记录开始查找有效数据
+ uint32_t oldest_index = (g_audio_duration_index >= g_audio_duration_count) ?
+ (g_audio_duration_index - g_audio_duration_count) :
+ (AUDIO_DURATION_BUFFER_SIZE - (g_audio_duration_count - g_audio_duration_index));
+
+ // 查找最早的有效记录
+ for (uint32_t i = 0; i < g_audio_duration_count; i++) {
+ uint32_t current_index = (oldest_index + i) % AUDIO_DURATION_BUFFER_SIZE;
+ audio_duration_record_t *record = &g_audio_duration_buffer[current_index];
+
+ if (record->is_valid) {
+ return record->duration;
+ }
+ }
+
+ return 0; // 没有找到有效的音频时长
+}
+
+// 设置最早且有效的音频时长记录为无效(播放完成后调用)
+static void mark_oldest_valid_audio_duration_invalid(void)
+{
+ if (g_audio_duration_count == 0) {
+ return;
+ }
+
+ // 从最早的记录开始查找有效数据
+ uint32_t oldest_index = (g_audio_duration_index >= g_audio_duration_count) ?
+ (g_audio_duration_index - g_audio_duration_count) :
+ (AUDIO_DURATION_BUFFER_SIZE - (g_audio_duration_count - g_audio_duration_index));
+
+ // 查找最早的有效记录
+ for (uint32_t i = 0; i < g_audio_duration_count; i++) {
+ uint32_t current_index = (oldest_index + i) % AUDIO_DURATION_BUFFER_SIZE;
+ audio_duration_record_t *record = &g_audio_duration_buffer[current_index];
+
+ if (record->is_valid) {
+ // 找到最早的有效记录,设置为无效
+ record->is_valid = false;
+ LN_WS_DEBUG("Marked oldest valid audio duration record as invalid (duration: %u ms)\n", record->duration);
+ return;
+ }
+ }
+
+ LN_WS_DEBUG("No valid audio duration record found to mark as invalid\n");
+}
+
+// ===== 播放完成判断函数 =====
+
+// 基于音频时长的精确判断
+static bool check_playback_completion_by_duration(void)
+{
+ uint32_t oldest_audio_duration_ms = get_oldest_valid_audio_duration();
+
+ if (oldest_audio_duration_ms == 0) {
+ return false; // 没有有效的音频时长数据
+ }
+
+ uint32_t current_tick = osKernelGetTickCount();
+ uint32_t expected_duration_ticks = oldest_audio_duration_ms;
+ uint32_t playback_elapsed_ticks = current_tick - g_audio_playback_start_tick;
+
+ LN_WS_DEBUG("Duration-based check: expected %u ticks, elapsed %u ticks\n",
+ expected_duration_ticks, playback_elapsed_ticks);
+
+ // 如果播放时间已经超过预期时长,认为播放完成
+ if (playback_elapsed_ticks >= expected_duration_ticks) {
+ LN_WS_INFO("Audio playback completed based on duration: %u ms\n", oldest_audio_duration_ms);
+
+ // 标记最早且有效的音频时长记录为无效(已播放完成)
+ mark_oldest_valid_audio_duration_invalid();
+ g_audio_playback_start_tick = g_last_audio_write_tick; // 重置播放开始时间
+
+ // 触发单句语音播放完成事件
+ if (g_ws_message_callback != NULL) {
+ g_ws_message_callback(NULL, WS_CMD_SENTENCE_PLAYBACK_COMPLETE);
+ }
+
+ return true; // 播放已完成
+ }
+
+ return false; // 播放未完成
+}
+
+// 基于超时的兜底机制判断
+static bool check_playback_completion_by_timeout(void)
+{
+ uint32_t current_tick = osKernelGetTickCount();
+ uint32_t elapsed_ticks = current_tick - g_last_audio_write_tick;
+
+ if (elapsed_ticks >= g_audio_timeout_ticks) {
+ LN_WS_INFO("Audio playback timeout detected, elapsed: %u ticks\n", elapsed_ticks);
+
+ // 计算播放总时长
+ uint32_t playback_duration = current_tick - g_audio_playback_start_tick;
+ LN_WS_INFO("Audio playback ended by timeout, total duration: %u ticks\n", playback_duration);
+
+ g_audio_playback_active = false;
+ g_audio_playback_start_tick = 0; // 重置播放开始时间
+
+ // 触发播放结束事件
+ if (g_ws_message_callback != NULL) {
+ g_ws_message_callback(NULL, WS_CMD_AUDIO_PLAYBACK_END);
+ }
+
+ return true; // 播放已完成
+ }
+
+ return false; // 播放未完成
+}