/* 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 下 mp3dec_ex_seek 只是 offset=pos + io->seek(pos)(F4, * 无需索引、无需关重开),stream_src 的 seek 回调只改 read_pos——目标超已下载区时 * 后续 read 自然阻塞等待数据到达。 */ #include "decoder.h" #include "minimp3_ex.h" #include "dr_flac.h" #include #include #include #include #define DEC_STASH_FRAMES 4096 /* open 预读上限(帧),与播放循环单块读同量级 */ enum { DEC_MP3 = 0, DEC_FLAC = 1 }; struct ncm_decoder { int kind; 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 */ drflac *flac; }; /* magic 探测:首 4 字节 "fLaC" → flac;其余(ID3 / 0xFF 帧同步 / 未知)→ mp3 *(未知格式留给 minimp3 在 open 时报错,不在本层加第三种分支)。 * 流模式下 peek 读已落盘前缀(预缓冲门槛 ≥128KB 已满足,或 final 截断有多少读多少)。 */ static int sniff_kind(stream_src_t *src) { unsigned char m[4] = {0}; 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; } /* 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) { 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(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 { 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); 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); 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; 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; } 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; 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: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)); } 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); } const char *ncm_dec_format(const ncm_decoder_t *d) { if (!d) return "?"; return d->kind == DEC_FLAC ? "flac" : "mp3"; }