Files
oak-editor/crates/oakaudio
Mike-Solar 2d4342ff48 ci: unpack ocio-sys before patching yaml-cpp; fix oakaudio test compile
- cargo fetch does not extract sources; the yaml-cpp <cstdint> patch
  now untars the .crate into the registry src dir first (the glob
  found nothing and the step failed with exit 2)
- oakaudio: the watchdog-wrapped audio test called Self::... from a
  free-function test module (compile error in lib test)
2026-08-21 02:40:44 +08:00
..

oakaudio Rust crate

Status: implemented. The C ABI (include/audio/*.h) is implemented by src/ffi.rs; the contract suite lives in tests/ (all green, ~88% line coverage under tarpaulin). The architecture below mirrors the oaknode/oakrender crate template (FFI discipline, testing layers) from crates/oaknode/README.md and crates/oakrender/README.md.

Scope

Replaces the C++ oakaudio module (src/audio/src, ~50k lines): the PortAudio output/input manager (AudioManager), the real-time resampler/format converter (AudioProcessor), timeline synchronization helpers (AudioSynchronizer, AudioWaveformSync), the level meter (AudioLevelMeter), the visual waveform store (AudioVisualWaveform), the header-only pull buffer (PreviewAudioDevice), and the config bridge (audio_config namespace).

Public contract: include/audio/*.h (5 headers plus error.h, ~45 functions) — frozen, implemented verbatim by src/ffi.rs.

Key architectural decisions (C++ → Rust mapping)

  1. Singleton manager. AudioManager is a process-wide PortAudio singleton. Rust keeps the singleton behind a OnceLock<Mutex<...>> with borrow-only handles: addref/release are no-ops exactly as on the C++ side, and an empty handle reports OAKAUDIO_E_STATE. No destruction ever happens through the handle.
  2. Processor is the only heavy FFI consumer. AudioProcessor wraps the ffmpeg_bridge audio filter graph (fb_audio_graph_*, fb_frame_*); every call funnels through bridge::ffmpeg. The resampler/format-conversion semantics and the always-planar-f32 output (OAKAUDIO_PROCESSOR_OUTPUT_FORMAT = 4) are preserved.
  3. Sync helpers are stateless. AudioSynchronizer and AudioWaveformSync have only static methods in C++; they become plain functions in synchronizer.rs / waveformsync.rs. No handles are involved on the sync headers except by-value arguments.
  4. Value types are local. params.rs defines AudioParams (a plain POD) and a planar-first SampleFormat enum mirroring olive::core::SampleFormat::Format exactly, because these values cross the C ABI as int. See the note in params.rs about why the crate does not reuse oakcore-rs's SampleFormat.
  5. Rational reuses oakcore-rs. core::Rational (used by synchronizer and waveform) comes from oakcore-rs; there is no local copy.
  6. Record path through oakcodec. AudioManager records through the oakcodec encoder C ABI (bridge::codec) and waveform extraction decodes through the oakcodec decoder C ABI — exactly as the C++ does. No direct ffmpeg_bridge use in the record path.
  7. Config via oakcommon. Device names and the output buffer size read through bridge::common (oakcommon_config_*), preserving the audio_config namespace semantics as a config.rs free-function module.

Layout

COVERAGE.md maps every C++ audio class/method to its Rust home. Review that first.

src/
  lib.rs         crate doc + module map
  error.rs       error codes (mirrors include/audio/error.h)
  handle.rs      refcounted-handle scaffolding (same pattern as node)
  params.rs      AudioParams + planar-first SampleFormat value types
  config.rs      audio_config namespace (bridge::common)
  manager.rs     AudioManager singleton (PortAudio I/O, recording)
  processor.rs   AudioProcessor (resampler/converter, bridge::ffmpeg)
  synchronizer.rs AudioSynchronizer placement helpers
  levelmeter.rs  AudioLevelMeter peak/RMS/VU/LUFS analysis
  waveform.rs    AudioVisualWaveform mipmapped store + extraction
  waveformsync.rs AudioWaveformSync envelope offset estimation
  previewdevice.rs PreviewAudioDevice pull buffer
  bridge/        C ABI imports: common.rs, codec.rs, ffmpeg.rs
  ffi.rs         include/audio/*.h export layer
tests/           contract + golden tests (see README test section)

Hard rules for the implementer

  1. Every extern "C" body goes through handle::guard*; no panic crosses FFI. The manager's borrow-only singleton is the one place guard_handle/guard_void are used with no refcount semantics.
  2. SampleFormat and AudioParams integer values MUST match the C++ enums bit-for-bit; // CPP-PARITY: comments mark every load-bearing layout decision.
  3. Behavior parity with C++ is proven by the C ABI test-suite (src/audio/tests, unchanged) plus the golden tests in tests/ (waveform mipmap/channel-interleaved layout, RMS/LUFS thresholds, sync placement).
  4. Where C++ behavior is genuinely load-bearing but ugly, port the behavior, not the aesthetics; leave a // CPP-PARITY: comment with the C++ file:line.
  5. src/plugin/ is frozen and out of scope; no oakaudio code reaches into it.

Dependency policy

Prefer mature third-party crates (MIT/Apache-2.0/BSD, GPL-compatible) over hand-rolling; register each addition (name + reason) here. Large existing C++ libraries (OTIO, OCIO, OIIO, FFmpeg) are NEVER rewritten — they are consumed through their C ABI / bridge layers.