add flac support and files management app

This commit is contained in:
francis.wang committed 2026-10-27 20:50:09 +08:00
1 parent 4126ea13a1
commit 45f7b83e7c
12 files changed
+1526 -139

No files matched your search

+266 -15
View File
@@ -34,6 +34,11 @@
#define WAV_MAX_BLOCKALIGN 8 /* 支持的 WAV 帧对齐上限(16/24/32bit × 最多 2 声道) */
#define WAV_MAX_FRAMES 1024 /* WAV 原始 PCM 中转缓冲容纳的最大帧数 */
#define AUDIO_SRC_MAX_FRAMES 1024 /* 单次 audio_source_read 最多输出的帧数(与播放器缓冲一致) */
#define DECIM_CHUNK_FRAMES 256 /* 抽取时每次处理多少输出帧(控制临时缓冲大小) */
#define AUDIO_MAX_OUT_RATE 48000 /* 输出采样率上限 */
#define AUDIO_MAX_DECIM 8 /* 最大抽取倍数 */
/* ---------------- 内部结构 ---------------- */
struct audio_source {
audio_codec_t codec;
@@ -54,6 +59,10 @@ struct audio_source {
/* ---- FLAC ---- */
flac_dec_t *flac;
/* ---- 高采样率抽取(192k/96k → ≤48k) ---- */
uint8_t decim; /* 抽取倍数,1 表示不抽取 */
uint8_t *decim_buf; /* 抽取用的原生采样中转缓冲 */
};
/* ---------------- 位宽映射 ---------------- */
@@ -76,6 +85,10 @@ static inline uint16_t pick_out_bits(uint16_t bits)
return 32;
}
/* 采样率限制 / 抽取(实现见文件末尾,供 audio_source_open 使用) */
static bool decim_setup(audio_source_t *src);
static void decim_mix(const uint8_t *src, uint8_t *dst, int frames, int decim, uint8_t ob);
/* ---------------- WAV 解析 ---------------- */
static inline uint16_t rd_u16(const uint8_t *p)
@@ -256,8 +269,7 @@ static int wav_read(audio_source_t *src, void *out, int max_frames)
}
}
src->frames_read += frames;
/* 注意:frames_read 统一由 audio_source_read() 累加,这里不要重复计数 */
return (int)frames;
}
@@ -323,14 +335,25 @@ audio_source_t *audio_source_open(const char *path, audio_info_t *info)
src->info.total_sec = (fi.total_samples > 0) ?
(uint32_t)(fi.total_samples / fi.sample_rate) : 0;
/* 高采样率(96k/192k)抽取到 ES8388 / MCLK 能承受的范围 */
if (!decim_setup(src))
{
ESP_LOGE(TAG, "unsupported sample rate %u Hz: %s",
(unsigned)fi.sample_rate, path);
audio_source_close(src);
return NULL;
}
if (info != NULL)
{
*info = src->info;
}
ESP_LOGI(TAG, "%s: FLAC %u Hz, %u bit -> I2S %u bit, %u ch, %u s",
path, (unsigned)src->info.sample_rate, src->info.bits,
src->info.out_bits, src->info.channels,
ESP_LOGI(TAG, "%s: FLAC %u Hz%s, %u bit -> I2S %u bit @ %u Hz, %u ch, %u s",
path, (unsigned)fi.sample_rate,
(src->decim > 1) ? " (downsampled)" : "",
src->info.bits, src->info.out_bits,
(unsigned)src->info.sample_rate, src->info.channels,
(unsigned)src->info.total_sec);
return src;
@@ -359,6 +382,15 @@ audio_source_t *audio_source_open(const char *path, audio_info_t *info)
src->info.out_bytes = (uint8_t)(bits / 8); /* WAV 位深必为 16/24/32 */
src->info.total_sec = src->data_size / src->byte_rate;
/* 高采样率(96k/192k)抽取到 ES8388 / MCLK 能承受的范围 */
if (!decim_setup(src))
{
ESP_LOGW(TAG, "unsupported sample rate %u Hz: %s",
(unsigned)src->info.sample_rate, path);
audio_source_close(src);
return NULL;
}
/* 原始 PCM 中转缓冲(16/24/32bit × 最多 2 声道 → 每帧最多 8 字节) */
src->scratch = (uint8_t *)malloc((size_t)WAV_MAX_FRAMES * src->block_align);
@@ -385,6 +417,87 @@ audio_source_t *audio_source_open(const char *path, audio_info_t *info)
return src;
}
/**
* @brief 带抽取的读取:把 decim 个原生帧合成 1 个输出帧
*/
static int audio_source_read_decim(audio_source_t *src, void *out, int max_frames)
{
uint8_t *dst = (uint8_t *)out;
const uint8_t ob = src->info.out_bytes;
const int decim = src->decim;
int produced = 0;
while (produced < max_frames)
{
int room = max_frames - produced;
if (room > DECIM_CHUNK_FRAMES)
{
room = DECIM_CHUNK_FRAMES;
}
if ((room * decim) > WAV_MAX_FRAMES)
{
room = WAV_MAX_FRAMES / decim; /* 受中转缓冲帧数限制 */
}
if (room <= 0)
{
break;
}
/* 先凑满 room * decim 个原生帧,避免每次少读几帧造成丢样本 */
const int need = room * decim;
int have = 0;
bool eof = false;
while (have < need)
{
int got = (src->codec == AUDIO_CODEC_FLAC)
? flac_dec_read(src->flac,
src->decim_buf + (size_t)have * 2u * ob,
need - have)
: wav_read(src,
src->decim_buf + (size_t)have * 2u * ob,
need - have);
if (got < 0)
{
return -1;
}
if (got == 0)
{
eof = true;
break;
}
have += got;
}
int frames = have / decim;
if (frames > 0)
{
decim_mix(src->decim_buf, dst + (size_t)produced * 2u * ob,
frames, decim, ob);
produced += frames;
}
if (eof)
{
break; /* 文件末尾 */
}
}
if (produced > 0)
{
src->frames_read += (uint64_t)produced;
}
return produced;
}
int audio_source_read(audio_source_t *src, void *out, int max_frames)
{
if ((src == NULL) || (out == NULL) || (max_frames <= 0))
@@ -392,19 +505,26 @@ int audio_source_read(audio_source_t *src, void *out, int max_frames)
return -1;
}
if (src->codec == AUDIO_CODEC_FLAC)
if (max_frames > AUDIO_SRC_MAX_FRAMES)
{
int n = flac_dec_read(src->flac, out, max_frames);
if (n > 0)
{
src->frames_read += (uint64_t)n;
}
return n;
max_frames = AUDIO_SRC_MAX_FRAMES; /* 保护调用者缓冲 */
}
return wav_read(src, out, max_frames);
if (src->decim > 1)
{
return audio_source_read_decim(src, out, max_frames);
}
int n = (src->codec == AUDIO_CODEC_FLAC)
? flac_dec_read(src->flac, out, max_frames)
: wav_read(src, out, max_frames);
if (n > 0)
{
src->frames_read += (uint64_t)n;
}
return n;
}
uint32_t audio_source_elapsed_sec(const audio_source_t *src)
@@ -446,5 +566,136 @@ void audio_source_close(audio_source_t *src)
free(src->scratch);
src->scratch = NULL;
free(src->decim_buf);
src->decim_buf = NULL;
free(src);
}
/* ---------------- 采样率限制 ---------------- */
/**
* @brief 计算抽取倍数(把采样率压到 AUDIO_MAX_OUT_RATE 以内)
* @note 板载 ES8388 的 MCLK 由 I2S 产生,MCLK = mclk_multiple × fs.
* 24bit 时 mclk_multiple 必须是 384(3 的倍数),若直接播放 192kHz,
* MCLK 会高达 73.7MHz,远超 ES8388 的输入范围,结果是无声音或杂音.
* 因此这里对 88.2k/96k/192k 等做整数抽取,统一落到 ≤48kHz.
*/
static uint8_t pick_decim(uint32_t rate)
{
uint8_t d = 1;
while (((rate / d) > AUDIO_MAX_OUT_RATE) && (d < AUDIO_MAX_DECIM))
{
d++;
}
return d;
}
/**
* @brief 按输出采样率为音源分配抽取缓冲
* @retval true 成功;false 采样率过高或内存不足
*/
static bool decim_setup(audio_source_t *src)
{
uint8_t d = pick_decim(src->info.sample_rate);
if ((src->info.sample_rate / d) > AUDIO_MAX_OUT_RATE)
{
return false; /* 采样率过高,无法用整数抽取覆盖 */
}
src->decim = d;
if (d <= 1)
{
return true; /* 常见 44.1k/48k:完全不额外占用内存 */
}
size_t bytes = (size_t)DECIM_CHUNK_FRAMES * d * 2u * src->info.out_bytes;
src->decim_buf = (uint8_t *)malloc(bytes);
if (src->decim_buf == NULL)
{
return false;
}
/* 对外统一暴露输出采样率,I2S / 界面都按这个值工作 */
src->info.sample_rate /= d;
return true;
}
/* ---------------- 抽取用的采样读写 ---------------- */
static inline int32_t pcm_sample_get(const uint8_t *p, uint8_t bytes)
{
switch (bytes)
{
case 2:
return (int16_t)(p[0] | (p[1] << 8));
case 3:
{
int32_t v = (int32_t)(p[0] | (p[1] << 8) | (p[2] << 16));
if (v & 0x800000)
{
v -= 0x1000000;
}
return v;
}
default:
return (int32_t)((uint32_t)p[0] | ((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24));
}
}
static inline void pcm_sample_put(uint8_t *p, uint8_t bytes, int32_t v)
{
switch (bytes)
{
case 2:
p[0] = (uint8_t)(v & 0xFF);
p[1] = (uint8_t)((v >> 8) & 0xFF);
break;
case 3:
p[0] = (uint8_t)(v & 0xFF);
p[1] = (uint8_t)((v >> 8) & 0xFF);
p[2] = (uint8_t)((v >> 16) & 0xFF);
break;
default:
p[0] = (uint8_t)(v & 0xFF);
p[1] = (uint8_t)((v >> 8) & 0xFF);
p[2] = (uint8_t)((v >> 16) & 0xFF);
p[3] = (uint8_t)((v >> 24) & 0xFF);
break;
}
}
/**
* @brief 整数抽取:把 decim 帧平均成 1 帧(简易低通,抑制抽取混叠)
*/
static void decim_mix(const uint8_t *src, uint8_t *dst, int frames, int decim, uint8_t ob)
{
for (int i = 0; i < frames; i++)
{
for (int c = 0; c < 2; c++)
{
int64_t sum = 0;
for (int k = 0; k < decim; k++)
{
const size_t idx = ((size_t)i * (size_t)decim + (size_t)k) * 2u + (size_t)c;
sum += pcm_sample_get(src + idx * ob, ob);
}
pcm_sample_put(dst + ((size_t)i * 2u + (size_t)c) * ob, ob, (int32_t)(sum / decim));
}
}
}
+7 -1
View File
@@ -21,6 +21,12 @@
* 17~24bit → 24bit(每采样 3 字节)
* 25~32bit → 32bit(每采样 4 字节)
* 非整字节位深(如 20bit)按左对齐放入更宽的槽位,音量不受影响。
*
* 采样率限制:
* ES8388 的 MCLK 由 I2S 产生(MCLK = mclk_multiple × fs),24bit 时
* mclk_multiple 必须是 384。若直接播放 192kHz,MCLK 会到 73.7MHz,
* 远超 codec 能力。因此 88.2k/96k/192k 等会做整数抽取降到 ≤48kHz,
* audio_info_t.sample_rate 返回的始终是「实际输出采样率」。
****************************************************************************************************
*/
@@ -43,7 +49,7 @@ typedef enum {
/** 音源信息 */
typedef struct {
audio_codec_t codec; /* 容器类型 */
uint32_t sample_rate; /* 采样率 → 用于配置 I2S 频率 */
uint32_t sample_rate; /* 采样率 → 用于配置 I2S 频率(已按输出上限抽取) */
uint16_t bits; /* 源位深(界面显示用) */
uint16_t channels; /* 源声道数 */
uint16_t out_bits; /* I2S 输出位宽:16 / 24 / 32 */
+15 -20
View File
@@ -292,6 +292,8 @@ static void audio_set_format(uint32_t sample_rate, uint16_t bits)
/* ---------------- 曲目扫描 ---------------- */
/**
* @brief 判断是否为可播放的音频文件(.wav / .flac)
* @note 两种扩展名都要检查,不能因为不是 .wav 就提前 return false,
* 否则 .flac 永远被过滤掉。
*/
static bool name_is_audio(const char *name)
{
@@ -302,34 +304,27 @@ static bool name_is_audio(const char *name)
return false;
}
const char *ext = name + len - 4; /* 指向 ".xxx" */
const char *ext = name + len - 4; /* 指向最后 4 个字符 */
if (ext[0] != '.')
{
return false;
}
/* .wav */
if (((ext[1] == 'w') || (ext[1] == 'W')) &&
/* .wav —— 最后 4 个字符为 ".wav" */
if ((ext[0] == '.') &&
((ext[1] == 'w') || (ext[1] == 'W')) &&
((ext[2] == 'a') || (ext[2] == 'A')) &&
((ext[3] == 'v') || (ext[3] == 'V')))
{
return true;
}
/* .flac(长度至少 5:"a.flac") */
if (len >= 5)
{
const char *f = name + len - 5;
/* .flac —— 最后 5 个字符为 ".flac" */
const char *ext5 = name + len - 5;
if ((f[0] == '.') &&
((f[1] == 'f') || (f[1] == 'F')) &&
((f[2] == 'l') || (f[2] == 'L')) &&
((f[3] == 'a') || (f[3] == 'A')) &&
((f[4] == 'c') || (f[4] == 'C')))
{
return true;
}
if ((ext5[0] == '.') &&
((ext5[1] == 'f') || (ext5[1] == 'F')) &&
((ext5[2] == 'l') || (ext5[2] == 'L')) &&
((ext5[3] == 'a') || (ext5[3] == 'A')) &&
((ext5[4] == 'c') || (ext5[4] == 'C')))
{
return true;
}
return false;