Initial commit
commit
75ce22c581
@ -0,0 +1 @@
|
||||
/target
|
@ -0,0 +1,447 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.54.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66c0bb6167449588ff70803f4127f0684f9063097eca5016f37eb52b92c2cf36"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
"cfg-if",
|
||||
"clang-sys",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
"peeking_take_while",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "0.29.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.33.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"atty",
|
||||
"bitflags",
|
||||
"strsim",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"humantime",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ffmpeg-sys-next"
|
||||
version = "4.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01bba495d04757f7a3e471b3e411759a8968571c4618235bc1c9e1099b4a84d1"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
"libc",
|
||||
"num_cpus",
|
||||
"pkg-config",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
|
||||
dependencies = [
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "5.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7f3fc75e3697059fb1bc465e3d8cca6cf92f56854f201158b3f9c77d5a3cfa0"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
"thread_local",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "transotf"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"ffmpeg-sys-next",
|
||||
"num-rational",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "transotf"
|
||||
version = "0.1.0"
|
||||
authors = ["eater <=@eater.me>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
num-rational = "0.3.0"
|
||||
byteorder = "1.3.4"
|
||||
|
||||
[dependencies.ffmpeg-sys-next]
|
||||
version = "4.3.0"
|
||||
features = ["avcodec", "avdevice", "avfilter", "avformat", "swresample", "swscale"]
|
||||
|
||||
#[dependencies.ffmpeg-next]
|
||||
#version = "4.3.0"
|
||||
#default-features = false
|
||||
#features = ["ffmpeg42","codec", "device", "filter", "format", "software-resampling", "software-scaling"]
|
@ -0,0 +1,17 @@
|
||||
use std::os::raw::c_void;
|
||||
|
||||
pub trait AsPtr<T> {
|
||||
fn as_ptr(&self) -> *const T;
|
||||
}
|
||||
|
||||
pub trait AsMutPtr<T> {
|
||||
fn as_mut_ptr(&self) -> *mut T;
|
||||
}
|
||||
|
||||
pub trait AsVoidPtr {
|
||||
fn as_void_ptr(&self) -> *const c_void;
|
||||
}
|
||||
|
||||
pub trait AsMutVoidPtr {
|
||||
fn as_mut_void_ptr(&self) -> *mut c_void;
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
use crate::av::as_ptr::{AsMutPtr, AsPtr};
|
||||
use ffmpeg_sys_next::{av_malloc, avio_alloc_context, avio_flush, AVIOContext};
|
||||
use std::cmp::min;
|
||||
use std::os::raw::c_void;
|
||||
use std::vec::Vec;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AVIO<T> {
|
||||
ctx: *mut AVIOContext,
|
||||
inner: Box<AVIOInner<T>>,
|
||||
}
|
||||
|
||||
impl<T> AVIO<T> {
|
||||
pub fn flush(&self) {
|
||||
unsafe {
|
||||
avio_flush(self.as_mut_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AVIOInner<T> {
|
||||
data: Box<T>,
|
||||
read: Option<fn(&mut T, i32) -> Vec<u8>>,
|
||||
write: Option<fn(&mut T, Vec<u8>)>,
|
||||
seek: Option<fn(&mut T, i64, i32)>,
|
||||
}
|
||||
|
||||
unsafe extern "C" fn read_packet_with_inner(
|
||||
ctx: *mut c_void,
|
||||
buffer: *mut u8,
|
||||
buffer_size: i32,
|
||||
) -> i32 {
|
||||
let mut avio: Box<AVIOInner<Box<dyn AVIOReader>>> = Box::from_raw(ctx.cast());
|
||||
let items = avio.data.read(buffer_size);
|
||||
let len = min(items.len(), buffer_size as usize);
|
||||
buffer.copy_from(items.as_ptr().cast(), len);
|
||||
len as i32
|
||||
}
|
||||
|
||||
unsafe extern "C" fn write_packet_with_inner(ctx: *mut c_void, buffer: *mut u8, size: i32) -> i32 {
|
||||
let mut avio: Box<AVIOInner<Box<dyn AVIOWriter>>> = Box::from_raw(ctx.cast());
|
||||
let mut byte_buffer = vec![0; size as usize];
|
||||
buffer.copy_to(byte_buffer.as_mut_ptr(), size as usize);
|
||||
let write_fn = avio.write.unwrap();
|
||||
write_fn(&mut avio.data, byte_buffer);
|
||||
std::mem::forget(avio);
|
||||
size
|
||||
}
|
||||
|
||||
unsafe extern "C" fn seek_with_inner(ctx: *mut c_void, offset: i64, whence: i32) -> i64 {
|
||||
println!("ok");
|
||||
let mut avio: Box<AVIOInner<Box<dyn AVIOSeekable>>> = Box::from_raw(ctx.cast());
|
||||
avio.data.seek(offset, whence);
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
impl<T> AVIO<T> {
|
||||
fn new(
|
||||
buffer_size: usize,
|
||||
obj: T,
|
||||
read: Option<fn(&mut T, i32) -> Vec<u8>>,
|
||||
write: Option<fn(&mut T, std::vec::Vec<u8>)>,
|
||||
seek: Option<fn(&mut T, i64, i32)>,
|
||||
) -> AVIO<T> {
|
||||
let mut opaque = Box::new(AVIOInner {
|
||||
data: Box::new(obj),
|
||||
read,
|
||||
write,
|
||||
seek,
|
||||
});
|
||||
let ctx = unsafe {
|
||||
let buf: *mut u8 = av_malloc(buffer_size).cast();
|
||||
let opaque_c: *mut AVIOInner<T> = &mut *opaque;
|
||||
|
||||
avio_alloc_context(
|
||||
buf,
|
||||
buffer_size as i32,
|
||||
if write.is_some() { 1 } else { 0 },
|
||||
opaque_c.cast(),
|
||||
if read.is_some() {
|
||||
Some(read_packet_with_inner)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
if write.is_some() {
|
||||
Some(write_packet_with_inner)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
if seek.is_some() {
|
||||
Some(seek_with_inner)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
AVIO { ctx, inner: opaque }
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &T {
|
||||
self.inner.data.as_ref()
|
||||
}
|
||||
|
||||
pub fn inner_mut(&mut self) -> &mut T {
|
||||
self.inner.data.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsMutPtr<AVIOContext> for AVIO<T> {
|
||||
fn as_mut_ptr(&self) -> *mut AVIOContext {
|
||||
self.ctx
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsPtr<AVIOContext> for AVIO<T> {
|
||||
fn as_ptr(&self) -> *const AVIOContext {
|
||||
self.ctx
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AVIOReader + AVIOWriter + AVIOSeekable> AVIO<T> {
|
||||
pub fn duplex_with_seek(item: T) -> AVIO<T> {
|
||||
AVIO::new(4096, item, Some(T::read), Some(T::write), Some(T::seek))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AVIOReader + AVIOWriter> AVIO<T> {
|
||||
pub fn duplex(item: T) -> AVIO<T> {
|
||||
AVIO::new(4096, item, Some(T::read), Some(T::write), None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AVIOReader + AVIOSeekable> AVIO<T> {
|
||||
pub fn reader_with_seek(item: T) -> AVIO<T> {
|
||||
AVIO::new(4096, item, Some(T::read), None, Some(T::seek))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AVIOReader> AVIO<T> {
|
||||
pub fn reader(item: T) -> AVIO<T> {
|
||||
AVIO::new(4096, item, Some(T::read), None, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AVIOWriter + AVIOSeekable> AVIO<T> {
|
||||
pub fn writer_with_seek(item: T) -> AVIO<T> {
|
||||
AVIO::new(4096, item, None, Some(T::write), Some(T::seek))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AVIOWriter> AVIO<T> {
|
||||
pub fn writer(item: T) -> AVIO<T> {
|
||||
AVIO::new(4096, item, None, Some(T::write), None)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AVIOSeekable {
|
||||
fn seek(&mut self, offset: i64, whence: i32);
|
||||
}
|
||||
|
||||
pub trait AVIOWriter {
|
||||
fn write(&mut self, buffer: Vec<u8>);
|
||||
}
|
||||
|
||||
pub trait AVIOReader {
|
||||
fn read(&mut self, length: i32) -> Vec<u8>;
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
use crate::av::{verify_response, Rational};
|
||||
use ffmpeg_sys_next::{
|
||||
av_malloc, avcodec_parameters_copy, AVCodecID, AVCodecParameters, AVPixelFormat, AVRational,
|
||||
};
|
||||
use std::mem;
|
||||
|
||||
pub struct CodecParameters(pub *mut AVCodecParameters);
|
||||
|
||||
impl CodecParameters {
|
||||
#[inline]
|
||||
pub fn as_ptr(&self) -> *const AVCodecParameters {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_mut_ptr(&self) -> *mut AVCodecParameters {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn codec(&self) -> AVCodecID {
|
||||
unsafe { (*self.as_ptr()).codec_id }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn width(&self) -> i32 {
|
||||
unsafe { (*self.as_ptr()).width }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_width(&self, width: i32) {
|
||||
unsafe { (*self.as_mut_ptr()).width = width }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn height(&self) -> i32 {
|
||||
unsafe { (*self.as_ptr()).height }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_height(&self, height: i32) {
|
||||
unsafe { (*self.as_mut_ptr()).height = height }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pixel_format(&self) -> AVPixelFormat {
|
||||
unsafe { mem::transmute((*self.as_ptr()).format) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_pixel_format(&self, pixel_format: AVPixelFormat) {
|
||||
unsafe { (*self.as_mut_ptr()).format = mem::transmute(pixel_format) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn extra_data(&self) -> Vec<u8> {
|
||||
unsafe {
|
||||
let extra_data_size = (*self.as_ptr()).extradata_size as usize;
|
||||
let mut extra_data = vec![0u8; extra_data_size];
|
||||
(*self.as_ptr())
|
||||
.extradata
|
||||
.copy_to(extra_data.as_mut_ptr(), extra_data_size);
|
||||
|
||||
extra_data
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_extra_data(&self, extra_data: &Vec<u8>) {
|
||||
unsafe {
|
||||
(*self.as_mut_ptr()).extradata_size = 0;
|
||||
let new_extra_data: *mut u8 = av_malloc(extra_data.len() + 64).cast();
|
||||
new_extra_data.copy_from(extra_data.as_ptr(), extra_data.len());
|
||||
(*self.as_mut_ptr()).extradata = new_extra_data;
|
||||
(*self.as_mut_ptr()).extradata_size = extra_data.len() as i32;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sample_aspect_ratio(&self) -> AVRational {
|
||||
unsafe { (*self.as_ptr()).sample_aspect_ratio }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_sample_aspect_ratio(&self, sample_aspect_ratio: impl Rational) {
|
||||
unsafe { (*self.as_mut_ptr()).sample_aspect_ratio = sample_aspect_ratio.to_av() }
|
||||
}
|
||||
|
||||
pub fn copy_to(&self, to: &CodecParameters) -> Result<(), String> {
|
||||
Self::copy(&self, to)
|
||||
}
|
||||
|
||||
pub fn copy_from(&self, from: &CodecParameters) -> Result<(), String> {
|
||||
Self::copy(from, &self)
|
||||
}
|
||||
|
||||
pub fn copy(from: &CodecParameters, to: &CodecParameters) -> Result<(), String> {
|
||||
verify_response("Failed to copy codec parameters", unsafe {
|
||||
avcodec_parameters_copy(to.as_mut_ptr(), from.as_ptr())
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
use crate::av::as_ptr::AsMutPtr;
|
||||
use crate::av::frame::Frame;
|
||||
use crate::av::packet::Packet;
|
||||
use crate::av::stream::Stream;
|
||||
use crate::av::xcoder::{process_return, XCoder};
|
||||
use crate::av::{decoder_from_name, verify_response};
|
||||
use crate::av_err2str;
|
||||
use ffmpeg_sys_next::{
|
||||
avcodec_parameters_to_context, avcodec_receive_frame, avcodec_send_packet, AVCodecContext,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Decoder(pub *mut AVCodecContext);
|
||||
|
||||
impl Decoder {
|
||||
pub fn send_packet(&self, packet: &Packet) -> Result<(), String> {
|
||||
verify_response("Failed sending packet to decoder", unsafe {
|
||||
avcodec_send_packet(self.0, packet.as_mut_ptr())
|
||||
})
|
||||
.and(Ok(()))
|
||||
}
|
||||
|
||||
pub fn read_frame(&self) -> Result<Option<Frame>, String> {
|
||||
let frame = Frame::alloc();
|
||||
if !process_return("Failed reading frame from decoder", unsafe {
|
||||
avcodec_receive_frame(self.as_mut_ptr(), frame.as_mut_ptr())
|
||||
})? {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(frame))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XCoder for Decoder {
|
||||
fn from_name(name: &str) -> Option<Self> {
|
||||
decoder_from_name(name).map(|codec| Decoder(codec))
|
||||
}
|
||||
|
||||
fn as_mut_ptr(&self) -> *mut AVCodecContext {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn configure(&self, stream: &Stream) -> Result<(), String> {
|
||||
let resp =
|
||||
unsafe { avcodec_parameters_to_context(self.as_mut_ptr(), (*stream.0).codecpar) };
|
||||
|
||||
if resp < 0 {
|
||||
return Err(format!(
|
||||
"Failed configuring decoder from stream (code {}: {})",
|
||||
resp,
|
||||
av_err2str(resp)
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
use crate::av::decoder::Decoder;
|
||||
use crate::av::xcoder::XCoder;
|
||||
use crate::av::{decoders_for_codec, FfmpegCodec};
|
||||
use ffmpeg_sys_next::AVCodecID;
|
||||
|
||||
pub struct DecoderSelector {
|
||||
decoders: Vec<FfmpegCodec>,
|
||||
}
|
||||
|
||||
impl DecoderSelector {
|
||||
pub fn for_codec_id(codec_id: AVCodecID) -> DecoderSelector {
|
||||
DecoderSelector {
|
||||
decoders: decoders_for_codec(codec_id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select(
|
||||
&self,
|
||||
hwaccel: Option<bool>,
|
||||
config_fn: impl Fn(&Decoder) -> Result<(), String>,
|
||||
) -> Result<Decoder, String> {
|
||||
for decoder in &self.decoders {
|
||||
if !hwaccel.is_none() && hwaccel != Some(decoder.hwaccel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(Ok(decoder)) =
|
||||
Decoder::from_name(&decoder.name).map(|d| -> Result<Decoder, String> {
|
||||
config_fn(&d)?;
|
||||
Ok(d)
|
||||
})
|
||||
{
|
||||
return Ok(decoder);
|
||||
}
|
||||
}
|
||||
|
||||
Err("No working decoders found".to_string())
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
use ffmpeg_sys_next::{av_dict_free, av_dict_get, av_dict_set, av_dict_set_int, AVDictionary};
|
||||
use std::ffi::CString;
|
||||
use std::ptr::{null, null_mut};
|
||||
|
||||
pub struct Dictionary {
|
||||
pub ptr: *mut AVDictionary,
|
||||
pub owned: bool,
|
||||
}
|
||||
|
||||
impl Dictionary {
|
||||
#[inline]
|
||||
pub fn as_ptr(&self) -> *const AVDictionary {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_mut_ptr(&mut self) -> *mut AVDictionary {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
pub fn new() -> Dictionary {
|
||||
Dictionary {
|
||||
ptr: null_mut(),
|
||||
owned: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disown(&mut self) {
|
||||
self.owned = false;
|
||||
}
|
||||
|
||||
pub fn own(&mut self) {
|
||||
self.owned = true;
|
||||
}
|
||||
|
||||
pub fn set(&mut self, key: &str, value: &str) {
|
||||
let key_c = CString::new(key).unwrap();
|
||||
let value_c = CString::new(value).unwrap();
|
||||
unsafe {
|
||||
av_dict_set(&mut self.ptr, key_c.as_ptr(), value_c.as_ptr(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(&mut self, key: &str) {
|
||||
let key_c = CString::new(key).unwrap();
|
||||
unsafe {
|
||||
av_dict_set(&mut self.ptr, key_c.as_ptr(), null(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &str) -> Option<String> {
|
||||
let key_c = CString::new(key).unwrap();
|
||||
unsafe {
|
||||
let entry = av_dict_get(self.ptr, key_c.as_ptr(), null(), 0);
|
||||
if entry.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
CString::from_raw((*entry).value)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Dictionary {
|
||||
fn drop(&mut self) {
|
||||
if !self.owned {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
av_dict_free(&mut self.ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DictionaryInt for Dictionary {
|
||||
fn set(&mut self, key: &str, value: i64) {
|
||||
let key_c = CString::new(key).unwrap();
|
||||
unsafe {
|
||||
av_dict_set_int(&mut self.ptr, key_c.as_ptr(), value, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DictionaryInt {
|
||||
fn set(&mut self, key: &str, value: i64);
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
use crate::av::as_ptr::{AsMutPtr, AsPtr};
|
||||
use ffmpeg_sys_next::{avio_close_dyn_buf, avio_open_dyn_buf, AVIOContext};
|
||||
use std::ptr::null_mut;
|
||||
|
||||
struct Dynbuf {
|
||||
ptr: *mut AVIOContext,
|
||||
freed: bool,
|
||||
}
|
||||
|
||||
impl Dynbuf {
|
||||
fn new() -> Dynbuf {
|
||||
let mut ctx = null_mut();
|
||||
unsafe {
|
||||
avio_open_dyn_buf(&mut ctx);
|
||||
}
|
||||
|
||||
Dynbuf {
|
||||
ptr: ctx,
|
||||
freed: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&mut self) -> Vec<u8> {
|
||||
let mut buffer = null_mut();
|
||||
self.freed = true;
|
||||
unsafe {
|
||||
let size = avio_close_dyn_buf(self.ptr, &mut buffer);
|
||||
Vec::from_raw_parts(buffer, size as usize, size as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Dynbuf {
|
||||
fn drop(&mut self) {
|
||||
if self.freed {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
avio_close_dyn_buf(self.ptr, null_mut());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsPtr<AVIOContext> for Dynbuf {
|
||||
fn as_ptr(&self) -> *const AVIOContext {
|
||||
self.ptr
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMutPtr<AVIOContext> for Dynbuf {
|
||||
fn as_mut_ptr(&self) -> *mut AVIOContext {
|
||||
self.ptr
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
use crate::av::as_ptr::AsMutPtr;
|
||||
use crate::av::frame::Frame;
|
||||
use crate::av::packet::Packet;
|
||||
use crate::av::scaler::Scaler;
|
||||
use crate::av::stream::Stream;
|
||||
use crate::av::xcoder::{process_return, XCoder};
|
||||
use crate::av::{encoder_from_name, verify_response};
|
||||
use crate::av_err2str;
|
||||
use ffmpeg_sys_next::{
|
||||
avcodec_parameters_from_context, avcodec_receive_packet, avcodec_send_frame, sws_freeContext,
|
||||
AVCodecContext,
|
||||
};
|
||||
|
||||
pub struct Encoder(pub *mut AVCodecContext);
|
||||
|
||||
impl Encoder {
|
||||
pub fn send_video_frame(&self, frame: &Frame) -> Result<(), String> {
|
||||
if frame.width() != self.width()
|
||||
|| frame.height() != self.height()
|
||||
|| frame.pixel_format() != self.pixel_format()
|
||||
{
|
||||
return Err(format!(
|
||||
"Frame and encoder data differ (frame: {}x{} {:?}, encoder: {}x{} {:?})",
|
||||
frame.width(),
|
||||
frame.height(),
|
||||
frame.pixel_format(),
|
||||
self.width(),
|
||||
self.height(),
|
||||
self.pixel_format()
|
||||
));
|
||||
}
|
||||
|
||||
self.send_frame(frame)
|
||||
}
|
||||
|
||||
pub fn send_audio_frame(&self, frame: &Frame) -> Result<(), String> {
|
||||
// if frame.sample_format() != self.sample_format() {
|
||||
// return Err(format!(
|
||||
// "Frame and encoder data differ (frame: {:?}, encoder: {:?})",
|
||||
// frame.sample_format(),
|
||||
// self.sample_format()
|
||||
// ));
|
||||
// }
|
||||
|
||||
println!("{:?} / {}", frame.nb_samples(), self.frame_size());
|
||||
|
||||
self.send_frame(frame)
|
||||
}
|
||||
|
||||
pub fn send_frame(&self, frame: &Frame) -> Result<(), String> {
|
||||
verify_response("Failed sending frame to encoder", unsafe {
|
||||
avcodec_send_frame(self.0, frame.as_mut_ptr())
|
||||
})
|
||||
.and(Ok(()))
|
||||
}
|
||||
|
||||
pub fn read_packet(&self) -> Result<Option<Packet>, String> {
|
||||
let packet = Packet::alloc();
|
||||
let resp = unsafe { avcodec_receive_packet(self.as_mut_ptr(), packet.as_mut_ptr()) };
|
||||
|
||||
if !process_return("Failed to read packet from encoder", resp)? {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(packet))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XCoder for Encoder {
|
||||
fn from_name(name: &str) -> Option<Self> {
|
||||
encoder_from_name(name).map(|codec| Encoder(codec))
|
||||
}
|
||||
|
||||
fn as_mut_ptr(&self) -> *mut AVCodecContext {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn configure(&self, stream: &Stream) -> Result<(), String> {
|
||||
let resp =
|
||||
unsafe { avcodec_parameters_from_context((*stream.0).codecpar, self.as_mut_ptr()) };
|
||||
|
||||
if resp < 0 {
|
||||
return Err(format!(
|
||||
"Failed configuring decoder from stream (code {}: {})",
|
||||
resp,
|
||||
av_err2str(resp)
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Scaler {
|
||||
fn drop(&mut self) {
|
||||
unsafe { sws_freeContext(self.as_mut_ptr()) }
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
use crate::av::encoder::Encoder;
|
||||
use crate::av::xcoder::XCoder;
|
||||
use crate::av::{encoders_for_codec, FfmpegCodec};
|
||||
use ffmpeg_sys_next::AVCodecID;
|
||||
|
||||
pub struct EncoderSelector {
|
||||
encoders: Vec<FfmpegCodec>,
|
||||
}
|
||||
|
||||
impl EncoderSelector {
|
||||
pub fn for_codec_id(codec_id: AVCodecID) -> EncoderSelector {
|
||||
EncoderSelector {
|
||||
encoders: encoders_for_codec(codec_id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select(
|
||||
&self,
|
||||
hwaccel: Option<bool>,
|
||||
config_fn: impl Fn(&Encoder) -> Result<(), String>,
|
||||
) -> Result<Encoder, String> {
|
||||
for encoder in &self.encoders {
|
||||
if !hwaccel.is_none() && hwaccel != Some(encoder.hwaccel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(Ok(encoder)) =
|
||||
Encoder::from_name(&encoder.name).map(|d| -> Result<Encoder, String> {
|
||||
config_fn(&d)?;
|
||||
Ok(d)
|
||||
})
|
||||
{
|
||||
return Ok(encoder);
|
||||
}
|
||||
}
|
||||
|
||||
Err("No working encoders found".to_string())
|
||||
}
|
||||
}
|
@ -0,0 +1,282 @@
|
||||
use crate::av::as_ptr::{AsMutPtr, AsMutVoidPtr, AsPtr, AsVoidPtr};
|
||||
use crate::av::avio::{AVIOWriter, AVIO};
|
||||
use crate::av::dictionary::Dictionary;
|
||||
use crate::av::options::Options;
|
||||
use crate::av::packet::Packet;
|
||||
use crate::av::stream::Stream;
|
||||
use crate::av::{decoder_codec_from_name, verify_response};
|
||||
use ffmpeg_sys_next::{
|
||||
av_interleaved_write_frame, av_read_frame, av_write_frame, av_write_trailer,
|
||||
avformat_alloc_context, avformat_alloc_output_context2, avformat_find_stream_info,
|
||||
avformat_init_output, avformat_new_stream, avformat_open_input, avformat_write_header,
|
||||
avio_open2, AVFormatContext, AVMediaType, AVIO_FLAG_WRITE,
|
||||
};
|
||||
use std::ffi::CString;
|
||||
use std::fmt::{Debug, Error, Formatter};
|
||||
use std::os::raw::{c_int, c_void};
|
||||
use std::ptr::{null, null_mut};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Format<T> {
|
||||
pub ctx: *mut AVFormatContext,
|
||||
pub avio: Option<AVIO<T>>,
|
||||
}
|
||||
|
||||
impl<T> Debug for Format<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||
f.debug_struct("Format<_>").field("ctx", &self.ctx).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Format<T> {
|
||||
pub fn flags(&self) -> c_int {
|
||||
unsafe { (*self.as_ptr()).flags }
|
||||
}
|
||||
|
||||
pub fn set_flags(&self, flags: c_int) {
|
||||
unsafe { (*self.as_mut_ptr()).flags = flags }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Format<T> {
|
||||
pub fn stream(&self, stream_type: AVMediaType, index: Option<i32>) -> Option<Stream> {
|
||||
unsafe {
|
||||
let ctx = self.ctx;
|
||||
let mut stream = (*ctx).streams;
|
||||
|
||||
for _ in 0..(*ctx).nb_streams {
|
||||
let curr_stream = *stream;
|
||||
if (*(*curr_stream).codecpar).codec_type == stream_type {
|
||||
if index.is_none() || Some((*curr_stream).index) == index {
|
||||
return Some(Stream(curr_stream));
|
||||
}
|
||||
}
|
||||
|
||||
stream = stream.add(1)
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn as_mut_ptr(&self) -> *mut AVFormatContext {
|
||||
self.ctx
|
||||
}
|
||||
|
||||
pub fn next_packet(&self) -> Option<Packet> {
|
||||
let packet = Packet::alloc();
|
||||
return if unsafe { av_read_frame(self.as_mut_ptr(), packet.as_mut_ptr()) } >= 0 {
|
||||
Some(packet)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
|
||||
pub fn write_header(&self, options: Option<Dictionary>) -> Result<(), String> {
|
||||
verify_response("failed to write header to output", unsafe {
|
||||
avformat_write_header(
|
||||
self.as_mut_ptr(),
|
||||
if let Some(mut options) = options {
|
||||
&mut options.as_mut_ptr()
|
||||
} else {
|
||||
null_mut()
|
||||
},
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_trailer(&self) -> Result<(), String> {
|
||||
verify_response("failed to write trailer to output", unsafe {
|
||||
av_write_trailer(self.as_mut_ptr())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_packet_null(&self) -> Result<(), String> {
|
||||
verify_response("failed to write to output", unsafe {
|
||||
av_write_frame(self.as_mut_ptr(), null_mut())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_packet_interleaved(&self, packet: &Packet) -> Result<(), String> {
|
||||
verify_response("failed to write interleaved to output", unsafe {
|
||||
av_interleaved_write_frame(self.as_mut_ptr(), packet.as_mut_ptr())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_packet(&self, packet: &Packet) -> Result<(), String> {
|
||||
verify_response("failed to write to output", unsafe {
|
||||
av_write_frame(self.as_mut_ptr(), packet.as_mut_ptr())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new_stream(&self, codec: &str) -> Result<Stream, String> {
|
||||
let codec =
|
||||
decoder_codec_from_name(codec).ok_or(format!("No codec found with name {}", codec))?;
|
||||
let stream = unsafe { avformat_new_stream(self.ctx, codec) };
|
||||
|
||||
Ok(Stream(stream))
|
||||
}
|
||||
|
||||
pub fn avio_inner(&self) -> Option<&T> {
|
||||
self.avio.as_ref().map(|x| x.inner())
|
||||
}
|
||||
|
||||
pub fn avio_inner_mut(&mut self) -> Option<&mut T> {
|
||||
self.avio.as_mut().map(|x| x.inner_mut())
|
||||
}
|
||||
|
||||
pub fn init_output(&self, mut options: Dictionary) -> Result<(), String> {
|
||||
verify_response("Failed to init output", unsafe {
|
||||
avformat_init_output(self.as_mut_ptr(), &mut options.as_mut_ptr())
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AVIOWriter> Format<T> {
|
||||
pub fn output_avio<'a>(avio: AVIO<T>, format: &str) -> Result<Format<T>, String> {
|
||||
let mut context = unsafe { avformat_alloc_context() };
|
||||
let format_c = CString::new(format).unwrap();
|
||||
|
||||
verify_response("Failed to open output with AVIO", unsafe {
|
||||
avformat_alloc_output_context2(&mut context, null_mut(), format_c.as_ptr(), null_mut())
|
||||
})?;
|
||||
|
||||
unsafe {
|
||||
(*context).pb = avio.as_mut_ptr();
|
||||
}
|
||||
|
||||
Ok(Format {
|
||||
ctx: context,
|
||||
avio: Some(avio),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type FS = ();
|
||||
|
||||
impl Format<FS> {
|
||||
pub fn output(
|
||||
path: &str,
|
||||
format: Option<&str>,
|
||||
options: Option<Dictionary>,
|
||||
) -> Result<Format<FS>, String> {
|
||||
let mut context = unsafe { avformat_alloc_context() };
|
||||
|
||||
let path_c = CString::new(path).unwrap();
|
||||
let format_c = if let Some(fmt) = format {
|
||||
CString::new(fmt).unwrap()
|
||||
} else {
|
||||
CString::default()
|
||||
};
|
||||
|
||||
let resp = unsafe {
|
||||
avformat_alloc_output_context2(
|
||||
&mut context,
|
||||
null_mut(),
|
||||
if format.is_none() {
|
||||
null()
|
||||
} else {
|
||||
format_c.as_ptr()
|
||||
},
|
||||
path_c.as_ptr(),
|
||||
)
|
||||
};
|
||||
|
||||
if resp < 0 {
|
||||
return Err(format!(
|
||||
"Failed to open output (path: {}, format: {:?})",
|
||||
path, format
|
||||
));
|
||||
}
|
||||
|
||||
verify_response("Failed to open output", unsafe {
|
||||
avio_open2(
|
||||
&mut (*context).pb,
|
||||
path_c.as_ptr(),
|
||||
AVIO_FLAG_WRITE,
|
||||
null(),
|
||||
if let Some(mut options) = options {
|
||||
options.disown();
|
||||
&mut options.as_mut_ptr()
|
||||
} else {
|
||||
null_mut()
|
||||
},
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(Format {
|
||||
ctx: context,
|
||||
avio: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open(path: &str, options: Option<Dictionary>) -> Result<Format<FS>, String> {
|
||||
unsafe {
|
||||
let mut context = avformat_alloc_context();
|
||||
let c_path = CString::new(path).unwrap();
|
||||
verify_response(
|
||||
"failed to open file",
|
||||
avformat_open_input(
|
||||
&mut context,
|
||||
c_path.as_ptr(),
|
||||
null_mut(),
|
||||
if let Some(mut options) = options {
|
||||
&mut options.as_mut_ptr()
|
||||
} else {
|
||||
null_mut()
|
||||
},
|
||||
),
|
||||
)?;
|
||||
verify_response(
|
||||
"failed to find stream info",
|
||||
avformat_find_stream_info(context, null_mut()),
|
||||
)?;
|
||||
|
||||
Ok(Format {
|
||||
ctx: context,
|
||||
avio: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsPtr<AVFormatContext> for Format<T> {
|
||||
#[inline]
|
||||
fn as_ptr(&self) -> *const AVFormatContext {
|
||||
self.ctx
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsMutPtr<AVFormatContext> for Format<T> {
|
||||
#[inline]
|
||||
fn as_mut_ptr(&self) -> *mut AVFormatContext {
|
||||
self.ctx
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsVoidPtr for Format<T> {
|
||||
#[inline]
|
||||
fn as_void_ptr(&self) -> *const c_void {
|
||||
self.as_ptr().cast()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsMutVoidPtr for Format<T> {
|
||||
#[inline]
|
||||
fn as_mut_void_ptr(&self) -> *mut c_void {
|
||||
self.as_mut_ptr().cast()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Options for Format<T> {}
|
@ -0,0 +1,140 @@
|
||||
use crate::av::as_ptr::{AsMutPtr, AsPtr};
|
||||
use crate::av::verify_response;
|
||||
use ffmpeg_sys_next::{
|
||||
av_frame_alloc, av_frame_get_buffer, av_frame_unref, AVFrame, AVPictureType, AVPixelFormat,
|
||||
AVSampleFormat,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Frame {
|
||||
ptr: *mut AVFrame,
|
||||
owned: bool,
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
pub fn alloc() -> Frame {
|
||||
let frame = unsafe { av_frame_alloc() };
|
||||
if frame.is_null() {
|
||||
panic!("Can't alloc frame");
|
||||
}
|
||||
|
||||
Frame {
|
||||
ptr: frame,
|
||||
owned: true,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn own(&mut self) {
|
||||
self.owned = true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn disown(&mut self) {
|
||||
self.owned = false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn width(&self) -> i32 {
|
||||
unsafe { (*self.ptr).width }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn height(&self) -> i32 {
|
||||
unsafe { (*self.ptr).height }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pict_type(&self) -> AVPictureType {
|
||||
unsafe { (*self.ptr).pict_type }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_pict_type(&self, pict_type: AVPictureType) {
|
||||
unsafe { (*self.ptr).pict_type = pict_type }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_width(&self, width: i32) {
|
||||
unsafe { (*self.ptr).width = width }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_height(&self, height: i32) {
|
||||
unsafe { (*self.ptr).height = height }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_pixel_format(&self, pixel_format: AVPixelFormat) {
|
||||
unsafe { (*self.ptr).format = std::mem::transmute(pixel_format) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pixel_format(&self) -> AVPixelFormat {
|
||||
unsafe { std::mem::transmute::<_, AVPixelFormat>((*self.ptr).format) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_sample_format(&self, sample_format: AVSampleFormat) {
|
||||
unsafe { (*self.ptr).format = std::mem::transmute(sample_format) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sample_format(&self) -> AVSampleFormat {
|
||||
unsafe { std::mem::transmute::<_, AVSampleFormat>((*self.ptr).format) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn nb_samples(&self) -> i32 {
|
||||
unsafe { (*self.ptr).nb_samples }
|
||||
}
|
||||
|
||||
pub fn allocate_video(
|
||||
&mut self,
|
||||
pixel_format: AVPixelFormat,
|
||||
width: i32,
|
||||
height: i32,
|
||||
) -> Result<(), String> {
|
||||
self.set_pixel_format(pixel_format);
|
||||
self.set_width(width);
|
||||
self.set_height(height);
|
||||
|
||||
verify_response("Failed to allocate frame", unsafe {
|
||||
av_frame_get_buffer(self.ptr, 32)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pts(&self) -> i64 {
|
||||
unsafe { (*self.ptr).pts }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_pts(&self, pts: i64) {
|
||||
unsafe { (*self.ptr).pts = pts }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsPtr<AVFrame> for Frame {
|
||||
#[inline]
|
||||
fn as_ptr(&self) -> *const AVFrame {
|
||||
self.ptr
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMutPtr<AVFrame> for Frame {
|
||||
#[inline]
|
||||
fn as_mut_ptr(&self) -> *mut AVFrame {
|
||||
self.ptr
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Frame {
|
||||
fn drop(&mut self) {
|
||||
if !self.owned {
|
||||
return;
|
||||
}
|
||||
unsafe { av_frame_unref(self.ptr) }
|
||||
}
|
||||
}
|
@ -0,0 +1,334 @@
|
||||
use crate::av_err2str;
|
||||
use ffmpeg_sys_next::AVCodecID::AV_CODEC_ID_HEVC;
|
||||
use ffmpeg_sys_next::{
|
||||
av_codec_is_decoder, av_codec_is_encoder, av_codec_next, av_inv_q, av_register_all,
|
||||
avcodec_alloc_context3, avcodec_find_decoder_by_name, avcodec_find_encoder_by_name, AVCodec,
|
||||
AVCodecContext, AVCodecID, AVRational, AV_CODEC_CAP_HARDWARE,
|
||||
};
|
||||
use num_rational::Ratio;
|
||||
use std::any::type_name;
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::fmt::Debug;
|
||||
use std::option::Option::Some;
|
||||
use std::os::raw::c_int;
|
||||
use std::ptr::null;
|
||||
|
||||
pub mod as_ptr;
|
||||
pub mod avio;
|
||||
pub mod codec_parameters;
|
||||
pub mod decoder;
|
||||
pub mod decoder_selector;
|
||||
pub mod dictionary;
|
||||
pub mod dynbuf;
|
||||
pub mod encoder;
|
||||
pub mod encoder_selector;
|
||||
pub mod format;
|
||||
pub mod frame;
|
||||
pub mod mov_avc;
|
||||
pub mod mov_mux;
|
||||
pub mod options;
|
||||
pub mod packet;
|
||||
pub mod resampler;
|
||||
pub mod scaler;
|
||||
pub mod stream;
|
||||
pub mod xcoder;
|
||||
|
||||
static mut CACHE: Option<FfmpegInfoCache> = None;
|
||||
|
||||
pub fn get_best_encoder(codec: AVCodecID, hw_accel: Option<bool>) -> Option<String> {
|
||||
let encoders = get_cache().encoders.get(&codec)?;
|
||||
get_best_codec(encoders, hw_accel)
|
||||
}
|
||||
|
||||
pub fn get_best_codec(
|
||||
codecs: &Vec<FfmpegCodec>,
|
||||
should_be_hw_accel: Option<bool>,
|
||||
) -> Option<String> {
|
||||
let hw_accel = codecs.iter().find(|x| x.hwaccel);
|
||||
if hw_accel.is_some() && should_be_hw_accel != Some(false) {
|
||||
hw_accel
|
||||
} else if should_be_hw_accel != Some(true) {
|
||||
codecs.iter().find(|x| !x.hwaccel)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.map(|x| x.name.clone())
|
||||
}
|
||||
|
||||
pub fn get_best_decoder(codec: AVCodecID, hw_accel: Option<bool>) -> Option<String> {
|
||||
let decoders = get_cache().decoders.get(&codec)?;
|
||||
get_best_codec(decoders, hw_accel)
|
||||
}
|
||||
|
||||
pub fn decoder_codec_from_name(name: &str) -> Option<*mut AVCodec> {
|
||||
let codec = unsafe {
|
||||
let name = CString::new(name).unwrap();
|
||||
avcodec_find_decoder_by_name(name.as_ptr())
|
||||
};
|
||||
|
||||
if codec.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(codec)
|
||||
}
|
||||
|
||||
pub fn decoder_from_name(name: &str) -> Option<*mut AVCodecContext> {
|
||||
let codec = decoder_codec_from_name(name)?;
|
||||
|
||||
let xcoder = unsafe { avcodec_alloc_context3(codec) };
|
||||
if xcoder.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(xcoder)
|
||||
}
|
||||
|
||||
pub fn encoders_for_codec(codec_id: AVCodecID) -> Vec<FfmpegCodec> {
|
||||
return if let Some(codecs) = get_cache().encoders.get(&codec_id) {
|
||||
let mut codecs: Vec<FfmpegCodec> = codecs.to_vec();
|
||||
codecs.sort_by_key(|x| !x.hwaccel);
|
||||
codecs
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
}
|
||||
|
||||
pub fn decoders_for_codec(codec_id: AVCodecID) -> Vec<FfmpegCodec> {
|
||||
return if let Some(codecs) = get_cache().decoders.get(&codec_id) {
|
||||
let mut codecs: Vec<FfmpegCodec> = codecs.to_vec();
|
||||
codecs.sort_by_key(|x| !x.hwaccel);
|
||||
codecs
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
}
|
||||
|
||||
pub fn encoder_codec_from_name(name: &str) -> Option<*mut AVCodec> {
|
||||
let codec = unsafe {
|
||||
let name = CString::new(name).unwrap();
|
||||
avcodec_find_encoder_by_name(name.as_ptr())
|
||||
};
|
||||
|
||||
if codec.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(codec)
|
||||
}
|
||||
|
||||
pub fn encoder_from_name(name: &str) -> Option<*mut AVCodecContext> {
|
||||
let codec = encoder_codec_from_name(name)?;
|
||||
|
||||
let encoder = unsafe { avcodec_alloc_context3(codec) };
|
||||
if encoder.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(encoder)
|
||||
}
|
||||
|
||||
pub fn get_cache() -> &'static FfmpegInfoCache {
|
||||
unsafe {
|
||||
return CACHE.get_or_insert_with(|| create_cache());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_cache() -> FfmpegInfoCache {
|
||||
let mut info = FfmpegInfoCache::default();
|
||||
unsafe {
|
||||
let mut desc = av_codec_next(null());
|
||||
while !desc.is_null() {
|
||||
let p_desc = *desc;
|
||||
let name = CStr::from_ptr(p_desc.name).to_str().unwrap().to_string();
|
||||
let long_name = CStr::from_ptr(p_desc.long_name)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
if av_codec_is_decoder(desc) == 1 {
|
||||
info.decoders
|
||||
.entry(p_desc.id)
|
||||
.or_default()
|
||||
.push(FfmpegCodec {
|
||||
name: name.clone(),
|
||||
long_name: long_name.clone(),
|
||||
hwaccel: (p_desc.capabilities & (AV_CODEC_CAP_HARDWARE as i32)) > 0,
|
||||
});
|
||||
}
|
||||
|
||||
if av_codec_is_encoder(desc) == 1 {
|
||||
info.encoders
|
||||
.entry(p_desc.id)
|
||||
.or_default()
|
||||
.push(FfmpegCodec {
|
||||
name,
|
||||
long_name,
|
||||
hwaccel: (p_desc.capabilities & (AV_CODEC_CAP_HARDWARE as i32)) > 0,
|
||||
});
|
||||
}
|
||||
|
||||
desc = av_codec_next(desc);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove deprecated nvenc codecs if present
|
||||
info.encoders.entry(AV_CODEC_ID_HEVC).and_modify(|item| {
|
||||
remove_if_exists(item, "h264_nvenc", vec!["nvenc", "nvenc_h264"]);
|
||||
remove_if_exists(item, "nvenc_h264", vec!["nvenc"]);
|
||||
remove_if_exists(item, "hevc_nvenc", vec!["nvenc_hevc"]);
|
||||
});
|
||||
|
||||
info
|
||||
}
|
||||
|
||||
fn remove_if_exists(list: &mut Vec<FfmpegCodec>, needle: &str, to_remove: Vec<&str>) {
|
||||
let mut found_deprecated = vec![];
|
||||
let mut found_new = false;
|
||||
let mut i = 0;
|
||||
for codec in Borrow::<Vec<_>>::borrow(list) {
|
||||
if codec.name == needle {
|
||||
found_new = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if to_remove.contains(&codec.name.as_str()) {
|
||||
found_deprecated.push(i)
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if found_new {
|
||||
found_deprecated.sort();
|
||||
found_deprecated.reverse();
|
||||
for index in found_deprecated {
|
||||
list.remove(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
unsafe {
|
||||
av_register_all();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn verify_response(error: &str, resp: c_int) -> Result<c_int, String> {
|
||||
if resp < 0 {
|
||||
Err(format!("{} (code {}, {})", error, resp, av_err2str(resp)))
|
||||
} else {
|
||||
Ok(resp)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct FfmpegInfoCache {
|
||||
pub decoders: HashMap<AVCodecID, Vec<FfmpegCodec>>,
|
||||
pub encoders: HashMap<AVCodecID, Vec<FfmpegCodec>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct FfmpegCodec {
|
||||
pub name: String,
|
||||
pub long_name: String,
|
||||
pub hwaccel: bool,
|
||||
}
|
||||
|
||||
pub trait Rational {
|
||||
fn new(num: i32, den: i32) -> Self;
|
||||
|
||||
fn to_av(&self) -> AVRational {
|
||||
AVRational {
|
||||
den: self.den(),
|
||||
num: self.num(),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_num(&self) -> Ratio<i32> {
|
||||
Ratio::new(self.num(), self.den())
|
||||
}
|
||||
|
||||
fn num(&self) -> i32;
|
||||
fn den(&self) -> i32;
|
||||
fn invert(&self) -> Self;
|
||||
fn trunc(&self) -> Ratio<i32> {
|
||||
self.to_num().trunc()
|
||||
}
|
||||
|
||||
fn debug(&self) -> String {
|
||||
return format!(
|
||||
"Rational[{}]({}/{})",
|
||||
type_name::<Self>(),
|
||||
self.num(),
|
||||
self.den()
|
||||
);
|
||||
}
|
||||
|
||||
fn simplify(&self) -> Self;
|
||||
}
|
||||
|
||||
impl Rational for Ratio<i32> {
|
||||
#[inline]
|
||||
fn new(num: i32, den: i32) -> Self {
|
||||
Ratio::new(num, den)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_num(&self) -> Ratio<i32> {
|
||||
*self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn num(&self) -> i32 {
|
||||
*self.numer()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn den(&self) -> i32 {
|
||||
*self.denom()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn invert(&self) -> Self {
|
||||
Ratio::new(self.den(), self.num())
|
||||
}
|
||||
|
||||
fn simplify(&self) -> Self {
|
||||
Self::new(1, self.den() / self.num())
|
||||
}
|
||||
}
|
||||
|
||||
impl Rational for AVRational {
|
||||
#[inline]
|
||||
fn new(num: i32, den: i32) -> Self {
|
||||
AVRational { num, den }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_av(&self) -> AVRational {
|
||||
*self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn num(&self) -> i32 {
|
||||
self.num
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn den(&self) -> i32 {
|
||||
self.den
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn invert(&self) -> Self {
|
||||
unsafe { av_inv_q(*self) }
|
||||
}
|
||||
|
||||
fn simplify(&self) -> Self {
|
||||
Self::new(1, (self.den() as f64 / self.num() as f64).round() as i32)
|
||||
}
|
||||
}
|
@ -0,0 +1,332 @@
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::fmt::{Debug, Error, Formatter};
|
||||
use std::io::{Cursor, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AvcDecoderConfigurationRecord {
|
||||
pub profile_idc: u8,
|
||||
pub constraint_set_flag: u8,
|
||||
pub level_idc: u8,
|
||||
pub sequence_parameter_set: Vec<u8>,
|
||||
pub picture_parameter_set: Vec<u8>,
|
||||
}
|
||||
|
||||
impl AvcDecoderConfigurationRecord {
|
||||
pub fn from_parameter_sets(
|
||||
sequence_parameter_set: Vec<u8>,
|
||||
picture_parameter_set: Vec<u8>,
|
||||
) -> AvcDecoderConfigurationRecord {
|
||||
AvcDecoderConfigurationRecord {
|
||||
profile_idc: sequence_parameter_set[1],
|
||||
constraint_set_flag: sequence_parameter_set[2],
|
||||
level_idc: sequence_parameter_set[3],
|
||||
sequence_parameter_set,
|
||||
picture_parameter_set,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, String> {
|
||||
let mut buffer = vec![];
|
||||
// configuration_version
|
||||
buffer.push(1);
|
||||
buffer.push(self.profile_idc);
|
||||
buffer.push(self.constraint_set_flag);
|
||||
buffer.push(self.level_idc);
|
||||
// reserved and length_size_minus_one
|
||||
buffer.push(0b1111_1100 | 0b0000_0011);
|
||||
// reserved and num_of_sequence_parameter_set_ext
|
||||
buffer.push(0b1110_0000 | 0b0000_0001);
|
||||
|
||||
buffer
|
||||
.write_u16::<BigEndian>(self.sequence_parameter_set.len() as u16)
|
||||
.map_err(|x| format!("Failed to write u16 to vec ({})", x))?;
|
||||
|
||||
buffer.append(&mut self.sequence_parameter_set.clone());
|
||||
// num_of_picture_parameter_set_ext
|
||||
buffer.push(0b0000_0001);
|
||||
buffer
|
||||
.write_u16::<BigEndian>(self.picture_parameter_set.len() as u16)
|
||||
.map_err(|x| format!("Failed to write u16 to vec ({})", x))?;
|
||||
buffer.append(&mut self.picture_parameter_set.clone());
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum NalUnitType {
|
||||
CodedSliceOfANonIdrPicture = 1,
|
||||
CodedSliceDataPartitionA = 2,
|
||||
CodedSliceDataPartitionB = 3,
|
||||
CodedSliceDataPartitionC = 4,
|
||||
CodedSliceOfAnIdrPicture = 5,
|
||||
SupplementalEnhancementInformation = 6,
|
||||
SequenceParameterSet = 7,
|
||||
PictureParameterSet = 8,
|
||||
AccessUnitDelimiter = 9,
|
||||
EndOfSequence = 10,
|
||||
EndOfStream = 11,
|
||||
FilterData = 12,
|
||||
SequenceParameterSetExtension = 13,
|
||||
PrefixNalUnit = 14,
|
||||
SubsetSequenceParameterSet = 15,
|
||||
CodedSliceOfAnAuxiliaryCodedPictureWithoutPartitioning = 19,
|
||||
CodedSliceExtension = 20,
|
||||
}
|
||||
|
||||
impl NalUnitType {
|
||||
fn from_u8(n: u8) -> Result<Self, String> {
|
||||
Ok(match n {
|
||||
1 => NalUnitType::CodedSliceOfANonIdrPicture,
|
||||
2 => NalUnitType::CodedSliceDataPartitionA,
|
||||
3 => NalUnitType::CodedSliceDataPartitionB,
|
||||
4 => NalUnitType::CodedSliceDataPartitionC,
|
||||
5 => NalUnitType::CodedSliceOfAnIdrPicture,
|
||||
6 => NalUnitType::SupplementalEnhancementInformation,
|
||||
7 => NalUnitType::SequenceParameterSet,
|
||||
8 => NalUnitType::PictureParameterSet,
|
||||
9 => NalUnitType::AccessUnitDelimiter,
|
||||
10 => NalUnitType::EndOfSequence,
|
||||
11 => NalUnitType::EndOfStream,
|
||||
12 => NalUnitType::FilterData,
|
||||
13 => NalUnitType::SequenceParameterSetExtension,
|
||||
14 => NalUnitType::PrefixNalUnit,
|
||||
15 => NalUnitType::SubsetSequenceParameterSet,
|
||||
19 => NalUnitType::CodedSliceOfAnAuxiliaryCodedPictureWithoutPartitioning,
|
||||
20 => NalUnitType::CodedSliceExtension,
|
||||
_ => return Err(format!("Couldn't find NalUnitType for {:?}", n)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct NalUnit {
|
||||
pub nal_ref_idc: u8,
|
||||
pub nal_unit_type: NalUnitType,
|
||||
}
|
||||
|
||||
impl NalUnit {
|
||||
fn from_byte(byte: u8) -> Result<Self, String> {
|
||||
let nal_ref_idc = (byte >> 5) & 0b11;
|
||||
let nal_unit_type = NalUnitType::from_u8(byte & 0b1_1111)?;
|
||||
|
||||
Ok(NalUnit {
|
||||
nal_ref_idc,
|
||||
nal_unit_type,
|
||||
})
|
||||
}
|
||||
|
||||
fn to_byte(&self) -> u8 {
|
||||
let nal_ref_idc = self.nal_ref_idc << 5;
|
||||
let nal_unit_type = self.nal_unit_type as u8;
|
||||
return nal_unit_type | nal_ref_idc;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NalBuffer {
|
||||
pub nal_unit: NalUnit,
|
||||
pub buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
struct StringyBoi(String);
|
||||
|
||||
impl Debug for StringyBoi {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||
f.write_str(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for NalBuffer {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||
if f.alternate() {
|
||||
f.debug_tuple("NalBuffer")
|
||||
.field(&self.nal_unit)
|
||||
.field(&self.buffer)
|
||||
.finish()
|
||||
} else {
|
||||
f.debug_tuple("NalBuffer")
|
||||
.field(&self.nal_unit)
|
||||
.field(&StringyBoi(format!("len={}", self.buffer.len())))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_mdat(data: &Vec<u8>) -> Option<usize> {
|
||||
for i in 0..=data.len() - 3 {
|
||||
if data[i] == 'm' as u8
|
||||
&& data.get(i + 3).copied() == Some('t' as u8)
|
||||
&& &data[i..i + 4] == b"mdat"
|
||||
{
|
||||
return Some(i + 4);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn verify_data(data: Vec<u8>) {
|
||||
let index = find_mdat(&data).expect("no mdat found") as u64;
|
||||
let mut cursor = Cursor::new(&data);
|
||||
cursor.set_position(index - 8);
|
||||
let size = cursor.read_u32::<BigEndian>().expect(":O") as u64;
|
||||
cursor.set_position(index);
|
||||
while cursor.position() < (size + index - 8) {
|
||||
let skip = cursor.read_u32::<BigEndian>().expect(":C") as i64;
|
||||
cursor
|
||||
.seek(SeekFrom::Current(skip))
|
||||
.expect("Failed to seek");
|
||||
}
|
||||
|
||||
assert_eq!(cursor.position() - index, size - 8);
|
||||
}
|
||||
|
||||
pub fn annex_b_to_avc(mut data: Vec<u8>, nal_length: u8) -> Result<Vec<u8>, String> {
|
||||
let nal_length = nal_length as usize;
|
||||
let mut result: Vec<u8> = vec![];
|
||||
let index = find_mdat(&data).ok_or("No mdat found".to_string())?;
|
||||
let end = {
|
||||
let mut cursor = Cursor::new(&data);
|
||||
cursor.set_position((index - 8) as u64);
|
||||
index + (cursor.read_u32::<BigEndian>().expect(":O") as usize) - 8
|
||||
};
|
||||
|
||||
result.extend_from_slice(&data[0..index]);
|
||||
let matcher = vec![0u8; nal_length - 1 as usize];
|
||||
let mut last_position = index + nal_length - 1;
|
||||
for i in (index + matcher.len() + nal_length)..end {
|
||||
if data[i] == 1 && &data[1 + i - nal_length..i] == &matcher[..] {
|
||||
let block_length = (i - last_position - 4) as u32;
|
||||
let mut header = &mut data[last_position - 3..last_position + 1];
|
||||
header
|
||||
.write_u32::<BigEndian>(block_length)
|
||||
.map_err(|x| format!("Failed writing to buffer: {}", x))?;
|
||||
|
||||
last_position = i;
|
||||
}
|
||||
}
|
||||
|
||||
let data_len = data.len();
|
||||
let mut header = &mut data[last_position - 3..=last_position];
|
||||
header
|
||||
.write_u32::<BigEndian>(((data_len - 1) - last_position) as u32)
|
||||
.map_err(|x| format!("Failed writing to buffer: {}", x))?;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
verify_data(data.clone());
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
impl NalBuffer {
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
let mut bytes = vec![0; 1 + self.buffer.len()];
|
||||
{
|
||||
let (left, right) = bytes.split_at_mut(1);
|
||||
left[0] = self.nal_unit.to_byte();
|
||||
right.copy_from_slice(&self.buffer);
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn from_stream(stream: Vec<u8>) -> Result<Vec<NalBuffer>, String> {
|
||||
let mut i = 0;
|
||||
let mut buffers = vec![];
|
||||
let mut byte_buffer = vec![];
|
||||
let mut current: Option<NalBuffer> = None;
|
||||
while i < stream.len() {
|
||||
if stream.len() > i + 3
|
||||
&& stream[i] == 0
|
||||
&& stream[i + 1] == 0
|
||||
&& (stream[i + 2] == 1
|
||||
|| (stream.len() > i + 4 && stream[i + 2] == 0 && stream[i + 3] == 1))
|
||||
{
|
||||
if let Some(mut buffer) = current.take() {
|
||||
buffer.buffer = byte_buffer;
|
||||
byte_buffer = vec![];
|
||||
buffers.push(buffer);
|
||||
}
|
||||
|
||||
i += if stream[i + 2] == 1 { 3 } else { 4 };
|
||||
if i >= stream.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
current = Some(NalBuffer {
|
||||
nal_unit: NalUnit::from_byte(stream[i])?,
|
||||
buffer: vec![],
|
||||
});
|
||||
i += 1;
|
||||
if i >= stream.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if stream.len() > i + 4
|
||||
&& stream[i] == 0
|
||||
&& stream[i + 1] == 0
|
||||
&& stream[i + 2] == 0
|
||||
&& stream[i + 3] == 3
|
||||
{
|
||||
byte_buffer.append(&mut (&stream[i..i + 2]).to_vec());
|
||||
i += 4;
|
||||
if i >= stream.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
byte_buffer.push(stream[i]);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if let Some(mut buffer) = current.take() {
|
||||
buffer.buffer = byte_buffer;
|
||||
buffers.push(buffer);
|
||||
}
|
||||
|
||||
Ok(buffers)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::av::mov_avc::{annex_b_to_avc, verify_data};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
#[test]
|
||||
fn test_annex_b_to_avc() {
|
||||
let mdat = b"mdat".to_vec();
|
||||
let mut insert: Vec<u8> = vec![
|
||||
0, 0, 0, 1, 1, 2, 3, 4, 0, 0, 0, 1, 1, 2, 3, 4, 5, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8,
|
||||
9,
|
||||
];
|
||||
let mut buffer: Vec<u8> = vec![0, 0, 0, insert.len() as u8 + 8];
|
||||
let mut result: Vec<u8> = vec![0, 0, 0, insert.len() as u8 + 8];
|
||||
buffer.extend_from_slice(&mdat);
|
||||
buffer.append(&mut insert);
|
||||
result.extend(mdat);
|
||||
result.append(&mut vec![
|
||||
0, 0, 0, 4, 1, 2, 3, 4, 0, 0, 0, 5, 1, 2, 3, 4, 5, 0, 0, 0, 9, 1, 2, 3, 4, 5, 6, 7, 8,
|
||||
9,
|
||||
]);
|
||||
assert_eq!(annex_b_to_avc(buffer, 4), Ok(result.clone()));
|
||||
|
||||
verify_data(result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bs() {
|
||||
let mut x = File::open("/home/eater/shit/dash-exmp/chunk-stream0-00001.m4s").expect("ok");
|
||||
let mut xxx = vec![];
|
||||
x.read_to_end(&mut xxx).unwrap();
|
||||
verify_data(xxx);
|
||||
let mut x = File::open("/tmp/transotf/segment-00000.m4s").expect("ok");
|
||||
let mut xxx = vec![];
|
||||
x.read_to_end(&mut xxx).unwrap();
|
||||
verify_data(xxx);
|
||||
}
|
||||
}
|
@ -0,0 +1,254 @@
|
||||
use ffmpeg_sys_next::{
|
||||
AVClass, AVCodecParameters, AVFormatContext, AVIOContext, AVPacket, AVStream,
|
||||
};
|
||||
use std::fmt::{Debug, Error, Formatter};
|
||||
use std::os::raw::{c_char, c_float, c_int, c_long, c_uint, c_void};
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(i32)]
|
||||
pub enum MOVEncryptionScheme {
|
||||
MOV_ENC_NONE = 0,
|
||||
MOV_ENC_CENC_AES_CTR,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(i32)]
|
||||
pub enum MOVPrftBox {
|
||||
MOV_PRFT_NONE = 0,
|
||||
MOV_PRFT_SRC_WALLCLOCK,
|
||||
MOV_PRFT_SRC_PTS,
|
||||
MOV_PRFT_NB,
|
||||
}
|
||||
|
||||
const MOV_SYNC_SAMPLE: u32 = 0x0001;
|
||||
const MOV_PARTIAL_SYNC_SAMPLE: u32 = 0x0002;
|
||||
const MOV_DISPOSABLE_SAMPLE: u32 = 0x0004;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct AVProducerReferenceTime {
|
||||
pub wallclock: i64,
|
||||
pub flags: c_int,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct MOVIentry {
|
||||
pub pos: u64,
|
||||
pub dts: i64,
|
||||
pub pts: i64,
|
||||
pub size: c_uint,
|
||||
pub samples_in_chunk: c_uint,
|
||||
///< Chunk number if the current entry is a chunk start otherwise 0
|
||||
pub chunk_num: c_uint,
|
||||
pub entries: c_uint,
|
||||
pub cts: c_int,
|
||||
pub flags: u32,
|
||||
pub prft: AVProducerReferenceTime,
|
||||
}
|
||||
|
||||
const MOV_TRACK_CTTS: u32 = 0x0001;
|
||||
const MOV_TRACK_STPS: u32 = 0x0002;
|
||||
const MOV_TRACK_ENABLED: u32 = 0x0004;
|
||||
|
||||
const MOV_TIMECODE_FLAG_DROPFRAME: u32 = 0x0001;
|
||||
const MOV_TIMECODE_FLAG_24HOURSMAX: u32 = 0x0002;
|
||||
const MOV_TIMECODE_FLAG_ALLOWNEGATIVE: u32 = 0x0004;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct HintSample {
|
||||
pub data: *mut u8,
|
||||
pub size: c_int,
|
||||
pub sample_number: c_int,
|
||||
pub offset: c_int,
|
||||
pub own_data: c_int,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct HintSampleQueue {
|
||||
pub size: c_int,
|
||||
pub len: c_int,
|
||||
pub samples: *mut HintSample,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct MOVFragmentInfo {
|
||||
pub offset: i64,
|
||||
pub time: i64,
|
||||
pub duration: i64,
|
||||
pub tfrf_offset: i64,
|
||||
pub size: c_int,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct MOVMuxCencContext {
|
||||
aes_ctr: *mut c_void,
|
||||
pub auxiliary_info: *mut u8,
|
||||
pub auxiliary_info_size: isize,
|
||||
pub auxiliary_info_alloc_size: isize,
|
||||
pub auxiliary_info_entries: u32,
|
||||
|
||||
/* subsample support */
|
||||
pub use_subsamples: c_int,
|
||||
pub subsample_count: i16,
|
||||
pub auxiliary_info_subsample_start: isize,
|
||||
pub auxiliary_info_sizes: *mut u8,
|
||||
pub auxiliary_info_sizes_alloc_size: isize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct MOVTrack {
|
||||
pub mode: c_int,
|
||||
pub entry: c_int,
|
||||
pub timescale: c_uint,
|
||||
pub time: u64,
|
||||
pub track_duration: i64,
|
||||
pub last_sample_is_subtitle_end: c_int,
|
||||
pub sample_count: c_long,
|
||||
pub sample_size: c_long,
|
||||
pub chunk_count: c_long,
|
||||
pub has_keyframes: c_int,
|
||||
pub has_disposable: c_int,
|
||||
pub flags: u32,
|
||||
pub timecode_flags: u32,
|
||||
pub language: c_int,
|
||||
pub track_id: c_int,
|
||||
///< stsd fourcc
|
||||
pub tag: c_int,
|
||||
pub st: *mut AVStream,
|
||||
pub par: *mut AVCodecParameters,
|
||||
pub multichannel_as_mono: c_int,
|
||||
|
||||
pub vos_len: c_int,
|
||||
pub vos_data: *mut u8,
|
||||
pub cluster: *mut MOVIentry,
|
||||
pub cluster_capacity: c_uint,
|
||||
pub audio_vbr: c_int,
|
||||
///< active picture (w/o VBI) height for D-10/IMX
|
||||
pub height: c_int,
|
||||
pub tref_tag: u32,
|
||||
///< trackID of the referenced track
|
||||
pub tref_id: c_int,
|
||||
pub start_dts: i64,
|
||||
pub start_cts: i64,
|
||||
pub end_pts: i64,
|
||||
pub end_reliable: c_int,
|
||||
pub dts_shift: i64,
|
||||
|
||||
///< the track that hints this track, -1 if no hint track is set
|
||||
pub hint_track: c_int,
|
||||
///< the track that this hint (or tmcd) track describes
|
||||
pub src_track: c_int,
|
||||
///< the format context for the hinting rtp muxer
|
||||
pub rtp_ctx: *mut AVFormatContext,
|
||||
pub prev_rtp_ts: u32,
|
||||
pub cur_rtp_ts_unwrapped: i64,
|
||||
pub max_packet_size: u32,
|
||||
|
||||
pub default_duration: i64,
|
||||
pub default_sample_flags: u32,
|
||||
pub default_size: u32,
|
||||
|
||||
pub sample_queue: HintSampleQueue,
|
||||
pub cover_image: AVPacket,
|
||||
pub mdat_buf: *mut AVIOContext,
|
||||
pub data_offset: i64,
|
||||
pub frag_start: i64,
|
||||
pub frag_discont: c_int,
|
||||
pub entries_flushed: c_int,
|
||||
|
||||
pub nb_frag_info: c_int,
|
||||
pub frag_info: *mut MOVFragmentInfo,
|
||||
pub frag_info_capacity: c_uint,
|
||||
|
||||
pub vc1_info: VC1Info,
|
||||
|
||||
pub eac3_priv: *mut c_void,
|
||||
pub cenc: MOVMuxCencContext,
|
||||
pub palette: Palette,
|
||||
pub pal_done: c_int,
|
||||
|
||||
pub is_unaligned_qt_rgb: c_int,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone)]
|
||||
pub struct Palette([u32; 256]);
|
||||
|
||||
impl Debug for Palette {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||
f.write_str("Palette")?;
|
||||
f.debug_list().entries(&self.0[..]).finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct VC1Info {
|
||||
pub first_packet_seq: c_int,
|
||||
pub first_packet_entry: c_int,
|
||||
pub first_packet_seen: c_int,
|
||||
pub first_frag_written: c_int,
|
||||
pub packet_seq: c_int,
|
||||
pub packet_entry: c_int,
|
||||
pub slices: c_int,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct MOVMuxContext {
|
||||
pub av_class: *const AVClass,
|
||||
pub mode: c_int,
|
||||
pub time: i64,
|
||||
pub nb_streams: c_int,
|
||||
///< number of new created tmcd track based on metadata (aka not data copy)
|
||||
pub nb_meta_tmcd: c_int,
|
||||
///< qt chapter track number
|
||||
pub chapter_track: c_int,
|
||||
pub mdat_pos: i64,
|
||||
pub mdat_size: u64,
|
||||
pub tracks: *mut MOVTrack,
|
||||
pub flags: c_int,
|
||||
pub rtp_flags: c_int,
|
||||
pub iods_skip: c_int,
|
||||
pub iods_video_profile: c_int,
|
||||
pub iods_audio_profile: c_int,
|
||||
pub moov_written: c_int,
|
||||
pub fragments: c_int,
|
||||
pub max_fragment_duration: c_int,
|
||||
pub min_fragment_duration: c_int,
|
||||
pub max_fragment_size: c_int,
|
||||
pub ism_lookahead: c_int,
|
||||
pub mdat_buf: *mut AVIOContext,
|
||||
pub first_trun: c_int,
|
||||
pub video_track_timescale: c_int,
|
||||
///< 0 for disabled, -1 for automatic, size otherwise
|
||||
pub reserved_moov_size: c_int,
|
||||
pub reserved_header_pos: i64,
|
||||
pub major_brand: *mut c_char,
|
||||
pub per_stream_grouping: c_int,
|
||||
pub fc: *mut AVFormatContext,
|
||||
pub use_editlist: c_int,
|
||||
pub gamma: c_float,
|
||||
pub frag_interleave: c_int,
|
||||
pub missing_duration_warned: c_int,
|
||||
pub encryption_scheme_str: *mut c_char,
|
||||
pub encryption_scheme: MOVEncryptionScheme,
|
||||
pub encryption_key: *mut u8,
|
||||
pub encryption_key_len: c_int,
|
||||
pub encryption_kid: *mut u8,
|
||||
pub encryption_kid_len: c_int,
|
||||
pub need_rewrite_extradata: c_int,
|
||||
pub use_stream_ids_as_track_ids: c_int,
|
||||
pub track_ids_ok: c_int,
|
||||
pub write_tmcd: c_int,
|
||||
pub write_prft: MOVPrftBox,
|
||||
pub empty_hdlr_name: c_int,
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
use crate::av::as_ptr::AsMutVoidPtr;
|
||||
use crate::av::dictionary::Dictionary;
|
||||
use ffmpeg_sys_next::{av_opt_find, av_opt_get_int, av_opt_set_dict, av_opt_set_int};
|
||||
use std::ffi::CString;
|
||||
use std::ptr::null;
|
||||
|
||||
pub trait Options: AsMutVoidPtr {
|
||||
fn set_options(&mut self, mut options: Dictionary) {
|
||||
options.disown();
|
||||
unsafe {
|
||||
av_opt_set_dict(self.as_mut_void_ptr(), &mut options.as_mut_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
fn set_flag(&self, option: &str, flag: &str) -> bool {
|
||||
let option_c = CString::new(option).unwrap();
|
||||
let flag_c = CString::new(flag).unwrap();
|
||||
unsafe {
|
||||
let option = av_opt_find(self.as_mut_void_ptr(), option_c.as_ptr(), null(), 0, 0);
|
||||
let flag = av_opt_find(self.as_mut_void_ptr(), flag_c.as_ptr(), null(), 0, 0);
|
||||
|
||||
if flag.is_null() || option.is_null() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut val: i64 = 0;
|
||||
av_opt_get_int(self.as_mut_void_ptr(), option_c.as_ptr(), 0, &mut val);
|
||||
av_opt_set_int(
|
||||
self.as_mut_void_ptr(),
|
||||
option_c.as_ptr(),
|
||||
val | (*flag).default_val.i64,
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
use crate::av::Rational;
|
||||
use ffmpeg_sys_next::AVPacketSideDataType::AV_PKT_DATA_NEW_EXTRADATA;
|
||||
use ffmpeg_sys_next::{
|
||||
av_packet_alloc, av_packet_get_side_data, av_packet_rescale_ts, av_packet_unref, AVPacket,
|
||||
};
|
||||
|
||||
pub struct Packet(pub *mut AVPacket);
|
||||
|
||||
impl Packet {
|
||||
pub fn alloc() -> Packet {
|
||||
let pkt = unsafe { av_packet_alloc() };
|
||||
if pkt.is_null() {
|
||||
panic!("Failed to allocate packet");
|
||||
}
|
||||
|
||||
Packet(pkt)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stream(&self) -> i32 {
|
||||
return unsafe { (*self.0).stream_index };
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_stream(&self, stream_index: i32) {
|
||||
unsafe { (*self.0).stream_index = stream_index };
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pts(&self) -> i64 {
|
||||
unsafe { (*self.0).pts }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_pts(&self, pts: i64) {
|
||||
unsafe { (*self.0).pts = pts }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn dts(&self) -> i64 {
|
||||
unsafe { (*self.0).dts }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_dts(&self, dts: i64) {
|
||||
unsafe { (*self.0).dts = dts }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rescale<R1: Rational, R2: Rational>(&self, from: R1, to: R2) {
|
||||
unsafe {
|
||||
av_packet_rescale_ts(self.0, from.to_av(), to.to_av());
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn duration(&self) -> i64 {
|
||||
unsafe { (*self.0).duration }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_duration(&self, duration: i64) {
|
||||
unsafe { (*self.0).duration = duration }
|
||||
}
|
||||
|
||||
pub fn extra_data(&self) -> Vec<u8> {
|
||||
unsafe {
|
||||
let mut size = 0;
|
||||
let data = av_packet_get_side_data(self.as_ptr(), AV_PKT_DATA_NEW_EXTRADATA, &mut size);
|
||||
let size = size as usize;
|
||||
let mut extra_data = vec![0u8; size];
|
||||
data.copy_to(extra_data.as_mut_ptr(), size);
|
||||
|
||||
extra_data
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data(&self) -> Vec<u8> {
|
||||
unsafe {
|
||||
let size = (*self.0).size as usize;
|
||||
let mut data = vec![0u8; size];
|
||||
(*self.0).data.copy_to(data.as_mut_ptr(), size);
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_ptr(&self) -> *const AVPacket {
|
||||
return self.0;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_mut_ptr(&self) -> *mut AVPacket {
|
||||
return self.0;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Packet {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
av_packet_unref(self.0);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
use crate::av::as_ptr::{AsMutPtr, AsPtr};
|
||||
use crate::av::decoder::Decoder;
|
||||
use crate::av::encoder::Encoder;
|
||||
use crate::av::frame::Frame;
|
||||
use crate::av::xcoder::XCoder;
|
||||
use ffmpeg_sys_next::{
|
||||
swr_alloc, swr_alloc_set_opts, swr_convert_frame, swr_get_delay, AVFrame, SwrContext,
|
||||
};
|
||||
use std::ptr::{null, null_mut};
|
||||
|
||||
pub struct Resampler {
|
||||
ptr: *mut SwrContext,
|
||||
output_sample_rate: i64,
|
||||
}
|
||||
|
||||
impl Resampler {
|
||||
pub fn from_coder(decoder: &Decoder, encoder: &Encoder) -> Resampler {
|
||||
unsafe {
|
||||
let swr = swr_alloc();
|
||||
swr_alloc_set_opts(
|
||||
swr,
|
||||
encoder.channel_layout() as i64,
|
||||
encoder.sample_format(),
|
||||
encoder.sample_rate(),
|
||||
decoder.channel_layout() as i64,
|
||||
decoder.sample_format(),
|
||||
decoder.sample_rate(),
|
||||
0,
|
||||
null_mut(),
|
||||
);
|
||||
|
||||
Resampler {
|
||||
ptr: swr,
|
||||
output_sample_rate: encoder.sample_rate() as i64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert(&self, input: Frame) -> Frame {
|
||||
let output = Frame::alloc();
|
||||
unsafe {
|
||||
swr_convert_frame(self.ptr, output.as_mut_ptr(), input.as_ptr());
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
pub fn drain(&self) -> Option<Frame> {
|
||||
if 0 < unsafe { swr_get_delay(self.ptr, self.output_sample_rate) } {
|
||||
let output = Frame::alloc();
|
||||
unsafe {
|
||||
swr_convert_frame(self.ptr, output.as_mut_ptr(), null());
|
||||
}
|
||||
|
||||
Some(output)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
use crate::av::as_ptr::{AsMutPtr, AsPtr};
|
||||
use crate::av::decoder::Decoder;
|
||||
use crate::av::encoder::Encoder;
|
||||
use crate::av::frame::Frame;
|
||||
use crate::av::verify_response;
|
||||
use crate::av::xcoder::XCoder;
|
||||
use ffmpeg_sys_next::{sws_getContext, sws_scale, AVPixelFormat, SwsContext};
|
||||
use std::os::raw::c_int;
|
||||
use std::ptr::null_mut;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Scaler {
|
||||
pub ctx: *mut SwsContext,
|
||||
flags: c_int,
|
||||
input: VideoInfo,
|
||||
output: VideoInfo,
|
||||
pass_through: bool,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
struct VideoInfo {
|
||||
width: i32,
|
||||
height: i32,
|
||||
pixel_format: AVPixelFormat,
|
||||
}
|
||||
|
||||
impl Scaler {
|
||||
pub fn new(
|
||||
from_pixel_format: AVPixelFormat,
|
||||
from_width: i32,
|
||||
from_height: i32,
|
||||
to_pixel_format: AVPixelFormat,
|
||||
to_width: i32,
|
||||
to_height: i32,
|
||||
flags: c_int,
|
||||
) -> Scaler {
|
||||
unsafe {
|
||||
let ptr = sws_getContext(
|
||||
from_width,
|
||||
from_height,
|
||||
from_pixel_format,
|
||||
to_width,
|
||||
to_height,
|
||||
to_pixel_format,
|
||||
flags,
|
||||
null_mut(),
|
||||
null_mut(),
|
||||
null_mut(),
|
||||
);
|
||||
|
||||
let input = VideoInfo {
|
||||
width: from_width,
|
||||
height: from_height,
|
||||
pixel_format: from_pixel_format,
|
||||
};
|
||||
|
||||
let output = VideoInfo {
|
||||
width: to_width,
|
||||
height: to_height,
|
||||
pixel_format: to_pixel_format,
|
||||
};
|
||||
|
||||
Scaler {
|
||||
ctx: ptr,
|
||||
pass_through: input.eq(&output),
|
||||
flags,
|
||||
input,
|
||||
output,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_coder(decoder: &Decoder, encoder: &Encoder, flags: i32) -> Scaler {
|
||||
Scaler::new(
|
||||
decoder.pixel_format(),
|
||||
decoder.width(),
|
||||
decoder.height(),
|
||||
encoder.pixel_format(),
|
||||
encoder.width(),
|
||||
encoder.height(),
|
||||
flags,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn scale(&mut self, input: Frame) -> Result<Frame, String> {
|
||||
if self.input.pixel_format != input.pixel_format()
|
||||
|| self.input.width != input.width()
|
||||
|| self.input.height != input.height()
|
||||
{
|
||||
self.input = VideoInfo {
|
||||
pixel_format: input.pixel_format(),
|
||||
width: input.width(),
|
||||
height: input.height(),
|
||||
};
|
||||
|
||||
self.pass_through = self.input == self.output;
|
||||
if !self.pass_through {
|
||||
unsafe {
|
||||
self.ctx = sws_getContext(
|
||||
self.input.width,
|
||||
self.input.height,
|
||||
self.input.pixel_format,
|
||||
self.output.width,
|
||||
self.output.height,
|
||||
self.output.pixel_format,
|
||||
self.flags,
|
||||
null_mut(),
|
||||
null_mut(),
|
||||
null_mut(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.pass_through {
|
||||
return Ok(input);
|
||||
}
|
||||
|
||||
let mut frame = Frame::alloc();
|
||||
frame.allocate_video(
|
||||
self.output.pixel_format,
|
||||
self.output.width,
|
||||
self.output.height,
|
||||
)?;
|
||||
|
||||
verify_response("Failed to scale frame", unsafe {
|
||||
sws_scale(
|
||||
self.as_mut_ptr(),
|
||||
(*input.as_ptr()).data.as_ptr() as *const *const _,
|
||||
(*input.as_mut_ptr()).linesize.as_ptr() as *const _,
|
||||
0,
|
||||
self.input.height,
|
||||
(*frame.as_mut_ptr()).data.as_ptr() as *const *mut _,
|
||||
(*frame.as_mut_ptr()).linesize.as_ptr() as *mut _,
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(frame)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsPtr<SwsContext> for Scaler {
|
||||
fn as_ptr(&self) -> *const SwsContext {
|
||||
self.ctx
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMutPtr<SwsContext> for Scaler {
|
||||
fn as_mut_ptr(&self) -> *mut SwsContext {
|
||||
self.ctx
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
use crate::av::as_ptr::{AsMutPtr, AsPtr};
|
||||
use crate::av::codec_parameters::CodecParameters;
|
||||
use crate::av::decoder::Decoder;
|
||||
use crate::av::decoder_selector::DecoderSelector;
|
||||
use crate::av::encoder::Encoder;
|
||||
use crate::av::encoder_selector::EncoderSelector;
|
||||
use crate::av::format::Format;
|
||||
use crate::av::xcoder::XCoder;
|
||||
use crate::av::{get_best_decoder, get_best_encoder, Rational};
|
||||
use ffmpeg_sys_next::{av_guess_frame_rate, AVCodecID, AVRational, AVStream};
|
||||
use num_rational::Ratio;
|
||||
use std::ptr::null_mut;
|
||||
|
||||
pub struct Stream(pub *mut AVStream);
|
||||
|
||||
impl Stream {
|
||||
pub fn as_ptr(&self) -> *const AVStream {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn decoder(&self, hw_accel: Option<bool>) -> Option<Decoder> {
|
||||
get_best_decoder(self.codec(), hw_accel)
|
||||
.and_then(|decoder_name| Decoder::from_name(&decoder_name))
|
||||
}
|
||||
|
||||
pub fn decoder_select(
|
||||
&self,
|
||||
hw_accel: Option<bool>,
|
||||
select_fn: impl Fn(&Decoder) -> Result<(), String>,
|
||||
) -> Result<Decoder, String> {
|
||||
DecoderSelector::for_codec_id(self.codec()).select(hw_accel, select_fn)
|
||||
}
|
||||
|
||||
pub fn encoder(&self, hw_accel: Option<bool>) -> Option<Encoder> {
|
||||
let encoder = get_best_encoder(self.codec(), hw_accel);
|
||||
encoder.and_then(|encoder_name| Encoder::from_name(&encoder_name))
|
||||
}
|
||||
|
||||
pub fn encoder_select(
|
||||
&self,
|
||||
hw_accel: Option<bool>,
|
||||
select_fn: impl Fn(&Encoder) -> Result<(), String>,
|
||||
) -> Result<Encoder, String> {
|
||||
EncoderSelector::for_codec_id(self.codec()).select(hw_accel, select_fn)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn index(&self) -> i32 {
|
||||
unsafe { (*self.0).index }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_index(&self, index: i32) {
|
||||
unsafe { (*self.0).index = index }
|
||||
}
|
||||
|
||||
pub fn container_id(&self) -> i32 {
|
||||
unsafe { (*self.0).id }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn codec(&self) -> AVCodecID {
|
||||
unsafe { (*(*self.0).codec).codec_id }
|
||||
}
|
||||
|
||||
pub fn params(&self) -> CodecParameters {
|
||||
unsafe { CodecParameters((*self.0).codecpar) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn time_base(&self) -> Ratio<i32> {
|
||||
unsafe { (*self.0).time_base.to_num() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_time_base<R: Rational>(&self, time_base: R) {
|
||||
unsafe { (*self.0).time_base = time_base.to_av() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sample_aspect_ratio(&self) -> AVRational {
|
||||
unsafe { (*self.as_ptr()).sample_aspect_ratio }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_sample_aspect_ratio(&self, sample_aspect_ratio: impl Rational) {
|
||||
unsafe { (*self.as_mut_ptr()).sample_aspect_ratio = sample_aspect_ratio.to_av() }
|
||||
}
|
||||
|
||||
pub fn avg_frame_rate<T>(&self, fmt: &Format<T>) -> Option<impl Rational> {
|
||||
Some(unsafe { av_guess_frame_rate(fmt.as_mut_ptr(), self.as_mut_ptr(), null_mut()) })
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMutPtr<AVStream> for Stream {
|
||||
#[inline]
|
||||
fn as_mut_ptr(&self) -> *mut AVStream {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsPtr<AVStream> for Stream {
|
||||
#[inline]
|
||||
fn as_ptr(&self) -> *const AVStream {
|
||||
self.0
|
||||
}
|
||||
}
|
@ -0,0 +1,257 @@
|
||||
use crate::av::dictionary::Dictionary;
|
||||
use crate::av::stream::Stream;
|
||||
use crate::av::{verify_response, Rational};
|
||||
use crate::av_err2str;
|
||||
use ffmpeg_sys_next::AVPixelFormat::AV_PIX_FMT_NONE;
|
||||
use ffmpeg_sys_next::{
|
||||
av_malloc, avcodec_open2, AVCodecContext, AVColorPrimaries, AVColorRange,
|
||||
AVColorTransferCharacteristic, AVPixelFormat, AVRational, AVSampleFormat, AVERROR, AVERROR_EOF,
|
||||
EAGAIN,
|
||||
};
|
||||
use num_rational::Ratio;
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_int;
|
||||
use std::ptr::null_mut;
|
||||
|
||||
pub trait XCoder: Sized {
|
||||
fn from_name(name: &str) -> Option<Self>;
|
||||
fn as_mut_ptr(&self) -> *mut AVCodecContext;
|
||||
|
||||
#[inline]
|
||||
fn as_ptr(&self) -> *const AVCodecContext {
|
||||
self.as_mut_ptr()
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
unsafe {
|
||||
if let Some(x) = (*(*self.as_ptr()).av_class).item_name {
|
||||
CStr::from_ptr(x(self.as_mut_ptr().cast()))
|
||||
.to_str()
|
||||
.unwrap_or("")
|
||||
.to_string()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn configure(&self, stream: &Stream) -> Result<(), String>;
|
||||
|
||||
fn open(&self) -> Result<(), String> {
|
||||
let ctx = self.as_mut_ptr();
|
||||
let resp = unsafe { avcodec_open2(ctx, (*ctx).codec, null_mut()) };
|
||||
|
||||
if resp < 0 {
|
||||
return Err(format!(
|
||||
"Failed configuring coder from stream (code {}: {})",
|
||||
resp,
|
||||
av_err2str(resp)
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn time_base(&self) -> Ratio<i32> {
|
||||
unsafe { (*self.as_ptr()).time_base.to_num() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_time_base<R: Rational>(&self, ratio: R) {
|
||||
unsafe { (*self.as_mut_ptr()).time_base = ratio.to_av() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pixel_format(&self) -> AVPixelFormat {
|
||||
unsafe { (*self.as_ptr()).pix_fmt }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_pixel_format(&self, pixel_format: AVPixelFormat) {
|
||||
unsafe { (*self.as_mut_ptr()).pix_fmt = pixel_format }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn width(&self) -> c_int {
|
||||
unsafe { (*self.as_ptr()).width }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_width(&self, width: c_int) {
|
||||
unsafe { (*self.as_mut_ptr()).width = width }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn height(&self) -> c_int {
|
||||
unsafe { (*self.as_ptr()).height }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_height(&self, height: c_int) {
|
||||
unsafe { (*self.as_mut_ptr()).height = height }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bit_rate(&self) -> i64 {
|
||||
unsafe { (*self.as_mut_ptr()).bit_rate }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_bit_rate(&self, bit_rate: i64) {
|
||||
unsafe {
|
||||
(*self.as_mut_ptr()).bit_rate = bit_rate;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_aspect_ratio(&self) -> AVRational {
|
||||
unsafe { (*self.as_ptr()).sample_aspect_ratio }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn color_range(&self) -> AVColorRange {
|
||||
unsafe { (*self.as_ptr()).color_range }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_color_range(&self, color_range: AVColorRange) {
|
||||
unsafe { (*self.as_mut_ptr()).color_range = color_range }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn color_primaries(&self) -> AVColorPrimaries {
|
||||
unsafe { (*self.as_ptr()).color_primaries }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_color_primaries(&self, color_primaries: AVColorPrimaries) {
|
||||
unsafe { (*self.as_mut_ptr()).color_primaries = color_primaries }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn color_trc(&self) -> AVColorTransferCharacteristic {
|
||||
unsafe { (*self.as_ptr()).color_trc }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_color_trc(&self, color_trc: AVColorTransferCharacteristic) {
|
||||
unsafe { (*self.as_mut_ptr()).color_trc = color_trc }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> i32 {
|
||||
unsafe { (*self.as_ptr()).channels }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_channels(&self, channels: i32) {
|
||||
unsafe { (*self.as_mut_ptr()).channels = channels }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channel_layout(&self) -> u64 {
|
||||
unsafe { (*self.as_ptr()).channel_layout }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_channel_layout(&self, channel_layout: u64) {
|
||||
unsafe { (*self.as_mut_ptr()).channel_layout = channel_layout }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_format(&self) -> AVSampleFormat {
|
||||
unsafe { (*self.as_ptr()).sample_fmt }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_sample_format(&self, sample_format: AVSampleFormat) {
|
||||
unsafe { (*self.as_mut_ptr()).sample_fmt = sample_format }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn frame_size(&self) -> i32 {
|
||||
unsafe { (*self.as_ptr()).frame_size }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_frame_size(&self, frame_size: i32) {
|
||||
unsafe { (*self.as_mut_ptr()).frame_size = frame_size }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> i32 {
|
||||
unsafe { (*self.as_ptr()).sample_rate }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_sample_rate(&self, sample_rate: i32) {
|
||||
unsafe { (*self.as_mut_ptr()).sample_rate = sample_rate }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn extra_data(&self) -> Vec<u8> {
|
||||
unsafe {
|
||||
let extra_data_size = (*self.as_ptr()).extradata_size as usize;
|
||||
let mut extra_data = vec![0u8; extra_data_size];
|
||||
(*self.as_ptr())
|
||||
.extradata
|
||||
.copy_to(extra_data.as_mut_ptr(), extra_data_size);
|
||||
|
||||
extra_data
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_extra_data(&self, extra_data: Vec<u8>) {
|
||||
unsafe {
|
||||
let new_extra_data: *mut u8 = av_malloc(extra_data.len()).cast();
|
||||
(*self.as_mut_ptr()).extradata = new_extra_data;
|
||||
(*self.as_mut_ptr()).extradata_size = extra_data.len() as i32;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_private_data(&self, key: &str, value: &str) {
|
||||
unsafe {
|
||||
Dictionary {
|
||||
ptr: (*self.as_ptr()).priv_data.cast(),
|
||||
owned: false,
|
||||
}
|
||||
.set(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
fn pixel_formats(&self) -> Vec<AVPixelFormat> {
|
||||
let mut pixel_formats = vec![];
|
||||
unsafe {
|
||||
let mut current = (*(*self.as_ptr()).codec).pix_fmts;
|
||||
while *current != AV_PIX_FMT_NONE {
|
||||
pixel_formats.push(*current);
|
||||
current = current.add(1);
|
||||
}
|
||||
}
|
||||
|
||||
pixel_formats
|
||||
}
|
||||
|
||||
fn sample_formats(&self) -> Vec<AVSampleFormat> {
|
||||
let mut sample_formats = vec![];
|
||||
unsafe {
|
||||
let mut current = (*(*self.as_ptr()).codec).sample_fmts;
|
||||
while *current != AVSampleFormat::AV_SAMPLE_FMT_NONE {
|
||||
sample_formats.push(*current);
|
||||
current = current.add(1);
|
||||
}
|
||||
}
|
||||
|
||||
sample_formats
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn process_return(error: &str, resp: i32) -> Result<bool, String> {
|
||||
if resp == AVERROR(EAGAIN) || resp == AVERROR_EOF {
|
||||
Ok(false)
|
||||
} else {
|
||||
verify_response(error, resp).and(Ok(true))
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::av::init;
|
||||
use crate::transcoder::Transcoder;
|
||||
use ffmpeg_sys_next::{av_make_error_string, av_malloc, av_version_info};
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::{c_char, c_int};
|
||||
|
||||
pub mod av;
|
||||
pub mod transcoder;
|
||||
pub mod utils;
|
||||
|
||||
// const INPUT_PATH: &str = "/tank/ephemeral/anime/series/Witch Hunter Robin - 2ndfire/[2ndfire]Witch_Hunter_Robin_-_01[91DCE49A].mkv";
|
||||
const INPUT_PATH: &str = "/tank/ephemeral/anime/series/Brave Witches - Eila/Extras/[Eila] Brave Witches - NCOP [BD 1080p][DB44ED0B].mkv";
|
||||
const OUTPUT_PATH: &str = "/tmp/transotf";
|
||||
|
||||
fn av_err2str(error_code: c_int) -> String {
|
||||
unsafe {
|
||||
let str: *mut c_char = av_malloc(1024).cast();
|
||||
av_make_error_string(str, 1024, error_code);
|
||||
CString::from_raw(str).to_str().unwrap().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("> transotf: ffmpeg {}", unsafe {
|
||||
CStr::from_ptr(av_version_info()).to_str().unwrap()
|
||||
});
|
||||
|
||||
init();
|
||||
|
||||
let mut transcoder = Transcoder::create(INPUT_PATH, OUTPUT_PATH, None, None)
|
||||
.expect("Failed to create transcoder");
|
||||
transcoder.transcode().expect("Failed to transcode");
|
||||
}
|
@ -0,0 +1,488 @@
|
||||
use crate::av::avio::{AVIOWriter, AVIO};
|
||||
use crate::av::decoder::Decoder;
|
||||
use crate::av::dictionary::Dictionary;
|
||||
use crate::av::encoder::Encoder;
|
||||
use crate::av::format::Format;
|
||||
use crate::av::mov_avc::{annex_b_to_avc, AvcDecoderConfigurationRecord, NalBuffer, NalUnitType};
|
||||
use crate::av::packet::Packet;
|
||||
use crate::av::resampler::Resampler;
|
||||
use crate::av::scaler::Scaler;
|
||||
use crate::av::stream::Stream;
|
||||
use crate::av::xcoder::XCoder;
|
||||
use crate::av::Rational;
|
||||
use crate::utils::SortedFrameBuffer;
|
||||
use ffmpeg_sys_next::AVMediaType::{AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO};
|
||||
use ffmpeg_sys_next::AVPictureType::AV_PICTURE_TYPE_I;
|
||||
use ffmpeg_sys_next::AVPixelFormat::AV_PIX_FMT_YUV420P;
|
||||
use ffmpeg_sys_next::{avio_wb32, AVIOContext, AVPixelFormat, SWS_BILINEAR};
|
||||
use num_rational::Ratio;
|
||||
use std::fs::{DirBuilder, File};
|
||||
use std::io::Write;
|
||||
use std::option::Option::Some;
|
||||
|
||||
pub struct Transcoder {
|
||||
input: Format<()>,
|
||||
input_video: Stream,
|
||||
input_frame_rate: Ratio<i32>,
|
||||
video_output: Format<Buffer>,
|
||||
output_path: String,
|
||||
output_video: Stream,
|
||||
video_frame_buffer: SortedFrameBuffer,
|
||||
video_encoder: Encoder,
|
||||
video_decoder: Decoder,
|
||||
frame_scaler: Scaler,
|
||||
last_pts: i64,
|
||||
current_pts: i64,
|
||||
video_segment: u32,
|
||||
extra_data: Option<Vec<u8>>,
|
||||
audio: Option<AudioTranscoder>,
|
||||
}
|
||||
|
||||
pub struct AudioTranscoder {
|
||||
encoder: Encoder,
|
||||
decoder: Decoder,
|
||||
frame_buffer: SortedFrameBuffer,
|
||||
output_stream: Stream,
|
||||
input_stream: Stream,
|
||||
format: Format<Buffer>,
|
||||
output_path: String,
|
||||
resampler: Resampler,
|
||||
segment: u32,
|
||||
last_pts: i64,
|
||||
current_pts: i64,
|
||||
}
|
||||
|
||||
impl AudioTranscoder {
|
||||
fn process_packet(&mut self, packet: Packet) -> Result<(), String> {
|
||||
self.decoder
|
||||
.send_packet(&packet)
|
||||
.map_err(|err| format!("Failed sending audio packet: {}", err))?;
|
||||
while let Some(frame) = self.decoder.read_frame()? {
|
||||
self.frame_buffer.insert_frame(frame);
|
||||
self.encode()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_segment(&self, force: bool) {
|
||||
if self.segment == 0 && !force {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let pb = (*self.format.as_mut_ptr()).pb;
|
||||
avio_wb32(pb, 24);
|
||||
|
||||
ffio_wfourcc(pb, b"styp");
|
||||
ffio_wfourcc(pb, b"msdh");
|
||||
avio_wb32(pb, 0); /* minor */
|
||||
ffio_wfourcc(pb, b"msdh");
|
||||
ffio_wfourcc(pb, b"msix");
|
||||
}
|
||||
}
|
||||
|
||||
fn write_header(&mut self) -> Result<(), String> {
|
||||
File::create(format!("{}/audio-init.mp4", &self.output_path))
|
||||
.unwrap()
|
||||
.write_all(&mut self.format.avio_inner_mut().unwrap().buffer())
|
||||
.map_err(|_| "Failed to write audio init segment".to_string())?;
|
||||
|
||||
self.start_segment(true);
|
||||
self.format.write_packet_null()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_segment(&mut self) -> Result<(), String> {
|
||||
if self.segment == 0 {
|
||||
self.write_header()?;
|
||||
}
|
||||
|
||||
if let Some(avio) = self.format.avio.as_mut() {
|
||||
avio.flush()
|
||||
}
|
||||
|
||||
let mut segment = self.format.avio_inner_mut().unwrap().buffer();
|
||||
|
||||
File::create(format!(
|
||||
"{}/audio-segment-{:0>5}.m4s",
|
||||
self.output_path, self.segment
|
||||
))
|
||||
.unwrap()
|
||||
.write_all(&mut segment)
|
||||
.map_err(|_| format!("Failed to write audio segment {}", self.segment))?;
|
||||
self.start_segment(false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encode(&mut self) -> Result<(), String> {
|
||||
while let Some(frame) = self.frame_buffer.pop_first() {
|
||||
let pts_passed = frame.pts() - self.last_pts;
|
||||
if (pts_passed + self.current_pts) > (5 * self.input_stream.time_base().den()) as i64 {
|
||||
self.format.write_packet_null()?;
|
||||
println!(
|
||||
"Next audio segment (current: {}, pts: {}/{})!",
|
||||
self.segment, self.last_pts, self.current_pts,
|
||||
);
|
||||
self.write_segment()?;
|
||||
self.segment += 1;
|
||||
self.current_pts =
|
||||
(pts_passed + self.current_pts) % self.input_stream.time_base().den() as i64;
|
||||
} else {
|
||||
self.current_pts += pts_passed
|
||||
}
|
||||
|
||||
self.last_pts = frame.pts();
|
||||
self.encoder.send_audio_frame(&frame)?;
|
||||
while let Some(new_packet) = self.encoder.read_packet()? {
|
||||
new_packet.set_pts(frame.pts());
|
||||
new_packet.set_dts(new_packet.pts());
|
||||
new_packet.set_duration(pts_passed);
|
||||
new_packet.rescale(
|
||||
self.input_stream.time_base(),
|
||||
self.output_stream.time_base(),
|
||||
);
|
||||
new_packet.set_stream(0);
|
||||
self.format.write_packet(&new_packet)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Transcoder {
|
||||
pub fn create(
|
||||
input_path: &str,
|
||||
output_path: &str,
|
||||
video_index: Option<i32>,
|
||||
audio_index: Option<i32>,
|
||||
) -> Result<Transcoder, String> {
|
||||
let input = Format::open(input_path, None).expect("Failed to open media");
|
||||
let output_path = output_path.to_string();
|
||||
|
||||
let input_video = input
|
||||
.stream(AVMEDIA_TYPE_VIDEO, video_index)
|
||||
.ok_or("Failed to find video stream".to_string())?;
|
||||
|
||||
let input_audio = input.stream(AVMEDIA_TYPE_AUDIO, audio_index);
|
||||
if audio_index.is_some() && input_audio.is_none() {
|
||||
return Err("Failed to find audio stream".to_string());
|
||||
}
|
||||
|
||||
let input_frame_rate = input_video.avg_frame_rate(&input).unwrap().to_num();
|
||||
|
||||
let mut options = Dictionary::new();
|
||||
options.set("movflags", "+dash+delay_moov+skip_trailer+frag_custom");
|
||||
|
||||
let video_avio = AVIO::writer(Buffer::default());
|
||||
let video_output = Format::output_avio(video_avio, "mp4")?;
|
||||
let output_video = video_output.new_stream("h264")?;
|
||||
output_video.params().copy_from(&input_video.params())?;
|
||||
output_video.set_sample_aspect_ratio(input_video.sample_aspect_ratio());
|
||||
video_output.set_flags(input.flags());
|
||||
|
||||
let audio = if let Some(input_stream) = input_audio {
|
||||
let audio_avio = AVIO::writer(Buffer::default());
|
||||
let format = Format::output_avio(audio_avio, "mp4")?;
|
||||
let output_stream = format.new_stream("aac")?;
|
||||
|
||||
format.set_flags(input.flags());
|
||||
|
||||
let decoder = input_stream.decoder(None).ok_or(format!(
|
||||
"Couldn't find encoder for input audio with codec: {:?}",
|
||||
input_stream.codec()
|
||||
))?;
|
||||
decoder.configure(&input_stream)?;
|
||||
decoder.open()?;
|
||||
|
||||
let encoder = output_stream
|
||||
.encoder(None)
|
||||
.ok_or("Couldn't find encoder for AAC".to_string())?;
|
||||
encoder.set_channels(decoder.channels());
|
||||
encoder.set_channel_layout(decoder.channel_layout());
|
||||
encoder.set_sample_format(
|
||||
encoder
|
||||
.sample_formats()
|
||||
.first()
|
||||
.copied()
|
||||
.ok_or("No sample formats found for AAC")?,
|
||||
);
|
||||
encoder.set_sample_rate(decoder.sample_rate());
|
||||
encoder.set_time_base(input_stream.time_base());
|
||||
encoder.set_time_base(Ratio::new(1, decoder.sample_rate()));
|
||||
output_stream.set_time_base(encoder.time_base());
|
||||
encoder.set_frame_size(decoder.frame_size() * 4);
|
||||
encoder.open()?;
|
||||
encoder.configure(&output_stream)?;
|
||||
|
||||
println!(
|
||||
"audio decoder[{}] -> audio encoder[{}]",
|
||||
decoder.name(),
|
||||
encoder.name()
|
||||
);
|
||||
|
||||
let resampler = Resampler::from_coder(&decoder, &encoder);
|
||||
|
||||
Some(AudioTranscoder {
|
||||
frame_buffer: SortedFrameBuffer::new(),
|
||||
encoder,
|
||||
decoder,
|
||||
format,
|
||||
output_stream,
|
||||
input_stream,
|
||||
output_path: output_path.to_string(),
|
||||
current_pts: 0,
|
||||
last_pts: 0,
|
||||
segment: 0,
|
||||
resampler,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let video_decoder = input_video.decoder_select(None, |d| {
|
||||
d.configure(&input_video)?;
|
||||
d.open()?;
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let video_encoder = output_video.encoder_select(None, |encoder| {
|
||||
encoder.set_pixel_format(AVPixelFormat::AV_PIX_FMT_YUV420P);
|
||||
encoder.set_color_range(video_decoder.color_range());
|
||||
encoder.set_color_primaries(video_decoder.color_primaries());
|
||||
encoder.set_color_trc(video_decoder.color_trc());
|
||||
encoder.set_height(video_decoder.height());
|
||||
encoder.set_width(video_decoder.width());
|
||||
encoder.set_time_base(input_frame_rate.invert());
|
||||
output_video.set_time_base(encoder.time_base());
|
||||
encoder.open()?;
|
||||
encoder.configure(&output_video)?;
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
println!(
|
||||
"video decoder[{}] -> video encoder[{}]",
|
||||
video_decoder.name(),
|
||||
video_encoder.name()
|
||||
);
|
||||
|
||||
let param = output_video.params();
|
||||
param.set_height(video_decoder.height());
|
||||
param.set_width(video_decoder.width());
|
||||
param.set_pixel_format(AV_PIX_FMT_YUV420P);
|
||||
video_output.init_output(options)?;
|
||||
|
||||
let frame_scaler = Scaler::from_coder(&video_decoder, &video_encoder, SWS_BILINEAR);
|
||||
let video_frame_buffer = SortedFrameBuffer::new();
|
||||
|
||||
Ok(Transcoder {
|
||||
input,
|
||||
input_video,
|
||||
input_frame_rate,
|
||||
video_output,
|
||||
output_video,
|
||||
audio,
|
||||
output_path,
|
||||
video_encoder,
|
||||
video_decoder,
|
||||
frame_scaler,
|
||||
video_frame_buffer,
|
||||
last_pts: 0,
|
||||
current_pts: 0,
|
||||
video_segment: 0,
|
||||
extra_data: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn transcode(&mut self) -> Result<(), String> {
|
||||
DirBuilder::new()
|
||||
.recursive(true)
|
||||
.create(self.output_path.to_string())
|
||||
.map_err(|_| "Failed to create target directory".to_string())?;
|
||||
|
||||
self.video_output.write_header(None).unwrap();
|
||||
let input_video_index = self.input_video.index();
|
||||
let input_audio_index = self.audio.as_ref().map(|ia| ia.input_stream.index());
|
||||
|
||||
while let Some(packet) = self.input.next_packet() {
|
||||
if input_video_index == packet.stream() {
|
||||
self.video_process_packet(packet)?;
|
||||
} else if input_audio_index == Some(packet.stream()) {
|
||||
// Safe assumption
|
||||
if let Some(ref mut audio) = &mut self.audio {
|
||||
audio.process_packet(packet)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.encode_video()?;
|
||||
self.video_output.write_trailer()?;
|
||||
self.video_write_segment()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn video_process_packet(&mut self, packet: Packet) -> Result<(), String> {
|
||||
self.video_decoder.send_packet(&packet)?;
|
||||
while let Some(frame) = self.video_decoder.read_frame()? {
|
||||
let pts = frame.pts();
|
||||
let frame = self
|
||||
.frame_scaler
|
||||
.scale(frame)
|
||||
.expect("Failed to scale video frame");
|
||||
frame.set_pts(pts);
|
||||
self.video_frame_buffer.insert_frame(frame);
|
||||
self.encode_video()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn video_write_header(&mut self) -> Result<(), String> {
|
||||
File::create(format!("{}/video-init.mp4", &self.output_path))
|
||||
.unwrap()
|
||||
.write_all(&mut self.video_output.avio_inner_mut().unwrap().buffer())
|
||||
.map_err(|_| "Failed to write video init segment".to_string())?;
|
||||
|
||||
self.start_segment(true);
|
||||
self.video_output.write_packet_null()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn video_write_segment(&mut self) -> Result<(), String> {
|
||||
if self.video_segment == 0 {
|
||||
self.video_write_header()?;
|
||||
}
|
||||
|
||||
if let Some(avio) = self.video_output.avio.as_mut() {
|
||||
avio.flush()
|
||||
}
|
||||
|
||||
let segment = self.video_output.avio_inner_mut().unwrap().buffer();
|
||||
let mut segment = annex_b_to_avc(segment, 4)?;
|
||||
|
||||
File::create(format!(
|
||||
"{}/video-segment-{:0>5}.m4s",
|
||||
self.output_path, self.video_segment
|
||||
))
|
||||
.unwrap()
|
||||
.write_all(&mut segment)
|
||||
.map_err(|_| format!("Failed to write video segment {}", self.video_segment))?;
|
||||
self.start_segment(false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encode_video(&mut self) -> Result<(), String> {
|
||||
while let Some(frame) = self.video_frame_buffer.pop_first() {
|
||||
let pts_passed = frame.pts() - self.last_pts;
|
||||
if (pts_passed + self.current_pts) > (5 * self.input_frame_rate.den()) as i64 {
|
||||
self.video_output.write_packet_null()?;
|
||||
println!(
|
||||
"Next video segment (current: {}, pts: {}/{})!",
|
||||
self.video_segment, self.last_pts, self.current_pts,
|
||||
);
|
||||
self.video_write_segment()?;
|
||||
self.video_segment += 1;
|
||||
self.current_pts =
|
||||
(pts_passed + self.current_pts) % self.input_frame_rate.den() as i64;
|
||||
frame.set_pict_type(AV_PICTURE_TYPE_I);
|
||||
} else {
|
||||
self.current_pts += pts_passed
|
||||
}
|
||||
|
||||
self.last_pts = frame.pts();
|
||||
self.video_encoder.send_video_frame(&frame)?;
|
||||
|
||||
while let Some(new_packet) = self.video_encoder.read_packet()? {
|
||||
if self.extra_data.is_none() {
|
||||
let items = NalBuffer::from_stream(new_packet.data())?;
|
||||
let mut sps = None;
|
||||
let mut pss = None;
|
||||
|
||||
for item in items {
|
||||
if item.nal_unit.nal_unit_type == NalUnitType::PictureParameterSet {
|
||||
pss = Some(item.to_vec());
|
||||
} else if item.nal_unit.nal_unit_type == NalUnitType::SequenceParameterSet {
|
||||
sps = Some(item.to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(sps) = sps {
|
||||
if let Some(pss) = pss {
|
||||
let config =
|
||||
AvcDecoderConfigurationRecord::from_parameter_sets(sps, pss);
|
||||
self.extra_data = Some(config.to_bytes()?);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(extra_data) = &self.extra_data {
|
||||
self.output_video.params().set_extra_data(extra_data);
|
||||
self.output_video
|
||||
.params()
|
||||
.set_sample_aspect_ratio(Ratio::new(1, 1));
|
||||
}
|
||||
}
|
||||
|
||||
new_packet.set_pts(frame.pts());
|
||||
new_packet.set_dts(new_packet.pts());
|
||||
new_packet.set_duration(pts_passed);
|
||||
new_packet.rescale(self.input_video.time_base(), self.output_video.time_base());
|
||||
new_packet.set_stream(0);
|
||||
self.video_output.write_packet(&new_packet)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_segment(&self, force: bool) {
|
||||
if self.video_segment == 0 && !force {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let pb = (*self.video_output.as_mut_ptr()).pb;
|
||||
avio_wb32(pb, 24);
|
||||
|
||||
ffio_wfourcc(pb, b"styp");
|
||||
ffio_wfourcc(pb, b"msdh");
|
||||
avio_wb32(pb, 0); /* minor */
|
||||
ffio_wfourcc(pb, b"msdh");
|
||||
ffio_wfourcc(pb, b"msix");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
struct Buffer {
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
fn buffer(&mut self) -> Vec<u8> {
|
||||
std::mem::replace(&mut self.buffer, vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl AVIOWriter for Buffer {
|
||||
fn write(&mut self, mut buffer: Vec<u8>) {
|
||||
self.buffer.append(&mut buffer)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ffio_wfourcc(pb: *mut AVIOContext, tag: &[u8]) {
|
||||
let mut nr: u32 = 0;
|
||||
nr |= tag[3] as u32;
|
||||
nr |= (tag[2] as u32) << 8;
|
||||
nr |= (tag[1] as u32) << 16;
|
||||
nr |= (tag[0] as u32) << 24;
|
||||
unsafe { avio_wb32(pb, nr) }
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
use crate::av::frame::Frame;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Debug, Error, Formatter};
|
||||
use std::hash::Hash;
|
||||
|
||||
pub struct SortedBuffer<K: Copy, V> {
|
||||
lowest_value: Option<K>,
|
||||
sorted_keys: Vec<K>,
|
||||
items: HashMap<K, V>,
|
||||
open: bool,
|
||||
buffer_size: usize,
|
||||
}
|
||||
|
||||
impl<K: Debug + Copy + Eq + Hash, V: Debug> Debug for SortedBuffer<K, V> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||
f.debug_struct("SortedBuffer")
|
||||
.field("lowest_value", &self.lowest_value)
|
||||
.field("sorted_keys", &self.sorted_keys)
|
||||
.field("items", &self.items)
|
||||
.field("open", &self.open)
|
||||
.field("buffer_size", &self.buffer_size)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub type SortedFrameBuffer = SortedBuffer<i64, Frame>;
|
||||
|
||||
impl SortedFrameBuffer {
|
||||
pub fn insert_frame(&mut self, frame: Frame) -> bool {
|
||||
self.insert(frame.pts(), frame)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Copy + Debug + Eq + Hash, V: Debug> Default for SortedBuffer<K, V> {
|
||||
fn default() -> Self {
|
||||
SortedBuffer {
|
||||
lowest_value: None,
|
||||
sorted_keys: vec![],
|
||||
items: Default::default(),
|
||||
open: true,
|
||||
buffer_size: 32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Copy + Debug + Eq + Hash + Ord, V: Debug> SortedBuffer<K, V> {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn with_buffer_size(buffer_size: usize) -> Self {
|
||||
let mut buffer: Self = Default::default();
|
||||
buffer.buffer_size = buffer_size;
|
||||
buffer
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, key: K, item: V) -> bool {
|
||||
if self.items.len() >= self.buffer_size
|
||||
&& self.lowest_value.map(|it| it > key).unwrap_or(false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
self.insert_key(key);
|
||||
self.items.insert(key, item);
|
||||
true
|
||||
}
|
||||
|
||||
fn insert_key(&mut self, key: K) {
|
||||
if self.lowest_value.map(|it| it > key).unwrap_or(true) {
|
||||
self.lowest_value = Some(key)
|
||||
}
|
||||
|
||||
let mut i = 0;
|
||||
for entry in &self.sorted_keys {
|
||||
if key < *entry {
|
||||
break;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
self.sorted_keys.insert(i, key);
|
||||
}
|
||||
|
||||
pub fn close(&mut self) {
|
||||
self.open = false;
|
||||
}
|
||||
|
||||
pub fn pop_first(&mut self) -> Option<V> {
|
||||
if self.sorted_keys.len() == 0 || (self.open && self.buffer_size >= self.items.len()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let first = self.sorted_keys.remove(0);
|
||||
if let Some(new_first) = self.sorted_keys.get(0).copied() {
|
||||
self.lowest_value = Some(new_first);
|
||||
}
|
||||
|
||||
self.items.remove(&first)
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.sorted_keys.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::utils::SortedBuffer;
|
||||
|
||||
#[test]
|
||||
fn test_sorted_buffer() {
|
||||
let mut buffer = SortedBuffer::with_buffer_size(5);
|
||||
assert!(buffer.insert(2, 2));
|
||||
assert!(buffer.insert(1, 1));
|
||||
assert!(buffer.insert(3, 3));
|
||||
assert!(buffer.insert(4, 4));
|
||||
assert!(buffer.insert(5, 5));
|
||||
assert!(!buffer.insert(0, 0));
|
||||
assert_eq!(buffer.len(), 5);
|
||||
assert!(buffer.insert(6, 6));
|
||||
assert_eq!(buffer.len(), 6);
|
||||
assert_eq!(buffer.pop_first(), Some(1));
|
||||
assert_eq!(buffer.pop_first(), None);
|
||||
assert_eq!(buffer.len(), 5);
|
||||
buffer.close();
|
||||
assert_eq!(buffer.pop_first(), Some(2));
|
||||
assert_eq!(buffer.pop_first(), Some(3));
|
||||
assert_eq!(buffer.pop_first(), Some(4));
|
||||
assert_eq!(buffer.pop_first(), Some(5));
|
||||
assert_eq!(buffer.pop_first(), Some(6));
|
||||
assert_eq!(buffer.pop_first(), None);
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>DASH Test</title>
|
||||
<link href="//vjs.zencdn.net/7.8.2/video-js.min.css" rel="stylesheet">
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/shaka-player/3.0.1/shaka-player.ui.js"></script>
|
||||
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/shaka-player/3.0.1/controls.css">
|
||||
</head>
|
||||
<body>
|
||||
<video id="shaka" width="1280" height="720" controls></video>
|
||||
|
||||
<script type="application/javascript">
|
||||
const manifestUri = 'test.mpd';
|
||||
|
||||
function initApp() {
|
||||
// Install built-in polyfills to patch browser incompatibilities.
|
||||
shaka.polyfill.installAll();
|
||||
|
||||
// Check to see if the browser supports the basic APIs Shaka needs.
|
||||
if (shaka.Player.isBrowserSupported()) {
|
||||
// Everything looks good!
|
||||
initPlayer();
|
||||
} else {
|
||||
// This browser does not have the minimum set of APIs we need.
|
||||
console.error('Browser not supported!');
|
||||
}
|
||||
}
|
||||
|
||||
async function initPlayer() {
|
||||
// Create a Player instance.
|
||||
const video = document.getElementById('shaka');
|
||||
const player = new shaka.Player(video);
|
||||
|
||||
// Attach player to the window to make it easy to access in the JS console.
|
||||
window.shakaPlayer = player;
|
||||
|
||||
// Listen for error events.
|
||||
player.addEventListener('error', onErrorEvent);
|
||||
|
||||
// Try to load a manifest.
|
||||
// This is an asynchronous process.
|
||||
try {
|
||||
await player.load(manifestUri);
|
||||
// This runs if the asynchronous load is successful.
|
||||
console.log('The video has now been loaded!');
|
||||
} catch (e) {
|
||||
// onError is executed if the asynchronous load fails.
|
||||
onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
function onErrorEvent(event) {
|
||||
// Extract the shaka.util.Error object from the event.
|
||||
onError(event.detail);
|
||||
}
|
||||
|
||||
function onError(error) {
|
||||
// Log the error.
|
||||
console.error('Error code', error.code, 'object', error);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initApp);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0"?>
|
||||
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"
|
||||
profiles="urn:mpeg:dash:profile:isoff-live:2011"
|
||||
type="static"
|
||||
minBufferTime="PT6.1S"
|
||||
mediaPresentationDuration="PT1M30S">
|
||||
<Period start="PT0.0S">
|
||||
<BaseURL>data/</BaseURL>
|
||||
<AdaptationSet contentType="video">
|
||||
<Representation mimeType="video/mp4" codecs="avc1.640028" id="test" bandwidth="1654197" width="1920" height="1080" frameRate="24000/1001">
|
||||
<SegmentTemplate media="video-segment-$Number%05d$.m4s"
|
||||
initialization="video-init.mp4" duration="5005" timescale="1001"
|
||||
startNumber="0"/>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>DASH Test</title>
|
||||
<link href="//vjs.zencdn.net/7.8.2/video-js.min.css" rel="stylesheet">
|
||||
<script src="//vjs.zencdn.net/7.8.2/video.js"></script>
|
||||
<script src="https://unpkg.com/@videojs/http-streaming@1.13.3/dist/videojs-http-streaming.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video-js controls width=1280 height=720 class="video-js" data-setup='{}'>
|
||||
<source type="application/dash+xml" src="test.mpd">
|
||||
</video-js>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue