前言
在前面的4篇连载中我们已经讲解了百度语音识别的流程,如何使用webclient软件包进行语音识别,如何使用CJson软件包进行数据解析,如何在LCD上显示识别结果,如何通过语音识别控制外设。这一切的一切的首要前提,就是语音,那我们前面使用的都是事先录制好的音频,而本次连载,我们终于要来实现录音功能了,有了录音,你想怎么识别就可以怎么识别,是不是很棒。
我将采用RT-Thread的Audio设备框架,下面我会简单介绍该框架。但在那之前,我建议你先去看看正点原子教程的 “音乐播放器” 及 “录音机” 两个例程,确保你对WAVE文件,音频编解码芯片,SAI等知识点有一定的了解。
Audio设备框架
Audio(音频)设备是嵌入式系统中非常重要的一个组成部分,负责音频数据的采样和输出。如下图所示:
RT-Thread的Audio设备驱动框架为我们提供了标准 device 接口(open/close/read/control),只要我们对接好设备框架,就可以在我们的应用代码里直接使用这些标准接口,对设备进行操作。(RT-Thread其他设备框架实现原理都是如此)
详细介绍见RT-Thread文档中心
本篇我们不具体讲解Audio设备框架的对接,因为我使用的潘多拉开发板是官方支持的板子,所以底层驱动,框架对接这部分已经有相应的支持了。这里只简单提一下设备框架的对接方法,RT-Thread所有的设备框架的对接,基本上都是两大步骤:
- 准备好相应的设备驱动,实现对应框架的ops函数
- 进行设备注册
想要弄懂这两步,RT-Thread的文档中心必须熟看,一定要去看框架源码。
应用代码
#include <rtthread.h>
#include <rtdevice.h>
#include <dfs_posix.h>
#define RECORD_TIME_MS 5000
#define RECORD_SAMPLERATE 8000
#define RECORD_CHANNEL 2
#define RECORD_CHUNK_SZ ((RECORD_SAMPLERATE * RECORD_CHANNEL * 2) * 20 / 1000)
#define SOUND_DEVICE_NAME "mic0" /* Audio 设备名称 */
static rt_device_t mic_dev; /* Audio 设备句柄 */
struct wav_header
{
char riff_id[4]; /* "RIFF" */
int riff_datasize; /* RIFF chunk data size,exclude riff_id[4] and riff_datasize,total - 8 */
char riff_type[4]; /* "WAVE" */
char fmt_id[4]; /* "fmt " */
int fmt_datasize; /* fmt chunk data size,16 for pcm */
short fmt_compression_code; /* 1 for PCM */
short fmt_channels; /* 1(mono) or 2(stereo) */
int fmt_sample_rate; /* samples per second */
int fmt_avg_bytes_per_sec; /* sample_rate * channels * bit_per_sample / 8 */
short fmt_block_align; /* number bytes per sample, bit_per_sample * channels / 8 */
short fmt_bit_per_sample; /* bits of each sample(8,16,32). */
char data_id[4]; /* "data" */
int data_datasize; /* data chunk size,pcm_size - 44 */
};
static void wavheader_init(struct wav_header *header, int sample_rate, int channels, int datasize)
{
memcpy(header->riff_id, "RIFF", 4);
header->riff_datasize = datasize + 44 - 8;
memcpy(header->riff_type, "WAVE", 4);
memcpy(header->fmt_id, "fmt ", 4);
header->fmt_datasize = 16;
header->fmt_compression_code = 1;
header->fmt_channels = channels;
header->fmt_sample_rate = sample_rate;
header->fmt_bit_per_sample = 16;
header->fmt_avg_bytes_per_sec = header->fmt_sample_rate * header->fmt_channels * header->fmt_bit_per_sample / 8;
header->fmt_block_align = header->fmt_bit_per_sample * header->fmt_channels / 8;
memcpy(header->data_id, "data", 4);
header->data_datasize = datasize;
}
int wavrecord_sample(int argc, char **argv)
{
int fd = -1;
uint8_t *buffer = NULL;
struct wav_header header;
struct rt_audio_caps caps = {0};
int length, total_length = 0;
if (argc != 2)
{
rt_kprintf("Usage:\n");
rt_kprintf("wavrecord_sample file.wav\n");
return -1;
}
fd = open(argv[1], O_WRONLY | O_CREAT);
if (fd < 0)
{
rt_kprintf("open file for recording failed!\n");
return -1;
}
write(fd, &header, sizeof(struct wav_header));
buffer = rt_malloc(RECORD_CHUNK_SZ);
if (buffer == RT_NULL)
goto __exit;
/* 根据设备名称查找 Audio 设备,获取设备句柄 */
mic_dev = rt_device_find(SOUND_DEVICE_NAME);
if (mic_dev == RT_NULL)
goto __exit;
/* 以只读方式打开 Audio 录音设备 */
rt_device_open(mic_dev, RT_DEVICE_OFLAG_RDONLY);
/* 设置采样率、通道、采样位数等音频参数信息 */
caps.main_type = AUDIO_TYPE_INPUT; /* 输入类型(录音设备 )*/
caps.sub_type = AUDIO_DSP_PARAM; /* 设置所有音频参数信息 */
caps.udata.config.samplerate = RECORD_SAMPLERATE; /* 采样率 */
caps.udata.config.channels = RECORD_CHANNEL; /* 采样通道 */
caps.udata.config.samplebits = 16; /* 采样位数 */
rt_device_control(mic_dev, AUDIO_CTL_CONFIGURE, &caps);
while (1)
{
/* 从 Audio 设备中,读取 20ms 的音频数据 */
length = rt_device_read(mic_dev, 0, buffer, RECORD_CHUNK_SZ);
if (length)
{
/* 写入音频数据到到文件系统 */
write(fd, buffer, length);
total_length += length;
}
if ((total_length / RECORD_CHUNK_SZ) > (RECORD_TIME_MS / 20))
break;
}
/* 重新写入 wav 文件的头 */
wavheader_init(&header, RECORD_SAMPLERATE, RECORD_CHANNEL, total_length);
lseek(fd, 0, SEEK_SET);
write(fd, &header, sizeof(struct wav_header));
close(fd);
/* 关闭 Audio 设备 */
rt_device_close(mic_dev);
__exit:
if (fd >= 0)
close(fd);
if (buffer)
rt_free(buffer);
return 0;
}
MSH_CMD_EXPORT(wavrecord_sample, record voice to a wav file);
注意这里:
#define RECORD_SAMPLERATE 8000
#define RECORD_CHANNEL 2
音频采样率设置为8000,通道数设置为2,因为百度语音要求的是16000(8000*2)的采样率。
同样的,我们这里导出了wavrecord_sample这个命令,使用方式:
在finsh控制台输入:
wavrecord_sample bd.wav
录音功能便开启了,程序里设置的录音时间是5s,音频将存放于bd.wav中。
再用之前实现的bd命令进行语音识别,发现效果还是非常不错的。
总结
至此,我们的录音功能就算实现了。说实话,Audio设备还是挺复杂的,需要反复学习,所以我可能讲的不好,需要大家多去看看框架的源码。
不知大家有没有发现,我前面的所有实现都是基于finsh控制台的命令来的,那实际做一个项目,总不能敲一个命令,实现一个功能吧。所以在下一篇(也是连载的最后一篇),我将把各个部分的功能拆成多个线程,通过线程间通信及同步的方式,来完成整个项目的串接,这才算是用上了实时操作系统,尽情期待~