【AI小智硬件程序(九)】

AI小智硬件程序(九)

链接: B站Up

调用关系重构

这里主要是整理整个代码框架,比如琐碎看视频比较清楚,这里就放关键步骤。

第一部分

1.删除部分

旧功能代码:删除录音相关的旧代码(包括对应的 2 个文件)。

依赖项:删除旧功能对应的组件依赖、头文件依赖。

配置项:在simic list中删除音频相关的配置项。

函数参数 / 调用:删除函数中传入的文件指针、音频指针,以及旧的调用逻辑。

冗余代码:注释 / 删除旧的运行逻辑、测试代码。


2.更改

函数重构:修改录音函数的入参(不再传文件 / 音频指针),改为内部调用板载资源。

资源管理:将原定义在ES7210中的采样率(16KHz)、通道数(2)、位宽(16 位),迁移到当前文件中定义。

调用逻辑:从 "外部传入资源指针" 改为 "内部直接调用板载抽象层"。

3.添加宏定义

cpp 复制代码
#define EXAMPLE_I2S_SAMPLE_RATE 16000
#define EXAMPLE_I2S_CHAN_NUM 2
#define EXAMPLE_I2S_SAMPLE_BITS 16

第二部分

1、添加文件抽象到板子抽象层

把文件系统相关的接口(如文件操作类)迁移到板子抽象层(上层架构),方便不同板子复用。

在板子抽象层中定义 "获取文件接口" 的方法,并在具体实现中重写该方法(返回文件系统指针)。

cpp 复制代码
    AudioHAL* audio_hal;
    FileInterface* file_interface;
    
    
     virtual FileInterface* GetFileInterface() = 0;
cpp 复制代码
    FileInterface* GetFileInterface() override;

2、修改 SD 卡构造函数

把 SD 卡的硬件引脚(如 CMD、CLK、D0 等)从底层驱动中剥离,改为从上层(板子抽象层)传入。

让 SD 卡驱动仅保留 "驱动逻辑"(与硬件无关),实现驱动与硬件的解耦(SD 卡驱动代码更稳定)。

3、构造文件存储对象

在板子抽象层中实例化 SD 卡对象(传入硬件引脚),完成文件系统的初始化。

通过板子抽象层提供的 "获取文件接口",后续代码可直接调用文件系统的功能(无需关心底层硬件)。

cpp 复制代码
#define SD_CARD_PIN_CLK  GPIO_NUM_47
#define SD_CARD_PIN_CMD  GPIO_NUM_48
#define SD_CARD_PIN_D0   GPIO_NUM_21
cpp 复制代码
 file_interface = new SdCard(SD_CARD_PIN_CMD,SD_CARD_PIN_CLK,SD_CARD_PIN_D0);
 
 FileInterface* IoelinDevBoard::GetFileInterface() 
{
    return file_interface;
}

第三部分

1.录音10s测试

cpp 复制代码
    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. 测试播放(完整流程)

这节课的核心是完成 "录音→编码→解码→播放" 全链路测试,整体流程如下:

cpp 复制代码
 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.设置播放音量

cpp 复制代码
virtual void set_out_volume(int volume)=0;

int output_volume=90;
cpp 复制代码
virtual void set_out_volume(int volume)override;
cpp 复制代码
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 并实现里面的方法

音频抽象层提供如下方法

cpp 复制代码
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 提供如下方法

cpp 复制代码
    /**打开文件,传入文件名及打开模式 */
    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 目前提供如下方法

cpp 复制代码
AudioHAL* GetAudioHAL(); //获取音频对象
FileInterface* GetFileInterface();//获取文件存储对象

4、应用层代码

5、关于编译


相关推荐
深圳佛手2 小时前
未来已来,首款AI手机“豆包手机”问世
人工智能·智能手机
力学与人工智能2 小时前
博士学位答辩PPT分享 | 基于机器学习的复杂流场预测方法研究
人工智能·机器学习·西北工业大学·航空航天·答辩·博士学位·ppt分享
视觉震撼2 小时前
为大型语言模型(LLM)自动化知识图谱流水线:2026年手册
人工智能·算法·机器学习
Hi202402172 小时前
使用星图AI算力平台训练PETRV2-BEV模型
人工智能·自动驾驶·gpu·机器视觉·bev·算力平台
Li emily2 小时前
如何获取免费加密货币历史数据和实时行情接口
人工智能·api·美股
中科天工2 小时前
解锁效率革命:智能包装的工业4.0实践
大数据·人工智能·智能
明明如月学长2 小时前
别再神话 Claude Skills 了:这 12 个“致命”局限性你必须知道
人工智能
aigcapi2 小时前
GPT API 哪家好?2026 企业级优选,4SAPI(星链引擎)凭四大核心优势领跑
大数据·人工智能·gpt
围炉聊科技2 小时前
GLM-Image:国产芯片训练的混合架构图像生成模型解析
人工智能