702 lines
19 KiB
C
702 lines
19 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 中转缓冲容纳的最大帧数 */
|
||
|
||
#define AUDIO_SRC_MAX_FRAMES 1024 /* 单次 audio_source_read 最多输出的帧数(与播放器缓冲一致) */
|
||
#define DECIM_CHUNK_FRAMES 256 /* 抽取时每次处理多少输出帧(控制临时缓冲大小) */
|
||
#define AUDIO_MAX_OUT_RATE 48000 /* 输出采样率上限 */
|
||
#define AUDIO_MAX_DECIM 8 /* 最大抽取倍数 */
|
||
|
||
/* ---------------- 内部结构 ---------------- */
|
||
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;
|
||
|
||
/* ---- 高采样率抽取(192k/96k → ≤48k) ---- */
|
||
uint8_t decim; /* 抽取倍数,1 表示不抽取 */
|
||
uint8_t *decim_buf; /* 抽取用的原生采样中转缓冲 */
|
||
};
|
||
|
||
/* ---------------- 位宽映射 ---------------- */
|
||
|
||
/**
|
||
* @brief 源位深 → I2S 输出位宽
|
||
*/
|
||
static inline uint16_t pick_out_bits(uint16_t bits)
|
||
{
|
||
if (bits <= 16)
|
||
{
|
||
return 16;
|
||
}
|
||
|
||
if (bits <= 24)
|
||
{
|
||
return 24;
|
||
}
|
||
|
||
return 32;
|
||
}
|
||
|
||
/* 采样率限制 / 抽取(实现见文件末尾,供 audio_source_open 使用) */
|
||
static bool decim_setup(audio_source_t *src);
|
||
static void decim_mix(const uint8_t *src, uint8_t *dst, int frames, int decim, uint8_t ob);
|
||
|
||
/* ---------------- 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);
|
||
}
|
||
}
|
||
|
||
/* 注意:frames_read 统一由 audio_source_read() 累加,这里不要重复计数 */
|
||
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;
|
||
|
||
/* 高采样率(96k/192k)抽取到 ES8388 / MCLK 能承受的范围 */
|
||
if (!decim_setup(src))
|
||
{
|
||
ESP_LOGE(TAG, "unsupported sample rate %u Hz: %s",
|
||
(unsigned)fi.sample_rate, path);
|
||
audio_source_close(src);
|
||
return NULL;
|
||
}
|
||
|
||
if (info != NULL)
|
||
{
|
||
*info = src->info;
|
||
}
|
||
|
||
ESP_LOGI(TAG, "%s: FLAC %u Hz%s, %u bit -> I2S %u bit @ %u Hz, %u ch, %u s",
|
||
path, (unsigned)fi.sample_rate,
|
||
(src->decim > 1) ? " (downsampled)" : "",
|
||
src->info.bits, src->info.out_bits,
|
||
(unsigned)src->info.sample_rate, 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;
|
||
|
||
/* 高采样率(96k/192k)抽取到 ES8388 / MCLK 能承受的范围 */
|
||
if (!decim_setup(src))
|
||
{
|
||
ESP_LOGW(TAG, "unsupported sample rate %u Hz: %s",
|
||
(unsigned)src->info.sample_rate, path);
|
||
audio_source_close(src);
|
||
return NULL;
|
||
}
|
||
|
||
/* 原始 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;
|
||
}
|
||
|
||
/**
|
||
* @brief 带抽取的读取:把 decim 个原生帧合成 1 个输出帧
|
||
*/
|
||
static int audio_source_read_decim(audio_source_t *src, void *out, int max_frames)
|
||
{
|
||
uint8_t *dst = (uint8_t *)out;
|
||
const uint8_t ob = src->info.out_bytes;
|
||
const int decim = src->decim;
|
||
int produced = 0;
|
||
|
||
while (produced < max_frames)
|
||
{
|
||
int room = max_frames - produced;
|
||
|
||
if (room > DECIM_CHUNK_FRAMES)
|
||
{
|
||
room = DECIM_CHUNK_FRAMES;
|
||
}
|
||
|
||
if ((room * decim) > WAV_MAX_FRAMES)
|
||
{
|
||
room = WAV_MAX_FRAMES / decim; /* 受中转缓冲帧数限制 */
|
||
}
|
||
|
||
if (room <= 0)
|
||
{
|
||
break;
|
||
}
|
||
|
||
/* 先凑满 room * decim 个原生帧,避免每次少读几帧造成丢样本 */
|
||
const int need = room * decim;
|
||
int have = 0;
|
||
bool eof = false;
|
||
|
||
while (have < need)
|
||
{
|
||
int got = (src->codec == AUDIO_CODEC_FLAC)
|
||
? flac_dec_read(src->flac,
|
||
src->decim_buf + (size_t)have * 2u * ob,
|
||
need - have)
|
||
: wav_read(src,
|
||
src->decim_buf + (size_t)have * 2u * ob,
|
||
need - have);
|
||
|
||
if (got < 0)
|
||
{
|
||
return -1;
|
||
}
|
||
|
||
if (got == 0)
|
||
{
|
||
eof = true;
|
||
break;
|
||
}
|
||
|
||
have += got;
|
||
}
|
||
|
||
int frames = have / decim;
|
||
|
||
if (frames > 0)
|
||
{
|
||
decim_mix(src->decim_buf, dst + (size_t)produced * 2u * ob,
|
||
frames, decim, ob);
|
||
produced += frames;
|
||
}
|
||
|
||
if (eof)
|
||
{
|
||
break; /* 文件末尾 */
|
||
}
|
||
}
|
||
|
||
if (produced > 0)
|
||
{
|
||
src->frames_read += (uint64_t)produced;
|
||
}
|
||
|
||
return produced;
|
||
}
|
||
|
||
int audio_source_read(audio_source_t *src, void *out, int max_frames)
|
||
{
|
||
if ((src == NULL) || (out == NULL) || (max_frames <= 0))
|
||
{
|
||
return -1;
|
||
}
|
||
|
||
if (max_frames > AUDIO_SRC_MAX_FRAMES)
|
||
{
|
||
max_frames = AUDIO_SRC_MAX_FRAMES; /* 保护调用者缓冲 */
|
||
}
|
||
|
||
if (src->decim > 1)
|
||
{
|
||
return audio_source_read_decim(src, out, max_frames);
|
||
}
|
||
|
||
int n = (src->codec == AUDIO_CODEC_FLAC)
|
||
? flac_dec_read(src->flac, out, max_frames)
|
||
: wav_read(src, out, max_frames);
|
||
|
||
if (n > 0)
|
||
{
|
||
src->frames_read += (uint64_t)n;
|
||
}
|
||
|
||
return n;
|
||
}
|
||
|
||
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->decim_buf);
|
||
src->decim_buf = NULL;
|
||
|
||
free(src);
|
||
}
|
||
|
||
/* ---------------- 采样率限制 ---------------- */
|
||
|
||
/**
|
||
* @brief 计算抽取倍数(把采样率压到 AUDIO_MAX_OUT_RATE 以内)
|
||
* @note 板载 ES8388 的 MCLK 由 I2S 产生,MCLK = mclk_multiple × fs.
|
||
* 24bit 时 mclk_multiple 必须是 384(3 的倍数),若直接播放 192kHz,
|
||
* MCLK 会高达 73.7MHz,远超 ES8388 的输入范围,结果是无声音或杂音.
|
||
* 因此这里对 88.2k/96k/192k 等做整数抽取,统一落到 ≤48kHz.
|
||
*/
|
||
static uint8_t pick_decim(uint32_t rate)
|
||
{
|
||
uint8_t d = 1;
|
||
|
||
while (((rate / d) > AUDIO_MAX_OUT_RATE) && (d < AUDIO_MAX_DECIM))
|
||
{
|
||
d++;
|
||
}
|
||
|
||
return d;
|
||
}
|
||
|
||
/**
|
||
* @brief 按输出采样率为音源分配抽取缓冲
|
||
* @retval true 成功;false 采样率过高或内存不足
|
||
*/
|
||
static bool decim_setup(audio_source_t *src)
|
||
{
|
||
uint8_t d = pick_decim(src->info.sample_rate);
|
||
|
||
if ((src->info.sample_rate / d) > AUDIO_MAX_OUT_RATE)
|
||
{
|
||
return false; /* 采样率过高,无法用整数抽取覆盖 */
|
||
}
|
||
|
||
src->decim = d;
|
||
|
||
if (d <= 1)
|
||
{
|
||
return true; /* 常见 44.1k/48k:完全不额外占用内存 */
|
||
}
|
||
|
||
size_t bytes = (size_t)DECIM_CHUNK_FRAMES * d * 2u * src->info.out_bytes;
|
||
src->decim_buf = (uint8_t *)malloc(bytes);
|
||
|
||
if (src->decim_buf == NULL)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
/* 对外统一暴露输出采样率,I2S / 界面都按这个值工作 */
|
||
src->info.sample_rate /= d;
|
||
|
||
return true;
|
||
}
|
||
|
||
/* ---------------- 抽取用的采样读写 ---------------- */
|
||
|
||
static inline int32_t pcm_sample_get(const uint8_t *p, uint8_t bytes)
|
||
{
|
||
switch (bytes)
|
||
{
|
||
case 2:
|
||
return (int16_t)(p[0] | (p[1] << 8));
|
||
|
||
case 3:
|
||
{
|
||
int32_t v = (int32_t)(p[0] | (p[1] << 8) | (p[2] << 16));
|
||
|
||
if (v & 0x800000)
|
||
{
|
||
v -= 0x1000000;
|
||
}
|
||
|
||
return v;
|
||
}
|
||
|
||
default:
|
||
return (int32_t)((uint32_t)p[0] | ((uint32_t)p[1] << 8) |
|
||
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24));
|
||
}
|
||
}
|
||
|
||
static inline void pcm_sample_put(uint8_t *p, uint8_t bytes, int32_t v)
|
||
{
|
||
switch (bytes)
|
||
{
|
||
case 2:
|
||
p[0] = (uint8_t)(v & 0xFF);
|
||
p[1] = (uint8_t)((v >> 8) & 0xFF);
|
||
break;
|
||
|
||
case 3:
|
||
p[0] = (uint8_t)(v & 0xFF);
|
||
p[1] = (uint8_t)((v >> 8) & 0xFF);
|
||
p[2] = (uint8_t)((v >> 16) & 0xFF);
|
||
break;
|
||
|
||
default:
|
||
p[0] = (uint8_t)(v & 0xFF);
|
||
p[1] = (uint8_t)((v >> 8) & 0xFF);
|
||
p[2] = (uint8_t)((v >> 16) & 0xFF);
|
||
p[3] = (uint8_t)((v >> 24) & 0xFF);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 整数抽取:把 decim 帧平均成 1 帧(简易低通,抑制抽取混叠)
|
||
*/
|
||
static void decim_mix(const uint8_t *src, uint8_t *dst, int frames, int decim, uint8_t ob)
|
||
{
|
||
for (int i = 0; i < frames; i++)
|
||
{
|
||
for (int c = 0; c < 2; c++)
|
||
{
|
||
int64_t sum = 0;
|
||
|
||
for (int k = 0; k < decim; k++)
|
||
{
|
||
const size_t idx = ((size_t)i * (size_t)decim + (size_t)k) * 2u + (size_t)c;
|
||
sum += pcm_sample_get(src + idx * ob, ob);
|
||
}
|
||
|
||
pcm_sample_put(dst + ((size_t)i * 2u + (size_t)c) * ob, ob, (int32_t)(sum / decim));
|
||
}
|
||
}
|
||
}
|