1177 lines
32 KiB
C
1177 lines
32 KiB
C
/**
|
|
* Copyright (2017) Baidu Inc. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
/**
|
|
* File: lightduer_voice.c
|
|
* Auth: Su Hao (suhao@baidu.com)
|
|
* Desc: Light duer voice procedure.
|
|
*/
|
|
|
|
#include "fs/fs.h"
|
|
#include "printf.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include "duerapp_config.h"
|
|
|
|
#include "lightduer_log.h"
|
|
#include "lightduer_memory.h"
|
|
#include "lightduer_events.h"
|
|
#include "lightduer_connagent.h"
|
|
#include "lightduer_voice.h"
|
|
#include "lightduer_random.h"
|
|
#if !USE_OWN_SPEEX_LIB
|
|
#include "lightduer_speex.h"
|
|
#endif
|
|
#include "lightduer_lib.h"
|
|
#include "lightduer_session.h"
|
|
|
|
#include "mbedtls/base64.h"
|
|
|
|
#include "lightduer_ds_log_recorder.h"
|
|
#include "lightduer_timestamp.h"
|
|
#include "lightduer_ds_log_e2e.h"
|
|
#include "lightduer_queue_cache.h"
|
|
#include "lightduer_mutex.h"
|
|
#include "lightduer_sleep.h"
|
|
#include "lightduer_dcs.h"
|
|
|
|
#ifdef ENABLE_DUER_STORE_VOICE
|
|
#include "lightduer_store_voice.h"
|
|
#endif // ENABLE_DUER_STORE_VOICE
|
|
|
|
//unused static void *_encoder_buffer;
|
|
|
|
#define O_BUFFER_LEN (800 + 16)
|
|
#define I_BUFFER_LEN ((O_BUFFER_LEN - 1) / 4 * 3)
|
|
#define MAX_CACHED_VOICE_SEGMENTS ((5000) / ((O_BUFFER_LEN) / 4))
|
|
|
|
typedef struct _duer_topic_s {
|
|
duer_u32_t _id;
|
|
duer_u32_t _samplerate;
|
|
duer_u32_t _segment;
|
|
duer_bool _eof;
|
|
duer_qcache_handler cache;
|
|
duer_bool _need_cache;
|
|
} duer_topic_t;
|
|
|
|
typedef struct _duer_voice_statistics {
|
|
duer_u32_t _topic_id;
|
|
duer_u32_t _segment;
|
|
duer_bool _eof;
|
|
duer_u32_t _request;
|
|
duer_u32_t _start;
|
|
duer_u32_t _finish;
|
|
} duer_vstat_t;
|
|
|
|
typedef struct _duer_cache_item_s {
|
|
baidu_json *value;
|
|
duer_context_t context;
|
|
} duer_cache_item_t;
|
|
|
|
static duer_topic_t g_topic;
|
|
static duer_mutex_t s_mutex;
|
|
|
|
static char _data[I_BUFFER_LEN];
|
|
static size_t _size;
|
|
|
|
#ifdef DUER_SECOND_WAKEUP_ENABLE
|
|
static duer_wakeup_bit _wakeup_bits = 0;
|
|
static duer_voice_send_state s_voice_send_state = DUER_VOICE_SEND_STATE_DEFAULT;
|
|
static duer_voice_cache_t *s_wakeup_voice_cache = NULL;
|
|
static int s_wakeup_eof_num = 0;
|
|
#endif//DUER_SECOND_WAKEUP_ENABLE
|
|
|
|
#if !USE_OWN_SPEEX_LIB
|
|
static duer_speex_handler _speex = NULL;
|
|
#endif
|
|
|
|
#ifdef DUER_VOICE_SEND_ASYNC
|
|
static duer_events_handler g_events_handler = NULL;
|
|
#endif
|
|
|
|
static duer_voice_mode s_voice_mode = DUER_VOICE_MODE_DEFAULT;
|
|
static duer_mutex_t s_func_mutex = NULL;
|
|
|
|
static duer_u32_t s_voice_delay_threshold = (duer_u32_t) - 1;
|
|
static duer_voice_delay_func s_voice_delay_callback = NULL;
|
|
|
|
// duer_voice cache:
|
|
#define DUER_VOICE_CACHE_SIZE (20*5) // (20 * 5) * (20 ms) = 2000 ms
|
|
#define DUER_SPEEX_ENCODE_SIZE (46) // speex enocde size
|
|
|
|
static duer_voice_cache_t *s_voice_cache = NULL;
|
|
|
|
static duer_get_dialog_id_func s_dialog_id_cb;
|
|
|
|
#ifndef DUER_COMPRESS_QUALITY
|
|
#define DUER_COMPRESS_QUALITY (5)
|
|
#endif
|
|
static int s_speex_quality = DUER_COMPRESS_QUALITY;
|
|
|
|
static void local_mutex_lock(duer_bool status)
|
|
{
|
|
duer_status_t rs;
|
|
|
|
if (status) {
|
|
rs = duer_mutex_lock(s_mutex);
|
|
} else {
|
|
rs = duer_mutex_unlock(s_mutex);
|
|
}
|
|
|
|
if (rs != DUER_OK) {
|
|
DUER_LOGE("lock failed: rs = %d, status = %d", rs, status);
|
|
}
|
|
}
|
|
|
|
static void duer_cache_item_release(void *param)
|
|
{
|
|
duer_cache_item_t *item = (duer_cache_item_t *)param;
|
|
|
|
if (item) {
|
|
if (item->value) {
|
|
baidu_json_Delete(item->value);
|
|
item->value = NULL;
|
|
}
|
|
|
|
if (item->context._param) {
|
|
DUER_FREE(item->context._param);
|
|
item->context._param = NULL;
|
|
}
|
|
|
|
DUER_FREE(item);
|
|
}
|
|
}
|
|
|
|
static int duer_request_send_start(duer_context_t *context)
|
|
{
|
|
duer_vstat_t *stat = context ? context->_param : NULL;
|
|
|
|
if (stat) {
|
|
local_mutex_lock(DUER_TRUE);
|
|
duer_cache_item_t *item = duer_qcache_top(g_topic.cache);
|
|
local_mutex_lock(DUER_FALSE);
|
|
|
|
if (item == NULL || item->context._param != stat) {
|
|
DUER_LOGE("The qcache is not matched, may be destroyed, stat:%p, item:%p", stat, item);
|
|
} else {
|
|
stat->_start = duer_timestamp();
|
|
}
|
|
}
|
|
|
|
return DUER_OK;
|
|
}
|
|
|
|
static int duer_request_send_finish(duer_context_t *context, duer_ds_e2e_event_t evt)
|
|
{
|
|
duer_vstat_t *stat = context ? context->_param : NULL;
|
|
duer_u32_t delay = 0;
|
|
|
|
if (stat) {
|
|
int context_status = context->_status;
|
|
local_mutex_lock(DUER_TRUE);
|
|
duer_cache_item_t *item = duer_qcache_pop(g_topic.cache);
|
|
local_mutex_lock(DUER_FALSE);
|
|
if (context_status != DUER_CANCEL) {
|
|
duer_ds_e2e_event(evt);
|
|
}
|
|
|
|
if (item == NULL || item->context._param != stat) {
|
|
DUER_LOGE("The qcache is not matched, may be destroyed, item:%p, stat:%p", item, stat);
|
|
} else {
|
|
stat->_finish = duer_timestamp();
|
|
DUER_LOGD("finish send segment: %d\n", stat->_segment);
|
|
delay = stat->_finish - stat->_request;
|
|
if (context_status != DUER_CANCEL) {
|
|
DUER_LOGD("id: %d, segment: %d, eof: %d, request: %u, start: %u, finish: %u, "
|
|
"send delay: %u, sent usage: %u",
|
|
stat->_topic_id, stat->_segment, stat->_eof, stat->_request,
|
|
stat->_start, stat->_finish,
|
|
stat->_start - stat->_request, stat->_finish - stat->_start);
|
|
} else {
|
|
DUER_LOGD("id: %d, segment: %d, eof: %d, request: %u, start: %u, stop: %u",
|
|
stat->_topic_id, stat->_segment, stat->_eof, stat->_request,
|
|
stat->_start, stat->_finish);
|
|
}
|
|
if (context_status != DUER_CANCEL) {
|
|
duer_ds_rec_delay_info_update(stat->_request, stat->_start, stat->_finish);
|
|
if (delay > s_voice_delay_threshold && s_voice_delay_callback) {
|
|
s_voice_delay_callback(stat->_finish - stat->_request);
|
|
}
|
|
}
|
|
|
|
duer_cache_item_release(item);
|
|
|
|
local_mutex_lock(DUER_TRUE);
|
|
item = duer_qcache_top(g_topic.cache);
|
|
|
|
if (item) {
|
|
duer_data_report_async(&(item->context), item->value);
|
|
|
|
if (item->value) {
|
|
baidu_json_Delete(item->value);
|
|
item->value = NULL;
|
|
}
|
|
}
|
|
|
|
local_mutex_lock(DUER_FALSE);
|
|
}
|
|
} else {
|
|
DUER_LOGE("No stat in the request!!!");
|
|
}
|
|
|
|
return DUER_OK;
|
|
}
|
|
|
|
static int duer_request_send_finish_callback(duer_context_t *context)
|
|
{
|
|
return duer_request_send_finish(context, DUER_E2E_SEND);
|
|
}
|
|
|
|
#ifdef DUER_SECOND_WAKEUP_ENABLE
|
|
static int duer_request_send_wakeup_finish_callback(duer_context_t *context)
|
|
{
|
|
return duer_request_send_finish(context, DUER_E2E_WAKEUP_SEND);
|
|
}
|
|
#endif//DUER_SECOND_WAKEUP_ENABLE
|
|
|
|
void duer_add_translate_flag(baidu_json *data)
|
|
{
|
|
if (!data) {
|
|
DUER_LOGE("Invalid param: data cannot be null");
|
|
return;
|
|
}
|
|
|
|
switch (s_voice_mode) {
|
|
case DUER_VOICE_MODE_CHINESE_TO_ENGLISH:
|
|
baidu_json_AddStringToObject(data, "translate", "c2e");
|
|
break;
|
|
|
|
case DUER_VOICE_MODE_ENGLISH_TO_CHINESE:
|
|
baidu_json_AddStringToObject(data, "translate", "e2c");
|
|
break;
|
|
|
|
case DUER_VOICE_MODE_WCHAT:
|
|
baidu_json_AddNumberToObject(data, "wchat", 1);
|
|
break;
|
|
|
|
case DUER_VOICE_MODE_C2E_BOT:
|
|
baidu_json_AddStringToObject(data, "translate", "duer_c2e");
|
|
break;
|
|
|
|
case DUER_VOICE_MODE_INTERACTIVE_CLASS:
|
|
baidu_json_AddStringToObject(data, "interactive", "class");
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void duer_reg_dialog_id_cb(duer_get_dialog_id_func cb)
|
|
{
|
|
s_dialog_id_cb = cb;
|
|
}
|
|
|
|
static int duer_send_content(const void *data, size_t size, int eof, int wakeup_bits)
|
|
{
|
|
#ifdef DUER_SECOND_WAKEUP_ENABLE
|
|
duer_u32_t current_is_wakeup = 0;
|
|
#endif//DUER_SECOND_WAKEUP_ENABLE
|
|
baidu_json *voice = NULL;
|
|
baidu_json *value = NULL;
|
|
duer_vstat_t *stat = NULL;
|
|
duer_u32_t timestamp;
|
|
duer_cache_item_t *item = NULL;
|
|
duer_cache_item_t *top = NULL;
|
|
int voice_segments_num = 0;
|
|
|
|
int rs = DUER_ERR_FAILED;
|
|
size_t olen;
|
|
char buff[O_BUFFER_LEN];
|
|
const char *dialog_id = NULL;
|
|
|
|
DUER_LOGV("duer_send_content ==>");
|
|
local_mutex_lock(DUER_TRUE);
|
|
voice_segments_num = duer_qcache_length(g_topic.cache);
|
|
printf("voice_segments_num ===%d\n", voice_segments_num);
|
|
local_mutex_lock(DUER_FALSE);
|
|
|
|
if (voice_segments_num > MAX_CACHED_VOICE_SEGMENTS) {
|
|
// ignore the voice data
|
|
DUER_LOGW("too many voice segmetns cached, max:%d!!!", MAX_CACHED_VOICE_SEGMENTS);
|
|
return rs;
|
|
}
|
|
|
|
do {
|
|
g_topic._eof = eof || data == NULL || size == 0;
|
|
|
|
if (data != NULL && size > 0) {
|
|
#ifdef ENABLE_DUER_STORE_VOICE
|
|
duer_store_speex_write(data, size);
|
|
#endif // ENABLE_DUER_STORE_VOICE
|
|
rs = mbedtls_base64_encode(
|
|
(unsigned char *)buff,
|
|
O_BUFFER_LEN,
|
|
&olen,
|
|
(const unsigned char *)data, size);
|
|
|
|
if (rs < 0) {
|
|
DUER_LOGE("Encode the speex data failed: rs = %d", rs);
|
|
break;
|
|
}
|
|
}
|
|
|
|
timestamp = duer_timestamp();
|
|
|
|
item = DUER_MALLOC(sizeof(duer_cache_item_t));
|
|
|
|
if (item == NULL) {
|
|
DUER_LOGE("Memory overflow!!!");
|
|
break;
|
|
}
|
|
|
|
DUER_MEMSET(item, 0, sizeof(duer_cache_item_t));
|
|
|
|
voice = baidu_json_CreateObject();
|
|
|
|
if (voice == NULL) {
|
|
DUER_LOGE("Memory overflow!!!");
|
|
break;
|
|
}
|
|
|
|
value = baidu_json_CreateObject();
|
|
|
|
if (value == NULL) {
|
|
DUER_LOGE("Memory overflow!!!");
|
|
break;
|
|
}
|
|
|
|
stat = DUER_MALLOC(sizeof(duer_vstat_t));
|
|
|
|
if (stat) {
|
|
DUER_MEMSET(stat, 0, sizeof(duer_vstat_t));
|
|
stat->_request = timestamp;
|
|
stat->_topic_id = g_topic._id;
|
|
stat->_segment = g_topic._segment;
|
|
stat->_eof = g_topic._eof;
|
|
} else {
|
|
DUER_LOGE("Memory overflow!!!");
|
|
}
|
|
|
|
baidu_json_AddNumberToObjectCS(voice, "id", g_topic._id);
|
|
baidu_json_AddNumberToObjectCS(voice, "segment", g_topic._segment++);
|
|
baidu_json_AddNumberToObjectCS(voice, "rate", g_topic._samplerate);
|
|
baidu_json_AddNumberToObjectCS(voice, "channel", 1);
|
|
#ifdef DUER_SECOND_WAKEUP_ENABLE
|
|
current_is_wakeup = (wakeup_bits & DUER_WAKEUP_BIT) > 0;
|
|
if (current_is_wakeup) {
|
|
baidu_json_AddNumberToObjectCS(voice, "wakeup",
|
|
(wakeup_bits & DUER_WAKEUP_EOF_BIT) ? s_wakeup_eof_num : 0);
|
|
|
|
DUER_LOGD("segment: %d, wakeup: %d, wakeup_eof: %d",
|
|
stat->_segment,
|
|
(wakeup_bits & DUER_WAKEUP_BIT) ? 1 : 0,
|
|
(wakeup_bits & DUER_WAKEUP_EOF_BIT) ? s_wakeup_eof_num : 0);
|
|
} else {
|
|
DUER_LOGD("segment: %d, is not wakeup voice.", stat->_segment);
|
|
}
|
|
#endif//DUER_SECOND_WAKEUP_ENABLE
|
|
baidu_json_AddNumberToObjectCS(voice, "eof", g_topic._eof ? 1 : 0);
|
|
duer_add_translate_flag(voice);
|
|
if (s_dialog_id_cb) {
|
|
dialog_id = s_dialog_id_cb();
|
|
if (dialog_id) {
|
|
baidu_json_AddStringToObject(voice, "dialogRequestId", dialog_id);
|
|
}
|
|
}
|
|
|
|
baidu_json_AddNumberToObjectCS(voice, "ts", timestamp);
|
|
|
|
#ifdef DISABLE_SPX_ON_WCHAT
|
|
|
|
if (duer_voice_get_mode() == DUER_VOICE_MODE_WCHAT) {
|
|
baidu_json_AddStringToObjectCS(voice, "format", "pcm");
|
|
}
|
|
|
|
#endif
|
|
|
|
if (data != NULL && size > 0) {
|
|
baidu_json_AddStringToObjectCS(voice, "voice_data", buff);
|
|
}
|
|
|
|
baidu_json_AddItemToObjectCS(value, "duer_voice", voice);
|
|
voice = NULL;
|
|
|
|
item->context._param = stat;
|
|
item->context._on_report_start = duer_request_send_start;
|
|
#ifdef DUER_SECOND_WAKEUP_ENABLE
|
|
if (current_is_wakeup) {
|
|
item->context._on_report_finish = duer_request_send_wakeup_finish_callback;
|
|
} else {
|
|
item->context._on_report_finish = duer_request_send_finish_callback;
|
|
}
|
|
#else // if second wakeup disabled.
|
|
item->context._on_report_finish = duer_request_send_finish_callback;
|
|
#endif//DUER_SECOND_WAKEUP_ENABLE
|
|
item->value = value;
|
|
value = NULL;
|
|
|
|
local_mutex_lock(DUER_TRUE);
|
|
top = duer_qcache_top(g_topic.cache);
|
|
rs = duer_qcache_push(g_topic.cache, item);
|
|
local_mutex_lock(DUER_FALSE);
|
|
|
|
if (rs < DUER_OK) {
|
|
DUER_LOGE("send segment %d fail for topic_id:%d!", g_topic._segment, g_topic._id);
|
|
--g_topic._segment;
|
|
break;
|
|
}
|
|
|
|
if (top == NULL) {
|
|
rs = duer_data_report_async(&(item->context), item->value);
|
|
if (rs < DUER_OK) {
|
|
DUER_LOGE("send segment %d fail for topic_id:%d", g_topic._segment, g_topic._id);
|
|
local_mutex_lock(DUER_TRUE);
|
|
duer_cache_item_t *tmp_item = duer_qcache_pop(g_topic.cache);
|
|
local_mutex_lock(DUER_FALSE);
|
|
--g_topic._segment;
|
|
if (tmp_item == NULL) {
|
|
DUER_LOGW("qcache has been finalized!");
|
|
return rs;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
item = NULL;
|
|
} while (0);
|
|
|
|
DUER_LOGV("duer_send_content <== rs = %d", rs);
|
|
|
|
if (item != NULL) {
|
|
if (item->value) {
|
|
baidu_json_Delete(item->value);
|
|
}
|
|
|
|
if (item->context._param) {
|
|
DUER_FREE(item->context._param);
|
|
}
|
|
|
|
DUER_FREE(item);
|
|
}
|
|
|
|
#ifdef DUER_SECOND_WAKEUP_ENABLE
|
|
if (current_is_wakeup) {
|
|
duer_ds_e2e_event(DUER_E2E_WAKEUP_SEND_ENQUEUE);
|
|
}
|
|
#endif//DUER_SECOND_WAKEUP_ENABLE
|
|
|
|
return rs;
|
|
}
|
|
|
|
static void duer_voice_clean_cache(duer_voice_cache_t *cache)
|
|
{
|
|
int i = 0;
|
|
|
|
if (cache == NULL) {
|
|
DUER_LOGE("voice cache haven't initialized");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < (cache->size); ++i) {
|
|
if (cache->items[i]) {
|
|
DUER_FREE(cache->items[i]);
|
|
cache->items[i] = NULL;
|
|
}
|
|
}
|
|
|
|
cache->tail = 0;
|
|
cache->head = 0;
|
|
}
|
|
|
|
static int duer_voice_save_cache(duer_voice_cache_t *cache, const char *buff_ps)
|
|
{
|
|
if (cache == NULL) {
|
|
DUER_LOGE("voice cache haven't initialized");
|
|
return -1;
|
|
}
|
|
|
|
// check if the neweset covered the oldest
|
|
if (cache->tail == cache->head && cache->items[cache->head] != NULL) {
|
|
cache->head = (cache->head + 1) % cache->size;
|
|
}
|
|
|
|
// check if memory malloc
|
|
if (NULL == cache->items[cache->tail]) {
|
|
char *tmp = DUER_MALLOC(DUER_SPEEX_ENCODE_SIZE);
|
|
|
|
if (NULL == tmp) {
|
|
DUER_LOGE("[duer_voice_save_cache] malloc fail\n");
|
|
return DUER_ERR_FAILED;
|
|
}
|
|
|
|
cache->items[cache->tail] = tmp;
|
|
}
|
|
|
|
// store voice
|
|
DUER_MEMCPY(cache->items[cache->tail], buff_ps, DUER_SPEEX_ENCODE_SIZE);
|
|
|
|
// new_index ++
|
|
cache->tail = (cache->tail + 1) % cache->size;
|
|
|
|
//DUER_LOGE("(%d,%d)",cache->head,cache->tail);
|
|
return 0;
|
|
}
|
|
|
|
static char *duer_voice_get_cache(duer_voice_cache_t *cache)
|
|
{
|
|
char *old_voice = NULL;
|
|
|
|
if (cache == NULL) {
|
|
DUER_LOGE("voice cache haven't initialized");
|
|
return NULL;
|
|
}
|
|
|
|
old_voice = cache->items[cache->head];
|
|
cache->items[cache->head] = NULL;
|
|
|
|
// check if cache empty
|
|
if (old_voice == NULL) {
|
|
;
|
|
} else {
|
|
// old_index ++
|
|
cache->head = (cache->head + 1) % cache->size;
|
|
}
|
|
|
|
//DUER_LOGE("[%d,%d]",cache->head,cache->tail);
|
|
return old_voice;
|
|
}
|
|
|
|
static duer_bool duer_voice_not_empty(duer_voice_cache_t *cache)
|
|
{
|
|
if (cache == NULL) {
|
|
DUER_LOGE("voice cache haven't initialized");
|
|
return DUER_FALSE;
|
|
}
|
|
|
|
//DUER_LOGE("[duer_voice_not_empty] %d %d %p %p",cache->head,cache->tail,cache->items[cache->head],cache->items[cache->tail]);
|
|
if (NULL == cache->items[cache->head]) {
|
|
//DUER_LOGE(" empty\n");
|
|
return DUER_FALSE;
|
|
} else {
|
|
//DUER_LOGE("not empty\n");
|
|
return DUER_TRUE;
|
|
}
|
|
}
|
|
|
|
static void duer_voice_init_cache(duer_voice_cache_t **p_cache, int size)
|
|
{
|
|
if (*p_cache) {
|
|
if (size == (*p_cache)->size) {
|
|
return;
|
|
} else {
|
|
DUER_FREE((*p_cache));
|
|
(*p_cache) = NULL;
|
|
}
|
|
}
|
|
|
|
size_t mem_size = (sizeof(duer_voice_cache_t) + sizeof(char *) * size);
|
|
(*p_cache) = (duer_voice_cache_t *)DUER_MALLOC(mem_size);
|
|
if ((*p_cache) == NULL) {
|
|
DUER_LOGE("[duer_voice_init_cache] malloc fail\n");
|
|
return;
|
|
}
|
|
|
|
memset((*p_cache), 0, mem_size);
|
|
(*p_cache)->size = size;
|
|
(*p_cache)->tail = 0;
|
|
(*p_cache)->head = 0;
|
|
}
|
|
|
|
static void duer_voice_uninit_cache(duer_voice_cache_t **p_cache)
|
|
{
|
|
DUER_FREE(*p_cache);
|
|
(*p_cache) = NULL;
|
|
}
|
|
|
|
static int duer_voice_cache_size(duer_voice_cache_t *cache)
|
|
{
|
|
if (cache == NULL) {
|
|
DUER_LOGE("voice cache haven't initialized");
|
|
return -1;
|
|
}
|
|
|
|
if (cache->tail > cache->head) {
|
|
return cache->tail - cache->head;
|
|
} else {
|
|
return cache->tail + (cache->size - cache->head);
|
|
}
|
|
}
|
|
|
|
#if !USE_OWN_SPEEX_LIB
|
|
static void duer_speex_encoded_callback_send(const void *data, size_t size,
|
|
int wakeup_bits)
|
|
{
|
|
//DUER_LOGD("_size + size:%d, I_BUFFER_LEN:%d", _size + size, I_BUFFER_LEN);
|
|
if (_size + size > I_BUFFER_LEN) {
|
|
#ifdef DUER_SECOND_WAKEUP_ENABLE
|
|
duer_send_content(_data, _size, 0, _wakeup_bits);
|
|
_wakeup_bits = 0;
|
|
#else// if second wakeup disabled
|
|
duer_send_content(_data, _size, 0, 0);
|
|
#endif//DUER_SECOND_WAKEUP_ENABLE
|
|
_size = 0;
|
|
}
|
|
|
|
DUER_MEMCPY(_data + _size, data, size);
|
|
_size += size;
|
|
#ifdef DUER_SECOND_WAKEUP_ENABLE
|
|
_wakeup_bits = wakeup_bits;
|
|
#endif//DUER_SECOND_WAKEUP_ENABLE
|
|
}
|
|
|
|
static void duer_speex_encoded_callback(const void *data, size_t size)
|
|
{
|
|
#ifdef DUER_SECOND_WAKEUP_ENABLE
|
|
static duer_u32_t old_topic_id = 0;
|
|
//int count = 0;
|
|
|
|
if (s_voice_send_state != DUER_VOICE_SEND_STATE_DEFAULT) {
|
|
if (s_wakeup_voice_cache == NULL) {
|
|
DUER_LOGE("call duer_voice_second_wakeup_init at first!");
|
|
}
|
|
|
|
// Manipulate cached data.
|
|
switch (s_voice_send_state) {
|
|
case DUER_VOICE_SEND_STATE_WAKEUP:
|
|
//count = 0;
|
|
if (old_topic_id != g_topic._id &&
|
|
duer_session_is_matched(g_topic._id) == DUER_TRUE) {
|
|
old_topic_id = g_topic._id;
|
|
//DUER_LOGI("before send_wakeup_count = %d, _size: %d", count, _size);
|
|
while (1) {
|
|
char *voice_data = NULL;
|
|
duer_bool wakeup_eof = DUER_FALSE;
|
|
if (duer_voice_not_empty(s_wakeup_voice_cache)) {
|
|
voice_data = duer_voice_get_cache(s_wakeup_voice_cache);
|
|
wakeup_eof = !duer_voice_not_empty(s_wakeup_voice_cache);
|
|
|
|
if (voice_data) {
|
|
//++count;
|
|
duer_speex_encoded_callback_send(voice_data, DUER_SPEEX_ENCODE_SIZE,
|
|
DUER_WAKEUP_BIT);
|
|
DUER_FREE(voice_data);
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
}
|
|
//DUER_LOGI("end send_wakeup_count = %d, _size: %d", count, _size);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Process new data.
|
|
if (size > 0) {
|
|
switch (s_voice_send_state) {
|
|
case DUER_VOICE_SEND_STATE_WAKEUP:
|
|
// set second wakeup bit.
|
|
duer_speex_encoded_callback_send(data, size, DUER_WAKEUP_BIT);
|
|
break;
|
|
case DUER_VOICE_SEND_STATE_QUERY:
|
|
// clear wakeup bit.
|
|
duer_speex_encoded_callback_send(data, size, 0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Process caching.
|
|
duer_voice_save_cache(s_wakeup_voice_cache, data);
|
|
}
|
|
} else {
|
|
#endif//DUER_SECOND_WAKEUP_ENABLE
|
|
if (g_topic._need_cache) {
|
|
duer_voice_save_cache(s_voice_cache, data);
|
|
} else {
|
|
while (duer_voice_not_empty(s_voice_cache)) {
|
|
char *voice_data = duer_voice_get_cache(s_voice_cache);
|
|
|
|
if (voice_data) {
|
|
duer_speex_encoded_callback_send(voice_data, DUER_SPEEX_ENCODE_SIZE, 0);
|
|
DUER_FREE(voice_data);
|
|
}
|
|
}
|
|
|
|
duer_speex_encoded_callback_send(data, size, 0);
|
|
}
|
|
#ifdef DUER_SECOND_WAKEUP_ENABLE
|
|
}
|
|
#endif//DUER_SECOND_WAKEUP_ENABLE
|
|
}
|
|
#endif
|
|
|
|
static void duer_send_original_voice(const void *data, size_t size)
|
|
{
|
|
if (g_topic._need_cache) {
|
|
DUER_LOGE("TODO: vad cache does not support original voice");
|
|
}
|
|
|
|
while (_size + size > I_BUFFER_LEN) {
|
|
if (_size < I_BUFFER_LEN) {
|
|
size_t left = I_BUFFER_LEN - _size;
|
|
DUER_MEMCPY(_data + _size, data, left);
|
|
_size += left;
|
|
data = (const char *)data + left;
|
|
size -= left;
|
|
}
|
|
|
|
duer_send_content(_data, _size, 0, 0);
|
|
_size = 0;
|
|
}
|
|
|
|
DUER_MEMCPY(_data + _size, data, size);
|
|
_size += size;
|
|
}
|
|
|
|
size_t duer_increase_topic_id(void)
|
|
{
|
|
return duer_session_generate();
|
|
}
|
|
|
|
static void duer_voice_terminate_internal(int what, void *object)
|
|
{
|
|
//duer_cache_item_t *top = NULL; //unused
|
|
duer_session_consume(g_topic._id);
|
|
|
|
local_mutex_lock(DUER_TRUE);
|
|
|
|
if (g_topic.cache) {
|
|
duer_qcache_destroy_traverse(g_topic.cache, duer_cache_item_release);
|
|
g_topic.cache = NULL;
|
|
}
|
|
|
|
local_mutex_lock(DUER_FALSE);
|
|
}
|
|
|
|
static void duer_voice_start_internal(int what, void *object)
|
|
{
|
|
#ifdef DUER_SECOND_WAKEUP_ENABLE
|
|
if (s_wakeup_voice_cache && duer_voice_not_empty(s_wakeup_voice_cache)) {
|
|
duer_voice_clean_cache(s_wakeup_voice_cache);
|
|
}
|
|
#endif
|
|
|
|
if (s_voice_cache && duer_voice_not_empty(s_voice_cache)) {
|
|
duer_voice_clean_cache(s_voice_cache);
|
|
}
|
|
|
|
duer_voice_terminate_internal(what, object);
|
|
|
|
#if !USE_OWN_SPEEX_LIB
|
|
if (_speex) {
|
|
duer_speex_destroy(_speex);
|
|
_speex = NULL;
|
|
}
|
|
#endif
|
|
|
|
// Initialize the topic parameters
|
|
g_topic._samplerate = what;
|
|
g_topic._id = duer_session_generate();
|
|
g_topic._segment = 0;
|
|
g_topic._eof = DUER_FALSE;
|
|
local_mutex_lock(DUER_TRUE);
|
|
g_topic.cache = duer_qcache_create();
|
|
local_mutex_lock(DUER_FALSE);
|
|
|
|
#ifdef DISABLE_SPX_ON_WCHAT
|
|
|
|
if (duer_voice_get_mode() != DUER_VOICE_MODE_WCHAT)
|
|
#endif
|
|
{
|
|
#if !USE_OWN_SPEEX_LIB
|
|
_speex = duer_speex_create(g_topic._samplerate, s_speex_quality);
|
|
#endif
|
|
}
|
|
|
|
_size = 0;
|
|
|
|
duer_ds_log_rec_start(g_topic._id);
|
|
#ifdef ENABLE_DUER_STORE_VOICE
|
|
duer_store_voice_start(g_topic._id);
|
|
#endif // ENABLE_DUER_STORE_VOICE
|
|
}
|
|
|
|
static void duer_voice_send_internal(int what, void *object)
|
|
{
|
|
if (object) {
|
|
if (duer_session_is_matched(g_topic._id) == DUER_TRUE) {
|
|
DUER_LOGD("duer_voice_send_internal");
|
|
#ifdef DISABLE_SPX_ON_WCHAT
|
|
|
|
if (duer_voice_get_mode() == DUER_VOICE_MODE_WCHAT) {
|
|
duer_send_original_voice(object, (size_t)what);
|
|
goto out;
|
|
}
|
|
|
|
#endif
|
|
|
|
#if !USE_OWN_SPEEX_LIB
|
|
if (_speex) {
|
|
#ifdef ENABLE_DUER_PRINT_SPEEX_COST
|
|
duer_u32_t t1 = duer_timestamp();
|
|
#endif // ENABLE_DUER_PRINT_SPEEX_COST
|
|
duer_speex_encode(_speex, object, (size_t)what, duer_speex_encoded_callback);
|
|
#ifdef ENABLE_DUER_PRINT_SPEEX_COST
|
|
duer_u32_t t2 = duer_timestamp();
|
|
DUER_LOGI("size:%d, speex cost:%d", what, t2 - t1);
|
|
#endif // ENABLE_DUER_PRINT_SPEEX_COST
|
|
}
|
|
|
|
#else
|
|
if (what) {
|
|
int len = 0;
|
|
|
|
do {
|
|
if (len + I_BUFFER_LEN > what) {
|
|
duer_send_content((unsigned char *)object + len, what - len, 0, 0);
|
|
break;
|
|
} else {
|
|
duer_send_content((unsigned char *)object + len, I_BUFFER_LEN, 0, 0);
|
|
len += I_BUFFER_LEN;
|
|
}
|
|
} while (len < what);
|
|
}
|
|
#endif
|
|
}
|
|
out:
|
|
;
|
|
#ifdef DUER_VOICE_SEND_ASYNC
|
|
DUER_FREE(object);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void duer_voice_cache_internal(int what, void *object)
|
|
{
|
|
g_topic._need_cache = what;
|
|
}
|
|
|
|
static void duer_voice_stop_internal(int what, void *object)
|
|
{
|
|
#ifdef DUER_SECOND_WAKEUP_ENABLE
|
|
if (s_wakeup_voice_cache && duer_voice_not_empty(s_wakeup_voice_cache)) {
|
|
duer_voice_clean_cache(s_wakeup_voice_cache);
|
|
}
|
|
#endif
|
|
|
|
if (s_voice_cache && duer_voice_not_empty(s_voice_cache)) {
|
|
duer_voice_clean_cache(s_voice_cache);
|
|
}
|
|
|
|
if (duer_session_is_matched(g_topic._id) == DUER_TRUE) {
|
|
#if !USE_OWN_SPEEX_LIB
|
|
if (_speex) {
|
|
duer_speex_encode(_speex, NULL, 0, duer_speex_encoded_callback);
|
|
}
|
|
#endif
|
|
if (_size > 0 || g_topic._segment > 0) {
|
|
#if !USE_OWN_SPEEX_LIB
|
|
duer_send_content(_data, _size, 1, 0, 0);
|
|
#else
|
|
if (what >= I_BUFFER_LEN) {
|
|
duer_send_content((unsigned char *)object, I_BUFFER_LEN, 1, 0);
|
|
} else {
|
|
duer_send_content((unsigned char *)object, what, 1, 0);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
duer_ds_log_rec_stop(g_topic._id);
|
|
_size = 0;
|
|
|
|
#if !USE_OWN_SPEEX_LIB
|
|
if (_speex) {
|
|
duer_speex_destroy(_speex);
|
|
_speex = NULL;
|
|
}
|
|
#endif
|
|
|
|
#ifdef ENABLE_DUER_STORE_VOICE
|
|
duer_store_voice_end();
|
|
#endif // ENABLE_DUER_STORE_VOICE
|
|
|
|
// consume the topic id, so that we can check if voice stopped.
|
|
duer_session_consume(g_topic._id);
|
|
}
|
|
|
|
static int duer_events_call_internal(duer_events_func func, int what, void *object)
|
|
{
|
|
#ifdef ENABLE_DUER_STORE_VOICE
|
|
duer_store_voice_write(object, what);
|
|
#endif // ENABLE_DUER_STORE_VOICE
|
|
#ifdef DUER_VOICE_SEND_ASYNC
|
|
|
|
if (g_events_handler) {
|
|
return duer_events_call(g_events_handler, func, what, object);
|
|
} else {
|
|
DUER_LOGE("duer_events_call_internal, handler not initialied...");
|
|
}
|
|
|
|
return DUER_ERR_FAILED;
|
|
#else
|
|
|
|
if (func) {
|
|
if (!s_func_mutex || !s_mutex) {
|
|
DUER_LOGW("voice has finalized, s_func_mutex:%p, s_mutex:%p",
|
|
s_func_mutex, s_mutex);
|
|
return DUER_ERR_FAILED;
|
|
}
|
|
|
|
duer_mutex_lock(s_func_mutex);
|
|
func(what, object);
|
|
duer_mutex_unlock(s_func_mutex);
|
|
}
|
|
|
|
return DUER_OK;
|
|
#endif
|
|
}
|
|
|
|
int duer_voice_initialize(void)
|
|
{
|
|
#ifdef DUER_VOICE_SEND_ASYNC
|
|
|
|
if (g_events_handler == NULL) {
|
|
g_events_handler = duer_events_create("lightduer_voice", 1024 * 6, 64);
|
|
}
|
|
|
|
#endif
|
|
|
|
if (!s_mutex) {
|
|
s_mutex = duer_mutex_create();
|
|
}
|
|
|
|
if (!s_func_mutex) {
|
|
DUER_LOGI("Mutex initializing");
|
|
s_func_mutex = duer_mutex_create();
|
|
}
|
|
|
|
if (!s_func_mutex) {
|
|
duer_voice_init_cache(&s_voice_cache, DUER_VOICE_CACHE_SIZE);
|
|
}
|
|
|
|
s_voice_mode = DUER_VOICE_MODE_DEFAULT;
|
|
|
|
return DUER_OK;
|
|
}
|
|
|
|
void duer_voice_finalize(void)
|
|
{
|
|
#ifdef DUER_VOICE_SEND_ASYNC
|
|
|
|
if (g_events_handler) {
|
|
duer_events_destroy(g_events_handler);
|
|
g_events_handler = NULL;
|
|
}
|
|
|
|
#endif
|
|
|
|
duer_voice_terminate_internal(0, NULL);
|
|
|
|
if (s_mutex) {
|
|
duer_mutex_destroy(s_mutex);
|
|
s_mutex = NULL;
|
|
}
|
|
|
|
if (s_func_mutex) {
|
|
duer_mutex_destroy(s_func_mutex);
|
|
s_func_mutex = NULL;
|
|
}
|
|
|
|
if (s_func_mutex) {
|
|
duer_voice_uninit_cache(&s_voice_cache);
|
|
}
|
|
}
|
|
|
|
void duer_voice_set_delay_threshold(duer_u32_t delay, duer_voice_delay_func func)
|
|
{
|
|
s_voice_delay_threshold = delay;
|
|
s_voice_delay_callback = func;
|
|
}
|
|
|
|
void duer_voice_set_mode(duer_voice_mode mode)
|
|
{
|
|
s_voice_mode = mode;
|
|
}
|
|
|
|
duer_voice_mode duer_voice_get_mode(void)
|
|
{
|
|
return s_voice_mode;
|
|
}
|
|
|
|
int duer_voice_start(int sample_rate)
|
|
{
|
|
duer_dcs_on_listen_started(sample_rate);
|
|
return duer_events_call_internal(duer_voice_start_internal, sample_rate, NULL);
|
|
}
|
|
|
|
int duer_voice_send(const void *data, size_t size)
|
|
{
|
|
int rs = DUER_ERR_FAILED;
|
|
|
|
#ifdef DUER_VOICE_SEND_ASYNC
|
|
void *buff = NULL;
|
|
|
|
do {
|
|
buff = DUER_MALLOC(size);
|
|
|
|
if (!buff) {
|
|
DUER_LOGE("Alloc temp memory failed!");
|
|
break;
|
|
}
|
|
|
|
DUER_MEMCPY(buff, data, size);
|
|
|
|
rs = duer_events_call_internal(duer_voice_send_internal, (int)size, buff);
|
|
|
|
if (rs != DUER_OK) {
|
|
DUER_FREE(buff);
|
|
}
|
|
} while (0);
|
|
|
|
#else
|
|
DUER_LOGD("duer_voice_send");
|
|
|
|
rs = duer_events_call_internal(duer_voice_send_internal, (int)size, (void *)data);
|
|
|
|
#endif
|
|
|
|
return rs;
|
|
}
|
|
|
|
int duer_voice_cache(duer_bool cached)
|
|
{
|
|
return duer_events_call_internal(duer_voice_cache_internal, cached, NULL);
|
|
}
|
|
|
|
int duer_voice_stop(const void *data, int size)
|
|
{
|
|
return duer_events_call_internal(duer_voice_stop_internal, size, (void *)data);
|
|
}
|
|
|
|
int duer_voice_terminate(void)
|
|
{
|
|
return duer_events_call_internal(duer_voice_terminate_internal, 0, NULL);
|
|
}
|
|
|
|
int duer_voice_set_speex_quality(int quality)
|
|
{
|
|
// the valid range of speex quality is [0,10]
|
|
if (quality < 0 || quality > 10) {
|
|
DUER_LOGE("Invalid speex quality");
|
|
return DUER_ERR_FAILED;
|
|
}
|
|
|
|
s_speex_quality = quality;
|
|
return DUER_OK;
|
|
}
|
|
|
|
#ifdef DUER_SECOND_WAKEUP_ENABLE
|
|
void duer_voice_truncate_wakeup_cache(int count)
|
|
{
|
|
int cur_count = 0;
|
|
char *voice_data = NULL;
|
|
|
|
if (s_wakeup_voice_cache == NULL) {
|
|
DUER_LOGE("s_wakeup_voice_cache haven't initialized!");
|
|
return;
|
|
}
|
|
|
|
if (!duer_voice_not_empty(s_wakeup_voice_cache)) {
|
|
return;
|
|
}
|
|
|
|
cur_count = duer_voice_cache_size(s_wakeup_voice_cache);
|
|
while (cur_count > count) {
|
|
voice_data = duer_voice_get_cache(s_wakeup_voice_cache);
|
|
if (voice_data) {
|
|
DUER_FREE(voice_data);
|
|
}
|
|
--cur_count;
|
|
}
|
|
}
|
|
|
|
static void duer_voice_restart_internal(int what, void *object)
|
|
{
|
|
// discard old id.
|
|
duer_session_consume(g_topic._id);
|
|
|
|
// Initialize the topic parameters
|
|
g_topic._id = duer_session_generate();
|
|
g_topic._segment = 0;
|
|
g_topic._eof = DUER_FALSE;
|
|
|
|
_size = 0;
|
|
}
|
|
|
|
static void duer_voice_send_wakeup_internal(int what, void *object)
|
|
{
|
|
if (duer_session_is_matched(g_topic._id) == DUER_TRUE) {
|
|
s_wakeup_eof_num = what;
|
|
if (_size > 0) {
|
|
duer_send_content(_data, _size, 0, DUER_WAKEUP_BIT | DUER_WAKEUP_EOF_BIT);
|
|
_size = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void duer_voice_second_wakeup_init(int cache_size)
|
|
{
|
|
duer_voice_init_cache(&s_wakeup_voice_cache, cache_size);
|
|
DUER_LOGD("Init wakeup cache with size: %d", cache_size);
|
|
}
|
|
|
|
void duer_voice_second_wakeup_uninit()
|
|
{
|
|
duer_voice_uninit_cache(&s_wakeup_voice_cache);
|
|
}
|
|
|
|
duer_bool duer_voice_is_started()
|
|
{
|
|
return duer_session_is_matched(g_topic._id);
|
|
}
|
|
|
|
void duer_voice_set_send_state(duer_voice_send_state send_state)
|
|
{
|
|
s_voice_send_state = send_state;
|
|
}
|
|
|
|
int duer_voice_restart(void)
|
|
{
|
|
return duer_events_call_internal(duer_voice_restart_internal, 0, NULL);
|
|
}
|
|
|
|
int duer_voice_send_wakeup(int wakeup)
|
|
{
|
|
return duer_events_call_internal(duer_voice_send_wakeup_internal, wakeup, NULL);
|
|
}
|
|
#endif//DUER_SECOND_WAKEUP_ENABLE
|
|
|