【AI小智硬件程序(九)】
16000216。
AI小智硬件程序(九)
链接: B站Up
调用关系重构
这里主要是整理整个代码框架,比如琐碎看视频比较清楚,这里就放关键步骤。
第一部分
1.删除部分
旧功能代码:删除录音相关的旧代码(包括对应的 2 个文件)。
依赖项:删除旧功能对应的组件依赖、头文件依赖。
配置项:在simic list中删除音频相关的配置项。
函数参数 / 调用:删除函数中传入的文件指针、音频指针,以及旧的调用逻辑。
冗余代码:注释 / 删除旧的运行逻辑、测试代码。


2.更改
函数重构:修改录音函数的入参(不再传文件 / 音频指针),改为内部调用板载资源。
资源管理:将原定义在ES7210中的采样率(16KHz)、通道数(2)、位宽(16 位),迁移到当前文件中定义。
调用逻辑:从 “外部传入资源指针” 改为 “内部直接调用板载抽象层”。

3.添加宏定义
#define EXAMPLE_I2S_SAMPLE_RATE 16000
#define EXAMPLE_I2S_CHAN_NUM 2
#define EXAMPLE_I2S_SAMPLE_BITS 16

第二部分
1、添加文件抽象到板子抽象层
把文件系统相关的接口(如文件操作类)迁移到板子抽象层(上层架构),方便不同板子复用。
在板子抽象层中定义 “获取文件接口” 的方法,并在具体实现中重写该方法(返回文件系统指针)。
AudioHAL* audio_hal;
FileInterface* file_interface;
virtual FileInterface* GetFileInterface() = 0;

FileInterface* GetFileInterface() override;
2、修改 SD 卡构造函数
把 SD 卡的硬件引脚(如 CMD、CLK、D0 等)从底层驱动中剥离,改为从上层(板子抽象层)传入。
让 SD 卡驱动仅保留 “驱动逻辑”(与硬件无关),实现驱动与硬件的解耦(SD 卡驱动代码更稳定)。

3、构造文件存储对象
在板子抽象层中实例化 SD 卡对象(传入硬件引脚),完成文件系统的初始化。
通过板子抽象层提供的 “获取文件接口”,后续代码可直接调用文件系统的功能(无需关心底层硬件)。
#define SD_CARD_PIN_CLK GPIO_NUM_47
#define SD_CARD_PIN_CMD GPIO_NUM_48
#define SD_CARD_PIN_D0 GPIO_NUM_21

file_interface = new SdCard(SD_CARD_PIN_CMD,SD_CARD_PIN_CLK,SD_CARD_PIN_D0);
FileInterface* IoelinDevBoard::GetFileInterface()
{
return file_interface;
}
第三部分
1.录音10s测试

