Fix JOC detection inside mp4/mkv containers
build / windows (push) Has been cancelled
build / release (push) Has been cancelled

This commit is contained in:
2026-09-25 20:10:08 +08:00
parent ad62a914e6
commit 188cb9ebfb
12 changed files with 980 additions and 58 deletions
+37 -8
View File
@@ -9,16 +9,26 @@ install beside `foo_input_joc.dll`.
## What it does
1. Reads the E-AC-3 syncframes and decides from the bitstream whether the file really
carries JOC (an EMDF container holding both the OAMD and the JOC payload).
2. A file without JOC is handed back to foobar2000 with `exception_io_unsupported_format`,
so the built-in E-AC-3 decoder plays it — this component never decodes plain E-AC-3.
3. A JOC file is decoded as: the syncframes go to the renderer as metadata, the 5.1 core
1. Finds the audio: a bare `.eac3` / `.ec3` stream is read as it is, while a container
(`.mp4`, `.m4a`, `.m4b`, `.m4p`, `.m4r`, `.mov`, `.mkv`, `.mka`, `.webm`) is looked into
first — the container's own headers say whether an E-AC-3 track is present and which
audio track it is (MP4 sample entry `ec-3`, Matroska `CodecID A_EAC3`), and ffmpeg then
copies that track out of the file byte for byte. The header walk is bounded and cheap, so
an MP4 holding AAC is declined without starting anything.
2. Decides from the bitstream whether it really carries JOC (an EMDF container holding both
the OAMD and the JOC payload — container metadata only ever says "E-AC-3", and the JOC
flag inside it is frequently missing).
3. A file with no E-AC-3 track, or with one that carries no JOC, is handed back to
foobar2000 with `exception_io_unsupported_format`, so the built-in decoder plays it — this
component never decodes plain AC-3 or E-AC-3.
4. A JOC file is decoded as: the syncframes go to the renderer as metadata, the 5.1 core
PCM comes from ffmpeg, and the renderer pairs them (one syncframe : 1536 bed samples)
and produces the output PCM, which is handed back to foobar2000.
```
.eac3 file
.eac3 / .ec3 file container (.mp4 .mkv .m4a ...)
│ ├─ header walk (src/container_scan.cpp) ── no E-AC-3 ──▶ next decoder
│ └─ E-AC-3 track ── ffmpeg -c:a copy ──▶ syncframes
├─ JOC check (src/eac3_scan.cpp) ─── no JOC ──▶ built-in E-AC-3 decoder
└─ JOC
├─ syncframes ────────────────────▶ renderer metadata
@@ -34,12 +44,13 @@ install beside `foo_input_joc.dll`.
|---|---|
| `kernel/` | Copy of the `joc_core` C++ sources (`include/` + `src/`) and `joc_kernel.vcxproj`, the static library the component links |
| `src/eac3_scan.*` | Syncframe walk and the JOC bitstream test |
| `src/container_scan.*` | Bounded header walk of MP4/MOV and Matroska: is there an E-AC-3 track, which one, and how long is the file |
| `src/joc_decode.*` | Decode engine: starts ffmpeg, drives the renderer, handles the end of stream. No foobar2000 headers, so it also builds into the offline tools |
| `src/input_joc.cpp` | The foobar2000 input: format recognition, yielding, `get_info`, `initialize`, `run` |
| `src/settings.*` | Configuration values and their environment overrides (development only) |
| `src/prefs.cpp`, `src/prefs.rc` | The preferences page |
| `src/log.*` | Diagnostic log written next to the DLL |
| `tests/` | Offline tools: bitstream self-test and cross-check against the renderer, render harness, preferences-page layout check |
| `tests/` | Offline tools: bitstream self-test and cross-check against the renderer, render harness, preferences-page layout check, container-probe check |
| `tools/` | SDK fetch, build, package, deploy, unattended test bed run |
## Build
@@ -70,6 +81,21 @@ Always let foobar2000 exit through `/exit`; a force-killed instance leaves a
`<profile>\running` marker behind and the next start then refuses to load any user
component.
### Containers need one look at the decoder list
foobar2000 tries the decoders in the order shown in Preferences → **Decoding** (the
"list of available decoders", where entries can be moved up and down). The built-in
container readers are in that list too, and when one of them is offered an MP4 or Matroska
file before this component, it takes the file and the JOC objects are lost — the file plays
as plain E-AC-3.
So, to play JOC from a container, move **JOC decoder (E-AC-3 JOC)** above **foobar2000 MP4
Demuxer** and **foobar2000 Matroska/WebM Reader** in that list. Nothing else is needed, and
bare `.eac3` / `.ec3` files are unaffected by the order. This is the same thing every
third-party decoder (the FFmpeg wrapper, for one) asks for, which is why the component does
not try to work around it. If a container still plays as plain E-AC-3, that list is where to
look.
## Settings
Preferences → Tools → **JOC decoder**:
@@ -101,7 +127,10 @@ Development only: they override the stored settings for one run and every use is
## Known limitations
* ADM BWF output is not implemented.
* Containers (`.m4a`, `.mkv`) are not claimed: only bare `.eac3` / `.ec3` streams.
* A container is only claimed when this component is ahead of the built-in container reader
in Preferences → Decoding, as described under [Install](#install); the core does not let a
decoder ask for a file another entry has already taken.
* Transport streams (`.ts`, `.m2ts`) are not claimed.
* The room tail is returned in full; the reference command-line renderer additionally trims
trailing samples below a threshold, so its output can be shorter.
* x86 and x64 do not produce bit-identical binaural output (last-bit differences): the