Skip to content

Support binding generation with system FFmpeg #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,27 @@ jobs:
- name: BindingBuild
run: PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" cargo build --verbose

build_with_system_ffmpeg:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Install latest nightly
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true

- name: Install System FFmpeg
run: |
sudo apt-get install -y libavcodec-dev libavdevice-dev \
libavfilter-dev libavformat-dev libavutil-dev \
libpostproc-dev libswresample-dev libswscale-dev

- name: Binding Build
run: cargo build --verbose

# Check if correct documentation can be generated by docs.rs
docs_rs_check:
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ FFI binding for FFmpeg inner library.
$ curl -s https://static.rust-lang.org/rustup.sh | sh -s -- --channel=nightly
```
2. Generate and build the bindings:
Run `cargo build` to build the bindings, we will compile the FFmpeg in the git submodule for you. If you have a pre-built ffmpeg, set `PKG_CONFIG_PATH` to the path which points to `*.pc` files in the build result(e.g. `PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" cargo build`) then we will use the pre-built FFmpeg libraries. After the FFmpeg is built, the build script will take advantage of the package-config(`*.pc`) files to:
Run `cargo build` to build the bindings. If you have a pre-built ffmpeg, set `PKG_CONFIG_PATH` to the path which points to `*.pc` files in the build result(e.g. `PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" cargo build`) then it will use the pre-built FFmpeg libraries. If no `PKG_CONFIG_PATH` is set, it will first check if there are `libav*-dev` installed. If not, it will git clone the FFmpeg from <https://github.com/ffmpeg/ffmpeg> and then configure and compile it for you. After the FFmpeg libraries is ready, the build script will take advantage of the package-config(`*.pc`) files to:
1. Probe paths of the header files for binding generation and generate the binding.
2. Probe library dependencies as project dependencies to ensure this project can be built successfully.

Expand Down
228 changes: 121 additions & 107 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,30 @@ impl callbacks::ParseCallbacks for FilterCargoCallbacks {
}
}

fn probe_system_ffmpeg() -> Result<(), String> {
match (&*LIBS)
.iter()
.map(|name| "lib".to_owned() + name)
.find(|libname| {
pkgconfig::Config::new()
.statik(true)
// Remove side effect
.cargo_metadata(false)
.probe(&libname)
.is_err()
}) {
Some(libname) => Err(format!("{} not found", libname)),
None => Ok(()),
}
}

