1.安装和调试

1.1 进入科大迅飞开放平台

1.2 创建一个应用,我的 是ros机器人123      

1.3 下载SDK,选linux,彩色表示选中,语音听写有次数限制一天500次,而且在线,选离线命令识别,测试中        

 

1.4 测试语音合成是否下载成功

先下载mplayer播放器

sudo apt-get install mplayer


到tts_offline_sample目录下(离线和在线名字不一样)

cd ~/SoftWare/samples/tts_offline_sample/

 

source 64bit_make.sh

 

make


将“64bit_make.sh”这个文件夹拷贝到bin目录下,我们后面需要使用到

cp 64bit_make.sh ../../bin/


回到bin目录下

 cd ~/SoftWare/bin/
./tts_offline_sample


此时我们可以看到编译完成,而且bin目录下多了一个WAV文件
 

play tts_offline_sample.wav

此时,我们需要播m放WAV文件的内容,在此之前需要下在SOX,安装完后才能在可以用命令行来播放

sudo apt install sox
play tts_offline_sample.wav


在此,就能听到声音了,但是关掉命令窗口后重新执行tts_samples会发现libmsc.so找不到,主要是因为没有source,路径找不到
但是为以后不用每次都要source所以我们需要进行一下步骤

cd ~/SoftWare/libs/x64
sudo cp libmsc.so /usr/lib

这样以后就不用每次都source了,当我们在bin目录下再执行./tts_samples就不会出错了
同时我们需要在tts_offline_samples中修改Makefile文件,在文件中加入” $(DIR_BIN)/*.wav“使每次make clean都会删除之前的WAV文件

vim ~/SoftWare/samples/tts_offline_sample/Makefile
#common makefile header
 
DIR_INC = ../../include
DIR_BIN = ../../bin
DIR_LIB = ../../libs
 
TARGET	= tts_sample
BIN_TARGET = $(DIR_BIN)/$(TARGET)
 
CROSS_COMPILE = 
CFLAGS = -g -Wall -I$(DIR_INC)
 
 
LDFLAGS := -L$(DIR_LIB)/x64
LDFLAGS += -lmsc -lrt -ldl -lpthread -lstdc++
 
OBJECTS := $(patsubst %.c,%.o,$(wildcard *.c))
 
$(BIN_TARGET) : $(OBJECTS)
	$(CROSS_COMPILE)gcc $(CFLAGS) $^ -o $@ $(LDFLAGS)
 
%.o : %.c
	$(CROSS_COMPILE)gcc -c $(CFLAGS) $< -o $@
clean:
	@rm -f *.o $(BIN_TARGET) $(DIR_BIN)/*.wav
 
.PHONY:clean
 
#common makefile foot

现在再次运行就可以听到声音

 

yys@yys:~/SoftWare/bin$ ./tts_offline_sample

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

开始合成 ...
正在合成 ...

合成完毕
按任意键退出 ...

yys@yys:~/SoftWare/bin$ play tts_sample.wav 
play WARN alsa: can't encode 0-bit Unknown or not applicable

tts_sample.wav:

 File Size: 498k      Bit Rate: 256k
  Encoding: Signed PCM    
  Channels: 1 @ 16-bit   
Samplerate: 16000Hz      
Replaygain: off         
  Duration: 00:00:15.57  

In:100%  00:00:15.57 [00:00:00.00] Out:249k  [      |      ]        Clip:0    
Done.

2.ros离线语音合成部署

cd catkin_ws/src/
catkin_create_pkg voice_system std_msgs rospy roscpp

把科大迅飞包里的代码复制到你创建的包里,并重命名为xf_tts.cpp

cp ~/SoftWare/samples/tts_offline_sample/tts_offline_sample.c ~/catkin_ws/src/voice_system/src/xf_tts.cpp

修改xf_tts.cpp文件(注意要把appid改为自己的appid,gtts.h,.jet,等路径要改为自己该文件的路径)

vim /home/yys/catkin_ws/src/voice_system/src/xf_tts.cpp
/*

* 语音合成(Text To Speech,TTS)技术能够自动将任意文字实时转换为连续的

* 自然语音,是一种能够在任何时间、任何地点,向任何人提供语音信息服务的

* 高效便捷手段,非常符合信息时代海量数据、动态更新和个性化查询的需求。

*/

 

#include <stdlib.h>

#include <stdio.h>

#include <unistd.h>

#include <errno.h>

#include <ros/ros.h>

#include <std_msgs/String.h>

#include "/home/yys/SoftWare/include/qtts.h"

#include "/home/yys/SoftWare/include/msp_cmn.h"

#include "/home/yys/SoftWare/include/msp_errors.h"

const char* fileName="/home/yys/voice.wav";

