ncm: FLAC 解码落地(dr_flac 0.13.4 单头入库) + 缓存键=歌曲ID+音质档位
- 解码分发 decoder.c:magic(fLaC→dr_flac / 其它→minimp3);flac ≤16bit→S16、 >16bit→S32 满幅送 audiod(AUDIO_FMT_S32),rate/ch/bits 按实际流,零 SRC 零裁剪 - song_url_auto 接受 flac(type 权威/url 扩展名兜底),降级语义不变;flac 下载限幅 1GB - 缓存键 song_<id>_<level>.<ext>:同歌同档命中不重下,换档新条目旧档留 LRU; ext 前导点正名(修双点名),lookup 回退历史双点名+旧无档键温和兼容 - audio_out S32 通路(AUDIOD/ALSA/WAV)+ 流死重开按 bits 还原格式 - 验证(0 真实请求):44.1k/16 与 96k/24 自制样本宿主+qemu 帧数/时长精确、 逐样本零误差;mp3 回归逐帧一致;player_flow_sim P1-P6(新 P6 档位键/flac ext)、 cache_evict/cover/pagination/window/audiod_chain sim 全 PASS;mipsel 0 warning - REPORT §19 / VENDOR_NOTES dr_flac 条目;未部署真机(留主会话)
This commit is contained in:
1 parent
80bc191593
commit
70aec677ce
23 files changed
+13377
-110
No files matched your search
@@ -0,0 +1,141 @@
|
||||
/* play/decoder.c — 格式分发解码实现(接口见 decoder.h,REPORT §19)。
|
||||
*
|
||||
* mp3 细节:minimp3_ex 的 dec.info(hz/channels)在首次 mp3dec_ex_read 后才保证有效,
|
||||
* 而调用方开 audio_out 前就要 rate/ch —— open 时先预读一批进 stash,read 先吐 stash。
|
||||
* seek(mp3):关+重开(MP3D_SEEK_TO_BYTE 索引在 open 时重建)再按字节近似 seek,同旧语义。
|
||||
*/
|
||||
#include "decoder.h"
|
||||
#include "minimp3_ex.h"
|
||||
#include "dr_flac.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define DEC_STASH_FRAMES 4096 /* open 预读上限(帧),与播放循环单块读同量级 */
|
||||
|
||||
enum { DEC_MP3 = 0, DEC_FLAC = 1 };
|
||||
|
||||
struct ncm_decoder {
|
||||
int kind;
|
||||
char path[512];
|
||||
int rate, ch, bits;
|
||||
int64_t total_frames; /* 0=未知 */
|
||||
/* mp3 */
|
||||
mp3dec_ex_t mp3;
|
||||
int16_t *stash; /* 预读样本(交织 s16),stash_n 个样本 */
|
||||
size_t stash_n, stash_off;
|
||||
/* flac */
|
||||
drflac *flac;
|
||||
};
|
||||
|
||||
/* magic 探测:首 4 字节 "fLaC" → flac;其余(ID3 / 0xFF 帧同步 / 未知)→ mp3
|
||||
*(未知格式留给 minimp3 在 open 时报错,不在本层加第三种分支)。 */
|
||||
static int sniff_kind(const char *path)
|
||||
{
|
||||
unsigned char m[4] = {0};
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (f) { size_t n = fread(m, 1, 4, f); fclose(f); if (n < 4) return DEC_MP3; }
|
||||
if (m[0] == 'f' && m[1] == 'L' && m[2] == 'a' && m[3] == 'C') return DEC_FLAC;
|
||||
return DEC_MP3;
|
||||
}
|
||||
|
||||
int ncm_dec_open(ncm_decoder_t **out, const char *path,
|
||||
int *rate, int *ch, int *bits, int64_t *total_frames)
|
||||
{
|
||||
if (!out || !path) return -1;
|
||||
ncm_decoder_t *d = (ncm_decoder_t *)calloc(1, sizeof *d);
|
||||
if (!d) return -1;
|
||||
d->kind = sniff_kind(path);
|
||||
snprintf(d->path, sizeof d->path, "%s", path);
|
||||
|
||||
if (d->kind == DEC_FLAC) {
|
||||
d->flac = drflac_open_file(path, NULL);
|
||||
if (!d->flac) { free(d); return -1; }
|
||||
d->rate = (int)d->flac->sampleRate;
|
||||
d->ch = (int)d->flac->channels;
|
||||
/* 位宽映射(AUDIOD §4 归一交给 SDK/audiod):≤16 → S16;>16 → S32 满幅 */
|
||||
d->bits = (d->flac->bitsPerSample <= 16) ? 16 : 32;
|
||||
d->total_frames = (int64_t)d->flac->totalPCMFrameCount;
|
||||
} else {
|
||||
if (mp3dec_ex_open(&d->mp3, path, MP3D_SEEK_TO_BYTE) != 0) { free(d); return -1; }
|
||||
d->bits = 16;
|
||||
d->stash = (int16_t *)malloc(DEC_STASH_FRAMES * 2 * sizeof(int16_t));
|
||||
if (!d->stash) { mp3dec_ex_close(&d->mp3); free(d); return -1; }
|
||||
/* 预读首块:填 dec.info(hz/channels)+ 预热 stash */
|
||||
size_t got = mp3dec_ex_read(&d->mp3, d->stash, DEC_STASH_FRAMES * 2);
|
||||
d->stash_n = got;
|
||||
d->rate = d->mp3.info.hz;
|
||||
d->ch = d->mp3.info.channels;
|
||||
if (d->rate <= 0 || d->ch <= 0) { /* 首块即失败/空流 */
|
||||
mp3dec_ex_close(&d->mp3); free(d->stash); free(d); return -1;
|
||||
}
|
||||
d->total_frames = (d->ch > 0) ? (int64_t)(d->mp3.samples / (uint64_t)d->ch) : 0;
|
||||
}
|
||||
|
||||
if (d->ch < 1 || d->ch > 2) { ncm_dec_close(d); return -1; } /* v1 只接 单/双声道 */
|
||||
*out = d;
|
||||
if (rate) *rate = d->rate;
|
||||
if (ch) *ch = d->ch;
|
||||
if (bits) *bits = d->bits;
|
||||
if (total_frames) *total_frames = d->total_frames;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t ncm_dec_read(ncm_decoder_t *d, void *buf, int64_t cap_frames)
|
||||
{
|
||||
if (!d || !buf || cap_frames <= 0) return 0;
|
||||
if (d->kind == DEC_FLAC) {
|
||||
if (d->bits == 16)
|
||||
return (int64_t)drflac_read_pcm_frames_s16(d->flac, (drflac_uint64)cap_frames,
|
||||
(drflac_int16 *)buf);
|
||||
return (int64_t)drflac_read_pcm_frames_s32(d->flac, (drflac_uint64)cap_frames,
|
||||
(drflac_int32 *)buf);
|
||||
}
|
||||
/* mp3:先吐 stash,再直读 */
|
||||
int16_t *out = (int16_t *)buf;
|
||||
int64_t cap_samples = cap_frames * d->ch;
|
||||
int64_t done = 0;
|
||||
if (d->stash_off < d->stash_n) {
|
||||
size_t left = d->stash_n - d->stash_off;
|
||||
size_t take = left < (size_t)cap_samples ? left : (size_t)cap_samples;
|
||||
memcpy(out, d->stash + d->stash_off, take * sizeof(int16_t));
|
||||
d->stash_off += take;
|
||||
done += (int64_t)take;
|
||||
}
|
||||
if (done < cap_samples) {
|
||||
size_t got = mp3dec_ex_read(&d->mp3, out + done, (size_t)(cap_samples - done));
|
||||
done += (int64_t)got;
|
||||
}
|
||||
return done / d->ch;
|
||||
}
|
||||
|
||||
int ncm_dec_seek_ms(ncm_decoder_t *d, int64_t ms, int64_t file_bytes, int64_t dur_ms)
|
||||
{
|
||||
if (!d || ms < 0) return -1;
|
||||
if (d->kind == DEC_FLAC) {
|
||||
drflac_uint64 frame = (drflac_uint64)(ms * d->rate / 1000);
|
||||
return drflac_seek_to_pcm_frame(d->flac, frame) ? 0 : -1;
|
||||
}
|
||||
/* mp3:关+重开(重建字节索引)再按字节近似 */
|
||||
mp3dec_ex_close(&d->mp3);
|
||||
memset(&d->mp3, 0, sizeof d->mp3);
|
||||
if (mp3dec_ex_open(&d->mp3, d->path, MP3D_SEEK_TO_BYTE) != 0) return -1;
|
||||
d->stash_n = d->stash_off = 0;
|
||||
int64_t target = (file_bytes > 0 && dur_ms > 0) ? file_bytes * ms / dur_ms : 0;
|
||||
return mp3dec_ex_seek(&d->mp3, (uint64_t)(target > 0 ? target : 0));
|
||||
}
|
||||
|
||||
void ncm_dec_close(ncm_decoder_t *d)
|
||||
{
|
||||
if (!d) return;
|
||||
if (d->kind == DEC_FLAC) { if (d->flac) drflac_close(d->flac); }
|
||||
else { mp3dec_ex_close(&d->mp3); free(d->stash); }
|
||||
free(d);
|
||||
}
|
||||
|
||||
const char *ncm_dec_format(const ncm_decoder_t *d)
|
||||
{
|
||||
if (!d) return "?";
|
||||
return d->kind == DEC_FLAC ? "flac" : "mp3";
|
||||
}
|
||||
Reference in new issue
Block a user