/** **************************************************************************************************** * @file audio_source.c * @author HiFi Player Project * @version V1.5 * @date 2026-01-01 * @brief 统一音源实现(WAV 解析 + FLAC 解码转发) * * 两种格式都输出「立体声 + 源文件原生位深」的 PCM,不做降位: * * WAV :f_read 原始 PCM ──▶ 单声道则复制 ──▶ 输出 * FLAC :flac_dec_read() ──▶ 已在内部做声道规整 + 原生位深输出 * * 格式识别:读取文件头 4 字节 * "RIFF" → WAV * "fLaC" → 原生 FLAC * "OggS" → Ogg FLAC **************************************************************************************************** */ #include "audio_source.h" #include #include #include #include "esp_log.h" #include "ff.h" #include "flac_dec.h" #define TAG "audio_src" #define WAV_MAX_BLOCKALIGN 8 /* 支持的 WAV 帧对齐上限(16/24/32bit × 最多 2 声道) */ #define WAV_MAX_FRAMES 1024 /* WAV 原始 PCM 中转缓冲容纳的最大帧数 */ /* ---------------- 内部结构 ---------------- */ struct audio_source { audio_codec_t codec; audio_info_t info; uint64_t frames_read; /* 已输出帧数(用于计算进度) */ /* ---- WAV ---- */ FIL wav_fp; bool wav_open; uint16_t block_align; /* 源每帧字节数 = channels * out_bytes */ uint32_t byte_rate; uint32_t data_offset; uint32_t data_size; uint32_t data_read; /* 已读字节数 */ uint8_t *scratch; /* 原始 PCM 中转缓冲 */ int scratch_frames; /* 缓冲能容纳的帧数 */ /* ---- FLAC ---- */ flac_dec_t *flac; }; /* ---------------- 位宽映射 ---------------- */ /** * @brief 源位深 → I2S 输出位宽 */ static inline uint16_t pick_out_bits(uint16_t bits) { if (bits <= 16) { return 16; } if (bits <= 24) { return 24; } return 32; } /* ---------------- WAV 解析 ---------------- */ static inline uint16_t rd_u16(const uint8_t *p) { return (uint16_t)(p[0] | (p[1] << 8)); } static inline uint32_t rd_u32(const uint8_t *p) { return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); } /** * @brief 解析 WAV 头部(支持 fmt/data 之间夹着 LIST、fact 等块) * @note 调用前文件指针需在文件开头;结束后文件指针位于 data 起始处 */ static bool wav_parse(FIL *fp, audio_info_t *info, uint32_t *data_offset, uint32_t *data_size, uint16_t *block_align, uint32_t *byte_rate, uint16_t *bits, uint16_t *channels) { uint8_t header[12]; uint8_t chunk[8]; uint8_t fmt[40]; UINT br = 0; bool have_fmt = false; bool have_data = false; uint16_t audio_format = 0; if ((f_read(fp, header, sizeof(header), &br) != FR_OK) || (br != sizeof(header))) { return false; } if ((memcmp(header, "RIFF", 4) != 0) || (memcmp(header + 8, "WAVE", 4) != 0)) { return false; } while (!have_data) { if ((f_read(fp, chunk, sizeof(chunk), &br) != FR_OK) || (br != sizeof(chunk))) { break; } uint32_t size = rd_u32(chunk + 4); if (memcmp(chunk, "fmt ", 4) == 0) { if ((size < 16) || (size > sizeof(fmt))) { break; } if ((f_read(fp, fmt, size, &br) != FR_OK) || (br != size)) { break; } audio_format = rd_u16(fmt); *channels = rd_u16(fmt + 2); info->sample_rate = rd_u32(fmt + 4); *byte_rate = rd_u32(fmt + 8); *block_align = rd_u16(fmt + 12); *bits = rd_u16(fmt + 14); if ((audio_format == 0xFFFE) && (size >= 26)) { /* WAVE_FORMAT_EXTENSIBLE:真实格式在 SubFormat GUID 的前两个字节 */ audio_format = rd_u16(fmt + 24); } have_fmt = true; } else if (memcmp(chunk, "data", 4) == 0) { FSIZE_t offset = f_tell(fp); FSIZE_t remain = f_size(fp) - offset; *data_offset = (uint32_t)offset; /* 有些录音文件 data 长度写 0 或 0xFFFFFFFF,此时用文件剩余长度 */ *data_size = ((size == 0) || (size > (uint32_t)remain)) ? (uint32_t)remain : size; have_data = true; break; } else { /* 跳过不关心的块(块长度 2 字节对齐) */ if (f_lseek(fp, f_tell(fp) + size + (size & 0x01)) != FR_OK) { break; } } } if (!have_fmt || !have_data) { return false; } if (*block_align == 0) { *block_align = (uint16_t)(((*bits) / 8) * (*channels)); } if (*byte_rate == 0) { *byte_rate = info->sample_rate * (*block_align); } /* 只接受线性 PCM,位深需为 16/24/32(非整字节位深无法从 WAV 头可靠判断) */ return (audio_format == 1) && (*channels >= 1) && (*channels <= 2) && (*block_align > 0) && (*block_align <= WAV_MAX_BLOCKALIGN) && ((*bits == 16) || (*bits == 24) || (*bits == 32)) && (info->sample_rate >= 8000) && (*byte_rate > 0); } /** * @brief WAV:把原始 PCM 规整成立体声(保留原生位深) */ static int wav_read(audio_source_t *src, void *out, int max_frames) { if ((src->data_read >= src->data_size) || (max_frames <= 0)) { return 0; } uint32_t remain_frames = (src->data_size - src->data_read) / src->block_align; uint32_t frames = (remain_frames < (uint32_t)max_frames) ? remain_frames : (uint32_t)max_frames; if ((int)frames > src->scratch_frames) { frames = (uint32_t)src->scratch_frames; } if (frames == 0) { return 0; } UINT br = 0; size_t want = (size_t)frames * src->block_align; if (f_read(&src->wav_fp, src->scratch, (UINT)want, &br) != FR_OK) { return -1; } if (br < src->block_align) { return 0; /* 不足一帧,视作结束 */ } frames = br / src->block_align; src->data_read += br; /* ---- 声道规整(位深保持不变) ---- */ const uint8_t *s = src->scratch; uint8_t *dst = (uint8_t *)out; const size_t ob = src->info.out_bytes; if (src->info.channels == 2) { /* 最常见路径:立体声直接拷贝(block_align == 2 * out_bytes) */ memcpy(dst, s, (size_t)frames * 2 * ob); } else { /* 单声道:左右都写同一样本 */ for (uint32_t i = 0; i < frames; i++) { uint8_t *o = dst + i * 2 * ob; memcpy(o, s + i * ob, ob); memcpy(o + ob, s + i * ob, ob); } } src->frames_read += frames; return (int)frames; } /* ---------------- 对外接口 ---------------- */ audio_source_t *audio_source_open(const char *path, audio_info_t *info) { if (path == NULL) { return NULL; } audio_source_t *src = (audio_source_t *)calloc(1, sizeof(audio_source_t)); if (src == NULL) { return NULL; } /* ---------------- 1. 打开文件并嗅探文件头 ---------------- */ if (f_open(&src->wav_fp, path, FA_READ) != FR_OK) { ESP_LOGE(TAG, "open %s failed", path); audio_source_close(src); return NULL; } src->wav_open = true; uint8_t magic[4] = {0}; UINT br = 0; bool is_flac = false; if ((f_read(&src->wav_fp, magic, sizeof(magic), &br) == FR_OK) && (br == sizeof(magic))) { is_flac = (memcmp(magic, "fLaC", 4) == 0) || (memcmp(magic, "OggS", 4) == 0); } /* ---------------- 2A. FLAC ---------------- */ if (is_flac) { /* 交给专门的解码器重新打开(它需要从文件开头开始解析) */ f_close(&src->wav_fp); src->wav_open = false; flac_info_t fi; src->flac = flac_dec_open(path); if ((src->flac == NULL) || (flac_dec_get_info(src->flac, &fi) != 0)) { ESP_LOGE(TAG, "flac open failed: %s", path); audio_source_close(src); return NULL; } src->codec = AUDIO_CODEC_FLAC; src->info.codec = AUDIO_CODEC_FLAC; src->info.sample_rate = fi.sample_rate; src->info.bits = fi.bits; src->info.channels = fi.channels; src->info.out_bytes = fi.out_bytes; src->info.out_bits = (uint16_t)fi.out_bytes * 8; src->info.total_sec = (fi.total_samples > 0) ? (uint32_t)(fi.total_samples / fi.sample_rate) : 0; 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, (unsigned)src->info.total_sec); return src; } /* ---------------- 2B. WAV ---------------- */ src->codec = AUDIO_CODEC_WAV; src->info.codec = AUDIO_CODEC_WAV; f_lseek(&src->wav_fp, 0); /* 回到文件开头重新解析 */ uint16_t bits = 0; uint16_t channels = 0; if (!wav_parse(&src->wav_fp, &src->info, &src->data_offset, &src->data_size, &src->block_align, &src->byte_rate, &bits, &channels)) { ESP_LOGW(TAG, "unsupported wav: %s", path); audio_source_close(src); return NULL; } src->info.bits = bits; src->info.channels = channels; src->info.out_bits = pick_out_bits(bits); src->info.out_bytes = (uint8_t)(bits / 8); /* WAV 位深必为 16/24/32 */ src->info.total_sec = src->data_size / src->byte_rate; /* 原始 PCM 中转缓冲(16/24/32bit × 最多 2 声道 → 每帧最多 8 字节) */ src->scratch = (uint8_t *)malloc((size_t)WAV_MAX_FRAMES * src->block_align); if (src->scratch == NULL) { ESP_LOGE(TAG, "no memory for wav scratch"); audio_source_close(src); return NULL; } src->scratch_frames = WAV_MAX_FRAMES; f_lseek(&src->wav_fp, src->data_offset); if (info != NULL) { *info = src->info; } ESP_LOGI(TAG, "%s: WAV %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, (unsigned)src->info.total_sec); return src; } int audio_source_read(audio_source_t *src, void *out, int max_frames) { if ((src == NULL) || (out == NULL) || (max_frames <= 0)) { return -1; } if (src->codec == AUDIO_CODEC_FLAC) { int n = flac_dec_read(src->flac, out, max_frames); if (n > 0) { src->frames_read += (uint64_t)n; } return n; } return wav_read(src, out, max_frames); } uint32_t audio_source_elapsed_sec(const audio_source_t *src) { if ((src == NULL) || (src->info.sample_rate == 0)) { return 0; } uint32_t sec = (uint32_t)(src->frames_read / src->info.sample_rate); if ((src->info.total_sec > 0) && (sec > src->info.total_sec)) { sec = src->info.total_sec; } return sec; } void audio_source_close(audio_source_t *src) { if (src == NULL) { return; } if (src->flac != NULL) { flac_dec_close(src->flac); src->flac = NULL; } if (src->wav_open) { f_close(&src->wav_fp); src->wav_open = false; } free(src->scratch); src->scratch = NULL; free(src); }