Files
banban/ql-application/threadx/interface/custom_app/ws_talkingq.c
T

1373 lines
36 KiB
C

#include <stdlib.h>
#include <string.h>
#include <librws.h>
#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"
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;
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;
int audio_packet_type = 0;
typedef struct ice_work_item_s
{
int type; //0-tts_out 1-session_continue 2-session_end 3-error
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'; // 添加字符串结束符
}
/*
* 将后台下发的任意角色对话超时音频 URL 改为 banban 角色的 HTTP 地址,
* 并在 tts_error00~tts_error09 中随机选择一条。
*/
static int randomize_tts_error_url(char *url, size_t url_size)
{
static uint32_t random_sequence = 0;
static unsigned int last_random_index = 10;
char *file_name;
uint32_t random_value;
unsigned int random_index;
int written;
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;
}
/*
* 某些设备上硬件随机接口可能持续返回 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;
}
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;
}
// 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;
add_file_to_play_player_queue(url_buff, 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();
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;
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) return -1;
}
char filename_buff[64]={0x00};
sprintf(filename_buff, "U:/%s", mp3_file[index]);
if(index==0) {
file_valid_check = 0;
}else{
if(ql_access(filename_buff, 0)==0){
file_valid_check = 1;
}else{
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;
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);
app_set_app_operation(1);
if(player_mq_couter>0) player_mq_couter--;
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);
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;
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);
app_play_tips_file(9);
//talkingq_ws_send_text("REQUEST_PROMPT_SOUND:tts_error");
//改为播放本地音频 听不清 请再说一遍
}
#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(left<PCM_FRAME_LENGTH){
if(!vad_audio_is_recording()){//录音结束
hal_os_rb_read(ring_sender,sender_buf, left, 5);
memset(sender_buf+left, 0, PCM_FRAME_LENGTH-left);
}else{
continue;
}
}else{
hal_os_rb_read(ring_sender,sender_buf, PCM_FRAME_LENGTH, 5);
}
cnt = 0;
for(index=0; index<576; index++)
{
mp3_buff[index] = sender_buf[cnt] + (sender_buf[cnt+1]<<8);
cnt=cnt+2;
}
data_out = shine_encode_buffer(sh_shine,&ppbuff,&cnt);
sendAudioPacket(data_out, cnt);
//quec_usbcdc_write(data_out, cnt);
//vad 结束 进来发送最后一个包
if(!vad_audio_is_recording()){
left = rb_get_space_used(ring_sender);
if(left>0) 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;
}