您的位置:首页 - 文章 -  - 正文

8.使用科大讯飞语音实现智能家居控制

0x00 智能家居控制简介

现在智能家居越来越火,从最初的概念逐步开始变为现实。而且真正的智能家居产品也在慢慢的走进我们的生活,现在国内多家厂商已经开始积极布局智能家居行业。目前像海尔、小米、百度、紫光物联、欧瑞博等,都已经很有多产品开始销售了。现在的大多数智能家居产品还是使用控制面板、手机等实体来操控的,随着现在语音技术的不断成熟。我相信以后智能家居控制产品最终会使用语音来进行控制,当然面板控制也不会丢弃,只是作为备用,大部分时间各种操作都是通过语音来控制。现在的电视机很多都支持语音控制了,不过现在好像很多人不怎么看电视,都是玩手机了。现在市面上常用的智能家居控制产品,我感觉还是智能插座比较实用点,其他的感觉比较鸡肋。

现在国内语音领域技术较为先进和全面的,就属我们耳熟能详的科大讯飞了。在前面的课程介绍了科大讯飞唤醒模块、离线命令词识别模块、离线语音合成模块。前面都是分开介绍的,在这里我们就可以将以上功能给结合在一起,这样就可以实现一个完整的语音控制系统了。


0x01 语音控制整体源码介绍

在开始介绍各模块之前,还是给大家看下整体代码的组成结构。整体代码结构比较简单,就是把前面三次课程的代码融合、连接在一起。所有的代码我都已经上传至代码服务仓库,代码仓库网址如下:

https://code.corvin.cn:3000/corvin_zhang/AIVoiceSystem.git

本次课程的代码,我是放在了example目录下xf_voiceHome中,具体如下图所示:

这里需要注意的是libmsc.so动态库,它就是我们在前面3篇文章中介绍的库是一样的。如果在前面下载过,这里就不用再下载了。如果以前没下载,也可以在这里下载,不过仍然是需要收费的:

隐藏内容需要支付: ¥30
立即购买 升级VIP

在对源码有个初步认识后,我们可以来看下这次课程最终做出来的效果是什么样的。可以通过下面的视频来了解一下,在看完效果后,我们下面在分开介绍各个代码模块是如何连接起来的:

语音系统整体效果演示

0x02 main函数文件

这里算是整个语音交互系统源码的入口,这里包含了执行文件的main()函数。当然这里的main()函数很简单,那是因为整个语音交互系统是从前往后顺序执行的。就是说,必须先唤醒后,才能进入到ASR(离线命令词识别)流程。在ASR结束后,才能进入到TTS流程,最终把语音合成的文件播报出来就结束了本次的语音交互流程。然后会重新进入到等待唤醒词唤醒的流程中,这样循环往复的开启语音交互流程的。下面是main()函数的源码:

 1#include "../include/awaken.h"
2
3int main(int argc, char **argv)
4
{
5    while(1)
6    {
7        waitAwaken();
8    }
9
10    return 0;
11}

从上述代码可以看出,这里的main()函数就是一个while(1)死循环。我们一直在不断的执行waiAwaken()函数,就是一直在进入到等待唤醒的流程中。不过这里的waitAwaken()是会阻塞执行的,不会是一直在不断的重复执行。这是因为在waitAwaken()函数中,我们会不断的检测有没有唤醒词,当有唤醒词被检测到才进入到后面的流程。下面就是main文件的流程图,很简单:

语音交互系统的main()函数

0x03 语音唤醒代码

在这里语音唤醒的源码文件为awaken.c,具体源码如下,其实我们这里只不过将前面课程中唤醒源码的main()函数改为waitAwaken(),代码改动部分很小:

  1#include <stdlib.h>
2#include <stdio.h>
3#include <unistd.h>
4#include <errno.h>
5#include "msp_cmn.h"
6#include "msp_errors.h"
7#include "linuxrec.h"
8#include "formats.h"
9#include "qivw.h"
10#include "asr.h"
11
12
13#define SAMPLE_RATE_16K     (16000)
14
15#define DEFAULT_FORMAT    \
16{\
17    WAVE_FORMAT_PCM,\
18    1,          \
19    16000,      \
20    32000,      \
21    2,          \
22    16,         \
23    sizeof(WAVEFORMATEX)\
24}

