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

0x00 智能家居控制简介

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

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

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

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

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

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

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

本次课程的代码,我是放在了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文件的流程图,很简单:

8.使用科大讯飞语音实现智能家居控制 - 第3张
语音交互系统的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()开启后面的命令词检测,代码的整体逻辑流程图如下所示:

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

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()开始进行不断的检测语料文件中是否包含语法网络中的命令词了,下面就是该源码文件的函数调用流程图:

8.使用科大讯飞语音实现智能家居控制 - 第5张
科大讯飞离线命令词识别函数调用流程图

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实现的流程:

8.使用科大讯飞语音实现智能家居控制 - 第6张
科大讯飞离线语音合成函数调用流程图

0x06 语音系统整体函数调用流程

前面几个小节我们依次介绍了语音系统的唤醒、语音识别、语音合成是如何实现的。分开看整体流程感觉还是没有一个全局感觉,现在我们就可以将上述各模块的函数调用流程图整合在一起。这样大家就可以对整体代码的实现有个整体认识了,下面是整合在一起的函数调用流程图,如下图所示:

8.使用科大讯飞语音实现智能家居控制 - 第7张
语音系统整体实现流程图

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]

本文原创,作者:corvin_zhang,其版权均为ROS小课堂所有。
如需转载,请注明出处:https://www.corvin.cn/1722.html