1276 lines
33 KiB
C
1276 lines
33 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 "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 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;
|
|
|
|
|
|
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)
|
|
{
|
|
char *file_name;
|
|
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;
|
|
}
|
|
|
|
random_index = hal_os_get_rand() % 10;
|
|
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)
|
|
{
|
|
// 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){
|
|
add_file_to_play_player_queue(filename_buff, 2);
|
|
}else{
|
|
add_file_to_play_player_queue(filename_buff, 0);
|
|
}
|
|
}else{
|
|
return -2;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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)
|
|
{
|
|
//如果在vad状态,则直接返回
|
|
if(vad_audio_is_recording()||g_mic_working) return -1;
|
|
|
|
g_mic_working = 1;//等待转为vad状态就清0 或者拾音超时 或者ws没有正常
|
|
|
|
xiaoice_close_all_audio();
|
|
|
|
talkingq_ws_connect();
|
|
|
|
if(get_xiaoice_connected_status()!=2) {
|
|
g_mic_working = 0;
|
|
return -2;
|
|
}
|
|
|
|
if(tts_playing==2){
|
|
printf("vad_start_func0\n");
|
|
vad_start_func();//如果是打断就不播报
|
|
}else{
|
|
app_play_tips_file(18);
|
|
}
|
|
}
|
|
|
|
void stop_player_func(void)
|
|
{
|
|
vad_audio_stop_record();
|
|
xiaoice_close_all_audio();
|
|
|
|
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;
|
|
}
|
|
free(player_item);
|
|
player_item = NULL;
|
|
}
|
|
}
|
|
|
|
player_mq_couter=0;
|
|
}
|
|
|
|
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();
|
|
|
|
while(tts_play_status&&tts_playing!=2){
|
|
ql_rtos_task_sleep_ms(20);
|
|
if(hal_os_up_time_ms()-timeout>10000) 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;
|
|
}
|