const char* playPath="play /home/yys/voice.wav";

 

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("正在合成 ...\n");

	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;

	}

	printf("\n");

	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 makeTextToWav(const char* text, const char* filename){

int         ret                  = MSP_SUCCESS;

	const char* login_params         = "appid = 5f6c1268, work_dir = .";//登录参数,appid与msc库绑定,请勿随意改动

	/*

	* rdn:           合成音频数字发音方式

	* volume:        合成音频的音量

	* pitch:         合成音频的音调

	* speed:         合成音频对应的语速

 

	* voice_name:    合成发音人

	* sample_rate:   合成音频采样率

	* text_encoding: 合成文本编码格式

	*

 

	*/

	const char* session_begin_params = "engine_type = local,voice_name=xiaoyan, text_encoding = UTF8, tts_res_path = fo|/home/yys/SoftWare/bin/msc/res/tts/xiaoyan.jet;fo|/home/yys/SoftWare/bin/msc/res/tts/common.jet, sample_rate = 16000, speed = 50, volume = 50, pitch = 50, rdn = 0";

	

	/* 用户登录 */

	ret = MSPLogin(NULL, NULL, login_params); //第一个参数是用户名,第二个参数是密码,第三个参数是登录参数,用户名和密码可在http://www.xfyun.cn注册获取

	if (MSP_SUCCESS != ret)

	{

		printf("MSPLogin failed, error code: %d.\n", ret);

		

	}

        else{

	printf("开始合成 ...\n");

	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");

        }

        MSPLogout();

   	return 0;

 

}

void playWav()

{

	system(playPath);

    

}

void topicCallBack(const std_msgs::String::ConstPtr& msg)

{

	std::cout<<"get topic text:" << msg->data.c_str();

	makeTextToWav(msg->data.c_str(),fileName);

	playWav();	

}

 

int main(int argc, char* argv[]){

	const char* start= "科大迅飞在线语音合成模块启动";

	makeTextToWav(start,fileName);

	playWav();

 

	ros::init(argc,argv, "xf_tts_node");	

        ros::NodeHandle n;

        ros::Subscriber sub = n.subscribe("/voice/xf_tts_topic", 3,topicCallBack);

	ros::spin();

	return 0;

}

注意:科大迅飞接在ros上进行语音识别遇到的问题,科大迅飞10102错误

找到jet的路径 (换成绝对路径一般为/home/yys/SoftWare/bin/msc/res/tts)

修改如下:xiaoyan是选择发音的人

const char* session_begin_params = "engine_type = local,voice_name=xiaoyan, text_encoding = UTF8, tts_res_path = fo|/home/yys/SoftWare/bin/msc/res/tts/xiaoyan.jet;fo|/home/yys/SoftWare/bin/msc/res/tts/common.jet, sample_rate = 16000, speed = 50, volume = 50, pitch = 50, rdn = 0";

 在CMakeList文件的“include_directories”后加入“include”,在文件末尾加入

vim /catkin_ws/src/voice_system/CMakeLists.txt
add_executable(xf_tts_node src/xf_tts.cpp)
target_link_libraries(xf_tts_node ${catkin_LIBRARIES}   -lmsc -lrt -ldl -lpthread)

编译

cd ~/catkin_ws/
catkin_make
rosrun voice_system xf_tts_node

你能听到,“科大迅飞语音模块启动”的声音“ 

rostopic pub /voice/xf_tts_topic std_msgs/String 哈哈

会听到哈哈声音,语音合成成功

3.离线命令识别

 

unzip -q Linux_aisound_exp1227_aitalk_exp1227_awaken_exp1227_5f6c1268.zip -d SoftWare/
cd SoftWare/

 

