478 lines
12 KiB
C
478 lines
12 KiB
C
#include "server/ai_server.h"
|
|
#include "duerapp_config.h"
|
|
#include "duerapp_recorder.h"
|
|
#include "duerapp_event.h"
|
|
#include "lightduer_voice.h"
|
|
#include "system/os/os_api.h"
|
|
#include "server/server_core.h"
|
|
#include "version.h"
|
|
#include "generic/circular_buf.h"
|
|
|
|
#ifdef DUER_SDK_VERSION
|
|
MODULE_VERSION_EXPORT(duer_sdk, DUER_SDK_VERSION);
|
|
#endif
|
|
|
|
extern int duer_app_init(void);
|
|
extern bool duer_app_get_connect_state(void);
|
|
extern void duer_app_uninit(void);
|
|
|
|
|
|
const struct ai_sdk_api duer_sdk_api;
|
|
|
|
static const int events[][2] = {
|
|
{ AI_EVENT_SPEAK_END, DUER_SPEAK_END },
|
|
{ AI_EVENT_MEDIA_END, DUER_MEDIA_END },
|
|
{ AI_EVENT_MEDIA_STOP, DUER_MEDIA_STOP },
|
|
{ AI_EVENT_PLAY_PAUSE, DUER_PLAY_PAUSE },
|
|
{ AI_EVENT_PREVIOUS_SONG, DUER_PREVIOUS_SONG },
|
|
{ AI_EVENT_NEXT_SONG, DUER_NEXT_SONG },
|
|
{ AI_EVENT_VOLUME_CHANGE, DUER_VOLUME_CHANGE },
|
|
{ AI_EVENT_VOLUME_INCR, DUER_VOLUME_INCR },
|
|
{ AI_EVENT_VOLUME_DECR, DUER_VOLUME_DECR },
|
|
{ AI_EVENT_VOLUME_MUTE, DUER_VOLUME_MUTE },
|
|
{ AI_EVENT_RECORD_START, DUER_RECORD_START },
|
|
{ AI_EVENT_RECORD_STOP, DUER_RECORD_STOP },
|
|
{ AI_EVENT_VOICE_MODE, DUER_VOICE_MODE },
|
|
{ AI_EVENT_COLLECT_RES, DUER_COLLECT_RES },
|
|
{ AI_EVENT_QUIT , DUER_QUIT },
|
|
};
|
|
|
|
enum {
|
|
DUER_SPEEK_URL,
|
|
DUER_MEDIA_URL,
|
|
};
|
|
|
|
#if DUER_SAMPLE_RATE == 8000
|
|
static u8 cache_buf[576];
|
|
#else
|
|
static u8 cache_buf[552];
|
|
#endif
|
|
|
|
|
|
#define VAD_CACHE_BUF_LEN (4 * sizeof(cache_buf))
|
|
|
|
static u32 total_size;
|
|
static OS_SEM sem;
|
|
static u8 s_vad_status;
|
|
static u16 curr_offset = 0;
|
|
#ifdef DUER_USE_VAD
|
|
static cbuffer_t vad_cbuf;
|
|
static u8 *s_vad_cache_buf;
|
|
#endif
|
|
static struct server *enc_server = NULL;
|
|
static duer_rec_state_t s_duer_rec_state = RECORDER_STOP;
|
|
|
|
static int event2duer(int event)
|
|
{
|
|
for (int i = 0; i < ARRAY_SIZE(events); i++) {
|
|
if (events[i][0] == event) {
|
|
return events[i][1];
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int duer_sdk_do_event(int event, int arg)
|
|
{
|
|
int err;
|
|
int argc = 2;
|
|
int argv[4];
|
|
int duer_event;
|
|
|
|
duer_event = event2duer(event);
|
|
if (duer_event == -1) {
|
|
return 0;
|
|
}
|
|
|
|
printf("duer_event: %x\n", duer_event);
|
|
|
|
argv[1] = arg;
|
|
argv[0] = duer_event;
|
|
|
|
if (duer_event == DUER_RECORD_START) {
|
|
if (s_duer_rec_state == RECORDER_START) {
|
|
return 0;
|
|
}
|
|
os_sem_set(&sem, 0);
|
|
} else if (duer_event == DUER_RECORD_STOP) {
|
|
argv[1] = 0;
|
|
}
|
|
|
|
do {
|
|
err = os_taskq_post_type("duer_app_task", Q_USER, argc, argv);
|
|
if (err != OS_Q_FULL) {
|
|
break;
|
|
}
|
|
os_time_dly(10);
|
|
} while (1);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int duer_vfs_fwrite(void *file, void *data, u32 len)
|
|
{
|
|
int err;
|
|
u32 ret_len = len;
|
|
u32 rlen = 0;
|
|
u8 *p = (u8 *)data;
|
|
|
|
p[0] = (u8)len - 4;
|
|
#if DUER_SAMPLE_RATE == 8000
|
|
p[1] = 0x53;
|
|
#else
|
|
p[1] = 0x54;
|
|
#endif
|
|
p[2] = 0x50;
|
|
p[3] = 0x58;
|
|
|
|
#ifdef DUER_USE_VAD
|
|
if ((DUER_VOICE_MODE_DEFAULT == duer_voice_get_mode() ||
|
|
DUER_VOICE_MODE_C2E_BOT == duer_voice_get_mode()) && s_vad_status == 0) {
|
|
if (0 == cbuf_is_write_able(&vad_cbuf, len)) {
|
|
rlen = len;
|
|
if (rlen != cbuf_read(&vad_cbuf, cache_buf, rlen)) {
|
|
return 0;
|
|
}
|
|
}
|
|
cbuf_write(&vad_cbuf, data, len);
|
|
return ret_len;
|
|
}
|
|
#endif
|
|
|
|
if (s_duer_rec_state == RECORDER_STOP) {
|
|
return 0;
|
|
}
|
|
/* printf("spx_len: %d\n", len); */
|
|
|
|
total_size += len;
|
|
|
|
#ifdef DUER_USE_VAD
|
|
if (cbuf_get_data_size(&vad_cbuf) > 0) {
|
|
while (cbuf_get_data_size(&vad_cbuf) > sizeof(cache_buf)) {
|
|
cbuf_read(&vad_cbuf, cache_buf, sizeof(cache_buf));
|
|
do {
|
|
if (s_duer_rec_state != RECORDER_START) {
|
|
return 0;
|
|
}
|
|
err = os_taskq_post("duer_app_task", 4, DUER_RECORD_SEND, cache_buf,
|
|
sizeof(cache_buf), &sem);
|
|
if (err != OS_Q_FULL) {
|
|
break;
|
|
}
|
|
os_time_dly(10);
|
|
} while (1);
|
|
os_sem_pend(&sem, 0);
|
|
}
|
|
if (cbuf_get_data_size(&vad_cbuf) > 0) {
|
|
curr_offset += cbuf_read(&vad_cbuf, cache_buf, cbuf_get_data_size(&vad_cbuf));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
do {
|
|
int remain = sizeof(cache_buf) - curr_offset;
|
|
int cplen = remain < len ? remain : len;
|
|
if (cplen == 0) {
|
|
do {
|
|
if (s_duer_rec_state != RECORDER_START) {
|
|
return 0;
|
|
}
|
|
err = os_taskq_post("duer_app_task", 4, DUER_RECORD_SEND, cache_buf,
|
|
sizeof(cache_buf), &sem);
|
|
if (err != OS_Q_FULL) {
|
|
break;
|
|
}
|
|
os_time_dly(10);
|
|
} while (1);
|
|
|
|
os_sem_pend(&sem, 0);
|
|
if (s_duer_rec_state != RECORDER_START) {
|
|
break;
|
|
}
|
|
curr_offset = 0;
|
|
} else {
|
|
memcpy(cache_buf + curr_offset, p, cplen);
|
|
curr_offset += cplen;
|
|
p += cplen;
|
|
len -= cplen;
|
|
}
|
|
|
|
} while (len);
|
|
|
|
return ret_len;
|
|
}
|
|
|
|
static int duer_vfs_fclose(void *file)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const struct audio_vfs_ops duer_vfs_ops = {
|
|
.fwrite = duer_vfs_fwrite,
|
|
.fclose = duer_vfs_fclose,
|
|
};
|
|
|
|
static void enc_server_event_handler(void *priv, int argc, int *argv)
|
|
{
|
|
switch (argv[0]) {
|
|
case AUDIO_SERVER_EVENT_ERR:
|
|
case AUDIO_SERVER_EVENT_END:
|
|
if (s_duer_rec_state == RECORDER_START) {
|
|
if (OS_NO_ERR != os_taskq_post("duer_app_task", 2, DUER_RECORD_STOP, 0)) {
|
|
puts("duer_app_task send msg DUER_RECORD_STOP fail ");
|
|
}
|
|
}
|
|
break;
|
|
case AUDIO_SERVER_EVENT_SPEAK_START:
|
|
s_vad_status = 1;
|
|
puts("speak start ! \n");
|
|
#ifdef DUER_USE_VAD
|
|
if (s_duer_rec_state == RECORDER_START) {
|
|
duer_voice_start(DUER_SAMPLE_RATE);
|
|
}
|
|
#endif
|
|
break;
|
|
case AUDIO_SERVER_EVENT_SPEAK_STOP:
|
|
s_vad_status = 2;
|
|
puts("speak stop ! \n");
|
|
if (s_duer_rec_state == RECORDER_START) {
|
|
if (OS_NO_ERR != os_taskq_post("duer_app_task", 2, DUER_RECORD_STOP, 0)) {
|
|
puts("duer_app_task send msg DUER_RECORD_STOP fail ");
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
__attribute__((weak)) void *get_asr_read_input_cb(void)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
int duer_recorder_start(u16 sample_rate, u8 voice_mode)
|
|
{
|
|
int err;
|
|
union audio_req req = {0};
|
|
OS_SEM mic_sem;
|
|
|
|
puts("duer_recorder_start\n");
|
|
|
|
if (!enc_server) {
|
|
enc_server = server_open("audio_server", "enc");
|
|
if (!enc_server) {
|
|
return DUER_ERR_FAILED;
|
|
}
|
|
server_register_event_handler(enc_server, NULL, enc_server_event_handler);
|
|
}
|
|
|
|
s_vad_status = 0;
|
|
curr_offset = 0;
|
|
total_size = 0;
|
|
|
|
#ifdef DUER_USE_VAD
|
|
s_vad_cache_buf = (u8 *)malloc(VAD_CACHE_BUF_LEN);
|
|
if (s_vad_cache_buf == NULL) {
|
|
return DUER_ERR_FAILED;
|
|
}
|
|
cbuf_init(&vad_cbuf, s_vad_cache_buf, VAD_CACHE_BUF_LEN);
|
|
#endif
|
|
|
|
req.enc.cmd = AUDIO_ENC_OPEN;
|
|
req.enc.channel = 1;
|
|
req.enc.volume = 100;
|
|
req.enc.priority = 0;
|
|
req.enc.output_buf = NULL;
|
|
req.enc.output_buf_len = 2 * 1024;
|
|
req.enc.sample_rate = sample_rate;
|
|
req.enc.format = "speex";
|
|
req.enc.frame_size = 4480;
|
|
req.enc.sample_source = "mic";
|
|
req.enc.frame_head_reserve_len = 4;
|
|
req.enc.vfs_ops = &duer_vfs_ops;
|
|
req.enc.file = NULL;
|
|
if (voice_mode & 0x78) {
|
|
u8 source = (voice_mode & 0x78) >> 3;
|
|
if (source <= 4) {
|
|
req.enc.channel_bit_map = BIT(source - 1);
|
|
} else if (source == 5) {
|
|
req.enc.sample_source = "plnk0";
|
|
} else if (source == 6) {
|
|
req.enc.sample_source = "plnk1";
|
|
} else if (source == 7) {
|
|
req.enc.sample_source = "iis0";
|
|
} else if (source == 8) {
|
|
req.enc.sample_source = "iis1";
|
|
}
|
|
}
|
|
req.enc.use_vad = (voice_mode & VAD_ENABLE) ? 1 : 0;
|
|
if (voice_mode & VAD_ENABLE) {
|
|
/* req.enc.vad_start_threshold = 320; */
|
|
req.enc.msec = 15 * 1000;
|
|
}
|
|
|
|
os_sem_create(&mic_sem, 0);
|
|
ai_server_event_notify(&duer_sdk_api, &mic_sem, AI_SERVER_EVENT_MIC_OPEN);
|
|
os_sem_pend(&mic_sem, 0);
|
|
os_sem_del(&mic_sem, 1);
|
|
|
|
if ((voice_mode & WECHAT_MODE) == 0) {
|
|
int(*read_input)(u8 *, u32) = get_asr_read_input_cb();
|
|
if (read_input) {
|
|
req.enc.sample_source = "virtual";
|
|
req.enc.read_input = read_input;
|
|
req.enc.frame_size = sample_rate == 16000 ? 640 : 320;
|
|
req.enc.channel_bit_map = 0;
|
|
}
|
|
}
|
|
|
|
s_duer_rec_state = RECORDER_START;
|
|
|
|
err = server_request(enc_server, AUDIO_REQ_ENC, &req);
|
|
if (err == 0) {
|
|
return DUER_OK;
|
|
}
|
|
|
|
return DUER_ERR_FAILED;
|
|
}
|
|
|
|
int duer_recorder_stop(void)
|
|
{
|
|
int err;
|
|
union audio_req req;
|
|
|
|
if (!enc_server || s_duer_rec_state == RECORDER_STOP) {
|
|
return DUER_OK;
|
|
}
|
|
s_duer_rec_state = RECORDER_STOP;
|
|
os_sem_post(&sem);
|
|
|
|
/* duer_voice_stop(NULL, 0); */
|
|
|
|
req.enc.cmd = AUDIO_ENC_CLOSE;
|
|
err = server_request(enc_server, AUDIO_REQ_ENC, &req);
|
|
if (err == 0) {
|
|
ai_server_event_notify(&duer_sdk_api, NULL, AI_SERVER_EVENT_MIC_CLOSE);
|
|
}
|
|
|
|
#ifdef DUER_USE_VAD
|
|
if (s_vad_cache_buf) {
|
|
free(s_vad_cache_buf);
|
|
s_vad_cache_buf = NULL;
|
|
}
|
|
#endif
|
|
|
|
server_close(enc_server);
|
|
enc_server = NULL;
|
|
|
|
if (DUER_VOICE_MODE_WCHAT == duer_voice_get_mode() &&
|
|
((total_size < 32 * 54 && DUER_SAMPLE_RATE == 8000) ||
|
|
(total_size < 46 * 52 && DUER_SAMPLE_RATE == 16000))
|
|
) {
|
|
ai_server_event_notify(&duer_sdk_api, NULL, AI_SERVER_EVENT_REC_TOO_SHORT);
|
|
}
|
|
|
|
return DUER_OK;
|
|
}
|
|
|
|
unsigned char duer_get_recorder_vad_status(void)
|
|
{
|
|
return s_vad_status;
|
|
}
|
|
|
|
duer_rec_state_t duer_get_recorder_state(void)
|
|
{
|
|
return s_duer_rec_state;
|
|
}
|
|
|
|
void JL_duer_media_speak_play(const char *url)
|
|
{
|
|
ai_server_event_url(&duer_sdk_api, url, AI_SERVER_EVENT_URL_TTS);
|
|
}
|
|
|
|
void duer_wechat_speak_play(const char *url)
|
|
{
|
|
char *save_url = NULL;
|
|
|
|
if (url && strlen(url) > 0) {
|
|
save_url = (char *)malloc(strlen(url) + 1);
|
|
if (!save_url) {
|
|
return;
|
|
}
|
|
strcpy(save_url, url);
|
|
if (0 != ai_server_event_notify(&duer_sdk_api, save_url, AI_SERVER_EVENT_RECV_CHAT)) {
|
|
free(save_url);
|
|
}
|
|
}
|
|
}
|
|
|
|
void JL_duer_media_audio_play(const char *url)
|
|
{
|
|
ai_server_event_url(&duer_sdk_api, url, AI_SERVER_EVENT_URL_MEDIA);
|
|
}
|
|
|
|
void JL_duer_media_audio_continue(const char *url)
|
|
{
|
|
ai_server_event_url(&duer_sdk_api, url, AI_SERVER_EVENT_CONTINUE);
|
|
}
|
|
|
|
void JL_duer_media_audio_pause(const char *url)
|
|
{
|
|
ai_server_event_url(&duer_sdk_api, url, AI_SERVER_EVENT_PAUSE);
|
|
}
|
|
|
|
void JL_duer_upgrade_notify(int event, void *arg)
|
|
{
|
|
ai_server_event_notify(&duer_sdk_api, arg, event);
|
|
}
|
|
|
|
void JL_duer_volume_change_notify(int volume)
|
|
{
|
|
ai_server_event_notify(&duer_sdk_api, (void *)volume, AI_SERVER_EVENT_VOLUME_CHANGE);
|
|
}
|
|
|
|
static int duer_sdk_open()
|
|
{
|
|
os_sem_create(&sem, 0);
|
|
return duer_app_init();
|
|
}
|
|
|
|
static int duer_sdk_check()
|
|
{
|
|
if (duer_app_get_connect_state()) {
|
|
return AI_STAT_CONNECTED;
|
|
}
|
|
|
|
return AI_STAT_DISCONNECTED;
|
|
}
|
|
|
|
static int duer_sdk_disconnect()
|
|
{
|
|
union audio_req req = {0};
|
|
|
|
duer_app_uninit();
|
|
|
|
if (enc_server) {
|
|
if (s_duer_rec_state != RECORDER_STOP) {
|
|
s_duer_rec_state = RECORDER_STOP;
|
|
req.enc.cmd = AUDIO_ENC_CLOSE;
|
|
server_request(enc_server, AUDIO_REQ_ENC, &req);
|
|
}
|
|
server_close(enc_server);
|
|
enc_server = NULL;
|
|
}
|
|
|
|
os_sem_del(&sem, 1);
|
|
return 0;
|
|
}
|
|
|
|
REGISTER_AI_SDK(duer_sdk_api) = {
|
|
.name = "duer",
|
|
.connect = duer_sdk_open,
|
|
.state_check = duer_sdk_check,
|
|
.do_event = duer_sdk_do_event,
|
|
.disconnect = duer_sdk_disconnect,
|
|
};
|