#创作者激励#【FFH】OpenHarmony轻量化系统声音收录 原创 精华

X丶昕雪
发布于 2023-3-12 19:07
浏览
1收藏

【本文正在参加2023年第一期优质创作者激励计划】

概括

前一阵子想着语音识别作为物联网不可或缺的一部分,前提是获取到语音的声音数据。对于声音收录数字化,stm32有很多现成的样例,而OpenHarmony方面较少该方面的资料。便想着在OpenHarmony实现接收INMP441麦克风模块。本次实现通过I2S接收INMP441模块的PCM数据。
#创作者激励#【FFH】OpenHarmony轻量化系统声音收录-鸿蒙开发者社区

环境

  • OpenHarmony-3.1
  • 润和hispark_pegasus Hi3861开发板
  • DevEco Device Tool
  • SerialPlot
  • INMP441麦克风模块

声音数字化

生活中的声音是通过一定介质传播的波、主要由振幅和频率两个指标来描述。

声音数字化:麦克风将声音以量化位数将声音数字化,常见的量化位深有16bit、24bit、32bit,其意义就是将每个采样点用多少位表示声音振幅的范围,其位深越大音质越好。麦克风再根据采样率进行采集声音,采样率的意思就是1秒中采集声音的次数,采样率越高音质越好。还决定音质的便是声道数,使用双声道可以大大丰富声音的表现力,但随之而来的便是数据量的翻倍。

现实生活中的声音信号是如下图般的波形图,但是我们的计算机中只能保存数值。于是我们将波形图量化,使用一个个整数数据记录声音。
#创作者激励#【FFH】OpenHarmony轻量化系统声音收录-鸿蒙开发者社区
本次使用的INMP441是一个数字麦克风,即本身包含了ADC,传递进来的数据是数字量。得到的数据便是直接的PCM编码格式的数据,若想生成WAV文件,只需要生成一个wav head来标识即可。

// 生成wav header,32bit 位深
void wavHeader(byte* header, int wavSize){ // 数字小端格式,字符大端格式
  header[0] = 'R';
  header[1] = 'I';
  header[2] = 'F';
  header[3] = 'F';
  unsigned int fileSize = wavSize + headerSize - 8;
  header[4] = (byte)(fileSize & 0xFF); // file size, 4byte integer
  header[5] = (byte)((fileSize >> 8) & 0xFF);
  header[6] = (byte)((fileSize >> 16) & 0xFF);
  header[7] = (byte)((fileSize >> 24) & 0xFF);
  header[8] = 'W';
  header[9] = 'A';
  header[10] = 'V';
  header[11] = 'E';
  header[12] = 'f';
  header[13] = 'm';
  header[14] = 't';
  header[15] = ' ';
  header[16] = 0x10; // length of format data = 16, 4byte integer
  header[17] = 0x00;
  header[18] = 0x00;
  header[19] = 0x00;
  header[20] = 0x01;  // format type:1(PCM), 2byte integer 
  header[21] = 0x00;
  header[22] = 0x01; // channel number:1, 2byte integer
  header[23] = 0x00;
  header[24] = 0x80; // sample rate:16000=0x00003E80, 4byte integer
  header[25] = 0x3E;
  header[26] = 0x00;
  header[27] = 0x00;
  header[28] = 0x00; // SampleRate*BitPerSample*ChannelNum/8=16000*32*1/8=64000=0x0000FA00, 4byte integer
  header[29] = 0xFA;
  header[30] = 0x00;
  header[31] = 0x00;
  header[32] = 0x04; // BitPerSample*ChannelNum/8 = 4, 2byte integer 
  header[33] = 0x00;
  header[34] = 0x20; // BitPerSample:32 = 0x0020, 2byte integer 
  header[35] = 0x00;
  header[36] = 'd';
  header[37] = 'a';
  header[38] = 't';
  header[39] = 'a';
  header[40] = (byte)(wavSize & 0xFF);
  header[41] = (byte)((wavSize >> 8) & 0xFF);
  header[42] = (byte)((wavSize >> 16) & 0xFF);
  header[43] = (byte)((wavSize >> 24) & 0xFF); 
}

针脚定义

本次实验只用到了以下四个I2S的针脚

  • SCK(CK):串行时钟,由主机产生的时钟线,用于控制每位数据的传输时序,SCK频率= 声道数 采样频率 * 采样位数*,在OpenHarmony上被定义为BCLK口

  • SD:I2S数据线,从机通过此发送数据给主机,在OpenHarmony上被定义为RX口

  • WS:声道选择线,由主机发送给从机,从机根据此判断发送左声道还是右声道。低电平为左声道,高电平为右声道。

  • L/R:左右声道选择线,指定此从机为左声道还是右声道。低电平为左声道,高电平为右声道。

INMP441 Hi3861
SCK 7
SD 11
WS 8
L/R 根据需求接GND或3V3
GND GND
VDD 3V3