yys@yys:~/SoftWare$ tree -L 2
.
├── bin  
│   ├── asr_offline_record_sample
│   ├── audio
│   ├── call.bnf  巴科斯范式格式的语法文件
│   ├── control.bnf
│   ├── msc
│   └── wav
├── doc
│   ├── BNF Grammar Development Manual.pdf 巴科斯范式格式的语法开发手册
│   └── readme.txt
├── include 离线命合词识别重要的头文件是msp_cmn.h和qisr.h
│   ├── msp_cmn.h  通用接口(Mobile Speech Platform Common Interface Header File)
│   ├── msp_errors.h  
│   ├── msp_types.h 
│   ├── qisr.h 语音识别(iFLY Speech Recognizer Header File)
│   ├── qivw.h 语音唤醒(iFLY Speech Voice Wakeup Header File)
│   └── qtts.h 语音合成(iFLY Speech Synthesizer Header Fil
├── libs
│   ├── x64
│   └── x86
├── README
├── release.txt
├── samples
│   ├── asr_offline_record_sample  从麦克风识别离线命令
│   ├── asr_offline_sample 从语音文件识别离线命令
│   ├── awaken_offline_sample 唤醒
│   └── tts_offline_sample 离线语音合成
└── wordlist.txt

14 directories, 14 files

动态库移动到/usr/lib,占一个装机量(默认64位)

sudo mv ~/SoftWare/libs/x64/libmsc.so /usr/lib
cd ~/SoftWare/samples/asr_offline_record_sample
yys@yys:~/SoftWare/samples$ tree
.
├── asr_offline_record_sample  从麦克风识别离线命令
│   ├── asr_offline_record_sample.c 离线命令识别入口代码
│   ├── asr_offline_record_sample.o
│   ├── formats.h
│   ├── linuxrec.c  录音文件
│   ├── linuxrec.h  录音文件
│   ├── linuxrec.o
│   ├── Makefile
│   ├── make.sh
│   ├── speech_recognizer.c 进行识别代码
│   ├── speech_recognizer.h 进行识别代码
│   └── speech_recognizer.o
├── asr_offline_sample
│   ├── 32bit_make.sh
│   ├── 64bit_make.sh
│   ├── asr_offline_sample.c
│   └── Makefile
├── awaken_offline_sample
│   ├── 32bit_make.sh
│   ├── 64bit_make.sh
│   ├── awaken_offline_sample.c
│   └── Makefile
└── tts_offline_sample
    ├── 32bit_make.sh
    ├── 64bit_make.sh
    ├── Makefile
    └── tts_offline_sample.c

4 directories, 23 files
source 64bit_make.sh

 

报错

linuxrec.c:12:10: fatal error: alsa/asoundlib.h: 没有那个文件或目录
 #include <alsa/asoundlib.h>
          ^~~~~~~~~~~~~~~~~~

sudo apt install libasound2-dev

 

如果电脑第一次装科大迅飞要移动/libs/x64下面libmsc.so移动到/usr/lib下

sudo cp libmsc.so /usr/lib

但是在执行之前,我们需要认识一下这个巴科斯范式格式的语法文件,因为这个语法文件就是我们后面要离线识别的命令词了

 

cd ~/SoftWare/bin

 

cat call.bnf

#BNF+IAT 1.0 UTF-8;

!grammar call;

!slot <want>;

!slot <dialpre>;

!slot <dialsuf>;

!slot <contact>;

 

!start <callstart>;

<callstart>:[<want>]<dial>;

<want>:我想|我要||帮我|我想要|请帮我;

<dial>:<dialpre><contact>[<dialsuf>];

<dialpre>:打电话给!id(10001)|打给!id(10001)|拨打!id(10001)|拨打电话给!id(10001)|呼叫!id(10001)|

打一个电话给!id(10001)|打个电话给!id(10001)||拨通!id(10001)|

接通!id(10001)|呼叫!id(10001)|呼叫给!id(10001)|!id(10001);

<dialsuf>:打电话!id(10001)|打个电话!id(10001)|打一个电话!id(10001)|

拨打电话!id(10001)|拨电话!id(10001)|拨个电话!id(10001)|呼个电话!id(10001)|

的电话!id(10001)|的号码!id(10001)|的手机!id(10001)|

的办公电话!id(10001)|的移动号码!id(10001)|的联通号码!id(10001)|

的电信号码!id(10001)|客服电话!id(10001);

<contact>:丁伟;

就是使用巴科斯范式实现的语法文件,最终上述语法文件将检测的是[<want>]<dial>这样的语音。也就是说<want>规则中的:我想、我要、请、帮我...,这些可以说也可以不说。但是<dial>这个规则必须要说,而<dial>规则是由<dialpre><contact>[<dialsuf>]组成的。这个dialpre是必须要说的,它将检测用户有没有说出来:打电话给、打给、拨打、呼叫....这些语音。后面的<contact>就是检测的联系人,这里的联系人只有一个"丁伟"。最后的这个<dialsuf>是可说可不说,这是因为我们平时要给某人打电话的语法就是:“打电话给丁伟”这样的语法,当然还可以换另外一种表达方式,那就是“拨打丁伟的电话”,所以这里是两种语法规则都支持的。

运行程序

./asr_offline_record_sample

 

在了解了语法文件后,我们还需要查看下示例源码,因为这里的语法文件中的规则,我们是可以动态的更新的。就是说我们不用重新启动程序,就可以实时的修改检测的语法规则,更新语法规则的API如下:

那接下来看一下在asr_offline_record_sample.c源码中是如何更新本次语法词典文件的,调用该API的代码如下:

    int update_lex_cb(int ecode, const char *info, void *udata)
    {
        UserData *lex_data = (UserData *)udata;
        if (NULL != lex_data) {
            lex_data->update_fini = 1;
            lex_data->errcode = ecode;
        }
        if (MSP_SUCCESS == ecode)
            printf("更新词典成功!\n");
        else
            printf("更新词典失败!%d\n", ecode);
        return 0;
    }
    int update_lexicon(UserData *udata)
    {
        const char *lex_content                   = "丁伟\n黄辣椒";
        unsigned int lex_cnt_len                  = strlen(lex_content);
        char update_lex_params[MAX_PARAMS_LEN]    = {NULL}; 
        snprintf(update_lex_params, MAX_PARAMS_LEN - 1, 
            "engine_type = local, text_encoding = UTF-8, \
            asr_res_path = %s, sample_rate = %d, \
            grm_build_path = %s, grammar_list = %s, ",
            ASR_RES_PATH,
            SAMPLE_RATE_16K,
            GRM_BUILD_PATH,
            udata->grammar_id);
        return QISRUpdateLexicon(LEX_NAME, lex_content, lex_cnt_len, update_lex_params, update_lex_cb, udata);
    }

可以看到在update_lexicon()函数中,将lex_content变量赋值为“丁伟\n黄辣椒”,那就是将在call.bnf语法文件中的<contact>规则,从“丁伟”更新到了“丁伟\n黄辣椒”。意思是说,我们在第一次识别的时候只能识别到“打电话给丁伟”这样的句子,如果没有更新参数之前说“打电话给黄辣椒”是无法识别的,因为默认的call.bnf语法文件中只有“丁伟”。但是当我们在update_lexicon()函数中调用了QISRUpdateLexicon()函数后,我们就可以将<contact>变量更新为“丁伟\n黄辣椒”了。 

 

0x04 实现自定义的命令词识别

例如,开収一个简单的语音拨号应用,可以定义一个如下的语法:
#BNF+IAT 1.0;
!grammar call;
!slot <name>;
!start <commands>;
<commands>:(找一下|打电话给) <name>;
<name>: 张三|李四;
找一下张三
打电话给张三
找一下李四
打电话给李四
 
该语法使识别引擎可以支持以下说法:凡是用户说出这个范围中的任意一句话,均可以被识别系统识别。如果用户说的话不在上述范围 之类,识别系统可能拒绝识别。可见语法使用一种结构描述了用户可能说出的语言范围和构成模式。
定义
记号,对应英文为 Token,其描述了用户语音所对应的文本内容,类似于“说法“,如“中国|美国表示支持中国不美国两个记号的并列。
语义,对应英文为 Sementic,表示用户说法所对应的用户所关心的内容,在应用开发中, 可以将部分语义内置于语法文本中,以斱便应用程序的处理。如在语音拨号的场景中,记 号对应为“火警”,而语义为“119”。通过在语法中定义“火警”的语义,当用户说法为 “火警”时,识别引擎将“119”返回给应用程序,而无须在应用程序内部再进行文本的解释工作,大大方便了应用程序的开发。
规则,规则定义了一系列记号及其相互关系的集合,且可以包含其它子规则。通过指定规 则的唯一的名称,使其它的规则可以通过名称引用该规则。
槽,槽是一种特殊的规则。槽描述了一系列记号的并列关系,且丌包含任何子规则。利用 Aitalk SDK,用户可以在程序运行过程中实时增减槽中的记号。借用此功能,用户通过定义槽,描述语法中的频繁变化的内容,如通讯录中的人名,而无须更改语法。
 
我的小车
#BNF+IAT 1.0 UTF-8;
!grammar control;
!slot <devices>;
!slot <operate>;
!start <cmdstart>;
<cmdstart>:<operate><devices>;
<operate>:停止!id(0)|前进!id(1)|后退!id(2)|左拐!id(3)|右拐!id(4);
<devices>:小车!id(10);

 

vim ~/SoftWare/samples/asr_offline_record_sample/asr_offline_record_sample.c

不用每次选0和1

        printf("从MIC说话\n");
        demo_mic(asr_params);
        return 0;

在更新离线语法词典完成,开始识别...\n加入while(1)可以无限循环

	printf("更新离线语法词典完成,开始识别...\n");
	while(1){
	
		ret = run_asr(&asr_data);
		if (MSP_SUCCESS != ret) {
			printf("离线语法识别出错: %d \n", ret);
			goto exit;
		}
	}

每次说话后显示最后结果

static void show_result(char *string, char is_over)
{
        printf("\rResult: [ %s ]", string);
        if(is_over)
                putchar('\n');
}

暂时先安装一个能说话的,

sudo apt install espeak

修改后

/*
* 语音听写(iFly Auto Transform)技术能够实时地将语音转换成对应的文字。
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#include "../../include/qisr.h"
#include "../../include/msp_cmn.h"
#include "../../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)

const char * ASR_RES_PATH        = "fo|res/asr/common.jet"; //离线语法识别资源路径
const char * GRM_BUILD_PATH      = "res/asr/GrmBuilld"; //构建离线语法识别网络生成数据保存路径
const char * GRM_FILE            = "/home/yys/xf/bin/call.bnf"; //构建离线识别语法网络所用的语法文件
const char * LEX_NAME            = "contact"; //更新离线识别语法的contact槽(语法文件为此示例中使用的call.bnf)
const char * stop                = "停止";
const char * go                  = "前进";
const char * back                = "后退";
const char * turnleft            = "左转";
const char * turnright           = "右转";

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 update_lexicon(UserData *udata); //更新离线识别语法词典
int run_asr(UserData *udata); //进行离线语法识别
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;
	char grm_build_params[MAX_PARAMS_LEN]    = {NULL};
	int ret                                  = 0;

	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;
}

int update_lex_cb(int ecode, const char *info, void *udata)
{
	UserData *lex_data = (UserData *)udata;

	if (NULL != lex_data) {
		lex_data->update_fini = 1;
		lex_data->errcode = ecode;
	}

	if (MSP_SUCCESS == ecode)
		printf("更新词典成功!\n");
	else
		printf("更新词典失败!%d\n", ecode);

	return 0;
}

int update_lexicon(UserData *udata)
{
	const char *lex_content                   = "丁伟\n小车";
	unsigned int lex_cnt_len                  = strlen(lex_content);
	char update_lex_params[MAX_PARAMS_LEN]    = {NULL}; 

	snprintf(update_lex_params, MAX_PARAMS_LEN - 1, 
		"engine_type = local, text_encoding = UTF-8, \
		asr_res_path = %s, sample_rate = %d, \
		grm_build_path = %s, grammar_list = %s, ",
		ASR_RES_PATH,
		SAMPLE_RATE_16K,
		GRM_BUILD_PATH,
		udata->grammar_id);
	return QISRUpdateLexicon(LEX_NAME, lex_content, lex_cnt_len, update_lex_params, update_lex_cb, udata);
}





static void show_result(char *string, char is_over)
{
	char check = NULL;
	printf("\r结果:\n [ %s ]", string);
	if(is_over)
		putchar('\n');

    check = strstr(string, stop);
    	if(check != NULL)
    	{
           printf("停止");	
	   system("python3 /home/yys/car.py '0'");
	   exit(0);

    	}
	check = strstr(string, go);
    	if(check != NULL)
    	{
           printf("为前进");	
	   system("python3 /home/yys/car.py '1'");
	   exit(0);

    	}
	check = strstr(string, back);
    	if(check != NULL)
    	{
           printf("为后退");	
	   system("python3 /home/yys/car.py '2'");
	   exit(0);
    	}
	check = strstr(string, turnleft);
    	if(check != NULL)
    	{
           printf("为左转");	
	   system("python3 /home/yys/car.py '3'");
	   exit(0);
    	}
	check = strstr(string, turnright);
    	if(check != NULL)
    	{
           printf("为右转");	
	   system("python3 /home/yys/car.py '4'");
	   exit(0);
    	}
}

static char *g_result = NULL;
static unsigned int g_buffersize = BUFFER_SIZE;

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);

	printf("Start Listening...\n");
}
void on_speech_end(int reason)
{
	if (reason == END_REASON_VAD_DETECT)
		printf("\nSpeaking done \n");
	else
		printf("\nRecognizer error %d\n", reason);
}

/* demo send audio data from a file */
static void demo_file(const char* audio_file, const char* session_begin_params)
{
	int	errcode = 0;
	FILE*	f_pcm = NULL;
	char*	p_pcm = NULL;
	unsigned long	pcm_count = 0;
	unsigned long	pcm_size = 0;
	unsigned long	read_size = 0;
	struct speech_rec iat;
	struct speech_rec_notifier recnotifier = {
		on_result,
		on_speech_begin,
		on_speech_end
	};

	if (NULL == audio_file)
		goto iat_exit;

	f_pcm = fopen(audio_file, "rb");
	if (NULL == f_pcm)
	{
		printf("\nopen [%s] failed! \n", audio_file);
		goto iat_exit;
	}

	fseek(f_pcm, 0, SEEK_END);
	pcm_size = ftell(f_pcm);
	fseek(f_pcm, 0, SEEK_SET);

	p_pcm = (char *)malloc(pcm_size);
	if (NULL == p_pcm)
	{
		printf("\nout of memory! \n");
		goto iat_exit;
	}

	read_size = fread((void *)p_pcm, 1, pcm_size, f_pcm);
	if (read_size != pcm_size)
	{
		printf("\nread [%s] error!\n", audio_file);
		goto iat_exit;
	}

	errcode = sr_init(&iat, session_begin_params, SR_USER, &recnotifier);
	if (errcode) {
		printf("speech recognizer init failed : %d\n", errcode);
		goto iat_exit;
	}

	errcode = sr_start_listening(&iat);
	if (errcode) {
		printf("\nsr_start_listening failed! error code:%d\n", errcode);
		goto iat_exit;
	}

	while (1)
	{
		unsigned int len = 10 * FRAME_LEN; /* 200ms audio */
		int ret = 0;

		if (pcm_size < 2 * len)
			len = pcm_size;
		if (len <= 0)
			break;

		ret = sr_write_audio_data(&iat, &p_pcm[pcm_count], len);

		if (0 != ret)
		{
			printf("\nwrite audio data failed! error code:%d\n", ret);
			goto iat_exit;
		}

		pcm_count += (long)len;
		pcm_size -= (long)len;		
	}

	errcode = sr_stop_listening(&iat);
	if (errcode) {
		printf("\nsr_stop_listening failed! error code:%d \n", errcode);
		goto iat_exit;
	}

iat_exit:
	if (NULL != f_pcm)
	{
		fclose(f_pcm);
		f_pcm = NULL;
	}
	if (NULL != p_pcm)
	{
		free(p_pcm);
		p_pcm = NULL;
	}

	sr_stop_listening(&iat);
	sr_uninit(&iat);
}

/* demo recognize the audio from microphone */
static void demo_mic(const char* session_begin_params)
{
	int errcode;
	int i = 0;

	struct speech_rec iat;

	struct speech_rec_notifier recnotifier = {
		on_result,
		on_speech_begin,
		on_speech_end
	};

	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);
	}
	/* demo 15 seconds recording */
	while(i++ < 15)
		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]    = {NULL};
	const char *rec_rslt               = NULL;
	const char *session_id             = NULL;
	const char *asr_audiof             = NULL;
	FILE *f_pcm                        = NULL;
	char *pcm_data                     = NULL;
	long pcm_count                     = 0;
	long pcm_size                      = 0;
	int last_audio                     = 0;

	int aud_stat                       = MSP_AUDIO_SAMPLE_CONTINUE;
	int ep_status                      = MSP_EP_LOOKING_FOR_SPEECH;
	int rec_status                     = MSP_REC_STATUS_INCOMPLETE;
	int rss_status                     = MSP_REC_STATUS_INCOMPLETE;
	int errcode                        = -1;
	int aud_src                        = 0;
	//离线语法识别参数设置
	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 = xml, result_encoding = UTF-8, ",
		ASR_RES_PATH,
		SAMPLE_RATE_16K,
		GRM_BUILD_PATH,
		udata->grammar_id
		);
	printf("从MIC说话\n");
	//第一次监听说话
	system("espeak -v zh '开始说话'");
	demo_mic(asr_params);
	return 0;
}

