8.使用科大讯飞语音实现智能家居控制
0x00 智能家居控制简介
现在智能家居越来越火,从最初的概念逐步开始变为现实。而且真正的智能家居产品也在慢慢的走进我们的生活,现在国内多家厂商已经开始积极布局智能家居行业。目前像海尔、小米、百度、紫光物联、欧瑞博等,都已经很有多产品开始销售了。现在的大多数智能家居产品还是使用控制面板、手机等实体来操控的,随着现在语音技术的不断成熟。我相信以后智能家居控制产品最终会使用语音来进行控制,当然面板控制也不会丢弃,只是作为备用,大部分时间各种操作都是通过语音来控制。现在的电视机很多都支持语音控制了,不过现在好像很多人不怎么看电视,都是玩手机了。现在市面上常用的智能家居控制产品,我感觉还是智能插座比较实用点,其他的感觉比较鸡肋。
现在国内语音领域技术较为先进和全面的,就属我们耳熟能详的科大讯飞了。在前面的课程介绍了科大讯飞唤醒模块、离线命令词识别模块、离线语音合成模块。前面都是分开介绍的,在这里我们就可以将以上功能给结合在一起,这样就可以实现一个完整的语音控制系统了。
0x01 语音控制整体源码介绍
在开始介绍各模块之前,还是给大家看下整体代码的组成结构。整体代码结构比较简单,就是把前面三次课程的代码融合、连接在一起。所有的代码我都已经上传至代码服务仓库,代码仓库网址如下:
https://code.corvin.cn:3000/corvin_zhang/AIVoiceSystem.git
本次课程的代码,我是放在了example目录下xf_voiceHome中,具体如下图所示:
这里需要注意的是libmsc.so动态库,它就是我们在前面3篇文章中介绍的库是一样的。如果在前面下载过,这里就不用再下载了。如果以前没下载,也可以在这里下载,不过仍然是需要收费的:
[wshop_downloads]
在对源码有个初步认识后,我们可以来看下这次课程最终做出来的效果是什么样的。可以通过下面的视频来了解一下,在看完效果后,我们下面在分开介绍各个代码模块是如何连接起来的:
0x02 main函数文件
这里算是整个语音交互系统源码的入口,这里包含了执行文件的main()函数。当然这里的main()函数很简单,那是因为整个语音交互系统是从前往后顺序执行的。就是说,必须先唤醒后,才能进入到ASR(离线命令词识别)流程。在ASR结束后,才能进入到TTS流程,最终把语音合成的文件播报出来就结束了本次的语音交互流程。然后会重新进入到等待唤醒词唤醒的流程中,这样循环往复的开启语音交互流程的。下面是main()函数的源码:
#include "../include/awaken.h"
int main(int argc, char **argv)
{
while(1)
{
waitAwaken();
}
return 0;
}
从上述代码可以看出,这里的main()函数就是一个while(1)死循环。我们一直在不断的执行waiAwaken()函数,就是一直在进入到等待唤醒的流程中。不过这里的waitAwaken()是会阻塞执行的,不会是一直在不断的重复执行。这是因为在waitAwaken()函数中,我们会不断的检测有没有唤醒词,当有唤醒词被检测到才进入到后面的流程。下面就是main文件的流程图,很简单:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include "msp_cmn.h"
#include "msp_errors.h"
#include "linuxrec.h"
#include "formats.h"
#include "qivw.h"
#include "asr.h"
#define SAMPLE_RATE_16K (16000)
#define DEFAULT_FORMAT \
{\
WAVE_FORMAT_PCM,\
1, \
16000, \
32000, \
2, \
16, \
sizeof(WAVEFORMATEX)\
}
struct recorder *recorder = NULL;
static int flag = 1;
void sleep_ms(int ms)
{
usleep(ms * 1000);
}
/* the record call back */
void record_data_cb(char *data, unsigned long len, void *user_para)
{
int errcode = 0;
const char *session_id = (const char *)user_para;
if(len == 0 || data == NULL)
return;
errcode = QIVWAudioWrite(session_id, (const void *)data, len, MSP_AUDIO_SAMPLE_CONTINUE);
if (MSP_SUCCESS != errcode)
{
printf("QIVWAudioWrite failed! error code:%d\n",errcode);
int ret = stop_record(recorder);
if (ret != 0)
{
printf("Stop failed! \n");
}
QIVWAudioWrite(session_id, NULL, 0, MSP_AUDIO_SAMPLE_LAST);
}
}
int cb_ivw_msg_proc( const char *sessionID, int msg, int param1, int param2, const void *info, void *userData )
{
if(MSP_IVW_MSG_WAKEUP == msg) //唤醒成功消息
{
//printf("\n\nMSP_IVW_MSG_WAKEUP result = %s\n\n", (char*)info);
system("play -q --multi-threaded ./msc/ding.wav");
flag = 0;
}
else if(MSP_IVW_MSG_ERROR == msg) //唤醒出错消息
{
printf("\nMSP_IVW_MSG_ERROR errCode = %d\n\n", param1);
return -1;
}
return 0;
}
void run_ivw(const char* session_begin_params)
{
const char *session_id = NULL;
int err_code = MSP_SUCCESS;
char sse_hints[128];
flag = 1;
WAVEFORMATEX wavfmt = DEFAULT_FORMAT;
wavfmt.nSamplesPerSec = SAMPLE_RATE_16K;
wavfmt.nAvgBytesPerSec = wavfmt.nBlockAlign * wavfmt.nSamplesPerSec;
//start QIVW
session_id=QIVWSessionBegin(NULL, session_begin_params, &err_code);
if (err_code != MSP_SUCCESS)
{
printf("QIVWSessionBegin failed! error code:%d\n",err_code);
goto exit;
}
err_code = QIVWRegisterNotify(session_id, cb_ivw_msg_proc, NULL);
if (err_code != MSP_SUCCESS)
{
snprintf(sse_hints, sizeof(sse_hints), "QIVWRegisterNotify errorCode=%d", err_code);
printf("QIVWRegisterNotify failed! error code:%d\n",err_code);
goto exit;
}
//1.create recorder
err_code = create_recorder(&recorder, record_data_cb, (void*)session_id);
if (recorder == NULL || err_code != 0)
{
printf("create recorder failed: %d\n", err_code);
err_code = MSP_ERROR_FAIL;
goto exit;
}
//2.open_recorder
err_code = open_recorder(recorder, get_default_input_dev(), &wavfmt);
if (err_code != 0)
{
printf("recorder open failed: %d\n", err_code);
err_code = MSP_ERROR_FAIL;
goto exit;
}
//3.start record
err_code = start_record(recorder);
if (err_code != 0)
{
printf("start record failed: %d\n", err_code);
err_code = MSP_ERROR_FAIL;
goto exit;
}
while(flag)
{
sleep_ms(1000); //模拟人说话时间间隙,10帧的音频时长为200ms
printf("Listening wakeup trigger... \n");
}
snprintf(sse_hints, sizeof(sse_hints), "success");
exit:
if (recorder)
{
if(!is_record_stopped(recorder))
stop_record(recorder);
close_recorder(recorder);
destroy_recorder(recorder);
recorder = NULL;
}
if (NULL != session_id)
{
QIVWSessionEnd(session_id, sse_hints);
}
}
int waitAwaken()
{
int ret = MSP_SUCCESS;
const char *lgi_param = "appid = 5d5b9efd, work_dir = .";
const char *ssb_param = "ivw_threshold=0:2000, sst=wakeup, ivw_res_path=fo|res/ivw/wakeupresource.jet";
ret = MSPLogin(NULL, NULL, lgi_param);
if(MSP_SUCCESS != ret)
{
printf("Awaken MSPLogin failed, error code: %d.\n", ret);
MSPLogout(); //登录失败,退出登录
return -1;
}
run_ivw(ssb_param);
MSPLogout(); //退出登录
//start ASR
startASR();
return 0;
}
通过waitAwaken()函数我们可以得知,当初始化好登录参数后,就可以来进入监听唤醒词的流程了,这里使用run_ivw()函数来实现。在此函数中,如果没有唤醒词检测回调函数的反馈,那就好一直在while()循环中,只有在cb_ivw_msg_proc()函数中得到了MSP_IVW_MSG_WAKEUP的消息,才好退出while()循环,然后调用startASR()开启后面的命令词检测,代码的整体逻辑流程图如下所示:
0x04 离线命令词识别流程
这里的离线命令词识别其实是ASR功能的子功能,ASR(Automatic Speech Recognition)本意是自动语音识别。它可以检测任意的语音资料,并将其转换成对应的文本文件。在这个将语音文件转换为文本的过程中,就诞生了这个命令词识别的功能。因为我们可以设计语法文件,不用将任意的语音语料转换为相应的文本,而是只是将我们需要检测的关键字,这样就可以大大减小资源消耗。所以说这里的离线命令词识别,就是ASR功能的一个子功能实现。它只是对特定的语音文件进行检测识别,不过这里我们需要设置好需要检测的语法文件。下面就是命令词检测的源码文件asr.c:
/*
* 语音听写(iFly Auto Transform)技术能够实时地将语音转换成对应的文字。
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <wiringPi.h>
#include "qisr.h"
#include "tts.h"
#include "msp_cmn.h"
#include "msp_errors.h"
#include "speech_recognizer.h"
#define FRAME_LEN 640
#define BUFFER_SIZE 4096
#define SAMPLE_RATE_16K (16000)
#define SAMPLE_RATE_8K (8000)
#define MAX_GRAMMARID_LEN (32)
#define MAX_PARAMS_LEN (1024)
#define LED_PIN 26
#define FAN_PIN 25
#define ON 1
#define OFF 2
#define LED_DEVICE 3
#define FAN_DEVICE 4
static int recEndFlag = 0; //是否识别出结果的标志
static int exitFlag = 0;
static char *g_result = NULL;
static unsigned int g_buffersize = BUFFER_SIZE;
static char outText[100]; //最终要语音合成的文本
const char * ASR_RES_PATH = "fo|res/asr/common.jet"; //离线语法识别资源路径
const char * GRM_BUILD_PATH = "res/asr/GrmBuilld"; //构建离线语法识别网络生成数据保存路径
const char * GRM_FILE = "./msc/control.bnf"; //构建离线识别语法网络所用的语法文件
const char * led_str = "灯";
const char * fan_str = "风扇";
const char * open_str = "打开";
const char * close_str = "关闭";
const char * ok_str = "好的,";
const char *noCmdStr = "没有听到您的命令";
typedef struct _UserData {
int build_fini; //标识语法构建是否完成
int update_fini; //标识更新词典是否完成
int errcode; //记录语法构建或更新词典回调错误码
char grammar_id[MAX_GRAMMARID_LEN]; //保存语法构建返回的语法ID
}UserData;
int build_grammar(UserData *udata); //构建离线识别语法网络
int run_asr(UserData *udata); //进行离线语法识别
void controlDevice(int deviceID, int flag)
{
exitFlag = 1; //exit asr function
if(LED_DEVICE == deviceID) //control led light
{
if(ON == flag) //light on
{
digitalWrite(LED_PIN, LOW);
}
else //light off
{
digitalWrite(LED_PIN, HIGH);
}
}
else //control fan device
{
if(ON == flag) //fan on
{
digitalWrite(FAN_PIN, HIGH);
}
else //fan off
{
digitalWrite(FAN_PIN, LOW);
}
}
}
int build_grm_cb(int ecode, const char *info, void *udata)
{
UserData *grm_data = (UserData *)udata;
if (NULL != grm_data) {
grm_data->build_fini = 1;
grm_data->errcode = ecode;
}
if (MSP_SUCCESS == ecode && NULL != info) {
printf("构建语法成功! 语法ID:%s\n", info);
if (NULL != grm_data)
snprintf(grm_data->grammar_id, MAX_GRAMMARID_LEN - 1, info);
}
else
printf("构建语法失败!%d\n", ecode);
return 0;
}
int build_grammar(UserData *udata)
{
FILE *grm_file = NULL;
char *grm_content = NULL;
unsigned int grm_cnt_len = 0;
int ret = 0;
char grm_build_params[MAX_PARAMS_LEN];
memset(grm_build_params, '\0', MAX_PARAMS_LEN);
grm_file = fopen(GRM_FILE, "rb");
if(NULL == grm_file) {
printf("打开\"%s\"文件失败![%s]\n", GRM_FILE, strerror(errno));
return -1;
}
fseek(grm_file, 0, SEEK_END);
grm_cnt_len = ftell(grm_file);
fseek(grm_file, 0, SEEK_SET);
grm_content = (char *)malloc(grm_cnt_len + 1);
if (NULL == grm_content)
{
printf("内存分配失败!\n");
fclose(grm_file);
grm_file = NULL;
return -1;
}
fread((void*)grm_content, 1, grm_cnt_len, grm_file);
grm_content[grm_cnt_len] = '\0';
fclose(grm_file);
grm_file = NULL;
snprintf(grm_build_params, MAX_PARAMS_LEN - 1,
"engine_type = local, \
asr_res_path = %s, sample_rate = %d, \
grm_build_path = %s, ",
ASR_RES_PATH,
SAMPLE_RATE_16K,
GRM_BUILD_PATH
);
ret = QISRBuildGrammar("bnf", grm_content, grm_cnt_len, grm_build_params, build_grm_cb, udata);
free(grm_content);
grm_content = NULL;
return ret;
}
static void procResultStr(char *string)
{
memset(outText, '\0', sizeof(outText));
strcat(outText, ok_str);
char *tmp = strstr(string, "input=");
strcat(outText, tmp+6);
}
static void show_result(char *string, char is_over)
{
char *check = NULL;
int device_id = 0;
int operate_id = 0;
printf("Result: [ %s ]", string);
procResultStr(string);
if(is_over)
putchar('\n');
check = strstr(string, led_str);
if(check != NULL)
{
device_id = 3;
}
check = strstr(string, fan_str);
if(check != NULL)
{
device_id = 4;
}
check = strstr(string, open_str);
if(check != NULL)
{
operate_id = 1;
}
check = strstr(string, close_str);
if(check != NULL)
{
operate_id = 2;
}
if((device_id != 0) && (operate_id != 0))
{
controlDevice(device_id, operate_id);
}
}
void on_result(const char *result, char is_last)
{
if (result) {
size_t left = g_buffersize - 1 - strlen(g_result);
size_t size = strlen(result);
if (left < size) {
g_result = (char*)realloc(g_result, g_buffersize + BUFFER_SIZE);
if (g_result)
g_buffersize += BUFFER_SIZE;
else {
printf("mem alloc failed\n");
return;
}
}
strncat(g_result, result, size);
show_result(g_result, is_last);
}
}
void on_speech_begin()
{
if (g_result)
{
free(g_result);
}
g_result = (char*)malloc(BUFFER_SIZE);
g_buffersize = BUFFER_SIZE;
memset(g_result, 0, g_buffersize);
}
void on_speech_end(int reason)
{
recEndFlag = 1;
if (reason == END_REASON_VAD_DETECT)
printf("\nSpeaking done \n");
else
printf("\nRecognizer error %d\n", reason);
}
/* demo recognize the audio from microphone */
static void demo_mic(const char* session_begin_params)
{
int errcode;
struct speech_rec iat;
struct speech_rec_notifier recnotifier = {
on_result,
on_speech_begin,
on_speech_end
};
recEndFlag = 0;
errcode = sr_init(&iat, session_begin_params, SR_MIC, &recnotifier);
if (errcode) {
printf("speech recognizer init failed\n");
return;
}
errcode = sr_start_listening(&iat);
if (errcode) {
printf("start listen failed %d\n", errcode);
}
/*mic always recording */
while(1)
{
printf("listening control command...\n");
if(recEndFlag)
{
break;
}
sleep(1);
}
errcode = sr_stop_listening(&iat);
if (errcode) {
printf("stop listening failed %d\n", errcode);
}
sr_uninit(&iat);
}
int run_asr(UserData *udata)
{
char asr_params[MAX_PARAMS_LEN];
memset(asr_params, '\0', MAX_PARAMS_LEN);
//离线语法识别参数设置
snprintf(asr_params, MAX_PARAMS_LEN - 1,
"engine_type = local, \
asr_res_path = %s, sample_rate = %d, \
grm_build_path = %s, local_grammar = %s, \
result_type = plain, result_encoding = UTF-8",
ASR_RES_PATH,
SAMPLE_RATE_16K,
GRM_BUILD_PATH,
udata->grammar_id
);
demo_mic(asr_params);
return 0;
}
int startASR()
{
int ret = 0 ;
exitFlag = 0;
const char *login_config = "appid = 5d5b9efd"; //登录参数
UserData asr_data;
wiringPiSetup();
pinMode(LED_PIN, OUTPUT);
pinMode(FAN_PIN, OUTPUT);
ret = MSPLogin(NULL, NULL, login_config); //第一个参数为用户名,第二个参数为密码,传NULL即可,第三个参数是登录参数
if (MSP_SUCCESS != ret)
{
printf("ASR 登录失败:%d\n", ret);
MSPLogout();
return -1;
}
memset(&asr_data, 0, sizeof(UserData));
printf("构建离线识别语法网络...\n");
ret = build_grammar(&asr_data); //第一次使用某语法进行识别,需要先构建语法网络,获取语法ID,之后使用此语法进行识别,无需再次构建
if (MSP_SUCCESS != ret)
{
printf("构建语法调用失败!\n");
MSPLogout();
return -2;
}
while (1 != asr_data.build_fini)
usleep(300 * 1000);
if (MSP_SUCCESS != asr_data.errcode)
{
MSPLogout();
return -3;
}
printf("离线识别语法网络构建完成,开始识别...\n");
run_asr(&asr_data);
MSPLogout();
if(exitFlag == 1)
{
startTTS(outText);
}
else
{
startTTS(noCmdStr);
}
return 0;
}
这里的代码相对于以前的离线命令文章中的代码变化很小,只是将main()函数改为了startASR()。这里的startASR()中的各流程其实跟唤醒中流程类似,第一步仍然是MSPLogin(),因为科大讯飞为了保证你用的SDK库中的libmsc.so需要和Appid配套才行。接下来就是build_grammar(),构建生成我们要识别的语法网络。最后就是调用run_asr()开始进行不断的检测语料文件中是否包含语法网络中的命令词了,下面就是该源码文件的函数调用流程图:
0x05 TTS语音合成代码
这里使用的是科大讯飞的离线语音合成功能,只有两种发音人,一种是男声:xiaofeng,一种女声:xiaoyan。语音合成的API调用过程,其实跟唤醒、ASR差不多。下面我们就来看下TTS部分的源码tts.c:
/*
* 语音合成(Text To Speech,TTS)技术能够自动将任意文字实时转换为连续的
* 自然语音,是一种能够在任何时间、任何地点,向任何人提供语音信息服务的
* 高效便捷手段,非常符合信息时代海量数据、动态更新和个性化查询的需求。
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include "qtts.h"
#include "msp_cmn.h"
#include "msp_errors.h"
typedef int SR_DWORD;
typedef short int SR_WORD ;
/* wav音频头部格式 */
typedef struct _wave_pcm_hdr
{
char riff[4]; // = "RIFF"
int size_8; // = FileSize - 8
char wave[4]; // = "WAVE"
char fmt[4]; // = "fmt "
int fmt_size; // = 下一个结构体的大小 : 16
short int format_tag; // = PCM : 1
short int channels; // = 通道数 : 1
int samples_per_sec; // = 采样率 : 8000 | 6000 | 11025 | 16000
int avg_bytes_per_sec; // = 每秒字节数 : samples_per_sec * bits_per_sample / 8
short int block_align; // = 每采样点字节数 : wBitsPerSample / 8
short int bits_per_sample; // = 量化比特数: 8 | 16
char data[4]; // = "data";
int data_size; // = 纯数据长度 : FileSize - 44
} wave_pcm_hdr;
/* 默认wav音频头部数据 */
wave_pcm_hdr default_wav_hdr =
{
{ 'R', 'I', 'F', 'F' },
0,
{'W', 'A', 'V', 'E'},
{'f', 'm', 't', ' '},
16,
1,
1,
16000,
32000,
2,
16,
{'d', 'a', 't', 'a'},
0
};
/* 文本合成 */
int text_to_speech(const char* src_text, const char* des_path, const char* params)
{
int ret = -1;
FILE* fp = NULL;
const char* sessionID = NULL;
unsigned int audio_len = 0;
wave_pcm_hdr wav_hdr = default_wav_hdr;
int synth_status = MSP_TTS_FLAG_STILL_HAVE_DATA;
if (NULL == src_text || NULL == des_path)
{
printf("params is error!\n");
return ret;
}
fp = fopen(des_path, "wb");
if (NULL == fp)
{
printf("open %s error.\n", des_path);
return ret;
}
/* 开始合成 */
sessionID = QTTSSessionBegin(params, &ret);
if (MSP_SUCCESS != ret)
{
printf("QTTSSessionBegin failed, error code: %d.\n", ret);
fclose(fp);
return ret;
}
ret = QTTSTextPut(sessionID, src_text, (unsigned int)strlen(src_text), NULL);
if (MSP_SUCCESS != ret)
{
printf("QTTSTextPut failed, error code: %d.\n",ret);
QTTSSessionEnd(sessionID, "TextPutError");
fclose(fp);
return ret;
}
printf("正在合成:%s\n", src_text);
fwrite(&wav_hdr, sizeof(wav_hdr) ,1, fp); //添加wav音频头,使用采样率为16000
while (1)
{
/* 获取合成音频 */
const void* data = QTTSAudioGet(sessionID, &audio_len, &synth_status, &ret);
if (MSP_SUCCESS != ret)
break;
if (NULL != data)
{
fwrite(data, audio_len, 1, fp);
wav_hdr.data_size += audio_len; //计算data_size大小
}
if (MSP_TTS_FLAG_DATA_END == synth_status)
break;
}
if (MSP_SUCCESS != ret)
{
printf("QTTSAudioGet failed, error code: %d.\n",ret);
QTTSSessionEnd(sessionID, "AudioGetError");
fclose(fp);
return ret;
}
/* 修正wav文件头数据的大小 */
wav_hdr.size_8 += wav_hdr.data_size + (sizeof(wav_hdr) - 8);
/* 将修正过的数据写回文件头部,音频文件为wav格式 */
fseek(fp, 4, 0);
fwrite(&wav_hdr.size_8,sizeof(wav_hdr.size_8), 1, fp); //写入size_8的值
fseek(fp, 40, 0); //将文件指针偏移到存储data_size值的位置
fwrite(&wav_hdr.data_size,sizeof(wav_hdr.data_size), 1, fp); //写入data_size的值
fclose(fp);
fp = NULL;
/* 合成完毕 */
ret = QTTSSessionEnd(sessionID, "Normal");
if (MSP_SUCCESS != ret)
{
printf("QTTSSessionEnd failed, error code: %d.\n",ret);
}
return ret;
}
int startTTS(char *text)
{
int ret = MSP_SUCCESS;
const char* login_params = "appid = 5d5b9efd, work_dir = .";//登录参数,appid与msc库绑定,请勿随意改动
/*
* rdn: 合成音频数字发音方式
* volume: 合成音频的音量
* pitch: 合成音频的音调
* speed: 合成音频对应的语速
* voice_name: 合成发音人
* sample_rate: 合成音频采样率
* text_encoding: 合成文本编码格式
*/
const char* session_begin_params = "engine_type = local,voice_name=xiaofeng, text_encoding = UTF8, tts_res_path = fo|res/tts/xiaofeng.jet;fo|res/tts/common.jet, sample_rate = 16000, speed = 50, volume = 50, pitch = 50, rdn = 0, effect= 3";
const char* filename = "tts_sample.wav"; //合成的语音文件名称
/* 用户登录 */
ret = MSPLogin(NULL, NULL, login_params); //第一个参数是用户名,第二个参数是密码,第三个参数是登录参数,用户名和密码可在http://www.xfyun.cn注册获取
if (MSP_SUCCESS != ret)
{
printf("TTS MSPLogin failed, error code: %d.\n", ret);
goto exit; //登录失败,退出登录
}
/*语音合成*/
ret = text_to_speech(text, filename, session_begin_params);
if (MSP_SUCCESS != ret)
{
printf("text_to_speech failed, error code: %d.\n", ret);
}
printf("合成完毕,开始播放合成语音...\n");
system("play -q --multi-threaded tts_sample.wav");
exit:
MSPLogout(); //退出登录
return 0;
}
通过查看上述源码,我们就可以发现其实这里TTS的实现也很简单。这里的代码基本上跟前面一篇课程介绍的没多少区别。这里将main()函数修改为startTTS(),然后剩下的就是MSPLogin()用户登录,调用text_to_speech()进行语音合成了。通过下面的函数调用流程图,大家会更容易理解tts实现的流程:
0x06 语音系统整体函数调用流程
前面几个小节我们依次介绍了语音系统的唤醒、语音识别、语音合成是如何实现的。分开看整体流程感觉还是没有一个全局感觉,现在我们就可以将上述各模块的函数调用流程图整合在一起。这样大家就可以对整体代码的实现有个整体认识了,下面是整合在一起的函数调用流程图,如下图所示:
0x07 参考资料
[1]. MSC for Windows&Linux API V1.4. http://mscdoc.xfyun.cn/windows/api/iFlytekMSCReferenceManual/files.html
0x08 问题反馈
大家在按照教程学习过程中有任何问题,可以直接在文章末尾给我留言,或者关注ROS小课堂的官方微信公众号,在公众号中给我发消息反馈问题也行。我基本上每天都会处理公众号中的留言!当然如果你要是顺便给ROS小课堂打个赏,我也会感激不尽的,打赏30块还会被邀请进ROS小课堂的微信群,与更多志同道合的小伙伴一起学习和交流!
[wshop_reward]