Files
MeowOS-For-R3P2/patch_v2/ncm_app/app/play/decoder.c
T

197 lines
8.2 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#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";
}