int main(int argc, char* argv[])
{
	const char *login_config    = "appid = 5f6c1268"; //登录参数
	UserData asr_data; 
	int ret                     = 0 ;
	char c;

	ret = MSPLogin(NULL, NULL, login_config); //第一个参数为用户名,第二个参数为密码,传NULL即可,第三个参数是登录参数
	if (MSP_SUCCESS != ret) {
		printf("登录失败:%d\n", ret);
		goto exit;
	}

	memset(&asr_data, 0, sizeof(UserData));
	printf("构建离线识别语法网络...\n");
	ret = build_grammar(&asr_data);  //第一次使用某语法进行识别,需要先构建语法网络,获取语法ID,之后使用此语法进行识别,无需再次构建
	if (MSP_SUCCESS != ret) {
		printf("构建语法调用失败!\n");
		goto exit;
	}
	while (1 != asr_data.build_fini)
		usleep(300 * 1000);
	if (MSP_SUCCESS != asr_data.errcode)
		goto exit;
	printf("离线识别语法网络构建完成,开始识别...\n");	
	ret = run_asr(&asr_data);
	if (MSP_SUCCESS != ret) {
		printf("离线语法识别出错: %d \n", ret);
		goto exit;
	}

	printf("更新离线语法词典...\n");
	ret = update_lexicon(&asr_data);  //当语法词典槽中的词条需要更新时,调用QISRUpdateLexicon接口完成更新
	if (MSP_SUCCESS != ret) {
		printf("更新词典调用失败!\n");
		goto exit;
	}
	while (1 != asr_data.update_fini)
		usleep(300 * 1000);
	if (MSP_SUCCESS != asr_data.errcode)
		goto exit;
	printf("更新离线语法词典完成,开始识别...\n");
	while(1){
	
	ret = run_asr(&asr_data);
	if (MSP_SUCCESS != ret) {
		printf("离线语法识别出错: %d \n", ret);
		goto exit;
	}
	}

exit:
	MSPLogout();
	printf("请按任意键退出...\n");
	getchar();
	return 0;
}



