Files
oak-editor/crates/oakengine/tests/audio.rs
T
Mike-Solar 013a175707 refactor: workspace layout — crates/, app at root, legacy C++ removed
Single mechanical restructure commit:
- root Cargo.toml = oakapp bin + workspace; one cargo build produces
  oakapp, oak-cli, oak-worker, liboakengine.dylib
- app/rust/src -> src/ (app at repo root, no rust/ nesting)
- src/<mod>/rust -> crates/oak<mod>; src/oakcore-rs -> crates/oakcore;
  src/bindings/oakotio -> crates/oakotio; src/engine/rust ->
  crates/oakengine (keeps cdylib+staticlib+rlib)
- public C headers include/<mod>/ -> crates/oakengine/include/<mod>/
- OFX SDK headers vendored into crates/oakplugin/ofx/ (HostSupport gone)
- legacy deleted: old src/ C++ modules, engine/, core/, ffmpeg_bridge/,
  app/ (Qt), cli/worker C++, root CMakeLists, third_party/KDDockWidgets
  submodule, otio-install, all build-* output (~40GB)
- oakstorage kept but excluded from the workspace (skeleton w/ todos);
  gpui excluded (own workspace)
- verified: cargo build green, cargo test --workspace 1845/0
  (with the documented OCIO_RS_* env override for the homebrew OCIO)
2026-08-10 20:24:25 +08:00

151 lines
5.2 KiB
Rust

// Oak Video Editor - Non-Linear Video Editor
// Copyright (C) 2026 Oak Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! Smoke tests for the audio family (`engine/include/oakengine/audio.h`).
//! The AudioManager singleton is process-wide, so manager tests run in a
//! single serialized test function; processor and sync tests are
//! independent.
#[path = "common/mod.rs"]
mod common;
use oakengine::audio::{
oakengine_audio_clear_buffered_output, oakengine_audio_create_instance,
oakengine_audio_destroy_instance, oakengine_audio_estimate_envelope_offset,
oakengine_audio_get_output_device, oakengine_audio_hard_reset,
oakengine_audio_processor_close, oakengine_audio_processor_create,
oakengine_audio_processor_free, oakengine_audio_processor_is_open,
oakengine_audio_processor_open, oakengine_audio_push_to_output,
oakengine_audio_reset_output_clock, oakengine_audio_set_output_device,
oakengine_audio_set_output_notify_interval, oakengine_audio_stop_output,
oakengine_audio_sync_place_by_waveform_offset, OakAudioSyncPlacement,
OakAudioWaveformOffset,
};
/// Manager lifecycle: create/destroy round-trip and device accessors
/// (serialized — the singleton is process-wide).
#[test]
fn manager_lifecycle() {
// Start from a destroyed state.
unsafe { oakengine_audio_destroy_instance() };
// No instance → create succeeds, destroy is idempotent.
assert_eq!(unsafe { oakengine_audio_create_instance() }, 0);
assert_eq!(unsafe { oakengine_audio_destroy_instance() }, 0);
assert_eq!(unsafe { oakengine_audio_destroy_instance() }, 0);
// Recreate for the device tests.
assert_eq!(unsafe { oakengine_audio_create_instance() }, 0);
// paNoDevice (-1) until a device is set.
assert_eq!(unsafe { oakengine_audio_get_output_device() }, -1);
// The module records any device index (PortAudio validation is not
// bridged), so setting succeeds and reads back.
assert_eq!(unsafe { oakengine_audio_set_output_device(999999) }, 0);
assert_eq!(unsafe { oakengine_audio_get_output_device() }, 999999);
assert_eq!(unsafe { oakengine_audio_set_output_device(-1) }, 0);
// Stateless no-op calls succeed with a live manager.
assert_eq!(unsafe { oakengine_audio_reset_output_clock() }, 0);
assert_eq!(unsafe { oakengine_audio_stop_output() }, 0);
assert_eq!(unsafe { oakengine_audio_clear_buffered_output() }, 0);
assert_eq!(unsafe { oakengine_audio_set_output_notify_interval(1024) }, 0);
assert_eq!(unsafe { oakengine_audio_hard_reset() }, 0);
// push with a NULL params handle fails cleanly.
assert_eq!(
unsafe { oakengine_audio_push_to_output(std::ptr::null(), c"data".as_ptr(), 4, std::ptr::null_mut(), 0) },
-3 // OAKENGINE_E_FAILED
);
unsafe { oakengine_audio_destroy_instance() };
}
/// Sync envelope-offset correlation runs and fills the result struct.
#[test]
fn sync_envelope_offset() {
let reference = [0.0_f64, 0.5, 1.0, 0.5, 0.0];
let candidate = [0.0_f64, 0.0, 0.5, 1.0, 0.5];
let mut out = OakAudioWaveformOffset {
offset_samples: 0,
confidence: 0.0,
valid: 0,
};
let rc = unsafe {
oakengine_audio_estimate_envelope_offset(
reference.as_ptr(),
5,
candidate.as_ptr(),
5,
std::ptr::null(),
0,
std::ptr::null(),
0,
128,
16,
&mut out,
)
};
assert_eq!(rc, 0);
// The result is filled in either way; the correlation may or may not
// find a valid offset for this tiny synthetic input.
assert!(out.confidence >= 0.0 && out.confidence <= 1.0);
}
/// Waveform-offset placement runs and reports validity.
#[test]
fn sync_place_by_waveform_offset() {
let mut out = OakAudioSyncPlacement {
timeline_in_num: 0,
timeline_in_den: 1,
valid: 0,
};
let rc = unsafe {
oakengine_audio_sync_place_by_waveform_offset(0, 1, 48000, 48000, &mut out)
};
assert_eq!(rc, 0);
// 48000 samples at 48 kHz = 1 second.
assert_eq!(out.timeline_in_num, 1);
assert_eq!(out.timeline_in_den, 1);
assert_eq!(out.valid, 1);
// NULL out → E_INVALID.
assert_eq!(
unsafe { oakengine_audio_sync_place_by_waveform_offset(0, 1, 0, 48000, std::ptr::null_mut()) },
-1
);
}
/// Processor lifecycle: create/free round-trip; open with NULL params
/// fails with E_INVALID.
#[test]
fn processor_lifecycle() {
let p = unsafe { oakengine_audio_processor_create() };
assert!(!p.is_null());
assert_eq!(unsafe { oakengine_audio_processor_is_open(p) }, 0);
assert_eq!(unsafe { oakengine_audio_processor_close(p) }, 0);
// open with a NULL `to` params handle → E_INVALID.
assert_eq!(
unsafe { oakengine_audio_processor_open(p, std::ptr::null(), std::ptr::null(), 1.0) },
-1
);
unsafe { oakengine_audio_processor_free(p) };
// NULL free is a no-op.
unsafe { oakengine_audio_processor_free(std::ptr::null_mut()) };
}