用 ESP32S3 + PCM5102A 打造一个U盘音乐播放器(二)
·
这次使用轻量 红外线 解码,加入两段eq均衡器,通过红外遥控 可以控制 ,对
pcm5102a 的初始化做了调整,补齐了,系统崩溃重启的短板。本来想搞5段eq均衡
但是 就是播放卡顿,实在郁闷,大神们谁有好eq算法,交流一下啊。
#include <Arduino.h>
#include <driver/i2s.h>
#include <esp_heap_caps.h>
#include "flac.h"
#include "usb/usb_host.h"
#include "msc_host.h"
#include "msc_host_vfs.h"
#include <dirent.h>
#include "esp_random.h"
#include "bootloader_random.h"
#include <MP3DecoderMAD.h>
#include <vector>
#include <String>
#include <cstring>
#include <cmath>
#include "driver/rtc_io.h"
// ========== 硬件引脚定义(语义化命名) ==========
#define IR_RECEIVE_PIN 2 // 红外接收引脚
#define I2S_BIT_CLOCK_PIN 17 // I2S位时钟引脚
#define I2S_DATA_OUT_PIN 18 // I2S数据输出引脚
#define I2S_LR_CLOCK_PIN 8 // I2S左右声道时钟引脚
#define I2S_PORT_NUM I2S_NUM_0 // I2S端口号
// ========== 基础配置常量(语义化命名) ==========
#define USB_MOUNT_PATH "/usb" // USB挂载路径
#define MAD_DECODE_BUFFER_SIZE 8192 // MP3解码缓冲区大小
#define MAD_PCM_BUFFER_SIZE 4096 // MP3 PCM输出缓冲区大小
#define MP3_READ_BUFFER_SIZE 8192 // MP3文件读取缓冲区大小
#define FLAC_READ_BUFFER_SIZE 8192 // FLAC文件读取缓冲区大小
#define EQ_FREQ_55HZ 62.0f // EQ 55Hz中心频率
#define EQ_FREQ_8000HZ 8000.0f // EQ 8000Hz中心频率
#define EQ_Q_FACTOR 0.707f // EQ Q值
#define AUDIO_SAMPLE_RATE 44100 // 默认音频采样率
#define MAX_RANDOM_RETRY_COUNT 1000 // 随机选曲最大重试次数
#define DECODE_TASK_STACK_SIZE 48 * 1024 // 解码任务栈大小
#define AUDIO_LIST_CACHE "/usb/audio_list.cache" // 音频列表缓存路径
// ========== 深度睡眠配置 ==========
#define PAUSE_SLEEP_TIMEOUT_MS 10000 // 暂停超时深度睡眠时间(10分钟)
#define WAKEUP_PIN IR_RECEIVE_PIN // 唤醒引脚
// ========== 事件常量定义(语义化命名) ==========
#define USB_EVENT_DISCONNECTED 0 // USB断开事件
#define USB_EVENT_CONNECTED 1 // USB连接事件
#define USB_EVENT_QUIT 2 // USB退出事件
#define MSC_EVENT_CONNECTED 0 // MSC设备连接事件
#define MSC_EVENT_DISCONNECTED 1 // MSC设备断开事件
// ========== 枚举定义(语义化命名) ==========
// 播放模式枚举
enum PlayMode
{
PLAY_MODE_STOP = 0, // 停止模式
PLAY_MODE_PLAY = 1, // 播放模式
PLAY_MODE_NEXT = 2 // 下一曲模式
};
// 音频文件类型枚举
enum AudioFileType
{
AUDIO_TYPE_UNKNOWN, // 未知类型
AUDIO_TYPE_MP3, // MP3类型
AUDIO_TYPE_FLAC // FLAC类型
};
// ========== 全局变量定义(规范化命名) ==========
uint32_t g_ir_receive_code = 0; // 红外接收码
i2s_config_t g_i2s_config = {}; // I2S配置结构体
// USB相关状态
bool g_usb_mounted = false; // USB是否挂载
uint8_t g_usb_device_address = 0; // USB设备地址
uint8_t g_usb_event_id = 5; // USB事件ID
msc_host_device_handle_t g_msc_device = NULL; // MSC设备句柄
msc_host_vfs_handle_t g_msc_vfs_handle = NULL; // MSC VFS句柄
bool g_msc_device_present = false; // MSC设备是否存在
// EQ相关配置
bool g_eq_55hz_adjust_up = true; // 55Hz EQ增益上调标志
bool g_eq_8000hz_adjust_up = true; // 8000Hz EQ增益上调标志
bool g_eq_enabled = true; // EQ功能使能
bool g_i2s_configured = false; // I2S是否已配置
float g_audio_volume = 0.01f; // 当前音量(0.0-1.0)
volatile PlayMode g_play_mode = PLAY_MODE_STOP; // 当前播放模式
volatile int g_current_play_index = -1; // 当前播放曲目索引
int g_prev_play_index = -1; // 上一曲目索引(避免随机重复)
bool g_is_random_play = true; // 是否随机播放
// 解码器相关
FLAC__StreamDecoder *g_flac_decoder = nullptr; // FLAC解码器指针
libmad::MP3DecoderMAD *g_mp3_decoder = nullptr; // MP3解码器指针
uint8_t g_audio_read_buffer[8192]; // 音频文件读取缓冲区
int16_t g_pcm_output_buffer[4096]; // PCM输出缓冲区
// 播放控制
bool g_is_paused = false; // 暂停标志
std::vector<String> g_audio_file_list; // 音频文件列表
String g_audio_list_cache_file_path; // 音频列表缓存文件路径
bool g_device_mounted = false; // 设备挂载标志
uint32_t g_pause_start_timestamp = 0; // 暂停开始时间戳
bool g_enter_sleep_flag = false; // 进入睡眠标志
// EQ参数(语义化命名)
int g_eq_55hz_gain = 0; // 55Hz EQ增益值
int g_eq_8000hz_gain = 0; // 8000Hz EQ增益值
float g_eq_55hz_b0 = 0.0f; // 55Hz滤波器b0系数
float g_eq_55hz_a1 = 0.0f; // 55Hz滤波器a1系数
float g_eq_55hz_a2 = 0.0f; // 55Hz滤波器a2系数
float g_eq_8000hz_b0 = 0.0f; // 8000Hz滤波器b0系数
float g_eq_8000hz_a1 = 0.0f; // 8000Hz滤波器a1系数
float g_eq_8000hz_a2 = 0.0f; // 8000Hz滤波器a2系数
bool g_eq_coeffs_calculated = true; // EQ系数是否已计算
static float g_eq_55hz_left_buffer[4] = {0.0f}; // 55Hz左声道滤波缓存
static float g_eq_55hz_right_buffer[4] = {0.0f}; // 55Hz右声道滤波缓存
static float g_eq_8000hz_left_buffer[4] = {0.0f}; // 8000Hz左声道滤波缓存
static float g_eq_8000hz_right_buffer[4] = {0.0f}; // 8000Hz右声道滤波缓存
float g_eq_55hz_gain_linear = 0.0f; // 55Hz增益线性值
float g_eq_55hz_prev_gain = 10; // 55Hz上一次增益值
float g_eq_8000hz_gain_linear = 0.0f; // 8000Hz增益线性值
float g_eq_8000hz_prev_gain = 10; // 8000Hz上一次增益值
bool g_is_mp3_playing = true; // 是否正在播放MP3
int g_random_retry_count = 0; // 随机选曲重试计数
// 线程安全互斥锁
portMUX_TYPE g_audio_mux = portMUX_INITIALIZER_UNLOCKED;
/**
* @brief 计算单频段二阶IIR滤波器系数
* @param freq 中心频率
* @param b0 输出系数b0
* @param a1 输出系数a1
* @param a2 输出系数a2
*/
static void calculate_eq_filter_coefficients(float freq, float *b0, float *a1, float *a2)
{
float omega = 2.0f * M_PI * freq / AUDIO_SAMPLE_RATE;
float sin_omega = sinf(omega);
float cos_omega = cosf(omega);
float alpha = sin_omega / (2.0f * EQ_Q_FACTOR);
float b0_ = alpha;
float a0_ = 1.0f + alpha;
float a1_ = -2.0f * cos_omega;
float a2_ = 1.0f - alpha;
*b0 = b0_ / a0_;
*a1 = a1_ / a0_;
*a2 = a2_ / a0_;
}
/**
* @brief 重置所有滤波器缓存(切歌或切换曲目时调用)
*/
void reset_eq_buffers(void)
{
portENTER_CRITICAL(&g_audio_mux);
memset(g_eq_55hz_left_buffer, 0, sizeof(g_eq_55hz_left_buffer));
memset(g_eq_55hz_right_buffer, 0, sizeof(g_eq_55hz_right_buffer));
memset(g_eq_8000hz_left_buffer, 0, sizeof(g_eq_8000hz_left_buffer));
memset(g_eq_8000hz_right_buffer, 0, sizeof(g_eq_8000hz_right_buffer));
portEXIT_CRITICAL(&g_audio_mux);
}
/**
* @brief 双频段浮点精度EQ处理函数(修正参数类型)
* @param pcm_float 输入输出浮点PCM数据(范围[-1.0, 1.0])
* @param sample_count 总采样点数(包含左右声道)
*/
void process_audio_eq(float *pcm_float, size_t sample_count)
{
if (!pcm_float || sample_count == 0)
return;
// 首次计算EQ系数
if (g_eq_coeffs_calculated)
{
g_eq_coeffs_calculated = false;
calculate_eq_filter_coefficients(EQ_FREQ_55HZ, &g_eq_55hz_b0, &g_eq_55hz_a1, &g_eq_55hz_a2);
calculate_eq_filter_coefficients(EQ_FREQ_8000HZ, &g_eq_8000hz_b0, &g_eq_8000hz_a1, &g_eq_8000hz_a2);
Serial.println("EQ系数已计算完成");
}
// 转换增益dB为线性系数
if (g_eq_55hz_prev_gain != g_eq_55hz_gain)
{
g_eq_55hz_gain_linear = powf(10.0f, (float)g_eq_55hz_gain * 0.05f);
g_eq_55hz_prev_gain = g_eq_55hz_gain;
Serial.printf("55Hz EQ增益: %d dB (线性值: %.3f)\n", g_eq_55hz_gain, g_eq_55hz_gain_linear);
}
if (g_eq_8000hz_prev_gain != g_eq_8000hz_gain)
{
g_eq_8000hz_gain_linear = powf(10.0f, (float)g_eq_8000hz_gain * 0.05f);
g_eq_8000hz_prev_gain = g_eq_8000hz_gain;
Serial.printf("8000Hz EQ增益: %d dB (线性值: %.3f)\n", g_eq_8000hz_gain, g_eq_8000hz_gain_linear);
}
// 处理立体声PCM数据(每次处理左右两个采样)
for (size_t i = 0; i < sample_count; i += 2)
{
float in_left = pcm_float[i];
float in_right = (i + 1 < sample_count) ? pcm_float[i + 1] : in_left;
// 55Hz频段处理
float out_left_55hz = g_eq_55hz_b0 * (in_left - g_eq_55hz_left_buffer[1]) - g_eq_55hz_a1 * g_eq_55hz_left_buffer[2] - g_eq_55hz_a2 * g_eq_55hz_left_buffer[3];
g_eq_55hz_left_buffer[1] = g_eq_55hz_left_buffer[0];
g_eq_55hz_left_buffer[0] = in_left;
g_eq_55hz_left_buffer[3] = g_eq_55hz_left_buffer[2];
g_eq_55hz_left_buffer[2] = out_left_55hz;
float out_right_55hz = g_eq_55hz_b0 * (in_right - g_eq_55hz_right_buffer[1]) - g_eq_55hz_a1 * g_eq_55hz_right_buffer[2] - g_eq_55hz_a2 * g_eq_55hz_right_buffer[3];
g_eq_55hz_right_buffer[1] = g_eq_55hz_right_buffer[0];
g_eq_55hz_right_buffer[0] = in_right;
g_eq_55hz_right_buffer[3] = g_eq_55hz_right_buffer[2];
g_eq_55hz_right_buffer[2] = out_right_55hz;
// 8000Hz频段处理
float out_left_8000hz = g_eq_8000hz_b0 * (in_left - g_eq_8000hz_left_buffer[1]) - g_eq_8000hz_a1 * g_eq_8000hz_left_buffer[2] - g_eq_8000hz_a2 * g_eq_8000hz_left_buffer[3];
g_eq_8000hz_left_buffer[1] = g_eq_8000hz_left_buffer[0];
g_eq_8000hz_left_buffer[0] = in_left;
g_eq_8000hz_left_buffer[3] = g_eq_8000hz_left_buffer[2];
g_eq_8000hz_left_buffer[2] = out_left_8000hz;
float out_right_8000hz = g_eq_8000hz_b0 * (in_right - g_eq_8000hz_right_buffer[1]) - g_eq_8000hz_a1 * g_eq_8000hz_right_buffer[2] - g_eq_8000hz_a2 * g_eq_8000hz_right_buffer[3];
g_eq_8000hz_right_buffer[1] = g_eq_8000hz_right_buffer[0];
g_eq_8000hz_right_buffer[0] = in_right;
g_eq_8000hz_right_buffer[3] = g_eq_8000hz_right_buffer[2];
g_eq_8000hz_right_buffer[2] = out_right_8000hz;
// 信号混合
float final_left = in_left + out_left_55hz * g_eq_55hz_gain_linear + out_left_8000hz * g_eq_8000hz_gain_linear;
float final_right = in_right + out_right_55hz * g_eq_55hz_gain_linear + out_right_8000hz * g_eq_8000hz_gain_linear;
// 限幅保护
final_left = constrain(final_left, -1.0f, 1.0f);
final_right = constrain(final_right, -1.0f, 1.0f);
// 输出赋值
pcm_float[i] = final_left;
if (i + 1 < sample_count)
{
pcm_float[i + 1] = final_right;
}
}
}
/**
* @brief 生成指定范围随机数(增加边界检查)
* @param min 最小值
* @param max 最大值
* @return 指定范围内的随机数
*/
uint32_t generate_random_number(uint32_t min, uint32_t max)
{
if (min > max)
return min;
return min + (esp_random() % (max - min + 1));
}
/**
* @brief 过滤音频文件(判断是否为支持的音频格式)
* @param file_name 文件名
* @return true-是音频文件,false-不是
*/
bool is_supported_audio_file(const String &file_name)
{
int dot_index = file_name.lastIndexOf('.');
if (dot_index == -1)
return false;
String extension = file_name.substring(dot_index + 1);
extension.toLowerCase();
return (extension == "mp3" || extension == "wav" || extension == "flac");
}
/**
* @brief 遍历目录并执行回调函数
* @param dir_path 目录路径
* @param callback 回调函数
* @return true-遍历成功,false-失败
*/
static bool traverse_directory(const String &dir_path, bool (*callback)(const String &, bool))
{
DIR *dir = opendir(dir_path.c_str());
if (!dir)
{
Serial.printf("打开目录失败:%s\n", dir_path.c_str());
return false;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL)
{
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
String full_path = dir_path + "/" + entry->d_name;
if (full_path.startsWith("//"))
full_path = full_path.substring(1);
bool is_directory = (entry->d_type == DT_DIR);
if (!callback(full_path, is_directory))
{
closedir(dir);
return false;
}
}
closedir(dir);
return true;
}
/**
* @brief 保存音频列表到缓存文件
* @return true-保存成功,false-失败
*/
static bool save_audio_list_to_cache()
{
if (!g_device_mounted)
return false;
FILE *cache_file = fopen(g_audio_list_cache_file_path.c_str(), "w");
if (!cache_file)
{
Serial.printf("创建缓存文件失败:%s\n", g_audio_list_cache_file_path.c_str());
return false;
}
fprintf(cache_file, "%d\n", g_audio_file_list.size());
for (size_t i = 0; i < g_audio_file_list.size(); i++)
{
String file_name = g_audio_file_list[i];
if (!file_name.startsWith("/"))
file_name = "/" + file_name;
fprintf(cache_file, "%s\n", file_name.c_str());
}
fclose(cache_file);
Serial.printf("音频列表已缓存到USB,共%d个文件\n", g_audio_file_list.size());
return true;
}
/**
* @brief 从缓存文件加载音频列表
* @return true-加载成功,false-失败
*/
static bool load_audio_list_from_cache()
{
if (!g_device_mounted)
return false;
FILE *cache_file = fopen(g_audio_list_cache_file_path.c_str(), "r");
if (!cache_file)
{
Serial.println("缓存文件不存在,需要扫描USB");
return false;
}
g_audio_file_list.clear();
int file_count = 0;
if (fscanf(cache_file, "%d\n", &file_count) != 1)
{
Serial.println("读取缓存文件数量失败");
fclose(cache_file);
return false;
}
char buffer[256];
for (int i = 0; i < file_count; i++)
{
if (fgets(buffer, sizeof(buffer), cache_file) == NULL)
break;
String file_name = String(buffer);
file_name.trim();
if (file_name.length() > 0)
{
if (!file_name.startsWith("/"))
file_name = "/" + file_name;
g_audio_file_list.push_back(file_name);
}
}
fclose(cache_file);
Serial.printf("从USB缓存加载音频列表,共%d个文件\n", g_audio_file_list.size());
return true;
}
/**
* @brief 扫描音频文件(优先加载缓存)
* @param scan_path 扫描路径
* @param is_mounted 设备是否挂载
* @param cache_file_path 缓存文件路径
*/
void scan_audio_files(String scan_path, bool is_mounted, String cache_file_path)
{
g_audio_list_cache_file_path = cache_file_path;
g_device_mounted = is_mounted;
if (!g_device_mounted)
{
g_audio_file_list.clear();
return;
}
// 先尝试加载缓存
if (load_audio_list_from_cache())
return;
// 缓存加载失败,扫描USB
g_audio_file_list.clear();
traverse_directory(scan_path, [](const String &full_path, bool is_dir) -> bool
{
if (!is_dir && is_supported_audio_file(full_path))
{
String file_name = full_path;
if (!file_name.startsWith("/"))
file_name = "/" + file_name;
g_audio_file_list.push_back(file_name);
Serial.printf("发现音频文件:%s\n", file_name.c_str());
}
return true; });
save_audio_list_to_cache();
Serial.printf("扫描USB完成,共%d个音频文件\n", g_audio_file_list.size());
}
/**
* @brief 检查文件是否存在
* @param file_path 文件路径
* @return true-存在,false-不存在
*/
bool file_exists(const String &file_path)
{
struct stat file_stat;
return (stat(file_path.c_str(), &file_stat) == 0);
}
/**
* @brief MP3 PCM 输出回调函数(修复类型转换+内存管理)
* @param info 音频信息
* @param pcm_buffer PCM数据缓冲区
* @param length 数据长度
*/
static void on_mp3_pcm_data(libmad::MadAudioInfo &info, short *pcm_buffer, size_t length)
{
// 1. 分配浮点缓冲区
float *pcm_float = (float *)heap_caps_malloc(length * sizeof(float), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
if (!pcm_float)
{
Serial.println("MP3浮点缓冲区分配失败");
return;
}
// 2. 整数转浮点(带音量,修正范围为32768)
portENTER_CRITICAL(&g_audio_mux);
float volume = g_audio_volume;
portEXIT_CRITICAL(&g_audio_mux);
for (size_t i = 0; i < length; i++)
{
float sample = ((float)pcm_buffer[i] / 32768.0f) * volume;
pcm_float[i] = sample;
}
// 3. EQ处理
portENTER_CRITICAL(&g_audio_mux);
bool eq_enabled = g_eq_enabled;
portEXIT_CRITICAL(&g_audio_mux);
if (eq_enabled)
{
process_audio_eq(pcm_float, length);
}
// 4. 浮点转回整数(修正范围+限幅)
for (size_t i = 0; i < length; i++)
{
int32_t sample = (int32_t)(pcm_float[i] * 32768.0f);
g_pcm_output_buffer[i] = constrain(sample, -32768, 32767);
}
// 释放浮点缓冲区
heap_caps_free(pcm_float);
// 首次设置I2S采样率
static bool first_config = true;
if (first_config)
{
i2s_set_clk(I2S_PORT_NUM, info.sample_rate, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_STEREO);
first_config = false;
Serial.printf("I2S配置: %d Hz, %d 声道\n", info.sample_rate, info.channels);
}
// 写入I2S输出
size_t bytes_written;
i2s_write(I2S_PORT_NUM, g_pcm_output_buffer, length * 2, &bytes_written, portMAX_DELAY);
}
/**
* @brief MP3音频信息回调函数
* @param info 音频信息
*/
static bool first_audio_info = true;
static void on_mp3_audio_info(libmad::MadAudioInfo &info)
{
if (first_audio_info)
{
Serial.printf("MP3信息: %d Hz, %d 声道, %d 位\n", info.sample_rate, info.channels, info.bits_per_sample);
first_audio_info = false;
}
}
/**
* @brief 获取音频文件类型
* @param file_path 文件路径
* @return 音频文件类型枚举
*/
AudioFileType get_audio_file_type(const String &file_path)
{
String path_lower = file_path;
path_lower.toLowerCase();
if (path_lower.endsWith(".mp3"))
return AUDIO_TYPE_MP3;
if (path_lower.endsWith(".flac"))
return AUDIO_TYPE_FLAC;
return AUDIO_TYPE_UNKNOWN;
}
/**
* @brief 销毁音频解码器(防止内存泄漏)
*/
void destroy_audio_decoders()
{
portENTER_CRITICAL(&g_audio_mux);
if (g_mp3_decoder)
{
delete g_mp3_decoder;
g_mp3_decoder = nullptr;
}
if (g_flac_decoder)
{
FLAC__stream_decoder_delete(g_flac_decoder);
g_flac_decoder = nullptr;
}
portEXIT_CRITICAL(&g_audio_mux);
Serial.println("音频解码器已销毁");
}
/**
* @brief 初始化解码器指针
*/
void init_audio_decoders()
{
// 初始化MP3解码器(仅初始化一次)
if (g_mp3_decoder == nullptr)
{
g_mp3_decoder = new libmad::MP3DecoderMAD();
g_mp3_decoder->setBufferSize(MAD_DECODE_BUFFER_SIZE);
g_mp3_decoder->setResultBufferSize(MAD_PCM_BUFFER_SIZE);
g_mp3_decoder->setDataCallback(on_mp3_pcm_data);
g_mp3_decoder->setInfoCallback(on_mp3_audio_info);
g_mp3_decoder->begin();
}
// 初始化FLAC解码器
if (g_flac_decoder == nullptr)
g_flac_decoder = FLAC__stream_decoder_new();
vTaskDelay(pdMS_TO_TICKS(500));
if (g_flac_decoder)
Serial.println("FLAC解码器初始化完成");
if (g_mp3_decoder)
Serial.println("MP3解码器初始化完成");
}
/**
* @brief 播放MP3文件
* @param file_path 文件路径
* @return true-播放成功,false-失败
*/
bool play_mp3_file(const String &file_path)
{
portENTER_CRITICAL(&g_audio_mux);
g_is_paused = false;
portEXIT_CRITICAL(&g_audio_mux);
// 检查解码器是否初始化
if (g_mp3_decoder == nullptr)
{
Serial.println("MP3解码器未初始化");
return false;
}
// 打开文件(增加错误处理)
FILE *audio_file = fopen(file_path.c_str(), "rb");
if (!audio_file)
{
Serial.printf("文件不存在:%s\n", file_path.c_str());
return false;
}
Serial.printf("开始播放MP3: %s\n", file_path.c_str());
first_audio_info = true;
// 播放循环
while (!feof(audio_file))
{
// 检查播放模式(加锁保护)
portENTER_CRITICAL(&g_audio_mux);
PlayMode current_mode = g_play_mode;
bool is_paused = g_is_paused;
portEXIT_CRITICAL(&g_audio_mux);
if (current_mode != PLAY_MODE_PLAY)
{
break;
}
size_t bytes_read = fread(g_audio_read_buffer, 1, MP3_READ_BUFFER_SIZE, audio_file);
if (bytes_read > 0)
{
g_mp3_decoder->write(g_audio_read_buffer, bytes_read);
}
// 暂停处理
while (is_paused && current_mode == PLAY_MODE_PLAY)
{
vTaskDelay(pdMS_TO_TICKS(200));
portENTER_CRITICAL(&g_audio_mux);
is_paused = g_is_paused;
current_mode = g_play_mode;
portEXIT_CRITICAL(&g_audio_mux);
}
vTaskDelay(pdMS_TO_TICKS(1));
}
Serial.println("MP3播放循环退出");
// 安全关闭文件
if (audio_file != NULL)
{
fclose(audio_file);
audio_file = NULL;
}
vTaskDelay(pdMS_TO_TICKS(50));
Serial.printf("MP3文件已关闭:%s\n", file_path.c_str());
return true;
}
/**
* @brief FLAC解码写入回调函数(修复类型转换+内存泄漏+goto作用域)
* @param decoder FLAC解码器指针
* @param frame FLAC帧数据
* @param buffer PCM数据缓冲区
* @param data 用户数据
* @return 解码状态
*/
static FLAC__StreamDecoderWriteStatus flac_write_callback(
const FLAC__StreamDecoder *decoder,
const FLAC__Frame *frame,
const FLAC__int32 *const buffer[],
void *data)
{
if (!frame || !buffer)
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
uint32_t sample_rate = frame->header.sample_rate;
uint32_t channels = frame->header.channels;
uint32_t bits_per_sample = frame->header.bits_per_sample;
uint32_t block_size = frame->header.blocksize;
// 提前定义所有变量,避免goto跨初始化
float *pcm_float = NULL;
int16_t *pcm_int16 = NULL;
float volume = 0.0f;
bool eq_enabled = false;
PlayMode current_mode = PLAY_MODE_STOP;
// 配置I2S采样率
if (!g_i2s_configured || g_i2s_config.sample_rate != sample_rate)
{
g_i2s_config.sample_rate = sample_rate;
i2s_set_clk(I2S_PORT_NUM, sample_rate, I2S_BITS_PER_SAMPLE_16BIT,
channels == 1 ? I2S_CHANNEL_MONO : I2S_CHANNEL_STEREO);
g_i2s_configured = true;
Serial.printf("FLAC信息: %d Hz, %d 声道, %d 位\n", sample_rate, channels, bits_per_sample);
}
// 1. 分配浮点缓冲区(用于EQ计算)
pcm_float = (float *)heap_caps_malloc(block_size * channels * sizeof(float),
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
if (!pcm_float)
{
Serial.println("FLAC浮点缓冲区分配失败");
goto cleanup;
}
// 2. 分配16位整数缓冲区(用于I2S输出)
pcm_int16 = (int16_t *)heap_caps_malloc(block_size * channels * sizeof(int16_t),
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
if (!pcm_int16)
{
Serial.println("FLAC整数缓冲区分配失败");
goto cleanup;
}
// 3. 获取音量(加锁保护)
portENTER_CRITICAL(&g_audio_mux);
volume = g_audio_volume;
portEXIT_CRITICAL(&g_audio_mux);
// 4. FLAC数据转换为浮点(先缩放至[-1.0, 1.0]范围)
for (uint32_t i = 0; i < block_size; i++)
{
for (uint32_t c = 0; c < channels; c++)
{
if (c >= frame->header.channels || i >= frame->header.blocksize)
continue;
// FLAC原始数据(可能是24/32位)→ 缩放到[-1.0, 1.0]浮点
int32_t sample = buffer[c][i];
if (bits_per_sample > 16)
{
sample >>= (bits_per_sample - 16); // 先降为16位精度
}
// 应用音量 + 转换为浮点(修正为32768范围)
float sample_float = ((float)sample / 32768.0f) * volume;
pcm_float[i * channels + c] = sample_float;
}
}
// 5. 应用EQ处理(直接操作浮点数组)
portENTER_CRITICAL(&g_audio_mux);
eq_enabled = g_eq_enabled;
portEXIT_CRITICAL(&g_audio_mux);
if (eq_enabled)
{
process_audio_eq(pcm_float, block_size * channels);
}
// 6. 浮点转回16位整数(限幅保护)
for (uint32_t i = 0; i < block_size * channels; i++)
{
// 缩放回16位整数范围 + 限幅(修正为32768)
int32_t sample = (int32_t)(pcm_float[i] * 32768.0f);
pcm_int16[i] = constrain(sample, -32768, 32767);
}
// 7. 写入I2S(16位整数数据)
size_t bytes_written;
i2s_write(I2S_PORT_NUM, pcm_int16, block_size * channels * 2, &bytes_written, pdMS_TO_TICKS(100));
cleanup: // 统一清理内存
if (pcm_float)
heap_caps_free(pcm_float);
if (pcm_int16)
heap_caps_free(pcm_int16);
// 检查播放模式
portENTER_CRITICAL(&g_audio_mux);
current_mode = g_play_mode;
portEXIT_CRITICAL(&g_audio_mux);
// 根据是否分配成功返回状态
if (!pcm_float || !pcm_int16)
{
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
return (current_mode == PLAY_MODE_PLAY) ? FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE
: FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
/**
* @brief FLAC解码错误回调函数
* @param decoder FLAC解码器指针
* @param status 错误状态
* @param data 用户数据
*/
static void flac_error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *data)
{
if (status != FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC)
{
Serial.printf("FLAC解码错误: %s\n", FLAC__StreamDecoderErrorStatusString[status]);
}
}
/**
* @brief 播放FLAC文件
* @param file_path 文件路径
* @return true-播放成功,false-失败
*/
bool play_flac_file(const String &file_path)
{
portENTER_CRITICAL(&g_audio_mux);
g_is_paused = false;
portEXIT_CRITICAL(&g_audio_mux);
if (g_flac_decoder == nullptr)
{
Serial.println("FLAC解码器未初始化");
return false;
}
// 打开FLAC文件
FILE *audio_file = fopen(file_path.c_str(), "rb");
if (!audio_file)
{
Serial.printf("文件不存在:%s\n", file_path.c_str());
return false;
}
// 清理旧解码器状态
if (g_flac_decoder)
{
FLAC__stream_decoder_finish(g_flac_decoder);
}
// 初始化解码器
FLAC__StreamDecoderInitStatus init_status = FLAC__stream_decoder_init_FILE(
g_flac_decoder, audio_file, flac_write_callback, nullptr, flac_error_callback, nullptr);
if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK)
{
Serial.printf("FLAC解码器初始化失败:%d\n", init_status);
FLAC__stream_decoder_delete(g_flac_decoder);
g_flac_decoder = nullptr;
fclose(audio_file);
return false;
}
FLAC__stream_decoder_set_metadata_ignore_all(g_flac_decoder);
Serial.printf("开始播放FLAC: %s\n", file_path.c_str());
// 解码循环
while (true)
{
// 检查播放模式
portENTER_CRITICAL(&g_audio_mux);
PlayMode current_mode = g_play_mode;
bool is_paused = g_is_paused;
portEXIT_CRITICAL(&g_audio_mux);
if (current_mode != PLAY_MODE_PLAY)
{
break;
}
if (!g_flac_decoder)
{
Serial.println("FLAC解码器已释放");
break;
}
FLAC__bool decode_ok = FLAC__stream_decoder_process_single(g_flac_decoder);
FLAC__StreamDecoderState decoder_state = FLAC__stream_decoder_get_state(g_flac_decoder);
if (!decode_ok || decoder_state == FLAC__STREAM_DECODER_END_OF_STREAM)
{
Serial.printf("FLAC解码结束:状态=%d\n", decoder_state);
break;
}
// 暂停处理
while (is_paused && current_mode == PLAY_MODE_PLAY)
{
vTaskDelay(pdMS_TO_TICKS(200));
portENTER_CRITICAL(&g_audio_mux);
is_paused = g_is_paused;
current_mode = g_play_mode;
portEXIT_CRITICAL(&g_audio_mux);
}
vTaskDelay(pdMS_TO_TICKS(1));
}
Serial.println("FLAC播放循环退出");
// 清理资源
if (g_flac_decoder)
FLAC__stream_decoder_finish(g_flac_decoder);
fclose(audio_file);
audio_file = nullptr;
return true;
}
/**
* @brief 音频解码任务
* @param param 任务参数
*/
void audio_decode_task(void *param)
{
while (true)
{
// 检查曲目列表是否为空
if (g_audio_file_list.empty())
{
portENTER_CRITICAL(&g_audio_mux);
g_play_mode = PLAY_MODE_STOP;
portEXIT_CRITICAL(&g_audio_mux);
vTaskDelay(pdMS_TO_TICKS(1000));
continue;
}
// 切歌逻辑
portENTER_CRITICAL(&g_audio_mux);
PlayMode current_mode = g_play_mode;
portEXIT_CRITICAL(&g_audio_mux);
if (current_mode == PLAY_MODE_NEXT)
{
g_random_retry_count = 0;
int new_index = generate_random_number(0, g_audio_file_list.size() - 1);
// 增加重试次数限制,防止死循环(避免重复选曲)
while (g_random_retry_count < MAX_RANDOM_RETRY_COUNT)
{
// 边界检查,防止数组越界
if (new_index < 0 || new_index >= (int)g_audio_file_list.size())
{
new_index = generate_random_number(0, g_audio_file_list.size() - 1);
g_random_retry_count++;
continue;
}
// 避免和上一曲重复(列表长度>1时生效)
if (g_audio_file_list.size() > 1 && new_index == g_prev_play_index)
{
new_index = generate_random_number(0, g_audio_file_list.size() - 1);
g_random_retry_count++;
continue;
}
AudioFileType file_type = get_audio_file_type(g_audio_file_list[new_index]);
if (file_type == AUDIO_TYPE_MP3 || file_type == AUDIO_TYPE_FLAC)
{
reset_eq_buffers();
i2s_zero_dma_buffer(I2S_PORT_NUM); // 清空I2S缓冲区,消除咔哒声
g_prev_play_index = new_index; // 记录当前索引
break;
}
g_random_retry_count++;
new_index = generate_random_number(0, g_audio_file_list.size() - 1);
}
if (g_random_retry_count >= MAX_RANDOM_RETRY_COUNT)
{
Serial.println("没有能播放的音频文件");
portENTER_CRITICAL(&g_audio_mux);
g_play_mode = PLAY_MODE_STOP;
portEXIT_CRITICAL(&g_audio_mux);
vTaskDelay(pdMS_TO_TICKS(1000));
continue;
}
portENTER_CRITICAL(&g_audio_mux);
g_current_play_index = new_index;
g_play_mode = PLAY_MODE_PLAY;
portEXIT_CRITICAL(&g_audio_mux);
Serial.printf("切换到下一曲: 索引=%d\n", new_index);
continue;
}
// 首次播放
if (g_current_play_index == -1)
{
int retry_count = 0;
int new_index = -1;
do
{
new_index = generate_random_number(0, g_audio_file_list.size() - 1);
retry_count++;
// 防止死循环
if (retry_count > MAX_RANDOM_RETRY_COUNT)
{
Serial.println("没有找到可播放的音频文件");
portENTER_CRITICAL(&g_audio_mux);
g_play_mode = PLAY_MODE_STOP;
portEXIT_CRITICAL(&g_audio_mux);
g_current_play_index = -1;
break;
}
// 避免重复(首次播放无历史,仅检查文件类型)
} while (new_index < 0 || new_index >= (int)g_audio_file_list.size() ||
get_audio_file_type(g_audio_file_list[new_index]) == AUDIO_TYPE_UNKNOWN);
if (new_index != -1)
{
portENTER_CRITICAL(&g_audio_mux);
g_current_play_index = new_index;
g_play_mode = PLAY_MODE_PLAY;
portEXIT_CRITICAL(&g_audio_mux);
g_prev_play_index = new_index;
}
else
{
continue;
}
}
// 边界检查,防止数组越界
if (g_current_play_index < 0 || g_current_play_index >= (int)g_audio_file_list.size())
{
portENTER_CRITICAL(&g_audio_mux);
g_play_mode = PLAY_MODE_NEXT;
portEXIT_CRITICAL(&g_audio_mux);
continue;
}
// 获取文件路径并播放
String file_path = g_audio_file_list[g_current_play_index];
AudioFileType file_type = get_audio_file_type(file_path);
// 播放逻辑
if (file_type == AUDIO_TYPE_MP3)
play_mp3_file(file_path);
else if (file_type == AUDIO_TYPE_FLAC)
play_flac_file(file_path);
else
{
portENTER_CRITICAL(&g_audio_mux);
g_play_mode = PLAY_MODE_NEXT;
portEXIT_CRITICAL(&g_audio_mux);
continue;
}
// 播放完成自动切歌
portENTER_CRITICAL(&g_audio_mux);
if (g_play_mode == PLAY_MODE_PLAY)
{
g_play_mode = PLAY_MODE_NEXT;
}
portEXIT_CRITICAL(&g_audio_mux);
// 增加延时,防止任务独占CPU
vTaskDelay(pdMS_TO_TICKS(10));
}
}
/**
* @brief 进入深度睡眠模式
*/
/**
* @brief 进入深度睡眠模式(新增USB安全卸载逻辑)
*/
void enter_deep_sleep_mode()
{
Serial.println("=== 进入深度睡眠模式 ===");
// ========== 第一步:安全卸载USB设备(核心新增逻辑) ==========
Serial.println("开始卸载USB设备...");
// 1. 卸载VFS挂载(对应原代码的 msc_host_vfs_unregister)
if (g_msc_vfs_handle)
{
esp_err_t err = msc_host_vfs_unregister(g_msc_vfs_handle);
if (err == ESP_OK)
{
Serial.println("✅ USB VFS挂载已卸载");
g_msc_vfs_handle = NULL;
g_usb_mounted = false;
}
else
{
Serial.printf("❌ USB VFS卸载失败: %s\n", esp_err_to_name(err));
}
}
// 2. 卸载MSC设备(对应原代码的 msc_host_uninstall_device)
if (g_msc_device)
{
esp_err_t err = msc_host_uninstall_device(g_msc_device);
if (err == ESP_OK)
{
Serial.println("✅ MSC设备已卸载");
g_msc_device = NULL;
g_msc_device_present = false;
}
else
{
Serial.printf("❌ MSC设备卸载失败: %s\n", esp_err_to_name(err));
}
}
// 3. 卸载MSC主机驱动(对应原代码的 msc_host_uninstall)
// 注意:驱动卸载后下次唤醒需要重新初始化,因此只在休眠时执行
esp_err_t msc_uninstall_err = msc_host_uninstall();
if (msc_uninstall_err == ESP_OK)
{
Serial.println("✅ MSC主机驱动已卸载");
}
else
{
Serial.printf("❌ MSC驱动卸载失败: %s\n", esp_err_to_name(msc_uninstall_err));
}
// 4. 重置USB相关状态
g_usb_device_address = 0;
g_usb_event_id = 5;
Serial.println("✅ USB设备卸载完成");
// ========== 第二步:清理播放资源(原有逻辑保留) ==========
portENTER_CRITICAL(&g_audio_mux);
g_play_mode = PLAY_MODE_STOP;
g_is_paused = false;
portEXIT_CRITICAL(&g_audio_mux);
// ========== 第三步:销毁解码器(原有逻辑保留) ==========
destroy_audio_decoders();
// ========== 第四步:卸载I2S驱动(原有逻辑保留) ==========
i2s_driver_uninstall(I2S_PORT_NUM);
Serial.println("✅ I2S驱动已卸载");
// ========== 第五步:配置深度睡眠唤醒源(原有逻辑保留) ==========
// 配置唤醒源:红外接收引脚低电平唤醒
esp_sleep_enable_ext0_wakeup(GPIO_NUM_2, 0);
// 将GPIO2切换到RTC GPIO模式(必须)
rtc_gpio_deinit(GPIO_NUM_2);
rtc_gpio_init(GPIO_NUM_2);
rtc_gpio_set_direction(GPIO_NUM_2, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_pullup_en(GPIO_NUM_2); // 启用上拉
// ========== 第六步:进入深度睡眠 ==========
Serial.println("✅ 所有资源已清理,进入深度睡眠...");
esp_deep_sleep_start();
}
/**
* @brief 检查暂停超时,超时则进入深度睡眠
*/
void check_pause_timeout()
{
// 加锁读取状态
portENTER_CRITICAL(&g_audio_mux);
bool is_paused = g_is_paused;
PlayMode current_mode = g_play_mode;
portEXIT_CRITICAL(&g_audio_mux);
// 如果处于暂停状态
if (is_paused && current_mode == PLAY_MODE_PLAY)
{
// 记录暂停开始时间
if (g_pause_start_timestamp == 0)
{
g_pause_start_timestamp = millis();
Serial.printf("暂停计时开始:%lu ms\n", g_pause_start_timestamp);
}
// 检查是否超时(10分钟)
else if (millis() - g_pause_start_timestamp >= PAUSE_SLEEP_TIMEOUT_MS)
{
g_enter_sleep_flag = true;
Serial.println("暂停超时,准备进入深度睡眠...");
}
}
else
{
// 非暂停状态,重置计时
g_pause_start_timestamp = 0;
g_enter_sleep_flag = false;
}
// 触发深度睡眠
if (g_enter_sleep_flag)
{
enter_deep_sleep_mode();
}
}
/**
* @brief 处理红外按键事件(修复switch case作用域)
*/
void process_ir_key_events()
{
// 提前定义所有变量,避免case跨初始化
uint32_t ir_code = 0;
float vol_plus = 0.0f;
float vol_minus = 0.0f;
bool paused = false;
bool eq_enabled = false;
int gain_55 = 0;
int gain_8000 = 0;
// 加锁读取红外码
portENTER_CRITICAL(&g_audio_mux);
ir_code = g_ir_receive_code;
portEXIT_CRITICAL(&g_audio_mux);
if (ir_code != 0 && ir_code != 0x55)
{
Serial.printf("接收到红外按键码: 0x%04X\n", ir_code);
switch (ir_code)
{
case 0x4054: // 音量+
portENTER_CRITICAL(&g_audio_mux);
g_audio_volume = constrain(g_audio_volume + 0.01f, 0.0f, 1.0f);
vol_plus = g_audio_volume;
portEXIT_CRITICAL(&g_audio_mux);
Serial.printf("当前音量: %.2f\n", vol_plus);
break;
case 0x4445: // 音量-
portENTER_CRITICAL(&g_audio_mux);
g_audio_volume = constrain(g_audio_volume - 0.01f, 0.0f, 1.0f);
vol_minus = g_audio_volume;
portEXIT_CRITICAL(&g_audio_mux);
Serial.printf("当前音量: %.2f\n", vol_minus);
break;
case 0x4444: // 下一曲
portENTER_CRITICAL(&g_audio_mux);
g_play_mode = PLAY_MODE_NEXT;
// 重置暂停计时
g_pause_start_timestamp = 0;
g_enter_sleep_flag = false;
portEXIT_CRITICAL(&g_audio_mux);
Serial.println("切换到下一曲");
break;
case 0x4050: // 暂停/播放
portENTER_CRITICAL(&g_audio_mux);
g_is_paused = !g_is_paused;
paused = g_is_paused;
portEXIT_CRITICAL(&g_audio_mux);
// 暂停时开始计时,播放时重置计时
if (paused)
{
g_pause_start_timestamp = millis();
Serial.println("暂停播放(开始计时)");
}
else
{
g_pause_start_timestamp = 0;
g_enter_sleep_flag = false;
Serial.println("恢复播放(重置计时)");
}
break;
case 0x4014: // EQ开关
portENTER_CRITICAL(&g_audio_mux);
g_eq_enabled = !g_eq_enabled;
eq_enabled = g_eq_enabled;
portEXIT_CRITICAL(&g_audio_mux);
Serial.printf("EQ功能: %s\n", eq_enabled ? "开启" : "关闭");
break;
case 0x0411: // 55Hz增益调节
portENTER_CRITICAL(&g_audio_mux);
if (g_eq_55hz_adjust_up)
{
g_eq_55hz_gain++;
if (g_eq_55hz_gain > 12)
{
g_eq_55hz_gain = 12;
g_eq_55hz_adjust_up = false;
}
}
else
{
g_eq_55hz_gain--;
if (g_eq_55hz_gain < -12)
{
g_eq_55hz_gain = -12;
g_eq_55hz_adjust_up = true;
}
}
gain_55 = g_eq_55hz_gain;
portEXIT_CRITICAL(&g_audio_mux);
Serial.printf("55Hz EQ增益: %d dB\n", gain_55);
break;
case 0x0441: // 8000Hz增益调节
portENTER_CRITICAL(&g_audio_mux);
if (g_eq_8000hz_adjust_up)
{
g_eq_8000hz_gain++;
if (g_eq_8000hz_gain > 12)
{
g_eq_8000hz_gain = 12;
g_eq_8000hz_adjust_up = false;
}
}
else
{
g_eq_8000hz_gain--;
if (g_eq_8000hz_gain < -12)
{
g_eq_8000hz_gain = -12;
g_eq_8000hz_adjust_up = true;
}
}
gain_8000 = g_eq_8000hz_gain;
portEXIT_CRITICAL(&g_audio_mux);
Serial.printf("8000Hz EQ增益: %d dB\n", gain_8000);
break;
}
// 清除按键状态(加锁)
portENTER_CRITICAL(&g_audio_mux);
g_ir_receive_code = 0;
portEXIT_CRITICAL(&g_audio_mux);
}
}
/**
* @brief 红外接收中断处理函数(修复线程安全)
*/
void IRAM_ATTR ir_receive_isr()
{
static uint32_t time_prev = 0, time_curr = 0, bit_count = 0, ir_data = 0;
time_curr = micros();
uint32_t time_diff = time_curr - time_prev;
time_prev = time_curr;
// 起始码检测
if (time_diff > 8500 && time_diff < 9500)
{
bit_count = 0;
ir_data = 0;
return;
}
// 数据位接收
if (bit_count < 32 && time_diff > 400 && time_diff < 1800)
{
ir_data = (ir_data << 1) | (time_diff > 1000 ? 1 : 0);
bit_count++;
if (bit_count == 32)
{
uint32_t temp_code = (ir_data >> 8) & 0xFFFF;
// 加锁赋值,避免主循环读到不完整数据
portENTER_CRITICAL_ISR(&g_audio_mux);
g_ir_receive_code = temp_code;
portEXIT_CRITICAL_ISR(&g_audio_mux);
bit_count = 0;
}
}
}
/**
* @brief 初始化红外接收功能
*/
void init_ir_receiver()
{
pinMode(IR_RECEIVE_PIN, INPUT);
attachInterrupt(IR_RECEIVE_PIN, ir_receive_isr, CHANGE);
}
void init_i2s_audio(uint8_t BCLK, uint8_t LRC, uint8_t DOUT)
{
g_i2s_config.sample_rate = 44100;
g_i2s_config.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT;
g_i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
g_i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; // interrupt priority
g_i2s_config.dma_buf_count = 16;
g_i2s_config.dma_buf_len = 512;
g_i2s_config.use_apll = 0;
g_i2s_config.tx_desc_auto_clear = true;
g_i2s_config.fixed_mclk = true;
g_i2s_config.mclk_multiple = I2S_MCLK_MULTIPLE_128;
g_i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
g_i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S); // Arduino vers. > 2.0.0
esp_err_t err = i2s_driver_install((i2s_port_t)I2S_PORT_NUM, &g_i2s_config, 0, NULL);
if (err != ESP_OK)
{
Serial.printf("I2S驱动安装失败: %s\n", esp_err_to_name(err));
while (1)
vTaskDelay(pdMS_TO_TICKS(100));
}
i2s_zero_dma_buffer((i2s_port_t)I2S_PORT_NUM);
i2s_pin_config_t m_pin_config = {};
m_pin_config.bck_io_num = BCLK;
m_pin_config.ws_io_num = LRC; // wclk = lrc
m_pin_config.data_out_num = DOUT;
m_pin_config.data_in_num = I2S_PIN_NO_CHANGE;
m_pin_config.mck_io_num = I2S_PIN_NO_CHANGE;
esp_err_t result = i2s_set_pin((i2s_port_t)I2S_PORT_NUM, &m_pin_config);
if (result != ESP_OK)
{
Serial.printf("I2S引脚配置失败: %s\n", esp_err_to_name(result));
while (1)
vTaskDelay(pdMS_TO_TICKS(100));
}
g_i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
i2s_set_clk((i2s_port_t)I2S_PORT_NUM, 44100, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_STEREO);
Serial.println("I2S音频输出初始化完成");
}
/**
* @brief MSC设备事件回调函数
* @param event 事件结构体
* @param arg 用户参数
*/
static void msc_device_event_callback(const msc_host_event_t *event, void *arg)
{
if (event->event == MSC_EVENT_CONNECTED)
{
Serial.println("MSC存储设备已连接");
g_usb_device_address = event->device.address;
g_usb_event_id = USB_EVENT_CONNECTED;
}
else if (event->event == MSC_EVENT_DISCONNECTED)
{
Serial.println("MSC存储设备已断开");
g_usb_event_id = USB_EVENT_DISCONNECTED;
}
}
/**
* @brief USB主机任务
* @param args 任务参数
*/
static void usb_host_task(void *args)
{
printf("USB主机任务运行在核心: %d\n", xPortGetCoreID());
// 初始化USB主机
const usb_host_config_t host_config = {.intr_flags = ESP_INTR_FLAG_LEVEL1};
ESP_ERROR_CHECK(usb_host_install(&host_config));
// 初始化MSC主机驱动
const msc_host_driver_config_t msc_config = {
.create_backround_task = true,
.task_priority = 1,
.stack_size = 4096,
.core_id = 1,
.callback = msc_device_event_callback,
};
ESP_ERROR_CHECK(msc_host_install(&msc_config));
bool has_clients = true;
while (true)
{
uint32_t event_flags;
usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS)
{
has_clients = false;
if (usb_host_device_free_all() == ESP_OK)
break;
}
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE && !has_clients)
break;
vTaskDelay(pdMS_TO_TICKS(100));
}
vTaskDelay(pdMS_TO_TICKS(10));
Serial.println("正在卸载USB主机驱动");
ESP_ERROR_CHECK(usb_host_uninstall());
vTaskDelete(NULL);
}
/**
* @brief 初始化USB主机功能
*/
void init_usb_host()
{
printf("USB初始化运行在核心: %d\n", xPortGetCoreID());
// 创建USB主机任务
BaseType_t task_created = xTaskCreatePinnedToCore(
usb_host_task, "usb_host_task", 4096, NULL, 2, NULL, 1);
assert(task_created == pdPASS);
int wait_count = 0;
Serial.println("等待USB存储设备连接...");
// 等待USB设备连接
while (true)
{
if (g_usb_event_id == USB_EVENT_CONNECTED)
{
g_usb_event_id = 5;
if (!g_msc_device_present)
{
g_msc_device_present = true;
// 安装MSC设备
esp_err_t err = msc_host_install_device(g_usb_device_address, &g_msc_device);
if (err != ESP_OK)
{
Serial.printf("MSC设备安装失败: %s\n", esp_err_to_name(err));
continue;
}
// 配置VFS挂载参数
const esp_vfs_fat_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 3,
.allocation_unit_size = 8192,
};
// 挂载USB设备
err = msc_host_vfs_register(g_msc_device, USB_MOUNT_PATH, &mount_config, &g_msc_vfs_handle);
if (err == ESP_OK)
{
g_usb_mounted = true;
Serial.println("USB存储设备挂载成功");
break;
}
else
{
Serial.printf("USB挂载失败: %s\n", esp_err_to_name(err));
}
}
}
// 超时处理
if (wait_count++ > 10)
{
Serial.println("USB设备连接超时");
break;
}
vTaskDelay(pdMS_TO_TICKS(500));
}
}
/**
* @brief 系统初始化函数
*/
void setup()
{
Serial.begin(115200);
vTaskDelay(pdMS_TO_TICKS(500)); // 替换delay为vTaskDelay,更适合FreeRTOS
// 初始化红外接收
init_ir_receiver();
int wakeup_wait_count = 0;
// 检查是否从深度睡眠唤醒
esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
if (wakeup_reason == ESP_SLEEP_WAKEUP_EXT0)
{
// 等待用户确认唤醒
while (1)
{
portENTER_CRITICAL(&g_audio_mux);
uint32_t ir_code = g_ir_receive_code;
portEXIT_CRITICAL(&g_audio_mux);
if (ir_code != 0 && ir_code != 0x55)
{
Serial.printf("唤醒按键码: 0x%04X\n", ir_code);
if (ir_code == 0x4050) // OK键确认唤醒
{
portENTER_CRITICAL(&g_audio_mux);
g_ir_receive_code = 0;
portEXIT_CRITICAL(&g_audio_mux);
break;
}
}
vTaskDelay(pdMS_TO_TICKS(1000));
Serial.printf("请按OK键唤醒,%d秒后将再次进入深度睡眠\n", 10 - wakeup_wait_count);
if (wakeup_wait_count++ > 10)
enter_deep_sleep_mode();
}
Serial.println("\n=== 从深度睡眠唤醒 ===");
}
else
{
Serial.println("\n=== 系统首次启动 ===");
}
// 初始化USB主机
init_usb_host();
vTaskDelay(pdMS_TO_TICKS(500));
// 初始化I2S音频输出
init_i2s_audio(I2S_BIT_CLOCK_PIN, I2S_LR_CLOCK_PIN, I2S_DATA_OUT_PIN);
vTaskDelay(pdMS_TO_TICKS(500));
// 初始化解码器
init_audio_decoders();
// 扫描音频文件
scan_audio_files(USB_MOUNT_PATH, g_usb_mounted, AUDIO_LIST_CACHE);
// 重置睡眠状态
g_pause_start_timestamp = 0;
g_enter_sleep_flag = false;
// 创建解码任务
BaseType_t decode_task_created = xTaskCreatePinnedToCore(
audio_decode_task, "AudioDecodeTask", DECODE_TASK_STACK_SIZE, nullptr, 5, nullptr, 1);
assert(decode_task_created == pdPASS);
Serial.println("音频播放器初始化完成(按暂停/播放键开始播放)");
}
/**
* @brief 主循环函数
*/
void loop()
{
// 检查暂停超时
check_pause_timeout();
// 处理红外按键事件
process_ir_key_events();
vTaskDelay(pdMS_TO_TICKS(10));
}
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:esp32-s3-devkitc-1]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
board_upload.psram_size = 8MB
board_upload.flash_size = 16MB
board_build.arduino.partitions = default_16MB.csv
board_build.arduino.memory_type = qio_opi
build_flags =
-DBOARD_HAS_PSRAM
-mfix-esp32-psram-cache-issue
-DFLAC__NO_ASM
-DFLAC__HAS_OGG=0
-DFLAC__USE_STDINT_H=1
-O2
-ffast-math
-DNDEBUG
-DARDUINO_LOOP_STACK_SIZE=8192
-DARDUINO_RUNNING_CORE=1
monitor_speed = 115200
lib_deps =
firechip/usb-host-msc@^1.1.4
# 指定版本的Git库(tag/分支/提交哈希)
;https://github.com/pschatzmann/arduino-libflac.git#1.0.1
; https://github.com/pschatzmann/arduino-libmad.git#0.7.1
; https://github.com/pschatzmann/arduino-libopus.git#main # main分支
更多推荐


所有评论(0)