Versioned C ABI¶
The distributable header is the canonical openjoc.h header.
It is manually maintained, deterministic, and compiled in both C and C++ by
the repository smoke script. The crate builds rlib, static-library, and
dynamic-library targets through Cargo. Platform release archives expose the
consumer-facing subset as include/openjoc.h plus
libopenjoc_capi.a/libopenjoc_capi.dylib on macOS,
openjoc_capi.lib/openjoc_capi.dll.lib/openjoc_capi.dll on Windows, and
the corresponding .a/.so files on Linux. The .rlib is an internal Rust
artifact, not the primary C consumer library.
ABI policy¶
The ABI is 1.4-experimental, independent of the OpenJOC package version.
Major changes may break layout or ownership rules and require an ABI-major
increment. Minor additions must append fields or functions and preserve the
meaning of existing fields. Configuration, PCM-frame, and output-info structs
contain struct_size; callers must initialize them and producers must reject a
smaller size. The dialnorm_mode field was appended in ABI minor 1. A caller
presenting the ABI 1.0 configuration size is accepted and receives
OPENJOC_DIALNORM_DEFAULT. ABI 1.2 appends functions and statuses without
changing any existing structure layout. openjoc_get_abi_version() returns
(major << 16) | minor.
ABI 1.4 appends custom_speaker_layout to openjoc_decoder_config. Set it to
an in-memory openjoc_custom_speaker_layout whose ordered
openjoc_custom_speaker array contains finite azimuth/elevation degrees and a
OPENJOC_SPEAKER_FULL_RANGE or OPENJOC_SPEAKER_LFE role. The descriptor and
all strings are borrowed only during openjoc_decoder_create; the decoder
copies the validated layout and reports the same order through output labels.
Existing callers leave the field null and retain preset behavior. The custom
layout contract, coordinate convention, validation limits, and WAV/CAF
metadata boundary are documented in
custom speaker layouts.
openjoc_decoder_config_init() remains the legacy-safe ABI 1.3 prefix
initializer: it never writes the ABI 1.4 appended field, so a real ABI 1.3
caller may link it against the ABI 1.4 library without a struct over-write.
ABI 1.4 callers should use openjoc_decoder_config_init_v1_4() when they need
the complete current struct or custom geometry.
Experimental means the C surface may evolve during OpenJOC 0.x integration work. It does not mean that existing decoder correctness claims are withdrawn.
Ownership and calls¶
openjoc_decoder_config config;
openjoc_decoder_config_init_v1_4(&config);
openjoc_decoder *decoder = NULL;
openjoc_decoder_create(&config, &decoder);
openjoc_decoder_send_packet(decoder, bytes, byte_count,
OPENJOC_NO_PTS, 0);
openjoc_pcm_frame frame;
openjoc_pcm_frame_init(&frame);
while (openjoc_decoder_receive_frame(decoder, &frame) ==
OPENJOC_STATUS_FRAME_AVAILABLE) {
/* frame.data is interleaved float32, valid until the next send/receive/reset */
}
openjoc_decoder_drain(decoder);
openjoc_decoder_destroy(decoder);
The decoder is an opaque handle. Packet memory is borrowed only during
openjoc_decoder_send_packet; it is never retained. PCM memory is owned by
the decoder and remains valid until the next send, receive, flush, reset, or
destroy on that handle. Applications that need longer ownership copy the
frame. Multiple handles are independent.
ABI 1.2 also provides openjoc_stream_decoder, a framework-neutral handle for
adapters whose packet boundaries are not complete access-unit boundaries. Its
openjoc_stream_decoder_send_chunk() call accepts arbitrary compressed bytes,
an optional 1/48000 sample-domain PTS, and the existing discontinuity/preroll
flags. The handle reuses the external FFmpeg bridge's single 131,072-byte-
bounded assembler, positive JOC admission, timestamp model, output queue,
semantic channel permutation, and lazy OpenJocSession creation. It supports
fragmented AUs and multiple AUs per chunk without exposing any framework type.
openjoc_stream_decoder_receive_frame() returns packed float PCM in the order
reported by its semantic channel labels. Output semantics, the exact shared
configuration descriptor/fingerprint, and current bounded staging size are
available before or during decoding. OPENJOC_STATUS_NOT_JOC distinguishes a
positive ordinary-E-AC-3 rejection; out-of-memory and external-library
categories have dedicated numeric statuses for host error mapping.
ABI 1.3 adds openjoc_classifier, a decode-free, framework-neutral compressed
stream probe. openjoc_classifier_send_chunk() shares the bounded access-unit
parser and positive JOC admission rules but never creates an OpenJOC render
session or emits PCM. openjoc_classifier_finish() closes the probe so a final
complete one-AU stream can be classified without a following syncframe. The
output is one of UNKNOWN, CONFIRMED_JOC, CONFIRMED_NON_JOC, or
INVALID_OR_UNSUPPORTED; the staged and inspected-byte accessors expose
bounded probe accounting. This is intended for players that must choose a
decoder before sending the first packet to a renderer.
Semantic labels are available through openjoc_decoder_get_channel_label and
the output/frame descriptors. The canonical PCM sample format value is 1
(interleaved float32).
Set render_mode to OPENJOC_RENDER_BINAURAL with a null/zero sofa_data /
sofa_size pair to use the bundled offline SADIE II generic HRTF. Supplying a
non-empty SOFA buffer selects the existing strict user-dataset path. The
virtual layout defaults to the configured speaker layout when
virtual_layout is null. A native 22.2 speaker session is selected with
speaker_layout = "22.2"; its output exposes 24 ordered semantic labels,
including LFE1 and LFE2.
The C adapter inherits the shared session's calibrated Default E-AC-3 dialnorm
program calibration unless dialnorm_mode is explicitly set to
OPENJOC_DIALNORM_DIGITAL or OPENJOC_DIALNORM_ANALOG. Default is recommended
for normal playback/decoding. Digital explicitly selects encoded digital
program-level calibration. Analog uses unity dialnorm gain and is an advanced
compatibility/diagnostic policy, not a recommended louder-output or mastering
mode. Dialnorm is metadata-derived and separate from the existing DRC fields;
DRC changes encoded dynamic-range behavior. FinalLinkedGain is internal
renderer headroom behavior, not a user mastering control.
The C ABI is a streaming PCM interface and does not perform file-export peak
normalization or spool a complete program for a file-level transform.
Applications may apply their own final static gain policy after receiving PCM.
The CLI's
--normalize-peak is an offline file-output convenience: it normalizes the
final rendered file to a requested sample peak after decoder and renderer
processing, and is not dialnorm, DRC, a limiter, compressor, LUFS, or true-peak
normalization.
Failure containment¶
Every exported operation contains Rust panics before returning. No Rust panic,
Rust error object, or Rust struct layout crosses the ABI. last_error is
owned by the decoder instance and is not process-global. Null arguments,
invalid struct sizes, malformed packets, unsupported configurations, format
changes, and render failures return numeric status codes.
The public C header has no third-party generated material and is distributed under the repository Apache-2.0 license.