11 Commits
6 changed files with 438 additions and 161 deletions
@@ -187,6 +187,9 @@ static void key_thread_entry(void *parameter)
if(ql_rtos_get_systicks() - key_press_mic_time > KEY_LONG_PRESS_3S && power_down_status==0){ if(ql_rtos_get_systicks() - key_press_mic_time > KEY_LONG_PRESS_3S && power_down_status==0){
nfc_tx_click_count = 0; nfc_tx_click_count = 0;
power_down_status = 1; power_down_status = 1;
/* Shutdown audio must own the codec. Stop VAD and any
* interrupted TTS before queueing the power-off prompt. */
stop_player_func();
app_play_tips_file(10); app_play_tips_file(10);
power_down_status = 2; power_down_status = 2;
//ql_rtos_task_sleep_s(3); //ql_rtos_task_sleep_s(3);
@@ -34,6 +34,7 @@ const char *mp3_file[] ={
"volume_up_zh.mp3",//16 "volume_up_zh.mp3",//16
"volume_down_zh.mp3",//17 "volume_down_zh.mp3",//17
"ai_active_zh.mp3",//18 "ai_active_zh.mp3",//18
"barge_in_cute.mp3",//19 对话打断提示音
}; };
//http://banban.api.talkingq.com:8080/assets/roles/banban/tts_error.mp3 //http://banban.api.talkingq.com:8080/assets/roles/banban/tts_error.mp3
@@ -183,9 +184,11 @@ void app_file_check_download(void)
char filename_buff[64]={0x00}; char filename_buff[64]={0x00};
int ret = 0, cnt = 0; int ret = 0, cnt = 0;
for(index=1;index<19;index++){ for(index=1;index<20;index++){
if(index==4||index==5||index==6||index==7||index==8){ /* barge_in_cute.mp3 is shipped in customer_fs and is not hosted by the
* legacy prompt download service. */
if(index==4||index==5||index==6||index==7||index==8||index==19){
continue; continue;
} }
@@ -208,9 +211,11 @@ void app_file_check_del(void)
int index = 1; int index = 1;
char filename_buff[64]={0x00}; char filename_buff[64]={0x00};
for(index=1;index<19;index++){ for(index=1;index<20;index++){
if(index==4||index==5||index==6||index==7||index==8){ /* Keep the bundled barge-in prompt across app-only FOTA. Unlike the
* server-managed prompts, it cannot be restored by the downloader. */
if(index==4||index==5||index==6||index==7||index==8||index==19){
continue; continue;
} }
@@ -25,6 +25,7 @@ static unsigned char mp3_player_status = 0;
QFILE *local_mp3_file_fd=NULL; QFILE *local_mp3_file_fd=NULL;
static int mp3_player_end_frame = 0; static int mp3_player_end_frame = 0;
static int mp3_player_last_result = 0;
int current_sample_rate = 0; int current_sample_rate = 0;
static audio_track_handle track_hd; static audio_track_handle track_hd;
@@ -278,9 +279,10 @@ u32 local_mp3_data_fetch(const char *uri, uint8_t *buffer, u32 length)
return read_bytes; return read_bytes;
} }
size_t url_mp3_data_fetch(const char *uri, uint8_t *buffer, size_t length) int url_mp3_data_fetch(const char *uri, uint8_t *buffer, size_t length)
{ {
int resp_status = -1; int resp_status = -1;
int result = -1;
uint32_t nw = length; uint32_t nw = length;
uint32_t page_pos = 0; uint32_t page_pos = 0;
uint32_t content_length = 0; uint32_t content_length = 0;
@@ -353,7 +355,10 @@ size_t url_mp3_data_fetch(const char *uri, uint8_t *buffer, size_t length)
total_length += page_pos; total_length += page_pos;
} }
printf("end read:%d, %d\n", total_length, session->content_length); printf("end read:%d, %d\n", total_length, session->content_length);
if(total_length == session->content_length){
result = 0;
}
__exit: __exit:
@@ -363,12 +368,12 @@ __exit:
session = NULL; session = NULL;
} }
if(total_length<content_length){ if(result != 0 || total_length < content_length){
total_length = 0; total_length = 0;
return -1; return -1;
} }
return 0; return result;
} }
@@ -390,11 +395,24 @@ int aofei_mp3_player_init(void)
void aofei_mp3_player(char *file_name) void aofei_mp3_player(char *file_name)
{ {
int ret = 0; int ret = 0;
uint8_t *mp3_buff = malloc(100*1024); uint8_t *mp3_buff = NULL;
mp3_player_last_result = -1;
if(file_name == NULL){
printf("mp3 player file name is null\r\n");
return;
}
mp3_buff = malloc(100*1024);
if(mp3_buff == NULL){
printf("mp3 player buffer malloc failed\r\n");
return;
}
memset(mp3_buff, 0, 100*1024); memset(mp3_buff, 0, 100*1024);
//直接下载到一个100k的buffer //直接下载到一个100k的buffer
if(strstr(file_name, "http:")){//播放url if(strncmp(file_name, "http://", strlen("http://")) == 0 ||
strncmp(file_name, "https://", strlen("https://")) == 0){//播放url
ret = url_mp3_data_fetch(file_name, mp3_buff, 100*1024); ret = url_mp3_data_fetch(file_name, mp3_buff, 100*1024);
}else if(strstr(file_name, "test.mp3")){//播放本地音频 }else if(strstr(file_name, "test.mp3")){//播放本地音频
total_length = test_data_size; total_length = test_data_size;
@@ -403,10 +421,18 @@ void aofei_mp3_player(char *file_name)
total_length = local_mp3_data_fetch(file_name, mp3_buff, 100*1024); total_length = local_mp3_data_fetch(file_name, mp3_buff, 100*1024);
} }
if(ret < 0 || total_length == 0){
ret = -1;
goto __exit;
}
mp3_player_end_frame = 0; mp3_player_end_frame = 0;
if(aofei_mp3_player_init()==-1)return; if(aofei_mp3_player_init()==-1){
ret = -1;
goto __exit;
}
if(ret>=0){ if(ret>=0){
aofei_mp3_decoder->read_ptr = mp3_buff; aofei_mp3_decoder->read_ptr = mp3_buff;
@@ -457,6 +483,7 @@ void aofei_mp3_player(char *file_name)
app_codec_pa_close(); app_codec_pa_close();
} }
__exit:
if(aofei_mp3_decoder!=NULL){ if(aofei_mp3_decoder!=NULL){
mp3_decoder_delete(aofei_mp3_decoder); mp3_decoder_delete(aofei_mp3_decoder);
aofei_mp3_decoder = NULL; aofei_mp3_decoder = NULL;
@@ -480,6 +507,13 @@ void aofei_mp3_player(char *file_name)
total_length = 0; total_length = 0;
mp3_player_end_frame = 0; mp3_player_end_frame = 0;
mp3_player_status = 0; mp3_player_status = 0;
mp3_player_last_result = ret;
}
/* 播放调用是同步的,队列线程可在返回后读取本次结果决定是否使用本地兜底。 */
int aofei_mp3_player_get_result(void)
{
return mp3_player_last_result;
} }
@@ -29,6 +29,9 @@
// 在文件顶部添加状态变量 // 在文件顶部添加状态变量
static uint8_t speech_detect_count = 0; // 连续语音帧计数器 static uint8_t speech_detect_count = 0; // 连续语音帧计数器
#define SPEECH_DETECT_THRESHOLD 20 // 连续检测到语音的阈值 #define SPEECH_DETECT_THRESHOLD 20 // 连续检测到语音的阈值
#define PLAYBACK_VAD_GUARD_MS 800 // 屏蔽播放结束后的尾音和音频通路切换噪声
#define VAD_FRAME_BYTES 640 // 20ms @ 16kHz, 16-bit mono
#define VAD_PREROLL_FRAMES 25 // 保留500ms预录音,避免吞掉用户首字
uint8_t no_speech_timeout = 0; uint8_t no_speech_timeout = 0;
extern int g_mic_working; extern int g_mic_working;
@@ -57,6 +60,9 @@ static int64_t no_sound_start_time = 0;
static int64_t cmd_wait_start_time = 0; static int64_t cmd_wait_start_time = 0;
static bool first_sound_detected = false; static bool first_sound_detected = false;
static int64_t recording_start_time = 0; // 记录录音开始时间(首次检测到声音时) static int64_t recording_start_time = 0; // 记录录音开始时间(首次检测到声音时)
static uint8_t vad_preroll[VAD_PREROLL_FRAMES][VAD_FRAME_BYTES];
static size_t vad_preroll_next = 0;
static size_t vad_preroll_count = 0;
static int vad_tts_playback_finished_status = 0; static int vad_tts_playback_finished_status = 0;
// 统计 // 统计
@@ -101,6 +107,9 @@ void set_state(system_state_t new_state) {
no_sound_start_time = now; no_sound_start_time = now;
first_sound_detected = false; first_sound_detected = false;
speech_frame_count = 0; speech_frame_count = 0;
speech_detect_count = 0;
vad_preroll_next = 0;
vad_preroll_count = 0;
g_mic_working = 0; g_mic_working = 0;
break; break;
case STATE_RECORDING_ACTIVE: case STATE_RECORDING_ACTIVE:
@@ -290,17 +299,55 @@ void vad_leave_message(void)
audio_packet_type = 4; audio_packet_type = 4;
vad_leave_message_flag = 1; vad_leave_message_flag = 1;
/* A previous conversation may still be parked in WAITING_COMMAND. NFC
* leave-message recording must enter IDLE first, otherwise g_triggered is
* left pending forever and its 10-second no-speech timer never starts. */
set_state(STATE_IDLE);
vad_audio_trigger_record(); vad_audio_trigger_record();
} }
int vad_leave_message_is_active(void)
{
return vad_leave_message_flag != 0 || audio_packet_type == 4;
}
/* The conversation key has priority over an unfinished NFC leave-message
* transaction. Clear every local latch before the normal conversation prompt
* is queued so that its recording can never inherit packetType=4. */
int vad_cancel_leave_message(void)
{
int was_active = vad_leave_message_is_active();
if (!was_active) return 0;
printf("cancel pending leave message\r\n");
vad_leave_message_flag = 0;
g_triggered = false;
audio_packet_type = 0;
no_speech_timeout = 0;
speech_detect_count = 0;
vad_preroll_next = 0;
vad_preroll_count = 0;
key_start_leave = 0;
if (vad_audio_is_recording()) {
set_state(STATE_IDLE);
vad_audio_stop_no_wait_resp();
} else {
set_state(STATE_IDLE);
}
return 1;
}
// 主处理函数 - 需要在循环中定期调用 // 主处理函数 - 需要在循环中定期调用
void vad_audio_process(void) { void vad_audio_process(void) {
size_t bytes_read = 0; size_t bytes_read = 0;
int64_t now = hal_os_up_time_ms(); int64_t now = hal_os_up_time_ms();
static size_t buffer_offset = 0; static size_t buffer_offset = 0;
uint8_t mic_buf[640]={0}; uint8_t mic_buf[VAD_FRAME_BYTES]={0};
int record_len = 640; int record_len = VAD_FRAME_BYTES;
uint16 frame[320]; uint16 frame[320];
int index = 0, i = 0; int index = 0, i = 0;
@@ -308,7 +355,6 @@ void vad_audio_process(void) {
switch (g_state) { switch (g_state) {
case STATE_IDLE: case STATE_IDLE:
no_speech_timeout = 0; no_speech_timeout = 0;
vad_leave_message_flag = 0;
speech_detect_count = 0; // 空闲时清零计数器 speech_detect_count = 0; // 空闲时清零计数器
vad_tts_playback_finished_status = 0; vad_tts_playback_finished_status = 0;
@@ -326,15 +372,19 @@ void vad_audio_process(void) {
//每次读取640字节音频数据 //每次读取640字节音频数据
ql_audio_record_read(record_hd, mic_buf, &record_len); ql_audio_record_read(record_hd, mic_buf, &record_len);
//如果检测到声音再去传输 if (g_state == STATE_RECORDING_WAIT_SOUND) {
// if(first_sound_detected){ /* 首次开口确认前不向服务端发送静音。只在本地保留最近500ms,
//实际上每次编码需要输入1152字节的PCM数据 * 确认开口时再回填,防止截掉用户首字。 */
if(hal_os_rb_write(ring_sender, (uint8*)mic_buf, record_len, 25)>0){ memcpy(vad_preroll[vad_preroll_next], mic_buf, VAD_FRAME_BYTES);
vad_preroll_next = (vad_preroll_next + 1) % VAD_PREROLL_FRAMES;
//quec_usbcdc_write((uint8*)mic_buf, record_len); if (vad_preroll_count < VAD_PREROLL_FRAMES) {
ql_rtos_semaphore_release(sender_sem); //告诉线程去获取录音数据 vad_preroll_count++;
}
} else {
if(hal_os_rb_write(ring_sender, (uint8*)mic_buf, record_len, 25)>0){
ql_rtos_semaphore_release(sender_sem);
}
} }
// }
#if 1 #if 1
for(index=0; index<320; index++) for(index=0; index<320; index++)
{ {
@@ -347,6 +397,12 @@ void vad_audio_process(void) {
// 首次声音检测 // 首次声音检测
if (!first_sound_detected) { if (!first_sound_detected) {
/* 播放刚结束时扬声器尾音和PA切换噪声容易被FVAD认为用户开口。 */
if ((now - no_sound_start_time) < PLAYBACK_VAD_GUARD_MS) {
speech_detect_count = 0;
buffer_offset = 0;
break;
}
if (current_vad == VAD_STATE_ACTIVE) { if (current_vad == VAD_STATE_ACTIVE) {
speech_detect_count++; speech_detect_count++;
printf("Speech frame count: %d\r\n", speech_detect_count); printf("Speech frame count: %d\r\n", speech_detect_count);
@@ -363,8 +419,23 @@ void vad_audio_process(void) {
} }
#endif #endif
first_sound_detected = true; first_sound_detected = true;
set_state(STATE_RECORDING_ACTIVE); {
size_t first = (vad_preroll_next + VAD_PREROLL_FRAMES - vad_preroll_count) % VAD_PREROLL_FRAMES;
size_t n;
int preroll_queued = 0;
for (n = 0; n < vad_preroll_count; n++) {
size_t frame_index = (first + n) % VAD_PREROLL_FRAMES;
if (hal_os_rb_write(ring_sender, vad_preroll[frame_index], VAD_FRAME_BYTES, 25) > 0) {
preroll_queued = 1;
}
}
if (preroll_queued) {
ql_rtos_semaphore_release(sender_sem);
}
vad_preroll_count = 0;
}
set_state(STATE_RECORDING_ACTIVE);
printf("First speech confirmed by FVAD (continuous %d frames)", SPEECH_DETECT_THRESHOLD); printf("First speech confirmed by FVAD (continuous %d frames)", SPEECH_DETECT_THRESHOLD);
} }
} else { } else {
@@ -419,13 +490,18 @@ void vad_audio_process(void) {
break; break;
case STATE_WAITING_COMMAND: case STATE_WAITING_COMMAND:
if(vad_tts_playback_finished_status == 1) { if(vad_tts_playback_finished_status == 1) {
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); printf("finished_status:%d, no_speech_timeout:%d\n", vad_tts_playback_finished_status, no_speech_timeout);
set_state(STATE_IDLE); set_state(STATE_IDLE);
ql_rtos_task_sleep_ms(50); ql_rtos_task_sleep_ms(50);
if(no_speech_timeout == 0&&vad_leave_message_flag==0) { if(no_speech_timeout == 0&&vad_leave_message_flag==0) {
vad_audio_trigger_record(); vad_audio_trigger_record();
} }
if(leave_message_finished) {
vad_leave_message_flag = 0;
audio_packet_type = 0;
}
} }
key_start_leave = 0; key_start_leave = 0;
@@ -32,6 +32,7 @@
#define PCM_FRAME_LENGTH 1152 #define PCM_FRAME_LENGTH 1152
#define CLOUD_TTS_ERROR_URL_FORMAT \ #define CLOUD_TTS_ERROR_URL_FORMAT \
"http://101.35.224.118:8080/assets/roles/banban/tts_error/tts_error%02u.mp3" "http://101.35.224.118:8080/assets/roles/banban/tts_error/tts_error%02u.mp3"
#define LOCAL_TTS_ERROR_PATH "U:/tts_error.mp3"
static int g_sim_ready = 0; static int g_sim_ready = 0;
@@ -53,7 +54,10 @@ static int onmessage_status = -1;
volatile int tts_playing = 0; volatile int tts_playing = 0;
unsigned char wss_connect_start = 0; unsigned char wss_connect_start = 0;
static volatile int player_mq_couter = 0; static volatile int player_mq_couter = 0;
static unsigned char audio_in_task_cancel = 0; /* Set while a barge-in prompt bridges the old and new sessions. The old
* server response can still deliver URLs after its player queue was cleared. */
static volatile int barge_in_transition = 0;
static volatile unsigned char audio_in_task_cancel = 0;
static unsigned char xiaoice_wss_connect_status = 0; static unsigned char xiaoice_wss_connect_status = 0;
static unsigned char wss_connect_counter = 0; static unsigned char wss_connect_counter = 0;
@@ -66,13 +70,20 @@ extern int mqtt_task_run_flag;
static unsigned char shine_init = 0; static unsigned char shine_init = 0;
extern int fisrt_net_start; extern int fisrt_net_start;
extern u8 power_down_status; extern u8 power_down_status;
extern int aofei_mp3_player_get_result(void);
extern int vad_leave_message_is_active(void);
extern int vad_cancel_leave_message(void);
void sendAuthentication(void);
void vad_start_func(void);
void xiaoice_close_all_audio(void);
int audio_packet_type = 0; int audio_packet_type = 0;
typedef struct ice_work_item_s typedef struct ice_work_item_s
{ {
int type; //0-tts_out 1-session_continue 2-session_end 3-error int type; //0-tts_out 1-session_continue 2-session_end 3-云端超时提示(失败时播放本地兜底)
int error; int error;
int del_url; int del_url;
void *data; void *data;
@@ -195,15 +206,11 @@ static void remove_underscores(char *str) {
*dst = '\0'; // 添加字符串结束符 *dst = '\0'; // 添加字符串结束符
} }
/* /* 生成 banban 角色的随机云端对话超时提示音 URL。 */
* 将后台下发的任意角色对话超时音频 URL 改为 banban 角色的 HTTP 地址, static int build_random_tts_error_url(char *url, size_t url_size)
* 并在 tts_error00~tts_error09 中随机选择一条。
*/
static int randomize_tts_error_url(char *url, size_t url_size)
{ {
static uint32_t random_sequence = 0; static uint32_t random_sequence = 0;
static unsigned int last_random_index = 10; static unsigned int last_random_index = 10;
char *file_name;
uint32_t random_value; uint32_t random_value;
unsigned int random_index; unsigned int random_index;
int written; int written;
@@ -212,14 +219,6 @@ static int randomize_tts_error_url(char *url, size_t url_size)
return 0; return 0;
} }
file_name = strrchr(url, '/');
file_name = (file_name == NULL) ? url : file_name + 1;
if(strncmp(file_name, "tts_error", strlen("tts_error")) != 0 &&
strncmp(file_name, "tts_err", strlen("tts_err")) != 0){
return 0;
}
/* /*
* 某些设备上硬件随机接口可能持续返回 0,因此混合伪随机数、系统 tick * 某些设备上硬件随机接口可能持续返回 0,因此混合伪随机数、系统 tick
* 和递增序列;若仍与上一次相同,则向后偏移,避免连续重复播放。 * 和递增序列;若仍与上一次相同,则向后偏移,避免连续重复播放。
@@ -245,6 +244,29 @@ static int randomize_tts_error_url(char *url, size_t url_size)
return 1; return 1;
} }
/*
* 后台下发任意角色的对话超时 URL 时,统一替换成 banban 的随机云端资源。
* 非对话超时 URL 保持不变。
*/
static int randomize_tts_error_url(char *url, size_t url_size)
{
char *file_name;
if(url == NULL || url_size == 0){
return 0;
}
file_name = strrchr(url, '/');
file_name = (file_name == NULL) ? url : file_name + 1;
if(strncmp(file_name, "tts_error", strlen("tts_error")) != 0 &&
strncmp(file_name, "tts_err", strlen("tts_err")) != 0){
return 0;
}
return build_random_tts_error_url(url, url_size);
}
void add_file_to_play_player_queue(char *path, int type) void add_file_to_play_player_queue(char *path, int type)
{ {
ice_work_item *item = NULL; ice_work_item *item = NULL;
@@ -299,6 +321,14 @@ static void onmessage_text(rws_socket socket, const char *text, const unsigned i
return; return;
} }
/* The new session id is generated only when recording starts. Until
* then, every TTS message on this socket belongs to the interrupted
* session and must not be allowed to refill the cleared player queue. */
if (barge_in_transition) {
printf("drop stale websocket message during barge-in\r\n");
return;
}
// printf("message(txt), %d(byte): %s ", len, text); // printf("message(txt), %d(byte): %s ", len, text);
char url_buff[128]={0x00}; char url_buff[128]={0x00};
@@ -323,6 +353,7 @@ static void onmessage_text(rws_socket socket, const char *text, const unsigned i
char *ret_str2 = NULL; char *ret_str2 = NULL;
size_t url_len = 0; size_t url_len = 0;
int is_tts_error_url = 0; int is_tts_error_url = 0;
int is_message_ok_url = 0;
if(ret_str1 == NULL){ if(ret_str1 == NULL){
ret_str1 = strstr(text, "http://"); ret_str1 = strstr(text, "http://");
} }
@@ -339,6 +370,14 @@ static void onmessage_text(rws_socket socket, const char *text, const unsigned i
} }
memcpy(url_buff, ret_str1, url_len); memcpy(url_buff, ret_str1, url_len);
url_buff[url_len] = '\0'; url_buff[url_len] = '\0';
is_message_ok_url = strstr(url_buff, "message_ok") != NULL;
/* A cancelled NFC transaction may still have a response in flight.
* Never let that acknowledgement enter a normal conversation. */
if(is_message_ok_url && !vad_leave_message_is_active()){
printf("drop stale leave message ack\r\n");
return;
}
/* 两个角色统一使用 banban 的 HTTP 随机超时提示音 */ /* 两个角色统一使用 banban 的 HTTP 随机超时提示音 */
is_tts_error_url = randomize_tts_error_url(url_buff, sizeof(url_buff)); is_tts_error_url = randomize_tts_error_url(url_buff, sizeof(url_buff));
@@ -363,7 +402,7 @@ static void onmessage_text(rws_socket socket, const char *text, const unsigned i
tts_play_status = 2; tts_play_status = 2;
} }
if(is_tts_error_url == 1 || strstr(url_buff, "message_ok")!=NULL){ if(is_tts_error_url == 1 || is_message_ok_url){
printf("tts_error or message_ok\n"); printf("tts_error or message_ok\n");
vad_set_speech_timeout_flag(1); vad_set_speech_timeout_flag(1);
} }
@@ -372,7 +411,8 @@ static void onmessage_text(rws_socket socket, const char *text, const unsigned i
if(tts_playing==2) return; if(tts_playing==2) return;
add_file_to_play_player_queue(url_buff, 0); /* 对话超时提示使用 type=3,云端播放失败时由播放线程切换到本地兜底。 */
add_file_to_play_player_queue(url_buff, is_tts_error_url == 1 ? 3 : 0);
} }
} }
@@ -745,6 +785,9 @@ void generateSessionId(void)
#if 1 #if 1
void sendStartPacket(void) { void sendStartPacket(void) {
generateSessionId(); generateSessionId();
/* sessionId now identifies the new conversation. Late URLs from the old
* conversation will fail the existing session-id check below. */
barge_in_transition = 0;
generateDeviceId(); generateDeviceId();
gSequenceNumber = 0; gSequenceNumber = 0;
@@ -865,13 +908,19 @@ extern const char *mp3_file[];
int app_play_tips_file(int index) int app_play_tips_file(int index)
{ {
int file_valid_check = 0; int file_valid_check = 0;
int access_ret = 0;
printf("xiaoice_play_index:%d\n", index); printf("xiaoice_play_index:%d\n", index);
//正在录音过程中不允许播放 //正在录音过程中不允许播放
// if(xiaoice_get_tts_play_status() || get_start_mic_flag()==1) return -1; // if(xiaoice_get_tts_play_status() || get_start_mic_flag()==1) return -1;
if(power_down_status!=1){ if(power_down_status!=1){
if(vad_audio_is_recording() || tts_playing!=0 || fota_start==1 || power_down_status==2 || get_sleep_mode()==1) return -1; if(vad_audio_is_recording() || tts_playing!=0 || fota_start==1 || power_down_status==2 || get_sleep_mode()==1) {
printf("tips blocked:index=%d vad=%d tts=%d fota=%d power=%d sleep=%d\r\n",
index, vad_audio_is_recording(), tts_playing, fota_start,
power_down_status, get_sleep_mode());
return -1;
}
} }
char filename_buff[64]={0x00}; char filename_buff[64]={0x00};
@@ -881,15 +930,18 @@ int app_play_tips_file(int index)
if(index==0) { if(index==0) {
file_valid_check = 0; file_valid_check = 0;
}else{ }else{
if(ql_access(filename_buff, 0)==0){ access_ret = ql_access(filename_buff, 0);
if(access_ret==0){
file_valid_check = 1; file_valid_check = 1;
}else{ }else{
printf("tips file missing:index=%d path=%s access=%d\r\n",
index, filename_buff, access_ret);
file_valid_check = 2; file_valid_check = 2;
} }
} }
if(file_valid_check!=2){ if(file_valid_check!=2){
if(index==18){ if(index==18 || index==19){
add_file_to_play_player_queue(filename_buff, 2); add_file_to_play_player_queue(filename_buff, 2);
}else{ }else{
add_file_to_play_player_queue(filename_buff, 0); add_file_to_play_player_queue(filename_buff, 0);
@@ -946,7 +998,8 @@ int app_play_ota_tips_file(void)
void app_player_thread(void *arvg) void app_player_thread(void *arvg)
{ {
ice_work_item *player_item=NULL; ice_work_item *player_item=NULL;
int play_ret;
while(1){ while(1){
ql_rtos_queue_wait(player_queue, (u8*)(&player_item), sizeof(ice_work_item *), 0xFFFFFFFF); ql_rtos_queue_wait(player_queue, (u8*)(&player_item), sizeof(ice_work_item *), 0xFFFFFFFF);
@@ -956,11 +1009,28 @@ void app_player_thread(void *arvg)
if(player_item->data){ if(player_item->data){
// printf("xiaoice player! %s\n",player_item->data); // printf("xiaoice player! %s\n",player_item->data);
app_set_app_operation(1); app_set_app_operation(1);
aofei_mp3_player(player_item->data); aofei_mp3_player(player_item->data);
app_set_app_operation(1); play_ret = aofei_mp3_player_get_result();
app_set_app_operation(1);
if(player_mq_couter>0) player_mq_couter--; if(player_mq_couter>0) player_mq_couter--;
/*
* type=3 只用于对话超时提示。云端连接、HTTP、读取或播放器初始化
* 失败时才排入本地兜底;如果用户已经开始下一轮录音,则丢弃旧提示。
*/
if(play_ret != 0 && player_item->type == 3 &&
!vad_audio_is_recording() && tts_playing_ret() != 2){
printf("cloud tts error play failed:%d, fallback:%s\r\n",
play_ret, LOCAL_TTS_ERROR_PATH);
if(ql_access(LOCAL_TTS_ERROR_PATH, 0) == 0){
add_file_to_play_player_queue(LOCAL_TTS_ERROR_PATH, 0);
}else{
printf("local tts error fallback missing:%s\r\n", LOCAL_TTS_ERROR_PATH);
}
}
if(player_mq_couter==0){ if(player_mq_couter==0){
printf("player_mq_couter==0\r\n"); printf("player_mq_couter==0\r\n");
@@ -1001,26 +1071,69 @@ void app_player_thread(void *arvg)
int tts_play_interrupt_func(void) int tts_play_interrupt_func(void)
{ {
int playback_interrupted = 0;
int leave_message_cancelled = 0;
/* A short press explicitly starts a normal conversation. Cancel an NFC
* leave-message before the generic "already recording" guard, otherwise a
* pending/recording leave-message can leak packetType=4 into this session. */
leave_message_cancelled = vad_cancel_leave_message();
if(leave_message_cancelled){
uint64_t cancel_start;
/* Let the sender task discard buffered PCM and its encoder without
* emitting END. Only then close the old leave-message socket. */
audio_in_task_cancel = 1;
ql_rtos_semaphore_release(sender_sem);
cancel_start = hal_os_up_time_ms();
while(audio_in_task_cancel &&
hal_os_up_time_ms() - cancel_start < OTA_AUDIO_STOP_TIMEOUT_MS){
ql_rtos_task_sleep_ms(20);
}
if(audio_in_task_cancel){
printf("wait leave message sender cancel timeout\r\n");
}
talkingq_ws_disconnect();
}
//如果在vad状态,则直接返回 //如果在vad状态,则直接返回
if(vad_audio_is_recording()||g_mic_working) return -1; if(vad_audio_is_recording()||g_mic_working) return -1;
g_mic_working = 1;//等待转为vad状态就清0 或者拾音超时 或者ws没有正常 g_mic_working = 1;//等待转为vad状态就清0 或者拾音超时 或者ws没有正常
/* Remember whether this key press is interrupting TTS. tts_playing == 2
* is only a cancellation signal for the old player; it must not leak into
* the next conversation, otherwise prompts and error audio are discarded. */
playback_interrupted = (tts_playing != 0 || player_mq_couter > 0);
barge_in_transition = playback_interrupted;
xiaoice_close_all_audio(); xiaoice_close_all_audio();
talkingq_ws_connect(); talkingq_ws_connect();
if(get_xiaoice_connected_status()!=2) { if(get_xiaoice_connected_status()!=2) {
/* Let the caller play its local network-error prompt. */
tts_play_status = 0;
tts_playing = 0;
barge_in_transition = 0;
g_mic_working = 0; g_mic_working = 0;
return -2; return -2;
} }
if(tts_playing==2){ if(playback_interrupted){
printf("vad_start_func0\n"); printf("barge_in_prompt\n");
vad_start_func();//如果是打断就不播报 /* The old player has finished consuming the cancellation state. */
tts_play_status = 0;
tts_playing = 0;
/* Use the same local-prompt flow as ai_active_zh.mp3. Queue type 2
* starts VAD after playback; fall back to VAD if the file is absent. */
if(app_play_tips_file(19) != 0){
vad_start_func();
}
}else{ }else{
app_play_tips_file(18); app_play_tips_file(18);
} }
return 0;
} }
void stop_player_func(void) void stop_player_func(void)
@@ -1028,6 +1141,7 @@ void stop_player_func(void)
vad_audio_stop_record(); vad_audio_stop_record();
xiaoice_close_all_audio(); xiaoice_close_all_audio();
tts_play_status = 0;
tts_playing = 0; tts_playing = 0;
} }
@@ -1147,9 +1261,19 @@ static void check_connect_thread_entry(void *parameter)
printf("tts_error_set_speech1\n"); printf("tts_error_set_speech1\n");
vad_set_speech_timeout_flag(1); vad_set_speech_timeout_flag(1);
app_play_tips_file(9); /*
//talkingq_ws_send_text("REQUEST_PROMPT_SOUND:tts_error"); * 后台没有返回任何音频 URL 时,也先由设备生成随机云端提示音。
//改为播放本地音频 听不清 请再说一遍 * type=3 会在 HTTP 下载或播放初始化失败时自动切换到本地音频。
*/
{
char cloud_tts_error_url[128] = {0};
if(build_random_tts_error_url(cloud_tts_error_url,
sizeof(cloud_tts_error_url)) == 1){
add_file_to_play_player_queue(cloud_tts_error_url, 3);
}else{
app_play_tips_file(9);
}
}
} }
#endif #endif
@@ -1194,9 +1318,16 @@ void xiaoice_close_all_audio(void)
timeout = hal_os_up_time_ms(); timeout = hal_os_up_time_ms();
while(tts_play_status&&tts_playing!=2){ /* The queue counter also includes the item currently owned by the
* player task. Wait for that item to observe tts_playing == 2 and
* finish cleanup before the cancellation state is reused/reset. */
while(player_mq_couter>0){
ql_rtos_task_sleep_ms(20); ql_rtos_task_sleep_ms(20);
if(hal_os_up_time_ms()-timeout>10000) break; if(hal_os_up_time_ms()-timeout>OTA_AUDIO_STOP_TIMEOUT_MS){
printf("wait player stop timeout, counter:%d\r\n",
player_mq_couter);
break;
}
} }
} }
#endif #endif
@@ -1216,9 +1347,34 @@ void app_sender_thread(void *arvg)
ql_rtos_semaphore_wait(sender_sem, 0xFFFFFFFF); ql_rtos_semaphore_wait(sender_sem, 0xFFFFFFFF);
sender_loop: sender_loop:
if(audio_in_task_cancel){
left = rb_get_space_used(ring_sender);
while(left > 0){
int discard_len = left > PCM_FRAME_LENGTH ? PCM_FRAME_LENGTH : left;
hal_os_rb_read(ring_sender, sender_buf, discard_len, 0);
left = rb_get_space_used(ring_sender);
}
if(shine_init != 0){
shine_close(sh_shine);
sh_shine = NULL;
shine_init = 0;
}
audio_packet_type = 0;
audio_in_task_cancel = 0;
printf("leave message sender cancelled\r\n");
continue;
}
if(!app_get_data_call_net_status()) continue; if(!app_get_data_call_net_status()) continue;
/* One recording can release the semaphore many times while the sender
* drains the ring buffer. A stale token must never create an empty
* session after the previous encoder has already been closed. */
left = rb_get_space_used(ring_sender);
if(shine_init == 0 && left <= 0){
continue;
}
if(shine_init==0){ if(shine_init==0){
shine_set_config_mpeg_defaults(&(sh_config.mpeg)); //--init sh_config struct shine_set_config_mpeg_defaults(&(sh_config.mpeg)); //--init sh_config struct
sh_config.wave.channels=PCM_MONO; //PCM_STEREO;//PCM_MONO; sh_config.wave.channels=PCM_MONO; //PCM_STEREO;//PCM_MONO;
@@ -1272,6 +1428,9 @@ sender_loop:
shine_init = 0; shine_init = 0;
audio_packet_type = 0; audio_packet_type = 0;
/* Discard semaphore tokens already represented by the PCM that this
* session drained. Recording has stopped, so no new frames race here. */
while(ql_rtos_semaphore_wait(sender_sem, 0) == 0) {}
} }
} }
} }
Binary file not shown.