25
26struct recorder *recorder = NULL;
27static int flag = 1;
28
29void sleep_ms(int ms)
30
{
31    usleep(ms * 1000);
32}
33
34/* the record call back */
35void record_data_cb(char *data, unsigned long len, void *user_para)
36
{
37    int errcode = 0;
38    const char *session_id = (const char *)user_para;
39
40    if(len == 0 || data == NULL)
41        return;
42
43    errcode = QIVWAudioWrite(session_id, (const void *)data, len, MSP_AUDIO_SAMPLE_CONTINUE);
44    if (MSP_SUCCESS != errcode)
45    {
46        printf("QIVWAudioWrite failed! error code:%d\n",errcode);
47        int ret = stop_record(recorder);
48        if (ret != 0)
49        {
50            printf("Stop failed! \n");
51        }
52        QIVWAudioWrite(session_id, NULL0, MSP_AUDIO_SAMPLE_LAST);
53    }
54}
55
56int cb_ivw_msg_procconst char *sessionID, int msg, int param1, int param2, const void *info, void *userData )
57
{
58  if(MSP_IVW_MSG_WAKEUP == msg) //唤醒成功消息
59  {
60    //printf("\n\nMSP_IVW_MSG_WAKEUP result = %s\n\n", (char*)info);
61    system("play -q --multi-threaded ./msc/ding.wav");
62    flag = 0;
63  }
64  else if(MSP_IVW_MSG_ERROR == msg) //唤醒出错消息
65  {
66    printf("\nMSP_IVW_MSG_ERROR errCode = %d\n\n", param1);
67    return -1;
68  }
69
70  return 0;
71}
72
73
74void run_ivw(const char* session_begin_params)
75
{
76    const char *session_id = NULL;
77    int err_code = MSP_SUCCESS;
78    char sse_hints[128];
79    flag = 1;
80
81    WAVEFORMATEX wavfmt = DEFAULT_FORMAT;
82    wavfmt.nSamplesPerSec = SAMPLE_RATE_16K;
83    wavfmt.nAvgBytesPerSec = wavfmt.nBlockAlign * wavfmt.nSamplesPerSec;
84
85    //start QIVW
86    session_id=QIVWSessionBegin(NULL, session_begin_params, &err_code);
87    if (err_code != MSP_SUCCESS)
88    {
89        printf("QIVWSessionBegin failed! error code:%d\n",err_code);
90        goto exit;
91    }
92
93    err_code = QIVWRegisterNotify(session_id, cb_ivw_msg_proc, NULL);
94    if (err_code != MSP_SUCCESS)
95    {
96        snprintf(sse_hints, sizeof(sse_hints), "QIVWRegisterNotify errorCode=%d", err_code);
97        printf("QIVWRegisterNotify failed! error code:%d\n",err_code);
98        goto exit;
99    }
100
101    //1.create recorder
102    err_code = create_recorder(&recorder, record_data_cb, (void*)session_id);
103    if (recorder == NULL || err_code != 0)
104    {
105            printf("create recorder failed: %d\n", err_code);
106            err_code = MSP_ERROR_FAIL;
107            goto exit;
108    }
109
110    //2.open_recorder
111    err_code = open_recorder(recorder, get_default_input_dev(), &wavfmt);
112    if (err_code != 0)
113    {
114        printf("recorder open failed: %d\n", err_code);
115        err_code = MSP_ERROR_FAIL;
116        goto exit;
117    }
118
119    //3.start record
120    err_code = start_record(recorder);
121    if (err_code != 0)
122    {
123        printf("start record failed: %d\n", err_code);
124        err_code = MSP_ERROR_FAIL;
125        goto exit;
126    }
127
128    while(flag)
129    {
130        sleep_ms(1000); //模拟人说话时间间隙,10帧的音频时长为200ms
131        printf("Listening wakeup trigger... \n");
132    }
133    snprintf(sse_hints, sizeof(sse_hints), "success");
134
135exit:
136    if (recorder)
137    {
138        if(!is_record_stopped(recorder))
139            stop_record(recorder);
140
141        close_recorder(recorder);
142        destroy_recorder(recorder);
143        recorder = NULL;
144    }
145
146    if (NULL != session_id)
147    {
148        QIVWSessionEnd(session_id, sse_hints);
149    }
150}
151
152
153int waitAwaken()
154
{
155    int         ret       = MSP_SUCCESS;
156    const char *lgi_param = "appid = 5d5b9efd, work_dir = .";
157    const char *ssb_param = "ivw_threshold=0:2000, sst=wakeup, ivw_res_path=fo|res/ivw/wakeupresource.jet";
158
159    ret = MSPLogin(NULLNULL, lgi_param);
160    if(MSP_SUCCESS != ret)
161    {
162        printf("Awaken MSPLogin failed, error code: %d.\n", ret);
163        MSPLogout();  //登录失败,退出登录
164        return -1;
165    }
166
167    run_ivw(ssb_param);
168    MSPLogout();  //退出登录
169
170    //start ASR
171    startASR();
172
173    return 0;
174}