4.语音唤醒(未成功循环监听)

0x01 下载语音唤醒SDK

我们在使用讯飞的唤醒引擎服务之前,需要前往讯飞开放平台注册一个账户。然后登录账户后就可以前往自己的控制台,创建一个应用。创建这个应用以后,我们就可以得到一个调用科大讯飞各项语音服务的AppID。这个AppID是很重要的,它会跟我们后面下载的各项语音服务是打包在一起使用的。也就是说你下载的语音SDK必须要搭配着你的AppID来使用,否则可能调用就会出错。那注册科大讯飞开放平台账户是比较简单的,这里不再做介绍,大家可以直接去下面的讯飞开放平台上注册即可:

https://www.xfyun.cn/

那现在注册账户的话,还是会有各种免费的资源服务可以使用,官网页面的可以免费使用的服务如下:

那注册账户后,我们就可以登录自己的控制台。这里要做的第一件事就是“创建新应用”,然后就可以准备使用“语音唤醒”服务了:

0x02 测试语音唤醒效果

当我们下载好自己设置的唤醒词SDK后,我们就可以将该SDK发送到树莓派板上。然后就可以解压,查看源码了。我们可以在本地电脑上使用如下命令,将下载的SDK发送到树莓派上:

scp Linux_awaken1226_5d5b9efd.zip corvin@192.168.*.*:~/

