因为业务需要,需要实现TTS功能。现讲开发过程和实现方式整理成文档,供有需要的人参考和讨论。
1、科大讯飞讯飞开放平台可以联网实现TTS功能,注册之后每天可以免费500次访问。
2、访问科大讯飞平台
目前访问需要Websocket API,帮助文档链接,具体的使用流程可以参看文档说明。
3、在Openharmony下移植websocket
访问websocket使用的是nopoll开源方案。将nopoll工程复制到third_party\nopoll下,在该文件下,添加BUILD.gn文件。
import("//build/lite/config/component/lite_component.gni")
import("//build/lite/ndk/ndk.gni")
config("nopoll_config") {
include_dirs = [
"nopoll",
"//device/hisilicon/hispark_pegasus/sdk_liteos/third_party/lwip_sack/include",
"//kernel/liteos_m/components/cmsis/2.0",
"//third_party/lwip/src/include",
"//third_party/tinycrypt/include",
]
}
cflags = [ "-Wno-unused-variable" ]
cflags += [ "-Wno-unused-but-set-variable" ]
cflags += [ "-Wno-unused-parameter" ]
cflags += [ "-Wno-sign-compare" ]
cflags += [ "-Wno-unused-function" ]
cflags += [ "-Wno-return-type" ]
nopoll_sources = [
"nopoll/nopoll.c",
"nopoll/nopoll_decl.c",
"nopoll/nopoll_win32.c",
"nopoll/nopoll_ctx.c",
"nopoll/nopoll_conn.c",
"nopoll/nopoll_log.c",
"nopoll/nopoll_listener.c",
"nopoll/nopoll_loop.c",
"nopoll/nopoll_io.c",
"nopoll/nopoll_msg.c",
"nopoll/nopoll_conn_opts.c",
"nopoll/nopoll_rtthread.c",
]
lite_library("nopoll_static") {
target_type = "static_library"
sources = nopoll_sources
public_configs = [ ":nopoll_config" ]
}
lite_library("nopoll_shared") {
target_type = "shared_library"
sources = nopoll_sources
public_configs = [ ":nopoll_config" ]
}
ndk_lib("nopoll_ndk") {
if (board_name != "hi3861v100") {
lib_extension = ".so"
deps = [
":nopoll_shared"
]
} else {
deps = [
":nopoll_static"
]
}
head_files = [
"//third_party/nopoll"
]
}
然后在工程的gn文件下,
4、实现websocket功能(关键代码)
nopoll还是很吃内存的,需要动态开辟很大的空间。因为考虑到空间,所以,转换的tts格式是mp3格式。
(1)websocket规则的日期获取
char *week[] = {"Mon, ", "Tues, ", "Wed, ", "Thur, ","Fri, ", "Sat, ","Sun, "};
char *month[] = {"", " Jan ", " Feb ", " Mar ", " Apr "," May ", " June "," July ", " Aug ", " Sept ", " Oct "," Nov ", " Dec "};
static void get_date(char *date)
{
int tv_sec = hi_get_real_time();
DEBUG_printf("hi_get_real_time=%d\r\n",tv_sec);
//timeutils_struct_time_t tm;
//timeutils_seconds_since_2000_to_struct_time(tv_sec, &tm);
time_t t = tv_sec;
struct tm *tm = localtime(&t);
// date: Tue, 15 Oct 2019 07:00:50 GMT
sprintf(date, "%s%02d%s%d%s%02d%s%02d%s%02d%s", week[tm->tm_wday], tm->tm_mday, month[tm->tm_mon], tm->tm_year+1900, " ",tm->tm_hour,":", tm->tm_min, ":", tm->tm_sec, " GMT");
}
因为需要校验时间,所以,设备需要联网,然后从网络拉取时间,进行时间更新。
(2)上传的json打包
void ws_xfyun_tts_request_json(char *buff)
{
char *string = NULL;
cJSON *root = cJSON_CreateObject();
//common
cJSON *cj_common = cJSON_CreateObject();
cJSON_AddItemToObject(root, "common", cj_common);
cJSON_AddItemToObject(cj_common, "app_id", cJSON_CreateString("0ea5cd21"));
//business
cJSON *cj_business = cJSON_CreateObject();
cJSON_AddItemToObject(root, "business", cj_business);
cJSON_AddItemToObject(cj_business, "aue", cJSON_CreateString("lame"));
cJSON_AddItemToObject(cj_business, "sfl", cJSON_CreateNumber(1));
cJSON_AddItemToObject(cj_business, "vcn",cJSON_CreateString("xiaoyan"));
cJSON_AddItemToObject(cj_business, "tte",cJSON_CreateString("UTF8"));
cJSON_AddItemToObject(cj_business, "pitch",cJSON_CreateNumber(50));
cJSON_AddItemToObject(cj_business, "speed",cJSON_CreateNumber(50));
//data
cJSON *cj_data = cJSON_CreateObject();
cJSON_AddItemToObject(root, "data", cj_data);
cJSON_AddItemToObject(cj_data, "status", cJSON_CreateNumber(2));
char base64_text[64];
int base64_len = sizeof(base64_text);
tiny_base64_encode(base64_text,&base64_len,tts_text,strlen(tts_text));
cJSON_AddItemToObject(cj_data, "text", cJSON_CreateString(base64_text));//北京 5YyX5Lqs
string = cJSON_PrintUnformatted(root);
strcpy(buff, string);
cJSON_Delete(root);
free(string);
}
(3)MP3解码
使用了helix库。
void mp3_decode_array(char *data,int len)
{
HMP3Decoder Decoder;
MP3FrameInfo mp3FrameInfo;
int bytesleft = len;
int decode_step = 0;;
unsigned short int output[1024*2];
Decoder = MP3InitDecoder();
int offset = MP3FindSyncWord(data,bytesleft); //搜索缓存中第一个有效数据帧
DEBUG_printf("offset = %d\r\n",offset);
if (offset < 0)
{
DEBUG_printf("MP3FindSyncWord not find.\r\n");
bytesleft = 0; // All data not avalible, clear the buffer.
return;
}
else if (offset > 0)
{
//去除头部无效数据
bytesleft -= offset;
decode_step += offset;
}
//以下解码n帧,readPtr会递增,bytesleft递减
unsigned char *readPtr;
readPtr = data+decode_step;
while (bytesleft > 0)
{
int ret = MP3Decode(Decoder, &readPtr, &bytesleft, (short *)output, 0);
if (ret == ERR_MP3_NONE) //正常解码
{
DEBUG_printf("decode ok:bytesleft=%d\r\n",bytesleft);
MP3GetLastFrameInfo(Decoder, &(mp3FrameInfo));
hi_i2s_write((unsigned char *)output, mp3FrameInfo.outputSamps * 2, 1000);
}
else//解码异常
{
DEBUG_printf("decode err: %d %d\r\n", ret,bytesleft);
}
}
DEBUG_printf("decode end.\r\n");
}