Files
esp32s3_player/main/APP/player/audio_source.c
T

451 lines
12 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 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);
}