树莓派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
简要说明一下重要的库:
alsa-tools: 该库提供了对音频操作的相关指令
libasound2-dev:提供alsa应用编程API,如果使用c/c++编程会用到该库的一些函数。
一切准备妥当,现在插上设备,启动电源。一切准备就绪,先测试一下录音效果;在终端输入:
arecord -d 10 -D plughw:1,0 test.wav
解析:
arecord 工具为我们刚刚安装的alsa-tools提供的音频操作,即录音
-d: 录制时间(秒)
-D: 指明设备名(plughw:i,j):其中i是卡号,j是这块声卡上的设备号
plughw:,表示一个插件,详细介绍如下:
设备命名
API库使用逻辑设备名而不是设备文件。设备名字可以是真实的硬件名字也可以是插件名字。
硬件名字使用hw:i,j这样的格式。其中i是卡号,j是这块声卡上的设备号。第一个声音设备是
hw:0,0.这个别名默认引用第一块声音设备并且在本文示例中一直会被用到。插件使用另外的唯一
名字。比如plughw:,表示一个插件,这个插件不提供对硬件设备的访问,而是提供像采样率转
换这样的软件特性,硬件本身并不支持这样的特性。
更多详细内容请在终端输入
arecord -help
录制完之后肯定播放一下啦,你可以使用简单的应用工具打开音频文件,当然也可以用命令方式,当先安装如下工具
sudo apt-get install sox
之后插上耳机执行如下:
play xxx.wav 或者 play xxx.mp3
不出意外,你应该能成功录音并且播放,到现在为止,硬件方面的测试已经成功了!基础搭好了,现在该如何做呢?
首先我们一个方面想下去,先录音,再识别,进而实现交互。问题来了,我们脱离任何控制按键时如何识别出采集到的音频是语音呢?这是我们需要探讨的问题,这个问题先不急着怎么解决,留到下篇博客慢慢道来,我们先来解决音频采集的问题。我们要分析音频,那么先要从采集到的原始数据下手,如何做?说来也就那么一回事,不就是读取声卡采集到的数据嘛!思路来了,我们从一开始安装的 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(¶ms);
/* Fill it in with default values. */
snd_pcm_hw_params_any(handle, params);
需要注意的一点是 handle:这个结构体变量类似打开文件返回的一个语句斌,不用在意它里面的具体内容!打开硬件,那么就要设置参数,具体使用函数:
snd_pcm_hw_params_set_XXX(handle, params, value);
设置ok 后,若需查询硬件配置是否正确,可使用
snd_pcm_hw_params_get_XXX(handle, params, &value);
最后到了读取采集到的数据啦!使用
/*
* handle: 回话语句柄,在打开硬件函数中绑定
* buffer: 帧缓冲区
* frames: 要读取的帧的总数
*/
rc = snd_pcm_readi(handle, buffer, frames);
例如我要读取 通道数=1;样本长度 = 16 ;的PCM 数据,那么我申请可以如下
short buffer[2]
rc = snd_pcm_readi(handle, buffer, 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"
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;
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;
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");
fd = create_and_open_file(PATH_NAME);
printf("Start to record...\n");
start_sec = get_sec();
while(loops--)
{
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);
}
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
正常来说,现在能够录音啦,不过对于没有处理过的音频信息,能否用软件播放呢?大家自己去尝试一下,或者自己写个播音程序也是简单的事,这里就不再说明,后续博客还会记录如何操作。