mediadecode-ffmpeg 0.8.0

FFmpeg adapter for the `mediadecode` abstraction layer — implements its `VideoAdapter` / `AudioAdapter` / `SubtitleAdapter` traits and the matching push-style decoder traits, with hardware-acceleration auto-probe across VideoToolbox / VAAPI / NVDEC / D3D11VA and software fallback via ffmpeg-next.
Documentation
//! Harness for faults that cannot be injected in this process.
//!
//! Two kinds of test need their own process. One is a fault switch that
//! is **global**: `av_max_alloc` makes every FFmpeg allocation past a
//! size fail, which is the only honest way to test an out-of-memory
//! path against the real library — and it would break every other lane
//! running beside it. The other is a failure whose whole point is that
//! it *used* to end the process: a test that asserts the process
//! survives has to be able to observe a process that did not.
//!
//! [`in_subprocess`] re-runs one named test in a child of the test
//! binary itself, single-threaded and alone, and the parent asserts the
//! child exited normally. A child that aborts is reported as the
//! signal it died of rather than as a passing test.

/// Runs `body` in a child process running only this test, and asserts
/// the child exited cleanly.
///
/// `test_path` must be the test's name as libtest spells it — the
/// module path inside the crate, e.g. `demuxer::tests::the_lane` — so
/// that `--exact` selects exactly this test in the child.
pub(crate) fn in_subprocess(test_path: &str, body: impl FnOnce()) {
  const CHILD: &str = "MEDIADECODE_FFMPEG_FAULT_CHILD";

  if std::env::var(CHILD).as_deref() == Ok(test_path) {
    body();
    return;
  }

  let exe = std::env::current_exe().expect("the test binary's own path");
  let output = std::process::Command::new(exe)
    .args(["--exact", test_path, "--nocapture", "--test-threads=1"])
    .env(CHILD, test_path)
    .output()
    .expect("spawning the child test process");

  let stdout = String::from_utf8_lossy(&output.stdout);
  let stderr = String::from_utf8_lossy(&output.stderr);
  assert_eq!(
    output.status.code(),
    Some(0),
    "the child running `{test_path}` did not exit cleanly ({:?})\n--- stdout ---\n{stdout}\n--- stderr ---\n{stderr}",
    output.status,
  );
  // A child that matched no test also exits zero. Insisting that it ran
  // exactly one is what keeps a renamed test from turning this lane
  // into a lane that asserts nothing.
  assert!(
    stdout.contains("1 passed"),
    "the child ran no test — is `{test_path}` still the name?\n--- stdout ---\n{stdout}",
  );
}

/// Makes every FFmpeg allocation larger than `max` fail, returning
/// `NULL` the way an out-of-memory allocator would.
///
/// Process-global — only ever called from inside [`in_subprocess`].
pub(crate) fn cap_ffmpeg_allocations(max: usize) {
  // SAFETY: `av_max_alloc` stores an atomic and returns nothing.
  unsafe { ffmpeg_next::ffi::av_max_alloc(max) };
}

/// Lifts the cap [`cap_ffmpeg_allocations`] set.
pub(crate) fn uncap_ffmpeg_allocations() {
  cap_ffmpeg_allocations(i32::MAX as usize);
}