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

  • Post author:
  • Post category:其他


树莓派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

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