突破ESP32内存墙:音频对象与I2S接口完全释放技术深度解析
引言:嵌入式音频开发的内存困境
你是否还在为ESP32音频项目中的内存泄漏问题而头疼?当系统运行一段时间后,音频播放卡顿、杂音甚至崩溃,这些问题的根源往往在于音频对象与I2S接口资源未能被正确释放。本文将深入剖析ESP32-audioI2S项目中的音频对象管理与I2S接口释放技术,带你彻底解决内存泄漏难题,打造稳定可靠的嵌入式音频应用。
读完本文,你将获得:
- 理解ESP32-audioI2S项目中音频对象的生命周期管理
- 掌握I2S接口资源的完全释放方法
- 学会使用ps_ptr智能指针进行内存管理
- 了解音频缓冲区的优化策略
- 掌握调试内存泄漏的实用技巧
音频对象模型:Audio类的核心设计
Audio类的结构概览
ESP32-audioI2S项目的核心是Audio类,它封装了音频播放的所有关键功能。以下是Audio类的主要组成部分:
class Audio {
public:
// 构造函数与析构函数
Audio(uint8_t i2sPort = I2S_NUM_0);
~Audio();
// 音频控制方法
bool connecttohost(const char* host, const char* user = "", const char* pwd = "");
bool connecttoFS(fs::FS& fs, const char* path, int32_t fileStartTime = -1);
uint32_t stopSong();
void loop();
// 音频配置方法
bool setPinout(uint8_t BCLK, uint8_t LRC, uint8_t DOUT, int8_t MCLK = I2S_GPIO_UNUSED);
void setVolume(uint8_t vol, uint8_t curve = 0);
void forceMono(bool m);
// 状态查询方法
bool isRunning() { return m_f_running; }
uint32_t getAudioCurrentTime();
uint32_t getAudioFileDuration();
private:
// I2S相关成员
i2s_chan_handle_t m_i2s_tx_handle;
i2s_chan_config_t m_i2s_chan_cfg;
i2s_std_config_t m_i2s_std_cfg;
// 音频缓冲区
AudioBuffer InBuff;
// 解码器相关成员
int m_codec;
// ...其他解码器相关变量
// 任务与同步机制
TaskHandle_t m_audioTaskHandle;
SemaphoreHandle_t mutex_playAudioData;
SemaphoreHandle_t mutex_audioTask;
// 内部方法
void setDefaults();
void startAudioTask();
void stopAudioTask();
void audioTask();
// ...其他内部方法
};
音频对象的生命周期
Audio对象的生命周期从创建到销毁经历了多个阶段,每个阶段都需要精心管理以避免资源泄漏:
- 初始化阶段:构造函数中初始化I2S接口、创建信号量和任务。
- 运行阶段:通过connecttohost或connecttoFS方法开始音频播放,音频任务处理解码和输出。
- 停止阶段:调用stopSong方法停止播放,释放解码器资源。
- 销毁阶段:析构函数中停止音频任务、释放I2S接口和其他资源。
以下是Audio类构造函数和析构函数的关键实现:
Audio::Audio(uint8_t i2sPort) {
// 创建信号量
mutex_playAudioData = xSemaphoreCreateMutex();
mutex_audioTask = xSemaphoreCreateMutex();
// 检查PSRAM是否可用
if(!psramFound()) AUDIO_LOG_ERROR("audioI2S requires PSRAM!");
// 配置I2S通道
memset(&m_i2s_chan_cfg, 0, sizeof(i2s_chan_config_t));
m_i2s_chan_cfg.id = (i2s_port_t)m_i2s_num;
m_i2s_chan_cfg.role = I2S_ROLE_MASTER;
m_i2s_chan_cfg.dma_desc_num = 16;
m_i2s_chan_cfg.dma_frame_num = 512;
m_i2s_chan_cfg.auto_clear = true;
m_i2s_chan_cfg.allow_pd = false;
i2s_new_channel(&m_i2s_chan_cfg, &m_i2s_tx_handle, NULL);
// 配置I2S标准模式
memset(&m_i2s_std_cfg, 0, sizeof(i2s_std_config_t));
m_i2s_std_cfg.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO);
// ...其他I2S配置
// 启动音频任务
startAudioTask();
}
Audio::~Audio() {
stopSong();
setDefaults();
// 禁用并删除I2S通道
i2s_channel_disable(m_i2s_tx_handle);
i2s_del_channel(m_i2s_tx_handle);
// 停止音频任务
stopAudioTask();
// 删除信号量
vSemaphoreDelete(mutex_playAudioData);
vSemaphoreDelete(mutex_audioTask);
}
I2S接口管理:从初始化到释放
I2S接口初始化
I2S(Inter-IC Sound)是一种用于数字音频设备之间通信的串行总线标准。在ESP32-audioI2S项目中,I2S接口的初始化是音频播放的基础。以下是I2S接口初始化的关键步骤:
esp_err_t Audio::I2Sstart() {
zeroI2Sbuff();
return i2s_channel_enable(m_i2s_tx_handle);
}
void Audio::zeroI2Sbuff() {
uint8_t buff[2] = {0, 0};
size_t bytes_loaded = 0;
i2s_channel_preload_data(m_i2s_tx_handle, buff, 2, &bytes_loaded);
}
bool Audio::setPinout(uint8_t BCLK, uint8_t LRC, uint8_t DOUT, int8_t MCLK) {
m_i2s_std_cfg.gpio_cfg.bclk = BCLK;
m_i2s_std_cfg.gpio_cfg.ws = LRC;
m_i2s_std_cfg.gpio_cfg.dout = DOUT;
m_i2s_std_cfg.gpio_cfg.mclk = MCLK;
esp_err_t err = i2s_channel_init_std_mode(m_i2s_tx_handle, &m_i2s_std_cfg);
if (err != ESP_OK) {
AUDIO_LOG_ERROR("i2s_channel_init_std_mode failed: %d", err);
return false;
}
return true;
}
I2S接口释放
I2S接口的释放是防止资源泄漏的关键步骤。在ESP32-audioI2S项目中,I2S接口的释放主要包括禁用通道和删除通道两个步骤:
esp_err_t Audio::I2Sstop() {
m_outBuff.clear(); // 清除输出缓冲区
m_samplesBuff48K.clear(); // 清除样本缓冲区
std::fill(std::begin(m_inputHistory), std::end(m_inputHistory), 0); // 清除历史数据
return i2s_channel_disable(m_i2s_tx_handle);
}
// 在析构函数中删除I2S通道
i2s_del_channel(m_i2s_tx_handle);
I2S资源完全释放的检查清单
为确保I2S接口资源被完全释放,建议使用以下检查清单:
- 调用
i2s_channel_disable禁用I2S通道 - 清除与I2S相关的缓冲区
- 调用
i2s_del_channel删除I2S通道 - 检查是否有未完成的I2S数据传输
- 确认I2S相关的GPIO引脚已释放
智能指针:ps_ptr的内存管理革命
ps_ptr的设计与实现
在嵌入式系统开发中,内存管理是一个永恒的挑战。ESP32-audioI2S项目引入了ps_ptr智能指针,专门用于管理PSRAM(伪静态随机存取存储器)中的内存分配。ps_ptr的设计借鉴了C++11的std::unique_ptr,但针对ESP32的PSRAM进行了优化。
template <typename T>
class ps_ptr {
private:
std::unique_ptr<T[], PsramDeleter> mem;
size_t allocated_size = 0;
char* name = nullptr;
public:
// 构造函数与析构函数
ps_ptr() = default;
explicit ps_ptr(const char* name_str);
~ps_ptr();
// 内存分配方法
void alloc(std::size_t size, const char* alloc_name = nullptr, bool usePSRAM = true);
void calloc(std::size_t num_elements, const char* alloc_name = nullptr, bool usePSRAM = true);
// 内存释放方法
void reset();
void clear();
// 内存访问方法
T* get() const { return mem.get(); }
operator T*() const { return mem.get(); }
T& operator[](std::size_t index) const { return mem.get()[index]; }
// 内存状态查询
bool valid() const { return mem != nullptr; }
size_t size() const { return allocated_size; }
// 字符串操作方法(仅当T为char时可用)
void assign(const char* src);
void append(const char* suffix);
bool starts_with(const char* prefix) const;
bool ends_with(const char* suffix) const;
};
ps_ptr的内存管理优势
ps_ptr智能指针相比传统的手动内存管理具有以下优势:
- 自动释放:通过RAII(资源获取即初始化)机制,确保内存在离开作用域时自动释放。
- 防内存泄漏:避免因忘记调用free()而导致的内存泄漏。
- PSRAM优化:优先使用PSRAM,充分利用ESP32的扩展内存。
- 内存对齐:自动进行16字节内存对齐,提高访问效率。
- 调试友好:支持命名分配,便于内存泄漏追踪。
ps_ptr在音频缓冲区管理中的应用
在ESP32-audioI2S项目中,ps_ptr被广泛应用于音频缓冲区管理。以下是AudioBuffer类中使用ps_ptr管理缓冲区的示例:
class AudioBuffer {
private:
ps_ptr<uint8_t> m_buffer;
uint8_t* m_writePtr = NULL;
uint8_t* m_readPtr = NULL;
uint8_t* m_endPtr = NULL;
size_t m_buffSize = UINT16_MAX * 10;
size_t m_resBuffSize = 4096 * 6;
// ...其他成员变量
public:
AudioBuffer(size_t maxBlockSize = 0);
~AudioBuffer();
size_t init() {
m_buffer.alloc(m_buffSize + m_resBuffSize, "AudioBuffer");
m_f_init = true;
resetBuffer();
return m_buffSize;
}
void resetBuffer() {
m_writePtr = m_buffer.get();
m_readPtr = m_buffer.get();
m_endPtr = m_buffer.get() + m_buffSize;
m_f_isEmpty = true;
}
// ...其他方法
};
音频缓冲区优化:环形缓冲与内存效率
AudioBuffer的环形缓冲区设计
ESP32-audioI2S项目中的AudioBuffer类采用了环形缓冲区(Ring Buffer)设计,高效管理音频数据的读写。环形缓冲区的核心思想是使用一个固定大小的缓冲区,通过两个指针(读指针和写指针)来标记数据的有效区域。
// AudioBuffer的内存布局
// m_buffer m_readPtr m_writePtr m_endPtr
// | |<------dataLength------->|<------ writeSpace ----->|
// ▼ ▼ ▼ ▼
// ---------------------------------------------------------------------------------------------------------------
// | <--m_buffSize--> | <--m_resBuffSize --> |
// ---------------------------------------------------------------------------------------------------------------
// |<-----freeSpace------->| |<------freeSpace-------->|
缓冲区读写操作
以下是环形缓冲区的关键读写操作实现:
size_t AudioBuffer::freeSpace() {
if(m_readPtr == m_writePtr) {
if(m_f_isEmpty == true) m_freeSpace = m_buffSize;
else m_freeSpace = 0;
}
if(m_readPtr < m_writePtr) {
m_freeSpace = (m_endPtr - m_writePtr) + (m_readPtr - m_buffer.get());
}
if(m_readPtr > m_writePtr) {
m_freeSpace = m_readPtr - m_writePtr;
}
return m_freeSpace;
}
size_t AudioBuffer::bufferFilled() {
if(m_readPtr == m_writePtr) {
if(m_f_isEmpty == true) m_dataLength = 0;
else m_dataLength = m_buffSize;
}
if(m_readPtr < m_writePtr) {
m_dataLength = m_writePtr - m_readPtr;
}
if(m_readPtr > m_writePtr) {
m_dataLength = (m_endPtr - m_readPtr) + (m_writePtr - m_buffer.get());
}
return m_dataLength;
}
void AudioBuffer::bytesWritten(size_t bw) {
if(!bw) return;
m_writePtr += bw;
if(m_writePtr == m_endPtr) { m_writePtr = m_buffer.get(); }
if(m_writePtr > m_endPtr) log_e("AudioBuffer: m_writePtr %p > m_endPtr %p", m_writePtr, m_endPtr);
m_f_isEmpty = false;
}
void AudioBuffer::bytesWasRead(size_t br) {
if(!br) return;
m_readPtr += br;
if(m_readPtr >= m_endPtr) {
size_t tmp = m_readPtr - m_endPtr;
m_readPtr = m_buffer.get() + tmp;
}
if(m_readPtr == m_writePtr) m_f_isEmpty = true;
}
缓冲区大小优化策略
缓冲区大小的选择需要在内存占用和播放流畅性之间取得平衡。以下是一些优化策略:
- 根据音频格式调整:不同的音频格式需要不同的缓冲区大小。例如,FLAC格式需要更大的缓冲区。
void AudioBuffer::changeMaxBlockSize(uint16_t mbs) {
m_maxBlockSize = mbs;
return;
}
// 在Audio类中根据解码器类型调整缓冲区
void Audio::setDecoderItems() {
switch (m_codec) {
case CODEC_FLAC:
InBuff.changeMaxBlockSize(m_frameSizeFLAC);
break;
case CODEC_OPUS:
InBuff.changeMaxBlockSize(m_frameSizeOPUS);
break;
// ...其他解码器类型
default:
InBuff.changeMaxBlockSize(m_frameSizeMP3);
break;
}
}
- 动态调整缓冲区大小:根据可用内存动态调整缓冲区大小,避免内存溢出。
bool AudioBuffer::setBufsize(size_t mbs) {
if(mbs < 2 * m_resBuffSize) {
log_e("not allowed buffer size must be greater than %i", 2 * m_resBuffSize);
return false;
}
m_buffSize = mbs;
if(!init()) return false;
return true;
}
- 使用PSRAM扩展内存:优先使用PSRAM存储音频数据,释放宝贵的DRAM空间。
void ps_ptr<T>::alloc(std::size_t size, const char* alloc_name, bool usePSRAM) {
size = (size + 15) & ~15; // 16字节对齐
if (psramFound() && usePSRAM) {
mem.reset(static_cast<T*>(ps_malloc(size)));
} else {
mem.reset(static_cast<T*>(malloc(size)));
}
// ...其他内存分配逻辑
}
解码器资源管理:从初始化到释放
多解码器架构
ESP32-audioI2S项目支持多种音频格式的解码,包括MP3、AAC、FLAC、OPUS等。每种解码器都有其独立的初始化和释放函数:
// 解码器初始化函数
bool Audio::initializeDecoder(uint8_t codec) {
switch (codec) {
case CODEC_MP3:
return MP3Decoder_Init() == 0;
case CODEC_AAC:
return AACDecoder_Init() == 0;
case CODEC_FLAC:
return FLACDecoder_Init() == 0;
case CODEC_OPUS:
return OPUSDecoder_Init() == 0;
case CODEC_VORBIS:
return VORBISDecoder_Init() == 0;
default:
return false;
}
}
// 解码器释放函数
void Audio::setDefaults() {
MP3Decoder_FreeBuffers();
FLACDecoder_FreeBuffers();
AACDecoder_FreeBuffers();
OPUSDecoder_FreeBuffers();
VORBISDecoder_FreeBuffers();
// ...其他资源释放
}
解码器内存管理
每个解码器都需要专用的内存缓冲区来存储解码过程中的中间数据。这些缓冲区通常使用ps_ptr进行管理,确保在解码器释放时能够正确释放内存:
// MP3解码器缓冲区管理示例
void MP3Decoder_FreeBuffers() {
if (hMP3Decoder) {
mpg123_delete(hMP3Decoder);
hMP3Decoder = nullptr;
}
mp3_outBuff.clear();
mp3_inBuff.clear();
}
解码器切换时的资源清理
在切换解码器时,需要确保前一个解码器的资源被完全释放,避免内存泄漏:
int Audio::decodeError(int8_t res, uint8_t* data, int32_t bytesDecoded) {
// 释放当前解码器资源
switch (m_codec) {
case CODEC_MP3:
MP3Decoder_FreeBuffers();
break;
case CODEC_AAC:
AACDecoder_FreeBuffers();
break;
// ...其他解码器
}
// 尝试重新初始化解码器
if (initializeDecoder(m_codec)) {
return 0;
} else {
// 解码器初始化失败,切换到错误处理流程
return -1;
}
}
实战案例:音频播放完整流程与资源释放
音频文件播放流程
以下是从SD卡播放音频文件的完整流程,展示了音频对象的创建、使用和释放过程:
#include "Audio.h"
// 创建Audio对象
Audio audio;
void setup() {
Serial.begin(115200);
// 初始化SD卡
if(!SD.begin()){
Serial.println("SD卡初始化失败");
return;
}
// 配置I2S引脚
audio.setPinout(27, 26, 25); // BCLK, LRC, DOUT
// 设置音量
audio.setVolume(21); // 0-21
}
void loop() {
static bool playing = false;
if(!playing){
// 开始播放SD卡中的音频文件
if(audio.connecttoFS(SD, "/test.mp3")){
Serial.println("开始播放音频");
playing = true;
} else {
Serial.println("无法打开音频文件");
delay(1000);
}
}
// 音频播放主循环
audio.loop();
// 检查是否播放结束
if(playing && !audio.isRunning()){
Serial.println("音频播放结束");
playing = false;
// 音频对象会在内部自动释放资源
// 无需手动调用额外的释放函数
}
}
音频任务管理
ESP32-audioI2S项目使用FreeRTOS任务来处理音频解码和播放,确保主循环不会被阻塞。以下是音频任务的创建和管理代码:
void Audio::startAudioTask() {
if (m_audioTaskHandle == nullptr) {
// 创建静态任务
m_audioTaskHandle = xTaskCreateStatic(
taskWrapper, // 任务函数包装器
"AudioTask", // 任务名称
AUDIO_STACK_SIZE, // 任务堆栈大小
this, // 任务参数
5, // 任务优先级
xAudioStack, // 任务堆栈缓冲区
&xAudioTaskBuffer // 任务控制块
);
}
}
void Audio::stopAudioTask() {
if (m_audioTaskHandle != nullptr) {
vTaskDelete(m_audioTaskHandle);
m_audioTaskHandle = nullptr;
}
}
// 任务函数包装器
static void Audio::taskWrapper(void* param) {
static_cast<Audio*>(param)->audioTask();
}
// 音频任务主函数
void Audio::audioTask() {
while (true) {
xSemaphoreTake(mutex_audioTask, portMAX_DELAY);
if (m_f_running) {
performAudioTask();
} else {
vTaskDelay(pdMS_TO_TICKS(10));
}
xSemaphoreGive(mutex_audioTask);
}
}
资源释放完整流程
当音频播放结束或调用stopSong()方法时,系统会执行一系列资源释放操作:
uint32_t Audio::stopSong() {
if (!m_f_running) return 0;
m_f_running = false;
// 等待音频任务完成
if (m_audioTaskHandle) {
xSemaphoreTake(mutex_audioTask, portMAX_DELAY);
xSemaphoreGive(mutex_audioTask);
}
// 禁用I2S通道
I2Sstop();
// 释放解码器缓冲区
setDefaults();
// 关闭网络连接
client.stop();
clientsecure.stop();
AUDIO_LOG_INFO("Audio stopped");
return m_audioCurrentTime;
}
void Audio::setDefaults() {
// 释放输入缓冲区
InBuff.resetBuffer();
// 释放解码器缓冲区
MP3Decoder_FreeBuffers();
FLACDecoder_FreeBuffers();
AACDecoder_FreeBuffers();
OPUSDecoder_FreeBuffers();
VORBISDecoder_FreeBuffers();
// 清除播放列表
vector_clear_and_shrink(m_playlistURL);
vector_clear_and_shrink(m_playlistContent);
// 清除输出缓冲区
m_outBuff.clear();
m_samplesBuff48K.clear();
// 重置各种标志位
m_f_playing = false;
m_f_metadata = false;
m_f_eof = false;
// ...其他标志位重置
}
调试与优化:内存泄漏检测与性能调优
内存泄漏检测方法
内存泄漏是嵌入式系统中常见的问题,以下是一些检测内存泄漏的实用方法:
- 使用ESP32内置内存检测函数:
// 打印当前可用内存
AUDIO_LOG_DEBUG("Free Heap: %lu bytes", (long unsigned int)ESP.getFreeHeap());
AUDIO_LOG_DEBUG("Free PSRAM: %lu bytes", (long unsigned int)ESP.getFreePsram());
- 跟踪内存分配与释放:
ps_ptr智能指针支持命名分配,可以在调试时跟踪每个内存块的分配和释放:
// 命名分配示例
ps_ptr<char> url("url_buffer");
url.alloc(256);
// 在ps_ptr析构函数中添加调试信息
ps_ptr<T>::~ps_ptr() {
if (mem) {
AUDIO_LOG_DEBUG("Freeing %s: %zu bytes at %p", name ? name : "unnamed", allocated_size, mem.get());
}
}
- 使用heap_trace工具:
ESP-IDF提供了heap_trace工具,可以跟踪内存分配和释放,帮助定位内存泄漏:
#include "esp_heap_trace.h"
void start_heap_trace() {
heap_trace_init_standalone();
heap_trace_start(HEAP_TRACE_LEAKS);
}
void stop_heap_trace() {
heap_trace_stop();
heap_trace_dump();
}
性能优化技巧
- 使用PSRAM存储大缓冲区:
// 优先使用PSRAM分配大缓冲区
ps_ptr<char> large_buffer;
large_buffer.alloc(1024 * 1024, "large_buffer", true); // 第三个参数设为true表示优先使用PSRAM
- 减少内存碎片:
- 尽量使用固定大小的内存分配
- 避免频繁的内存分配和释放
- 使用内存池管理频繁分配的小内存块
- 优化缓冲区大小:
根据实际需求调整缓冲区大小,避免过度分配:
// 根据音频格式调整缓冲区大小
if (codec == CODEC_FLAC) {
InBuff.setBufsize(1024 * 64); // FLAC需要更大的缓冲区
} else {
InBuff.setBufsize(1024 * 32); // 其他格式使用较小的缓冲区
}
- 使用中断和DMA:
ESP32的I2S接口支持DMA传输,可以大大提高音频播放的效率,减少CPU占用:
// 配置I2S DMA参数
m_i2s_chan_cfg.dma_desc_num = 16; // DMA描述符数量
m_i2s_chan_cfg.dma_frame_num = 512; // 每个DMA缓冲区的帧数
结论与展望
技术总结
本文深入剖析了ESP32-audioI2S项目中的音频对象管理与I2S接口释放技术,主要内容包括:
- 音频对象模型:Audio类的设计与实现,包括构造函数、析构函数和核心方法。
- I2S接口管理:I2S接口的初始化、配置和释放流程。
- 智能指针ps_ptr:专为ESP32 PSRAM优化的智能指针,用于自动内存管理。
- 音频缓冲区:环形缓冲区设计与实现,高效管理音频数据。
- 解码器资源管理:多种音频解码器的初始化与释放。
- 实战案例:音频播放完整流程与资源释放。
- 调试与优化:内存泄漏检测与性能优化技巧。
通过合理运用这些技术,可以有效解决ESP32音频项目中的内存泄漏问题,提高系统稳定性和可靠性。
未来展望
ESP32-audioI2S项目仍有很大的优化空间:
- 更智能的缓冲区管理:根据音频比特率和采样率动态调整缓冲区大小。
- 多线程解码:利用ESP32的双核优势,将解码和播放分配到不同核心。
- 硬件加速解码:探索使用ESP32的硬件编解码器加速音频解码。
- 更完善的错误恢复机制:提高系统在恶劣环境下的容错能力。
- 低功耗优化:通过动态调整CPU频率和关闭 unused 外设来降低功耗。
随着物联网和嵌入式音频应用的不断发展,ESP32-audioI2S项目将继续优化和完善,为开发者提供更强大、更稳定的音频解决方案。
参考资料
- ESP32官方文档:https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/
- ESP32-audioI2S项目仓库:https://gitcode.com/gh_mirrors/es/ESP32-audioI2S
- "Real-Time C++" by Christopher Kormanyos
- "Embedded Systems: Real-Time Operating Systems for Arm Cortex-M Microcontrollers" by Jonathan Valvano
- ESP32 I2S驱动文档:https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/i2s.html
附录:常用API参考
Audio类核心API
| 方法 | 描述 |
|---|---|
Audio(uint8_t i2sPort = I2S_NUM_0) | 构造函数,指定I2S端口 |
~Audio() | 析构函数,释放所有资源 |
bool connecttohost(const char* host) | 连接到网络音频流 |
bool connecttoFS(fs::FS& fs, const char* path) | 从文件系统打开音频文件 |
uint32_t stopSong() | 停止音频播放,释放资源 |
void loop() | 音频播放主循环 |
bool setPinout(uint8_t BCLK, uint8_t LRC, uint8_t DOUT) | 配置I2S引脚 |
void setVolume(uint8_t vol) | 设置音量,范围0-21 |
bool isRunning() | 检查音频是否正在播放 |
uint32_t getAudioCurrentTime() | 获取当前播放时间(秒) |
uint32_t getAudioFileDuration() | 获取音频文件总时长(秒) |
ps_ptr智能指针API
| 方法 | 描述 |
|---|---|
void alloc(size_t size, const char* name = nullptr) | 分配内存 |
void calloc(size_t num_elements, const char* name = nullptr) | 分配并清零内存 |
void reset() | 释放内存 |
T* get() const | 获取原始指针 |
bool valid() const | 检查内存是否有效 |
size_t size() const | 获取分配的内存大小 |
void assign(const char* src) | 字符串赋值(仅对char类型) |
void append(const char* suffix) | 字符串追加(仅对char类型) |
bool starts_with(const char* prefix) const | 检查字符串前缀(仅对char类型) |
bool ends_with(const char* suffix) const | 检查字符串后缀(仅对char类型) |
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



