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:
MeowOS committed 2026-09-01 14:14:38 +08:00
1 parent 80bc191593
commit 70aec677ce
23 files changed
+13377 -110

No files matched your search

+70 -15
View File
@@ -14,14 +14,15 @@
*
* 编译(宿主,ncm_app 目录下):
* gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
* -Icore -Ivendor/jsmn -Ivendor/minimp3 -c bench/player_flow_sim.c -o /tmp/pf.o
* -Icore -Ivendor/jsmn -c bench/player_flow_sim.c -o /tmp/pf.o
* gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
* -Icore -Ivendor/jsmn -Ivendor/minimp3 -c app/play/player_manager.c -o /tmp/pf_pm.o
* -Icore -Ivendor/jsmn -Ivendor/minimp3 -Ivendor/dr_flac -c app/play/player_manager.c -o /tmp/pf_pm.o
* gcc -O2 -Wl,--gc-sections -o player_flow_sim /tmp/pf.o /tmp/pf_pm.o -lpthread -lm
* 运行:./player_flow_sim (0 网络;PASS/FAIL 退出码)
* §19:P6 覆盖缓存键=歌曲ID+档位 + flac type → .flac 落盘扩展名。
*/
#include "../app/ncm_app_internal.h"
#include "minimp3_ex.h"
#include "../app/play/decoder.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
@@ -64,6 +65,9 @@ static int g_cache_ok = 1;
static int g_url_rc = NCM_OK;
static int g_dl_delay_ms = 0;
static int g_downloads; /* http_download 调用数(song_url 请求放大检测) */
static const char *g_url_type = "mp3"; /* §19:song_url 注入的 type(mp3/flac) */
static char g_last_key[80]; /* ncm_cache_open 收到的键(档位键断言用) */
static char g_last_ext[8]; /* ncm_cache_open 收到的扩展名 */
int ncm_cache_lookup(const ncm_cache_t *c, const char *id, const char *ext, char *out, size_t out_sz)
{
@@ -73,7 +77,12 @@ int ncm_cache_lookup(const ncm_cache_t *c, const char *id, const char *ext, char
return NCM_OK;
}
int ncm_cache_open(const ncm_cache_t *c, const char *id, const char *ext, ncm_cache_writer_t **w)
{ (void)c; (void)id; (void)ext; *w = (ncm_cache_writer_t *)calloc(1, 64); return NCM_OK; }
{
(void)c;
snprintf(g_last_key, sizeof g_last_key, "%s", id);
snprintf(g_last_ext, sizeof g_last_ext, "%s", ext ? ext : "");
*w = (ncm_cache_writer_t *)calloc(1, 64); return NCM_OK;
}
int ncm_cache_write(ncm_cache_writer_t *w, const unsigned char *d, size_t l) { (void)w; (void)d; (void)l; return NCM_OK; }
int ncm_cache_commit(ncm_cache_writer_t *w) { free(w); g_cache_ok = 1; return NCM_OK; }
int ncm_cache_abort(ncm_cache_writer_t *w) { free(w); return NCM_OK; }
@@ -85,7 +94,8 @@ int ncm_api_song_url_auto(const char *dev, const char *ck, const char *id, int l
(void)dev; (void)ck; (void)id; (void)level;
if (g_url_rc != NCM_OK) return g_url_rc;
memset(out, 0, sizeof *out);
snprintf(out->url, sizeof out->url, "http://stub/%s.mp3", id);
snprintf(out->url, sizeof out->url, "http://stub/%s.%s", id, g_url_type);
snprintf(out->type, sizeof out->type, "%s", g_url_type);
out->size = 4 * 1024 * 1024;
return NCM_OK;
}
@@ -105,22 +115,52 @@ int ncm_http_download(const ncm_http_req_t *req, ncm_http_write_cb cb, void *ctx
return NCM_OK;
}
/* ---- 解码器 stub:g_eof 前恒有数据(P1 用 g_reads_left 控制播完点) ---- */
/* ---- 解码器 stub(§19:player_manager 改走 ncm_dec_* 分发层):
* g_eof 前恒有数据(P1 用 g_reads_left 控制播完点) ---- */
static int g_eof;
static int g_reads_left = -1; /* >=0: 剩余读次数后 EOF;-1: 直到 g_eof */
static int g_dec_open_bits = 16; /* 模拟流位宽(记录用) */
int mp3dec_ex_open(mp3dec_ex_t *dec, const char *file_name, int flags)
{ (void)dec; (void)file_name; (void)flags; return 0; }
void mp3dec_ex_close(mp3dec_ex_t *dec) { (void)dec; }
int mp3dec_ex_seek(mp3dec_ex_t *dec, uint64_t position) { (void)dec; (void)position; return 0; }
size_t mp3dec_ex_read(mp3dec_ex_t *dec, mp3d_sample_t *buf, size_t samples)
int ncm_dec_open(ncm_decoder_t **out, const char *path, int *rate, int *ch, int *bits,
int64_t *total_frames)
{
(void)dec;
(void)path;
*out = (ncm_decoder_t *)calloc(1, 16);
if (rate) *rate = 44100;
if (ch) *ch = 2;
if (bits) *bits = g_dec_open_bits;
if (total_frames) *total_frames = 44100 * 180;
return *out ? 0 : -1;
}
int64_t ncm_dec_read(ncm_decoder_t *d, void *buf, int64_t cap_frames)
{
(void)d;
if (g_eof) return 0;
if (g_reads_left == 0) return 0;
if (g_reads_left > 0) g_reads_left--;
memset(buf, 0, samples * sizeof(mp3d_sample_t));
return samples;
memset(buf, 0, (size_t)cap_frames * 2 * sizeof(int16_t));
return cap_frames;
}
int ncm_dec_seek_ms(ncm_decoder_t *d, int64_t ms, int64_t fb, int64_t dur)
{ (void)d; (void)ms; (void)fb; (void)dur; return 0; }
void ncm_dec_close(ncm_decoder_t *d) { free(d); }
const char *ncm_dec_format(const ncm_decoder_t *d) { (void)d; return "stub"; }
/* §19:song_local_file 的缓存键用 ncm_api_level_str(g_level);ext 判定用 ncm_streq */
int ncm_streq(const char *a, const char *b)
{
if (!a || !b) return 0;
return strcmp(a, b) == 0;
}
const char *ncm_api_level_str(int level)
{
switch (level) {
case NCM_LEVEL_HIRES: return "hires";
case NCM_LEVEL_LOSSLESS: return "lossless";
case NCM_LEVEL_EXHIGH: return "exhigh";
case NCM_LEVEL_HIGHER: return "higher";
default: return "standard";
}
}
/* ---- 音频输出 stub:g_write_fail 注入写失败 ---- */
@@ -129,7 +169,7 @@ static int g_writes;
static int g_opens; /* audio_out_open 次数(每首 run_song 开一次) */
int audio_out_open(int rate, int ch, int bits) { (void)rate; (void)ch; (void)bits; g_opens++; return 0; }
int audio_out_write(const short *s, int frames)
int audio_out_write(const void *s, int frames)
{ (void)s; g_writes++; return g_write_fail ? -1 : frames; }
int audio_out_pause(int paused) { (void)paused; return 0; }
void audio_out_close(void) { }
@@ -250,6 +290,21 @@ int main(void)
CHECK(player_manager_error() == 0, "err cleared on retry");
g_eof = 1;
printf("== P6: cache key = id+level, flac type -> .flac ext (§19) ==\n");
usleep(300 * 1000); /* 等 P5 曲收尾 */
g_eof = 0; g_reads_left = -1; g_cache_ok = 0; g_dl_delay_ms = 0;
g_url_type = "flac";
player_manager_set_quality(NCM_LEVEL_LOSSLESS, 10); /* 换档 → g_level=lossless */
player_manager_play(0);
CHECK(wait_state(NCM_PC_PLAYING, 3000), "flac download song playing");
CHECK(strcmp(g_last_key, "1000_lossless") == 0,
"cache key=%s (expect 1000_lossless = id+档位)", g_last_key);
CHECK(strcmp(g_last_ext, "flac") == 0,
"cache ext=%s (expect flac, type=flac → .flac 落盘)", g_last_ext);
g_url_type = "mp3";
player_manager_set_quality(NCM_LEVEL_STANDARD, 10); /* 还原,避免影响后续(无) */
g_eof = 1;
usleep(200 * 1000);
player_manager_stop_thread();
printf(g_fail ? "== PLAYER FLOW SIM: FAIL ==\n" : "== PLAYER FLOW SIM: ALL PASS ==\n");