手把手教你实现基于RT-Thread的百度语音识别(五)

前言

在前面的4篇连载中我们已经讲解了百度语音识别的流程,如何使用webclient软件包进行语音识别,如何使用CJson软件包进行数据解析,如何在LCD上显示识别结果,如何通过语音识别控制外设。这一切的一切的首要前提,就是语音,那我们前面使用的都是事先录制好的音频,而本次连载,我们终于要来实现录音功能了,有了录音,你想怎么识别就可以怎么识别,是不是很棒。

我将采用RT-Thread的Audio设备框架,下面我会简单介绍该框架。但在那之前,我建议你先去看看正点原子教程的 “音乐播放器” 及 “录音机” 两个例程,确保你对WAVE文件,音频编解码芯片,SAI等知识点有一定的了解。

Audio设备框架

Audio(音频)设备是嵌入式系统中非常重要的一个组成部分,负责音频数据的采样和输出。如下图所示:

undefined

RT-Thread的Audio设备驱动框架为我们提供了标准 device 接口(open/close/read/control),只要我们对接好设备框架,就可以在我们的应用代码里直接使用这些标准接口,对设备进行操作。(RT-Thread其他设备框架实现原理都是如此)

详细介绍见RT-Thread文档中心

本篇我们不具体讲解Audio设备框架的对接,因为我使用的潘多拉开发板是官方支持的板子,所以底层驱动,框架对接这部分已经有相应的支持了。这里只简单提一下设备框架的对接方法,RT-Thread所有的设备框架的对接,基本上都是两大步骤:

  1. 准备好相应的设备驱动,实现对应框架的ops函数
  2. 进行设备注册

想要弄懂这两步,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控制台的命令来的,那实际做一个项目,总不能敲一个命令,实现一个功能吧。所以在下一篇(也是连载的最后一篇),我将把各个部分的功能拆成多个线程,通过线程间通信及同步的方式,来完成整个项目的串接,这才算是用上了实时操作系统,尽情期待~

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值