Dpdo external decoder library API (v1)
Dpdo ships no third-party decoder and links none. You provide a dynamic library (
.so/.dll), select it in the player, and Dpdo calls it through the ABI below — you get back standard interleaved float32 PCM.
Why it works this way
Licensing and patent responsibility for third-party decoders (ffmpeg and friends) belongs to whoever supplies the library. Dpdo only provides the mechanism: load, verify, dispatch by capability. The library is yours, placed by you, and only used when you turn it on — the same approach VLC and similar players take.
Two kinds of library, one entry point
| Form | What it is | How Dpdo uses it |
|---|---|---|
| Plugin library (recommended) | Exports the seven dpdo_ext_* functions below; the library does its own container parsing and decoding | Called strictly through the ABI |
| Bare ffmpeg | An ordinary ffmpeg build (exports avformat_open_input, avcodec_open2, …) with no dpdo_ext_* | Detected automatically, driven through ffmpeg's older API; whatever decoders it carries are what you can play |
Loading tries the plugin ABI first, then bare ffmpeg. When both fail the log reports both reasons, for example:
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']
How to load it
| Where | How |
|---|---|
| Command line (Linux / Windows) | DpdoPlayerEngine -ext-decoder /path/to/lib.so |
| Turn off an already loaded library | DpdoPlayerEngine -no-ext-decoder |
| Desktop GUI | Settings → Enable external decoder library → choose the file (the GUI passes the same argument) |
| Android / HarmonyOS | Settings → Enable external decoder library → choose the file (the app copies it into its private directory) |
The load result is reported honestly as one of three states: ok (plugin ABI) /
okraw (bare ffmpeg) / fail: reason.
The seven ABI functions
The header is the single authority: public/dpdo_ext_decoder.h. C linkage, no name mangling
(wrap in extern "C" from C++).
int dpdo_ext_abi_version(void); /* 1. version */
const char *dpdo_ext_codecs(void); /* 2. capabilities */
void *dpdo_ext_open(const char *path, int *channels,
int *sample_rate, int *bits_per_sample,
long long *total_frames); /* 3. open */
long long dpdo_ext_read(void *handle, float *out, long long frames); /* 4. read */
int dpdo_ext_seek(void *handle, long long frame); /* 5. seek */
void dpdo_ext_close(void *handle); /* 6. close */
const char *dpdo_ext_last_error(void *handle); /* 7. last error */
| # | Meaning | Hard requirement |
|---|---|---|
| 1 | Return DPDO_EXT_DECODER_ABI_V1 (currently 1) | A mismatch makes the engine dlclose immediately and report it — no call is ever dispatched to that library |
| 2 | Comma-separated lowercase capability names, e.g. "dts,ape,alac" | The string is owned by the library and must stay valid for the process lifetime; an empty string is treated as a failure |
| 3 | Return a handle on success; write all four out-parameters | Return NULL on failure and set the reason for #7 |
| 4 | Read up to frames frames into out (interleaved float32); return the number of frames read | 0 = end of stream; negative = error; the engine never asks for more than total_frames |
| 5 | Seek to frame frame (0-based) | Return 0 on success; approximate seeking is allowed, but subsequent reads must continue from that point without channel mix-up |
| 6 | Release the handle | Each successful open gets exactly one close |
| 7 | Human-readable error string (owned by the library, do not free); "" when there is no error | When open fails the engine has no handle and calls this with NULL — support library-level errors and return the reason anyway |
Output contract
- Interleaved float32, range −1.0 … +1.0; one frame = one sample per channel.
- Integer PCM normalisation is the library's job (matching Dpdo's internal float pipeline).
- Do not resample or change the sample rate: the engine uses the
sample_rateyou report.
Limits of the bare-ffmpeg path
- The engine does not declare ffmpeg structs; it reads them at measured offsets (ffmpeg 3.x era). If the layout does not match it refuses instead of guessing.
- Only the decoders your
.soactually contains are usable. The UI therefore never promises a fixed format list. - Seeking reopens the container and skips frames exactly, rather than trusting the demuxer's
av_seek_frame. - When the container header disagrees with the content, the content wins (classic case: a DTS-CD
.wavwhose RIFF header claims 16-bit PCM).
Platforms
| Platform | Library file | Notes |
|---|---|---|
| Linux | .so | Give an absolute path |
| Windows | .dll | Sibling dependency DLLs are found automatically (LOAD_WITH_ALTERED_SEARCH_PATH) |
| Android | .so | The app copies the selected library into its private directory files/decoders/; to change libraries, select the file again in Settings |
| HarmonyOS | .so | Same as Android |
| iOS | — | The OS forbids loading external dynamic libraries; this path is not available |
Verify your library yourself
- Load it: run
DpdoPlayerEngine -ext-decoder <path> -i <a file>once and read the first lines. Success printsexternal decoder library loaded: … (codecs: …)orraw ffmpeg loaded: …; failure prints both reasons (which symbol is missing / ABI version mismatch). - Confirm it is really in use: the session arguments must contain
-ext-decoder <path>— that is the hard evidence. - Check the PCM: compare against a reference implementation (e.g.
ffmpeg -i x.wv -f f32le -) sample by sample; the expected difference is zero. - Negative case: a truncated file must fail clearly — never silently pad and present it as complete audio.
Licensing and responsibility
- Dpdo does not bundle or redistribute any third-party decoder library; the installer contains none of their files.
- Licensing (including GPL and patent obligations) and compliance are the responsibility of whoever supplies the library. Make sure you are allowed to use and distribute it.
- We do not provide, recommend, or download any specific third-party library for you.
Change log
| ABI version | Change |
|---|---|
| v1 | First release: seven symbols; three-state load result; last_error(NULL) semantics made explicit |