451 lines
12 KiB
C
451 lines
12 KiB
C
/**
|
||
****************************************************************************************************
|
||
* @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);
|
||
}
|