通过waitAwaken()函数我们可以得知,当初始化好登录参数后,就可以来进入监听唤醒词的流程了,这里使用run_ivw()函数来实现。在此函数中,如果没有唤醒词检测回调函数的反馈,那就好一直在while()循环中,只有在cb_ivw_msg_proc()函数中得到了MSP_IVW_MSG_WAKEUP的消息,才好退出while()循环,然后调用startASR()开启后面的命令词检测,代码的整体逻辑流程图如下所示:


0x04 离线命令词识别流程

这里的离线命令词识别其实是ASR功能的子功能,ASR(Automatic Speech Recognition)本意是自动语音识别。它可以检测任意的语音资料,并将其转换成对应的文本文件。在这个将语音文件转换为文本的过程中,就诞生了这个命令词识别的功能。因为我们可以设计语法文件,不用将任意的语音语料转换为相应的文本,而是只是将我们需要检测的关键字,这样就可以大大减小资源消耗。所以说这里的离线命令词识别,就是ASR功能的一个子功能实现。它只是对特定的语音文件进行检测识别,不过这里我们需要设置好需要检测的语法文件。下面就是命令词检测的源码文件asr.c:

  1/*
2* 语音听写(iFly Auto Transform)技术能够实时地将语音转换成对应的文字。
3*/

