Dpdo 外部解码库 API(v1)
Dpdo 本体不打包、不链接任何第三方解码器。你自己放一个动态库(
.so/.dll), 在播放器里选中它,Dpdo 就按下面这套 ABI 调用它 —— 解出来的是标准交错 float32 PCM。
为什么是这样
第三方解码器(尤其 ffmpeg 这类)的许可与专利责任在提供库的一方。Dpdo 只提供"装载 + 按能力分派"的机制: 库是你拿到的、你放进去的、你决定装不装。这与 VLC 等播放器的做法一致。
两种库,一个装载入口
| 形态 | 说明 | Dpdo 侧 |
|---|---|---|
| 插件库(推荐) | 导出下表 7 个 dpdo_ext_* 函数,由库自己做容器解析与解码 | 严格按 ABI 调用 |
| 裸 ffmpeg | 直接是 ffmpeg 的 .so(导出 avformat_open_input / avcodec_open2 等),没有 dpdo_ext_* | 自动识别,按 ffmpeg 老 API 直驱;库自带什么解码器就解什么 |
装载时先按插件 ABI 校验;不是插件就再按裸 ffmpeg 试。两条都失败时,日志会同时给出两条路各自的原因,例如:
player_cli: not a DPDO decoder plugin [ABI version mismatch: library reports 99, this build expects 1];
not a usable raw ffmpeg [missing symbol 'avformat_open_input']
装载方式
| 场景 | 做法 |
|---|---|
| 命令行(Linux / Windows) | DpdoPlayerEngine -ext-decoder /path/to/lib.so |
| 关闭已装载的库 | DpdoPlayerEngine -no-ext-decoder |
| 桌面 GUI | 设置 → 启用外部解码库 → 选择文件(同一份参数由 GUI 传下去) |
| Android / 鸿蒙 | 设置 → 启用外部解码库 → 选择文件(应用会把它拷进自己的私有目录) |
试装结论是三态,界面与日志如实显示:ok(插件 ABI)/ okraw(裸 ffmpeg)/ fail: 原因。
插件 ABI:7 个函数
头文件是唯一权威定义:public/dpdo_ext_decoder.h。C 链接、不加名字修饰(C++ 请包 extern "C")。
int dpdo_ext_abi_version(void); /* ① 版本 */
const char *dpdo_ext_codecs(void); /* ② 能力 */
void *dpdo_ext_open(const char *path, int *channels,
int *sample_rate, int *bits_per_sample,
long long *total_frames); /* ③ 打开 */
long long dpdo_ext_read(void *handle, float *out, long long frames); /* ④ 读 */
int dpdo_ext_seek(void *handle, long long frame); /* ⑤ 定位 */
void dpdo_ext_close(void *handle); /* ⑥ 关闭 */
const char *dpdo_ext_last_error(void *handle); /* ⑦ 最近错误 */
| # | 语义 | 硬约束 |
|---|---|---|
| ① | 返回 DPDO_EXT_DECODER_ABI_V1(当前 = 1) | 不匹配 ⇒ 引擎立即 dlclose 并报错,不会把调用落到这个库上 |
| ② | 逗号分隔的小写能力名,如 "dts,ape,alac" | 字符串由库持有,进程存活期内必须有效;空串 = 什么都解不了,按失败处理 |
| ③ | 成功返回句柄;四个出参必须全部写有效值 | 失败返回 NULL,原因走 ⑦ |
| ④ | 读最多 frames 帧到 out(交错 float32);返回实际帧数 | 0 = 流结束;负值 = 出错;引擎不会请求超过 total_frames |
| ⑤ | 跳到第 frame 帧(0 起) | 返回 0 = 成功;允许近似,但之后的读必须从该点继续、不串音 |
| ⑥ | 释放句柄 | 每个成功的 open 恰好配一次 close |
| ⑦ | 可读错误串(库持有,不要 free);无错返回 "" | open 失败时引擎没有句柄,会以 NULL 调用 ⇒ 必须支持库级错误并照样返回原因 |
输出约定
- 交错 float32,取值 −1.0 … +1.0;一帧 = 每声道一个采样。
- 整型 PCM 的归一化由库负责(与引擎内部 float 管线一致)。
- 不改采样率、不做重采样:引擎按你报的
sample_rate走后续链路。
裸 ffmpeg 的边界
- 引擎不声明 ffmpeg 的结构体,按实测偏移读写(对应 ffmpeg 3.x 时代布局);布局对不上时会拒绝而不是硬猜。
- 只有库自己带的解码器可用 —— 你的
.so里没有 APE 解码器,就解不了 APE。界面文案因此不预设格式名单。 - 定位(seek)走"重开容器 + 精确跳帧",不依赖 demuxer 的
av_seek_frame。 - 容器头与内容冲突时以内容为准(典型:DTS-CD 的
.wav头声明的是 PCM 16bit,那是假的)。
平台
| 平台 | 库文件 | 说明 |
|---|---|---|
| Linux | .so | 直接给绝对路径 |
| Windows | .dll | 同目录的依赖 DLL 会自动找(装载用 LOAD_WITH_ALTERED_SEARCH_PATH) |
| Android | .so | 应用会把选中的库拷到私有目录 files/decoders/;换库要在设置里重新选一次 |
| 鸿蒙 | .so | 同上 |
| iOS | — | 系统不允许加载外来动态库,这条路走不通 |
自己验一遍你的库
- 装载:跑一次
DpdoPlayerEngine -ext-decoder <路径> -i <素材>,看启动几行里的结论 —— 成功会打印external decoder library loaded: … (codecs: …)或raw ffmpeg loaded: …; 失败会打印两条路各自的原因(缺哪个符号 / ABI 版本不符)。 - 能解什么:让它打开一个该格式的文件,看会话参数里有没有
-ext-decoder <路径>(这是"库真的生效"的硬判据)。 - 解出来的 PCM 对不对:与参照实现(例如
ffmpeg -i x.wv -f f32le -)逐样点比对,期望差值为 0。 - 负例:截断文件必须明确失败,不允许静默补齐后当完整素材放出去。
许可与责任
- Dpdo 不随包分发任何第三方解码库,安装包里没有它们的文件。
- 库的许可(含 GPL / 专利)与合规责任在提供库的一方;请自行确认你有权使用与分发。
- 我们不提供、不推荐、不代下载任何具体的第三方库。
变更记录
| ABI 版本 | 变更 |
|---|---|
| v1 | 首版:7 个符号;三态试装结果;last_error(NULL) 语义明确化 |