Files
oak-editor/crates/oakcommon/tests/ffi_ffmpegutils.rs
T
Mike-Solar 18ff60f147 feat(engine): clip move, clip effect_input, mandatory static FFmpeg
- oakengine_sequence_move_clip implemented for real (oaktimeline
  TrackMoveBlockCommand; fixes the graph-ownership/gap-anchor/ripple
  trim bugs the stub was hiding); same-track via the frozen C ABI,
  cross-track supported by the module command
- oaknode clip blocks now declare a tex_in texture input and set
  effect_input to it, so timeline clips can host effect chains; facade
  test covers effect insert/remove on a real clip
- oakffmpeg-link: FFMPEG_DIR is now mandatory with a clear panic (a
  Homebrew upgrade left the system ffmpeg .pc pointing at a deleted
  dav1d Cellar path, breaking links); reads a git-ignored workspace
  .env for IDEs that cannot inject env vars (RustRover); links the C++
  stdlib for C++ codec libs (svt-av1)
- oakengine re-exports oaknode so tests share one crate instance;
  it_node uses the direct instance's value type where it calls the
  module FFI (the --workspace dev-dependency feature split builds
  oaknode twice)
2026-08-11 23:04:48 +08:00

248 lines
7.8 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/>.
//! Integration tests for the C ABI surface of `oakcommon::ffi::ffmpegutils`,
//! which implements `include/common/ffmpegutils.h` (oracle:
//! `src/common/src/ffmpegutils.cpp`).
//!
//! MUST be run with `--features test-stubs`: unlike every other ffi module,
//! `oakcommon_ffmpegutils_get_compatible_bridge_pixel_format` reaches the
//! real `fb_find_best_pix_fmt_of_list` symbol (ffmpeg_bridge C ABI) in
//! non-test builds, and the integration-test binary cannot link it. The
//! `test-stubs` feature substitutes the in-crate stub (see
//! `src/ffmpegutils.rs::find_best_pix_fmt_of_list`), mirroring the
//! oakplugin/oaktimeline convention. `cargo test --lib` needs no flag: the
//! stub is active under `cfg(test)` there.
//!
//! The pure mappings are already unit-tested in `src/ffmpegutils.rs`; these
//! tests pin the exported wrapper contract: a value written to a non-null
//! out-param with `OAKCOMMON_OK`, or `OAKCOMMON_E_INVALID` for a null
//! out-param, for every export.
#[cfg(feature = "test-stubs")]
mod ffi_ffmpegutils_tests {
use std::ptr::null_mut;
use oakcommon::error::{OAKCOMMON_E_INVALID, OAKCOMMON_OK};
use oakcommon::ffi::ffmpegutils::*;
/// Every out-param wrapper must reject a null pointer without writing.
#[test]
fn null_out_param_returns_invalid() {
let mut out: i32 = -999;
assert_eq!(
oakcommon_ffmpegutils_get_compatible_bridge_pixel_format(26, -1, null_mut()),
OAKCOMMON_E_INVALID
);
assert_eq!(
oakcommon_ffmpegutils_get_compatible_pixel_format(0, null_mut()),
OAKCOMMON_E_INVALID
);
assert_eq!(
oakcommon_ffmpegutils_get_ffmpeg_pixel_format(0, 4, null_mut()),
OAKCOMMON_E_INVALID
);
assert_eq!(
oakcommon_ffmpegutils_get_native_sample_format(0, null_mut()),
OAKCOMMON_E_INVALID
);
assert_eq!(
oakcommon_ffmpegutils_get_ffmpeg_sample_format(6, null_mut()),
OAKCOMMON_E_INVALID
);
assert_eq!(
oakcommon_ffmpegutils_convert_jpeg_space_to_regular_space(12, null_mut()),
OAKCOMMON_E_INVALID
);
// The out-param is untouched on the failure path.
assert_eq!(out, -999);
}
/// `get_compatible_bridge_pixel_format` picks an exact candidate from the
/// clamped list (values match `FB_PIX_FMT_*` / `PixelFormat`).
#[test]
fn compatible_bridge_pixel_format_exact_candidate() {
let mut out: i32 = -999;
// pix_fmt = RGBA (26), maximum = invalid (-1): no limit, RGBA is
// first in the candidate list.
assert_eq!(
oakcommon_ffmpegutils_get_compatible_bridge_pixel_format(26, -1, &mut out),
OAKCOMMON_OK
);
assert_eq!(out, 26);
// pix_fmt = RGBA_F32_LE (220), maximum = F32 (4): the f32 clamp adds
// RGBA_F32_LE, so it matches exactly.
assert_eq!(
oakcommon_ffmpegutils_get_compatible_bridge_pixel_format(220, 4, &mut out),
OAKCOMMON_OK
);
assert_eq!(out, 220);
}
/// `get_compatible_bridge_pixel_format` falls back to the first candidate
/// for an unknown source format (bridge loss-metric behaviour).
#[test]
fn compatible_bridge_pixel_format_unknown_falls_back() {
let mut out: i32 = -999;
// YUV420P (0) is not in the RGBA-oriented candidate list -> first
// candidate RGBA (26).
assert_eq!(
oakcommon_ffmpegutils_get_compatible_bridge_pixel_format(0, -1, &mut out),
OAKCOMMON_OK
);
assert_eq!(out, 26);
}
/// `get_compatible_pixel_format` maps native formats to the least-lossy
/// native format.
#[test]
fn compatible_pixel_format_maps_native() {
let mut out: i32 = -999;
assert_eq!(
oakcommon_ffmpegutils_get_compatible_pixel_format(0, &mut out),
OAKCOMMON_OK
); // U8
assert_eq!(out, 0);
assert_eq!(
oakcommon_ffmpegutils_get_compatible_pixel_format(3, &mut out),
OAKCOMMON_OK
); // F16
assert_eq!(out, 2); // -> U16
assert_eq!(
oakcommon_ffmpegutils_get_compatible_pixel_format(-1, &mut out),
OAKCOMMON_OK
); // invalid
assert_eq!(out, -1);
}
/// `get_ffmpeg_pixel_format` maps native format + channel count to a
/// bridge pixel format.
#[test]
fn ffmpeg_pixel_format_maps_native_to_bridge() {
let mut out: i32 = -999;
assert_eq!(
oakcommon_ffmpegutils_get_ffmpeg_pixel_format(0, 3, &mut out),
OAKCOMMON_OK
); // U8 RGB
assert_eq!(out, 2); // RGB24
assert_eq!(
oakcommon_ffmpegutils_get_ffmpeg_pixel_format(0, 4, &mut out),
OAKCOMMON_OK
); // U8 RGBA
assert_eq!(out, 26); // RGBA
assert_eq!(
oakcommon_ffmpegutils_get_ffmpeg_pixel_format(2, 4, &mut out),
OAKCOMMON_OK
); // U16 RGBA
assert_eq!(out, 105); // RGBA64LE
assert_eq!(
oakcommon_ffmpegutils_get_ffmpeg_pixel_format(1, 3, &mut out),
OAKCOMMON_OK
); // U10 RGB
assert_eq!(out, -1); // no bridge format
}
/// `get_native_sample_format` maps bridge sample formats to native.
#[test]
fn native_sample_format_maps_bridge_to_native() {
let mut out: i32 = -999;
assert_eq!(
oakcommon_ffmpegutils_get_native_sample_format(0, &mut out),
OAKCOMMON_OK
); // U8
assert_eq!(out, 6); // SMP_FMT_U8
assert_eq!(
oakcommon_ffmpegutils_get_native_sample_format(1, &mut out),
OAKCOMMON_OK
); // S16
assert_eq!(out, 7);
assert_eq!(
oakcommon_ffmpegutils_get_native_sample_format(8, &mut out),
OAKCOMMON_OK
); // FLTP
assert_eq!(out, 4); // SMP_FMT_F32_P
assert_eq!(
oakcommon_ffmpegutils_get_native_sample_format(999, &mut out),
OAKCOMMON_OK
);
assert_eq!(out, -1); // unknown -> invalid
}
/// `get_ffmpeg_sample_format` maps native sample formats to bridge.
#[test]
fn ffmpeg_sample_format_maps_native_to_bridge() {
let mut out: i32 = -999;
assert_eq!(
oakcommon_ffmpegutils_get_ffmpeg_sample_format(6, &mut out),
OAKCOMMON_OK
); // SMP_FMT_U8
assert_eq!(out, 0); // U8
assert_eq!(
oakcommon_ffmpegutils_get_ffmpeg_sample_format(10, &mut out),
OAKCOMMON_OK
); // SMP_FMT_F32
assert_eq!(out, 3); // FLT
assert_eq!(
oakcommon_ffmpegutils_get_ffmpeg_sample_format(-1, &mut out),
OAKCOMMON_OK
); // invalid
assert_eq!(out, -1);
}
/// JPEG-range bridge formats convert to their regular counterparts;
/// everything else passes through unchanged.
#[test]
fn jpeg_space_converts_to_regular_space() {
let mut out: i32 = -999;
assert_eq!(
oakcommon_ffmpegutils_convert_jpeg_space_to_regular_space(12, &mut out),
OAKCOMMON_OK
); // YUVJ420P
assert_eq!(out, 0); // YUV420P
assert_eq!(
oakcommon_ffmpegutils_convert_jpeg_space_to_regular_space(13, &mut out),
OAKCOMMON_OK
); // YUVJ422P
assert_eq!(out, 4); // YUV422P
assert_eq!(
oakcommon_ffmpegutils_convert_jpeg_space_to_regular_space(14, &mut out),
OAKCOMMON_OK
); // YUVJ444P
assert_eq!(out, 5); // YUV444P
assert_eq!(
oakcommon_ffmpegutils_convert_jpeg_space_to_regular_space(32, &mut out),
OAKCOMMON_OK
); // YUVJ440P
assert_eq!(out, 31); // YUV440P
assert_eq!(
oakcommon_ffmpegutils_convert_jpeg_space_to_regular_space(138, &mut out),
OAKCOMMON_OK
); // YUVJ411P
assert_eq!(out, 7); // YUV411P
// Non-JPEG formats pass through unchanged.
assert_eq!(
oakcommon_ffmpegutils_convert_jpeg_space_to_regular_space(26, &mut out),
OAKCOMMON_OK
); // RGBA
assert_eq!(out, 26);
assert_eq!(
oakcommon_ffmpegutils_convert_jpeg_space_to_regular_space(-1, &mut out),
OAKCOMMON_OK
); // none
assert_eq!(out, -1);
}
}