这里需要注意的是后面的IP地址,大家需要根据自己树莓派的IP地址来修改这条命令就可以了。执行完这条命令就可以将文件传输到树莓派的home目录下,注意我这里的树莓派系统用户名是corvin。这里需要注意的就是解压zip文件的命令,完整解压命令如下:

unzip -q Linux_awaken1226_5d5b9efd.zip -d xf_awaken/

接下来最重要的一件事就是来替换默认代码中提供的唤醒库了,因为默认提供的都是x86版本的,都是在我们的台式机这样电脑上使用的。

 

开始编译

 

source 64bit_make.sh

进入编译后bin文件夹

cd ~/SoftWare/bin
./awaken_offline_sample

###############################################################################################################
## 请注意,唤醒语音需要根据唤醒词内容自行录制并重命名为宏IVW_AUDIO_FILE_NAME所指定名称,存放在bin/audio文件里##
###############################################################################################################

QIVWSessionBegin failed! error code:25000

 

这里我们可以从源码中找到我们需要录制的文件格式和文件名,这里打开awaken_offline_sample.c文件可以找到,:

 #define IVW AUDIO FILE NAME "audio/awake. pcm" //这是唤醒程序需要读取的pcm格式语音文件,

 

cd ~/SoftWare/bin/audio

这里我们可以在audio目录中,使用如下命令来录制pcm格式的语料,然后我们就可以来运行程序来检测是否包含有唤醒词了,录制语料的命令如下:

