Fix missing tags, broken seeking, and the x64 playback crash
This commit is contained in:
@@ -1,143 +1,103 @@
|
||||
# foo_input_joc
|
||||
# foo_input_joc — foobar2000 的 E-AC-3 JOC 输入组件
|
||||
|
||||
foobar2000 input component for **E-AC-3 JOC (Dolby Atmos)** files: the JOC objects are
|
||||
rendered to binaural (HRTF) or to a speaker layout up to 7.1, in real time.
|
||||
[English](README.en.md) · [安装](#安装) · [设置](#设置) · [构建](#构建) · [已知限制](#已知限制)
|
||||
|
||||
Two files with the same name pay for the whole thing: `joc_core`'s C++ sources are copied
|
||||
into [`kernel/`](kernel/) and compiled straight into the component, so there is nothing to
|
||||
install beside `foo_input_joc.dll`.
|
||||
foobar2000 输入组件,播放 **E-AC-3 JOC(Dolby Atmos)** 文件:从 E-AC-3 同步帧里取出 JOC
|
||||
对象与 OAMD 元数据,与 ffmpeg 解出的 5.1 核心 PCM 配对后实时渲染,输出双耳(HRTF)或最多
|
||||
7.1 的扬声器布局。渲染内核直接编译进组件,除 `foo_input_joc.dll` 之外不需要安装任何东西。
|
||||
|
||||
## What it does
|
||||
输入是裸流 `.eac3` / `.ec3`,或容器里的 E-AC-3 JOC 轨道(`.mp4` `.m4a` `.m4b` `.m4p`
|
||||
`.m4r` `.mov` `.mkv` `.mka` `.webm`)。文件里没有 JOC 就不接管:普通 AC-3 / E-AC-3、音频轨道
|
||||
是 AAC 的容器、传输流都交还 foobar2000,由它原本的解码器播放。
|
||||
|
||||
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 / .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
|
||||
└─ ffmpeg -ac 6 -c:a pcm_f32le ───▶ 5.1 core PCM ──▶ renderer bed
|
||||
│
|
||||
▼
|
||||
2 ch or ≤7.1 PCM ──▶ foobar2000
|
||||
```
|
||||
* foobar2000 1.6(32 位)或 2.x(32 位与 64 位)。
|
||||
* 一个 `ffmpeg` 可执行文件,默认从 `PATH` 找,设置页可以指定路径。
|
||||
* 双耳输出需要一份 HRTF 数据文件。**本仓库不附带**,见 [HRTF 数据](#hrtf-数据)。
|
||||
|
||||
## Repository layout
|
||||
## 安装
|
||||
|
||||
| Path | Contents |
|
||||
从 [Releases](../../releases) 下载对应架构的包(`v*` 标签触发的 CI 会把两个架构都附上去),
|
||||
或按[构建](#构建)自行打出 `dist\` 下的产物;文件名都是
|
||||
`foo_input_joc-<版本>-<架构>.fb2k-component`。把它拖到 foobar2000 上,或用
|
||||
Preferences → Components → Install 安装,然后重启。1.6 和 2.x 32 位用 `-x86`,2.x 64 位用
|
||||
`-x64`。
|
||||
|
||||
手工安装就把 `foo_input_joc.dll` 放进当前版本会读取的 `user-components` 子目录:
|
||||
|
||||
| foobar2000 | 目录 |
|
||||
|---|---|
|
||||
| `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, container-probe check |
|
||||
| `tools/` | SDK fetch, build, package, deploy, unattended test bed run |
|
||||
| 1.6 | `<profile>\user-components\foo_input_joc\` |
|
||||
| 2.x | `<app>\user-components\foo_input_joc\`(portable 模式同样如此) |
|
||||
|
||||
## Build
|
||||
子目录是必须的——DLL 直接躺在 `user-components\` 下不会被扫描;放进当前版本不读的目录,或
|
||||
架构不对,都会**静默忽略**,不会有任何提示。
|
||||
|
||||
`tools/deploy.ps1 -TestBed <portable foobar2000>` 是上面手工步骤的脚本版;
|
||||
`tools/run.ps1 -TestBed <路径> -Play <文件>` 可以无人值守播放并把日志打出来。让 foobar2000
|
||||
通过 `/exit` 正常退出:被强杀的实例会在 `<profile>\running` 留下标记,下次启动会拒绝加载任何
|
||||
用户组件。
|
||||
|
||||
### 容器需要调一次解码器顺序
|
||||
|
||||
foobar2000 按 Preferences → **Decoding** 里的顺序询问解码器,内置的容器读取器也在那张表里。
|
||||
如果它排在前面接到 MP4 / Matroska 文件,文件就被它拿走,JOC 对象随之丢失——于是听起来只是
|
||||
普通 E-AC-3。
|
||||
|
||||
所以要把 **JOC decoder (E-AC-3 JOC)** 提到 **foobar2000 MP4 Demuxer** 与 **foobar2000
|
||||
Matroska/WebM Reader** 之前。裸流 `.eac3` / `.ec3` 不受这个顺序影响。
|
||||
|
||||
## 设置
|
||||
|
||||
Preferences → Tools → **JOC decoder**:
|
||||
|
||||
* **Output** —— 双耳,或 2.0 到 7.1 的扬声器布局;
|
||||
* **Binaural mode**(near / mid / far)与房间 **tail** 秒数;
|
||||
* **HRTF source** —— **SOFA** 文件或 **Rosella** `.personalized_headphone` 模型。路径留空表示
|
||||
用默认位置 `<组件目录>\HRTF\` 下的 `binaural.sofa` 或 `binaural.personalized_headphone`;
|
||||
* **Gain** —— 开关加 dB 值。双耳渲染在核心混音不削顶的素材上也可能超过满刻度,衰减放在这里;
|
||||
* **ffmpeg** 可执行文件路径。
|
||||
|
||||
页面上的控件都不禁用,状态行会说明当前生效的是什么。
|
||||
|
||||
### HRTF 数据
|
||||
|
||||
SOFA 测量集或个性化耳机模型由使用组件的人自己提供,并且写在 `.gitignore` 里,避免误提交。
|
||||
扬声器布局不需要 HRTF。双耳渲染缺少 HRTF 时会报错并指出它找的是哪个文件。
|
||||
|
||||
## 构建
|
||||
|
||||
```powershell
|
||||
pwsh -File tools/setup_sdk.ps1 # official SDK into SDK/, pinned to target 1.5/1.6
|
||||
pwsh -File tools/setup_sdk.ps1 # 官方 SDK 拉进 SDK/,固定到 target 1.5/1.6
|
||||
pwsh -File tools/build.ps1 # Win32 -> build\Win32\foo_input_joc.dll
|
||||
pwsh -File tools/build.ps1 -Platform x64
|
||||
pwsh -File tools/package.ps1 # both, packaged into dist\*.fb2k-component
|
||||
pwsh -File tools/package.ps1 # 两个架构,打包到 dist\*.fb2k-component
|
||||
```
|
||||
|
||||
`Release-Static` uses the static CRT (`/MT`); `/fp:precise` is required and must not be
|
||||
changed. `foo_input_joc.vcxproj` builds `kernel\joc_kernel.vcxproj` first through a project
|
||||
reference. The copied kernel sources are compiled with `JOC_STATIC` / `EJOC_STATIC` so their
|
||||
entry points are neither imported nor exported.
|
||||
配置固定为 `Release-Static`(静态 CRT,`/MT`);`/fp:precise` 是逐字节验收的前提,不要改。
|
||||
`foo_input_joc.vcxproj` 通过项目引用先构建 `kernel\joc_kernel.vcxproj`;`kernel/` 里的渲染内核
|
||||
源码以 `JOC_STATIC` / `EJOC_STATIC` 编译,入口既不导入也不导出。`tests\` 是离线工具(码流
|
||||
自检与交叉核对、渲染比对、设置页布局检查、容器探测检查),`tools\` 是构建与测试床脚本。设置项
|
||||
另有 `JOC_*` 环境变量覆盖(仅用于开发运行),清单与含义在 `src\settings.cpp`。
|
||||
|
||||
## Install
|
||||
排查问题看 DLL 旁边的 `joc_decoder.log`;组件启动时会把自己的版本、核心版本、日志路径写在
|
||||
里面。
|
||||
|
||||
Either drop `dist\foo_input_joc-<version>-<arch>.fb2k-component` onto foobar2000 (or use
|
||||
Preferences → Components → Install), or copy `foo_input_joc.dll` into
|
||||
`<profile>\user-components\foo_input_joc\`. The per-component subdirectory is required:
|
||||
a DLL lying directly in `user-components\` is not scanned. 1.6 is 32-bit, 2.x ships both,
|
||||
and a DLL of the wrong architecture is silently ignored.
|
||||
## 已知限制
|
||||
|
||||
`tools/deploy.ps1 -TestBed <path to portable foobar2000>` does the manual variant, and
|
||||
`tools/run.ps1 -TestBed <path> -Play <file>` runs it unattended and prints the log.
|
||||
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.
|
||||
* 不实现 ADM BWF 输出。
|
||||
* 容器只有在它排在内置容器读取器之前时才会被接管(见[安装](#容器需要调一次解码器顺序));核心
|
||||
不允许某个解码器去要一个已经被别的条目拿走的文件。这类文件的标签也仍旧归那个读取器。
|
||||
* 裸流 `.eac3` / `.ec3` 的标签(流前面的 ID3v2,或后面的 APEv2/ID3v1)**能读不能写**:没有
|
||||
组件声明可以写裸 E-AC-3,为插入标签重写整个文件也不是本组件该做的事。
|
||||
* 传输流(`.ts`、`.m2ts`)不接管。
|
||||
* 播放长度严格等于文件时长。双耳渲染器仍会算出房间尾音,但它不作为文件本身没有的播放时间交付。
|
||||
* 跳转会从包含目标位置的那个帧重新进入码流,而不是把前面的内容全部解码一遍——这是跳转代价与
|
||||
目标位置无关的原因。位置精确、不漂移;样本是同一段波形交给了从该处开始的解码器,与从头播放
|
||||
相比差一个很低的噪声底(−59 dBFS 或更低)。
|
||||
|
||||
### 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**:
|
||||
|
||||
* **Output** — binaural, or a speaker layout from 2.0 to 7.1;
|
||||
* **Binaural mode** (near / mid / far) and the room **tail** in seconds;
|
||||
* **HRTF source** — a **SOFA** file or a **Rosella** `.personalized_headphone` model. Leave
|
||||
the path empty to use the default location `<component directory>\HRTF\`:
|
||||
`binaural.sofa` or `binaural.personalized_headphone`;
|
||||
* **Gain** — a switch plus a value in dB. Binaural rendering can exceed full scale on
|
||||
material that does not clip in the core mix, so attenuation belongs here;
|
||||
* the **ffmpeg** executable to use.
|
||||
|
||||
Nothing on the page is disabled; the status line states what is in effect.
|
||||
|
||||
**HRTF data is not distributed with this repository.** A SOFA measurement set or a
|
||||
personalised headphone model is supplied by whoever runs the component (and is listed in
|
||||
`.gitignore` so it cannot be committed by accident). Speaker layouts and every offline test
|
||||
except binaural rendering work without one; binaural rendering without an HRTF fails with a
|
||||
message naming the file it looked for.
|
||||
|
||||
## Environment overrides
|
||||
|
||||
Development only: they override the stored settings for one run and every use is logged.
|
||||
`JOC_OUTPUT`, `JOC_LAYOUT`, `JOC_HRTF`, `JOC_HRTF_SOURCE`, `JOC_BINAURAL_MODE`, `JOC_GAIN_DB`,
|
||||
`JOC_GAIN_ENABLED`, `JOC_TAIL_SECONDS`, `JOC_OBJECT_DELAY`, `JOC_THREADS`, `JOC_FFMPEG`,
|
||||
`JOC_LOG`.
|
||||
|
||||
## Known limitations
|
||||
|
||||
* ADM BWF output is not implemented.
|
||||
* 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
|
||||
renderer's SIMD dispatch only applies to x86-64/ARM64, so 32-bit builds take the scalar
|
||||
path. The speaker path is bit-identical on both.
|
||||
|
||||
## Licence
|
||||
|
||||
`LICENSE` is the upstream MIT licence, copied unchanged; `kernel/` is a copy of the upstream
|
||||
renderer sources and keeps their notices. See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|
||||
`LICENSE` 是上游 MIT 许可,原样复制;`kernel/` 是上游渲染内核源码的副本,保留其声明。见
|
||||
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。
|
||||
|
||||
Reference in New Issue
Block a user