fn main() {
// If it's a documentation generation from docs.rs, just copy the bindings
// generated locally to `OUT_DIR`. We do this because the building
// environment of docs.rs doesn't have an network connection, so we cannot
// git clone the FFmpeg. And they also have a limitation on crate's size:
// 10MB, which is not enough to fit in full FFmpeg source files. So the only
// thing we can do is copy the locally generated binding files to the
// 10MB, which is not enough to fit in FFmpeg source files. So the only
// thing we can do is copying the locally generated binding files to the
// `OUT_DIR`.
if env::var("DOCS_RS").is_ok() {
fs::copy("src/binding.rs", &*BINDING_FILE_PATH)
Expand All @@ -130,127 +147,124 @@ fn main() {
}

if env::var("PKG_CONFIG_PATH").is_err() {
// All outputs are stored in ./ffmpeg/build/{bin, lib, share, include}
// If no prebuilt FFmpeg libraries provided, we custom build a FFmpeg.
env::set_var(
"PKG_CONFIG_PATH",
format!("{}/build/lib/pkgconfig", *FFMPEG_DIR),
);
env::set_var("PATH", format!("{}/build/bin:{}", *FFMPEG_DIR, *PATH));
// If no system FFmpeg found, download and build one
if let Err(msg) = probe_system_ffmpeg() {
eprintln!("{}! Try to git clone an FFmpeg and build.", msg);
// All outputs are stored in ./ffmpeg/build/{bin, lib, share, include}
// If no prebuilt FFmpeg libraries provided, we custom build a FFmpeg.
env::set_var(
"PKG_CONFIG_PATH",
format!("{}/build/lib/pkgconfig", *FFMPEG_DIR),
);
env::set_var("PATH", format!("{}/build/bin:{}", *FFMPEG_DIR, *PATH));

// Check if submodule is not get cloned.
if !path::PathBuf::from(format!("{}/fftools", &*FFMPEG_DIR)).is_dir() {
Command::new("git")
.current_dir(&*OUT_DIR)
.args(["clone", "https://github.com/ffmpeg/ffmpeg", "--depth", "1"].iter())
// Check if FFmpeg is not get cloned.
if !path::PathBuf::from(format!("{}/fftools", &*FFMPEG_DIR)).is_dir() {
Command::new("git")
.current_dir(&*OUT_DIR)
.args(["clone", "https://github.com/ffmpeg/ffmpeg", "--depth", "1"].iter())
.spawn()
.expect("FFmpeg submodule failed to clone.")
.wait()
.expect("FFmpeg submodule failed to clone.");
}

// Corresponding to the shell script below:
// ./configure \
// --prefix="$PWD/build" \
// --extra-cflags="-I$PWD/build/include" \
// --extra-ldflags="-L$PWD/build/lib" \
// --bindir="$PWD/build/bin" \
// --pkg-config-flags="--static" \
// --extra-libs="-lpthread -lm" \
// --enable-gpl \
// --enable-libass \
// --enable-libfdk-aac \
// --enable-libfreetype \
// --enable-libmp3lame \
// --enable-libopus \
// --enable-libvorbis \
// --enable-libvpx \
// --enable-libx264 \
// --enable-libx265 \
// --enable-nonfree
Command::new(format!("{}/configure", *FFMPEG_DIR))
.current_dir(&*FFMPEG_DIR)
.env(
"PKG_CONFIG_PATH",
format!("{}/build/lib/pkgconfig", *FFMPEG_DIR),
)
.args(
[
format!(r#"--prefix={}/build"#, *FFMPEG_DIR),
format!(r#"--extra-cflags=-I{}/build/include"#, *FFMPEG_DIR),
format!(r#"--extra-ldflags=-L{}/build/lib"#, *FFMPEG_DIR),
format!(r#"--bindir={}/build/bin"#, *FFMPEG_DIR),
]
.iter(),
)
.args(
[
"--pkg-config-flags=--static",
"--extra-libs=-lpthread -lm",
"--enable-gpl",
"--enable-libass",
"--enable-libfdk-aac",
"--enable-libfreetype",
"--enable-libmp3lame",
"--enable-libopus",
"--enable-libvorbis",
"--enable-libvpx",
"--enable-libx264",
"--enable-libx265",
"--enable-nonfree",
]
.iter(),
)
.spawn()
.expect("FFmpeg submodule failed to clone.")
.expect("FFmpeg build process: configure failed!")
.wait()
.expect("FFmpeg submodule failed to clone.");
}
.expect("FFmpeg build process: configure failed!");

// Corresponding to the shell script below:
// ./configure \
// --prefix="$PWD/build" \
// --extra-cflags="-I$PWD/build/include" \
// --extra-ldflags="-L$PWD/build/lib" \
// --bindir="$PWD/build/bin" \
// --pkg-config-flags="--static" \
// --extra-libs="-lpthread -lm" \
// --enable-gpl \
// --enable-libass \
// --enable-libfdk-aac \
// --enable-libfreetype \
// --enable-libmp3lame \
// --enable-libopus \
// --enable-libvorbis \
// --enable-libvpx \
// --enable-libx264 \
// --enable-libx265 \
// --enable-nonfree
Command::new(format!("{}/configure", *FFMPEG_DIR))
.current_dir(&*FFMPEG_DIR)
.env(
"PKG_CONFIG_PATH",
format!("{}/build/lib/pkgconfig", *FFMPEG_DIR),
)
.args(
[
format!(r#"--prefix={}/build"#, *FFMPEG_DIR),
format!(r#"--extra-cflags=-I{}/build/include"#, *FFMPEG_DIR),
format!(r#"--extra-ldflags=-L{}/build/lib"#, *FFMPEG_DIR),
format!(r#"--bindir={}/build/bin"#, *FFMPEG_DIR),
]
.iter(),
)
.args(
[
"--pkg-config-flags=--static",
"--extra-libs=-lpthread -lm",
"--enable-gpl",
"--enable-libass",
"--enable-libfdk-aac",
"--enable-libfreetype",
"--enable-libmp3lame",
"--enable-libopus",
"--enable-libvorbis",
"--enable-libvpx",
"--enable-libx264",
"--enable-libx265",
"--enable-nonfree",
]
.iter(),
)
.spawn()
.expect("FFmpeg build process: configure failed!")
.wait()
.expect("FFmpeg build process: configure failed!");

Command::new("make")
.current_dir(&*FFMPEG_DIR)
.arg(format!("-j{}", *NUM_CPUS))
.spawn()
.expect("FFmpeg build process: make compile failed!")
.wait()
.expect("FFmpeg build process: make compile failed!");
Command::new("make")
.current_dir(&*FFMPEG_DIR)
.arg(format!("-j{}", *NUM_CPUS))
.spawn()
.expect("FFmpeg build process: make compile failed!")
.wait()
.expect("FFmpeg build process: make compile failed!");

Command::new("make")
.current_dir(&*FFMPEG_DIR)
.arg(format!("-j{}", *NUM_CPUS))
.arg("install")
.spawn()
.expect("FFmpeg build process: make install failed!")
.wait()
.expect("FFmpeg build process: make install failed!");
Command::new("make")
.current_dir(&*FFMPEG_DIR)
.arg(format!("-j{}", *NUM_CPUS))
.arg("install")
.spawn()
.expect("FFmpeg build process: make install failed!")
.wait()
.expect("FFmpeg build process: make install failed!");

/* Commented because it's not needed, we are not using any specific shell.
Command::new("hash")
.current_dir(&*FFMPEG_DIR)
.arg("-r")
.spawn()
.expect("FFmpeg build process: clear hash cache failed!")
.wait()
.expect("FFmpeg build process: clear hash cache failed!");
*/
/* Commented because it's not needed, we are not using any specific shell.
Command::new("hash")
.current_dir(&*FFMPEG_DIR)
.arg("-r")
.spawn()
.expect("FFmpeg build process: clear hash cache failed!")
.wait()
.expect("FFmpeg build process: clear hash cache failed!");
*/
}
}

// We currently only support building with static libraries.

/* Thanks to pkg-config, we don't need this.
// Output link libraries
(&*LIBS)
.iter()
.for_each(|name| println!("cargo:rustc-link-lib={}={}", "static", name));
*/

// Probe libraries
// TODO if not enabled, we should not probe it
// TODO if not enabled, we should not probe it. Should modify probe_system_ffmpeg() too.
let include_paths = (&*LIBS)
.iter()
.map(|name| "lib".to_owned() + name)
.map(|libname| {
pkgconfig::Config::new()
.statik(true)
.cargo_metadata(true)
.probe(&libname)
.unwrap_or_else(|_| panic!(format!("{} not found!", libname)))
.include_paths
Expand Down