4#include <stdlib.h>
5#include <stdio.h>
6#include <string.h>
7#include <errno.h>
8#include <unistd.h>
9#include <wiringPi.h>
10
11#include "qisr.h"
12#include "tts.h"
13#include "msp_cmn.h"
14#include "msp_errors.h"
15#include "speech_recognizer.h"
16
17
18#define FRAME_LEN            640
19#define BUFFER_SIZE          4096
20#define SAMPLE_RATE_16K     (16000)
21#define SAMPLE_RATE_8K      (8000)
22#define MAX_GRAMMARID_LEN   (32)
23#define MAX_PARAMS_LEN      (1024)
24
25#define LED_PIN   26
26#define FAN_PIN   25
27
28#define ON         1
29#define OFF        2
30#define LED_DEVICE 3
31#define FAN_DEVICE 4
32
33static int recEndFlag = 0//是否识别出结果的标志
34static int exitFlag = 0;
35static char *g_result = NULL;
36static unsigned int g_buffersize = BUFFER_SIZE;
37static char outText[100]; //最终要语音合成的文本
38
39const char * ASR_RES_PATH    = "fo|res/asr/common.jet"//离线语法识别资源路径
40const char * GRM_BUILD_PATH  = "res/asr/GrmBuilld"//构建离线语法识别网络生成数据保存路径
41const char * GRM_FILE        = "./msc/control.bnf"//构建离线识别语法网络所用的语法文件
42const char * led_str         = "灯";
43const char * fan_str         = "风扇";
44const char * open_str        = "打开";
45const char * close_str       = "关闭";
46const char * ok_str          = "好的,";
47const char *noCmdStr         = "没有听到您的命令";
48
49typedef struct _UserData {
50    int     build_fini; //标识语法构建是否完成
51    int     update_fini; //标识更新词典是否完成
52    int     errcode; //记录语法构建或更新词典回调错误码
53    char    grammar_id[MAX_GRAMMARID_LEN]; //保存语法构建返回的语法ID
54}UserData;
55
56int build_grammar(UserData *udata)//构建离线识别语法网络
57int run_asr(UserData *udata)//进行离线语法识别
58
59void controlDevice(int deviceID, int flag)
60
{
61    exitFlag = 1//exit asr function
62
63    if(LED_DEVICE == deviceID) //control led light
64    {
65        if(ON == flag) //light on
66        {
67            digitalWrite(LED_PIN, LOW);
68        }
69        else //light off
70        {
71            digitalWrite(LED_PIN, HIGH);
72        }
73    }
74    else //control fan device
75    {
76        if(ON == flag) //fan on
77        {
78            digitalWrite(FAN_PIN, HIGH);
79        }
80        else //fan off
81        {
82            digitalWrite(FAN_PIN, LOW);
83        }
84    }
85}
86
87int build_grm_cb(int ecode, const char *info, void *udata)
88
{
89    UserData *grm_data = (UserData *)udata;
90
91    if (NULL != grm_data) {
92        grm_data->build_fini = 1;
93        grm_data->errcode = ecode;
94    }
95
96    if (MSP_SUCCESS == ecode && NULL != info) {
97        printf("构建语法成功! 语法ID:%s\n", info);
98        if (NULL != grm_data)
99            snprintf(grm_data->grammar_id, MAX_GRAMMARID_LEN - 1, info);
100    }
101    else
102        printf("构建语法失败!%d\n", ecode);
103
104    return 0;
105}
106
107int build_grammar(UserData *udata)
108
{
109    FILE *grm_file                           = NULL;
110    char *grm_content                        = NULL;
111    unsigned int grm_cnt_len                 = 0;
112    int ret                                  = 0;
113    char grm_build_params[MAX_PARAMS_LEN];
114
115    memset(grm_build_params, '\0', MAX_PARAMS_LEN);
116    grm_file = fopen(GRM_FILE, "rb");
117    if(NULL == grm_file) {
118        printf("打开\"%s\"文件失败![%s]\n", GRM_FILE, strerror(errno));
119        return -1;
120    }
121
122    fseek(grm_file, 0, SEEK_END);
123    grm_cnt_len = ftell(grm_file);
124    fseek(grm_file, 0, SEEK_SET);
125
126    grm_content = (char *)malloc(grm_cnt_len + 1);
127    if (NULL == grm_content)
128    {
129        printf("内存分配失败!\n");
130        fclose(grm_file);
131        grm_file = NULL;
132        return -1;
133    }
134    fread((void*)grm_content, 1, grm_cnt_len, grm_file);
135    grm_content[grm_cnt_len] = '\0';
136    fclose(grm_file);
137    grm_file = NULL;
138
139    snprintf(grm_build_params, MAX_PARAMS_LEN - 1,
140        "engine_type = local, \
141        asr_res_path = %s, sample_rate = %d, \
142        grm_build_path = %s, "
,
143        ASR_RES_PATH,
144        SAMPLE_RATE_16K,
145        GRM_BUILD_PATH
146        );
147    ret = QISRBuildGrammar("bnf", grm_content, grm_cnt_len, grm_build_params, build_grm_cb, udata);
148
149    free(grm_content);
150    grm_content = NULL;
151
152    return ret;
153}
154
155static void procResultStr(char *string)
156
{
157    memset(outText, '\0'sizeof(outText));
158
159    strcat(outText, ok_str);
160    char *tmp = strstr(string"input=");
161    strcat(outText, tmp+6);
162}
163
164static void show_result(char *stringchar is_over)
165
{
166    char *check = NULL;
167    int device_id = 0;
168    int operate_id = 0;
169
170    printf("Result: [ %s ]"string);
171    procResultStr(string);
172
173    if(is_over)
174        putchar('\n');
175
176    check = strstr(string, led_str);
177    if(check != NULL)
178    {
179        device_id = 3;
180    }
181    check = strstr(string, fan_str);
182    if(check != NULL)
183    {
184        device_id = 4;
185    }
186    check = strstr(string, open_str);
187    if(check != NULL)
188    {
189       operate_id = 1;
190    }
191    check = strstr(string, close_str);
192    if(check != NULL)
193    {
194        operate_id = 2;
195    }
196
197    if((device_id != 0) && (operate_id != 0))
198    {
199        controlDevice(device_id, operate_id);
200    }
201}
202
203void on_result(const char *result, char is_last)
204
{
205    if (result) {
206        size_t left = g_buffersize - 1 - strlen(g_result);
207        size_t size = strlen(result);
208        if (left < size) {
209            g_result = (char*)realloc(g_result, g_buffersize + BUFFER_SIZE);
210            if (g_result)
211                g_buffersize += BUFFER_SIZE;
212            else {
213                printf("mem alloc failed\n");
214                return;
215            }
216        }
217        strncat(g_result, result, size);
218        show_result(g_result, is_last);
219    }
220}
221void on_speech_begin()
222
{
223    if (g_result)
224    {
225        free(g_result);
226    }
227    g_result = (char*)malloc(BUFFER_SIZE);
228    g_buffersize = BUFFER_SIZE;
229    memset(g_result, 0, g_buffersize);
230}
231
232void on_speech_end(int reason)
233
{
234    recEndFlag = 1;
235
236    if (reason == END_REASON_VAD_DETECT)
237        printf("\nSpeaking done \n");
238    else
239        printf("\nRecognizer error %d\n", reason);
240}
241
242/* demo recognize the audio from microphone */
243static void demo_mic(const char* session_begin_params)
244
{
245    int errcode;
246    struct speech_rec iat;
247    struct speech_rec_notifier recnotifier = {
248        on_result,
249        on_speech_begin,
250        on_speech_end
251    };
252
253    recEndFlag = 0;
254    errcode = sr_init(&iat, session_begin_params, SR_MIC, &recnotifier);
255    if (errcode) {
256        printf("speech recognizer init failed\n");
257        return;
258    }
259    errcode = sr_start_listening(&iat);
260    if (errcode) {
261        printf("start listen failed %d\n", errcode);
262    }
263
264    /*mic always recording */
265    while(1)
266    {
267        printf("listening control command...\n");
268        if(recEndFlag)
269        {
270            break;
271        }
272        sleep(1);
273    }
274    errcode = sr_stop_listening(&iat);
275    if (errcode) {
276        printf("stop listening failed %d\n", errcode);
277    }
278
279    sr_uninit(&iat);
280}
281
282int run_asr(UserData *udata)
283
{
284    char asr_params[MAX_PARAMS_LEN];
285    memset(asr_params, '\0', MAX_PARAMS_LEN);
286
287    //离线语法识别参数设置
288    snprintf(asr_params, MAX_PARAMS_LEN - 1,
289        "engine_type = local, \
290        asr_res_path = %s, sample_rate = %d, \
291        grm_build_path = %s, local_grammar = %s, \
292        result_type = plain, result_encoding = UTF-8"
,
293        ASR_RES_PATH,
294        SAMPLE_RATE_16K,
295        GRM_BUILD_PATH,
296        udata->grammar_id
297        );
298
299    demo_mic(asr_params);
300
301    return 0;
302}
303
304int startASR()
305
{
306    int ret = 0 ;
307    exitFlag = 0;
308    const char *login_config = "appid = 5d5b9efd"//登录参数
309    UserData asr_data;
310
311    wiringPiSetup();
312    pinMode(LED_PIN, OUTPUT);
313    pinMode(FAN_PIN, OUTPUT);
314
315    ret = MSPLogin(NULLNULL, login_config); //第一个参数为用户名,第二个参数为密码,传NULL即可,第三个参数是登录参数
316    if (MSP_SUCCESS != ret)
317    {
318        printf("ASR 登录失败:%d\n", ret);
319        MSPLogout();
320        return -1;
321    }
322
323    memset(&asr_data, 0sizeof(UserData));
324    printf("构建离线识别语法网络...\n");
325    ret = build_grammar(&asr_data);  //第一次使用某语法进行识别,需要先构建语法网络,获取语法ID,之后使用此语法进行识别,无需再次构建
326    if (MSP_SUCCESS != ret)
327    {
328        printf("构建语法调用失败!\n");
329        MSPLogout();
330        return -2;
331    }
332    while (1 != asr_data.build_fini)
333        usleep(300 * 1000);
334    if (MSP_SUCCESS != asr_data.errcode)
335    {
336        MSPLogout();
337        return -3;
338    }
339
340    printf("离线识别语法网络构建完成,开始识别...\n");
341    run_asr(&asr_data);
342    MSPLogout();
343
344    if(exitFlag == 1)
345    {
346        startTTS(outText);
347    }
348    else
349    {
350        startTTS(noCmdStr);
351    }
352
353    return 0;
354}

