diff --git a/crates/oak-codec/Cargo.toml b/crates/oak-codec/Cargo.toml index b64b69bb4..9b19cbc86 100644 --- a/crates/oak-codec/Cargo.toml +++ b/crates/oak-codec/Cargo.toml @@ -14,17 +14,18 @@ oak-core = { path = "../oak-core" } # Real media decode/encode. The C++ ffmpeg_bridge library existed only to # absorb FFmpeg API churn; the Rust crate calls ffmpeg-next directly. # -# ffmpeg-next 9.x pairs with FFmpeg 8.x headers, which most distros do -# not ship yet — build a matching GPL FFmpeg (all free-license codecs + -# per-OS hardware acceleration, static+PIC) with +# ffmpeg-next 9.x compiles against the FFmpeg 8.x and 9.x headers (both +# verified locally with the project FFmpeg build), but most distros do +# not ship a matching prebuilt library yet — build one (all free-license +# codecs + per-OS hardware acceleration, static+PIC) with # tooling/ffmpeg/build-ffmpeg.sh and point cargo at it via FFMPEG_DIR # (see docs/build.md). The `static` feature makes the linker prefer the -# archives. +# archives. CI/CD uses the same script on Linux/macOS and BtbN's +# prebuilt shared archive on Windows. # -# ffmpeg-next's own `build` feature is NOT usable: it clones -# release/, and every such pairing is broken upstream -# (9.0.0 -> FFmpeg 9.0 removed AVCodec fields; 8.1.0 -> FFmpeg 8.1 added -# enum variants; 8.0.0 -> FFmpeg 8.0 renamed FF_PROFILE_*). +# ffmpeg-next's own `build` cargo feature is still not what we use: it +# clones release/, tying the FFmpeg version to the crate +# version instead of the project-pinned one. ffmpeg-next = { version = "9", features = ["static"] } # `std::error::Error` impls for the crate-internal error enum. thiserror = "2" diff --git a/crates/oak-ffmpeg-link/build.rs b/crates/oak-ffmpeg-link/build.rs index f3023eef9..81e84d44b 100644 --- a/crates/oak-ffmpeg-link/build.rs +++ b/crates/oak-ffmpeg-link/build.rs @@ -50,7 +50,7 @@ fn main() { assert!( pc_dir.exists(), "FFMPEG_DIR={dir} has no lib/pkgconfig — point it at a full install prefix \ - (the output of tooling/ffmpeg/build-ffmpeg.sh)" + (the output of tooling/ffmpeg/build-ffmpeg.sh, or the prebuilt archive on Windows)" ); println!("cargo:rerun-if-env-changed=FFMPEG_DIR"); let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); @@ -100,9 +100,24 @@ fn main() { Some(custom) => run(&custom).expect("PKG_CONFIG points at a program that failed to run"), None => match run("pkg-config") { Ok(output) => output, - Err(e) if e.kind() == std::io::ErrorKind::NotFound => { - run("pkgconf").expect("pkg-config/pkgconf is required when FFMPEG_DIR is set") - } + Err(e) if e.kind() == std::io::ErrorKind::NotFound => match run("pkgconf") { + Ok(output) => output, + Err(pkgconf_err) if pkgconf_err.kind() == std::io::ErrorKind::NotFound => { + // Windows CI/CD links the prebuilt SHARED FFmpeg (the + // BtbN archive, see the workflows): its import libraries + // carry canonical names and the archive is + // self-contained, so the fixed link line replaces + // pkg-config there (no pkg-config is installed). + #[cfg(target_os = "windows")] + { + link_prebuilt_shared(&dir); + return; + } + #[cfg(not(target_os = "windows"))] + panic!("pkg-config/pkgconf is required when FFMPEG_DIR is set"); + } + Err(pkgconf_err) => panic!("failed to run pkgconf: {pkgconf_err}"), + }, Err(e) => panic!("failed to run pkg-config: {e}"), }, }; @@ -173,6 +188,29 @@ fn main() { } } +/// Emit the link directives for the prebuilt SHARED FFmpeg archive the +/// Windows CI/CD jobs use (BtbN's build): the seven FFmpeg import +/// libraries with their canonical MSVC names. The archive is +/// self-contained — its `.pc` files carry no external `Libs.private` — so +/// no pkg-config resolution is needed (and no pkg-config binary is +/// installed there). MSYS2 local builds keep using pkg-config because +/// theirs is a static FFmpeg with the full transitive codec list. +#[cfg(target_os = "windows")] +fn link_prebuilt_shared(dir: &str) { + let libdir = PathBuf::from(dir).join("lib"); + assert!( + libdir.join("avcodec.lib").exists(), + "FFMPEG_DIR={dir} has no lib/avcodec.lib — point it at the prebuilt shared \ + archive (ffmpeg-n8.1-latest-win64-gpl-shared-8.1.zip)" + ); + println!("cargo:rustc-link-search=native={}", libdir.display()); + for lib in [ + "avformat", "avcodec", "avfilter", "avdevice", "avutil", "swscale", "swresample", + ] { + println!("cargo:rustc-link-lib={lib}"); + } +} + /// Reads `key` from the process environment, falling back to the `.env` /// file at the workspace root (this crate lives in crates/oakffmpeg-link, /// so the root is two levels up). The .env syntax is the common one: