afef6d2c52
Native builds / linux-x64 (push) Failing after 18s
Native builds / macos-arm64 (push) Has been cancelled
Native builds / macos-x64 (push) Has been cancelled
Native builds / windows-x64 (push) Has been cancelled
Native builds / Publish GitHub Release (push) Has been cancelled
192 lines
7.3 KiB
Markdown
192 lines
7.3 KiB
Markdown
# JustOneCacophony 原生核说明
|
||
|
||
[English](native.en.md) · [返回 README](../README.md)
|
||
|
||
## 1. 职责边界
|
||
|
||
`native/` 只承载状态密集、调用频繁的 DSP 与扬声器渲染核。EMDF/JOC/OAMD 高层解析、错误报告、ADM 组装和 CLI 保留在 Python 中。
|
||
|
||
Python 通过标准库 `ctypes` 调用 C ABI;原生核不使用 pybind11、Cython、FFTW、MKL 或 OpenMP。它是可选加速路径,不扩大项目所支持的码流范围。
|
||
|
||
主要文件:
|
||
|
||
```text
|
||
native/include/eac3joc_core.h C ABI
|
||
native/src/eac3joc_core.cpp JOC/QMF 对象重建
|
||
native/src/speaker_renderer.cpp 对象到扬声器渲染
|
||
native/src/qmf_tables.h QMF 表
|
||
native/src/speaker_layouts.h 布局表
|
||
native/src/joc_huffman_tables.h JOC Huffman 表
|
||
src/native_renderer.py JOC ctypes 桥
|
||
src/speaker_native_renderer.py 扬声器 ctypes 桥
|
||
```
|
||
|
||
## 2. JOC 渲染 ABI
|
||
|
||
一个 opaque renderer 保存所有跨帧状态。主要调用为:
|
||
|
||
```c
|
||
int ejoc_renderer_process(
|
||
ejoc_renderer_handle handle,
|
||
const float* bed5_planar, /* [5][1536] */
|
||
const float* lfe, /* [1536] or NULL */
|
||
uint32_t object_mask,
|
||
const uint8_t* n_bands, /* [15] */
|
||
const uint8_t* n_dpoints, /* [15] */
|
||
const uint8_t* slope_idx, /* [15] */
|
||
const uint8_t* offset_ts, /* [15][2] */
|
||
const double* dq, /* [15][2][5][23] */
|
||
double clipgain,
|
||
float phase_new,
|
||
float output_scale,
|
||
float* output16_planar); /* [16][1536] */
|
||
```
|
||
|
||
JOC 的 Huffman 解码、差分还原和去量化先在 Python 中完成,dense 与 sparse 两条语法共用同一条入口。原生核心消费已去量化的 `dq`(double),两条语法在该 ABI 上布局一致。
|
||
|
||
线程接口为:
|
||
|
||
```c
|
||
int ejoc_renderer_set_threads(ejoc_renderer_handle handle, uint32_t total_threads);
|
||
uint32_t ejoc_renderer_thread_count(ejoc_renderer_handle handle);
|
||
```
|
||
|
||
`total_threads` 包含调用线程。单个 renderer 实例必须顺序提交帧;实例内部可以按对象和 analysis channel 并行。
|
||
|
||
## 3. 跨帧状态
|
||
|
||
每个 JOC renderer 独立保存:
|
||
|
||
- analysis FIFO:`double[5][9][64]`;
|
||
- L/R/C analysis delay:`float[3][10][64]`;
|
||
- Ls/Rs QMF delay:`complex<double>[2][10][64]`;
|
||
- Ls/Rs band-0 FIR history:`complex<double>[2][20]`;
|
||
- 矩阵插值 previous:`double[15][5][64]`;
|
||
- inverse-QMF state:`double[15][640]`;
|
||
- LFE delay:`double[1217]`。
|
||
|
||
这些状态属于 renderer 实例,不能在无 checkpoint 的情况下任意分段或乱序处理。
|
||
|
||
## 4. FFT、QMF 与精度
|
||
|
||
原生核包含固定 64 点 radix-2 complex FFT:
|
||
|
||
- analysis QMF 使用 forward FFT 后除以 64;
|
||
- inverse QMF 使用固定重排、旋转和 640 项有效窗状态;
|
||
- 不调用外部 FFT 库。
|
||
|
||
JOC 路径的数值类型为:
|
||
|
||
- 核心 PCM 输入:float32;
|
||
- 矩阵、复 QMF、FFT、FIR 和跨帧状态:double;
|
||
- phase 与最终 gain:float32;
|
||
- 16 声道对象输出:float32。
|
||
|
||
## 5. 扬声器渲染 ABI
|
||
|
||
同一个共享库还导出对象到扬声器布局的渲染接口:
|
||
|
||
```c
|
||
uint32_t ejoc_speaker_layout_channel_count(uint32_t speaker_bitfield);
|
||
|
||
ejoc_speaker_renderer_handle
|
||
ejoc_speaker_renderer_create(uint32_t speaker_bitfield);
|
||
|
||
int ejoc_speaker_renderer_process(
|
||
ejoc_speaker_renderer_handle handle,
|
||
const float* objects16_interleaved,
|
||
uint32_t sample_count,
|
||
uint32_t metadata_count,
|
||
const uint32_t* metadata_offsets,
|
||
const uint32_t* ramp_durations,
|
||
const uint16_t* positions_q15,
|
||
const uint8_t* region_indices,
|
||
const uint8_t* height_enabled,
|
||
const double* object_gains,
|
||
double* output_interleaved);
|
||
```
|
||
|
||
输入声道 0 为 LFE,1–15 为对象。每个 metadata entry 是一份对象状态快照。`sample_count` 必须是 32 的倍数;未完成的增益斜坡保存在 handle 中并跨调用继续。
|
||
|
||
扬声器路径使用 float32 对象输入、double 坐标/增益/累加与 interleaved double 输出;写 WAV 时才量化为 float32 或 PCM24。
|
||
|
||
支持的布局为:
|
||
|
||
```text
|
||
2.0 3.1 5.1 7.1 5.1.2 5.1.4 7.1.2 7.1.4 9.1.4 9.1.6
|
||
```
|
||
|
||
## 6. 双耳渲染 ABI
|
||
|
||
共享库提供 512-sample float64 双耳 DSP:
|
||
|
||
```c
|
||
ejoc_binaural_renderer_handle ejoc_binaural_renderer_create(void);
|
||
int ejoc_binaural_renderer_configure_kernels(...);
|
||
int ejoc_binaural_renderer_configure_room(...);
|
||
int ejoc_binaural_renderer_process(
|
||
ejoc_binaural_renderer_handle handle,
|
||
const double* input16_interleaved, /* [512][16] */
|
||
const double* gains_complex, /* [16][2][77][2] */
|
||
const double* room_sends, /* [16] */
|
||
double output_gain,
|
||
double* output_stereo_interleaved); /* [512][2] */
|
||
```
|
||
|
||
Python 负责模型解析、OAMD 时间轴和每 512 samples 的 complex gains/room sends。C++ handle 保存 QMF、hybrid、递归 room 和 QMF synthesis 状态。全部输入、状态、乘加和输出均为 double/complex double。
|
||
|
||
## 6.1 公开 SOFA 双耳渲染 ABI
|
||
|
||
共享库同时提供完整的原生 SOFA 双耳渲染器(`ejoc_sofa_binaural_*`),它镜像
|
||
Python `SofaBinauralBackend` 的全部数学:64-QMF/77-hybrid analysis/synthesis、
|
||
五阶 ACN/N3D 实球谐方向场求值、whole-QMF-slot 逐对象 delay 历史、六面一阶
|
||
image-source early reflections、共享 unitary FDN late room、LFE 120–180 Hz
|
||
低通与 961-sample latency 语义。kernel 表、编译好的 HRTF 场与房间常数通过
|
||
`configure_kernels/configure_field/configure_room` 一次上传;每 512-sample
|
||
block 先 `set_source` 更新 16 个 source,再 `process` 输入 PCM;`process` 返回
|
||
裁剪后的 stereo 样本数(首个 961 samples 被丢弃)。`finish` 以 64-sample 对齐的
|
||
块排空尾音。Python 桥位于 `src/sofa_native_backend.py`,与 Python 参考实现逐值
|
||
一致(差异 < 1e-9);原生库缺失时 `main.py` 自动回退 Python。
|
||
|
||
## 7. 构建
|
||
|
||
CMake 定义位于 `native/CMakeLists.txt`。从仓库根目录运行:
|
||
|
||
```powershell
|
||
cmake -S native -B build/cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$PWD/lib"
|
||
cmake --build build/cmake --config Release
|
||
cmake --install build/cmake --config Release
|
||
```
|
||
|
||
平台运行库文件名:
|
||
|
||
```text
|
||
Windows lib/eac3joc_core.dll
|
||
Linux lib/libeac3joc_core.so
|
||
macOS lib/libeac3joc_core.dylib
|
||
```
|
||
|
||
MSVC 配置使用静态 CRT。其他运行时依赖由平台和工具链决定,发布预构建库前应对产物独立检查。
|
||
|
||
仓库默认不附带原生二进制。预构建的 Release 运行库或自行构建的运行库均可直接放入 `lib/`。
|
||
|
||
## 8. 运行时查找与回退
|
||
|
||
查找顺序为:
|
||
|
||
1. 显式 `--native-library`;
|
||
2. `EAC3JOC_NATIVE_LIBRARY`;
|
||
3. `lib/` 下当前平台的标准文件名。
|
||
|
||
`--backend auto` 在加载失败时回退到 NumPy;`--backend python` 跳过原生探测。`--backend native` 当前也会打印失败原因后回退,这是现有 CLI 行为,不应理解为原生库已成功使用。
|
||
|
||
## 9. 实现边界
|
||
|
||
- 原生层只接收 Python 已解析的 dense JOC 数据。
|
||
- ABI 固定了 1536-sample JOC 帧、最多 15 个对象、最多 23 个参数带和最多 2 个数据点。
|
||
- 共享库与 Python 桥需要 ABI version 一致。
|
||
- 跨平台只约定 ABI 与数据类型,不保证 float64 结果逐位一致。
|
||
- `native/src/` 中的私有表头只服务于原生侧;当前仓库不包含重新生成这些头文件的脚本。
|
||
|
||
相关公式见[数学说明](math.md)。
|