通过前一小节了解音频开发框架后,就可设计一个音频录制的处理流程,如下:
麦克风输入->音频编码->输出到文件
下面我们来实现这个流程。
本例使用一个支持I2S协议的麦克风模块,芯片为SPH0645LM4H
I2S MEMS麦克风详情
本示例使用I2S MEMS麦克风模块录制音频并存储到SD存储卡的文件中。
从 https://gitee.com/billyzh/esp32-cpp-lesson 下载本教程的源码到本地硬盘文件夹,如d:\esp32-cpp-lesson
在VSCode中,选择【文件】->【打开文件夹...】选择上一步保存的文件夹打开
打开项目后,选择config.h文件,修改第10行为
#define APP_LESSON102_A 1
打开unit10-lesson101a/app_config.h文件,设置应用的相关配置
#define CONFIG_USE_FS 1 //启用文件系统模块
#define CONFIG_USE_AUDIO 1 //启用音频模块
#define CONFIG_USE_DISPLAY 1 //启用显示模块
#define CONFIG_USE_TFT_ESPI 1 //使用TFT_eSPI显示驱动
打开unit10-lesson101a/board_config.h文件,设置显示硬件的相关配置
#define SD_MOSI_PIN GPIO_NUM_17 //SD存储模块引脚
#define SD_MISO_PIN GPIO_NUM_16
#define SD_CLK_PIN GPIO_NUM_15
#define SD_CS_PIN GPIO_NUM_14
#define MIC_BCLK_PIN GPIO_NUM_25 //麦克风模块引脚
#define MIC_WS_PIN GPIO_NUM_26
#define MIC_DIN_PIN GPIO_NUM_27
#define DISPLAY_WIDTH 240 //屏幕宽度像素
#define DISPLAY_HEIGHT 320 //屏幕高度像素
#define DISPLAY_INVERT_COLOR true //是否反转颜色
虽然音频开发框架提供了一些常用的音频硬件驱动类,但有些音频硬件需要做一些特殊处理,可以通过扩展相关类来实现。
本例使用的SPH0645LM4H芯片为单声道麦克风,数据采用32bit传输,需要做扩展处理。
AudioI2sSph0645类继承自AudioI2sSimplex类,代码如下(unit10-lesson102a/audio_i2s_sph0645.cpp)
AudioI2sSph0645::AudioI2sSph0645(gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din, gpio_num_t mclk)
{
i2s_driver_ = new I2sDriver();
i2s_driver_->setPins(mic_sck, mic_ws, -1, mic_din, mclk);
}
bool AudioI2sSph0645::Init(const audio_config_t &config)
{
Log::Info(TAG, "Init...");
AudioI2sCodec::Init(config);
uint32_t sample_rate = 16000;
if (!i2s_driver_->begin(I2S_MODE_STD, sample_rate, I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO, I2S_STD_SLOT_LEFT)) {
Log::Error(TAG, "Failed to initialize I2S!");
return false;
}
// 对于SPH0645LM4H型号的麦克风,配置一个32bit->16bit的转换程序
i2s_driver_->configureRX(sample_rate, I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO, I2S_RX_TRANSFORM_32_TO_16);
Log::Info(TAG, "i2s initialize success.");
return true;
}
audio_config_t AudioI2sSph0645::audio_config()
{
audio_config_t cfg = {
.input_rate = (sample_rate_t)16000,
.input_bits = (sample_bits_t)32,
.input_channels = (channels_t)1,
.output_rate = (sample_rate_t)i2s_driver_->rxSampleRate(),
.output_bits = (sample_bits_t)i2s_driver_->rxDataWidth(),
.output_channels = (channels_t)i2s_driver_->rxSlotMode()
};
return cfg;
}
代码解读
1.在构造函数内,创建I2sDriver类实例,然后设置引脚;
2.在Init函数内,调用begin方法启动i2s、调用configureRX配置32bit->16bit的音频数据转换程序;
3.重载audio_config()函数,返回一个包含音频采样率、位宽和通道数的配置用于后面的处理,当输入与输出的音频配置不一致时,必须重载此函数;
创建FileSystem实例和Display实例,同前一小节,代码见(unit10-lesson102a/my_board.cpp)
创建AudioCodec实例,代码如下(unit10-lesson102a/my_board.cpp)
void MyBoard::InitAudioCodec() {
Log::Info(TAG, "initial audio codec.");
audio_codec_ = new AudioI2sSph0645(MIC_BCLK_PIN, MIC_WS_PIN, MIC_DIN_PIN);
}
这里使用Sph0645驱动类来构造AudioCodec实例。
音频录制代码(unit10-lesson102a/my_application.cpp)
void MyApplication::OnInit() {
Display *display = Board::GetInstance().GetDisplay();
display->Rotate(1);
FileSystem *fsys = Board::GetInstance().GetFileSystem();
std::string filepath = "/test001.wav";
display->GetWindow()->SetText(1, "Recording to file: " + filepath);
// 音频处理流
// I2sInput(麦克风输入) -> Encoder(编码) -> FileOutput(输出到文件)
uint16_t samples_msec = 500; // 0.5s
// I2S输入
AudioCodec *audio_codec = Board::GetInstance().GetAudioCodec();
AudioI2sInput *input = new AudioI2sInput(audio_codec, samples_msec);
// 编码输出
AudioEncodeOutput *output = new AudioEncodeOutput(fsys, filepath, "wav");
audio_config_t output_config = {
.output_rate = SAMPLE_RATE_16K,
.output_bits = SAMPLE_BITS_16, // 32bit -> 16bit
.output_channels = CHANNELS_1
};
output->SetAudioConfig(output_config);
// 音频管道
pipe_ = new AudioPipe();
// 事件监听
pipe_->SetPipeListener([this,input](PipeAction action){
AudioCodec *codec = Board::GetInstance().GetAudioCodec();
if (action==PipeAction::Inited)
{
codec->Start();
}
else if (action==PipeAction::Ended)
{
codec->EnableInput(false);
Display *display = Board::GetInstance().GetDisplay();
display->GetWindow()->SetText(3, "Record msec: " + std::to_string(input->duration_ms()));
display->GetWindow()->SetText(4, "Audio pipe ended.");
}
else if (action==PipeAction::Processing)
{
Log::Info(TAG, "audio processing...");
info_count_++;
if (info_count_ % 10 == 0)
{ // 取整
int n = (info_count_ / 10) % 2;
Display *display = Board::GetInstance().GetDisplay();
display->GetWindow()->SetText(3, info_text_[n]);
}
}
else if (action==PipeAction::Error)
{
Display *display = Board::GetInstance().GetDisplay();
display->GetWindow()->SetText(4, pipe_->last_error());
}
});
// 启动管道
pipe_->Start(input, output);
Log::Info(TAG, "Audio pipe started.");
}
程序解读
1.创建音频输入AudioInput的实例,本例为麦克风输入,使用AudioI2sInput类,并关联AudioCodec实例;
2.创建音频输出AudioOutput的实例,本例为文件输出,使用Wav编码,故使用AudioEncodeOuput类;
3.创建音频管道实例,设置音频管道监听程序,音频管道处理中会在各处理节点进行回调;
4.启动音频管道;
5.双击按键可停止录制。
编译项目并上传开发板检验
通过音频开发框架,只需要设计音频处理流程,就可以简单快捷的开发音频应用,
更多音频开发的高级内容,请查看后续的【ESP32音频开发教程】
导轨又称滑轨、线性导轨、线性滑轨,用于直线往复运动场合,拥有比直线轴承更高的额定负载, 同时可以承担一定的扭矩,可在高负载的情况下实现高精度的直线运动。
MimiClaw 是一款基于 ESP32-S3 芯片的超轻量级AI助手,适合嵌入式AI与物联网开发者快速部署本地化AI代理。本系列教程基于MimiClaw的Arduino移植版本进行讲解,小节主要讲解部署和测试。
就像我们用手机打开WiFi功能后可以浏览附近的可用WiFi。要将手机连接到热点,通常需要打开Wi-Fi设置应用程序,列出可用的网络,然后选择所需的热点。然后输入密码(或不输入密码),可以使用ESP32进行相同的操作。
本文本介绍配置飞书机器人为MimiClaw的一个输入/输出端,和添加一个控制WS2812与LED的控制技能。
一块 30 块钱的开发板 + 一个大模型 API,就能做出可以听懂人话的智能硬件。 本文记录完整安装过程和踩坑经验,确保你跟着做就能跑通。
本文将从手绘架构图入手,逐层拆解 MimiClaw 的分层设计、核心模块、数据流转与底层实现,带你解剖这只“智能虾”的技术骨架,看懂在 C 语言加持下,AI 智能体如何以可穿戴设备的形态,在你身边稳稳运行、离线服务、主动响应。
本文介绍如何在不脱离 ArduinoIDE 可视化开发的前提下,通过一个名为 platform.local.txt 的小文件,实现对 ESP32 编译流程的精准控制。
本文将系统分析程序体积增长的五大根源,并提供经过验证的优化方案,帮助减小固件大小。
本文所DIY的语音助手设备端使用的是MicroPython、服务端是Python,对于很多开发者来说MicroPython入门没难度。
I2S协议通过BCLK、LRCLK和DATA三线精准传输音频数据,但时序边沿、帧格式、时钟源等细节常引发噪声或断连。本文详解ESP32的I2S实现,从协议原理到ESP-IDF v5.x代码配置,助你避开常见陷阱,确保音频稳定传输。
本小节介绍音频的基础知识、音频开发框架和AudioCodec的简介,用一个音频播放示例来说明音频管道的使用。