树莓派3 ROS语音包开发之音频采集

树莓派3 ROS语音包开发之音频采集

谈到语音交互,简单步骤你我都能想到,无非就是以下几个步骤:

1、语音采集 
2、语音识别 
3、语义理解 
4、语音合成

之前移植了ROS代码包,很多语音交互方面只有英文,对于中文识别来说,想要找到恰当的包比较困难,其中的难点接下来会一一来探究并找出相应的办法。该博客先不讨论后续内容,现在只着重介绍步骤一,即音频采集。音频采集首先要了解的几个概念:

1、样本精度(长度):样本是记录音频数据最基本的单位,常见有8位和16 
2、采样频率:每秒采样次数,可类比成每次中断读取一次数据 
3、通道数:分单声道(1)、立体声(2) 
4、桢:桢记录了一个声音单元,其长度为样本长度与通道数的乘积 
5、交错模式(interleaved):是一种音频数据的记录方式,在交错模式下,数据以连续桢的形式存放,即首先记录完桢1的左声道样本和右声道样本(假设为立体声格式),再开始桢2的记录。而在非交错模式下,首先记录的是一个周期内所有桢的左声道样本,再记录右声道样本,数据是以连续通道的方式存储。不过多数情况下,我们只需要使用交错模式就可以了。

概念了解清楚了,接下来就找找工具来操练一下,博主现在准备以下几样东西:

1、硬件:树莓派3、罗技usb免驱摄像头、普通的耳塞 
2、软件:Raspbian OS +ROS 代码完整包+ALSA(Linux 先进音频架构)

说明:对于树莓派官网操作系统安装ROS完整包介绍,可参考上篇博客:

http://blog.csdn.net/u013494117/article/details/51751939

对于ALSA库来说,只需在终端简单输入一下命令安装即可:

sudo apt-get install alsa-tools alsa-oss flex zlib1g-dev libc-bin libc-dev-bin python-pexpect libasound2-dev
 
 
  • 1

简要说明一下重要的库: 
alsa-tools: 该库提供了对音频操作的相关指令 
libasound2-dev:提供alsa应用编程API,如果使用c/c++编程会用到该库的一些函数。

一切准备妥当,现在插上设备,启动电源。一切准备就绪,先测试一下录音效果;在终端输入:

arecord -d 10 -D plughw:1,0 test.wav
 
 
  • 1

解析: 
arecord 工具为我们刚刚安装的alsa-tools提供的音频操作,即录音 
-d: 录制时间(秒) 
-D: 指明设备名(plughw:i,j):其中i是卡号,j是这块声卡上的设备号 
plughw:,表示一个插件,详细介绍如下:

设备命名
API库使用逻辑设备名而不是设备文件。设备名字可以是真实的硬件名字也可以是插件名字。
硬件名字使用hw:i,j这样的格式。其中i是卡号,j是这块声卡上的设备号。第一个声音设备是
hw:0,0.这个别名默认引用第一块声音设备并且在本文示例中一直会被用到。插件使用另外的唯一
名字。比如plughw:,表示一个插件,这个插件不提供对硬件设备的访问,而是提供像采样率转
换这样的软件特性,硬件本身并不支持这样的特性。
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

更多详细内容请在终端输入

arecord -help
 
 
  • 1

录制完之后肯定播放一下啦,你可以使用简单的应用工具打开音频文件,当然也可以用命令方式,当先安装如下工具

sudo apt-get install sox
 
 
  • 1

之后插上耳机执行如下:

play xxx.wav 或者 play xxx.mp3
 
 
  • 1

