HiFi player project, first commit.

This commit is contained in:
francis.wang committed 2026-10-26 20:07:12 +08:00
commit 4126ea13a1
49 files changed
+6333

No files matched your search

+450
View File
@@ -0,0 +1,450 @@
/**
****************************************************************************************************
* @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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}