通过前一小节了解音频开发框架后,就可设计一个音频录制的处理流程,如下:
麦克风输入->音频编码->输出到文件
下面我们来实现这个流程。
本例使用一个支持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 = {
.rate = (sample_rate_t)i2s_driver_->rxSampleRate(),
.bits = (sample_bits_t)i2s_driver_->rxDataWidth(),
.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);
// 编码输出
AudioEncoderOutput *output = new AudioEncoderOutput(fsys, filepath, "wav");
audio_config_t output_config = {
.rate = SAMPLE_RATE_16K,
.bits = SAMPLE_BITS_16, // 32bit -> 16bit
.channels = CHANNELS_1
};
output->SetAudioConfig(output_config);
// 音频管道
pipe_ = new AudioPipe();
// 事件监听
pipe_->SetPipeListener([this,input](PipeAction action){
AudioCodec *codec = Board::GetInstance().GetAudioCodec();
if (action==PipeAction::Begin)
{
codec->EnableInput(true); // 使能输入
}
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编码,故使用AudioEncoderOuput类;
3.创建音频管道实例,设置音频管道监听程序,音频管道处理中会在各处理节点进行回调;
4.启动音频管道;
5.双击按键可停止录制。
编译项目并上传开发板检验
通过音频开发框架,只需要设计音频处理流程,就可以简单快捷的开发音频应用,
更多音频开发的高级内容,请查看后续的【ESP32音频开发教程】
FreeRTOS是一个迷你的实时操作系统内核。作为一个轻量级的操作系统,功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满足较小系统的需要。
I2S协议通过BCLK、LRCLK和DATA三线精准传输音频数据,但时序边沿、帧格式、时钟源等细节常引发噪声或断连。本文详解ESP32的I2S实现,从协议原理到ESP-IDF v5.x代码配置,助你避开常见陷阱,确保音频稳定传输。
本小节介绍音频的基础知识、音频开发框架和AudioCodec的简介,用一个音频播放示例来说明音频管道的使用。
MimiClaw是基于ESP32-S3芯片的超轻量级AI助手,通过Telegram或WebSocket提供Claude/GPT智能服务。
本小节是一个Web服务结合SD卡文件系统的应用示例。
本节主要讲解FileSystem类的使用,以及Flash文件系统配置和SD存储模块的使用。
本节主要讲解Wifi热点的Web服务使用,以及使用网页交互来控制LED。
本节主要讲解WifiBoard类的功能和HTTPClient库及cJSON的使用。
本节主要讲解TFT-LCD显示屏的使用和Window派生类与TFT_eSPI库的使用。
这篇文章展示了如何将化学与工程、信息技术、现代制造技术紧密结合,以“血氧指标控制的简易供氧器”为载体,组织一次真实的跨学科项目。设计中突出“从需求出发”“闭环控制”“可视化反馈”,不仅呼应了新课标中“跨学科实践”的要求,更贴近生活实际需求,尤其适用于对科技应用、健康关怀有兴趣的学生群体,可作为项目式学习或社团活动的优质课例。
本节主要讲解OLED显示屏的使用和Display类及派生类的介绍及使用。