feat(ncm §21): 边下边播阶段1+2——stream_src流式源+decoder双路open_cb+下载线程预缓冲/stall联动+P2 HTTP空闲超时+cache fd写者+stream_sim全矩阵PCM金标准(0真实请求,未部署)

This commit is contained in:
MeowOS committed 2026-09-01 18:23:17 +08:00
1 parent dbee37e1d6
commit 9e25db77cc
18 files changed
+1335 -136

No files matched your search

+80 -25
View File
@@ -1,8 +1,19 @@
/* play/decoder.c — 格式分发解码实现(接口见 decoder.h,REPORT §19)。
/* play/decoder.c — 格式分发解码实现(接口见 decoder.h,REPORT §19;§21 切 stream_src 回调模式)。
*
* 两后端都建在 stream_src 上(边下边播 .tmp 与缓存命中静态源同套读回调):
* mp3 = mp3dec_ex_open_cb(MP3D_SEEK_TO_BYTE | MP3D_DO_NOT_SCAN) —— F3 红线:
* 不带 DO_NOT_SCAN 时 open_cb 对无 Xing tag 的流全扫描建索引,read_cb 阻塞
* 语义下 open 直接卡到整首下完。代价 dec->samples=0,时长走 meta(现状已是)。
* flac = drflac_open(stream_src 回调)。dr_flac 读回调语义「返回实际读到的字节数」,
* stream_src 的严格 fread 实现(填满才返回)是其超集,直接兼容。
* 缓存命中(ncm_dec_open 整文件路径)也走同一 cb 路径——不再 mmap(MAP_POPULATE)
* 整文件(P1 修复:旧 mp3dec_ex_open 在 Linux 预 fault 全部页,RSS ≈ 整文件)。
*
* 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,同旧语义。
* seek(mp3):MP3D_SEEK_TO_BYTE 下 mp3dec_ex_seek 只是 offset=pos + io->seek(pos)(F4,
* 无需索引、无需关重开),stream_src 的 seek 回调只改 read_pos——目标超已下载区时
* 后续 read 自然阻塞等待数据到达。
*/
#include "decoder.h"
#include "minimp3_ex.h"
@@ -11,6 +22,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#define DEC_STASH_FRAMES 4096 /* open 预读上限(帧),与播放循环单块读同量级 */
@@ -18,11 +30,13 @@ enum { DEC_MP3 = 0, DEC_FLAC = 1 };
struct ncm_decoder {
int kind;
char path[512];
stream_src_t *src; /* 读端(ncm_dec_open 自建静态源时 src_owned=1) */
int src_owned;
int rate, ch, bits;
int64_t total_frames; /* 0=未知 */
/* mp3 */
mp3dec_ex_t mp3;
mp3dec_io_t mp3_io; /* 生命周期须覆盖 mp3(minimp3 内部存指针) */
int16_t *stash; /* 预读样本(交织 s16),stash_n 个样本 */
size_t stash_n, stash_off;
/* flac */
@@ -30,48 +44,74 @@ struct ncm_decoder {
};
/* magic 探测:首 4 字节 "fLaC" → flac;其余(ID3 / 0xFF 帧同步 / 未知)→ mp3
*(未知格式留给 minimp3 在 open 时报错,不在本层加第三种分支)。 */
static int sniff_kind(const char *path)
*(未知格式留给 minimp3 在 open 时报错,不在本层加第三种分支)。
* 流模式下 peek 读已落盘前缀(预缓冲门槛 ≥128KB 已满足,或 final 截断有多少读多少)。 */
static int sniff_kind(stream_src_t *src)
{
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;
size_t n = stream_src_peek(src, m, 4, 0);
if (n >= 4 && 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)
/* dr_flac 回调签名适配:seek 的 origin 是枚举、tell 用 drflac_int64(long long),
* 与 stream_src 的 int/int64_t 形参在 C 里不兼容,包一层。 */
static drflac_bool32 dec_flac_seek(void *ud, int offset, drflac_seek_origin origin)
{
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);
return (drflac_bool32)stream_src_drflac_seek(ud, offset, (int)origin);
}
static drflac_bool32 dec_flac_tell(void *ud, drflac_int64 *cursor)
{
int64_t c = 0;
int r = stream_src_drflac_tell(ud, &c);
*cursor = (drflac_int64)c;
return (drflac_bool32)r;
}
static int dec_open_on_src(ncm_decoder_t *d)
{
if (d->kind == DEC_FLAC) {
d->flac = drflac_open_file(path, NULL);
if (!d->flac) { free(d); return -1; }
d->flac = drflac_open(stream_src_drflac_read, dec_flac_seek,
dec_flac_tell, d->src, NULL);
if (!d->flac) 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->mp3_io.read = stream_src_read_cb;
d->mp3_io.read_data = d->src;
d->mp3_io.seek = stream_src_seek_cb;
d->mp3_io.seek_data = d->src;
if (mp3dec_ex_open_cb(&d->mp3, &d->mp3_io,
MP3D_SEEK_TO_BYTE | MP3D_DO_NOT_SCAN) != 0) 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 */
if (!d->stash) { mp3dec_ex_close(&d->mp3); return -1; }
/* 预读首块:填 dec.info(hz/channels)+ 预热 stash。
* 流模式下此处可能阻塞到下载推进(read_cb 阻塞语义),属预期。 */
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;
mp3dec_ex_close(&d->mp3); free(d->stash); d->stash = NULL; return -1;
}
d->total_frames = (d->ch > 0) ? (int64_t)(d->mp3.samples / (uint64_t)d->ch) : 0;
}
return 0;
}
int ncm_dec_open_src(ncm_decoder_t **out, stream_src_t *src,
int *rate, int *ch, int *bits, int64_t *total_frames)
{
if (!out || !src) return -1;
ncm_decoder_t *d = (ncm_decoder_t *)calloc(1, sizeof *d);
if (!d) return -1;
d->src = src;
d->kind = sniff_kind(src);
if (dec_open_on_src(d) != 0) { ncm_dec_close(d); return -1; }
if (d->ch < 1 || d->ch > 2) { ncm_dec_close(d); return -1; } /* v1 只接 单/双声道 */
*out = d;
@@ -82,6 +122,22 @@ int ncm_dec_open(ncm_decoder_t **out, const char *path,
return 0;
}
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;
struct stat st;
if (stat(path, &st) != 0 || !S_ISREG(st.st_mode)) return -1;
stream_src_t *src = NULL;
if (stream_src_open_static(&src, path, (uint64_t)st.st_size) != 0) return -1;
if (ncm_dec_open_src(out, src, rate, ch, bits, total_frames) != 0) {
stream_src_close(src);
return -1;
}
(*out)->src_owned = 1;
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;
@@ -117,10 +173,8 @@ int ncm_dec_seek_ms(ncm_decoder_t *d, int64_t ms, int64_t file_bytes, int64_t du
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;
/* mp3:MP3D_SEEK_TO_BYTE 下 seek 只是 offset=pos + io->seek(pos)(F4),
* 无需索引、无需关重开;目标超已下载区时 read 自然阻塞等待。 */
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));
@@ -131,6 +185,7 @@ 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); }
if (d->src_owned) stream_src_close(d->src);
free(d);
}