这里的代码相对于以前的离线命令文章中的代码变化很小,只是将main()函数改为了startASR()。这里的startASR()中的各流程其实跟唤醒中流程类似,第一步仍然是MSPLogin(),因为科大讯飞为了保证你用的SDK库中的libmsc.so需要和Appid配套才行。接下来就是build_grammar(),构建生成我们要识别的语法网络。最后就是调用run_asr()开始进行不断的检测语料文件中是否包含语法网络中的命令词了,下面就是该源码文件的函数调用流程图:

科大讯飞离线命令词识别函数调用流程图

0x05 TTS语音合成代码

这里使用的是科大讯飞的离线语音合成功能,只有两种发音人,一种是男声:xiaofeng,一种女声:xiaoyan。语音合成的API调用过程,其实跟唤醒、ASR差不多。下面我们就来看下TTS部分的源码tts.c:

  1/*
2* 语音合成(Text To Speech,TTS)技术能够自动将任意文字实时转换为连续的
3* 自然语音,是一种能够在任何时间、任何地点,向任何人提供语音信息服务的
4* 高效便捷手段,非常符合信息时代海量数据、动态更新和个性化查询的需求。
5*/

6
7#include <stdlib.h>
8#include <stdio.h>
9#include <unistd.h>
10#include <errno.h>
11#include <string.h>
12#include "qtts.h"
13#include "msp_cmn.h"
14#include "msp_errors.h"
15
16
17typedef int SR_DWORD;
18typedef short int SR_WORD ;
19
20/* wav音频头部格式 */
21typedef struct _wave_pcm_hdr
22{

23    char            riff[4];                // = "RIFF"
24    int             size_8;                 // = FileSize - 8
25    char            wave[4];                // = "WAVE"
26    char            fmt[4];                 // = "fmt "
27    int             fmt_size;               // = 下一个结构体的大小 : 16
28
29    short int       format_tag;             // = PCM : 1
30    short int       channels;               // = 通道数 : 1
31    int             samples_per_sec;        // = 采样率 : 8000 | 6000 | 11025 | 16000
32    int             avg_bytes_per_sec;      // = 每秒字节数 : samples_per_sec * bits_per_sample / 8
33    short int       block_align;            // = 每采样点字节数 : wBitsPerSample / 8
34    short int       bits_per_sample;        // = 量化比特数: 8 | 16
35
36    char            data[4];                // = "data";
37    int             data_size;              // = 纯数据长度 : FileSize - 44
38} wave_pcm_hdr;
39
40/* 默认wav音频头部数据 */
41wave_pcm_hdr default_wav_hdr =
42{
43    { 'R''I''F''F' },
44    0,
45    {'W''A''V''E'},
46    {'f''m''t'' '},
47    16,
48    1,
49    1,
50    16000,
51    32000,
52    2,
53    16,
54    {'d''a''t''a'},
55    0
56};
57/* 文本合成 */
58int text_to_speech(const char* src_text, const char* des_path, const char* params)
59
{
60    int          ret          = -1;
61    FILE*        fp           = NULL;
62    const char*  sessionID    = NULL;
63    unsigned int audio_len    = 0;
64    wave_pcm_hdr wav_hdr      = default_wav_hdr;
65    int          synth_status = MSP_TTS_FLAG_STILL_HAVE_DATA;
66
67    if (NULL == src_text || NULL == des_path)
68    {
69        printf("params is error!\n");
70        return ret;
71    }
72    fp = fopen(des_path, "wb");
73    if (NULL == fp)
74    {
75        printf("open %s error.\n", des_path);
76        return ret;
77    }
78    /* 开始合成 */
79    sessionID = QTTSSessionBegin(params, &ret);
80    if (MSP_SUCCESS != ret)
81    {
82        printf("QTTSSessionBegin failed, error code: %d.\n", ret);
83        fclose(fp);
84        return ret;
85    }
86    ret = QTTSTextPut(sessionID, src_text, (unsigned int)strlen(src_text), NULL);
87    if (MSP_SUCCESS != ret)
88    {
89        printf("QTTSTextPut failed, error code: %d.\n",ret);
90        QTTSSessionEnd(sessionID, "TextPutError");
91        fclose(fp);
92        return ret;
93    }
94    printf("正在合成:%s\n", src_text);
95    fwrite(&wav_hdr, sizeof(wav_hdr) ,1, fp); //添加wav音频头,使用采样率为16000
96    while (1)
97    {
98        /* 获取合成音频 */
99        const void* data = QTTSAudioGet(sessionID, &audio_len, &synth_status, &ret);
100        if (MSP_SUCCESS != ret)
101            break;
102        if (NULL != data)
103        {
104            fwrite(data, audio_len, 1, fp);
105            wav_hdr.data_size += audio_len; //计算data_size大小
106        }
107        if (MSP_TTS_FLAG_DATA_END == synth_status)
108            break;
109    }
110    if (MSP_SUCCESS != ret)
111    {
112        printf("QTTSAudioGet failed, error code: %d.\n",ret);
113        QTTSSessionEnd(sessionID, "AudioGetError");
114        fclose(fp);
115        return ret;
116    }
117    /* 修正wav文件头数据的大小 */
118    wav_hdr.size_8 += wav_hdr.data_size + (sizeof(wav_hdr) - 8);
119
120    /* 将修正过的数据写回文件头部,音频文件为wav格式 */
121    fseek(fp, 40);
122    fwrite(&wav_hdr.size_8,sizeof(wav_hdr.size_8), 1, fp); //写入size_8的值
123    fseek(fp, 400); //将文件指针偏移到存储data_size值的位置
124    fwrite(&wav_hdr.data_size,sizeof(wav_hdr.data_size), 1, fp); //写入data_size的值
125    fclose(fp);
126    fp = NULL;
127
128    /* 合成完毕 */
129    ret = QTTSSessionEnd(sessionID, "Normal");
130    if (MSP_SUCCESS != ret)
131    {
132        printf("QTTSSessionEnd failed, error code: %d.\n",ret);
133    }
134
135    return ret;
136}
137
138int startTTS(char *text)
139
{
140    int ret = MSP_SUCCESS;
141    const char* login_params  = "appid = 5d5b9efd, work_dir = .";//登录参数,appid与msc库绑定,请勿随意改动
142    /*
143    * rdn:           合成音频数字发音方式
144    * volume:        合成音频的音量
145    * pitch:         合成音频的音调
146    * speed:         合成音频对应的语速
147    * voice_name:    合成发音人
148    * sample_rate:   合成音频采样率
149    * text_encoding: 合成文本编码格式
150    */

151    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";
152    const char* filename = "tts_sample.wav"//合成的语音文件名称
153
154    /* 用户登录 */
155    ret = MSPLogin(NULLNULL, login_params); //第一个参数是用户名,第二个参数是密码,第三个参数是登录参数,用户名和密码可在http://www.xfyun.cn注册获取
156    if (MSP_SUCCESS != ret)
157    {
158        printf("TTS MSPLogin failed, error code: %d.\n", ret);
159        goto exit//登录失败,退出登录
160    }
161
162    /*语音合成*/
163    ret = text_to_speech(text, filename, session_begin_params);
164    if (MSP_SUCCESS != ret)
165    {
166        printf("text_to_speech failed, error code: %d.\n", ret);
167    }
168    printf("合成完毕,开始播放合成语音...\n");
169    system("play -q --multi-threaded tts_sample.wav");
170
171exit:
172    MSPLogout(); //退出登录
173    return 0;
174}

通过查看上述源码,我们就可以发现其实这里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小课堂的微信群,与更多志同道合的小伙伴一起学习和交流!

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据