arecord -d 3 -r 16000 -c 1 -t wav -f S16_LE awake.pcm

yys@yys:~/SoftWare/bin/audio$ ls
awake.pcm   //录制好文件
yys@yys:~/SoftWare/bin/audio$ file awake.pcm  //查看文件格式
awake.pcm: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 16000 Hz
yys@yys:~/SoftWare/bin/audio$ play awake.pcm  //听录音
play WARN alsa: can't encode 0-bit Unknown or not applicable

awake.pcm:

 File Size: 96.0k     Bit Rate: 256k
  Encoding: Signed PCM    
  Channels: 1 @ 16-bit   
Samplerate: 16000Hz      
Replaygain: off         
  Duration: 00:00:03.00  

In:100%  00:00:03.00 [00:00:00.00] Out:48.0k [      |      ] Hd:0.0 Clip:0    
Done.

 

这里需要注意的是arecord命令后面的-d参数是表示录制3秒钟,所以我们执行完这条命令后需要立刻说出需要检测的唤醒词,到达3秒后,录音就自动结束了。

当录制好测试语料后,我们就可以来运行唤醒测试程序了。当出现下图所示的日志,就说明唤醒程序已经检测到唤醒词了:

这里需要注意的是唤醒结果中提示的各字段值,各字段值的意义如下图所示:

0x03 修改唤醒程序(失败)

通过上面的测试唤醒过程,大家就可以知道这个demo程序有点不够完善。它无法实现实时的检测唤醒词功能,每次都是要录制好测试语料,然后再运行唤醒检测程序。这样就很不方便了,那我们这里就来修改一下,使该demo程序可以像snowboy测试程序那样,可以实时的检测唤醒词。首先我们来看下原始的唤醒程序的源码:

linux.c

5.snowboy语音唤醒

 

 

 

0x01 下载snowboy代码

我们这款语音板现在就可以使用snowboy这个唤醒引擎,首先来下载snowboy的源码,然后我们就可以在本地进行测试了,下载snowboy的github仓库命令如下:

git clone https://github.com/Kitt-AI/snowboy.git

如果慢可以.我的gitee

git clone https://gitee.com/yang_yongsheng/snowboy.git

下载好代码后,就可以来安装下运行需要的软件了。后面我会把这些缺少的软件都提前安装好在下一版的ROS镜像中,把语音开发环境都配置好,这样后面的同学再使用语音板的时候,就不用这么麻烦来安装这些软件包了,直接就能用snowboy唤醒。这里的安装命令如下:没装python2的