不出意外,你应该能成功录音并且播放,到现在为止,硬件方面的测试已经成功了!基础搭好了,现在该如何做呢? 
首先我们一个方面想下去,先录音,再识别,进而实现交互。问题来了,我们脱离任何控制按键时如何识别出采集到的音频是语音呢?这是我们需要探讨的问题,这个问题先不急着怎么解决,留到下篇博客慢慢道来,我们先来解决音频采集的问题。我们要分析音频,那么先要从采集到的原始数据下手,如何做?说来也就那么一回事,不就是读取声卡采集到的数据嘛!思路来了,我们从一开始安装的 libasound2-dev 可派上用场了。对 ALSA 音频架构不了解的可参考下面博文:

http://www.cnblogs.com/lifan3a/articles/5481993.html

了解清楚后咱们开始进入正题,这里先强调一点,现在使用的是C/C++ ,如果你的python学的还ok,那么处理起来也不是难事。

编程思路别人已经说得清清楚楚,走点套路,少点弯路。

1、打开回放或录音接口 
2、设置硬件参数(访问模式,数据格式,信道数,采样率,等等) 
3、while 有数据要被处理: 
   读PCM数据(录音) 或 写PCM数据(回放) 
4、关闭接口

简单吧。到这某些人心里还是暗暗骂了:**

OK,上代码说明解说:plughw:1,0,所以要替换掉字符串”default”

打开设备并为设备参数空间分配空间并填充设置硬件参数,这里我们使用的是 

