HiFi player project, first commit.
This commit is contained in:
commit
4126ea13a1
49 files changed
+6333
No files matched your search
@@ -0,0 +1,578 @@
|
||||
/**
|
||||
****************************************************************************************************
|
||||
* @file flac_dec.cpp
|
||||
* @author HiFi Player Project
|
||||
* @version V1.5
|
||||
* @date 2026-01-01
|
||||
* @brief FLAC 解码器的 C++ 实现(对外暴露纯 C 接口,见 flac_dec.h)
|
||||
*
|
||||
* 数据流:
|
||||
* FATFS 文件 ──f_read──▶ 8KB 码流缓冲 ──decode()──▶ 原生位深交错 PCM
|
||||
* │
|
||||
* 单声道复制 / 多声道取前两声道
|
||||
* ▼
|
||||
* 立体声 + 原生位深(左对齐)→ I2S
|
||||
*
|
||||
* 位的处理:
|
||||
* 库的 uint8_t 输出重载是“原生字节打包 + 左对齐”:
|
||||
* 16bit → 2 字节、24bit → 3 字节、32bit → 4 字节,
|
||||
* 20bit 这类非整字节位深则占 3 字节并填满其高 20 位。
|
||||
* 正好等价于 I2S 的 16/24/32bit 数据格式,所以这里不做任何降位,
|
||||
* 上层按 out_bytes 配置 I2S 位宽即可获得无损通路。
|
||||
*
|
||||
* 码流缓冲策略:未消费数据前移 + 追加读取,保证 NEED_MORE_DATA 时不丢数据,
|
||||
* 并带“无进展”保护,坏文件不会死循环。
|
||||
****************************************************************************************************
|
||||
*/
|
||||
|
||||
#include "flac_dec.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "micro_flac/flac_decoder.h"
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
#define TAG "flac_dec"
|
||||
|
||||
#define IN_BUF_SIZE (8 * 1024) /* 码流缓冲(压缩数据) */
|
||||
#define MAX_STALL 8 /* 连续无进展的容忍次数 */
|
||||
|
||||
struct flac_dec {
|
||||
FIL fp; /* FATFS 文件对象 */
|
||||
bool fp_open;
|
||||
|
||||
micro_flac::FLACDecoder *decoder;
|
||||
|
||||
uint8_t *in_buf; /* 压缩码流缓冲 */
|
||||
size_t in_len; /* 缓冲内有效字节数 */
|
||||
size_t in_pos; /* 已消费位置 */
|
||||
bool in_eof; /* 文件已读完 */
|
||||
|
||||
uint8_t *pcm; /* 解码输出(源声道数 × 原生位深,交错) */
|
||||
size_t pcm_capacity; /* 缓冲字节数 */
|
||||
size_t pcm_bytes; /* 本帧有效字节数 */
|
||||
size_t pcm_pos; /* 已消费字节位置 */
|
||||
|
||||
uint8_t bps; /* 源每采样字节数 */
|
||||
uint8_t out_bytes; /* 输出每采样字节数(2/3/4) */
|
||||
|
||||
flac_info_t info;
|
||||
bool header_ready;
|
||||
bool stream_end;
|
||||
bool failed;
|
||||
};
|
||||
|
||||
/* ================================================================================================
|
||||
* 内部工具
|
||||
* ============================================================================================== */
|
||||
|
||||
/**
|
||||
* @brief 按 STREAMINFO 的块大小分配解码输出缓冲(字节数)
|
||||
* @retval true 成功
|
||||
*/
|
||||
static bool ensure_pcm_buffer(flac_dec_t *d, size_t bytes)
|
||||
{
|
||||
if (bytes <= d->pcm_capacity)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/* 优先放 PSRAM,失败再退回内部 RAM */
|
||||
void *p = heap_caps_malloc(bytes, MALLOC_CAP_SPIRAM);
|
||||
|
||||
if (p == NULL)
|
||||
{
|
||||
ESP_LOGW(TAG, "psram alloc failed (%u bytes), try internal", (unsigned)bytes);
|
||||
p = malloc(bytes);
|
||||
}
|
||||
|
||||
if (p == NULL)
|
||||
{
|
||||
ESP_LOGE(TAG, "no memory for pcm buffer (%u bytes)", (unsigned)bytes);
|
||||
return false;
|
||||
}
|
||||
|
||||
free(d->pcm); /* heap_caps_malloc 的指针可以用 free 释放 */
|
||||
d->pcm = (uint8_t *)p;
|
||||
d->pcm_capacity = bytes;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 把未消费的码流前移,并从文件追加新数据
|
||||
* @retval true 操作成功(不代表一定有新数据;看 in_len 是否增长)
|
||||
* false 读文件失败
|
||||
*/
|
||||
static bool feed_more(flac_dec_t *d)
|
||||
{
|
||||
size_t remain = d->in_len - d->in_pos;
|
||||
|
||||
if ((d->in_pos > 0) && (remain > 0))
|
||||
{
|
||||
memmove(d->in_buf, d->in_buf + d->in_pos, remain);
|
||||
}
|
||||
|
||||
d->in_len = remain;
|
||||
d->in_pos = 0;
|
||||
|
||||
if (d->in_eof || (d->in_len >= IN_BUF_SIZE))
|
||||
{
|
||||
return true; /* 文件读完或缓冲已满,只能靠现有数据 */
|
||||
}
|
||||
|
||||
UINT br = 0;
|
||||
FRESULT fr = f_read(&d->fp, d->in_buf + d->in_len, (UINT)(IN_BUF_SIZE - d->in_len), &br);
|
||||
|
||||
if (fr != FR_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "f_read failed (%d)", (int)fr);
|
||||
return false;
|
||||
}
|
||||
|
||||
d->in_len += br;
|
||||
|
||||
if (br == 0)
|
||||
{
|
||||
d->in_eof = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @brief 是否还有可交给解码器的数据 */
|
||||
static inline bool has_input(const flac_dec_t *d)
|
||||
{
|
||||
return d->in_pos < d->in_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按源位深推导输出每采样字节数
|
||||
* @note 1 字节(8bit 及以下)统一扩展成 2 字节(左对齐),
|
||||
* 其余直接沿用源字节数(2/3/4),这样 I2S 用 16/24/32bit 都能直接接收
|
||||
*/
|
||||
static inline uint8_t pick_out_bytes(uint8_t bps)
|
||||
{
|
||||
if (bps < 2)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (bps > 4)
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
return bps;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 写一个输出采样(把源采样按需要补齐/复制)
|
||||
* @param dst 输出位置(out_bytes 字节,小端,左对齐)
|
||||
* @param src 源采样起点(bps 字节,小端,左对齐)
|
||||
*/
|
||||
static inline void write_sample(uint8_t *dst, const uint8_t *src, uint8_t bps, uint8_t out_bytes)
|
||||
{
|
||||
if (bps == out_bytes)
|
||||
{
|
||||
memcpy(dst, src, out_bytes);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bps == 1)
|
||||
{
|
||||
/* 8bit 及以下:左对齐扩成 16bit(低字节补 0,高字节就是原样本) */
|
||||
dst[0] = 0;
|
||||
dst[1] = src[0];
|
||||
|
||||
for (uint8_t i = 2; i < out_bytes; i++)
|
||||
{
|
||||
dst[i] = (src[0] & 0x80) ? 0xFF : 0x00; /* 符号扩展到更高的字节 */
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* 理论上不会走到这里;保守处理为左对齐拷贝 */
|
||||
uint8_t n = (bps < out_bytes) ? bps : out_bytes;
|
||||
|
||||
memset(dst, 0, out_bytes);
|
||||
memcpy(dst + (out_bytes - n), src, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 把解码结果(源声道数)规整成 2 声道,写入调用者缓冲
|
||||
* @param frames 本次要输出的帧数
|
||||
* @retval 无
|
||||
*/
|
||||
static void expand_frames(flac_dec_t *d, const uint8_t *src, uint8_t *dst, size_t frames)
|
||||
{
|
||||
const size_t ch = d->info.channels;
|
||||
const size_t bps = d->bps;
|
||||
const size_t ob = d->out_bytes;
|
||||
|
||||
if ((ch == 2) && (bps == ob))
|
||||
{
|
||||
memcpy(dst, src, frames * 2 * ob); /* 最常见路径:立体声原生位深 */
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < frames; i++)
|
||||
{
|
||||
const uint8_t *s = src + i * ch * bps;
|
||||
uint8_t *o = dst + i * 2 * ob;
|
||||
|
||||
write_sample(o, s, (uint8_t)bps, (uint8_t)ob); /* 左声道 = 声道 0 */
|
||||
|
||||
if (ch >= 2)
|
||||
{
|
||||
write_sample(o + ob, s + bps, (uint8_t)bps, (uint8_t)ob); /* 右声道 = 声道 1 */
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(o + ob, o, ob); /* 单声道复制 */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================================================
|
||||
* 对外接口
|
||||
* ============================================================================================== */
|
||||
|
||||
flac_dec_t *flac_dec_open(const char *path)
|
||||
{
|
||||
if (path == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
flac_dec_t *d = new (std::nothrow) flac_dec();
|
||||
|
||||
if (d == NULL)
|
||||
{
|
||||
ESP_LOGE(TAG, "no memory for decoder");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(d, 0, sizeof(*d));
|
||||
|
||||
if (f_open(&d->fp, path, FA_READ) != FR_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "open %s failed", path);
|
||||
flac_dec_close(d);
|
||||
return NULL;
|
||||
}
|
||||
d->fp_open = true;
|
||||
|
||||
d->decoder = new (std::nothrow) micro_flac::FLACDecoder();
|
||||
|
||||
if (d->decoder == NULL)
|
||||
{
|
||||
ESP_LOGE(TAG, "no memory for FLACDecoder");
|
||||
flac_dec_close(d);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
d->in_buf = (uint8_t *)malloc(IN_BUF_SIZE);
|
||||
|
||||
if (d->in_buf == NULL)
|
||||
{
|
||||
ESP_LOGE(TAG, "no memory for input buffer");
|
||||
flac_dec_close(d);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ---------------- 解析头部(STREAMINFO) ---------------- */
|
||||
int stall = 0;
|
||||
|
||||
while (!d->header_ready)
|
||||
{
|
||||
if (!has_input(d) && !feed_more(d))
|
||||
{
|
||||
flac_dec_close(d);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!has_input(d))
|
||||
{
|
||||
ESP_LOGE(TAG, "empty or truncated stream");
|
||||
flac_dec_close(d);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t consumed = 0;
|
||||
size_t samples = 0;
|
||||
|
||||
/* 头部解析阶段不需要输出缓冲 */
|
||||
micro_flac::FLACDecoderResult res =
|
||||
d->decoder->decode(d->in_buf + d->in_pos, d->in_len - d->in_pos,
|
||||
(uint8_t *)NULL, 0, consumed, samples);
|
||||
|
||||
d->in_pos += consumed;
|
||||
|
||||
if (res == micro_flac::FLAC_DECODER_HEADER_READY)
|
||||
{
|
||||
const micro_flac::FLACStreamInfo &si = d->decoder->get_stream_info();
|
||||
|
||||
if (!si.is_valid() || (si.num_channels() < 1) || (si.num_channels() > 8) ||
|
||||
(si.max_block_size() == 0) || (si.bytes_per_sample() == 0) ||
|
||||
(si.bytes_per_sample() > 4))
|
||||
{
|
||||
ESP_LOGE(TAG, "invalid stream info");
|
||||
flac_dec_close(d);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
d->info.sample_rate = si.sample_rate();
|
||||
d->info.channels = (uint8_t)si.num_channels();
|
||||
d->info.bits = (uint8_t)si.bits_per_sample();
|
||||
d->info.max_block_size = si.max_block_size();
|
||||
d->info.total_samples = si.total_samples_per_channel();
|
||||
|
||||
d->bps = (uint8_t)si.bytes_per_sample();
|
||||
d->out_bytes = pick_out_bytes(d->bps);
|
||||
d->info.out_bytes = d->out_bytes;
|
||||
|
||||
/* 解码输出缓冲按「最大块 × 声道数 × 源字节数」分配 */
|
||||
if (!ensure_pcm_buffer(d, (size_t)d->info.max_block_size *
|
||||
d->info.channels * d->bps))
|
||||
{
|
||||
flac_dec_close(d);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
d->header_ready = true;
|
||||
stall = 0;
|
||||
}
|
||||
else if (res == micro_flac::FLAC_DECODER_NEED_MORE_DATA)
|
||||
{
|
||||
size_t before = d->in_len - d->in_pos; /* feed 之前的待解析字节数 */
|
||||
|
||||
if (!feed_more(d))
|
||||
{
|
||||
flac_dec_close(d);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 没有新数据可加(文件读完 / 缓冲已满)→ 头部不完整 */
|
||||
if ((d->in_len <= before) && d->in_eof)
|
||||
{
|
||||
ESP_LOGE(TAG, "truncated header");
|
||||
flac_dec_close(d);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (consumed == 0)
|
||||
{
|
||||
if (++stall > MAX_STALL)
|
||||
{
|
||||
ESP_LOGE(TAG, "header parse stalled");
|
||||
flac_dec_close(d);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stall = 0;
|
||||
}
|
||||
}
|
||||
else if (res == micro_flac::FLAC_DECODER_END_OF_STREAM)
|
||||
{
|
||||
ESP_LOGE(TAG, "no audio in stream");
|
||||
flac_dec_close(d);
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "header parse failed (%d)", (int)res);
|
||||
flac_dec_close(d);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "%s: %u Hz, %u bit (out %u byte, %u bit slot), %u ch, block=%u",
|
||||
path, (unsigned)d->info.sample_rate, d->info.bits,
|
||||
d->out_bytes, (unsigned)d->out_bytes * 8,
|
||||
d->info.channels, (unsigned)d->info.max_block_size);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
int flac_dec_get_info(flac_dec_t *dec, flac_info_t *info)
|
||||
{
|
||||
if ((dec == NULL) || (info == NULL) || !dec->header_ready)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
*info = dec->info;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int flac_dec_read(flac_dec_t *d, void *out, int max_frames)
|
||||
{
|
||||
if ((d == NULL) || (out == NULL) || (max_frames <= 0))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (d->failed || !d->header_ready)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
const size_t ch = d->info.channels;
|
||||
const size_t bps = d->bps;
|
||||
const size_t ob = d->out_bytes;
|
||||
const size_t src_frame = ch * bps; /* 源每帧字节数 */
|
||||
|
||||
uint8_t *dst_base = (uint8_t *)out;
|
||||
int written = 0;
|
||||
int stall = 0;
|
||||
|
||||
while (written < max_frames)
|
||||
{
|
||||
/* ---- 1. 先把已解码但没送出的帧搬走 ---- */
|
||||
if (d->pcm_pos < d->pcm_bytes)
|
||||
{
|
||||
size_t avail = (d->pcm_bytes - d->pcm_pos) / src_frame; /* 剩余帧数 */
|
||||
size_t need = (size_t)(max_frames - written);
|
||||
size_t n = (avail < need) ? avail : need;
|
||||
|
||||
if (n == 0)
|
||||
{
|
||||
d->pcm_pos = d->pcm_bytes; /* 丢弃不足一帧的残料 */
|
||||
continue;
|
||||
}
|
||||
|
||||
expand_frames(d, d->pcm + d->pcm_pos, dst_base + (size_t)written * 2 * ob, n);
|
||||
|
||||
d->pcm_pos += n * src_frame;
|
||||
written += (int)n;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (d->stream_end)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
/* ---- 2. 码流用尽则补充 ---- */
|
||||
if (!has_input(d) && !feed_more(d))
|
||||
{
|
||||
d->failed = true;
|
||||
return (written > 0) ? written : -1;
|
||||
}
|
||||
|
||||
if (!has_input(d))
|
||||
{
|
||||
d->stream_end = true; /* 文件读完且没有待解码数据 */
|
||||
break;
|
||||
}
|
||||
|
||||
/* ---- 3. 解一帧 ---- */
|
||||
size_t consumed = 0;
|
||||
size_t samples = 0;
|
||||
|
||||
micro_flac::FLACDecoderResult res =
|
||||
d->decoder->decode(d->in_buf + d->in_pos, d->in_len - d->in_pos,
|
||||
d->pcm, d->pcm_capacity, consumed, samples);
|
||||
|
||||
d->in_pos += consumed;
|
||||
|
||||
if (res == micro_flac::FLAC_DECODER_SUCCESS)
|
||||
{
|
||||
size_t bytes = samples * bps;
|
||||
|
||||
if (bytes > d->pcm_capacity)
|
||||
{
|
||||
ESP_LOGE(TAG, "decoder overflow (%u > %u)",
|
||||
(unsigned)bytes, (unsigned)d->pcm_capacity);
|
||||
d->failed = true;
|
||||
return (written > 0) ? written : -1;
|
||||
}
|
||||
|
||||
d->pcm_bytes = bytes;
|
||||
d->pcm_pos = 0;
|
||||
|
||||
/* 无进展保护:空帧且未消费数据 */
|
||||
if ((bytes == 0) && (consumed == 0))
|
||||
{
|
||||
if (++stall > MAX_STALL)
|
||||
{
|
||||
ESP_LOGW(TAG, "decode stalled, stop stream");
|
||||
d->stream_end = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stall = 0;
|
||||
}
|
||||
}
|
||||
else if (res == micro_flac::FLAC_DECODER_HEADER_READY)
|
||||
{
|
||||
/* 正常不会二次出现;出现时忽略即可 */
|
||||
continue;
|
||||
}
|
||||
else if (res == micro_flac::FLAC_DECODER_NEED_MORE_DATA)
|
||||
{
|
||||
size_t before = d->in_len - d->in_pos; /* feed 之前的待解码字节数 */
|
||||
|
||||
if (!feed_more(d))
|
||||
{
|
||||
d->failed = true;
|
||||
return (written > 0) ? written : -1;
|
||||
}
|
||||
|
||||
/* 文件结束且没有新数据可加 → 剩余数据不足以解出完整帧,正常收尾 */
|
||||
if ((d->in_len <= before) && d->in_eof)
|
||||
{
|
||||
d->stream_end = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (res == micro_flac::FLAC_DECODER_END_OF_STREAM)
|
||||
{
|
||||
d->stream_end = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "decode error (%d)", (int)res);
|
||||
d->failed = true;
|
||||
return (written > 0) ? written : -1;
|
||||
}
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
void flac_dec_close(flac_dec_t *d)
|
||||
{
|
||||
if (d == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (d->fp_open)
|
||||
{
|
||||
f_close(&d->fp);
|
||||
d->fp_open = false;
|
||||
}
|
||||
|
||||
delete d->decoder;
|
||||
d->decoder = NULL;
|
||||
|
||||
free(d->in_buf);
|
||||
free(d->pcm);
|
||||
d->in_buf = NULL;
|
||||
d->pcm = NULL;
|
||||
|
||||
delete d;
|
||||
}
|
||||
Reference in new issue
Block a user