/* play/stream_src.h — 边下边播共享流源(REPORT §21,方案 NCM_STREAM_PLAY A:写穿缓存)。 * * 模型:下载线程把字节顺序写进 .tmp(ncm_cache 写者 write(2) 直写,不走 stdio 缓冲), * 解码线程经本对象 pread 同一文件——落盘的文件本身就是下载缓冲,内存里无第二份拷贝。 * 写端每写完一块调用 stream_src_note_written() 推进可读边界并 broadcast 唤醒读端; * 下载结束(完成/中断)stream_src_finish() 置 final;切歌/停止 stream_src_abort()。 * * 读回调契约(实测红线 F7):stream_src_read_cb 严格 fread 语义——填满才返回, * 真 EOF(final/failed/abort 且数据耗尽)才短读;否则 minimp3 把短读当 EOF 假播完。 * dr_flac 回调语义不同(返回实际读到的字节数):本对象同样先尽力填满再返回, * 两种契约同时满足。 * * 本层只依赖 pthread/pread/write,不依赖 core(保持 app/play 层自含)。 */ #ifndef NCM_STREAM_SRC_H #define NCM_STREAM_SRC_H #include #include #ifdef __cplusplus extern "C" { #endif typedef struct stream_src stream_src_t; /* seek 原点(与 drflac_seek_origin 同值,避免本头依赖 dr_flac.h)。 */ enum { STREAM_SRC_SEEK_SET = 0, STREAM_SRC_SEEK_CUR = 1, STREAM_SRC_SEEK_END = 2 }; /* 下载路径:tmp_path 已由 ncm_cache_open 创建(写端持有自己的写 fd); * 本函数只开只读 fd(pread 与写端 write 无偏移冲突),written 从 0 起由写端推进。 */ int stream_src_open_stream(stream_src_t **out, const char *tmp_path); /* 断点续传(阶段3,§5.2):path 已含既存前缀(.part),written 置为前缀字节数—— * 读端 read_pos=0 可立即读到已下前缀(秒开播),写端从 written 继续推进。 */ int stream_src_open_stream_at(stream_src_t **out, const char *path, uint64_t written); /* 缓存命中路径:完整文件,written=final=size,read 永不阻塞(同一套读回调)。 */ int stream_src_open_static(stream_src_t **out, const char *path, uint64_t size); void stream_src_close(stream_src_t *s); /* ---- 写端(下载线程) ---- */ /* 数据已落盘(ncm_cache_write 成功)后调用:推进可读边界 + 唤醒读端。 */ void stream_src_note_written(stream_src_t *s, uint64_t n); /* 下载结束:err==0=完成(final=written),否则中断(final=written + failed=err)。 * 之后 read_cb 数据耗尽即短读(真 EOF),解码自然结束于截断点(F5)。 */ void stream_src_finish(stream_src_t *s, int err); /* 切歌/停止:唤醒所有等待,read 立即按 EOF 短读返回;下载回调应随后返回 NZ 中止 http。 */ void stream_src_abort(stream_src_t *s); int stream_src_aborted(stream_src_t *s); /* 下载回调每块开头廉价检查 */ /* ---- 预缓冲 ---- */ /* 等 written >= need;final/failed/abort 提前返回。timeout_ms 为单次等待上限 * (调用方在循环里套切歌护栏)。返回 1=满足,0=超时,<0=已结束/失败/中止。 */ int stream_src_wait(stream_src_t *s, uint64_t need, int timeout_ms); /* 同步 peek(pread,不动 read_pos):magic 探测用。off+n 超出 written 的部分不读, * 返回实际读到字节数。 */ size_t stream_src_peek(stream_src_t *s, void *buf, size_t n, uint64_t off); /* ---- minimp3 回调(MP3D_READ_CB / MP3D_SEEK_CB) ---- */ size_t stream_src_read_cb(void *buf, size_t size, void *ud); int stream_src_seek_cb(uint64_t pos, void *ud); /* ---- dr_flac 回调(drflac_read_proc / drflac_seek_proc / drflac_tell_proc) ---- */ size_t stream_src_drflac_read(void *ud, void *buf, size_t bytes_to_read); int stream_src_drflac_seek(void *ud, int offset, int origin); /* origin=STREAM_SRC_SEEK_* */ int stream_src_drflac_tell(void *ud, int64_t *cursor); /* ---- 统计 / 停滞联动 ---- */ /* underrun=read_cb 追上下载(无数据可给)的等待次数;wait_ms=累计等待 ms。 */ void stream_src_stats(stream_src_t *s, int *underruns, uint64_t *wait_ms); /* 单次 read 等待 >1s 置位(播放线程下一轮对 audiod pause(1),恢复后 pause(0))。 * 取走即清零。 */ int stream_src_take_stalled(stream_src_t *s); /* ---- page-cache 治理(OOM_PAGECACHE_GOVERNANCE §2.3:underrun/refault 检测)---- * refault = pread 处「数据已在盘上(written>read_pos)但读出耗时 >50ms」——DONTNEED 丢页后 * 重读 TF 的信号,区别于网络 underrun(avail==0 等待)。SDK meow_cachegov 据此做 A 回退。 * 播放线程读 refaults 落 g_play 取证;回调由 SDK(meow_cachegov_note_refault)提供, * 未链 SDK 时弱符号为空(host sim 兼容)。 */ int stream_src_refaults(stream_src_t *s); /* 当前解码逻辑读指针(stream_src_seek_cb 改写)。 */ uint64_t stream_src_read_pos(stream_src_t *s); uint64_t stream_src_written(stream_src_t *s); uint64_t stream_src_final(stream_src_t *s); /* 0=下载中;>0=最终大小(完成=全长,中断=截断点) */ int stream_src_failed(stream_src_t *s); /* 0=无错误;<0=下载/IO 错误码 */ #ifdef __cplusplus } #endif #endif /* NCM_STREAM_SRC_H */