fix(audio): defer and coalesce role switch notices

This commit is contained in:
chenjintao
2026-10-13 15:56:00 +08:00
parent 109a95497a
commit 23983ccdc8
4 changed files with 214 additions and 11 deletions
@@ -108,6 +108,7 @@ static char payload[MQTT_PAYLOAD_SIZE] = {0};
extern int fota_start;
int app_play_ota_tips_file(void);
int app_role_notice_handle_url(const char *url);
int xiaoice_get_play_counter(void);
typedef struct {
@@ -1510,7 +1511,9 @@ void handle_command(command_t *cmd)
app_mqtt_publish(resp, strlen(resp), 0);
free(resp);
add_file_to_play_player_queue(cmd->params.nfc.url, 0);
if (!app_role_notice_handle_url(cmd->params.nfc.url)) {
add_file_to_play_player_queue(cmd->params.nfc.url, 0);
}
break;
}
@@ -46,6 +46,7 @@ extern ql_sem_t sender_sem;
extern void conversation_no_speech(void);
extern void conversation_record_begin(void);
extern void conversation_cancel_wait(void);
extern int app_role_notice_before_finish(void);
// 配置
static int sample_rate = 16000; // FVAD 支持的采样率:8k/16k/32k/48k
@@ -502,6 +503,7 @@ void vad_audio_process(void) {
case STATE_WAITING_COMMAND:
if(vad_tts_playback_finished_status == 1) {
if (app_role_notice_before_finish()) break;
int leave_message_finished = vad_leave_message_flag != 0;
printf("finished_status:%d, no_speech_timeout:%d\n", vad_tts_playback_finished_status, no_speech_timeout);
set_state(STATE_IDLE);
@@ -65,6 +65,13 @@ static int round_response_received = 0;
static unsigned int active_session_round = 0;
static int active_session_valid = 0;
static uint64_t startup_start_ms = 0;
#define ROLE_NOTICE_TYPE 5
#define ROLE_NOTICE_URL_SIZE 256
static char role_notice_url[ROLE_NOTICE_URL_SIZE];
static int role_notice_pending = 0;
static int role_notice_playing = 0;
static int role_notice_consumed = 0;
static unsigned int role_notice_round = 0;
static void conversation_lock(void)
{
@@ -84,6 +91,9 @@ static void conversation_begin_round(void)
round_response_received = 0;
active_session_valid = 0;
startup_start_ms = hal_os_up_time_ms();
/* Accepted restart cancels the preceding round's deferred notice. */
role_notice_pending = role_notice_playing = role_notice_consumed = 0;
role_notice_url[0] = '\0';
}
void conversation_record_begin(void)
@@ -98,6 +108,8 @@ void conversation_cancel_wait(void)
{
conversation_lock();
conversation_wait = WAIT_NONE;
role_notice_pending = role_notice_playing = role_notice_consumed = 0;
role_notice_url[0] = '\0';
conversation_unlock();
}
@@ -129,6 +141,7 @@ extern int aofei_mp3_player_get_result(void);
extern int vad_leave_message_is_active(void);
extern int vad_cancel_leave_message(void);
extern void vad_reset_conversation_flags(void);
extern system_state_t vad_audio_get_state(void);
void sendAuthentication(void);
void vad_start_func(void);
@@ -139,8 +152,8 @@ int audio_packet_type = 0;
typedef struct ice_work_item_s
{
int type; //0-tts_out 1-session_continue 2-session_end 3-云端超时提示(失败时播放本地兜底) 4-本地超时兜底
unsigned int timeout_round; /* Timeout items (type 3/4) use this cancellation token. */
int type; //0-tts_out 1-session_continue 2-session_end 3-云端超时提示(失败时播放本地兜底) 4-本地超时兜底 5-角色切换提示
unsigned int timeout_round; /* Startup, timeout and role notices use this round token. */
int error;
int del_url;
void *data;
@@ -373,6 +386,99 @@ int add_file_to_play_player_queue(char *path, int type)
}
/* Only this resource is a role-change notice. Other NFC notices keep their
* existing immediate queue path, including binding and card audio. */
static int role_notice_is_url(const char *url)
{
const char *host, *path;
const char resource[] = "/assets/audio/switch_role.mp3";
size_t n = sizeof(resource) - 1;
if (!url) return 0;
host = strstr(url, "://");
if (!host) return 0;
path = strchr(host + 3, '/');
return path && strncmp(path, resource, n) == 0 &&
(path[n] == '\0' || path[n] == '?' || path[n] == '#');
}
/* Caller holds conversation_mutex. A full queue/failed download must not
* prevent the original conversation completion from proceeding. */
static int role_notice_start_locked(void)
{
role_notice_pending = 0;
role_notice_consumed = 1;
role_notice_playing = 1;
role_notice_round = conversation_round;
if (add_file_to_play_player_queue(role_notice_url, ROLE_NOTICE_TYPE) != 0) {
role_notice_playing = 0;
printf("role notice queue failed:round=%u\n", conversation_round);
return 0;
}
printf("role notice queued once:round=%u\n", conversation_round);
return 1;
}
/* Called only after the normal VAD playback-finished signal, or while idle.
* The original finished flag/no-speech flag stays untouched during the notice. */
static int role_notice_poll_locked(int finishing)
{
if (fota_start || power_down_status || get_sleep_mode()) {
role_notice_pending = role_notice_playing = 0;
return 0;
}
if (role_notice_playing) return 1;
if (!role_notice_pending) return 0;
if (role_notice_round != conversation_round) {
role_notice_pending = 0;
return 0;
}
if (vad_audio_is_recording() || g_mic_working || shine_init ||
timeout_action_busy || player_mq_couter > 0 || tts_playing != 0 ||
conversation_wait != WAIT_NONE) return 1;
if (!finishing && vad_audio_get_state() != STATE_IDLE) return 1;
return role_notice_start_locked();
}
int app_role_notice_handle_url(const char *url)
{
if (!role_notice_is_url(url)) return 0;
conversation_lock();
if (strlen(url) >= sizeof(role_notice_url)) {
printf("role notice URL too long\n");
} else if (role_notice_playing ||
(role_notice_consumed && vad_audio_get_state() != STATE_IDLE)) {
printf("role notice coalesced:round=%u\n", conversation_round);
} else {
/* Repeated commands before playback replace one pending slot. */
snprintf(role_notice_url, sizeof(role_notice_url), "%s", url);
role_notice_pending = 1;
role_notice_round = conversation_round;
printf("role notice pending:round=%u\n", conversation_round);
role_notice_poll_locked(0);
}
conversation_unlock();
return 1;
}
int app_role_notice_before_finish(void)
{
int wait;
conversation_lock();
wait = role_notice_poll_locked(1);
conversation_unlock();
return wait;
}
static void role_notice_finished(unsigned int round, int result)
{
conversation_lock();
if (role_notice_round == round && round == conversation_round) {
role_notice_playing = 0;
printf("role notice finished:round=%u result=%d\n", round, result);
}
conversation_unlock();
}
//如果录音后 超过10s 还没有收到信息 提示本轮对话结束
static void onmessage_text_locked(rws_socket socket, const char *text, const unsigned int len)
{
@@ -1104,6 +1210,9 @@ int app_play_tips_file(int index)
int app_play_ota_tips_file(void)
{
conversation_lock();
role_notice_pending = role_notice_playing = role_notice_consumed = 0;
conversation_unlock();
char filename_buff[64] = {0};
uint32_t timeout;
@@ -1170,8 +1279,10 @@ void app_player_thread(void *arvg)
if(player_item!=NULL){
int stale_timeout;
conversation_lock();
stale_timeout = (player_item->type == 2 || player_item->type == 3 || player_item->type == 4) &&
player_item->timeout_round != conversation_round;
stale_timeout = ((player_item->type == 2 || player_item->type == 3 || player_item->type == 4 ||
player_item->type == ROLE_NOTICE_TYPE) &&
player_item->timeout_round != conversation_round) ||
(player_item->type == ROLE_NOTICE_TYPE && !role_notice_playing);
conversation_unlock();
if (stale_timeout) {
if (player_mq_couter > 0) player_mq_couter--;
@@ -1210,7 +1321,7 @@ void app_player_thread(void *arvg)
conversation_unlock();
if(player_mq_couter==0){
if(player_mq_couter==0 && player_item->type != ROLE_NOTICE_TYPE){
printf("player_mq_couter==0\r\n");
if(tts_play_status==2){
@@ -1233,6 +1344,9 @@ void app_player_thread(void *arvg)
vad_leave_message();
}
}
if (player_item->type == ROLE_NOTICE_TYPE) {
role_notice_finished(player_item->timeout_round, play_ret);
}
if (player_item->type == 2) {
conversation_finish_startup_prompt(player_item->timeout_round, play_ret);
}
@@ -1507,6 +1621,9 @@ static void check_connect_thread_entry(void *parameter)
#endif
#if 1
conversation_check_timeout();
conversation_lock();
role_notice_poll_locked(0);
conversation_unlock();
#endif
ql_rtos_task_sleep_ms(3000);