/** **************************************************************************************************** * @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 #include #include #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; }