diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a8f06c3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,37 @@ +sudo: required +language: rust +rust: + - stable + - beta + - nightly +os: + - linux + - osx +matrix: + allow_failures: + - rust: nightly +addons: + apt: + packages: + - build-essential +before_install: + # Without rustfmt, bindgen puts everything on one line and any warnings dump so many logs they break Travis + # See https://github.com/rust-lang/rust-bindgen/issues/1600 + # optional, because nightlies may not have it + - rustup component add rustfmt || true + - if [[ $TRAVIS_OS_NAME == 'linux' ]]; then ./.travis/install_linux.sh; fi + - if [[ $TRAVIS_OS_NAME == 'osx' ]]; then brew update; fi + - if [[ $TRAVIS_OS_NAME == 'osx' ]]; then brew install yasm; fi + +script: | + if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + # Current Travis Ubuntu version uses libav which doesn't come with libswresample + cargo build --verbose --no-default-features --features "avcodec avfilter avformat avresample swscale" && + cargo test --verbose --no-default-features --features "avcodec avfilter avformat avresample swscale" + else + travis_wait cargo build --verbose --features "build" + cargo test --verbose --features "build" + fi + +after_failure: + - find /usr -type f 2>/dev/null | grep -E 'lib(avcodec/version|avcodec/avcodec).h$' | xargs -I THEFILE -- sh -c 'echo "=== THEFILE ==="; cat THEFILE' diff --git a/.travis/install_linux.sh b/.travis/install_linux.sh new file mode 100644 index 0000000..96dc5ec --- /dev/null +++ b/.travis/install_linux.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +sudo apt-get update -q +# From https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu +sudo apt-get -y --force-yes install autoconf automake build-essential libass-dev libfreetype6-dev libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev pkg-config texinfo zlib1g-dev +sudo apt-get install yasm +pushd ~ +git clone https://github.com/FFmpeg/FFmpeg.git +cd FFmpeg +git checkout release/3.2 +mkdir ~/FFmpeg-build +cd ~/FFmpeg-build +../FFmpeg/configure --disable-ffprobe --disable-ffserver --disable-doc --enable-avresample +make -j +sudo make install +make distclean +popd diff --git a/Cargo.toml b/Cargo.toml index 638ca1e..aabd9b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,10 @@ [package] -name = "ffmpeg-sys-next" +name = "ffmpeg-sys" version = "4.3.5" build = "build.rs" links = "ffmpeg" +readme = "README.md" authors = ["meh. ", "Zhiming Wang "] license = "WTFPL" @@ -22,7 +23,7 @@ libc = "0.2" num_cpus = "1.11" cc = "1.0" pkg-config = "0.3" -bindgen = { version = "0.54", default-features = false, features = ["runtime"] } +bindgen = { version = "0.56", default-features = false, features = ["runtime"] } [target.'cfg(target_env = "msvc")'.build-dependencies] vcpkg = "0.2" @@ -104,3 +105,4 @@ avresample = [] postproc = [] swresample = [] swscale = [] +lib-drm = [] diff --git a/README.md b/README.md index 7e4d1a1..a6c4ab8 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,21 @@ -[![ffmpeg-sys-next on crates.io](https://img.shields.io/crates/v/ffmpeg-sys-next?cacheSeconds=3600)](https://crates.io/crates/ffmpeg-sys-next) -[![build](https://github.com/zmwangx/rust-ffmpeg-sys/workflows/build/badge.svg)](https://github.com/zmwangx/rust-ffmpeg-sys/actions) +# Rust FFI bindings for ffmpeg -This is a fork of the abandoned [ffmpeg-sys](https://github.com/meh/rust-ffmpeg-sys) crate. You can find this crate as [ffmpeg-sys-next](https://crates.io/crates/ffmpeg-sys-next) on crates.io. +Low-level bindings for ffmpeg autogenerated with bindgen. This crate supports cross-compilation automatically. -This crate contains low level bindings to FFmpeg. You're probably interested in the high level bindings instead: [ffmpeg-next](https://github.com/zmwangx/rust-ffmpeg). +For higher-level library, see [ffmpeg crate](https://lib.rs/ffmpeg). + +## Building + +By default, the crate will search for ffmpeg v4 installed on the system. + +This crate can also download, build and statically link its own copy of ffmpeg if you enable `build` feature: + +```toml +[dependencies] +ffmpeg-sys = { version = "4", features = ["build"] } +``` + +# Versioning A word on versioning: major and minor versions track major and minor versions of FFmpeg, e.g. 4.2.x of this crate has been updated to support the 4.2.x series of FFmpeg. Patch level is reserved for bug fixes of this crate and does not track FFmpeg patch versions. @@ -28,3 +40,10 @@ In addition to feature flags declared in `Cargo.toml`, this crate performs vario - `ff_api_`, e.g. `ff_api_vaapi`, corresponding to whether their respective uppercase deprecation guards evaluate to true. - `ff_api__is_defined`, e.g. `ff_api_vappi_is_defined`, similar to above except these are enabled as long as the corresponding deprecation guards are defined. + +See [Cargo features](https://github.com/meh/rust-ffmpeg/blob/HEAD/Cargo.toml) to control which codecs are included. + +# Based On + +This combines bits from [meh/rust-ffmpeg-sys](https://github.com/meh/rust-ffmpeg-sys) and [zmwangx/rust-ffmpeg-sys](https://github.com/zmwangx/rust-ffmpeg-sys) because +when you have two almost identical projects that aren't actively maintained what you _really_ need is a third! \ No newline at end of file diff --git a/build.rs b/build.rs index 9a560e8..d769151 100644 --- a/build.rs +++ b/build.rs @@ -6,7 +6,7 @@ extern crate pkg_config; use std::env; use std::fs::{self, File}; use std::io::{self, BufRead, BufReader, Write}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::Command; use std::str; @@ -225,6 +225,7 @@ fn build() -> io::Result<()> { if env::var("DEBUG").is_ok() { configure.arg("--enable-debug"); configure.arg("--disable-stripping"); + configure.arg("--disable-optimizations"); } else { configure.arg("--disable-debug"); configure.arg("--enable-stripping"); @@ -604,15 +605,6 @@ fn search_include(include_paths: &[PathBuf], header: &str) -> String { format!("/usr/include/{}", header) } -fn maybe_search_include(include_paths: &[PathBuf], header: &str) -> Option { - let path = search_include(include_paths, header); - if fs::metadata(&path).is_ok() { - Some(path) - } else { - None - } -} - fn link_to_libraries(statik: bool) { let ffmpeg_ty = if statik { "static" } else { "dylib" }; for lib in LIBRARIES { @@ -627,6 +619,23 @@ fn link_to_libraries(statik: bool) { } fn main() { + // The long chain of `header` method calls for `bindgen::Builder` seems to be overflowing the default stack size on Windows. + // The main thread appears to have a hardcoded stack size which is unaffected by `RUST_MIN_STACK`. As a workaround, spawn a thread here with a stack size that works expermentally, and allow overriding it with `FFMPEG_SYS_BUILD_STACK_SIZE` just in case. + let stack_size = std::env::var("FFMPEG_SYS_BUILD_STACK_SIZE") + .map(|s| s.parse()) + .unwrap_or(Ok(3 * 1024 * 1024)); + eprintln!("Using stack size: {:?}", stack_size); + + std::thread::Builder::new() + .name("ffmpg-sys-build".into()) + .stack_size(stack_size.unwrap()) + .spawn(thread_main) + .unwrap() + .join() + .unwrap(); +} + +fn thread_main() { let statik = env::var("CARGO_FEATURE_STATIC").is_ok(); let include_paths: Vec = if env::var("CARGO_FEATURE_BUILD").is_ok() { @@ -694,10 +703,12 @@ fn main() { } // Fallback to pkg-config else { - pkg_config::Config::new() + let mut libavutil = pkg_config::Config::new() + .cargo_metadata(false) .statik(statik) .probe("libavutil") .unwrap(); + print_pkg_config_libs(statik, &libavutil); let libs = vec![ ("libavformat", "AVFORMAT"), @@ -710,18 +721,27 @@ fn main() { for (lib_name, env_variable_name) in libs.iter() { if env::var(format!("CARGO_FEATURE_{}", env_variable_name)).is_ok() { - pkg_config::Config::new() - .statik(statik) - .probe(lib_name) - .unwrap(); + print_pkg_config_libs( + statik, + &pkg_config::Config::new() + .cargo_metadata(false) + .statik(statik) + .probe(lib_name) + .unwrap(), + ); } } - pkg_config::Config::new() + let libavcodec = pkg_config::Config::new() + .cargo_metadata(false) .statik(statik) .probe("libavcodec") - .unwrap() - .include_paths + .unwrap(); + print_pkg_config_libs(statik, &libavcodec); + + let mut paths = libavcodec.include_paths; + paths.append(&mut libavutil.include_paths); + paths }; if statik && cfg!(target_os = "macos") { @@ -1208,7 +1228,6 @@ fn main() { .header(search_include(&include_paths, "libavutil/frame.h")) .header(search_include(&include_paths, "libavutil/hash.h")) .header(search_include(&include_paths, "libavutil/hmac.h")) - .header(search_include(&include_paths, "libavutil/hwcontext.h")) .header(search_include(&include_paths, "libavutil/imgutils.h")) .header(search_include(&include_paths, "libavutil/lfg.h")) .header(search_include(&include_paths, "libavutil/log.h")) @@ -1237,7 +1256,8 @@ fn main() { .header(search_include(&include_paths, "libavutil/timecode.h")) .header(search_include(&include_paths, "libavutil/twofish.h")) .header(search_include(&include_paths, "libavutil/avutil.h")) - .header(search_include(&include_paths, "libavutil/xtea.h")); + .header(search_include(&include_paths, "libavutil/xtea.h")) + .header(search_include(&include_paths, "libavutil/hwcontext.h")); if env::var("CARGO_FEATURE_POSTPROC").is_ok() { builder = builder.header(search_include(&include_paths, "libpostproc/postprocess.h")); @@ -1251,10 +1271,8 @@ fn main() { builder = builder.header(search_include(&include_paths, "libswscale/swscale.h")); } - if let Some(hwcontext_drm_header) = - maybe_search_include(&include_paths, "libavutil/hwcontext_drm.h") - { - builder = builder.header(hwcontext_drm_header); + if env::var("CARGO_FEATURE_LIB_DRM").is_ok() { + builder = builder.header(search_include(&include_paths, "libavutil/hwcontext_drm.h")) } // Finish the builder and generate the bindings. @@ -1268,3 +1286,48 @@ fn main() { .write_to_file(output().join("bindings.rs")) .expect("Couldn't write bindings!"); } + +fn print_pkg_config_libs(statik: bool, lib: &pkg_config::Library) { + let target = env::var("TARGET").unwrap(); + let is_msvc = target.contains("msvc"); + let is_apple = target.contains("apple"); + + for val in &lib.link_paths { + println!("cargo:rustc-link-search=native={}", val.display()); + } + for val in &lib.framework_paths { + println!("cargo:rustc-link-search=framework={}", val.display()); + } + for val in &lib.frameworks { + println!("cargo:rustc-link=framework={}", val); + } + + for val in &lib.libs { + if is_msvc && ["m", "c", "pthread"].contains(&val.as_str()) { + continue; + } + if is_apple && val == "stdc++" { + println!("cargo:rustc-link-lib=c++"); + continue; + } + + if statik && is_static_available(val, &lib.include_paths) { + println!("cargo:rustc-link-lib=static={}", val); + } else { + println!("cargo:rustc-link-lib={}", val); + } + } +} + +fn is_static_available(lib: &str, dirs: &[PathBuf]) -> bool { + let libname = format!("lib{}.a", lib); + let has = dirs + .iter() + .map(|d| d.as_path()) + .chain([Path::new("/usr/local/lib")].iter().copied()) + .any(|dir| dir.join(&libname).exists()); + if !has { + println!("cargo:warning=static {} not found", libname); + } + has +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..024bb7b --- /dev/null +++ b/shell.nix @@ -0,0 +1,21 @@ +let + mozilla = import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz); +in + +with (import { + overlays = [mozilla]; +}); + +mkShell { + name = "ffmpeg-sys"; + + buildInputs = [ + # For building. + clang rustChannels.stable.rust pkg-config ffmpeg + ]; + + RUST_BACKTRACE = 1; + RUSTFLAGS = "-C target-cpu=native"; + + LIBCLANG_PATH = "${llvmPackages.libclang}/lib"; +} diff --git a/src/avutil/rational.rs b/src/avutil/rational.rs index 641e09f..7809ee6 100644 --- a/src/avutil/rational.rs +++ b/src/avutil/rational.rs @@ -15,7 +15,7 @@ pub unsafe fn av_cmp_q(a: AVRational, b: AVRational) -> c_int { } else if b.den != 0 && a.den != 0 { 0 } else if a.num != 0 && b.num != 0 { - ((i64::from(a.num) >> 31) - (i64::from(b.num) >> 31)) as c_int + (a.num >> 31) - (b.num >> 31) } else { c_int::min_value() }