197 lines
8.2 KiB
C
197 lines
8.2 KiB
C
/* 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";
|
||
}
|