概述
本人是基于粤嵌的GEC210和ubuntu14.04开发的,源代码过长,需要的可以去这里下载:
https://download.csdn.net/download/weixin_42116930/10807763
语音交互平台框架:
平台:Linux+armA8+科大讯飞语音识别平台
功能:
1、通过文件检索可以将固定的目录下的三种类型的图片和音乐给检索出来,然后再利用libjpeg库和libpng库来对jpeg图片和png图片进行解码,
再通过直接操作framebuffer来将图片显示在LCD屏上,还可以使用触摸屏来切换图片。而播放音乐就要移植madplay库并使用当中的命令来播放音乐,
也可以使用触摸屏来切换音乐。
2、拍照功能,利用V4L2来实现采集一帧的图像并把它显示在LCD屏上。
3、语言交互功能,首先在客户端实现录音功能,并将录制的音频数据通过socket传输到服务端中,服务端就先进行语法构建然后再进行语法识别,
最后将识别的结果保存在xml文件中,再通过socket将xml文件传输到客户端中,客户端再对这个文件进行解析,并得到识别的id号,
然后再根据id进行相应的操作,如操作上述两个功能。
遇到问题的解决方法:通过debug来调试和上网查找一些资料来分析。
流程图:
打开LCD屏
刷屏刷成白色
眨眼
socket初始化
摄像头初始化
while循环里
触摸
进行语音识别
根据语音识别做出相关操作
退出循环
关闭LCD
客户端:
开机先显示几幅BMP图片,然后建立SOCKET连接,之后调用system函数来开始录音,并将录音好的音频保存为cmd.pcm文件,
向服务器发送我们录好的音频文件cmd.pcm(因为在Linux系统中,一切皆是文件,所以在发送函数里我们是用open打开cmd.pcm文件,
然后读取该文件数据,并将数据保存在我们申请的动态内存中,然后再将之发送给服务端),然后等待服务端那边发送数据过来,
发送过来的是xml结果,然后再将这个数据保存在我们的本地文件中(result.xml),之后就是分析xml文件获得ID号并返回ID号。然后就在根据ID号进行一些其他的工作。
服务端:
先设置登录参数,然后再建立SOCKET连接,然后在while循环里先进行登录,服务端每识别一次都要先登录,识别完后要登出,
所以每次识别的时候都要重复这个过程。(因为识别完一次语音,onResult都会被占用,需要通过登出再登陆才能释放占用资源)。
然后再构建语法网络,获取语法ID。之后进行语法识别,识别完之后最后登出。
其中遇到的困难:
之前进行语音识别的时候识别错误,总是不能够识别,一开始我认为是我们录制的音频的问题,
因为录制的音频不能够播放出来,所以我就往这方面排查,先单独进行录制音频,然后把音频利用socket连接传输到我们的ubuntu上。
一开始录制的是wav音频,利用socket传输到ubuntu上然后使其保存在另一个音频文件,但是奇怪的是服务器虽然接收到了同样大小的数据,
但是却不能播放保存的音频文件,打开音频文件来看看,发现数据非常少,跟原版的不一样,于是发现这里有问题,通过排查,
原因是我在服务器的程序中,用数组来接收数据,这是不行的,具体原因我也不清楚,改成用动态内存来保存发送过来的数据就ok了。
于是这个问题就解决了,音频能够传输了。最后在对比下这个成功的例子,发现原来是我进行语言识别的客户端的发送的程序漏了一行代码,
就是在计算音频文件大小完的时候,忘记把指针移到一开始的地方,所以导致我们发送的数据是空,也就不能识别了。
语音交互平台总结:
1、显示图片
2、要让开发板能够播放音乐,首先开发板得要有声卡驱动,这个取决于内核。由于我是用九鼎制作的内核的,该内核已经有声卡驱动了,
所以不需要我们去弄。所以我们只需在开发板上安装一个播放器madplay,进行移植,具体步骤如下:
首先,我们移植madplay需要三个库文件:
madplay-0.15.2b.tar.gz
libmad-0.15.1b.tar.gz
libid3tag-0.15.1b.tar.g
下载地址为: http://sourceforge.net/project/showfiles.php?group_id=12349
下载完之后,我们开始进行移植
①:在root目录上创建一个文件夹madplayer,然后将这三个压缩文件放在这个文件夹里,然后再进行解压。如下所示:
tar -xvf madplay-0.15.2b.tar.gz
tar -xvf libmad-0.15.1b.tar.gz
tar -xvf libid3tag-0.15.1b.tar.g
②:解压完之后,我们先检查下自己的ubuntu里面有没有zlib库,如果没有的话就要安装下,(新手)建议都安装下(不管有没有)。
首先也是从网上找到zlib库(http://www.zlib.net/),然后也将其放置到文件夹madplayer中,解压:
tar -xvf zlib-1.2.8.tar.gz
然后进行配置:
export CC=arm-linux-gcc
./configure -shared --prefix=/opt/libdecode
(这个是我们指定安装的目录,可以自己随意指定。因为我们ubuntu中的/opt目录中是没有libcode这个文件夹的,所以我们得要先在opt目录下创建这个文件夹)
之后进行编译和安装:
make 和 make install
安装完之后,我们还得在ubuntu上敲如下三个命令:
export LDFLAGS="-L/opt/libdecode/lib"
export CFLAGS="-I/opt/libdecode/include"
export CPPFLAGS="-I/opt/libdecode/include"
这是用来导出环境变量的,如果我们不导出,那么等下我们在配置其他库时会出错,提示找不到这个头文件等等。
③:进入到libid3tag-0.15.1b.tar.g解压完之后的目录libid3tag-0.15.1b中,
然后进行配置:
./configure --host=arm-linux --enable-shared --enable-static --prefix=/opt/libdecode
配置完之后会得到一个Makefile,检查下Makefile,看看交叉编译设置是否正确(一般默认的都不是交叉编译,所以我们得在Makefile中做修改):
CC=gcc 改为 CC=arm-linux-gcc
AR=ar 改为 AR=arm-linux-ar
RANLIB=ranlib 改为 RANLIB=arm-linux-ranlib
之后进行编译和安装:
make 和 make install
④:进入到libmad-0.15.1b.tar.gz解压完之后的目录libmad-0.15.1b中,
然后进行配置:
./configure --host=arm-linux --enable-shared --enable-static --prefix=/opt/libdecode
配置完之后会得到一个Makefile,检查下Makefile,看看交叉编译设置是否正确(一般默认的都不是交叉编译,所以我们得在Makefile中做修改):
CC=gcc 改为 CC=arm-linux-gcc
AR=ar 改为 AR=arm-linux-ar
RANLIB=ranlib 改为 RANLIB=arm-linux-ranlib
之后进行编译和安装:
make 和 make install
⑤:进入到madplay-0.15.2b.tar.gz解压完之后的目录madplay-0.15.2b中,
然后进行配置:
./configure --host=arm-linux --enable-shared --enable-static --prefix=/opt/libdecode
配置完之后会得到一个Makefile,检查下Makefile,看看交叉编译设置是否正确(一般默认的都不是交叉编译,所以我们得在Makefile中做修改):
CC=gcc 改为 CC=arm-linux-gcc
AR=ar 改为 AR=arm-linux-ar
RANLIB=ranlib 改为 RANLIB=arm-linux-ranlib
之后进行编译和安装:
make 和 make install
这时会生成两个文件:madplay和abxtest,放在/opt/libdecode/bin中。
⑥:接下来就是部署我们的动态文件了,由于我们生成的动态文件都是生成在/opt/libdecode/lib中,
所以我们要把这里面的.so文件都部署到我们的根文件系统中去(我的根文件系统是/root/rootfs/rootfs),部署到/root/rootfs/rootfs/usr/lib中,
命令如下:cp /opt/libdecode/lib/*so* /root/rootfs/rootfs/usr/lib 。然后将生成在/opt/libdecode/bin中的madplay复制到我们的/root/rootfs/rootfs/usr/bin中。
这样就部署完成了,接下来我们可以把一个音乐文件部署到/root/rootfs/rootfs/usr/bin中,然后让我们的开发板开机进入到系统中,然后进入到/bin目录中,执行如下命令:
./madplay 123.mp3
就可以听到音乐啦。
注意:根文件系统是自己制作的,然后我是通过nfs来挂载根文件系统的,因为这样调试比较方便。
3、录音播放
要进行录音播放,我们得需要安装两个库alsa-lib-1.0.22.tar.bz2和alsa-utils-1.0.22.tar.bz2。接下来我们就进行这两个库的配置和安装。
首先我们在~目录下创建一个文件夹放这两个压缩文件,然后分别进行解压:tar -jxvf alsaxxxxxxx
①:alsa-lib-1.0.22.tar.bz2
解压后会得到:alsa-lib-1.0.22
cd alsa-lib-1.0.22
配置:
./configure --prefix=/root/luyin/alsa-1.0.22
--host=arm-none-linux-gnueabi
--disable-python
注意:--prefix是自己指定的目录,将来我们编译和安装的文件都是放在这个目录下。
编译和安装:
make
make install
至此这个库安装完成,进入到/root/luyin/alsa-1.0.22会看到生成了一些文件夹和文件等。
②:alsa-utils-1.0.22.tar.bz2
解压后会得到:alsa-utils-1.0.22
cd alsa-utils-1.0.22
配置:
./configure --prefix=/root/luyin/alsa-1.0.22/
--host=arm-none-linux-gnueabi
--with-alsa-prefix=/root/luyin/alsa-1.0.22/lib/
--with-alsa-inc-prefix=/root/luyin/alsa-1.0.22/include/
--disable-alsamixer
--disable-xmlto
./configure --prefix=/opt/alsa-1.0.22/
--host=arm-none-linux-gnueabi
--with-alsa-prefix=/opt/alsa-1.0.22/lib/
--with-alsa-inc-prefix=/opt/alsa-1.0.22/include/
--disable-alsamixer
--disable-xmlto
编译和安装:
make
make install
③:接下来就是部署了
进入到/root/luyin/alsa-1.0.22/lib,将所有的so文件拷贝到我们自己制作的根文件系统中的/usr/lib中。我自己制作的根文件系统的目录是:/root/rootfs/rootfs。如下所示:
cp /root/luyin/alsa-1.0.22/lib/*so* /root/rootfs/rootfs/usr/lib
进入到/root/luyin/alsa-1.0.22/bin,将其中所有的文件拷贝到我们自己制作的根文件系统中的/usr/bin中。命令如下所示:
cp /root/luyin/alsa-1.0.22/bin/* /root/rootfs/rootfs/usr/bin
进入到/root/luyin/alsa-1.0.22/share,将其中的alsa文件(因为这个文件执行的目录跟我们之前配置的要相同,所以我们要先在我们的根文件系统中创建root/luyin/alsa-1.0.22/share),
所以复制到的目录是:/root/rootfs/rootfs/root/luyin/alsa-1.0.22/share。命令如下:
cd /root/rootfs/rootfs/
mkdir root/luyin/alsa-1.0.22/share -p
cp /root/luyin/alsa-1.0.22/share/alsa /root/rootfs/rootfs/root/luyin/alsa-1.0.22/share/
至此,这两个文件就安装完成,我们可以在开发板上测试了。
④:配置声卡
在开发板中依次运行如下命令:
mkdir /dev/snd
cd /dev/snd
mknod dsp c 14 3
mknod audio c 14 4
mknod mixer c 14 0
mknod controlC0 c 116 0
mknod seq c 116 1
mknod pcmC0D0c c 116 24
mknod pcmC0D0p c 116 16
mknod pcmC0D1c c 116 25
mknod pcmC0D1p c 116 17
mknod timer c 116 33
如果开发板上本身就有了,可以不用执行这个,但还是建议执行下,反正也没什么损失。
⑤:录音和播放
录音:
arecord -d3 -c1 -r16000 -twav -fS16_LE example.wav
说明:
-d:录音时长(duration)
-c:音轨(channels)
-r:采样频率(rate)
-t:封装格式(type)
-f:量化位数(format)
执行这条命令时,可能到时候播放录音时会有噪音,这是由于我们设置的音轨为1的原因,所以把它设置为2就可以了。具体命令如下:
arecord -d3 -c2 -r16000 -twav -fS16_LE example.wav
播放:
aplay example.wav
4、Linux下的科大讯飞语音离线识别
①:首先得要上科大讯飞官网( https://www.xfyun.cn/?ch=bdtg),注册一个账号
②:选择 资料库 ---> SDK下载 ----> 输入登录的用户名 ----> 首次使用需要添加应用 ---> 添加描述(应用名称:xxxx),其他描述自定义
---> 回到SDK下载
---> 选择刚刚的应用,点击添加 AI功能 (离线命令词识别),并选中
---> 最后确定SDK下载,选择 Linux 平台
③:将下载好的 SDK拷贝到Ubuntu系统的工作区
mkdir /root/AI
cp /mnt/hgfs/windows_share/Linux_aitalk_expxxxxxx.zip /root/AI -----> 注意 Linux_aitalk_expxxxxxx.zip 为自己的实际包名
④:编译SDK
cd /root/AI
unzip Linux_aitalk_expxxxxxx.zip
/**********************从现有语音中识别命令************************/
首先,进行编译
cd samples
cd asr_sample/
chmod 777 32bit_make.sh
./32bit_make.sh
其次,将so库移植到我们的/usr/lib库中
cp /root/AI/libs/x86/libmsc.so /usr/lib/
最后,测试
cd /root/AI/bin
./asr_sample
/*****************************************************************/
/*********************从现现场录音中识别命令**********************/
首先,进行编译
cd samples
cd asr_record_sample
chmod 777 32bit_make.sh
./32bit_make.sh
这时会出现错误,提示找不到动态库和头文件等,所以我们要安装两个库alsa-lib-1.0.22.tar.bz2和alsa-utils-1.0.22.tar.bz2。然后将其生成的文件部署到相应的地方,比如把生成的头文件拷贝到/usr/include中,将.so文件拷贝到/usr/lib中就行。反正就是把相应的放到ubuntu中相应的地方去,bin文件就放到/usr/bin中等等。
弄完之后还是会出现错误:/usr/bin/ld: cannot find -lasound
这时我们得要执行系统更新命令:
sudo apt-get update
sudo apt-get remove libasound2
sudo apt-get install alsa-base alsa-utils alsa-source
sudo apt-get install libasound2*
sudo apt-get install fglrx
sudo apt-get install --reinstall ubuntu-desktop
然后执行命令:
./32bit_make.sh
其次,将so库移植到我们的/usr/lib库中
cp /root/AI/libs/x86/libmsc.so /usr/lib/
最后,测试
cd /root/AI/bin
./asr_record_sample
/************************************************************************************/
这样就可以了。
利用socket将语音传送给主机:
首先得将libxml2-sources-2.9.3.tar库安装
安装步骤:
1、配置:
./configure --prefix=/opt/libdecode --host=arm-none-linux-gnueabi --without-python
2、编译和安装:
make、make install
完成这两个步骤之后,去编译我们的程序可能会出现这个问题:
fatal error: libxml/xmlmemory.h: No such file or directory
解决办法:
ln -s /opt/libdecode/include/libxml2/libxml /opt/libdecode/include/libxml
可能还会遇到的问题:
/mnt/hgfs/windows_share/yuyin/include/common.h:27: error: expected '=', ',', ';', 'asm' or '__attribute__' before '*' token
/mnt/hgfs/windows_share/yuyin/include/common.h:29: error: expected '=', ',', ';', 'asm' or '__attribute__' before '*' token
/mnt/hgfs/windows_share/yuyin/include/common.h:30: error: expected ')' before 'doc'
原因:那是因为没有包含相应的头文件,包含相应的头文件就行了。
视频:
VIDIOC_S_FMT failed (-1)
运行V4L2编写的camera测试程序出现如下错误:
Capability Informations:
driver: uvcvideo
card: USB2.0 PC Camera
bus_info: usb-0000:02:03.0-1
version: 3.5.7
capabilities: 04000001
VIDIOC_S_FMT failed (-1)
在开发板上运行camera程序时总是出现VIDIOC_S_FMT failed (-1)错误,查看源码发现是在 ret = ioctl(fd, VIDIOC_S_FMT, &fmt);时出错,用源码在PC机上调试运行却没问题,可以通过此条语句,goolgle也没查到什么有用的信息。后来在PC机上调试时发现在有一个调试程序的情况下再对该程序重启一个进程调试也出现这个问题,怀疑出现这个问题的原因是video0设备被别的程序占用了,所以无法再次设置FMT,
实验:杀断所有占用视频设备的程序或重启,在一个终端上运行正常,再开个终端运行时也出现相同情况,说明确实是因为被占用的原因。
以这个思路在开发板上排除了USB CAMERA设备节点被占用后,依然无法设置FMT,偿试了下先获取设备支持的视频格式,以此设置OK了,同一个摄像头在PC上的格式是YUV但在开发板上获取却变成MJPG了,所以以PC机上的格式设置无法在开发板上运行,格式不同的根本原因应该是UVC驱动不同造成。
检查了下源码发现退出时都没有close相应的设备文件,这样也会导致重新插拔USB摄像头时设备结点的次设备号增加变成video1,video2....videoN等。
服务端程序流程:
1、先执行以下两条命令:
system("rm msc -rf");
system("cp .msc msc -rf");
2、设置登陆参数
const char *login_config = "appid = 583467f7"; //登录参数
3、建立socket连接
init_sock();//服务端初始化一次socket,等待客户端的连接就好,socket不要重复初始化,不让资源被占用会直接失败只能识别一次
4、进入while循环中,该循环的主要作用:等待客户端的语音数据,如果客户端没有语音数据传过来,服务器就会陷入不可控的死循环(不能循环识别了)
5、循环里面:
①:ret = MSPLogin(NULL, NULL, login_config); //第一个参数为用户名,第二个参数为密码,传NULL即可,第三个参数是登录参数
注意:服务端每次识别一次语音,都要先登录;识别完之后要登出;需要重复这个过程;因为识别完一次语音,onResult都会被占用,需要通过登出再登陆才能释放占用资源。
②:第一次使用某语法进行识别,需要先构建语法网络,获取语法ID,之后使用此语法进行识别,无需再次构建
memset(&asr_data, 0, sizeof(UserData));
build_grammar(&asr_data); //构建语法网络
asr_data是UserData定义的一个结构体:
typedef struct _UserData
{
int build_fini; //标识语法构建是否完成
int update_fini;//标识更新词典是否完成
int errcode; //记录语法构建或更新词典回调错误码
char grammar_id[MAX_GRAMMARID_LEN]; //保存语法构建返回的语法ID
}UserData;
③:build_grammar函数解析
函数原型
int build_grammar(UserData *udata)
参数
udata : 用户数据
返回值: ret
先进行各种定义:
FILE *grm_file = NULL;
char *grm_content = NULL;
unsigned int grm_cnt_len = 0;
int ret = 0;
char *grm_build_params = calloc(1, MAX_PARAMS_LEN);
打开GRM_FILE文件(构建离线识别语法网络所用的语法文件)(const char * GRM_FILE = "cmd.bnf";)
grm_file = fopen(GRM_FILE, "rb");
if(NULL == grm_file) {
printf("打开"%s"文件失败![%s]n", GRM_FILE, strerror(errno));
return -1;
}
计算GRM_FILE文件的大小长度
fseek(grm_file, 0, SEEK_END);
grm_cnt_len = ftell(grm_file);
fseek(grm_file, 0, SEEK_SET);
向内存申请GRM_FILE文件的大小+1长度的内存,即(grm_cnt_len + 1)
grm_content = (char *)malloc(grm_cnt_len + 1);
if (NULL == grm_content)
{
printf("内存分配失败!n");
fclose(grm_file);
grm_file = NULL;
return -1;
}
将GRM_FILE文件中的内容读取到刚刚申请的内存grm_content中,最后以‘ '为结束符号
fread((void*)grm_content, 1, grm_cnt_len, grm_file);
grm_content[grm_cnt_len] = '