链接: 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、关于编译

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