This commit is contained in:
huxi
2025-12-03 11:12:34 +08:00
parent c23ae4f24c
commit bc195654bf
8163 changed files with 3799544 additions and 92 deletions
@@ -0,0 +1,297 @@
#include "sparkdesk_main.h"
#include "authentication.h"
#include "stdlib.h"
#include "app_config.h"
#include "app_task.h"
#include "system/timer.h"
#include "app_main.h"
#include "init.h"
#include "key_event_deal.h"
#include "device/device.h"
#include "app_power_manage.h"
#include "btstack/avctp_user.h"
#include "asm/charge.h"
#include "cJSON.h"
#include "media/includes.h"
#include "audio_config.h"
#include "ai_interaction/ai_audio.h"
#include "websocket_define.h"
#include "ifly_socket.h"
#include "ui/ui_api.h"
#include "ifly_common.h"
#if TCFG_IFLYTEK_ENABLE
#define LOG_TAG_CONST NET_IFLY
#define LOG_TAG "[IFLY_AI]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#define LOG_CLI_ENABLE
#include "debug.h"
#ifdef TCFG_IFLYTEK_APP_ID
#define APP_ID TCFG_IFLYTEK_APP_ID
#else
#define APP_ID "123"
#endif
#define MAX_SPARKDESK_TOKENS ((MAX_SPARKDESK_LEN - 100) / 6) //1个tokens相当于1.5个汉字,1个汉字最大4个byte,即1个tokens约6个byte
#define RECV_LAST_FRAME 2
typedef enum {
IFLY_AI_STATUS_NULL = 0,
IFLY_AI_STATUS_START, // 启动
IFLY_AI_STATUS_SEND, // 数据已经发给socket
IFLY_AI_STATUS_RECV, // 有接受到数据
IFLY_AI_STATUS_RECV_END,// 接受完成
IFLY_AI_STATUS_RECV_ERROR,// 接受错误
IFLY_AI_STATUS_EXIT, // 已经退出
} ifly_ai_status;
struct sparkdesk_info_t {
u8 force_stop;
u8 recv_finish;
ifly_ai_status status;
ifly_ai_param *param;
};
static struct sparkdesk_info_t sparkdesk_info;
static struct ifly_websocket_struct sparkdesk_socket;
//json解析函数_ai
static void ifly_sparkdesk_recv_cb(u8 *j_str, u32 len, u8 type)
{
if (sparkdesk_info.force_stop) {
return;
}
if (sparkdesk_info.status >= IFLY_AI_STATUS_RECV_END) {
return;
}
cJSON *cjson_root = cJSON_Parse((char *)j_str);
if (cjson_root == NULL) {
log_error("cjson error...\r\n");
if (sparkdesk_info.status <= IFLY_AI_STATUS_RECV_END) {
sparkdesk_info.status = IFLY_AI_STATUS_RECV_ERROR;
}
return;
}
sparkdesk_info.status = IFLY_AI_STATUS_RECV;
cJSON *cjson_header = cJSON_GetObjectItem(cjson_root, "header");
cJSON *cjson_status = cJSON_GetObjectItem(cjson_header, "status");
cJSON *cjson_payload = cJSON_GetObjectItem(cjson_root, "payload");
cJSON *cjson_choices = cJSON_GetObjectItem(cjson_payload, "choices");
cJSON *cjson_text = cJSON_GetObjectItem(cjson_choices, "text");
int arr_size = cJSON_GetArraySize(cjson_text);
cJSON *arr_item = cjson_text->child;
if (!sparkdesk_info.param->ai_res) {
return;
}
u32 ai_res_len = strlen(sparkdesk_info.param->ai_res);
for (int i = 0; i < arr_size; i++) {
cJSON *cjson_content = cJSON_GetObjectItem(arr_item, "content");
char *cjson_str = cJSON_Print(cjson_content);
u32 json_len = strlen(cjson_str);
if ((ai_res_len + json_len + 1) > sparkdesk_info.param->ai_res_len) {
log_error("len error\n");
} else {
strcpy(&sparkdesk_info.param->ai_res[ai_res_len], cjson_str);
ai_res_len += json_len;
}
arr_item = arr_item->next;
cJSON_free(cjson_str);
}
int res_len = strlen(sparkdesk_info.param->ai_res);
str_remove_quote(sparkdesk_info.param->ai_res, res_len);
log_info("final res:%s\n", sparkdesk_info.param->ai_res);
if (cjson_status->valueint == RECV_LAST_FRAME) {
sparkdesk_info.status = IFLY_AI_STATUS_RECV_END;
sparkdesk_info.recv_finish = 1;
sparkdesk_info.param->event_cb(IFLY_AI_EVT_RECV_OK, sparkdesk_info.param);
}
cJSON_Delete(cjson_root);
}
//ai数据模块
static char *ifly_sparkdesk_format_audio_data(void)
{
char *data_str = NULL;
cJSON *cjson_test = NULL;
cJSON *cjson_header = NULL;
cJSON *cjson_parameter = NULL;
cJSON *cjson_payload = NULL;
cJSON *cjson_chat = NULL;
cJSON *cjson_message = NULL;
cJSON *cjson_text = NULL;
cJSON *cjson_texts = NULL;
cjson_test = cJSON_CreateObject();
cjson_header = cJSON_CreateObject();
cjson_parameter = cJSON_CreateObject();
cjson_payload = cJSON_CreateObject();
cjson_chat = cJSON_CreateObject();
cjson_message = cJSON_CreateObject();
cjson_text = cJSON_CreateObject();
cJSON_AddStringToObject(cjson_header, "app_id", APP_ID);
cJSON_AddItemToObject(cjson_test, "header", cjson_header);
cJSON_AddStringToObject(cjson_chat, "domain", "generalv3");
cJSON_AddNumberToObject(cjson_chat, "max_tokens", MAX_SPARKDESK_TOKENS);
cJSON_AddItemToObject(cjson_parameter, "chat", cjson_chat);
cJSON_AddItemToObject(cjson_test, "parameter", cjson_parameter);
cjson_texts = cJSON_AddArrayToObject(cjson_message, "text");
cJSON_AddStringToObject(cjson_text, "role", "user");
cJSON_AddStringToObject(cjson_text, "content", sparkdesk_info.param->content);
cJSON_AddItemToArray(cjson_texts, cjson_text);
cJSON_AddItemToObject(cjson_payload, "message", cjson_message);
cJSON_AddItemToObject(cjson_test, "payload", cjson_payload);
data_str = cJSON_Print(cjson_test);
cJSON_Delete(cjson_test);
return data_str;
}
static bool ifly_sparkdesk_get_send(u8 **buf, u32 *len)
{
if (sparkdesk_info.force_stop) {
log_info("ai task kill!\n");
return false;
}
if (sparkdesk_info.status >= IFLY_AI_STATUS_RECV_END) {
log_info("ai task kill!\n");
return false;
}
if (sparkdesk_info.status < IFLY_AI_STATUS_SEND) {
sparkdesk_info.status = IFLY_AI_STATUS_SEND;
char *input_src_json = ifly_sparkdesk_format_audio_data();
if (input_src_json == NULL) {
log_error("get json err \n");
return false;
}
*buf = (u8 *)input_src_json;
*len = strlen(input_src_json);
return true;
}
os_time_dly(2);
return true;
}
static int ifly_sparkdesk_event_cb(ifly_socket_event_enum evt, void *param)
{
switch (evt) {
case IFLY_SOCKET_EVT_SEND_OK:
cJSON_free(param);
os_time_dly(5);
break;
case IFLY_SOCKET_EVT_SEND_ERROR:
cJSON_free(param);
break;
case IFLY_SOCKET_EVT_INIT_OK:
break;
case IFLY_SOCKET_EVT_INIT_ERROR:
case IFLY_SOCKET_EVT_HANSHACK_ERROR:
case IFLY_SOCKET_EVT_ACCIDENT_END:
case IFLY_SOCKET_EVT_END:
case IFLY_SOCKET_EVT_FORCE_END:
if ((evt != IFLY_SOCKET_EVT_END) && (evt != IFLY_SOCKET_EVT_FORCE_END)) {
if (!sparkdesk_info.recv_finish) {
sparkdesk_info.param->event_cb(IFLY_AI_EVT_NETWORK_FAIL, sparkdesk_info.param);
}
}
break;
case IFLY_SOCKET_EVT_EXIT:
sparkdesk_info.status = IFLY_AI_STATUS_EXIT;
sparkdesk_info.param->event_cb(IFLY_AI_EVT_EXIT, sparkdesk_info.param);
if (sparkdesk_socket.auth) {
printf("@@@@@@ free sparkdesk auth!!!\n");
net_iflytek_free(sparkdesk_socket.auth);
sparkdesk_socket.auth = NULL;
}
break;
default:
break;
}
return 0;
}
bool ifly_sparkdesk_start(ifly_ai_param *param)
{
memset(&sparkdesk_info, 0, sizeof(struct sparkdesk_info_t));
memset(&sparkdesk_socket, 0, sizeof(struct ifly_websocket_struct));
#if TCFG_IFLYTEK_USE_PSRAM
cJSON_Hooks hooks;
hooks.malloc_fn = net_iflytek_malloc;
hooks.free_fn = net_iflytek_free;
cJSON_InitHooks(&hooks);
#endif
sparkdesk_info.param = param;
sparkdesk_socket.auth = (u8 *)ifly_authentication("wss://spark-api.xf-yun.com/v3.1/chat",
"spark-api.xf-yun.com",
"GET /v3.1/chat HTTP/1.1", 20);
if (!sparkdesk_socket.auth) {
sparkdesk_info.param->event_cb(IFLY_AI_EVT_NETWORK_FAIL, sparkdesk_info.param);
return false;
}
sparkdesk_socket.task_name = "ifly_ai";
sparkdesk_socket.socket_mode = WEBSOCKET_MODE;
sparkdesk_socket.recv_cb = ifly_sparkdesk_recv_cb;
sparkdesk_socket.get_send = ifly_sparkdesk_get_send;
sparkdesk_socket.event_cb = ifly_sparkdesk_event_cb;
sparkdesk_info.status = IFLY_AI_STATUS_START;
//创建链接
bool ret = ifly_websocket_client_create(&sparkdesk_socket);
if (ret == false) {
sparkdesk_info.status = IFLY_AI_STATUS_NULL;
net_iflytek_free(sparkdesk_socket.auth);
sparkdesk_socket.auth = NULL;
}
return ret;
}
void ifly_sparkdesk_stop(u8 force_stop, u32 to_ms)
{
log_info("ai stop!\n");
sparkdesk_info.force_stop = force_stop;
while (sparkdesk_socket.auth) { // 结束时auth会自动释放
os_time_dly(1);
if (to_ms <= 10) {
break;
}
to_ms -= 10;
}
if (to_ms < 1000) {
to_ms = 1000;
}
sparkdesk_info.force_stop = 1;
ifly_websocket_client_release(&sparkdesk_socket, to_ms);
}
bool ifly_sparkdesk_is_work()
{
if ((sparkdesk_info.status != IFLY_AI_STATUS_NULL) && (sparkdesk_info.status != IFLY_AI_STATUS_EXIT)) {
return true;
}
return false;
}
#endif
@@ -0,0 +1,31 @@
#ifndef __SPARKDESK_MAIN_H
#define __SPARKDESK_MAIN_H
#include "generic/includes.h"
#define MAX_SPARKDESK_LEN 2000
typedef enum {
IFLY_AI_EVT_RECV_OK = 1, // AI对话完成。*param: ifly_ai_param*
IFLY_AI_EVT_NETWORK_FAIL, // 结束。*param: ifly_ai_param*
IFLY_AI_EVT_EXIT, // 结束。*param: ifly_ai_param*
} ifly_ai_event_enum ;
typedef struct ifly_ai_struct {
// 参数信息,所有参数都需要赋值
char *content; // 输入对话数据
char *ai_res; // 输出数据
u32 ai_res_len; // 输出数据buf长度,最大MAX_SPARKDESK_LEN
int (*event_cb)(ifly_ai_event_enum evt, void *param); // 事件回调
} ifly_ai_param;
// AI对话启动。*param参数句柄需要在stop之后才能释放
bool ifly_sparkdesk_start(ifly_ai_param *param);
// AI对话结束。
void ifly_sparkdesk_stop(u8 force_stop, u32 to_ms);
//判断是否正在运行
bool ifly_sparkdesk_is_work(void);
#endif
@@ -0,0 +1,212 @@
#include "app_config.h"
#include "ui/ui_api.h"
#include "jlui/ui.h"
#include "ui_style.h"
#include "app_task.h"
#include "system/timer.h"
#include "app_main.h"
#include "init.h"
#include "key_event_deal.h"
#include "device/device.h"
#include "app_power_manage.h"
#include "btstack/avctp_user.h"
#include "asm/charge.h"
#include "cat1/cat1_common.h"
#include "cJSON.h"
#include "media/includes.h"
#include "audio_config.h"
#include "res/resfile.h"
#include "res_config.h"
#include "circular_buf.h"
#include "ntp/ntp.h"
#include "mbedtls/base64.h"
#include "mbedtls/md.h"
#include "authentication.h"
#include "rtc.h"
#include "ifly_common.h"
#if TCFG_IFLYTEK_ENABLE
#define LOG_TAG_CONST NET_IFLY
#define LOG_TAG "[IFLY_AUTH]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#define LOG_CLI_ENABLE
#include "debug.h"
#ifdef TCFG_IFLYTEK_APP_SECRET
#define API_SECRET TCFG_IFLYTEK_APP_SECRET
#else
#define API_SECRET "123"
#endif
#ifdef TCFG_IFLYTEK_APP_KEY
#define API_KEY TCFG_IFLYTEK_APP_KEY
#else
#define API_KEY "123"
#endif
// 鉴权信息结构体
struct auth_info_t {
char sig_ori[100];
char sig_hmac[100];
char sig_hmac_base64[50];
char author_ori[200];
char author[300];
char date_str[50];
struct sys_time curtime;
struct tm pt;
};
extern void ntp_client_get_time(const char *host);
extern size_t strftime_2(char *ptr, size_t maxsize, const char *format, const struct tm *timeptr);
/* void *_calloc_r(struct _reent *r, size_t a, size_t b) */
/* { */
/* return calloc(a, b); */
/* } */
static char dec2hex(short int c)
{
if (0 <= c && c <= 9) {
return c + '0';
} else if (10 <= c && c <= 15) {
return c + 'A' - 10;
} else {
return -1;
}
}
static void urlencode(char url[])
{
int i = 0;
int len = strlen(url);
int res_len = 0;
char res[100];
for (i = 0; i < len; ++i) {
char c = url[i];
if (('0' <= c && c <= '9') ||
('a' <= c && c <= 'z') ||
('A' <= c && c <= 'Z') ||
c == '/' || c == '.') {
res[res_len++] = c;
} else {
int j = (short int)c;
if (j < 0) {
j += 256;
}
int i1, i0;
i1 = j / 16;
i0 = j - i1 * 16;
res[res_len++] = '%';
res[res_len++] = dec2hex(i1);
res[res_len++] = dec2hex(i0);
}
}
res[res_len] = '\0';
strcpy(url, res);
}
static int ifly_get_sys_time(struct sys_time *time)//获取时间
{
rtc_read_time(time);
int week = caculate_weekday_by_time(time);
return week;
/* void *fd = dev_open("rtc", NULL); */
/* if (!fd) { */
/* return ; */
/* } */
/* dev_ioctl(fd, IOCTL_GET_SYS_TIME, (u32)time); */
/* dev_close(fd); */
}
char *ifly_authentication(char *host_name, char *host, char *path, int retry)
{
//初始化
struct auth_info_t *auth = net_iflytek_malloc(sizeof(struct auth_info_t));
char *url_xf = net_iflytek_malloc(400);
int retry_cnt = 0;
__retry:
//获取网络时间
/* ntp_client_get_time("s2c.time.edu.cn"); */
ntp_client_get_time("ntp.ntsc.ac.cn");
/* int ntp = ntp_client_get_time("s2c.time.edu.cn"); */
/* if (ntp == -1) { */
/* log_error("get ntp error!!\n"); */
/* return NULL; */
/* } */
int week = ifly_get_sys_time(&(auth->curtime));
auth->pt.tm_year = auth->curtime.year - 1900;
auth->pt.tm_mon = auth->curtime.month - 1;
auth->pt.tm_mday = auth->curtime.day;
auth->pt.tm_hour = auth->curtime.hour;
auth->pt.tm_min = auth->curtime.min;
auth->pt.tm_sec = auth->curtime.sec;
if (week == 7) {
auth->pt.tm_wday = 0;
} else {
auth->pt.tm_wday = week;
}
strftime_2(auth->date_str, 40, "", &auth->pt);
log_info("%s\n", auth->date_str);
//拼接sig原始字符
sprintf(auth->sig_ori, "%s%s\ndate: %s\n%s", "host: ", host, auth->date_str, path);
//通过hmac_sha256加密
mbedtls_md_context_t ctx;
const mbedtls_md_info_t *info;
mbedtls_md_init(&ctx);
info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
mbedtls_md_setup(&ctx, info, 1);
mbedtls_md_hmac_starts(&ctx, (unsigned char *)API_SECRET, strlen(API_SECRET));
mbedtls_md_hmac_update(&ctx, (unsigned char *)auth->sig_ori, strlen(auth->sig_ori));
mbedtls_md_hmac_finish(&ctx, (unsigned char *)auth->sig_hmac);
mbedtls_md_free(&ctx);
//进行base64编码
int res;
mbedtls_base64_encode((unsigned char *)auth->sig_hmac_base64, 50, (size_t *)&res, (unsigned char *)auth->sig_hmac, strlen(auth->sig_hmac));
if (strlen(auth->sig_hmac_base64) != 44) { // 根据科大讯飞文档,hamc base64编码后,正常为44bytes
log_info("base64 encode error!!\n");
if (retry_cnt < retry) {
memset(auth, 0, sizeof(struct auth_info_t));
memset((char *)url_xf, 0, sizeof(url_xf));
retry_cnt++;
goto __retry;
}
}
//拼接authorization_ori字符串
strcat(auth->author_ori, "api_key=\"");
strcat(auth->author_ori, API_KEY);
strcat(auth->author_ori, "\",");
strcat(auth->author_ori, "algorithm=\"hmac-sha256\",");
strcat(auth->author_ori, "headers=\"host date request-line\",");
strcat(auth->author_ori, "signature=\"");
strcat(auth->author_ori, auth->sig_hmac_base64);
strcat(auth->author_ori, "\"");
//对autho_ori进行base64编码
int author_res;
mbedtls_base64_encode((unsigned char *)auth->author, 300, (size_t *)&author_res, (unsigned char *)auth->author_ori, strlen(auth->author_ori));
//拼接url
strcat(url_xf, host_name);
strcat(url_xf, "?");
urlencode(auth->date_str);
strcat(url_xf, "authorization=");
strcat(url_xf, auth->author);
strcat(url_xf, "&date=");
strcat(url_xf, auth->date_str);
strcat(url_xf, "&host=");
strcat(url_xf, host);
log_info("url_xf:%s\n", url_xf);
net_iflytek_free(auth);
return url_xf;
}
#endif
@@ -0,0 +1,9 @@
#ifndef __AUTHENTICATION_H
#define __AUTHENTICATION_H
#include "generic/includes.h"
// 鉴权接口。内存需要释放
char *ifly_authentication(char *host_name, char *host, char *path, int retry);
#endif
+123
View File
@@ -0,0 +1,123 @@
#include "ifly_socket.h"
#include "authentication.h"
#include "sparkdesk_main.h"
#include "tts_main.h"
#include "vad_main.h"
#include "system/timer.h"
#include "app_config.h"
#if TCFG_IFLYTEK_ENABLE
#define IFLY_DEMO_TEST 0
#if IFLY_DEMO_TEST
struct ifly_net_struct {
// vad
ifly_vad_param vad_param;
// ai
ifly_ai_param ai_param;
// tts
ifly_tts_param tts_param;
// other
char local_text[MAX_VAD_LEN]; // 对话文本。近端
char ai_text[MAX_SPARKDESK_LEN]; // 对话文本。远端
int vad_timer;
};
static struct ifly_net_struct *p_ifly_net = NULL;
static int ifly_tts_event_cb(ifly_tts_event_enum evt, void *param) //tts任务状态callback
{
switch (evt) {
case IFLY_TTS_EVT_PLAY_START:
break;
case IFLY_TTS_EVT_PLAY_STOP:
break;
case IFLY_TTS_EVT_PLAY_FAIL_STOP:
break;
case IFLY_TTS_EVT_EXIT:
if (p_ifly_net) {
free(p_ifly_net);
printf("free!!\n");
p_ifly_net = NULL;
}
break;
default:
break;
}
return 0;
}
static int ifly_ai_event_cb(ifly_ai_event_enum evt, void *param) //ai任务状态callback
{
switch (evt) {
case IFLY_AI_EVT_RECV_OK:
ifly_sparkdesk_stop(10);
p_ifly_net->tts_param.event_cb = ifly_tts_event_cb;
p_ifly_net->tts_param.text_res = p_ifly_net->ai_text;
ifly_tts_start(&p_ifly_net->tts_param); //创建tts任务
break;
case IFLY_AI_EVT_EXIT:
break;
default:
break;
}
return 0;
}
static int ifly_vad_event_cb(ifly_vad_event_enum evt, void *param) //vad任务状态callback
{
switch (evt) {
case IFLY_VAD_EVT_AUDIO_START:
break;
case IFLY_VAD_EVT_RECV_OK:
break;
case IFLY_VAD_EVT_EXIT: //vad结束,开启ai
p_ifly_net->ai_param.ai_res = p_ifly_net->ai_text;
p_ifly_net->ai_param.ai_res_len = MAX_SPARKDESK_LEN;
p_ifly_net->ai_param.event_cb = ifly_ai_event_cb;
p_ifly_net->ai_param.content = p_ifly_net->local_text;
p_ifly_net->ai_param.ai_res[0] = 0;
ifly_sparkdesk_start(&p_ifly_net->ai_param); //创建ai任务
break;
default:
break;
}
return 0;
}
static void stop_rec(void *priv) //8s结束录音,并关闭vad任务
{
ifly_vad_stop(10);
if (p_ifly_net->vad_timer) {
sys_timer_del(p_ifly_net->vad_timer);
p_ifly_net->vad_timer = 0;
}
}
void ifly_net_demo(void)
{
if (p_ifly_net == NULL) {
p_ifly_net = zalloc(sizeof(struct ifly_net_struct));
ASSERT(p_ifly_net);
}
p_ifly_net->vad_param.vad_res = p_ifly_net->local_text;
p_ifly_net->vad_param.vad_res_len = MAX_VAD_LEN;
p_ifly_net->vad_param.event_cb = ifly_vad_event_cb;
p_ifly_net->vad_param.vad_res[0] = 0;
if (!p_ifly_net->vad_timer) {
p_ifly_net->vad_timer = sys_timer_add(NULL, stop_rec, 8000);
}
ifly_vad_start(&p_ifly_net->vad_param); //创建vad任务
}
#endif
#endif
@@ -0,0 +1,37 @@
#include "ifly_common.h"
#include "app_config.h"
static void str_remove_quote(char *str, int len)
{
int i, j;
for (i = 0, j = 0; i < len; i++) {
if (str[i] != '\"') {
str[j++] = str[i];
}
}
str[j] = '\0';
}
void *net_iflytek_malloc(size_t size)
{
#if TCFG_IFLYTEK_USE_PSRAM && TCFG_PSRAM_DEV_ENABLE
void *p = malloc_psram(size);
if (p) {
memset(p, 0, size);
}
return p;
#else
return zalloc(size);
#endif
}
void net_iflytek_free(void *pv)
{
#if TCFG_IFLYTEK_USE_PSRAM && TCFG_PSRAM_DEV_ENABLE
return free_psram(pv);
#else
return free(pv);
#endif
}
@@ -0,0 +1,11 @@
#ifndef __IFLY_COMMON__H
#define __IFLY_COMMON__H
#include "generic/includes.h"
// 引号去除函数
void str_remove_quote(char *str, int len);
void *net_iflytek_malloc(size_t size);
void net_iflytek_free(void *pv);
#endif
@@ -0,0 +1,276 @@
#include "websocket_api.h"
#include "os/os_compat.h"
#ifdef WEBSOCKET_MEMHEAP_DEBUG
#include "mem_leak_test.h"
#endif
#include "ifly_socket.h"
#include "ui/ui_api.h"
#include "cJSON.h"
#include "app_config.h"
#if TCFG_IFLYTEK_ENABLE
#define LOG_TAG_CONST NET_IFLY
#define LOG_TAG "[IFLY_SOCKET]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#define LOG_CLI_ENABLE
#include "debug.h"
#define IFLY_SOCKET_KILL_TASK_NAME "ifly_socket_kill"
struct ifly_socket_info {
struct websocket_struct socket_info;
u8 force_stop; // 强制结束
};
extern int task_kill(const char *name);
extern void websockets_sleep(unsigned int ms);
static void task_kill_callback(char *buf)
{
log_info("[msg]>>>>>>>>>>>*buf=%s", buf);
task_kill(buf);
}
static void ifly_socket_kill_task_main(void *priv)
{
int msg[16];
while (1) {
os_taskq_pend(NULL, msg, ARRAY_SIZE(msg));
}
}
int ifly_socket_kill_task_create(void)
{
int err = os_task_create(ifly_socket_kill_task_main,
NULL,
5,
256,
256,
IFLY_SOCKET_KILL_TASK_NAME);
return err;
}
void ifly_socket_kill_task_release(void)
{
if (!strcmp(os_current_task(), IFLY_SOCKET_KILL_TASK_NAME)) {
task_kill(IFLY_SOCKET_KILL_TASK_NAME);
return ;
}
int msg[4];
msg[0] = (int)task_kill_callback;
msg[1] = 1;
msg[2] = (int)IFLY_SOCKET_KILL_TASK_NAME;
os_taskq_post_type("app_core", Q_CALLBACK, 3, msg);
}
static void websockets_client_reg(struct websocket_struct *websockets_info, ifly_socket_param *ifly_socket)
{
memset(websockets_info, 0, sizeof(struct websocket_struct));
websockets_info->_init = websockets_client_socket_init;
websockets_info->_exit = websockets_client_socket_exit;
websockets_info->_handshack = webcockets_client_socket_handshack;
websockets_info->_send = websockets_client_socket_send;
websockets_info->_recv_thread = websockets_client_socket_recv_thread;
websockets_info->_heart_thread = websockets_client_socket_heart_thread;
websockets_info->_recv_cb = ifly_socket->recv_cb;
websockets_info->_recv = NULL;
websockets_info->websocket_mode = ifly_socket->socket_mode;
websockets_info->kill_task = IFLY_SOCKET_KILL_TASK_NAME;
}
static int websockets_client_init(struct websocket_struct *websockets_info, u8 *url, const char *origin_str, const char *user_agent_str)
{
websockets_info->ip_or_url = url;
websockets_info->origin_str = origin_str;
websockets_info->user_agent_str = user_agent_str;
websockets_info->recv_time_out = 1000;
//应用层和库的版本检测,结构体不一样则返回出错
int err = websockets_struct_check(sizeof(struct websocket_struct));
if (err == FALSE) {
return err;
}
return websockets_info->_init(websockets_info);
}
static int websockets_client_handshack(struct websocket_struct *websockets_info)
{
return websockets_info->_handshack(websockets_info);
}
static int websockets_client_send(struct websocket_struct *websockets_info, u8 *buf, int len, char type)
{
//SSL加密时一次发送数据不能超过16K,用户需要自己分包
return websockets_info->_send(websockets_info, buf, len, type);
}
static void websockets_client_exit(struct websocket_struct *websockets_info)
{
websockets_info->_exit(websockets_info);
}
static void ifly_socket_task_main(void *priv)
{
int err;
ifly_socket_event_enum ifly_evt = IFLY_SOCKET_EVT_END;
ifly_socket_param *ifly_socket = (ifly_socket_param *)priv;
struct ifly_socket_info *info = (struct ifly_socket_info *)ifly_socket->socket_hdl;
const char *origin_str = "http://coolaf.com";
log_info(" . ----------------- Client Websocket ------------------\r\n");
/* 0 . malloc buffer */
struct websocket_struct *websockets_info = &info->socket_info;
/* 1 . register */
websockets_client_reg(websockets_info, ifly_socket);
/* 2 . init */
err = websockets_client_init(websockets_info, ifly_socket->auth, origin_str, NULL);
if (FALSE == err) {
log_error(" . ! Cilent websocket init error !!!\r\n");
ifly_evt = IFLY_SOCKET_EVT_INIT_ERROR;
goto exit_ws;
}
/* 3 . hanshack */
err = websockets_client_handshack(websockets_info);
if (FALSE == err) {
log_error(" . ! Handshake error !!!\r\n");
ifly_evt = IFLY_SOCKET_EVT_HANSHACK_ERROR;
goto exit_ws;
}
log_info(" . Handshake success \r\n");
/* 4 . CreateThread */
err = os_task_create(websockets_info->_heart_thread,
websockets_info,
3,
1024,
0,
"websocket_client_heart");
if (err == 0) {
websockets_info->ping_thread_id = 1;
} else {
websockets_info->ping_thread_id = 0;
}
err = os_task_create(websockets_info->_recv_thread,
websockets_info,
2,
1024,
0,
"websocket_client_recv");
if (err == 0) {
websockets_info->recv_thread_id = 1;
} else {
websockets_info->recv_thread_id = 0;
}
ifly_socket->event_cb(IFLY_SOCKET_EVT_INIT_OK, ifly_socket);
/* 5 . recv or send data */
while (1) {
u8 *send_buf = NULL;
u32 send_len = 0;
if (websockets_info->websocket_valid == INVALID_ESTABLISHED) {
//websocket底层断开连接
ifly_evt = IFLY_SOCKET_EVT_ACCIDENT_END;
goto exit_ws;
}
if (info->force_stop) {
ifly_evt = IFLY_SOCKET_EVT_FORCE_END;
goto exit_ws;
}
err = ifly_socket->get_send(&send_buf, &send_len);
if (err == false) {
// 获取不到数据,正常退出
goto exit_ws;
}
if ((send_buf == NULL) || (send_len == 0)) {
continue;
}
err = websockets_client_send(websockets_info, send_buf, send_len, WCT_TXTDATA);
if (FALSE == err) {
log_error(" . ! send err !!!\r\n");
ifly_socket->event_cb(IFLY_SOCKET_EVT_SEND_ERROR, send_buf);
goto exit_ws;
}
ifly_socket->event_cb(IFLY_SOCKET_EVT_SEND_OK, send_buf);
}
exit_ws:
/* 6 . exit */
ifly_socket->event_cb(ifly_evt, ifly_socket);
if (websockets_info->ping_thread_id) {
websockets_info->ping_kill_flag = 1;
}
if (websockets_info->recv_thread_id) {
websockets_info->recv_kill_flag = 1;
}
while (websockets_info->recv_kill_flag || websockets_info->ping_kill_flag) {
os_time_dly(1);
}
websockets_client_exit(websockets_info);
ifly_socket->event_cb(IFLY_SOCKET_EVT_EXIT, ifly_socket);
free(ifly_socket->socket_hdl);
ifly_socket->socket_hdl = NULL;
int msg[5];
msg[0] = (int)task_kill_callback;
msg[1] = 1;
msg[2] = (int)os_current_task();
err = os_taskq_post_type("app_core", Q_CALLBACK, 3, msg);
os_time_dly(-1);
}
bool ifly_websocket_client_create(ifly_socket_param *ifly_socket)
{
ifly_socket->socket_hdl = zalloc(sizeof(struct ifly_socket_info));
if (ifly_socket->socket_hdl == NULL) {
return false;
}
os_task_create(ifly_socket_task_main,
ifly_socket,
3,
2048,
0,
ifly_socket->task_name);
return true;
}
void ifly_websocket_client_release(ifly_socket_param *ifly_socket, u32 to_ms)
{
if (ifly_socket && ifly_socket->socket_hdl) {
struct ifly_socket_info *info = (struct ifly_socket_info *)ifly_socket->socket_hdl;
info->force_stop = 1;
if (!strcmp(os_current_task(), ifly_socket->task_name)) {
// 任务内部调用
return ;
}
// 其他任务调用
while (ifly_socket->socket_hdl) {
os_time_dly(1);
if (to_ms <= 10) {
break;
}
to_ms -= 10;
}
}
}
#endif
@@ -0,0 +1,45 @@
#ifndef __IFLY_SOCKET_H__
#define __IFLY_SOCKET_H__
#include "generic/includes.h"
typedef enum {
IFLY_SOCKET_EVT_SEND_OK = 1, // socket发数成功。*param: 发数buf
IFLY_SOCKET_EVT_SEND_ERROR, // socket发数失败。*param: 发数buf
IFLY_SOCKET_EVT_INIT_ERROR, // socket初始化失败。*param: ifly_socket_param*
IFLY_SOCKET_EVT_HANSHACK_ERROR, // socket握手失败。*param: ifly_socket_param*
IFLY_SOCKET_EVT_INIT_OK, // socket初始化成功。*param: ifly_socket_param*
IFLY_SOCKET_EVT_ACCIDENT_END, //意外结束。。*param: ifly_socket_param*
IFLY_SOCKET_EVT_END, // 结束。socket释放资源之前。*param: ifly_socket_param*
IFLY_SOCKET_EVT_FORCE_END, // 强制结束。socket释放资源之前。*param: ifly_socket_param*
IFLY_SOCKET_EVT_EXIT, // 结束。socket释放资源之后。*param: ifly_socket_param*
} ifly_socket_event_enum ;
typedef struct ifly_websocket_struct {
// 参数信息,所有参数都需要赋值
char *task_name; // 任务名
u8 *auth; // 鉴权buf
u8 socket_mode; // 是否加密。WEBSOCKET_MODE, WEBSOCKETS_MODE
void (*recv_cb)(u8 *buf, u32 len, u8 type); // 收数回调
bool (*get_send)(u8 **buf, u32 *len); // 获取发数buf
int (*event_cb)(ifly_socket_event_enum evt, void *param); // 事件回调
// 模块内部记录信息,不需要赋值
void *socket_hdl; // socket句柄
} ifly_socket_param;
// socket创建。*ifly_socket参数句柄需要在release之后才能释放
bool ifly_websocket_client_create(ifly_socket_param *ifly_socket);
// socket释放。
void ifly_websocket_client_release(ifly_socket_param *ifly_socket, u32 to_ms);
// 用于处理socket收发任务的删除,需要在所有socket创建之前创建,所有socket释放之后释放
int ifly_socket_kill_task_create(void);
void ifly_socket_kill_task_release(void);
#endif
@@ -0,0 +1,88 @@
#include "circular_buf.h"
#include "app_config.h"
/* #include "audio_dec.h" */
/* #include "media/pcm_decoder.h" */
#include "tts_main.h"
#include "jlstream.h"
#include "ai_rx_player.h"
#if TCFG_IFLYTEK_ENABLE
#define LOG_TAG_CONST NET_IFLY
#define LOG_TAG "[IFLY_SOCKET]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#define LOG_CLI_ENABLE
#include "debug.h"
extern struct ai_rx_cb ai_rx_cb_t;
enum stream_node_state iflytek_tts_ai_rx_get_frame(struct ai_rx_file_handle *hdl, struct stream_frame **pframe)
{
#define FIX_LEN 1024
extern int iflytek_tts_cbuf_data_len();
int available = iflytek_tts_cbuf_data_len();
int read_size = MIN(available, FIX_LEN);
if (read_size <= 0) {
*pframe = NULL;
return NODE_STA_RUN;
}
struct stream_frame *frame = jlstream_get_frame(hdl->node->oport, read_size);
if (!frame) {
*pframe = NULL;
return NODE_STA_RUN;
}
extern int iflytek_tts_cbuf_read_data(u8 * data, int size);
int rlen = iflytek_tts_cbuf_read_data(frame->data, read_size);
frame->len = MAX(rlen, 0);
if (rlen != read_size) {
printf("cbuf_read mismatch: req=%d, act=%d\n", read_size, rlen);
}
*pframe = frame;
return NODE_STA_RUN;
}
static void iflytek_tts_audio_begin(void)
{
struct ai_rx_player_param param = {0};
param.coding_type = AUDIO_CODING_STREAM_MP3;
param.sample_rate = 16000;
param.channel_mode = AUDIO_CH_MIX;
param.type = AI_SERVICE_VOICE;
ai_rx_player_open(NULL, 0, &param);
extern void audio_app_volume_set(u8 state, s16 volume, u8 fade);
audio_app_volume_set(1, 100, 1);
}
static void iflytek_tts_audio_start(void *arg)
{
iflytek_tts_audio_begin();
}
void iflytek_tts_audio_play()
{
if (ai_rx_cb_t.get_frame_event_cb == NULL) {
ai_rx_cb_t.get_frame_event_cb = iflytek_tts_ai_rx_get_frame;
}
iflytek_tts_audio_start(NULL);
}
void iflytek_tts_audio_stop(void *arg)
{
ai_rx_player_close(0);
if (ai_rx_cb_t.get_frame_event_cb) {
ai_rx_cb_t.get_frame_event_cb = NULL;
}
}
bool ifly_net_tts_dec_check_run(void)
{
extern bool ai_rx_player_runing(u8 source);
if (ai_rx_player_runing(0)) {
return true;
}
return false;
}
#endif
@@ -0,0 +1,8 @@
#ifndef __IFLY_DEC_FILE_H
#define __IFLY_DEC_FILE_H
bool ifly_net_tts_dec_check_run(void);
void iflytek_tts_audio_stop(void *arg);
void iflytek_tts_audio_play();
#endif
@@ -0,0 +1,527 @@
#include "tts_main.h"
#include "cJSON.h"
#include "circular_buf.h"
#include "stdlib.h"
#include "app_config.h"
#include "authentication.h"
#include "ifly_dec_file.h"
#include "sparkdesk_main.h"
#include "websocket_define.h"
#include "ifly_socket.h"
#include "authentication.h"
#include "stdlib.h"
#include "app_config.h"
#include "app_task.h"
#include "system/timer.h"
#include "app_main.h"
#include "init.h"
#include "key_event_deal.h"
#include "device/device.h"
#include "app_power_manage.h"
#include "btstack/avctp_user.h"
#include "asm/charge.h"
#include "cJSON.h"
#include "media/includes.h"
#include "audio_config.h"
#include "circular_buf.h"
#include "ui/ui_api.h"
#include "ifly_common.h"
#if TCFG_IFLYTEK_ENABLE
#define LOG_TAG_CONST NET_IFLY
#define LOG_TAG "[IFLY_TTS]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#define LOG_CLI_ENABLE
#include "debug.h"
#define TTS_AUDIO_SAVE_TEST 0
#if TTS_AUDIO_SAVE_TEST
static FILE *save_file = NULL;
#endif
#ifdef TCFG_IFLYTEK_APP_ID
#define APP_ID TCFG_IFLYTEK_APP_ID
#else
#define APP_ID "123"
#endif
#define RECV_LAST_FRAME 2
#define IFLY_TTS_PKG_MAX (1024 * 10) //经测试,最长一包下发数据的长度为8500bytes左右
#define IFLY_TTS_CBUF_LEN (IFLY_TTS_PKG_MAX + 4096)
#define TTS_TIMEOUT_TIME 8 //防止tts在播放过程中收不到消息而卡住,8s没接收消息,就关闭任务
typedef enum {
IFLY_TTS_STATUS_NULL = 0,
IFLY_TTS_STATUS_START, // 启动
IFLY_TTS_STATUS_SEND, // 数据已经发给socket
IFLY_TTS_STATUS_RECV, // 有接受到数据
IFLY_TTS_STATUS_RECV_SEC, // 接收到第二包数据
IFLY_TTS_STATUS_RECV_END,// 接受完成
IFLY_TTS_STATUS_PLAY_END,// 播放完
IFLY_TTS_STATUS_RECV_ERROR,// 接受错误
IFLY_TTS_STATUS_EXIT, // 已经退出
} ifly_tts_status;
struct tts_info_t {
u8 force_stop; // 强制结束
u8 recv_finish; // 接受完毕
u8 tts_to_cnt; // 超时计数
u16 tts_timer; // 超时用的timer ID
cbuffer_t dec_cbuf; // 用于tts音频播放的cbuf
char *dec_out_buf;
ifly_tts_status status;
ifly_tts_param *param;
};
static struct tts_info_t tts_info;
static struct ifly_websocket_struct tts_socket;
static void *ifly_tts_usr_task = "app_core";
extern int mbedtls_base64_encode(unsigned char *dst, size_t dlen, size_t *olen, const unsigned char *src, size_t slen);
extern int mbedtls_base64_decode(unsigned char *dst, size_t dlen, size_t *olen, const unsigned char *src, size_t slen);
#if 0
static int test_read(void *priv, void *buf, u32 len, u8 tmp_flag, int tmp_offset)
{
int rlen = 0;
if (tts_info.status > IFLY_TTS_STATUS_RECV_END) {
return 0;
}
if (tmp_flag) {
// 格式检查时读数,不保留读取记录
void *rbuf = cbuf_read_alloc(&tts_info.dec_cbuf, &rlen);
if (rlen) {
if (rlen > len) {
rlen = len;
}
memcpy(buf, rbuf, rlen);
}
} else {
// 正常读数
rlen = cbuf_read(&tts_info.dec_cbuf, buf, len);
if (rlen != len) {
if (tts_info.status >= IFLY_TTS_STATUS_RECV_END) {
rlen = cbuf_get_data_len(&tts_info.dec_cbuf);
rlen = cbuf_read(&tts_info.dec_cbuf, buf, rlen);
}
}
}
if (tts_info.status < IFLY_TTS_STATUS_RECV_END) {
if (rlen == 0) {
rlen = -1; // 没结束时返回-1挂起解码,不结束解码
}
}
return rlen;
}
#endif
int iflytek_tts_cbuf_data_len()
{
int rlen = cbuf_get_data_len(&tts_info.dec_cbuf);
return rlen;
}
int iflytek_tts_cbuf_read_data(u8 *data, int size)
{
int rlen = cbuf_read(&tts_info.dec_cbuf, data, size);
return rlen;
}
static void ifly_net_tts_stop(u32 dat)
{
int to = dat;
while (tts_info.dec_out_buf && (tts_info.status < IFLY_TTS_STATUS_PLAY_END)) {
/* if (cbuf_get_data_len(&tts_info.dec_cbuf) == 0) { */
/* break; */
/* } */
os_time_dly(1);
if (to < 10) {
break;
}
to -= 10;
}
if (tts_info.status < IFLY_TTS_STATUS_PLAY_END) {
tts_info.status = IFLY_TTS_STATUS_PLAY_END;
}
extern void iflytek_tts_audio_stop(void *arg);
iflytek_tts_audio_stop(NULL);
}
static void ifly_net_tts_start(u32 dat)
{
log_info("ifly_net_tts_start");
// 开启ai_rx节点
extern void iflytek_tts_audio_play();
iflytek_tts_audio_play();
}
static void ifly_tts_dec_func(void(*func)(u32 dat), int to)
{
int argv[3];
argv[0] = (int)func;
argv[1] = 1;
argv[2] = to;
int ret = os_taskq_post_type(ifly_tts_usr_task, Q_CALLBACK, 3, argv);
if ((ret == OS_ERR_POST_NULL_PTR) || (ret == OS_TASK_NOT_EXIST)) {
os_taskq_post_type("app_core", Q_CALLBACK, 3, argv);
}
}
//tts数据模块
char *ifly_tts_format_text_data(void)
{
char *data_str = NULL;
int out_len = 0;
cJSON *cjson_test = NULL;
cJSON *cjson_common = NULL;
cJSON *cjson_business = NULL;
cJSON *cjson_data = NULL;
char *buf = net_iflytek_malloc(MAX_SPARKDESK_LEN);
mbedtls_base64_encode((unsigned char *)buf, MAX_SPARKDESK_LEN, (size_t *)&out_len, (unsigned char *)tts_info.param->text_res, strlen(tts_info.param->text_res) + 1);
//定义最长回答
cjson_test = cJSON_CreateObject();
cjson_common = cJSON_CreateObject();
cjson_business = cJSON_CreateObject();
cjson_data = cJSON_CreateObject();
cJSON_AddStringToObject(cjson_common, "app_id", APP_ID);
cJSON_AddItemToObject(cjson_test, "common", cjson_common);
cJSON_AddStringToObject(cjson_business, "aue", "lame");
cJSON_AddNumberToObject(cjson_business, "sfl", 1);
cJSON_AddStringToObject(cjson_business, "auf", "audio/L16;rate=16000");
cJSON_AddStringToObject(cjson_business, "vcn", "xiaoyan");
cJSON_AddStringToObject(cjson_business, "tte", "UTF8");
cJSON_AddItemToObject(cjson_test, "business", cjson_business);
cJSON_AddNumberToObject(cjson_data, "status", 2);
cJSON_AddStringToObject(cjson_data, "text", buf);
cJSON_AddItemToObject(cjson_test, "data", cjson_data);
data_str = cJSON_Print(cjson_test);
net_iflytek_free(buf);
cJSON_Delete(cjson_test);
log_info("tts content:%s\n", data_str);
return data_str;
}
static void tts_recv_timeout_timer(void *priv)
{
if (tts_info.tts_to_cnt < TTS_TIMEOUT_TIME) {
tts_info.tts_to_cnt++;
if (tts_info.tts_to_cnt == TTS_TIMEOUT_TIME) {
log_info("tts timeout!");
if (tts_info.status < IFLY_TTS_STATUS_RECV_ERROR) {
tts_info.status = IFLY_TTS_STATUS_RECV_ERROR;
}
}
}
}
//tts解析播放模块
static void ifly_tts_recv_cb(u8 *j_str, u32 len, u8 type)
{
tts_info.tts_to_cnt = 0; //收到数据重置定时器
if (tts_info.force_stop) {
return;
}
if (tts_info.status >= IFLY_TTS_STATUS_RECV_END) {
return;
}
cJSON *cjson_root = cJSON_Parse((char *)j_str);
char *audio = NULL;
char *res_tts = NULL;
if (cjson_root == NULL) {
log_error("cjson error...\r\n");
if (tts_info.status <= IFLY_TTS_STATUS_RECV_END) {
tts_info.status = IFLY_TTS_STATUS_RECV_ERROR;
}
return;
}
cJSON *cjson_data = cJSON_GetObjectItem(cjson_root, "data");
cJSON *cjson_status = cJSON_GetObjectItem(cjson_data, "status");
cJSON *cjson_audio = cJSON_GetObjectItem(cjson_data, "audio");
audio = cJSON_Print(cjson_audio);
if (!audio) {
log_error("audio is null!!\n");
tts_info.status = IFLY_TTS_STATUS_RECV_ERROR;
return;
}
str_remove_quote(audio, strlen(audio));
res_tts = net_iflytek_malloc(IFLY_TTS_PKG_MAX);
if (!res_tts) {
log_error("malloc fail!!\n");
tts_info.status = IFLY_TTS_STATUS_RECV_ERROR;
return;
}
int olen = 0;
int ret = mbedtls_base64_decode((unsigned char *)res_tts, IFLY_TTS_PKG_MAX, (size_t *)&olen, (unsigned char *)audio, strlen(audio));
if (ret || (olen > IFLY_TTS_PKG_MAX)) {
log_error("mbedtls_base64_decode fail!!\n");
tts_info.status = IFLY_TTS_STATUS_RECV_ERROR;
return;
}
#if TTS_AUDIO_SAVE_TEST
if (save_file) {
int wlen = fwrite(save_file, res_tts, olen);
if (wlen != olen) {
log_error("save file err: %d, %d\n", wlen, olen);
}
}
#endif
//若cbuf满了,需要先while住,等待播放
int to = 1500;
while (!cbuf_write(&tts_info.dec_cbuf, res_tts, olen)) {
if (tts_info.force_stop) {
break;
}
os_time_dly(1);
if (to < 10) {
log_info(">>>>>>>>>>>>>>>>>>>>>>>cbuf full");
break;
}
to -= 10;
}
if (tts_info.status == IFLY_TTS_STATUS_RECV) {
tts_info.status = IFLY_TTS_STATUS_RECV_SEC; // 如果有多帧语音数据,第二帧才开始播放,避免mp3_dec获取不到数据提前结束解码
log_info("tts start play!\n");
ifly_tts_dec_func(ifly_net_tts_start, 0);
tts_info.param->event_cb(IFLY_TTS_EVT_PLAY_START, tts_info.param);
}
if (tts_info.status < IFLY_TTS_STATUS_RECV) {
tts_info.status = IFLY_TTS_STATUS_RECV;
if (cjson_status->valueint == RECV_LAST_FRAME) { // 如果只有一帧语音数据,直接播放
log_info("tts start play!\n");
ifly_tts_dec_func(ifly_net_tts_start, 0);
tts_info.param->event_cb(IFLY_TTS_EVT_PLAY_START, tts_info.param);
}
}
if (cjson_status->valueint == RECV_LAST_FRAME) {
log_info("tts last frame!\n");
tts_info.status = IFLY_TTS_STATUS_RECV_END;
tts_info.recv_finish = 1;
ifly_tts_dec_func(ifly_net_tts_stop, 3000);
tts_info.param->event_cb(IFLY_TTS_EVT_PLAY_STOP, tts_info.param);
}
cJSON_free(audio);
cJSON_Delete(cjson_root);
net_iflytek_free(res_tts);
/* ifly_net_tts_dec_resume(NULL); */
}
static bool ifly_tts_get_send(u8 **buf, u32 *len)
{
if (tts_info.force_stop) {
log_info("tts task kill!\n");
return false;
}
if (tts_info.status >= IFLY_TTS_STATUS_PLAY_END) {
log_info("tts task kill!\n");
return false;
}
if (tts_info.status < IFLY_TTS_STATUS_SEND) {
tts_info.status = IFLY_TTS_STATUS_SEND;
char *input_src_json = ifly_tts_format_text_data();
if (input_src_json == NULL) {
log_error("get json err \n");
return false;
}
*buf = (u8 *)input_src_json;
*len = strlen(input_src_json);
return true;
}
os_time_dly(2);
return true;
}
static int ifly_tts_event_cb(ifly_socket_event_enum evt, void *param)
{
switch (evt) {
case IFLY_SOCKET_EVT_SEND_OK:
if (!tts_info.tts_timer) {
tts_info.tts_timer = sys_timer_add(NULL, tts_recv_timeout_timer, 1000);
}
cJSON_free(param);
break;
case IFLY_SOCKET_EVT_SEND_ERROR:
cJSON_free(param);
break;
case IFLY_SOCKET_EVT_INIT_OK:
break;
case IFLY_SOCKET_EVT_INIT_ERROR:
case IFLY_SOCKET_EVT_HANSHACK_ERROR:
case IFLY_SOCKET_EVT_ACCIDENT_END:
case IFLY_SOCKET_EVT_END:
case IFLY_SOCKET_EVT_FORCE_END:
if (ifly_net_tts_dec_check_run()) { // 正常结束会自动关闭,这里为异常或者强制关闭处理
int to = 10;
if (evt == IFLY_SOCKET_EVT_ACCIDENT_END) { //如果是意外退出,先播放完cbuf的内容
to = 300;
} else {
if (tts_info.status < IFLY_TTS_STATUS_RECV_ERROR) {
tts_info.status = IFLY_TTS_STATUS_RECV_ERROR;
}
}
ifly_tts_dec_func(ifly_net_tts_stop, to * 10);
while (ifly_net_tts_dec_check_run()) {
os_time_dly(1);
to -= 1;
if (to <= 0) {
break;
}
if (tts_info.force_stop) {
tts_info.status = IFLY_TTS_STATUS_RECV_ERROR;
break;
}
}
}
if ((evt != IFLY_SOCKET_EVT_END) && (evt != IFLY_SOCKET_EVT_FORCE_END)) {
if (!tts_info.recv_finish) {
tts_info.param->event_cb(IFLY_TTS_EVT_PLAY_FAIL_STOP, tts_info.param);
}
}
break;
case IFLY_SOCKET_EVT_EXIT:
tts_info.status = IFLY_TTS_STATUS_EXIT;
tts_info.param->event_cb(IFLY_TTS_EVT_EXIT, tts_info.param);
if (tts_socket.auth) {
net_iflytek_free(tts_socket.auth);
tts_socket.auth = NULL;
}
if (tts_info.tts_timer) {
sys_timer_del(tts_info.tts_timer);
tts_info.tts_timer = 0;
}
if (tts_info.dec_out_buf) {
net_iflytek_free(tts_info.dec_out_buf);
tts_info.dec_out_buf = NULL;;
}
break;
default:
break;
}
return 0;
}
bool ifly_tts_start(ifly_tts_param *param)
{
memset(&tts_info, 0, sizeof(tts_info));
memset(&tts_socket, 0, sizeof(struct ifly_websocket_struct));
#if TCFG_IFLYTEK_USE_PSRAM
cJSON_Hooks hooks;
hooks.malloc_fn = net_iflytek_malloc;
hooks.free_fn = net_iflytek_free;
cJSON_InitHooks(&hooks);
#endif
tts_info.param = param;
tts_info.dec_out_buf = net_iflytek_malloc(sizeof(char) * IFLY_TTS_CBUF_LEN);
cbuf_init(&tts_info.dec_cbuf, tts_info.dec_out_buf, IFLY_TTS_CBUF_LEN);
tts_socket.auth = (u8 *)ifly_authentication("wss://tts-api.xfyun.cn/v2/tts",
"tts-api.xfyun.cn",
"GET /v2/tts HTTP/1.1", 20);
if (!tts_socket.auth) {
net_iflytek_free(tts_info.dec_out_buf);
tts_info.dec_out_buf = NULL;;
tts_info.param->event_cb(IFLY_TTS_EVT_PLAY_FAIL_STOP, tts_info.param);
return false;
}
tts_socket.task_name = "ifly_tts";
tts_socket.socket_mode = WEBSOCKET_MODE;
tts_socket.recv_cb = ifly_tts_recv_cb;
tts_socket.get_send = ifly_tts_get_send;
tts_socket.event_cb = ifly_tts_event_cb;
tts_info.status = IFLY_TTS_STATUS_START;
//创建链接
bool ret = ifly_websocket_client_create(&tts_socket);
if (ret == false) {
tts_info.status = IFLY_TTS_STATUS_NULL;
net_iflytek_free(tts_socket.auth);
tts_socket.auth = NULL;
net_iflytek_free(tts_info.dec_out_buf);
tts_info.dec_out_buf = NULL;;
}
#if TTS_AUDIO_SAVE_TEST
if (save_file) {
fclose(save_file);
save_file = NULL;
}
save_file = fopen("storage/sd0/C/sf.mp3", "w+");
if (!save_file) {
log_error("fopen err \n\n");
}
#endif
return ret;
}
void ifly_tts_stop(u8 force_stop, u32 to_ms)
{
log_info("tts close!\n");
tts_info.force_stop = force_stop;
while (tts_socket.auth) { // 结束时auth会自动释放
os_time_dly(1);
if (to_ms <= 10) {
break;
}
to_ms -= 10;
}
if (to_ms < 1000) {
to_ms = 1000;
}
tts_info.force_stop = 1;
ifly_websocket_client_release(&tts_socket, to_ms);
#if TTS_AUDIO_SAVE_TEST
if (save_file) {
fclose(save_file);
save_file = NULL;
}
#endif
}
bool ifly_tts_is_work()
{
if ((tts_info.status != IFLY_TTS_STATUS_NULL) && (tts_info.status != IFLY_TTS_STATUS_EXIT)) {
return true;
}
return false;
}
#endif
@@ -0,0 +1,26 @@
#ifndef __TTS_MAIN_H
#define __TTS_MAIN_H
#include "generic/includes.h"
typedef enum {
IFLY_TTS_EVT_PLAY_START = 1, // 播放开始。*param: ifly_tts_param*
IFLY_TTS_EVT_PLAY_STOP, // 播放结束。*param: ifly_tts_param*
IFLY_TTS_EVT_PLAY_FAIL_STOP, // 播放非正常结束。例如超时、强制结束等。*param: ifly_tts_param*
IFLY_TTS_EVT_EXIT, // 结束。*param: ifly_tts_param*
} ifly_tts_event_enum ;
typedef struct ifly_tts_struct {
// 参数信息,所有参数都需要赋值
char *text_res; // 文本数据
int (*event_cb)(ifly_tts_event_enum evt, void *param); // 事件回调
} ifly_tts_param;
// TTS启动。*param参数句柄需要在stop之后才能释放
bool ifly_tts_start(ifly_tts_param *param);
// TTS结束。
void ifly_tts_stop(u8 force_stop, u32 to_ms);
// 判断tts是否正在运行
bool ifly_tts_is_work(void);
#endif
@@ -0,0 +1,476 @@
#include "string.h"
#include "vad_main.h"
#include "ifly_dec_file.h"
#include "sparkdesk_main.h"
#include "authentication.h"
#include "websocket_define.h"
#include "ifly_socket.h"
#include "app_config.h"
#include "app_task.h"
#include "system/timer.h"
#include "app_main.h"
#include "init.h"
#include "key_event_deal.h"
#include "device/device.h"
#include "app_power_manage.h"
#include "btstack/avctp_user.h"
#include "asm/charge.h"
#include "cJSON.h"
#include "audio_config.h"
#include "third_party_profile/interface/app_protocol_common.h"
#include "circular_buf.h"
#include "ui/ui_api.h"
#include "ifly_common.h"
#if TCFG_IFLYTEK_ENABLE
#define LOG_TAG_CONST NET_IFLY
#define LOG_TAG "[IFLY_VAD]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#define LOG_CLI_ENABLE
#include "debug.h"
#ifdef TCFG_IFLYTEK_APP_ID
#define APP_ID TCFG_IFLYTEK_APP_ID
#else
#define APP_ID "123"
#endif
#if TCFG_ENC_SPEEX_ENABLE
#define AI_AUDIO_CODING_TYPE AUDIO_CODING_SPEEX // 编码格式
#else
#error "ONLY SUPPORT SPEEX"
#endif
#define AI_AUDIO_CODING_SR 16000 // 采样率。和audio_mic_enc_open()函数中的对应
#define PCM_OUT_BUF_LEN (AI_AUDIO_CODING_SR*2/1000 * 30)
#define SPEEX_SIZE 42
#define AUDIO_LEN 168
#define BASE63_AUDIO_LEN 256
#define STATUS_FIRST_FRAME 0
#define STATUS_CONTINUE_FRAME 1
#define STATUS_LAST_FRAME 2
#define STATUS_NED_FRAME 3
#define HEART_BEAT_REQ "client ping" // 服务器下发的心跳保持请求
typedef enum {
IFLY_VAD_STATUS_NULL = 0,
IFLY_VAD_STATUS_START, // 启动
IFLY_VAD_STATUS_PCM_START, // 音频启动发数
IFLY_VAD_STATUS_SENDING, // 音频数据发数中
IFLY_VAD_STATUS_SEND_END, // 音频数据发数完毕
IFLY_VAD_STATUS_RECV, // 有接受到数据
IFLY_VAD_STATUS_RECV_END, // 接受完成
IFLY_VAD_STATUS_RECV_ERROR, // 接受错误
IFLY_VAD_STATUS_EXIT, // 已经退出
} ifly_vad_status;
struct vad_info_t {
u8 force_stop; // 强制结束
u8 recv_finish; // 接收结束
u8 frame_status;
char *pcm_out_buf;
cbuffer_t pcm_cbuf;
ifly_vad_status status;
ifly_vad_param *param;
};
static struct vad_info_t vad_info;
static struct ifly_websocket_struct vad_socket;
#define AI_AUDIO_SAVE_TEST 0
#if AI_AUDIO_SAVE_TEST
static FILE *save_file = NULL;
#endif
extern int mbedtls_base64_encode(unsigned char *dst, size_t dlen, size_t *olen, const unsigned char *src, size_t slen);
//录音编码模块
static u16 vad_audio_send_data(u8 *voice_buf, u16 voice_len)
{
#if AI_AUDIO_SAVE_TEST
if (save_file) {
int wlen = fwrite(save_file, voice_buf, voice_len);
if (wlen != voice_len) {
log_error("save file err: %d, %d\n", wlen, voice_len);
}
}
#endif
//上传数据到服务器
int wlen = cbuf_write(&vad_info.pcm_cbuf, voice_buf, voice_len);
if (wlen != voice_len) {
log_error("pcm out err: %d, %d\n", wlen, voice_len);
}
return 0;
}
static int vad_audio_stop(int cancel)
{
if (!ai_mic_is_busy()) {
log_error("ai_mic_is_null \n\n");
return true;
}
ai_mic_rec_close();
#if AI_AUDIO_SAVE_TEST
if (save_file) {
fclose(save_file);
save_file = NULL;
}
#endif
return true;
}
static int vad_audio_start(void)
{
if (ai_mic_is_busy()) {
log_error("ai_mic_is_busy \n\n");
return false;
}
#if AI_AUDIO_SAVE_TEST
if (save_file) {
fclose(save_file);
save_file = NULL;
}
save_file = fopen("storage/sd0/C/sf.bin", "w+");
if (!save_file) {
log_error("fopen err \n\n");
}
#endif
mic_rec_pram_init(AI_AUDIO_CODING_TYPE, 0, vad_audio_send_data, 4, 1024);
ai_mic_rec_start();
return true;
}
static void ifly_vad_recv_cb(u8 *j_str, u32 len, u8 type)
{
log_info("recv:%s\n", j_str);
if (vad_info.force_stop) {
return;
}
if (vad_info.status >= IFLY_VAD_STATUS_RECV_END) {
return;
}
if (!strcmp((char *)j_str, HEART_BEAT_REQ)) { // 保持心跳请求,不解析
return;
}
cJSON *cjson_root = cJSON_Parse((char *)j_str); //json解析错误
if (cjson_root == NULL) {
log_error("cjson error...\r\n");
if (vad_info.status <= IFLY_VAD_STATUS_RECV_END) {
vad_info.status = IFLY_VAD_STATUS_RECV_ERROR;
}
vad_info.param->event_cb(IFLY_VAD_EVT_NETWORK_RECV_ERROR, vad_info.param);
return;
}
vad_info.status = IFLY_VAD_STATUS_RECV;
cJSON *cjson_data = cJSON_GetObjectItem(cjson_root, "data");
cJSON *cjson_status = cJSON_GetObjectItem(cjson_data, "status");
cJSON *cjson_result = cJSON_GetObjectItem(cjson_data, "result");
cJSON *cjson_ws = cJSON_GetObjectItem(cjson_result, "ws");
int arr_size = cJSON_GetArraySize(cjson_ws);
cJSON *arr_item = cjson_ws->child;
u32 vad_res_len = strlen(vad_info.param->vad_res);
for (int i = 0; i < arr_size; i++) {
cJSON *cjson_cw = cJSON_GetObjectItem(arr_item, "cw");
int arr_size_cw = cJSON_GetArraySize(cjson_cw);
cJSON *arr_item_cw = cjson_cw->child;
for (int j = 0; j < arr_size_cw; j++) {
cJSON *cjson_w = cJSON_GetObjectItem(arr_item_cw, "w");
char *cjson_str = cJSON_Print(cjson_w);
u32 json_len = strlen(cjson_str);
if ((vad_res_len + json_len + 1) > vad_info.param->vad_res_len) {
log_error("len error\n");
} else {
strcpy(&vad_info.param->vad_res[vad_res_len], cjson_str);
vad_res_len += json_len;
}
arr_item_cw = arr_item_cw->next;
cJSON_free(cjson_str);
}
arr_item = arr_item->next;
}
int res_len = strlen(vad_info.param->vad_res);
str_remove_quote(vad_info.param->vad_res, res_len);
log_info("final res:%s\n", vad_info.param->vad_res);
if (cjson_status->valueint == STATUS_LAST_FRAME) {
vad_info.status = IFLY_VAD_STATUS_RECV_END;
vad_info.recv_finish = 1;
vad_info.param->event_cb(IFLY_VAD_EVT_RECV_OK, vad_info.param);
}
cJSON_Delete(cjson_root);
}
//语音听写数据模块
char *ifly_vad_format_audio_data(void)
{
char *data_str = NULL;
cJSON *cjson_test = NULL;
cJSON *cjson_common = NULL;
cJSON *cjson_business = NULL;
cJSON *cjson_data = NULL;
int out_len = 0;
char *buf = net_iflytek_malloc(BASE63_AUDIO_LEN);
char *audio_data = net_iflytek_malloc(AUDIO_LEN);
ASSERT(buf);
ASSERT(audio_data);
int rlen = cbuf_read(&vad_info.pcm_cbuf, audio_data, AUDIO_LEN);
if (rlen != AUDIO_LEN) {
net_iflytek_free(buf);
net_iflytek_free(audio_data);
return NULL;
}
if (vad_info.frame_status == STATUS_FIRST_FRAME) {
mbedtls_base64_encode((unsigned char *)buf, BASE63_AUDIO_LEN, (size_t *)&out_len, (unsigned char *)audio_data, AUDIO_LEN);
//编码第一帧
cjson_test = cJSON_CreateObject();
cjson_common = cJSON_CreateObject();
cjson_business = cJSON_CreateObject();
cjson_data = cJSON_CreateObject();
cJSON_AddStringToObject(cjson_common, "app_id", APP_ID);
cJSON_AddItemToObject(cjson_test, "common", cjson_common);
cJSON_AddStringToObject(cjson_business, "language", "zh_cn");
cJSON_AddStringToObject(cjson_business, "domain", "iat");
cJSON_AddStringToObject(cjson_business, "accent", "mandarin");
cJSON_AddNumberToObject(cjson_business, "speex_size", SPEEX_SIZE);
cJSON_AddItemToObject(cjson_test, "business", cjson_business);
cJSON_AddNumberToObject(cjson_data, "status", 0);
cJSON_AddStringToObject(cjson_data, "format", "audio/L16;rate=16000");
cJSON_AddStringToObject(cjson_data, "encoding", "speex-wb");
cJSON_AddStringToObject(cjson_data, "audio", buf);
cJSON_AddItemToObject(cjson_test, "data", cjson_data);
data_str = cJSON_Print(cjson_test);
cJSON_Delete(cjson_test);
vad_info.frame_status = STATUS_CONTINUE_FRAME;
} else if (vad_info.frame_status == STATUS_CONTINUE_FRAME) {
mbedtls_base64_encode((unsigned char *)buf, BASE63_AUDIO_LEN, (size_t *)&out_len, (unsigned char *)audio_data, AUDIO_LEN);
//编码
cjson_test = cJSON_CreateObject();
cjson_data = cJSON_CreateObject();
cJSON_AddNumberToObject(cjson_data, "status", 1);
cJSON_AddStringToObject(cjson_data, "format", "audio/L16;rate=16000");
cJSON_AddStringToObject(cjson_data, "encoding", "speex-wb");
cJSON_AddStringToObject(cjson_data, "audio", buf);
cJSON_AddItemToObject(cjson_test, "data", cjson_data);
data_str = cJSON_Print(cjson_test);
cJSON_Delete(cjson_test);
} else {
//编码最后一帧
mbedtls_base64_encode((unsigned char *)buf, BASE63_AUDIO_LEN, (size_t *)&out_len, (unsigned char *)audio_data, AUDIO_LEN);
cjson_test = cJSON_CreateObject();
cjson_data = cJSON_CreateObject();
cJSON_AddNumberToObject(cjson_data, "status", 2);
cJSON_AddStringToObject(cjson_data, "format", "audio/L16;rate=16000");
cJSON_AddStringToObject(cjson_data, "encoding", "speex-wb");
cJSON_AddStringToObject(cjson_data, "audio", buf);
cJSON_AddItemToObject(cjson_test, "data", cjson_data);
data_str = cJSON_Print(cjson_test);
cJSON_Delete(cjson_test);
if (vad_info.status < IFLY_VAD_STATUS_SEND_END) {
vad_info.status = IFLY_VAD_STATUS_SEND_END;
vad_audio_stop(1);
cbuf_clear(&vad_info.pcm_cbuf);
}
}
net_iflytek_free(buf);
net_iflytek_free(audio_data);
return data_str;
}
static bool ifly_vad_get_send(u8 **buf, u32 *len)
{
if (vad_info.force_stop) {
log_info("vad task kill!\n");
return false;
}
if (vad_info.status >= IFLY_VAD_STATUS_RECV_END) {
log_info("recv end vad task kill!\n");
return false;
}
if (vad_info.status < IFLY_VAD_STATUS_PCM_START) {
vad_info.status = IFLY_VAD_STATUS_PCM_START;
vad_audio_start();
vad_info.param->event_cb(IFLY_VAD_EVT_AUDIO_START, vad_info.param);
}
if (cbuf_get_data_len(&vad_info.pcm_cbuf) >= AUDIO_LEN) {
char *input_src_json = ifly_vad_format_audio_data();
if (input_src_json == NULL) {
log_error("get json err \n");
return false;
}
*buf = (u8 *)input_src_json;
*len = strlen(input_src_json);
return true;
}
os_time_dly(2);
return true;
}
static int ifly_vad_event_cb(ifly_socket_event_enum evt, void *param)
{
switch (evt) {
case IFLY_SOCKET_EVT_SEND_OK:
cJSON_free(param);
break;
case IFLY_SOCKET_EVT_SEND_ERROR:
cJSON_free(param);
break;
case IFLY_SOCKET_EVT_INIT_OK:
break;
case IFLY_SOCKET_EVT_INIT_ERROR:
case IFLY_SOCKET_EVT_HANSHACK_ERROR:
case IFLY_SOCKET_EVT_ACCIDENT_END:
case IFLY_SOCKET_EVT_END:
case IFLY_SOCKET_EVT_FORCE_END:
vad_audio_stop(1);
if ((evt != IFLY_SOCKET_EVT_END) && (evt != IFLY_SOCKET_EVT_FORCE_END)) {
if (!vad_info.recv_finish) {
vad_info.param->event_cb(IFLY_VAD_EVT_NETWORK_FAIL, vad_info.param);
}
}
break;
case IFLY_SOCKET_EVT_EXIT:
vad_info.status = IFLY_VAD_STATUS_EXIT;
vad_info.param->event_cb(IFLY_VAD_EVT_EXIT, vad_info.param);
if (vad_socket.auth) {
net_iflytek_free(vad_socket.auth);
vad_socket.auth = NULL;
}
if (vad_info.pcm_out_buf) {
net_iflytek_free(vad_info.pcm_out_buf);
vad_info.pcm_out_buf = NULL;
}
break;
default:
break;
}
return 0;
}
bool ifly_vad_start(ifly_vad_param *param)
{
memset(&vad_info, 0, sizeof(struct vad_info_t));
memset(&vad_socket, 0, sizeof(struct ifly_websocket_struct));
#if TCFG_IFLYTEK_USE_PSRAM
cJSON_Hooks hooks;
hooks.malloc_fn = net_iflytek_malloc;
hooks.free_fn = net_iflytek_free;
cJSON_InitHooks(&hooks);
#endif
vad_info.pcm_out_buf = net_iflytek_malloc(PCM_OUT_BUF_LEN);
ASSERT(vad_info.pcm_out_buf);
cbuf_init(&vad_info.pcm_cbuf, vad_info.pcm_out_buf, PCM_OUT_BUF_LEN);
vad_info.param = param;
vad_socket.auth = (u8 *)ifly_authentication("wss://iat-api.xfyun.cn/v2/iat",
"iat-api.xfyun.cn",
"GET /v2/iat HTTP/1.1", 20);
if (!vad_socket.auth) {
net_iflytek_free(vad_info.pcm_out_buf);
vad_info.pcm_out_buf = NULL;
vad_info.param->event_cb(IFLY_VAD_EVT_NETWORK_FAIL, vad_info.param);
return false;
}
vad_socket.task_name = "ifly_vad";
vad_socket.socket_mode = WEBSOCKET_MODE;
vad_socket.recv_cb = ifly_vad_recv_cb;
vad_socket.get_send = ifly_vad_get_send;
vad_socket.event_cb = ifly_vad_event_cb;
vad_info.status = IFLY_VAD_STATUS_START;
//创建链接
bool ret = ifly_websocket_client_create(&vad_socket);
if (ret == false) {
vad_info.status = IFLY_VAD_STATUS_NULL;
net_iflytek_free(vad_socket.auth);
vad_socket.auth = NULL;
net_iflytek_free(vad_info.pcm_out_buf);
vad_info.pcm_out_buf = NULL;
}
return ret;
}
void ifly_vad_stop(u8 force_stop, u32 to_ms)
{
log_info("vad stop!\n");
ifly_vad_audio_stop();
vad_info.force_stop = force_stop;
while (vad_socket.auth) { // 结束时auth会自动释放
os_time_dly(1);
if (to_ms <= 10) {
break;
}
to_ms -= 10;
}
if (to_ms < 1000) {
to_ms = 1000;
}
vad_info.force_stop = 1;
ifly_websocket_client_release(&vad_socket, to_ms);
}
void ifly_vad_audio_stop(void)
{
vad_info.frame_status = STATUS_LAST_FRAME; // 停止语音发送
}
bool ifly_vad_is_work()
{
if ((vad_info.status != IFLY_VAD_STATUS_NULL) && (vad_info.status != IFLY_VAD_STATUS_EXIT)) {
return true;
}
return false;
}
#endif
@@ -0,0 +1,35 @@
#ifndef __VAD_MAIN_H
#define __VAD_MAIN_H
#include "generic/includes.h"
#define MAX_VAD_LEN 1000
typedef enum {
IFLY_VAD_EVT_AUDIO_START = 1, // 音频启动。*param: ifly_vad_param*
IFLY_VAD_EVT_RECV_OK, // VAD接受数据完毕。*param: ifly_vad_param*
IFLY_VAD_EVT_NETWORK_FAIL, // 网络错误。*param: ifly_ai_param*
IFLY_VAD_EVT_NETWORK_RECV_ERROR, // 网络接收错误。*param: ifly_ai_param*
IFLY_VAD_EVT_EXIT, // 结束。*param: ifly_ai_param*
} ifly_vad_event_enum ;
typedef struct ifly_vad_struct {
// 参数信息,所有参数都需要赋值
char *vad_res; // 输出数据
u32 vad_res_len; // 输出数据buf长度,最大MAX_VAD_LEN
int (*event_cb)(ifly_vad_event_enum evt, void *param); // 事件回调
} ifly_vad_param;
// VAD启动。*param参数句柄需要在stop之后才能释放
bool ifly_vad_start(ifly_vad_param *param);
// VAD结束。
void ifly_vad_stop(u8 force_stop, u32 to_ms);
// VAD停止发送语音。
void ifly_vad_audio_stop(void);
// 判断vad是否正在运行
bool ifly_vad_is_work(void);
#endif
@@ -0,0 +1,98 @@
#ifdef SUPPORT_MS_EXTENSIONS
#pragma bss_seg(".intelligent_duer_auth.data.bss")
#pragma data_seg(".intelligent_duer_auth.data")
#pragma const_seg(".intelligent_duer_auth.text.const")
#pragma code_seg(".intelligent_duer_auth.text")
#endif
#include "duer_auth_algorithm.h"
#if INTELLIGENT_DUER
#define LOG_TAG_CONST NET_DUER
#define LOG_TAG "[DUER_AUTH]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#define LOG_CLI_ENABLE
#include "debug.h"
#define CST_OFFSET_SECONDS (28800) // 北京时间时差(秒)
#define UUID_BYTE_LENGTH (16) // UUID字节数
#define STRIPPED_UUID_LEN (32) // 无横杠UUID长度
#define RANDOM_PART_LEN (8) // 随机部分长度
#define CHARSET_SIZE (62) // 字符集大小
#define UUID_BUFFER_SIZE (33) // UUID缓冲区大小
#define REQUEST_ID_BUFFER_SIZE (60) // 请求ID缓冲区大小
#define RANDOM_STRING_LENGTH (6) // 随机字符串长度
// 生成随机字节函数
static void duer_get_random_bytes(unsigned char *buf, int nbytes)
{
while (nbytes--) {
*buf = random32(0);
++buf;
}
}
// 生成UUID字符串函数
static void generate_uuid_string_without_hyphens(char *uuid_buffer)
{
unsigned char rand_bytes[UUID_BYTE_LENGTH];
duer_get_random_bytes(rand_bytes, UUID_BYTE_LENGTH);
snprintf(uuid_buffer, UUID_BUFFER_SIZE,
"%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x",
rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7],
rand_bytes[8], rand_bytes[9], rand_bytes[10], rand_bytes[11],
rand_bytes[12], rand_bytes[13], rand_bytes[14], rand_bytes[15]);
}
// 生成对话请求ID函数
void duer_generate_dialog_request_id(char *request_id)
{
struct sys_time curtime;
net_get_sys_time(&curtime);
// 时间参数日志
log_info("Current Time Parameters:");
log_info("Year: %d", curtime.year);
log_info("Month: %d", curtime.month);
log_info("Day: %d", curtime.day);
log_info("Hour: %d", curtime.hour);
log_info("Minute:%d", curtime.min);
log_info("Second:%d", curtime.sec);
// 时间戳计算
long long utc_seconds = timestamp_mytime_2_utc_sec(&curtime) - CST_OFFSET_SECONDS;
long long milliseconds = utc_seconds * 1000; // 转换为毫秒
log_info(">>>info: %s %d %s utc_s %lld utc_ms %lld \n", __FUNCTION__, __LINE__, __FILE__, utc_seconds, milliseconds);
char stripped_uuid[STRIPPED_UUID_LEN + 1] = {0};
generate_uuid_string_without_hyphens(stripped_uuid);
char random_part[RANDOM_PART_LEN + 1] = {0};
strncpy(random_part, stripped_uuid, RANDOM_PART_LEN);
snprintf(request_id, REQUEST_ID_BUFFER_SIZE, "%lld_%s", milliseconds, random_part);
}
// 生成随机字符串函数
void duer_generate_random_string(char *output, int length)
{
const char charset[] = "0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
const int charset_size = sizeof(charset) - 1; // 字符集实际大小
unsigned char rand_bytes[length];
duer_get_random_bytes(rand_bytes, length);
for (int i = 0; i < length; i++) {
output[i] = charset[rand_bytes[i] % charset_size];
}
output[length] = '\0'; // 确保字符串终止符
}
#endif
@@ -0,0 +1,21 @@
#ifndef _DUER_AUTH_ALGORITHM_H_
#define _DUER_AUTH_ALGORITHM_H_
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "sys_time.h"
#include "duer_common.h"
#include "app_config.h"
extern unsigned int random32(int type);
// 生成对话请求ID函数
extern void duer_generate_dialog_request_id(char *request_id);
// 生成6个字符的随机字符串
extern void duer_generate_random_string(char *output, int length);
#endif
@@ -0,0 +1,34 @@
#ifndef _DUER_COMMON_H_
#define _DUER_COMMON_H_
#include "system/includes.h"
#include "duer_auth_algorithm.h"
#include "duer_http_req.h"
#include "duer_socket.h"
#include "duer_json_parse.h"
#include "duer_json_request.h"
#include "duer_task.h"
#include "board_config.h"
#include "cJSON.h"
#include "net_includes.h"
#if TCFG_INTELLIGENT_DUER && TCFG_NETAPPLICATION_ENABLE
#define INTELLIGENT_DUER TCFG_INTELLIGENT_DUER
#else
#define INTELLIGENT_DUER 0
#endif
#define DUER_CLIENT_AK "xxxxxx"
#define DUER_CLIENT_SK "xxxxxx"
#define DUER_APP_PID "xxxxxx"
#define DUER_APP_FORMAT "opus"
#define DUER_APP_SAMPLE 16000
#endif
@@ -0,0 +1,62 @@
#ifdef SUPPORT_MS_EXTENSIONS
#pragma bss_seg(".intelligent_duer_network.data.bss")
#pragma data_seg(".intelligent_duer_network.data")
#pragma const_seg(".intelligent_duer_network.text.const")
#pragma code_seg(".intelligent_duer_network.text")
#endif
#include "duer_common.h"
#if INTELLIGENT_DUER
#define LOG_TAG_CONST NET_DUER
#define LOG_TAG "[DUER_HTTP_REQ]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#define LOG_CLI_ENABLE
#include "debug.h"
static TokenData *duer_get_access_token_child()
{
char url_buffer[256];
snprintf(url_buffer, sizeof(url_buffer),
"https://openapi.baidu.com/oauth/2.0/token?"
"grant_type=client_credentials&"
"client_id=%s&"
"client_secret=%s",
DUER_CLIENT_AK, DUER_CLIENT_SK);
char *response = NULL;
net_http_get_request(url_buffer, &response);
if (!response) {
log_error("API request failed\n");
return NULL;
}
TokenData *token = duer_parse_token_json(response);
net_interface_free(response); // 无论成功与否都要释放响应
if (!token) {
log_error("JSON parsing failed\n");
}
return token;
}
TokenData *duer_get_access_token()
{
TokenData *token = duer_get_access_token_child();
if (!token) {
log_error("Failed to get access token\n");
return NULL;
}
log_debug("refresh_token: %s\n", token->refresh_token);
log_debug("expires_in: %d\n", token->expires_in);
log_debug("session_key: %s\n", token->session_key);
log_debug("access_token: %s\n", token->access_token);
log_debug("scope: %s\n", token->scope);
log_debug("session_secret: %s\n", token->session_secret);
return token;
}
#endif
@@ -0,0 +1,13 @@
#ifndef _DUER_HTTP_REQ_H_
#define _DUER_HTTP_REQ_H_
#include "duer_common.h"
#include "duer_json_parse.h"
#include "time.h"
extern TokenData *duer_get_access_token();
#endif
@@ -0,0 +1,254 @@
#ifdef SUPPORT_MS_EXTENSIONS
#pragma bss_seg(".intelligent_duer_network.data.bss")
#pragma data_seg(".intelligent_duer_network.data")
#pragma const_seg(".intelligent_duer_network.text.const")
#pragma code_seg(".intelligent_duer_network.text")
#endif
#include "duer_socket.h"
#if INTELLIGENT_DUER
#define LOG_TAG_CONST NET_DUER
#define LOG_TAG "[DUER_SOCKET]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#define LOG_CLI_ENABLE
#include "debug.h"
#define OBJ_URL "ws://duer-kids.baidu.com/sandbox/sota/realtime_asr?sn=%s"
#define CUID_LEN 6
#define MAX_URL_LEN 256
#define MAX_TOKEN_LEN 256
static void task_kill_callback(char *buf);
static u8 force_kill = 0; //结束标志位
static void websockets_callback(u8 *buf, u32 len, u8 type)
{
log_info("wbs recv msg : %s\n", buf);
InsideRCResponse *response = duer_parse_inside_rc_json((char *)buf);
if (response &&
response->data &&
response->data->is_end == 1) {
force_kill = 1;
duer_free_inside_rc_response(response);
duer_netdownload_msg();
}
}
/*******************************************************************************
* Websocket Client api
*******************************************************************************/
static void websockets_client_reg(struct websocket_struct *websockets_info, char mode)
{
memset(websockets_info, 0, sizeof(struct websocket_struct));
websockets_info->_init = websockets_client_socket_init;
websockets_info->_exit = websockets_client_socket_exit;
websockets_info->_handshack = webcockets_client_socket_handshack;
websockets_info->_send = websockets_client_socket_send;
websockets_info->_recv_thread = websockets_client_socket_recv_thread;
websockets_info->_heart_thread = websockets_client_socket_heart_thread;
websockets_info->_recv_cb = websockets_callback;
websockets_info->_recv = NULL;
websockets_info->websocket_mode = mode;
}
static int websockets_client_init(struct websocket_struct *websockets_info, u8 *url, const char *origin_str, const char *user_agent_str)
{
websockets_info->ip_or_url = url;
websockets_info->origin_str = origin_str;
websockets_info->user_agent_str = user_agent_str;
websockets_info->recv_time_out = 1000;
//应用层和库的版本检测,结构体不一样则返回出错
int err = websockets_struct_check(sizeof(struct websocket_struct));
if (err == FALSE) {
return err;
}
return websockets_info->_init(websockets_info);
}
static int websockets_client_handshack(struct websocket_struct *websockets_info)
{
log_info("myurl %s \n", websockets_info->ip_or_url);
return websockets_info->_handshack(websockets_info);
}
static int websockets_client_send(struct websocket_struct *websockets_info, u8 *buf, int len, char type)
{
//SSL加密时一次发送数据不能超过16K,用户需要自己分包
return websockets_info->_send(websockets_info, buf, len, type);
}
static void websockets_client_exit(struct websocket_struct *websockets_info)
{
websockets_info->_exit(websockets_info);
}
/*******************************************************************************
* Websocket Client.c
* Just one example for test
*******************************************************************************/
static void websockets_client_main_thread(void *priv)
{
int err = 0;
char mode = WEBSOCKET_MODE;
char access_token[MAX_TOKEN_LEN] = {0};
char cuid_str[CUID_LEN + 1] = {0};
char url[MAX_URL_LEN] = {0};
duer_generate_random_string(cuid_str, sizeof(cuid_str) - 1);
log_debug("Generated CUID: %s", cuid_str);
if (snprintf(url, sizeof(url), OBJ_URL, cuid_str) >= sizeof(url)) {
log_error("URL buffer overflow");
return;
}
if (snprintf(access_token, sizeof(access_token), "%s", (char *)priv) >= sizeof(access_token)) {
log_error("Token buffer overflow");
return;
}
log_debug("WebSocket URL: %s, Access Token: %s", url, access_token);
const char *origin_str = "http://coolaf.com";
/* 0 . malloc buffer */
struct websocket_struct *websockets_info = net_interface_malloc(sizeof(struct websocket_struct));
if (!websockets_info) {
return;
}
/* 1 . register */
websockets_client_reg(websockets_info, mode);
/* 2 . init */
err = websockets_client_init(websockets_info, (u8 *)url, origin_str, NULL);
if (FALSE == err) {
log_error(" . ! Cilent websocket init error !!!\r\n");
goto exit_ws;
}
/* 3 . hanshack */
err = websockets_client_handshack(websockets_info);
if (FALSE == err) {
log_error(" . ! Handshake error !!!\r\n");
goto exit_ws;
}
log_debug(" . Handshake success \r\n");
/* 4 . CreateThread */
err = os_task_create(websockets_info->_heart_thread,
websockets_info,
19,
512,
0,
"websocket_client_heart");
if (err == 0) {
websockets_info->ping_thread_id = 1;
} else {
websockets_info->ping_thread_id = 0;
}
err = os_task_create(websockets_info->_recv_thread,
websockets_info,
18,
512,
0,
"websocket_client_recv");
if (err == 0) {
websockets_info->recv_thread_id = 1;
} else {
websockets_info->recv_thread_id = 0;
}
os_time_dly(100);
//server_log_id
char dialog_id[60];
duer_generate_dialog_request_id(dialog_id);// 生成对话请求ID
log_debug("Generated Dialog Request ID: %s\n", dialog_id);
//start_frame
char *start_str = duer_start_frame(DUER_CLIENT_AK, DUER_CLIENT_SK, DUER_CLIENT_AK, DUER_APP_PID,
cuid_str, DUER_APP_FORMAT, 1, DUER_APP_SAMPLE,
cuid_str, access_token, 1,
1, dialog_id, 1,
"1");
log_debug(">>>start_str %s \n", start_str);
err = websockets_client_send(websockets_info, (u8 *)start_str, strlen(start_str), WCT_TXTDATA);
if (FALSE == err) {
log_debug(" . ! send err !!!\r\n");
goto exit_ws;
}
os_time_dly(40);
/* char *finish = build_finish_frame(); */
net_rec_start();
u8 send_buf[40];
u32 buf_size = sizeof(send_buf);
while (1) {
int available = net_rec_data_len();
if (available == 0) {
os_time_dly(1);
continue;
}
int bytes_read = net_rec_read_data(send_buf, buf_size);
if (bytes_read == 0) {
os_time_dly(1);
continue;
}
printf("Read %d bytes from cbuf\n", bytes_read);
err = websockets_client_send(websockets_info, send_buf, bytes_read, WCT_BINDATA);
if (false == err) {
log_error(" . ! send err !!!\r\n");
goto exit_ws;
}
if (force_kill) {
goto exit_ws;
}
os_time_dly(10);
}
exit_ws:
/* 6 . exit */
net_record_stop_with_clean();
if (websockets_info->ping_thread_id) {
websockets_info->ping_kill_flag = 1;
}
if (websockets_info->recv_thread_id) {
websockets_info->recv_kill_flag = 1;
}
while (websockets_info->recv_kill_flag || websockets_info->ping_kill_flag) {
os_time_dly(10);
}
websockets_client_exit(websockets_info);
net_interface_free(websockets_info);
net_interface_free(start_str);
/* my_free(finish); */
int msg[5];
msg[0] = (int)task_kill_callback;
msg[1] = 1;
msg[2] = (int)os_current_task();
err = os_taskq_post_type("app_core", Q_CALLBACK, 3, msg);
os_time_dly(-1);
}
static void task_kill_callback(char *buf)
{
log_info("[msg]>>>>>>>>>>>*buf=%s", buf);
task_kill(buf);
}
void duer_websocket_client_thread_create(void *priv)
{
net_url_list_init();
force_kill = 0;
os_task_create(websockets_client_main_thread,
priv,
15,
512 * 5,
0,
"websockets_client_main");
}
#endif
@@ -0,0 +1,16 @@
#ifndef _DUER_SOCKET_H_
#define _DUER_SOCKET_H_
#include "websocket_api.h"
#include "duer_common.h"
extern void duer_websocket_client_thread_create(void *priv);
extern int task_kill(const char *name);
#endif
@@ -0,0 +1,488 @@
#ifdef SUPPORT_MS_EXTENSIONS
#pragma bss_seg(".intelligent_duer_parse.data.bss")
#pragma data_seg(".intelligent_duer_parse.data")
#pragma const_seg(".intelligent_duer_parse.text.const")
#pragma code_seg(".intelligent_duer_parse.text")
#endif
#include "duer_common.h"
#if INTELLIGENT_DUER
#define LOG_TAG_CONST NET_DUER
#define LOG_TAG "[DUER_JSON_PARSE]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#define LOG_CLI_ENABLE
#include "debug.h"
TokenData *duer_parse_token_json(const char *json_str)
{
cJSON *root = cJSON_Parse(json_str);
if (!root) {
log_error("JSON parse error:\n");
return NULL;
}
TokenData *data = (TokenData *)net_interface_malloc(sizeof(TokenData));
if (!data) {
log_error("Memory allocation failed");
cJSON_Delete(root);
return NULL;
}
memset(data, 0, sizeof(TokenData));
cJSON *item;
if ((item = cJSON_GetObjectItem(root, "refresh_token")) && cJSON_IsString(item)) {
data->refresh_token = strdup(item->valuestring);
}
if ((item = cJSON_GetObjectItem(root, "expires_in")) && cJSON_IsNumber(item)) {
data->expires_in = item->valueint;
}
if ((item = cJSON_GetObjectItem(root, "session_key")) && cJSON_IsString(item)) {
data->session_key = strdup(item->valuestring);
}
if ((item = cJSON_GetObjectItem(root, "access_token")) && cJSON_IsString(item)) {
data->access_token = strdup(item->valuestring);
}
if ((item = cJSON_GetObjectItem(root, "scope")) && cJSON_IsString(item)) {
data->scope = strdup(item->valuestring);
}
if ((item = cJSON_GetObjectItem(root, "session_secret")) && cJSON_IsString(item)) {
data->session_secret = strdup(item->valuestring);
}
cJSON_Delete(root);
return data;
}
void duer_free_token_data(TokenData *data)
{
if (data) {
net_interface_free(data->refresh_token);
net_interface_free(data->session_key);
net_interface_free(data->access_token);
net_interface_free(data->scope);
net_interface_free(data->session_secret);
net_interface_free(data);
}
}
InsideRCResponse *duer_parse_inside_rc_json(const char *json_string)
{
InsideRCResponse *response = net_interface_calloc(1, sizeof(InsideRCResponse));
if (!response) {
return NULL;
}
cJSON *root = cJSON_Parse(json_string);
if (!root) {
log_error("Error parsing JSON: %s\n", cJSON_GetErrorPtr());
net_interface_free(response);
return NULL;
}
cJSON *type = cJSON_GetObjectItem(root, "type");
if (cJSON_IsString(type)) {
response->type = strdup(type->valuestring);
}
if (!response->type || strcmp(response->type, "inside_rc") != 0) {
cJSON_Delete(root);
return response;
}
cJSON *status = cJSON_GetObjectItem(root, "status");
if (cJSON_IsString(status)) {
response->status = strdup(status->valuestring);
}
cJSON *sn = cJSON_GetObjectItem(root, "sn");
if (cJSON_IsString(sn)) {
response->sn = strdup(sn->valuestring);
}
cJSON *end = cJSON_GetObjectItem(root, "end");
if (cJSON_IsNumber(end)) {
response->end = end->valueint;
}
cJSON *data_obj = cJSON_GetObjectItem(root, "data");
if (data_obj && cJSON_IsObject(data_obj)) {
response->data = net_interface_calloc(1, sizeof(Data));
if (!response->data) {
cJSON_Delete(root);
duer_free_inside_rc_response(response);
return NULL;
}
cJSON *code = cJSON_GetObjectItem(data_obj, "code");
if (cJSON_IsNumber(code)) {
response->data->code = code->valueint;
}
cJSON *msg = cJSON_GetObjectItem(data_obj, "msg");
if (cJSON_IsString(msg)) {
response->data->msg = strdup(msg->valuestring);
}
cJSON *logid = cJSON_GetObjectItem(data_obj, "logid");
if (cJSON_IsString(logid)) {
response->data->logid = strdup(logid->valuestring);
}
cJSON *qid = cJSON_GetObjectItem(data_obj, "qid");
if (cJSON_IsString(qid)) {
response->data->qid = strdup(qid->valuestring);
}
cJSON *is_end = cJSON_GetObjectItem(data_obj, "is_end");
if (cJSON_IsNumber(is_end)) {
response->data->is_end = is_end->valueint;
}
cJSON *need_clear_history = cJSON_GetObjectItem(data_obj, "need_clear_history");
if (cJSON_IsNumber(need_clear_history)) {
response->data->need_clear_history = need_clear_history->valueint;
}
cJSON *assistant_answer = cJSON_GetObjectItem(data_obj, "assistant_answer");
if (assistant_answer) {
response->data->assistant_answer = net_interface_calloc(1, sizeof(AssistantAnswer));
if (response->data->assistant_answer) {
if (cJSON_IsString(assistant_answer)) {
cJSON *aa_root = cJSON_Parse(assistant_answer->valuestring);
if (aa_root) {
cJSON *content = cJSON_GetObjectItem(aa_root, "content");
if (cJSON_IsString(content)) {
response->data->assistant_answer->content = strdup(content->valuestring);
}
cJSON *nlu = cJSON_GetObjectItem(aa_root, "nlu");
if (cJSON_IsString(nlu)) {
response->data->assistant_answer->nlu = strdup(nlu->valuestring);
}
cJSON *aa_is_end = cJSON_GetObjectItem(aa_root, "is_end");
if (cJSON_IsNumber(aa_is_end)) {
response->data->assistant_answer->is_end = aa_is_end->valueint;
}
cJSON *metadata = cJSON_GetObjectItem(aa_root, "metadata");
if (metadata && cJSON_IsObject(metadata)) {
response->data->assistant_answer->metadata = net_interface_calloc(1, sizeof(Metadata));
if (response->data->assistant_answer->metadata) {
cJSON *multi_round_info = cJSON_GetObjectItem(metadata, "multi_round_info");
if (multi_round_info && cJSON_IsObject(multi_round_info)) {
response->data->assistant_answer->metadata->multi_round_info = net_interface_calloc(1, sizeof(MultiRoundInfo));
if (response->data->assistant_answer->metadata->multi_round_info) {
cJSON *is_in_multi = cJSON_GetObjectItem(multi_round_info, "is_in_multi");
if (cJSON_IsBool(is_in_multi)) {
response->data->assistant_answer->metadata->multi_round_info->is_in_multi = is_in_multi->valueint;
}
cJSON *target_bot_id = cJSON_GetObjectItem(multi_round_info, "target_bot_id");
if (cJSON_IsString(target_bot_id)) {
response->data->assistant_answer->metadata->multi_round_info->target_bot_id = strdup(target_bot_id->valuestring);
}
cJSON *intent = cJSON_GetObjectItem(multi_round_info, "intent");
if (cJSON_IsString(intent)) {
response->data->assistant_answer->metadata->multi_round_info->intent = strdup(intent->valuestring);
}
}
}
}
}
cJSON_Delete(aa_root);
} else {
response->data->assistant_answer->content = strdup(assistant_answer->valuestring);
}
} else if (cJSON_IsObject(assistant_answer)) {
cJSON *content = cJSON_GetObjectItem(assistant_answer, "content");
if (cJSON_IsString(content)) {
response->data->assistant_answer->content = strdup(content->valuestring);
}
cJSON *nlu = cJSON_GetObjectItem(assistant_answer, "nlu");
if (cJSON_IsString(nlu)) {
response->data->assistant_answer->nlu = strdup(nlu->valuestring);
}
cJSON *aa_is_end = cJSON_GetObjectItem(assistant_answer, "is_end");
if (cJSON_IsNumber(aa_is_end)) {
response->data->assistant_answer->is_end = aa_is_end->valueint;
}
cJSON *metadata = cJSON_GetObjectItem(assistant_answer, "metadata");
if (metadata && cJSON_IsObject(metadata)) {
response->data->assistant_answer->metadata = net_interface_calloc(1, sizeof(Metadata));
if (response->data->assistant_answer->metadata) {
cJSON *multi_round_info = cJSON_GetObjectItem(metadata, "multi_round_info");
if (multi_round_info && cJSON_IsObject(multi_round_info)) {
response->data->assistant_answer->metadata->multi_round_info = net_interface_calloc(1, sizeof(MultiRoundInfo));
if (response->data->assistant_answer->metadata->multi_round_info) {
cJSON *is_in_multi = cJSON_GetObjectItem(multi_round_info, "is_in_multi");
if (cJSON_IsBool(is_in_multi)) {
response->data->assistant_answer->metadata->multi_round_info->is_in_multi = is_in_multi->valueint;
}
cJSON *target_bot_id = cJSON_GetObjectItem(multi_round_info, "target_bot_id");
if (cJSON_IsString(target_bot_id)) {
response->data->assistant_answer->metadata->multi_round_info->target_bot_id = strdup(target_bot_id->valuestring);
}
cJSON *intent = cJSON_GetObjectItem(multi_round_info, "intent");
if (cJSON_IsString(intent)) {
response->data->assistant_answer->metadata->multi_round_info->intent = strdup(intent->valuestring);
}
}
}
}
}
}
}
}
cJSON *data_array = cJSON_GetObjectItem(data_obj, "data");
if (data_array && cJSON_IsArray(data_array)) {
int array_size = cJSON_GetArraySize(data_array);
response->data->data_items_count = array_size;
if (array_size > 0) {
response->data->data_items = net_interface_calloc(array_size, sizeof(DataItem *));
if (!response->data->data_items) {
cJSON_Delete(root);
duer_free_inside_rc_response(response);
return NULL;
}
for (int i = 0; i < array_size; i++) {
DataItem *item = net_interface_calloc(1, sizeof(DataItem));
if (!item) {
continue;
}
response->data->data_items[i] = item;
cJSON *array_item = cJSON_GetArrayItem(data_array, i);
cJSON *header = cJSON_GetObjectItem(array_item, "header");
if (header && cJSON_IsObject(header)) {
item->header = net_interface_calloc(1, sizeof(Header));
if (item->header) {
cJSON *namespace = cJSON_GetObjectItem(header, "namespace");
if (cJSON_IsString(namespace)) {
item->header->namespace = strdup(namespace->valuestring);
}
cJSON *name = cJSON_GetObjectItem(header, "name");
if (cJSON_IsString(name)) {
item->header->name = strdup(name->valuestring);
}
cJSON *messageId = cJSON_GetObjectItem(header, "messageId");
if (cJSON_IsString(messageId)) {
item->header->messageId = strdup(messageId->valuestring);
}
cJSON *dialogRequestId = cJSON_GetObjectItem(header, "dialogRequestId");
if (cJSON_IsString(dialogRequestId)) {
item->header->dialogRequestId = strdup(dialogRequestId->valuestring);
}
}
}
cJSON *payload = cJSON_GetObjectItem(array_item, "payload");
if (payload && cJSON_IsObject(payload)) {
item->payload = net_interface_calloc(1, sizeof(Payload));
if (item->payload) {
cJSON *rolling = cJSON_GetObjectItem(payload, "rolling");
if (cJSON_IsBool(rolling)) {
item->payload->rolling = rolling->valueint;
}
cJSON *text = cJSON_GetObjectItem(payload, "text");
if (cJSON_IsString(text)) {
item->payload->text = strdup(text->valuestring);
}
cJSON *content = cJSON_GetObjectItem(payload, "content");
if (cJSON_IsString(content)) {
item->payload->content = strdup(content->valuestring);
}
cJSON *format = cJSON_GetObjectItem(payload, "format");
if (cJSON_IsString(format)) {
item->payload->format = strdup(format->valuestring);
}
cJSON *token = cJSON_GetObjectItem(payload, "token");
if (cJSON_IsString(token)) {
item->payload->token = strdup(token->valuestring);
}
cJSON *url = cJSON_GetObjectItem(payload, "url");
if (cJSON_IsString(url)) {
item->payload->url = strdup(url->valuestring);
net_url_set(item->payload->url);
net_print_urls();
}
cJSON *type = cJSON_GetObjectItem(payload, "type");
if (cJSON_IsString(type)) {
item->payload->type = strdup(type->valuestring);
}
cJSON *answer = cJSON_GetObjectItem(payload, "answer");
if (cJSON_IsString(answer)) {
item->payload->answer = strdup(answer->valuestring);
}
cJSON *id = cJSON_GetObjectItem(payload, "id");
if (cJSON_IsString(id)) {
item->payload->id = strdup(id->valuestring);
}
cJSON *index = cJSON_GetObjectItem(payload, "index");
if (cJSON_IsNumber(index)) {
item->payload->index = index->valueint;
}
cJSON *payload_is_end = cJSON_GetObjectItem(payload, "is_end");
if (cJSON_IsNumber(payload_is_end)) {
item->payload->payload_is_end = payload_is_end->valueint;
}
cJSON *part = cJSON_GetObjectItem(payload, "part");
if (cJSON_IsString(part)) {
item->payload->part = strdup(part->valuestring);
}
cJSON *reasoning_part = cJSON_GetObjectItem(payload, "reasoning_part");
if (cJSON_IsString(reasoning_part)) {
item->payload->reasoning_part = strdup(reasoning_part->valuestring);
}
cJSON *tts = cJSON_GetObjectItem(payload, "tts");
if (cJSON_IsString(tts)) {
item->payload->tts = strdup(tts->valuestring);
}
cJSON *timeoutInMilliseconds = cJSON_GetObjectItem(payload, "timeoutInMilliseconds");
if (cJSON_IsNumber(timeoutInMilliseconds)) {
item->payload->timeoutInMilliseconds = timeoutInMilliseconds->valueint;
}
}
}
cJSON *property = cJSON_GetObjectItem(array_item, "property");
if (property && cJSON_IsObject(property)) {
cJSON *serviceCategory = cJSON_GetObjectItem(property, "serviceCategory");
if (cJSON_IsString(serviceCategory)) {
item->serviceCategory = strdup(serviceCategory->valuestring);
}
}
}
}
}
cJSON *lj_thread_id = cJSON_GetObjectItem(data_obj, "lj_thread_id");
if (cJSON_IsString(lj_thread_id)) {
response->data->lj_thread_id = strdup(lj_thread_id->valuestring);
}
cJSON *ab_conversation_id = cJSON_GetObjectItem(data_obj, "ab_conversation_id");
if (cJSON_IsString(ab_conversation_id)) {
response->data->ab_conversation_id = strdup(ab_conversation_id->valuestring);
}
cJSON *xiaoice_session_id = cJSON_GetObjectItem(data_obj, "xiaoice_session_id");
if (cJSON_IsString(xiaoice_session_id)) {
response->data->xiaoice_session_id = strdup(xiaoice_session_id->valuestring);
}
cJSON *dialog_request_id = cJSON_GetObjectItem(data_obj, "dialog_request_id");
if (cJSON_IsString(dialog_request_id)) {
response->data->dialog_request_id = strdup(dialog_request_id->valuestring);
}
}
cJSON_Delete(root);
return response;
}
void duer_free_inside_rc_response(InsideRCResponse *response)
{
if (!response) {
return;
}
net_interface_free(response->status);
net_interface_free(response->type);
net_interface_free(response->sn);
if (response->data) {
net_interface_free(response->data->msg);
net_interface_free(response->data->logid);
net_interface_free(response->data->qid);
net_interface_free(response->data->lj_thread_id);
net_interface_free(response->data->ab_conversation_id);
net_interface_free(response->data->xiaoice_session_id);
net_interface_free(response->data->dialog_request_id);
if (response->data->assistant_answer) {
net_interface_free(response->data->assistant_answer->content);
net_interface_free(response->data->assistant_answer->nlu);
if (response->data->assistant_answer->metadata) {
if (response->data->assistant_answer->metadata->multi_round_info) {
net_interface_free(response->data->assistant_answer->metadata->multi_round_info->target_bot_id);
net_interface_free(response->data->assistant_answer->metadata->multi_round_info->intent);
net_interface_free(response->data->assistant_answer->metadata->multi_round_info);
}
net_interface_free(response->data->assistant_answer->metadata);
}
net_interface_free(response->data->assistant_answer);
}
if (response->data->data_items) {
for (int i = 0; i < response->data->data_items_count; i++) {
DataItem *item = response->data->data_items[i];
if (!item) {
continue;
}
if (item->header) {
net_interface_free(item->header->namespace);
net_interface_free(item->header->name);
net_interface_free(item->header->messageId);
net_interface_free(item->header->dialogRequestId);
net_interface_free(item->header);
}
if (item->payload) {
net_interface_free(item->payload->text);
net_interface_free(item->payload->content);
net_interface_free(item->payload->format);
net_interface_free(item->payload->token);
net_interface_free(item->payload->url);
net_interface_free(item->payload->type);
net_interface_free(item->payload->answer);
net_interface_free(item->payload->id);
net_interface_free(item->payload->part);
net_interface_free(item->payload->reasoning_part);
net_interface_free(item->payload->tts);
net_interface_free(item->payload);
}
net_interface_free(item->serviceCategory);
net_interface_free(item);
}
net_interface_free(response->data->data_items);
}
net_interface_free(response->data);
}
net_interface_free(response);
}
#endif
@@ -0,0 +1,100 @@
#ifndef _DUER_JSON_PARSE_H_
#define _DUER_JSON_PARSE_H_
#include "duer_common.h"
#include <string.h>
typedef struct {
char *refresh_token;
int expires_in;
char *session_key;
char *access_token;
char *scope;
char *session_secret;
} TokenData;
extern TokenData *duer_parse_token_json(const char *json_str);
extern void duer_free_token_data(TokenData *data);
typedef struct {
char *namespace;
char *name;
char *messageId;
char *dialogRequestId;
} Header;
typedef struct {
char *text;
char *content;
char *format;
char *token;
char *url;
int rolling;
char *type;
char *answer;
char *id;
int index;
int payload_is_end;
char *part;
char *reasoning_part;
char *tts;
int timeoutInMilliseconds;
} Payload;
typedef struct {
Header *header;
Payload *payload;
char *serviceCategory;
} DataItem;
typedef struct {
int is_in_multi;
char *target_bot_id;
char *intent;
} MultiRoundInfo;
typedef struct {
MultiRoundInfo *multi_round_info;
} Metadata;
typedef struct {
char *content;
char *nlu;
Metadata *metadata;
int is_end;
} AssistantAnswer;
typedef struct {
int code;
char *msg;
char *logid;
char *qid;
int is_end;
AssistantAnswer *assistant_answer;
int need_clear_history;
DataItem **data_items;
int data_items_count;
char *lj_thread_id;
char *ab_conversation_id;
char *xiaoice_session_id;
char *dialog_request_id;
} Data;
typedef struct {
char *status;
char *type;
Data *data;
char *sn;
int end;
} InsideRCResponse;
extern InsideRCResponse *duer_parse_inside_rc_json(const char *json_string);
extern void duer_free_inside_rc_response(InsideRCResponse *response);
#endif
@@ -0,0 +1,119 @@
#ifdef SUPPORT_MS_EXTENSIONS
#pragma bss_seg(".intelligent_duer_parse.data.bss")
#pragma data_seg(".intelligent_duer_parse.data")
#pragma const_seg(".intelligent_duer_parse.text.const")
#pragma code_seg(".intelligent_duer_parse.text")
#endif
#include "duer_common.h"
#include "cJSON.h"
#if INTELLIGENT_DUER
char *duer_start_frame(const char *appid, const char *appkey, const char *client_id, const char *pid,
const char *cuid, const char *format, int support_dcs, int sample,
const char *user_id, const char *access_token, int support_tts,
int support_text2dcs, const char *dialog_request_id, int access_rc,
const char *rc_version)
{
cJSON *root = cJSON_CreateObject();
if (root == NULL) {
return NULL;
}
cJSON_AddItemToObject(root, "type", cJSON_CreateString("start"));
cJSON *data = cJSON_CreateObject();
if (data == NULL) {
cJSON_Delete(root);
return NULL;
}
cJSON_AddItemToObject(root, "data", data);
cJSON_AddItemToObject(data, "appid", cJSON_CreateString(appid));
cJSON_AddItemToObject(data, "appkey", cJSON_CreateString(appkey));
cJSON_AddItemToObject(data, "client_id", cJSON_CreateString(client_id));
cJSON_AddItemToObject(data, "pid", cJSON_CreateString(pid));
cJSON_AddItemToObject(data, "cuid", cJSON_CreateString(cuid));
cJSON_AddItemToObject(data, "format", cJSON_CreateString(format));
char support_dcs_str[32];
snprintf(support_dcs_str, sizeof(support_dcs_str), "%d", support_dcs);
cJSON_AddRawToObject(data, "support_dcs", support_dcs_str);
char sample_str[32];
snprintf(sample_str, sizeof(sample_str), "%d", sample);
cJSON_AddRawToObject(data, "sample", sample_str);
cJSON_AddItemToObject(data, "user_id", cJSON_CreateString(user_id));
cJSON_AddItemToObject(data, "access_token", cJSON_CreateString(access_token));
cJSON_AddItemToObject(data, "support_tts", cJSON_CreateBool(support_tts));
cJSON_AddItemToObject(data, "support_text2dcs", cJSON_CreateBool(support_text2dcs));
cJSON_AddItemToObject(data, "dialog_request_id", cJSON_CreateString(dialog_request_id));
cJSON_AddItemToObject(data, "access_rc", cJSON_CreateBool(access_rc));
cJSON_AddItemToObject(data, "rc_version", cJSON_CreateString(rc_version));
char *json_string = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
return json_string;
}
char *build_finish_frame()
{
cJSON *root = cJSON_CreateObject();
if (!root) {
return NULL;
}
cJSON_AddStringToObject(root, "type", "finish");
char *json_str = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
return json_str;
}
char *duer_text_info_frame(const char *CUID,
const char *current_dialog_request_id,
const char *text_query)
{
cJSON *root = cJSON_CreateObject();
if (!root) {
return NULL;
}
cJSON *event = cJSON_CreateObject();
cJSON *header = cJSON_CreateObject();
cJSON *payload = cJSON_CreateObject();
cJSON *initiator = cJSON_CreateObject();
if (!event || !header || !payload || !initiator) {
cJSON_Delete(root);
if (event) {
cJSON_Delete(event);
}
if (header) {
cJSON_Delete(header);
}
if (payload) {
cJSON_Delete(payload);
}
if (initiator) {
cJSON_Delete(initiator);
}
return NULL;
}
cJSON_AddStringToObject(root, "status", "ok");
cJSON_AddStringToObject(root, "type", "dcs_decide");
cJSON_AddStringToObject(root, "sn", CUID ? CUID : "");
cJSON_AddNumberToObject(root, "end", 1);
cJSON_AddItemToObject(root, "event", event);
cJSON_AddItemToObject(event, "header", header);
cJSON_AddItemToObject(event, "payload", payload);
cJSON_AddStringToObject(header, "namespace", "ai.dueros.device_interface.screen");
cJSON_AddStringToObject(header, "name", "LinkClicked");
cJSON_AddStringToObject(header, "dialogRequestId", current_dialog_request_id ? current_dialog_request_id : "");
char url[1024];
if (text_query) {
snprintf(url, sizeof(url), "dueros://server.dueros.ai/query?q=%s", text_query);
} else {
snprintf(url, sizeof(url), "dueros://server.dueros.ai/query");
}
cJSON_AddStringToObject(payload, "url", url);
cJSON_AddItemToObject(payload, "initiator", initiator);
cJSON_AddStringToObject(initiator, "type", "USER_CLICK");
char *json_str = cJSON_Print(root);
cJSON_Delete(root);
return json_str;
}
#endif
@@ -0,0 +1,20 @@
#ifndef _DUER_JSON_REQUEST_H_
#define _DUER_JSON_REQUEST_H_
#include "duer_common.h"
extern char *duer_start_frame(const char *appid, const char *appkey, const char *client_id, const char *pid,
const char *cuid, const char *format, int support_dcs, int sample,
const char *user_id, const char *access_token, int support_tts,
int support_text2dcs, const char *dialog_request_id, int access_rc,
const char *rc_version);
extern char *build_finish_frame() ;
extern char *duer_text_info_frame(const char *CUID,
const char *current_dialog_request_id,
const char *text_query);
#endif
@@ -0,0 +1,140 @@
#ifdef SUPPORT_MS_EXTENSIONS
#pragma bss_seg(".intelligent_duer_task.data.bss")
#pragma data_seg(".intelligent_duer_task.data")
#pragma const_seg(".intelligent_duer_task.text.const")
#pragma code_seg(".intelligent_duer_task.text")
#endif
#include "duer_task.h"
#include "duer_http_req.h"
#include "os_api.h"
#if INTELLIGENT_DUER
#define LOG_TAG_CONST NET_DUER
#define LOG_TAG "[DUER_TASK]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#define LOG_CLI_ENABLE
#include "debug.h"
#define INTELLIGENT_DUER_NAME "intelligent_duer"
static DUER_DATA *duer = NULL;
static TokenData *token = NULL;
void ntp_sync_callback(int result, const struct tm *sync_time, const char *msg)
{
if (result == 0) {
char time_str[64];
strftime_2(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", sync_time);
log_info("NTP同步成功: %s | %s\n", time_str, msg);
os_taskq_post(INTELLIGENT_DUER_NAME, 1, DUER_TOKEN_GET); //同步时间成功,下一步鉴权
} else {
log_info("NTP同步失败 (错误码: %d): %s\n", result, msg);
os_taskq_post(INTELLIGENT_DUER_NAME, 1, DUER_ERROR);
}
}
static void duer_data_init()
{
if (duer == NULL) {
duer = net_interface_malloc(sizeof(DUER_DATA));
}
}
static void duer_data_destory()
{
if (duer->access_token) {
net_interface_free(duer->access_token);
}
if (duer != NULL) {
net_interface_free(duer);
}
}
static void duer_ask_access_token()
{
token = duer_get_access_token();
if (token) {
size_t len = strlen(token->access_token);
duer->access_token = net_interface_malloc(len + 1);
if (duer->access_token == NULL) {
os_taskq_post(INTELLIGENT_DUER_NAME, 1, DUER_ERROR);
}
snprintf(duer->access_token, len + 1, "%s", token->access_token);
log_info(">>>>>>>>>>duer->access_token %s \n", duer->access_token);
duer_free_token_data(token);
os_taskq_post(INTELLIGENT_DUER_NAME, 1, DUER_WS_CONNECT);
} else {
os_taskq_post(INTELLIGENT_DUER_NAME, 1, DUER_ERROR);
}
}
static void task_kill_callback()
{
task_kill(INTELLIGENT_DUER_NAME);
}
static void intelligent_duer_task(void *priv)
{
int msg[3] = {0};
int res = 0;
while (1) {
res = os_taskq_pend(NULL, msg, ARRAY_SIZE(msg));
if (res == OS_TASKQ) {
switch (msg[1]) {
case DUER_NTP_SYNC:
ntp_get_time_to_sync_rtc_with_callback();
break;
case DUER_TOKEN_GET:
duer_ask_access_token();
break;
case DUER_WS_CONNECT://ws通信、opus数据交互、url数据接收
duer_websocket_client_thread_create(duer->access_token);
break;
case DUER_NETDOWN://URL索引下载,解码播放
net_url_download();
break;
case DUER_ERROR:
case DUER_CLOSING:
goto err; //error和close都会关闭
break;
}
}
}
err:
duer_data_destory();
msg[0] = (int)task_kill_callback;
msg[1] = 1;
os_taskq_post_type("app_core", Q_CALLBACK, 2, msg);
os_time_dly(-1);
}
void intelligent_duer_task_create()
{
duer_data_init();
os_task_create(intelligent_duer_task,
NULL,
30,
512 * 2,
16,
INTELLIGENT_DUER_NAME);
}
void duer_sync_time_msg()
{
os_taskq_post(INTELLIGENT_DUER_NAME, 1, DUER_NTP_SYNC);
}
void duer_netdownload_msg()
{
os_taskq_post(INTELLIGENT_DUER_NAME, 1, DUER_NETDOWN);
}
void duer_netdownload_close_msg()
{
os_taskq_post(INTELLIGENT_DUER_NAME, 1, DUER_CLOSING);
}
#endif
@@ -0,0 +1,47 @@
#ifndef _DUER_TASK
#define _DUER_TASK
#include "duer_common.h"
#include "time.h"
// 状态枚举
typedef enum {
DUER_NTP_SYNC,
DUER_TOKEN_GET,
DUER_WS_CONNECT,
DUER_NETDOWN,
DUER_ERROR,
DUER_CLOSING
} SystemState;
// 错误码枚举
typedef enum {
ERROR_NONE = 0,
ERROR_NTP_FAILED,
ERROR_HTTP_FAILED,
ERROR_TOKEN_INVALID,
ERROR_WS_CONNECT_FAILED,
ERROR_SERVER_REJECT,
ERROR_RECORD_FAILED,
ERROR_UPLOAD_FAILED,
ERROR_TIMEOUT
} ErrorCode;
typedef struct {
char *access_token;
u8 run;
} DUER_DATA;
extern void ntp_get_time_to_sync_rtc_with_callback();
extern int task_kill(const char *name);
extern void intelligent_duer_task_create();
extern void duer_sync_time_msg();
extern void duer_netdownload_msg();
extern size_t strftime_2(char *ptr, size_t maxsize, const char *format, const struct tm *timeptr);
#endif
@@ -0,0 +1,63 @@
#ifdef SUPPORT_MS_EXTENSIONS
#pragma bss_seg(".intelligent_duer_voice.data.bss")
#pragma data_seg(".intelligent_duer_voice.data")
#pragma const_seg(".intelligent_duer_voice.text.const")
#pragma code_seg(".intelligent_duer_voice.text")
#endif
#include "duer_ai_rx_get_frame.h"
#include "ai_rx_player.h"
#include "cpu/includes.h"
#if INTELLIGENT_DUER
#define FIX_LEN 1024
enum stream_node_state ai_rx_get_frame_duer(struct ai_rx_file_handle *hdl, struct stream_frame **pframe)
{
int available = net_cbuf_data_len();
int read_size = MIN(available, FIX_LEN);
if (read_size <= 0) {
*pframe = NULL;
return NODE_STA_RUN;
}
struct stream_frame *frame = jlstream_get_frame(hdl->node->oport, read_size);
if (!frame) {
*pframe = NULL;
return NODE_STA_RUN;
}
int rlen = net_cbuf_read_data(frame->data, read_size);
frame->len = MAX(rlen, 0);
if (rlen != read_size) {
printf("cbuf_read mismatch: req=%d, act=%d\n", read_size, rlen);
}
*pframe = frame;
return NODE_STA_RUN;
}
static void duer_audio_begin(void)
{
struct ai_rx_player_param param = {0};
param.coding_type = AUDIO_CODING_STREAM_MP3;
param.sample_rate = 16000;
param.channel_mode = AUDIO_CH_MIX;
param.type = AI_SERVICE_VOICE;
ai_rx_player_open(NULL, 0, &param);
extern void audio_app_volume_set(u8 state, s16 volume, u8 fade);
audio_app_volume_set(1, 100, 1);
}
static void duer_audio_start(void *arg)
{
duer_audio_begin();
}
void duer_audio_play()
{
duer_audio_start(NULL);
}
void duer_audio_stop(void *arg)
{
ai_rx_player_close(0);
}
#endif
@@ -0,0 +1,18 @@
#ifndef _DUER_AI_GET_FRAME_H_
#define _DUER_AI_GET_FRAME_H_
#include "duer_common.h"
#include "jlstream.h"
#include "ai_rx_player.h"
#if INTELLIGENT_DUER
extern enum stream_node_state ai_rx_get_frame_duer(struct ai_rx_file_handle *hdl, struct stream_frame **pframe);
extern void my_duer_audio_play();
extern void my_duer_audio_stop(void *arg);
#endif
#endif
@@ -0,0 +1,125 @@
#ifdef SUPPORT_MS_EXTENSIONS
#pragma bss_seg(".net_interface.data.bss")
#pragma data_seg(".net_interface.data")
#pragma const_seg(".net_interface.text.const")
#pragma code_seg(".net_interface.text")
#endif
#include "net_http.h"
#if NET_INTERFACE_EN
#define LOG_TAG_CONST NET_INTERFACE
#define LOG_TAG "[NET_INTERFACE_HTTP]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#define LOG_CLI_ENABLE
#include "debug.h"
int net_http_get_request(char *url, char **response)
{
int error = 0;
http_body_obj http_body_buf;
log_info("wt get url: %s\n", url);
httpcli_ctx *ctx = (httpcli_ctx *)net_interface_calloc(1, sizeof(httpcli_ctx));
if (!ctx) {
log_error("calloc failed\n");
return -1;
}
memset(&http_body_buf, 0x0, sizeof(http_body_obj));
http_body_buf.recv_len = 0;
http_body_buf.buf_len = 2 * 1024;
http_body_buf.buf_count = 1;
http_body_buf.p = (char *)net_interface_malloc(http_body_buf.buf_len * http_body_buf.buf_count);
if (!http_body_buf.p) {
net_interface_free(ctx);
ctx = NULL;
return -1;
}
ctx->url = url;
ctx->timeout_millsec = 5000;
ctx->priv = &http_body_buf;
ctx->connection = "close";
ctx->data_format = "application/json";
error = httpcli_get(ctx);
if (error != HERROR_OK) {
log_error("get failed\n");
} else {
if (http_body_buf.recv_len > 0) {
log_info("\nreceive %d bytes from(%s)\n", http_body_buf.recv_len, url);
log_info("%s\n", http_body_buf.p);
if (response != NULL) {
*response = net_interface_malloc(http_body_buf.recv_len + 1);
if (*response == NULL) {
log_error("Memory allocation failed for response\n");
error = -1;
} else {
memcpy(*response, http_body_buf.p, http_body_buf.recv_len);
(*response)[http_body_buf.recv_len] = '\0';
log_info("Response copied to external pointer, length: %d\n", http_body_buf.recv_len);
}
}
}
}
httpcli_close(ctx);
if (http_body_buf.p) {
net_interface_free(http_body_buf.p);
}
if (ctx) {
net_interface_free(ctx);
}
return error;
}
int net_http_post_request(char *url, char **response)
{
int ret = 0;
http_body_obj http_body_buf;
httpcli_ctx *ctx = (httpcli_ctx *)net_interface_calloc(1, sizeof(httpcli_ctx));
if (!ctx) {
log_error("calloc failed\n");
return -1;
}
memset(&http_body_buf, 0x0, sizeof(http_body_obj));
http_body_buf.recv_len = 0;
http_body_buf.buf_len = 2 * 1024;
http_body_buf.buf_count = 1;
http_body_buf.p = (char *)net_interface_malloc(http_body_buf.buf_len * http_body_buf.buf_count);
if (!http_body_buf.p) {
net_interface_free(ctx);
return -1;
}
ctx->url = url;
ctx->timeout_millsec = 5000;
ctx->priv = &http_body_buf;
ctx->connection = "close";
ctx->data_format = "application/json";
ret = httpcli_post(ctx);
if (ret != HERROR_OK) {
log_error("HTTP POST request failed\n");
} else {
if (http_body_buf.recv_len > 0) {
log_info("\nReceived %d Bytes from (%s)\n", http_body_buf.recv_len, url);
if (response != NULL) {
*response = net_interface_malloc(http_body_buf.recv_len + 1);
if (*response == NULL) {
log_error("Memory allocation failed for response\n");
ret = -1;
} else {
memcpy(*response, http_body_buf.p, http_body_buf.recv_len);
(*response)[http_body_buf.recv_len] = '\0';
log_info("Response copied to external pointer, length: %d\n", http_body_buf.recv_len);
}
}
}
}
httpcli_close(ctx);
if (http_body_buf.p) {
net_interface_free(http_body_buf.p);
}
if (ctx) {
net_interface_free(ctx);
}
return ret;
}
#endif //NET_INTERFACE_EN
@@ -0,0 +1,39 @@
#ifndef _NET_HTTP_H_
#define _NET_HTTP_H_
#include "http/http_cli.h"
#include "system/includes.h"
#include "net_includes.h"
/**
* @brief 执行HTTP GET请求
*
* @param[in] url 请求的URL地址
* @param[out] response 响应数据指针的地址,需要外部释放内存
*
* @return int 执行结果
* @retval 0 成功
* @retval -1 失败
*
* @note 调用者需要负责释放response指向的内存
* @warning URL参数不能为NULL
*/
extern int net_http_get_request(char *url, char **response);
/**
* @brief 执行HTTP POST请求
*
* @param[in] url 请求的URL地址
* @param[out] response 响应数据指针的地址,需要外部释放内存
*
* @return int 执行结果
* @retval 0 成功
* @retval -1 失败
*
* @note 调用者需要负责释放response指向的内存
* @warning URL参数不能为NULL
*/
extern int net_http_post_request(char *url, char **response);
#endif
@@ -0,0 +1,16 @@
#ifndef _NET_INCLUDES_H_
#define _NET_INCLUDES_H_
#include "system/includes.h"
#include "board_config.h"
#include "net_http.h"
#include "net_mem.h"
#include "net_time.h"
#include "net_url_list.h"
#include "net_record.h"
#include "net_list_download.h"
#define NET_INTERFACE_EN TCFG_NETAPPLICATION_ENABLE
#endif
@@ -0,0 +1,219 @@
#ifdef SUPPORT_MS_EXTENSIONS
#pragma bss_seg(".net_interface.data.bss")
#pragma data_seg(".net_interface.data")
#pragma const_seg(".net_interface.text.const")
#pragma code_seg(".net_interface.text")
#endif
#include "net_list_download.h"
#if NET_INTERFACE_EN
#define LOG_TAG_CONST NET_INTERFACE
#define LOG_TAG "[NET_INTERFACE_LIST_DOWNLOAD]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#define LOG_CLI_ENABLE
#include "debug.h"
static net_buf_t net;
/*=========================cbuf========================*/
#define NET_RX_BUF_SIZE 1024*10 //网络缓存cbuf大小,可适当调整
static bool net_cbuf_init(void)
{
if (!net.buf) {
net.buf = net_interface_malloc(NET_RX_BUF_SIZE);
if (!net.buf) {
return false;
}
}
cbuf_init(&net.cbuf, net.buf, NET_RX_BUF_SIZE);
return true;
}
static void net_cbuf_write(void *buf, int len)
{
int requested_len = len;
static u32 w_len;
int wlen = cbuf_write(&net.cbuf, buf, requested_len);
w_len += wlen;
if (wlen != requested_len) {
}
}
static void net_cbuf_exit(void)
{
cbuf_clear(&(net.cbuf));
net_interface_free(net.buf);
net.buf = NULL;
}
int net_cbuf_data_len()
{
return cbuf_get_data_len(&(net.cbuf));
}
int net_cbuf_read_data(void *buf, u32 len)
{
return cbuf_read(&(net.cbuf), buf, len);
}
/*=======================================================================*/
typedef struct {
void *handle; // 传入指针以获取句柄
int ret; // 返回值
int download_status; // 网络下载状态[net_download.h]
int http_err_status; // http链路状态
struct net_download_parm parm; // 下载参数配置
int file_len; // 获取下载文件大小
int bytes_read; // 记每次下载的数据量
int total_read; // 记录当前下载数据总量
char buf[1024]; // 缓冲数据
} my_net_download_variable; // 在download_demo里面所用
static int g_current_url_index = 0; //记录当前下载的URL索引
static int g_total_urls = 0; //记录总共需要下载的URL数量
static void download_demo(const char *url, const char *save_path, int url_index)
{
if (!url || !save_path) {
log_error("错误: 参数为空\n");
return;
}
log_info("开始下载第%d个URL: %s\n", url_index + 1, url);
my_net_download_variable mynet;
mynet.parm.url = url;
mynet.parm.cbuf_size = 10 * 1024; // 10KB 环形缓冲区
mynet.parm.timeout_millsec = 1000; // 10秒连接超时
mynet.parm.save_file = 1; // 保存到文件
mynet.parm.file_dir = save_path; // 保存目录
mynet.parm.dir_len = strlen(save_path);
mynet.parm.seek_threshold = 1024; // 1024KB跳转阈值
mynet.ret = net_download_open(&mynet.handle, &mynet.parm);
if (mynet.ret != 0) {
log_error("net_download_open failed: %d\n", mynet.ret);
return;
}
mynet.file_len = net_download_get_file_len(mynet.handle);
if (mynet.file_len > 0) {
log_error("File length: %d bytes\n", mynet.file_len);
}
while (1) {
if (net_download_exit_flag(mynet.handle)) {
log_error("Download exit flag set\n");
goto close;
}
net_download_get_status(mynet.handle, &mynet.download_status, &mynet.http_err_status);
if (mynet.download_status < 0) {
log_info("Download failed: status=%d, http_err=%d\n",
mynet.download_status, mynet.http_err_status);
goto close;
} else if (mynet.download_status == NET_DOWNLOAD_COMPLETE) {
log_info("Download completed successfully\n");
goto close;
}
if (net_download_check_ready(mynet.handle) >= 0) {
mynet.bytes_read = net_download_read(mynet.handle, mynet.buf, sizeof(mynet.buf));//这里实际会缓存够buf_size
if (mynet.bytes_read > 0) {
mynet.total_read += mynet.bytes_read;
log_info("Download progress: %d bytes\r", mynet.total_read);
net_cbuf_write(mynet.buf, mynet.bytes_read);
} else if (mynet.bytes_read < 0) {
log_info("Read error: %d\n", mynet.bytes_read);
goto close;
}
} else {
os_time_dly(10);
}
}
close:
mynet.ret = net_download_close(mynet.handle);
if (mynet.ret != 0) {
log_error("net_download_close failed: %d\n", mynet.ret);
} else {
log_info("Download closed normally\n");
}
}
static void stop_all_downloads();
static void net_download(void *priv)
{
int msg[3] = {0};
net_url_reset_iterator();
g_total_urls = net_url_get_count();
log_info("总共有 %d 个URL需要下载\n", g_total_urls);
if (g_total_urls == 0) {
log_error("错误: URL列表为空\n");
return;
}
char save_path[128];
for (g_current_url_index = 0; g_current_url_index < g_total_urls; g_current_url_index++) {
char *url = net_get_url_by_index(g_current_url_index);
if (url) {
log_info("开始下载第%d个URL(共%d个): %s\n",
g_current_url_index + 1, g_total_urls, url);
snprintf(save_path, sizeof(save_path), "storage/sd0/C/913_%d.mp3", g_current_url_index);
download_demo(url, save_path, g_current_url_index);
log_info("完成下载第%d个URL\n", g_current_url_index + 1);
} else {
log_error("错误: 获取第%d个URL失败\n", g_current_url_index + 1);
}
os_time_dly(50);
}
log_info("所有URL下载完成!\n");
msg[0] = (int)stop_all_downloads;
msg[1] = 1;
os_taskq_post_type("app_core", Q_CALLBACK, 2, msg);
os_time_dly(-1);
}
void net_url_download(void)
{
net_cbuf_init();
os_task_create(net_download, NULL, 29, 512 * 5, 0, "dl_task");
}
// 获取当前下载进度信息
void get_download_progress(int *current_index, int *total_urls)
{
if (current_index) {
*current_index = g_current_url_index;
}
if (total_urls) {
*total_urls = g_total_urls;
}
log_info("current_download_url_index: %d total_urls %d ", current_index, total_urls);
}
// 停止所有下载
static void stop_all_downloads()
{
net_cbuf_exit();
task_kill("dl_task");
g_current_url_index = 0;
g_total_urls = 0;
log_info("所有下载已停止\n");
}
#endif//NET_INTERFACE_EN
@@ -0,0 +1,49 @@
#ifndef _NET_LIST_DOWNLOAD_H_
#define _NET_LIST_DOWNLOAD_H_
#include "system/includes.h"
#include "circular_buf.h"
#include "net_download.h"
#include "http/http_cli.h"
#include <stdlib.h>
#include "net_includes.h"
#include "os/os_api.h"
typedef struct {
char *buf;
cbuffer_t cbuf;
} net_buf_t;
/**
* @brief 获取网络cbuf缓存数据长度
*
*/
extern int net_cbuf_data_len();
/**
* @brief 获取网络cbuf缓存数据
*
* @param[in] buf 外部传入buf地址
* @param[in] len 外部传入buf长度
* @param[out] response 响应数据指针的地址,需要外部释放内存
*
* @return int 执行结果
* @retval 0 成功
* @retval -1 失败
*
* @note 调用者需要负责释放buf指向的内存
*/
extern int net_cbuf_read_data(void *buf, u32 len);
/**
* @brief 执行url链式下载,需配合net_url_list执行,线程内部管理生存周期
*/
extern void net_url_download(void);
/**
* @brief url链式下载进度
* @param[in] current_index 外部传入获取当前url索引的变量地址
* @param[in] total_urls 外部传入获取总共url数量的变量地址
* @param[out] current_index 当前下载的url索引
* @param[out] total_urls 总共的url数量
*/
extern void get_download_progress(int *current_index, int *total_urls);
#endif
@@ -0,0 +1,62 @@
#ifdef SUPPORT_MS_EXTENSIONS
#pragma bss_seg(".net_interface.data.bss")
#pragma data_seg(".net_interface.data")
#pragma const_seg(".net_interface.text.const")
#pragma code_seg(".net_interface.text")
#endif
#include "net_mem.h"
#if NET_INTERFACE_EN
#define NET_INTERFACE_MEM_USE_PSRAM 1
#if NET_INTERFACE_MEM_USE_PSRAM
#define NET_MALLOC(size) malloc_psram(size)
#define NET_REALLOC(ptr,size) realloc_psram(ptr,size)
#define NET_FREE(ptr) free_psram(ptr)
#else
#define NET_MALLOC(size) malloc(size)
#define NET_REALLOC(ptr,size) realloc(ptr,size)
#define NET_FREE(ptr) free(ptr)
#endif
void *net_interface_malloc(size_t size)
{
return NET_MALLOC(size);
}
void net_interface_free(void *pv)
{
if (pv != NULL) {
NET_FREE(pv);
}
}
void *net_interface_calloc(unsigned long count, unsigned long size)
{
size_t total = count * size;
void *p = NET_MALLOC(total);
if (p) {
memset(p, 0, total);
}
return p;
}
void *net_interface_realloc(void *ptr, size_t size)
{
return NET_REALLOC(ptr, size);
}
_WEAK_
void *calloc(unsigned long count, unsigned long size)
{
void *p;
p = malloc(count * size);
if (p) {
memset(p, 0, count * size);
}
return p;
}
#endif//NET_INTERFACE_EN
@@ -0,0 +1,12 @@
#ifndef _NET_MEM_H
#define _NET_MEM_H
#include "system/includes.h"
#include "net_includes.h"
extern void *net_interface_malloc(size_t size);
extern void net_interface_free(void *pv);
extern void *net_interface_calloc(unsigned long count, unsigned long size);
extern void *net_interface_realloc(void *ptr, size_t size);
#endif // _NET_MEM_H
@@ -0,0 +1,146 @@
#ifdef SUPPORT_MS_EXTENSIONS
#pragma bss_seg(".net_interface.data.bss")
#pragma data_seg(".net_interface.data")
#pragma const_seg(".net_interface.text.const")
#pragma code_seg(".net_interface.text")
#endif
#include "net_record.h"
#if NET_INTERFACE_EN
#define LOG_TAG_CONST NET_INTERFACE
#define LOG_TAG "[NET_INTERFACE_RECORD]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#define LOG_CLI_ENABLE
#include "debug.h"
#define NET_AUDIO_SAVE_TEST 1
#if NET_AUDIO_SAVE_TEST
static FILE *rec_file = NULL;
#define __file rec_file
#define FILE_SAVE_PATH "storage/sd0/C/record.bin"
#endif
static net_rec_t rc;
#define __buf rc.buf
#define __cbuf rc.cbuf
static void net_rec_cbuf_init()
{
if (!__buf) {
__buf = net_interface_malloc(NET_REC_CBUF_SIZE);
}
cbuf_init(&__cbuf, __buf, NET_REC_CBUF_SIZE);
}
void net_rec_cbuf_exit(void)
{
cbuf_clear(&__cbuf);
net_interface_free(__buf);
__buf = NULL;
}
static int my_fwrite(FILE *file, void *buf, u32 size)
{
int ret = fwrite(buf, size, 1, file);
return ret;
}
static u16 net_rec_write_data(u8 *voice_buf, u16 voice_len)
{
#if NET_AUDIO_SAVE_TEST
if (__file) {
int wlen = my_fwrite(__file, voice_buf, voice_len);
if (wlen != voice_len) {
log_error("save file err: %d, %d\n", wlen, voice_len);
}
}
#endif
int wlen = cbuf_write(&__cbuf, voice_buf, voice_len);
if (wlen != voice_len) {
log_error("pcm out err: %d, %d\n", wlen, voice_len);
}
return 0;
}
static int net_rec_stop(StopCompletedCallback callback)
{
if (!ai_mic_is_busy()) {
log_info("ai_mic_is_null \n\n");
return true;
}
ai_mic_rec_close();
#if NET_AUDIO_SAVE_TEST
if (__file) {
fclose(__file);
__file = NULL;
}
#endif
if (callback) {
callback();
}
return true;
}
int net_rec_start(void)
{
printf(">>>zwz info: %s %d %s\n", __FUNCTION__, __LINE__, __FILE__);
net_rec_cbuf_init();
if (ai_mic_is_busy()) {
log_error("my_mic_is_busy \n\n");
return false;
}
#if NET_AUDIO_SAVE_TEST
if (__file) {
fclose(__file);
__file = NULL;
}
__file = fopen(FILE_SAVE_PATH, "w+");
if (!__file) {
log_error("fopen err \n\n");
}
#endif
mic_rec_pram_init(NET_REC_TYPE, 0, net_rec_write_data, 1, 1024);
ai_mic_rec_start();
return true;
}
int net_rec_data_len()
{
return cbuf_get_data_len(&__cbuf);
}
int net_rec_read_data(void *buf, u32 len)
{
return cbuf_read(&__cbuf, buf, len);
}
void net_record_stop_with_clean()
{
net_rec_stop(net_rec_cbuf_exit);
}
void net_record_stop_without_clean()
{
net_rec_stop(NULL);
}
void net_rec_test(void *priv)
{
net_record_stop_with_clean();
}
void net_record_test()
{
printf(">>>zwz info: %s %d %s\n", __FUNCTION__, __LINE__, __FILE__);
net_rec_start();
sys_timeout_add(NULL, net_rec_test, 5000);
}
#endif//NET_INTERFACE_EN
@@ -0,0 +1,69 @@
#ifndef _MY_PLATFORM_RECORD_H_
#define _MY_PLATFORM_RECORD_H_
#include "net_includes.h"
#include "fs.h"
#include "os/os_api.h"
#include "audio_def.h"
#define NET_REC_TYPE AUDIO_CODING_OPUS
extern int ai_mic_is_busy(void);
extern int ai_mic_rec_close(void);
extern int mic_rec_pram_init(/* const char **name, */u32 enc_type, u8 opus_type, u16(*speech_send)(u8 *buf, u16 len), u16 frame_num, u16 cbuf_size);
extern int ai_mic_rec_start(void);
#define NET_REC_CBUF_SIZE 10*1024
typedef struct {
char *buf;
cbuffer_t cbuf;
} net_rec_t;
typedef void (*StopCompletedCallback)(void);
/**
* @brief 执行AI_TX录音
*
* @return int 执行结果
* @retval 1 成功
* @retval 0 失败
*
*/
extern int net_rec_start(void);
/**
* @brief 获取录音缓存数据长度
*
* @return int 缓存长度
*
*/
extern int net_rec_data_len();
/**
* @brief 获取录音缓存数据长度
*
* @retval 非0 长度
* @retval 0 失败
*
*/
extern int net_rec_read_data(void *buf, u32 len);
/**
* @brief 清理缓存空间
*/
extern void net_rec_cbuf_exit(void);
/**
* @brief 停止录音同时清理缓存空间
*/
extern void net_record_stop_with_clean();
/**
* @brief 停止录音同时不清理缓存空间,需外部再去清理缓存空间
*/
extern void net_record_stop_without_clean();
#endif
@@ -0,0 +1,15 @@
#ifdef SUPPORT_MS_EXTENSIONS
#pragma bss_seg(".net_interface.data.bss")
#pragma data_seg(".net_interface.data")
#pragma const_seg(".net_interface.text.const")
#pragma code_seg(".net_interface.text")
#endif
#include "net_time.h"
#if NET_INTERFACE_EN
//获取rtc时间
void net_get_sys_time(struct sys_time *time)
{
rtc_read_time(time);
}
#endif//NET_INTERFACE_EN
@@ -0,0 +1,16 @@
#ifndef _NET_TIME_H_
#define _NET_TIME_H_
#include "system/includes.h"
#include "sys_time.h"
#include <time.h>
#include "net_includes.h"
#include "timestamp.h"
#include "rtc.h"
extern void net_get_sys_time(struct sys_time *time);
#endif
@@ -0,0 +1,211 @@
#ifdef SUPPORT_MS_EXTENSIONS
#pragma bss_seg(".net_interface.data.bss")
#pragma data_seg(".net_interface.data")
#pragma const_seg(".net_interface.text.const")
#pragma code_seg(".net_interface.text")
#endif
/**
* @file net_url_list.c
* @brief URL列表管理接口实现
*
* 该模块提供动态URL列表的存储、迭代和内存管理功能。列表使用动态数组实现,
* 支持自动扩容、安全迭代和资源清理。适用于需要管理多个URL字符串的场景,
* 支持最小单元测试。
*/
#include "net_url_list.h"
#if NET_INTERFACE_EN
#define LOG_TAG_CONST NET_INTERFACE
#define LOG_TAG "[NET_URL_LIST]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#define LOG_CLI_ENABLE
#include "debug.h"
static url_list_t g_url_list; // 全局URL列表实例
void net_url_list_init(void)
{
g_url_list.capacity = 5; // 初始容量设为5
g_url_list.count = 0;
g_url_list.iterator_index = 0;
// 为URL指针数组申请初始空间
g_url_list.urls = (char **)net_interface_malloc(sizeof(char *) * g_url_list.capacity);
if (g_url_list.urls == NULL) {
g_url_list.capacity = 0;
log_error("错误: 初始化内存分配失败\n");
}
}
// 销毁URL列表,释放所有内存
void net_url_list_destroy(void)
{
// 首先释放每个URL字符串本身的内存
for (int i = 0; i < g_url_list.count; i++) {
net_interface_free(g_url_list.urls[i]);
}
// 然后释放存放URL指针的数组
net_interface_free(g_url_list.urls);
// 重置所有状态
g_url_list.urls = NULL;
g_url_list.count = 0;
g_url_list.capacity = 0;
g_url_list.iterator_index = 0;
}
// 设置(添加)一个URL到列表中
void net_url_set(char *url)
{
if (url == NULL) {
log_warn("警告: 尝试添加空URL\n");
return;
}
// 检查容量是否足够,不足则扩容
if (g_url_list.count >= g_url_list.capacity) {
int new_capacity = g_url_list.capacity * 2; // 容量翻倍
char **new_urls = (char **)net_interface_realloc(g_url_list.urls, sizeof(char *) * new_capacity);
if (new_urls == NULL) {
log_error("错误: 内存扩容失败,无法添加URL: %s\n", url);
return;
}
g_url_list.urls = new_urls;
g_url_list.capacity = new_capacity;
}
// 为新的URL字符串分配内存并复制内容
g_url_list.urls[g_url_list.count] = (char *)net_interface_malloc(strlen(url) + 1); // +1 用于字符串结束符'\0'
if (g_url_list.urls[g_url_list.count] == NULL) {
log_error("错误: 无法为URL分配内存: %s\n", url);
return;
}
strcpy(g_url_list.urls[g_url_list.count], url);
g_url_list.count++;
}
// 获取当前URL列表中的URL数量
int net_url_get_count(void)
{
return g_url_list.count;
}
// 根据索引获取URL,索引从0开始
char *net_get_url_by_index(int index)
{
if (index < 0 || index >= g_url_list.count) {
log_error("错误: 索引%d越界(总数:%d)\n", index, g_url_list.count);
return NULL;
}
return g_url_list.urls[index];
}
// 重置迭代器到列表开头
void net_url_reset_iterator(void)
{
g_url_list.iterator_index = 0;
}
// 获取迭代器当前指向的URL,并将迭代器移动到下一个位置
char *net_url_get_next(void)
{
if (g_url_list.iterator_index >= g_url_list.count) {
return NULL; // 已经遍历完所有URL
}
return g_url_list.urls[g_url_list.iterator_index++];
}
// 获取第一个URL并重置迭代器
char *net_url_get_first(void)
{
net_url_reset_iterator();
return net_url_get_next();
}
// 判断当前URL是否是最后一个(相对于迭代器位置)
int net_url_is_last(void)
{
// 如果列表为空或迭代器已在最后一个或之后,返回1
return (g_url_list.count == 0) || (g_url_list.iterator_index >= g_url_list.count - 1);
}
// 打印所有URL
void net_print_urls(void)
{
log_info("URL列表(共%d个):\n", g_url_list.count);
for (int i = 0; i < g_url_list.count; i++) {
log_info("%d: %s\n", i, g_url_list.urls[i]);
}
}
#if 0
void test_url_management()
{
log_info("URL List Manager Test\n");
log_info("=====================\n");
// 1. 初始化URL链表
net_url_list_init();
log_info("1. List initialized\n");
// 2. 添加URL
net_url_set("https://www.example.com");
net_url_set("https://www.github.com");
net_url_set("https://www.openai.com");
net_url_set("https://www.kernel.org");
net_url_set("https://www.python.org");
log_info("2. URLs added\n");
// 3. 打印所有URL
net_print_urls();
// 4. 按索引获取URL
log_info("\n4. Access by index:");
for (int i = 0; i < net_url_get_count(); i++) {
char *url = net_get_url_by_index(i);
log_info("\n [%d] %s", i, url);
}
log_info("\n");
// 5. 迭代器测试
log_info("\n5. Iterator test:");
int count = 0;
for (char *url = net_url_get_first();
url != NULL;
url = net_url_get_next()) {
log_info("\n Iteration %d: %s", ++count, url);
}
// 6. 重置迭代器
net_url_reset_iterator();
// 7. 边缘测试:越界访问
log_info("\n\n7. Boundary tests:");
log_info("\n Index -1: %s",
net_get_url_by_index(-1) ? "Found" : "NULL");
log_info("\n Index %d: %s",
net_url_get_count(),
net_get_url_by_index(net_url_get_count()) ? "Found" : "NULL");
// 8. 销毁链表
net_url_list_destroy();
log_info("\n\n8. List destroyed\n");
// 9. 销毁后访问
log_info("\n9. Post-destruction access:");
log_info("\n Count: %d", net_url_get_count());
net_print_urls();
// 10. 重新初始化并添加新URL
net_url_list_init();
net_url_set("https://www.new-start.com");
log_info("\n10. Reinitialized list:\n");
net_print_urls();
net_url_list_destroy();
return;
}
#endif
#endif//NET_INTERFACE_EN
@@ -0,0 +1,150 @@
#ifndef _NET_URL_LIST_H_
#define _NET_URL_LIST_H_
#include "net_includes.h"
#include "list.h"
#include <stdlib.h>
#include <string.h>
// 定义URL列表的管理结构体
typedef struct {
char **urls; // 指向URL字符串指针数组的指针
int capacity; // 指针数组当前的容量
int count; // 当前存储的URL数量
int iterator_index; // 用于外部迭代的当前位置
} url_list_t;
/**
* @brief 检查当前迭代位置是否指向最后一个URL
*
* 判断迭代器是否已到达最后一个URL(或越界)。
*
* @param None
* @return int 1表示是最后一个或越界,0表示否
*
* @note 列表为空时返回1
* @example if (net_url_is_last()) break;
*/
extern int net_url_is_last(void);
/**
* @brief 初始化URL列表结构
*
* 初始化全局URL列表,设置初始容量为5,并分配初始内存。
* 如果内存分配失败,容量设为0并记录错误。
*
* @param None
* @return None
*
* @note 初始容量选择5是为了平衡内存使用和扩容频率。
* @example net_url_list_init();
*/
extern void net_url_list_init(void);
/**
* @brief 销毁URL列表并释放所有内存
*
* 安全释放所有URL字符串内存和指针数组内存,重置列表状态。
* 防止内存泄漏的关键函数,必须在功能结束前调用。
*
* @param None
* @return None
*
* @note 释放顺序:先释放每个URL字符串,再释放指针数组。
* @example net_url_list_destroy();
*/
extern void net_url_list_destroy(void);
/**
* @brief 添加URL到列表
*
* 将URL字符串复制到列表末尾。如果容量不足,自动扩容至原容量2倍。
* 支持空URL检查,扩容失败或内存分配失败时记录错误。
*
* @param url 要添加的URL字符串(需以空字符结尾)
* @return None
*
* @example net_url_set("xxxxxx");
*/
extern void net_url_set(char *url);
/**
* @brief 打印所有URL到日志
*
* 调试用途:将列表中所有URL按索引打印到日志。
*
* @param None
* @return None
*
* @example net_print_urls();
*/
extern void net_print_urls(void);
/**
* @brief 通过索引获取URL
*
* 根据索引(从0开始)返回对应的URL字符串。索引越界时返回NULL并记录错误。
*
* @param index URL的索引(0 ≤ index < count
* @return char* 成功返回URL字符串指针,失败返回NULL
*
* @note 返回的指针为列表内部数据,不应手动释放。
* @example char *url = net_get_url_by_index(0);
*/
extern char *net_get_url_by_index(int index);
/**
* @brief 获取第一个URL并重置迭代器
*
* 便捷函数:重置迭代器并返回第一个URL。列表为空时返回NULL。
*
* @param None
* @return char* 第一个URL或NULL
*
* @example char *first = net_url_get_first();
*/
extern char *net_url_get_first(void);
/**
* @brief 获取下一个URL(迭代器方式)
*
* 返回迭代器当前指向的URL,并将迭代器移动到下一个位置。
* 遍历完成后返回NULL。
*
* @param None
* @return char* URL字符串或NULL(遍历结束时)
*
* @note 与net_url_reset_iterator配合使用,实现安全迭代。
* @example
* net_url_reset_iterator();
* while ((char *url = net_url_get_next()) != NULL) { ... }
*/
extern char *net_url_get_next(void);
/**
* @brief 重置迭代器到列表开头
*
* 将内部迭代器位置重置为0,用于重新开始遍历。
*
* @param None
* @return None
*
* @see net_url_get_next
* @example net_url_reset_iterator();
*/
extern void net_url_reset_iterator(void);
/**
* @brief 获取当前URL数量
*
* 返回列表中当前存储的URL数量,用于循环或状态检查。
*
* @param None
* @return int URL数量(总为非负整数)
*
* @example int count = net_url_get_count();
*/
extern int net_url_get_count(void);
#endif