/* Open PCM device for recording (capture). 
*  we use "plughw:1,0" to instate "default" to read pcm data
*/
int rc = snd_pcm_open(&handle, "default",SND_PCM_STREAM_CAPTURE, 0);
if (rc < 0) {
    fprintf(stderr,"unable to open pcm device: %s\n",
    snd_strerror(rc));
/* Allocate a hardware parameters object. */
snd_pcm_hw_params_malloc(&params);
/* Fill it in with default values. */
snd_pcm_hw_params_any(handle, params);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

需要注意的一点是 handle:这个结构体变量类似打开文件返回的一个语句斌,不用在意它里面的具体内容!打开硬件,那么就要设置参数,具体使用函数:

snd_pcm_hw_params_set_XXX(handle, params, value);
 
 
  • 1

设置ok 后,若需查询硬件配置是否正确,可使用

snd_pcm_hw_params_get_XXX(handle, params, &value);
 
 
  • 1

最后到了读取采集到的数据啦!使用

/*
* handle: 回话语句柄,在打开硬件函数中绑定
* buffer: 帧缓冲区
* frames: 要读取的帧的总数
*/
rc = snd_pcm_readi(handle, buffer, frames);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

例如我要读取 通道数=1;样本长度 = 16 ;的PCM 数据,那么我申请可以如下

short buffer[2] //每次往声卡读回2帧数据
rc = snd_pcm_readi(handle, buffer, 2);
 
 
  • 1
  • 2

更多详情还是看看官网函数说明: 
ALSA project - the C library reference

不多说了,上完整代码:

头文件:

#ifndef PCM_ALSA_H
#define PCM_ALSA_H

#include <alsa/asoundlib.h>
#include "ros/ros.h"

#define CAPTURE_DEVICE "plughw:1,0"
#define PLAYBACK_DEVICE "default"
typedef unsigned int u32;
typedef unsigned char u8;

//录音配置
extern snd_pcm_t* record_config_init(u32 rate,u32 channle,u8 sample,const char* dev_name);
//播放配置
extern snd_pcm_t* playback_config_init(u32 rate,u32 channle,u8 sample,const char* dev_name);

#endif
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

pcm_alsa.c中函数

#include "pcm_alsa.h"
/*
    作者: hntea_hong
    函数功能:录音初始化配置
    参数说明:
            rate:采样频率
            channle:通道数
            sample:样本长度 16/8
            dev_name: pcm 设备
    返回值:设备回话柄
*/
snd_pcm_t* record_config_init(u32 rate,u32 channle,u8 sample,const char* dev_name)
{
    int i;
    int err;
    snd_pcm_t* capture_handle;  
    snd_pcm_hw_params_t *hw_params;

    //以录音形式打开设备
    if ((err = snd_pcm_open (&capture_handle,\
        dev_name, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
        fprintf (stderr, "cannot open audio device %s (%s)\n",\
        dev_name,snd_strerror (err));
        exit (1);
    }

    //申请硬件参数配置空间
    if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
        fprintf (stderr,\
        "cannot allocate hardware parameter structure (%s)\n",
        snd_strerror (err));
        exit (1);
    }

    //填充参数空间
    if ((err = snd_pcm_hw_params_any (capture_handle, hw_params)) < 0) {
        fprintf (stderr,\
         "cannot initialize hardware parameter structure (%s)\n",
        snd_strerror (err));
        exit (1);
    }

    //配置处理模式
    if ((err = snd_pcm_hw_params_set_access (capture_handle, hw_params,\
     SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
        fprintf (stderr, "cannot set access type (%s)\n",
        snd_strerror (err));
        exit (1);
    }

    //配置样本值
    switch(sample)
    {
        case 8:
            if ((err = snd_pcm_hw_params_set_format (capture_handle,\
             hw_params, SND_PCM_FORMAT_U8 )) < 0) {
                fprintf (stderr, "cannot set sample format (%s)\n",
                snd_strerror (err));
                exit (1);
            }   
            break;
        case 16:
            if ((err = snd_pcm_hw_params_set_format (capture_handle,\
             hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
                fprintf (stderr, "cannot set sample format (%s)\n",
                snd_strerror (err));
                exit (1);
            }
            break;
        default:
            if ((err = snd_pcm_hw_params_set_format (capture_handle,\
             hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
                fprintf (stderr, "cannot set sample format (%s)\n",
                snd_strerror (err));
                exit (1);
            }
            break;
    }

    //配置采样频率
    if ((err = snd_pcm_hw_params_set_rate_near (capture_handle,\
     hw_params, &rate, 0)) < 0) {
        fprintf (stderr, "cannot set sample rate (%s)\n",
        snd_strerror (err));
        exit (1);
    }

    //配置通道数
    if ((err = snd_pcm_hw_params_set_channels (capture_handle,\
     hw_params, channle)) < 0) {
        fprintf (stderr, "cannot set channel count (%s)\n",
        snd_strerror (err));
        exit (1);
    }

    //写入参数
    if ((err = snd_pcm_hw_params (capture_handle, hw_params)) < 0) {
        fprintf (stderr, "cannot set parameters (%s)\n",
        snd_strerror (err));
        exit (1);
    }

    //释放参数空间
    snd_pcm_hw_params_free (hw_params);

    if ((err = snd_pcm_prepare (capture_handle)) < 0) {
        fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
        snd_strerror (err));
        exit (1);
    }

    return capture_handle;  
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114

录音配置解决了,现在写个测试函数试一下: 
test.cpp 中代码如下:

/*******************************************
函数功能:在固定路径下创建文件
参数说明:
返回说明:文件语句柄
********************************************/
int create_and_open_file(const char* path)
{
    int fd = 0;
    // O_TRUNC: 如果文件存在并以只读或读写打开,则将其长度截短为0
    fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0664);
    if (fd < 0) {
        fprintf(stderr, "arec: cannot open '%d'\n", fd);
        return -1;
    }
    return fd;
}
//录制音频
u32 record()
{

    int fd = -1;
    int err = -1;
    u32 loops = 30000;          /*循环录音时间*/
    u32 totle_size = 0;         /*记录总量*/
    int frame = 2;              /* 帧大小 = 量化单位×通道数 / 字节位数 */
    short* buf;                 /*用来保存每一帧数据,每帧数据大小为 两个字节16位*/ 
    buf = (short*)malloc(sizeof(short));    
    int start_sec = 0;
    snd_pcm_t *capture_handle;//会语句柄

    //初始化声卡配置
    capture_handle = record_config_init(16000,1,16,CAPTURE_DEVICE);
    //录音开始
    printf("Ready to capture frame...\n");


    //在指定路径打开或创建固定音频文件 0664
    fd = create_and_open_file(PATH_NAME);
    printf("Start to record...\n");
    start_sec = get_sec();  
    while(loops--)                      //这里到时修正成 后端点检测
    {
        //bzero(buf,16);  
        if ((err = snd_pcm_readi (capture_handle, buf, frame)) != 2) 
        {
            fprintf (stderr, "read from audio interface failed (%s)\n",
            snd_strerror (err));
            exit (1);
        }
        totle_size = write_info_to_file(fd,buf,frame,totle_size);
    }

    //加入音频头
    //fd  = add_wav_head(fd, &hdr,totle_size);

    //关闭文件
    snd_pcm_close (capture_handle);
    free(buf);
    close(fd);
    printf("Use %d seconds!\n",get_sec()-start_sec);
    return totle_size;

}
main (int argc, char *argv[])
{
    u32 filesize;
    filesize = record();
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

正常来说,现在能够录音啦,不过对于没有处理过的音频信息,能否用软件播放呢?大家自己去尝试一下,或者自己写个播音程序也是简单的事,这里就不再说明,后续博客还会记录如何操作。

【资源说明】 基于ros语音识别源码(采用python与科大讯飞语音听写api)+项目使用说明.zip 基于ros语音识别,使用python与科大讯飞语音听写api实现实时的语音识别,并利用节点发布话题控制进程。 环境要求: * ros1,python3.x * 声卡:```sudo apt install libasound2-dev``` * requirement: * pass 项目结构: ``` XF_PYROS_IAT │ .gitattributes │ CMakeLists.txt │ package.xml │ README.md │ ├─launch │ main_node.launch │ ├─others │ node.png │ node_main.png │ ├─scripts │ close_switch_node.py │ main_node.py │ open_switch_node.py │ reset_node.py │ └─src func_open_node.cpp func_pause_node.cpp func_reset_node.cpp ``` 节点说明: ![节点图](others/node.png "节点图") * `main_node.py`是主程序,承担语音识别模块,对应节点为:/main_node,只运行该节点不会直接开始语音识别 * `open_switch_node.py`用于开启主程序语音识别,对应节点为:/open_node * `close_switch_node.py`用于关闭主程序语音识别,对应节点为:/pause_node * `reset_node.py`用于重置整个进程,使其恢复到休眠状态,对应节点为:/reset_node,运行一次该节点后就可以通过open_switch_node.py或/open_node再次开启语音识别 * src中的cpp生成的节点实际上就是用于控制以上节点,可有可无,根据自己需求使用,对应关系如下:(主要由于本人不会c++,故才加了src中的三个节点,如果你会C++可以自己把以上其他节点改写为C++) * _`func_open_node.cpp` --> `open_switch_node.py` 、`func_pause_node.cpp` --> `close_switch_node.py` 、`func_reset_node.cpp` --> `reset_node.py`_ 使用步骤: ***注意:请提前修改 main_node.py 中的科大讯飞APPID,APIKey,APISecret!*** 终端一: ``` roslaunch xf_pyros_iat main_node.launch ``` 终端二: * 发布话题:/func_switch_op启动语音识别,data: ' '里可以是任何字符串,没有也行 ``` rostopic pub /func_switch_op std_msgs/String "data: ''" ``` * 发布话题:/func_switch_cl关闭语音识别,data: ' '里可以是任何字符串,没有也行 ``` rostopic pub /func_switch_cl std_msgs/String "data: ''" ``` * 发布话题:/func_switch_re重置语音识别,data: ' '里可以是任何字符串,没有也行 ``` rostopic pub /func_switch_re std_msgs/String "data: ''" ``` * 此外通过:/func_op_node、/func_cl_node、/func_re_node三个节点也可以控制开、关、重置语音识别,命令如下: ``` rosrun xf_pyros_iat func_op_node ``` ``` rosrun xf_pyros_iat 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载使用,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值