oak-ffmpeg-link: link the prebuilt shared FFmpeg on Windows

The Windows CI/CD archive (BtbN) is a shared build whose seven MSVC
import libraries have canonical names and whose .pc files carry no
external Libs.private, so pkg-config resolution is unnecessary there.
When neither pkg-config nor pkgconf exists, the script now emits the
fixed link line instead of panicking — but only on Windows: everywhere
else a static FFmpeg still needs the transitive list from its .pc files,
and MSYS2 local builds keep using pkg-config.

The FFmpeg 9 note in oak-codec's manifest is refreshed as well.
This commit is contained in:
2026-09-24 18:49:54 +08:00
parent bd447e1a7d
commit 725a1d4da0
2 changed files with 51 additions and 12 deletions
+9 -8
View File
@@ -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/<crate-version>, 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/<crate-version>, 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"
+42 -4
View File
@@ -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: