Files
esp32s3_player/main/APP/player/flac_dec.cpp
T

579 lines
16 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
****************************************************************************************************
* @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;
}