WavRecorder recorder;
recorder.record(10);
(1)资源调用补全(替代外部传参)
文件系统调用:从板载抽象层获取文件系统指针,无需外部传参,直接在录音函数内完成 WAV 文件操作:
引入板载头文件,通过Board::getInstance()->getFileFS()获取文件系统指针;
打开test.wav文件,先写入 WAV 文件头,再写入录音数据;
录制完成后关闭文件,确保数据落盘。
音频模块调用:从板载抽象层获取音频抽象层实例,聚焦输入功能:
调用enableInput()使能音频输入;
处理采样数 / 字节数转换:定义的 8192 是字节数,因采样数据为 int16(2 字节),读取时需除以 2 得到采样数(8192),写入文件时直接用8192×2字节数;
循环读取音频采样数据并写入文件,累计录制 10 秒。
(2)测试执行
在运行逻辑中直接调用重构后的录音函数,传入 10 秒录制时长;
编译通过后下载程序到硬件,录制完成后取出 SD 卡,通过读卡器验证test.wav文件生成且录音正常。
2.报错解决
报错 i2c 驱动新的和旧的混用解决
idf.py menuconfig
(这里执行 idf.py menuconfig 异常看笔记里的问题点文件夹里面有解决方案)
报 DMA 错误解决
报 i2c 问题,地址问题将地址左移一位作为 8 位地址传入(组件为了兼容老的音频芯片)
无法录录音使能音频
第四部分
1. 测试播放(完整流程)
这节课的核心是完成 “录音→编码→解码→播放” 全链路测试,整体流程如下:
auto audio_= Board::GetInstance().GetAudioHAL();
audio_->enable_input();
std::printf("开始说话\n");
// 启动录音
vTaskDelay(pdMS_TO_TICKS(1000));
for (size_t i = 0; i < 50; i++)
{
std::vector<int16_t> pcm(960*2);
audio_->read(pcm.data(), pcm.size());
auto mic_channel = std::vector<int16_t>(pcm.size() / 2);
for (size_t i = 0, j = 0; i < mic_channel.size(); ++i, j += 2) {
mic_channel[i] = pcm[j];
}
work_task->add_task([this,mic_channel = std::move(mic_channel)]() mutable{
opus_encoder_->Encode(std::move(mic_channel), [this](std::vector<uint8_t>&& opus){
opus_packets_.emplace_back(std::move(opus));
});
});
}
// audio_->disable_input();
audio_->enable_output();
std::printf("开始播放\n");
for (auto& opus : opus_packets_) {
work_task->add_task([this,opus = std::move(opus),audio_]() mutable{
std::vector<int16_t> decoded_pcm ;
opus_decoder_->Decode(std::move(opus),decoded_pcm);
std::lock_guard<std::mutex> lock(pcm_mutex);
const uint8_t* data_ptr = (const uint8_t*) decoded_pcm.data();
audio_->write((int16_t *)data_ptr,decoded_pcm.size());
});
}
2. 配置功放使能管脚


3.设置播放音量

virtual void set_out_volume(int volume)=0;
int output_volume=90;

virtual void set_out_volume(int volume)override;

void AudioEs8311Es7210::set_out_volume(int volume)
{
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(audio_output_dev, volume));
output_volume = volume;
}
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(audio_output_dev, output_volume));
当前程序框架梳理
1、音频代码

audio_hal 提供音频抽象层,audio_es8311_es7210 作为一个音频类型,这个类型封装了 es8311 和 es7210 两种芯片组合出现在板子的情况,后续板子如果使用的是这种案例则代码无需修改,否则需要实现一个驱动层继承 audio_hal 并实现里面的方法
音频抽象层提供如下方法
void enable_input(); //使能音频输入
void enable_output();//使能音频输出
void disable_input();//关闭音频输入
void disable_output();//关闭音频输出
void write(const int16_t* data,int samples);//播放音频,data为音频pcm数据,samples为样本数
int read(int16_t* data,int samples);//读取音频,data为读取到的pcm数据,samples为样本数
void set_out_volume(int volume);//设置播放音量0-100
2、文件存储系统

file_interface 提供一个抽象接口 sd_card 实现 SD 卡的驱动,并将 IO 口抽象出来供上层设置
file_interface 提供如下方法
/**打开文件,传入文件名及打开模式 */
esp_err_t open(const char* filename, const char* mode);
/**关闭文件 */
esp_err_t close();
/**读取文件内容 */
esp_err_t read_file( char* output, size_t output_size);
/**写入文件内容, size为写入的字节数 */
esp_err_t write_file( const char* data, size_t size);
esp_err_t read_line(int line_num, char *output, size_t output_size);
esp_err_t seek(size_t offset,SeekMode mode);
3、封装所有板载资源

board 作为板子抽象层向上统一提供板子的抽象资源,使用单例模式,然后后静态工厂方法提供给底层板子注册,ioelin_dev_board 作为当前板子的驱动层,处理硬件相关的配置,例如管脚的配置,在 ioelin_dev_board 里面实例化了音频对象,传入音频相关的 IO 口,还实例化了文件存储对象(SD 卡)传入对应的 IO 口,后续还会有显示相关的对象都在这里进行实例化,实例化各种对象提供接口给抽象层 board,由 board 抽象层暴露给应用层使用
后续如果有其他板子需要和 ioelin_dev_board 类似写法继承 board 覆写 board 的方法
board 目前提供如下方法
AudioHAL* GetAudioHAL(); //获取音频对象
FileInterface* GetFileInterface();//获取文件存储对象
4、应用层代码

5、关于编译



更多推荐


所有评论(0)