#include #include #include #include "cJSON.h" #include "app_inc.h" #include "talkingq_wss.h" #include "ql_rtos.h" #include "ql_application.h" #include "ql_audio.h" #include "ql_gpio.h" #include "sockets.h" #include "ql_uart.h" #include "ql_func.h" #include "netdb.h" #include "ql_sim.h" #include "ql_dev.h" #include "ql_wtd.h" #include "ql_fs.h" #include "ql_fota.h" #include "ql_sim.h" #include "ql_ping.h" #include "ql_nw.h" #include "ql_atcmd.h" #include "ql_platform.h" #include "layer3.h" #include "ql_uart.h" #include "hal_api.h" #include "fvad.h" #define PCM_FRAME_LENGTH 1152 #define CLOUD_TTS_ERROR_URL_FORMAT \ "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; int g_mic_working = 0; static ql_queue_t player_queue = NULL; ql_sem_t sender_sem = NULL; ql_sem_t vad_check_sem = NULL; //ql_sem_t check_connect_sem = NULL; struct ringbuffer *ring_sender = NULL; static uint8_t sender_buf[PCM_FRAME_LENGTH]={0}; static int16 mp3_buff[2*PCM_FRAME_LENGTH]; static int16 *ppbuff=mp3_buff; static int onmessage_status = -1; volatile int tts_playing = 0; unsigned char wss_connect_start = 0; static volatile int player_mq_couter = 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 unsigned char audio_in_task_cancel = 0; static unsigned char xiaoice_wss_connect_status = 0; static unsigned char wss_connect_counter = 0; int fota_start = 0; int mqtt_task_run_flag = 0; extern int sub_topic_status; extern int mqtt_task_run_flag; static unsigned char shine_init = 0; extern int fisrt_net_start; extern u8 power_down_status; extern int aofei_mp3_player_get_result(void); void sendAuthentication(void); void vad_start_func(void); void xiaoice_close_all_audio(void); int audio_packet_type = 0; typedef struct ice_work_item_s { int type; //0-tts_out 1-session_continue 2-session_end 3-云端超时提示(失败时播放本地兜底) int error; int del_url; void *data; }ice_work_item; static uint32_t gSequenceNumber = 0; #define DEVICE_ID_SIZE 22 #define SESSION_ID_SIZE 33 typedef struct { uint32_t sequenceNumber; uint8_t packetType; uint32_t dataSize; uint8_t *data; } AudioPacket; char talkingq_host[64]={0}; char deviceId[DEVICE_ID_SIZE] = {0}; char sessionId[SESSION_ID_SIZE] = {0}; char serialNumber[SESSION_ID_SIZE] = {0}; struct librws_app { int init; rws_socket *socket; }; typedef struct librws_app* librws_app_t; static struct librws_app _app = {0}; static librws_app_t app = &_app; static ql_sem_t ws_connect_sem = NULL; extern int fota_start; #define OTA_AUDIO_STOP_TIMEOUT_MS 3000 #define OTA_AUDIO_STOP_POLL_MS 20 void xiaoice_clear_player_mq(void); static int tts_play_status = 0;//1播放中 2播放完成 int xiaoice_get_tts_play_flag(void) { return tts_play_status; } int xiaoice_get_play_counter(void) { return player_mq_couter; } int tts_playing_ret(void) { return tts_playing; } unsigned char get_xiaoice_connected_status(void) { return xiaoice_wss_connect_status; } static void onopen(rws_socket socket) { if (socket != app->socket || !app->init) return; wss_connect_counter=0; LOG_D("websocket connected. "); if(ws_connect_sem){ ql_rtos_semaphore_release(ws_connect_sem); } sendAuthentication(); } static void onclose(rws_socket socket) { rws_error error = rws_socket_get_error(socket); int error_code = rws_error_get_code(error); if(error) { LOG_E("websocket disconnect, error: %i, %s ", error_code, rws_error_get_description(error)); } else { LOG_D("websocket disconnect! "); } if (socket != app->socket) return; xiaoice_wss_connect_status = 0; app->socket = NULL; /* The worker owns and releases this socket after the callback. */ RLOGD("onclose end\r\n"); app->init = 0; if (ws_connect_sem) ql_rtos_semaphore_release(ws_connect_sem); hal_led_b_off(); } // 安全修复:添加最大长度限制防止越界 static void remove_underscores(char *str) { if (str == NULL) return; // 安全检测 char *src = str; // 源指针(读位置) char *dst = str; // 目标指针(写位置) size_t max_len = 128; // 最大处理长度限制 size_t processed = 0; while (*src && processed < max_len) { if (*src != '_') { *dst = *src; // 非下划线字符:复制并移动目标指针 dst++; } src++; // 始终移动源指针 processed++; } *dst = '\0'; // 添加字符串结束符 } /* 生成 banban 角色的随机云端对话超时提示音 URL。 */ static int build_random_tts_error_url(char *url, size_t url_size) { static uint32_t random_sequence = 0; static unsigned int last_random_index = 10; uint32_t random_value; unsigned int random_index; int written; if(url == NULL || url_size == 0){ return 0; } /* * 某些设备上硬件随机接口可能持续返回 0,因此混合伪随机数、系统 tick * 和递增序列;若仍与上一次相同,则向后偏移,避免连续重复播放。 */ random_sequence++; random_value = hal_os_get_rand(); random_value ^= ql_pseudo_random_number(); random_value ^= ql_rtos_get_systicks(); random_value ^= random_sequence * 0x9E3779B9U; random_index = random_value % 10; if(random_index == last_random_index){ random_index = (random_index + 1 + (random_sequence % 9)) % 10; } last_random_index = random_index; written = snprintf(url, url_size, CLOUD_TTS_ERROR_URL_FORMAT, random_index); if(written < 0 || (size_t)written >= url_size){ printf("random tts error url buffer too small\n"); return -1; } printf("random cloud tts error index:%02u, url:%s\n", random_index, url); 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) { ice_work_item *item = NULL; // 安全修复:检查path参数有效性 if(path == NULL || strlen(path) == 0){ printf("add_file_to_play_player_queue: invalid path\n"); return; } item = malloc(sizeof(ice_work_item)); if(item == NULL){ printf("add_file_to_play_player_queue: malloc item failed\n"); return; } item->data = malloc(strlen(path)+1); if(item->data == NULL){ printf("add_file_to_play_player_queue: malloc data failed\n"); free(item); return; } item->type = type; // 安全修复:使用strncpy替代sprintf防止溢出 snprintf((char *)item->data, strlen(path)+1, "%s", path); printf("play file:%s\r\n", (char *)item->data); if (ql_rtos_queue_release(player_queue, sizeof(item), &item, 0xFFFFFFFF) != 0) { printf("player_mq rt_mq_send ERR\n"); free(item->data); item->data=NULL; free(item); item = NULL; }else{ player_mq_couter++; printf("player_mq_couter:%d\r\n", player_mq_couter); } } //如果录音后 超过10s 还没有收到信息 提示本轮对话结束 static void onmessage_text(rws_socket socket, const char *text, const unsigned int len) { if (fota_start == 1) { printf("ignore websocket audio during OTA\r\n"); 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); char url_buff[128]={0x00}; char url_buff_bak[128]={0x00}; if(strstr(text, "_START")){ printf("TTS_START\r\n"); //语音交互音频开始 tts_playing = 1; tts_play_status = 1; // aofei_mp3_player_init(); }else if(strstr(text, "TTS_END")){ printf("TTS_END\r\n"); //语音交互音频结束 // tts_playing = 0; tts_play_status = 2; }else if(strstr(text, "END_ACK")||strstr(text, "START_ACK")){ }else{//TTS_URL:audio_url wss_connect_start = 0; char *ret_str1 = strstr(text, "https://"); char *ret_str2 = NULL; size_t url_len = 0; int is_tts_error_url = 0; if(ret_str1 == NULL){ ret_str1 = strstr(text, "http://"); } if(ret_str1 != NULL){ ret_str2 = strstr(ret_str1, ".mp3"); } if(ret_str1!=NULL && ret_str2!=NULL){ /* 提取完整的 HTTP/HTTPS MP3 URL,结尾额外包含“.mp3”四个字符 */ url_len = (size_t)(ret_str2 - ret_str1) + strlen(".mp3"); if(url_len >= sizeof(url_buff)){ printf("audio url too long:%d\n", (int)url_len); return; } memcpy(url_buff, ret_str1, url_len); url_buff[url_len] = '\0'; /* 两个角色统一使用 banban 的 HTTP 随机超时提示音 */ is_tts_error_url = randomize_tts_error_url(url_buff, sizeof(url_buff)); if(is_tts_error_url < 0){ return; } memcpy(url_buff_bak, url_buff, strlen(url_buff)+1); //先把文件下载存入到60k的全局数组里面 然后启动mp3播放 remove_underscores(url_buff_bak); //TARGET_DEVICE_REGISTERED_URL:{sound_url} if(strstr(text, "PROMPT_SOUND_URL")==NULL&&strstr(text, "TARGET_DEVICE_REGISTERED_URL")==NULL \ &&strstr(text, "NFC_SOUND_URL")==NULL){//提示音不需要判断sessionID if(strstr(url_buff_bak,sessionId)==NULL){ return; } }else{ tts_play_status = 2; } if(is_tts_error_url == 1 || strstr(url_buff, "message_ok")!=NULL){ printf("tts_error or message_ok\n"); vad_set_speech_timeout_flag(1); } //如果打断了 就不去做播放 if(tts_playing==2) return; /* 对话超时提示使用 type=3,云端播放失败时由播放线程切换到本地兜底。 */ add_file_to_play_player_queue(url_buff, is_tts_error_url == 1 ? 3 : 0); } } } static void onmessage_bin(rws_socket socket, const void * data, const unsigned int len) { LOG_D("message(bin): %d ", len); return; } int talkingq_ws_connect() { int result; int ret = -1; char wss_path[TALKINGQ_PATH_CAPACITY]={0}; char ws_talkingq_url[TALKINGQ_URL_CAPACITY]={0}; int port; // char session_id[56]={0}; // char ip_name[56]={0}; RLOGD("talkingq_ws_connect 1\r\n"); if(app->init == 1) { LOG_E("the websocket connection has been opened. "); return (-1); } RLOGD("talkingq_ws_connect 2\r\n"); app->init = 1;//防止重复进入等待 xiaoice_wss_connect_status = 1; if(ws_connect_sem==NULL){ ql_rtos_semaphore_create(&ws_connect_sem, 0); } if (ws_connect_sem) while (ql_rtos_semaphore_wait(ws_connect_sem, 0) == 0) {} hal_os_get_talkingq_url(ws_talkingq_url); printf("ws_talkingq_url %s",ws_talkingq_url); if (talkingq_parse_wss_url(ws_talkingq_url, talkingq_host, sizeof(talkingq_host), &port, wss_path, sizeof(wss_path)) != 0) { LOG_E("Invalid WSS endpoint; cleartext fallback is disabled\n"); app->init = 0; xiaoice_wss_connect_status = 0; return -1; } app->socket = rws_socket_create(); if(app->socket == NULL) { app->init = 0; xiaoice_wss_connect_status = 0; LOG_E("librws socket create failed. "); return (-1); } printf("talkingq host %s , port %d",talkingq_host,port); /* Server-authenticated TLS; application authentication stays in onopen. */ rws_socket_set_url(app->socket, "wss", talkingq_host, port, wss_path); rws_socket_set_server_cert(app->socket, talkingq_root_ca, talkingq_root_ca_size); rws_socket_set_on_connected(app->socket, &onopen); rws_socket_set_on_disconnected(app->socket, &onclose); rws_socket_set_on_received_text(app->socket, &onmessage_text); rws_socket_set_on_received_bin(app->socket, &onmessage_bin); //rws_socket_set_custom_mode(app->socket); ret = rws_socket_connect(app->socket); if(ret == rws_false) { rws_socket_disconnect_and_release(app->socket); app->socket = NULL; app->init = 0; LOG_E("rws_socket_connect failed. "); ret = -1; } else { ret = -1; LOG_D("try rws_socket_connect. "); if(ws_connect_sem){ result = ql_rtos_semaphore_wait(ws_connect_sem, 45000); if(result == 0 && app->init && app->socket && rws_socket_is_connected(app->socket)){ LOG_I("ws connect okay"); wss_connect_counter = 0; ret = 0; }else{ /* Stop the pending worker before allowing a new attempt. */ if (app->socket) rws_socket_disconnect_and_release(app->socket); app->socket = NULL; app->init = 0; LOG_E("ws connect failed or wait timeout"); wss_connect_counter++; } }else{ app->init = 0; LOG_E("ws_connect_sem null"); } } if(ret == 0 && app->init == 1){ xiaoice_wss_connect_status = 2; hal_led_b_on(); } else{ xiaoice_wss_connect_status = 0; hal_led_b_off(); } return ret; } int talkingq_ws_disconnect() { audio_packet_type = 0; g_mic_working = 0; hal_led_b_off(); if(app->init == 0) { LOG_W("no websocket connection. "); return (-1); } rws_socket_disconnect_and_release(app->socket); LOG_I("try disconnect websocket connection. "); app->socket = NULL; app->init = 0; return 0; } int talkingq_ws_send_text(char *senddata) { if(app->init == 0) { LOG_W("no websocket connection. "); return (-1); } LOG_D("talkingq_wss_send_text string = %s, len = %d", senddata, strlen(senddata)); rws_socket_send_text(app->socket, senddata); return 0; } int talkingq_ws_send_bin(uint8_t *senddata, int len) { if(app->init == 0) { LOG_W("no websocket connection. "); return (-1); } // LOG_D("talkingq_wss_send_bin, len = %d", len); rws_socket_send_bin(app->socket, senddata, len,2,rws_true); return 0; } void sendRecordingAudioPacket(AudioPacket *packet); /*串口AT指令做设置*/ void generateDeviceId(void) { char temp_DeviceId[32]; memset(temp_DeviceId, 0, sizeof(temp_DeviceId)); hal_os_get_device_id(temp_DeviceId); snprintf(deviceId, strlen(temp_DeviceId)+1, temp_DeviceId); printf("temp_DeviceId:%s\n",temp_DeviceId); } void generateSerialNumber(void) { char temp_SerialNumber[32]; memset(temp_SerialNumber, 0, sizeof(temp_SerialNumber)); hal_os_get_device_sn(temp_SerialNumber); snprintf(serialNumber, strlen(temp_SerialNumber)+1, temp_SerialNumber); printf("temp_SerialNumber:%s\n",temp_SerialNumber); } #if 0 #define INT_MAX 2147483647 #define INT_MIN (-1 - INT_MAX) void int_to_digits_str(int num, char *buf, int buf_size) { if (buf_size < 2) { // 至少需要存储空字符 if (buf_size > 0) buf[0] = '\0'; return; } unsigned int n; // 用于存储绝对值 // 处理INT_MIN特殊情况(避免溢出) if (num == INT_MIN) { n = (unsigned int)INT_MAX + 1; // 2147483648 } // 处理其他负数 else if (num < 0) { n = (unsigned int)(-num); // 取绝对值 } // 处理正数和0 else { n = (unsigned int)num; } // 处理0特殊情况 if (n == 0) { buf[0] = '0'; buf[1] = '\0'; return; } // 临时存储数字(逆序) char temp[11]; // 足够存储10位数字 int temp_index = 0; // 提取数字(逆序) while (n > 0) { temp[temp_index++] = '0' + (n % 10); n /= 10; } temp[temp_index] = '\0'; // 计算所需空间:数字位数 + 结束符 int required_size = temp_index + 1; // 检查缓冲区大小 if (buf_size < required_size) { buf[0] = '\0'; return; } // 反转数字到缓冲区 for (int i = 0; i < temp_index; i++) { buf[i] = temp[temp_index - 1 - i]; } buf[temp_index] = '\0'; // 终止字符串 } void generateSessionId(void) { static int rand_num = 0; int index = 0; if(rand_num==0){ rand_num = hal_os_get_rand(); }else{ rand_num++; } if(rand_num<10000000) rand_num += 10000000; if(rand_num>99999999) rand_num = 10000000; char buffer[12]; // 足够存储任何int值 int_to_digits_str(rand_num, buffer, sizeof(buffer)); for(index=0;index<8;index++){ sessionId[index]=buffer[index]; } const char charset[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const int charset_size = sizeof(charset) - 1; int i = 0; for(i = 8; i < 32; i++) { index= (hal_os_get_rand()+i) % charset_size; sessionId[i] = charset[index]; } sessionId[32] = '\0'; RLOGE("sessionId:%s\n", sessionId); } #endif /** * @brief 基于本地时间生成32字节sessionId * * 格式说明(32字节): * [0-1] : 年份后两位 (00-99) * [2-3] : 月份 (01-12) * [4-5] : 日期 (01-31) * [6-7] : 小时 (00-23) * [8-9] : 分钟 (00-59) * [10-11]: 秒 (00-59) * [12-13]: 毫秒级计数器(同一秒内递增,保证唯一性) * [14-31]: 随机字符(数字+大写字母) * * 示例:26060914305201A3B7C9D2E4F6G8H0J1K */ void generateSessionId(void) { static unsigned short seq_counter = 0; // 同一秒内的递增计数器 char time_buf[6]; int i = 0; int index = 0; // 获取当前本地时间 hal_os_get_localtime(time_buf); // 同一秒内计数器递增,超过99则回绕 seq_counter++; if (seq_counter > 99) { seq_counter = 0; } // ========== 前14位:时间戳部分(基于本地时间)========== // 将时间数据转为ASCII字符存入sessionId // 年份 (2位) - buf[0] 是年份后两位,如26表示2026年 sessionId[0] = '0' + (time_buf[0] / 10); // 年十位 sessionId[1] = '0' + (time_buf[0] % 10); // 年个位 // 月份 (2位) sessionId[2] = '0' + (time_buf[1] / 10); // 月十位 sessionId[3] = '0' + (time_buf[1] % 10); // 月个位 // 日期 (2位) sessionId[4] = '0' + (time_buf[2] / 10); // 日十位 sessionId[5] = '0' + (time_buf[2] % 10); // 日个位 // 小时 (2位) sessionId[6] = '0' + (time_buf[3] / 10); // 时十位 sessionId[7] = '0' + (time_buf[3] % 10); // 时个位 // 分钟 (2位) sessionId[8] = '0' + (time_buf[4] / 10); // 分十位 sessionId[9] = '0' + (time_buf[4] % 10); // 分个位 // 秒 (2位) sessionId[10] = '0' + (time_buf[5] / 10); // 秒十位 sessionId[11] = '0' + (time_buf[5] % 10); // 秒个位 // 同一秒内序列号 (2位,00-99) sessionId[12] = '0' + (seq_counter / 10); sessionId[13] = '0' + (seq_counter % 10); // ========== 后18位:随机字符部分 ========== const char charset[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const int charset_size = sizeof(charset) - 1; for (i = 14; i < 32; i++) { index = (hal_os_get_rand() + i) % charset_size; sessionId[i] = charset[index]; } sessionId[32] = '\0'; RLOGE("sessionId:%s\n", sessionId); } #if 1 void sendStartPacket(void) { 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(); gSequenceNumber = 0; AudioPacket startPacket = {0}; startPacket.sequenceNumber = gSequenceNumber++; startPacket.packetType = 1; startPacket.dataSize = 0; sendRecordingAudioPacket(&startPacket); // rt_kprintf("sendStartPacket, seq=%u\r\n", startPacket.sequenceNumber); } void sendEndPacket(void) { AudioPacket endPacket = {0}; endPacket.sequenceNumber = gSequenceNumber++; endPacket.packetType = 2; endPacket.dataSize = 0; sendRecordingAudioPacket(&endPacket); // rt_kprintf("sendEndPacket, seq=%u\r\n", endPacket.sequenceNumber); } void sendAudioPacket(const uint8_t* data, size_t dataSize) { AudioPacket audioPacket = {0}; audioPacket.sequenceNumber = gSequenceNumber++; audioPacket.packetType = audio_packet_type; //audioPacket.packetType = 4; audioPacket.dataSize = dataSize; if(audioPacket.dataSize>0){ audioPacket.data = malloc(audioPacket.dataSize); if(audioPacket.data==NULL){ RLOGE("audioPacket.data NULL"); return; } memcpy(audioPacket.data, data, dataSize); } sendRecordingAudioPacket(&audioPacket); // rt_kprintf("sendAudioPacket, seq=%u\r\n", audioPacket.sequenceNumber); if(audioPacket.data){ free(audioPacket.data); } } void sendInterruptPacket(void) { AudioPacket interruptPacket = {0}; interruptPacket.sequenceNumber = gSequenceNumber++; interruptPacket.packetType = 3; interruptPacket.dataSize = 0; sendRecordingAudioPacket(&interruptPacket); } void sendAuthentication() { cJSON *sendAuth = cJSON_CreateObject(); generateDeviceId(); generateSerialNumber(); cJSON_AddItemToObject(sendAuth,"device_id",cJSON_CreateString(deviceId)); cJSON_AddItemToObject(sendAuth,"serial_number",cJSON_CreateString(serialNumber)); // cJSON_AddItemToObject(sendAuth,"deviceId",cJSON_CreateString(deviceId)); //cJSON_AddItemToObject(sendAuth,"serialNumber",cJSON_CreateString(serialNumber)); char *jstr = cJSON_PrintUnformatted(sendAuth); if(jstr){ talkingq_ws_send_text(jstr); free(jstr); } cJSON_Delete(sendAuth); } void sendRecordingAudioPacket(AudioPacket *packet) { uint8_t *sendBuffer=NULL; sendBuffer = malloc(DEVICE_ID_SIZE + SESSION_ID_SIZE + sizeof(uint32_t) * 2 + sizeof(uint32_t) + packet->dataSize); if(sendBuffer==NULL){ RLOGE("sendBuffer NULL"); return; } size_t headerSize = DEVICE_ID_SIZE + SESSION_ID_SIZE + sizeof(packet->sequenceNumber) + sizeof(packet->packetType) + sizeof(packet->dataSize); size_t totalSize = headerSize + packet->dataSize; size_t offset = 0; memcpy(sendBuffer + offset, deviceId, DEVICE_ID_SIZE); offset += DEVICE_ID_SIZE; memcpy(sendBuffer + offset, sessionId, SESSION_ID_SIZE); offset += SESSION_ID_SIZE; memcpy(sendBuffer + offset, &packet->sequenceNumber, sizeof(packet->sequenceNumber)); offset += sizeof(packet->sequenceNumber); memcpy(sendBuffer + offset, &packet->packetType, sizeof(packet->packetType)); offset += sizeof(packet->packetType); memcpy(sendBuffer + offset, &packet->dataSize, sizeof(packet->dataSize)); offset += sizeof(packet->dataSize); memcpy(sendBuffer + offset, packet->data, packet->dataSize); // recorder->save_len = totalSize; // memcpy(recorder->save_buf, (void *)sendBuffer, recorder->save_len); //record_msg_send(recorder, RECORD_MSG_DATA, recorder->save_len); talkingq_ws_send_bin((uint8_t*)sendBuffer, totalSize); free(sendBuffer); // rt_kprintf("sendRecordingAudioPacket - Device ID: %s, Session ID: %s\n", deviceId, sessionId); } #endif extern const char *mp3_file[]; int app_play_tips_file(int index) { int file_valid_check = 0; int access_ret = 0; printf("xiaoice_play_index:%d\n", index); //正在录音过程中不允许播放 // if(xiaoice_get_tts_play_status() || get_start_mic_flag()==1) return -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) { 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}; sprintf(filename_buff, "U:/%s", mp3_file[index]); if(index==0) { file_valid_check = 0; }else{ access_ret = ql_access(filename_buff, 0); if(access_ret==0){ file_valid_check = 1; }else{ printf("tips file missing:index=%d path=%s access=%d\r\n", index, filename_buff, access_ret); file_valid_check = 2; } } if(file_valid_check!=2){ if(index==18 || index==19){ add_file_to_play_player_queue(filename_buff, 2); }else{ add_file_to_play_player_queue(filename_buff, 0); } }else{ return -2; } return 0; } int app_play_ota_tips_file(void) { char filename_buff[64] = {0}; uint32_t timeout; /* OTA owns the audio path from this point on. Stop the active conversation * before queuing the prompt, otherwise recording/TTS rejects or masks it. */ if (vad_audio_is_recording()) { vad_audio_stop_record(); } if (get_xiaoice_connected_status() == 2) { talkingq_ws_disconnect(); } tts_playing = 2; xiaoice_clear_player_mq(); timeout = hal_os_up_time_ms(); while (player_mq_couter > 0) { ql_rtos_task_sleep_ms(OTA_AUDIO_STOP_POLL_MS); if (hal_os_up_time_ms() - timeout > OTA_AUDIO_STOP_TIMEOUT_MS) { printf("OTA wait previous audio timeout, counter:%d\r\n", player_mq_couter); return -3; } } tts_play_status = 0; tts_playing = 0; /* Normal prompts are blocked while fota_start is set. Queue the dedicated * OTA prompt directly after the old conversation has fully stopped. */ snprintf(filename_buff, sizeof(filename_buff), "U:/%s", mp3_file[3]); printf("xiaoice_play_index:3\n"); if (ql_access(filename_buff, 0) != 0) { return -2; } add_file_to_play_player_queue(filename_buff, 0); return player_mq_couter > 0 ? 0 : -1; } void app_player_thread(void *arvg) { ice_work_item *player_item=NULL; int play_ret; while(1){ ql_rtos_queue_wait(player_queue, (u8*)(&player_item), sizeof(ice_work_item *), 0xFFFFFFFF); if(player_item!=NULL){ // if(player_item->type==0){//local mp3 file if(player_item->data){ // printf("xiaoice player! %s\n",player_item->data); app_set_app_operation(1); aofei_mp3_player(player_item->data); play_ret = aofei_mp3_player_get_result(); app_set_app_operation(1); 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){ printf("player_mq_couter==0\r\n"); if(tts_play_status==2){ tts_play_status = 0; if(tts_playing!=2){ vad_tts_playback_finished(); tts_playing = 0; } printf("tts_play_status0==0\r\n"); } if(tts_playing==2){ tts_play_status = 0; printf("tts_play_status1==0\r\n"); } if(player_item->type==1){ printf("vad_leave_message\r\n"); vad_leave_message(); }else if(player_item->type==2){ // vad_tts_playback_finished(); vad_start_func(); } } // } } if(player_item->data){ free(player_item->data); player_item->data = NULL; } free(player_item); player_item = NULL; } } } int tts_play_interrupt_func(void) { int playback_interrupted = 0; //如果在vad状态,则直接返回 if(vad_audio_is_recording()||g_mic_working) return -1; 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(); talkingq_ws_connect(); 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; return -2; } if(playback_interrupted){ printf("barge_in_prompt\n"); /* 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{ app_play_tips_file(18); } return 0; } void stop_player_func(void) { vad_audio_stop_record(); xiaoice_close_all_audio(); tts_play_status = 0; tts_playing = 0; } void vad_start_func(void) { set_state(STATE_IDLE); //ql_rtos_task_sleep_ms(50); vad_audio_trigger_record(); } static void check_connect_thread_entry(void *parameter) { uint32 time_counter = 0; uint32 cfun_recorder = 0; int sim_status = 0; int per_socket_status = 0; int ret = 0; QL_NW_REG_STATUS_INFO_T ql_nw_reg_status; while(1) { #if 0 if(app_get_data_call_net_status() && per_net_status==0){ //app_play_tips_file(1); per_net_status = 1; hal_led_b_on(); }else if(!app_get_data_call_net_status() && per_net_status==1){ app_play_tips_file(2); per_net_status = 0; hal_led_b_off(); } #endif //判断网络状态 //a.如果连续3次连不上后台 //b.查询注网状态 正常则进行重新拨号 //c.注网状态异常,则cfun0/1一次,查询卡状态,如果卡状态异常,则后续不再做cfun0/1操作 //d.卡状态正常,注册网络异常,则每2个小时cfun0/1一次 if(wss_connect_counter>3 && fisrt_net_start==0){ wss_connect_counter = 0; ql_sim_get_card_status(&sim_status); if(sim_status == QL_SIM_STATUS_READY){ g_sim_ready = 1; }else{ g_sim_ready = 0; } ql_nw_get_reg_status(&ql_nw_reg_status); printf("net.state:%d, g_sim_ready:%d\n", ql_nw_reg_status.data_reg.state, g_sim_ready); if(ql_nw_reg_status.data_reg.state==1 || ql_nw_reg_status.data_reg.state==5){ if(g_sim_ready){ //触发拨号 printf("app_data_recall\r\n"); app_data_recall(); } }else{ //两个小时 CFUN01一次 if(cfun_recorder==0 || ql_rtos_get_systicks()-cfun_recorder>2*60*60000/5) { if(g_sim_ready){ printf("ql_dev_set_modem_fun\r\n"); ql_dev_set_modem_fun(QL_DEV_MODEM_MIN_FUN,0); ql_rtos_task_sleep_ms(10000); ql_dev_set_modem_fun(QL_DEV_MODEM_FULL_FUN,0); ql_rtos_task_sleep_ms(10000); } cfun_recorder = ql_rtos_get_systicks(); } } } #if 0 //if(app_get_app_sleep_flag()!=0){ //判断网络状态ok在进行 if(!get_rws_socket_thread_status()&&app_get_data_call_net_status()&&fota_start!=1&&sub_topic_status==1){ if(xiaoice_wss_connect_status!=1){//如果没有正在连接状态 hal_led_b_off(); wss_connect_counter++; talkingq_ws_disconnect(); printf("reconnect wss socket:%d\r\n", wss_connect_counter); ret = talkingq_ws_connect(); if(ret!=0){ /*播报网络异常*/ per_socket_status = 0; app_play_tips_file(2); hal_led_b_off(); } } }else {//如果网络正常 if(app_get_data_call_net_status()){ if(per_socket_status==0){ per_socket_status = 1; app_play_tips_file(11); hal_led_b_on(); } } } //}else{ // ql_rtos_semaphore_wait(check_connect_sem, 0xFFFFFFFF); //} #endif #if 1 //如果后台10s以上没有反馈 则进行重连 if(wss_connect_start==1){ wss_connect_start = 2; time_counter = ql_rtos_get_systicks(); }else if(wss_connect_start==2 && (ql_rtos_get_systicks()-time_counter)>5000/5){ wss_connect_start = 0; tts_playing = 0; audio_packet_type = 0; talkingq_ws_disconnect(); printf("tts_error_set_speech1\n"); vad_set_speech_timeout_flag(1); /* * 后台没有返回任何音频 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 ql_rtos_task_sleep_ms(3000); } } #if 1 void xiaoice_clear_player_mq(void) { ice_work_item *player_item=NULL; while(ql_rtos_queue_wait(player_queue, (u8*)(&player_item), sizeof(ice_work_item *), 0)==0){ if(player_item!=NULL){ if(player_item->data){ free(player_item->data); player_item->data = NULL; } if(player_mq_couter>0) player_mq_couter--; free(player_item); player_item = NULL; } } } void xiaoice_close_all_audio(void) { int timeout=0; app_codec_pa_close();//防止突然关掉的滋滋声 #if 1 if(tts_playing){ tts_playing = 2; sendInterruptPacket(); } if(player_mq_couter>0){ xiaoice_clear_player_mq(); timeout = hal_os_up_time_ms(); /* 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); 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 void app_sender_thread(void *arvg) { int index = 0, cnt = 0; uint8 *data_out = NULL; shine_t sh_shine; shine_config_t sh_config; int left = 0; unsigned char value = 0; while(1) { ql_rtos_semaphore_wait(sender_sem, 0xFFFFFFFF); sender_loop: if(!app_get_data_call_net_status()) continue; if(shine_init==0){ 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.samplerate=16000;//valid samplerates: 44100,48000,32000,22050,24000,16000,11025,12000,8000 sh_config.mpeg.copyright=1; sh_config.mpeg.original=1; sh_config.mpeg.mode=MONO;//STEREO;//MONO; sh_config.mpeg.bitr=16; //64kbps for 16k //32kbps for 8k samplerate sh_shine=shine_initialise(&sh_config); shine_init = 1; talkingq_ws_connect(); sendStartPacket(); } left = rb_get_space_used(ring_sender); if(left0) goto sender_loop; printf("sendEndPacket\n"); data_out=shine_flush(sh_shine,&index); sendAudioPacket(data_out, index); sendEndPacket(); if(sh_shine!=NULL){ shine_close(sh_shine); sh_shine = NULL; } shine_init = 0; audio_packet_type = 0; } } } void app_nfc_sender(int8 *uuid) { int8 uuid_buff[128]; memset(uuid_buff, 0, sizeof(uuid_buff)); sprintf(uuid_buff, "REGISTER_TARGET_DEVICE:%s", uuid); printf("uuid_buff:%s\r\n", uuid_buff); talkingq_ws_connect(); talkingq_ws_send_text(uuid_buff); audio_packet_type = 4; return 0; } void app_vad_check_thread(void *arvg) { while(1) { if(app_get_app_sleep_flag()!=0){ vad_audio_process(); ql_rtos_task_sleep_ms(20); }else{ ql_rtos_semaphore_wait(vad_check_sem, 0xFFFFFFFF); } } } void app_vad_check_sem_release(void) { ql_rtos_semaphore_release(vad_check_sem); } #if 0 void app_check_connect_sem_release(void) { ql_rtos_semaphore_release(check_connect_sem); } #endif int app_ws_talkingq_init() { //one second = 640*50 = 32000 ring_sender = hal_os_rb_create(32000*5); ql_rtos_semaphore_create(&sender_sem, 0); ql_rtos_semaphore_create(&vad_check_sem, 0); //ql_rtos_semaphore_create(&check_connect_sem, 0); ql_rtos_queue_create(&player_queue, sizeof(ice_work_item *), 20); ql_task_t playerTaskRef = NULL; // 安全修复:增加栈大小从2KB到4KB,防止音频处理时栈溢出 ql_rtos_task_create(&playerTaskRef, 1024*4, 100, "app_player", app_player_thread, NULL); ql_task_t checkTaskRef = NULL; ql_rtos_task_create(&checkTaskRef, 1024*3, 102, "check_connect", check_connect_thread_entry, NULL); ql_task_t senderTaskRef = NULL; ql_rtos_task_create(&senderTaskRef, 1024*8, 101, "app_sender", app_sender_thread, NULL); ql_task_t vadTaskRef = NULL; // 安全修复:减小栈大小从20KB到8KB(VAD不需要那么大),节约内存 ql_rtos_task_create(&vadTaskRef, 1024*8, 101, "vad_check", app_vad_check_thread, NULL); return 0; }