查看音频波形

Hi3861接收到了麦克风模块上传的音频数据,我们可以利用串口将音频数据发送到电脑,电脑使用串口绘画工具SerialPlot查看音频波形,该工具的使用方法和使用其他串口工具相似,网上也有许多使用教程,这里就不再详细阐述。
#创作者激励#【FFH】OpenHarmony轻量化系统声音收录-鸿蒙开发者社区

代码

流程:

  1. 初始化IO口
  2. 配置I2S(采样率为8KHz,量化位数为24bit)
  3. 初始化I2S
  4. 读取I2S数据,OpenHarmony的读取函数得到的数据就是24bit数字量;无需像stm32需要读取的数据是byte类型,然后再拼接。

数据将以LRLR分布,即是一个左声道数据,一个右声道数据分布

若是只使用到单声道,也是LRLR分布,另外一个声道数据为0

#include <stdio.h>
#include <unistd.h>

#include "ohos_init.h"
#include "cmsis_os2.h"
#include "iot_gpio.h"
#include "iot_pwm.h"
#include "iot_i2c.h"
#include "iot_errno.h"

#include "hi_dma.h"
#include "hi_types_base.h"
#include "hi_i2s.h"

#include "hi_io.h"

void i2s_init_demo(void)
{
    hi_u32 ret;
    //初始化IO口
    IoTGpioInit(HI_IO_NAME_GPIO_7);
    IoTGpioInit(HI_IO_NAME_GPIO_8);
    IoTGpioInit(HI_IO_NAME_GPIO_11);
    IoTGpioInit(HI_IO_NAME_GPIO_10);
    hi_io_set_func(HI_IO_NAME_GPIO_7, HI_IO_FUNC_GPIO_7_I2S0_BCLK);
    hi_io_set_func(HI_IO_NAME_GPIO_8, HI_IO_FUNC_GPIO_8_I2S0_WS);
    hi_io_set_func(HI_IO_NAME_GPIO_11, HI_IO_FUNC_GPIO_11_I2S0_RX);
    hi_io_set_func(HI_IO_NAME_GPIO_10, HI_IO_FUNC_GPIO_10_I2S0_TX);

    ret = hi_i2s_deinit();
    //配置I2S,采样率为8KHz,量化位数为24bit
    hi_i2s_attribute i2s_cfg = {
        .sample_rate = HI_I2S_SAMPLE_RATE_8K,
        .resolution = HI_I2S_RESOLUTION_24BIT,
    };
    if (ret != HI_ERR_SUCCESS)
        printf("Failed to deinit i2s!\n");
    //初始化
    ret = hi_i2s_init(&i2s_cfg);
    if (ret != HI_ERR_SUCCESS)
        printf("Failed to init i2s!\n");
    printf("ret = %d \n", ret);
    printf("I2s init succrss!\n");
}

void i2s_main_demo(void)
{
    hi_u32 ret;
    i2s_init_demo();
    sleep(2);

    hi_u32 get_buff[100] = {0};
    while(1)
    {
        //读取I2S信息
        hi_i2s_read(get_buff, 100, HI_SYS_WAIT_FOREVER);
        for (int i = 0; i < 100;i++)
        {
        	printf("%d\n", get_buff[i]);
        }
    }
}
//demo线程创建
void INMP441TestDemo(void)
{
    osThreadAttr_t attr;

    attr.name = "INMP441Task";
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = 10240;
    attr.priority = osPriorityNormal;

    if (osThreadNew(i2s_main_demo, NULL, &attr) == NULL)
    {
        printf("[INMP441Task] Falied to create INMP441Task!\n");
    }
}
APP_FEATURE_INIT(INMP441TestDemo);


static_library("mic_test") {
    sources = [
        "inmp441_demo.c",
    ]

    include_dirs = [
        "//utils/native/lite/include",
        "//kernel/liteos_m/components/cmsis/2.0",
        "//base/iot_hardware/peripheral/interfaces/kits",
        "//device/soc/hisilicon/hi3861v100/hi3861_adapter/hals/communication/wifi_lite/wifiservice",
        "//device/soc/hisilicon/hi3861v100/hi3861_adapter/kal",
        "//ohos_bundles/@ohos/device_soc_hisilicon/hi3861v100/sdk_liteos/include",
        "//device/soc/hisilicon/hi3861v100/sdk_liteos/include"
    ]
}

疑问

每次调用hi_i2s_read读取函数,最后得到的数据只有前四分之一有数据,其余都是0,找了很久都没有找到问题所在,希望能有大佬解答一下,万分感谢!

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2023-3-12 19:09:26修改
2
收藏 1
回复
举报
1条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

蹲一下只能取四分之一数据的后续,会不会是存储的问题

回复
2023-3-13 10:26:57
回复
    相关推荐