add flac support and files management app
This commit is contained in:
1 parent
4126ea13a1
commit
45f7b83e7c
12 files changed
+1526
-139
No files matched your search
+266
-15
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user