sudo apt-get install python3-pyaudio 

 如果报错,再进行下面两步

sudo apt-get --fix-broken install
sudo apt-get install python-pyaudio python3-pyaudio swig

接下来就是安装python版本的PortAudio,安装命令如下:

pip3 install pyaudio

上述操作过程可以参考如下的截图:

在安装好这些软件后,接下来就可以来验证下是否可以正常录音了,只要语音板麦克风可以正常工作,可以录音了,我们才可以进行后面的操作。完整的测试录音命令如下:

rec test.wav

当在终端中输入完该命令后,就会自动开启录音,我们就可以对着麦克风说几句话。最终会保存到test.wav文件中,最后我们播放出来test.wav看看该文件是否正常,如果正常的话,那我们语音板就是进入正常工作状态了。要想播放一下wav文件,命令也很简单:

play test.wav

0x02 编译唤醒引擎动态库

在这里我们在使用唤醒引擎前,需要编译对应编程语言版本所使用的动态库。由于snowboy的唤醒引擎可以在多种语言环境中使用,我们这里选择python版本,也就是python2.7的版本。当然也可以编译python3的版本,这里编译对应语言版本动态库用到的工具就是swig,这个swig是由Simple Wrapper and Interface Generator的首字母组成,根据这个英文也大概了解swig是干嘛的了。简单来说,swig就是为了将我们使用C/C++编写的代码封装起来,然后可以给其他语言使用的一个工具。这里的snowboy唤醒引擎是使用C/C++开发的,所以python要想使用就需要做下转换了。

这里编译python版本的引擎库也很简单,整个操作过程如下图所示:

如果make不能用安装swig

安裝swig (編譯所需)

乌班图20.04

sudo apt install swig

 

对于乌班图18.04 

 

wget http://hahack-1253537070.file.myqcloud.com/misc/swig-3.0.10.tar.gz

 

tar xvf swig-3.0.10.tar.gz

 

cd swig-3.0.10

 

 

./configure --prefix=/usr --without-clisp --without-maximum-compile-warnings

 

make

 

sudo make install
sudo install -v -m755 -d /usr/share/doc/swig-3.0.10
sudo cp -v -R Doc/* /usr/share/doc/swig-3.0.10

 

安装snowboy

cd ~/snowboy/swig/Python3
sudo apt-get install g++

 

这时候使用make会报错下面错,就执行下面命令

g++ -I../../ -O3 -fPIC -D_GLIBCXX_USE_CXX11_ABI=0 -std=c++0x  -shared snowboy-detect-swig.o \
../..//lib/ubuntu64/libsnowboy-detect.a -L/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu -L/usr/lib -lpython3.6m -lpthread -ldl  -lutil -lm  -Xlinker -export-dynamic -Wl,-O1 -Wl,-Bsymbolic-functions -lm -ldl -lf77blas -lcblas -llapack -latlas -o _snowboydetect.so
/usr/bin/ld: 找不到 -lf77blas
/usr/bin/ld: 找不到 -lcblas
/usr/bin/ld: 找不到 -latlas
collect2: error: ld returned 1 exit status
Makefile:73: recipe for target '_snowboydetect.so' failed
make: *** [_snowboydetect.so] Error 1

进入snowboy/examples/Python3文件夹

sudo apt-get install libatlas-base-dev
make

生成 _snowboydetect.so

然后进入/snowboy/examples/Python3文件夹

cd ~/snowboy/examples/Python3

运性    python3 demo.py ./resources/models/snowboy.umdl会报错

  1. Traceback (most recent call last):
  2. File "demo.py", line 1, in
  3. import snowboydecoder
  4. File "/home/corvin/snowboy/examples/Python3/snowboydecoder.py", line 5, in
  5. from . import snowboydetect
  6. ImportError: attempted relative import with no known parent package

这里的修改也很简单,我们就是要把snowboy/examples/Python3/snowboydecoder.py中第5行中的from . import snowboydetect修改为import snowboydetect就可以了

vim snowboydecoder.py
python3 demo.py ./resources/models/snowboy.umdl

运行成功如下

Listening... Press Ctrl+C to exit
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock

 

最后训练

自己訓練則到 https://snowboy.kitt.ai/dashboard 下載 xxx.pmdl 檔案

yang.pmdl 自己训练的,复制到snowboy/examples/Python3

python3 demo.py yang.pmdl 

在snowboydecoder.py 210多行可以调用条件

 #small state machine to handle recording of phrase after keyword
      ¦   ¦   if state == "PASSIVE":
      ¦   ¦   ¦   if status > 0: #key word found

                    os.system("/home/yys/xf/bin/asr_offline_record_sample")

 

可以在科大迅飞bin文件夹下运行命令

一个是demo文件,一个是训练的文件

python3 /home/yys/snowboy/examples/Python3/demo.py /home/yys/snowboy/examples/Python3/yang.pmdl