From 75ce22c581e4ad70287398af9abb8195f3fc38ea Mon Sep 17 00:00:00 2001 From: eater <=@eater.me> Date: Thu, 9 Jul 2020 15:48:44 +0200 Subject: [PATCH] Initial commit --- .gitignore | 1 + Cargo.lock | 447 +++++++++++++++++++++++++++++++++ Cargo.toml | 20 ++ src/av/as_ptr.rs | 17 ++ src/av/avio.rs | 170 +++++++++++++ src/av/codec_parameters.rs | 102 ++++++++ src/av/decoder.rs | 58 +++++ src/av/decoder_selector.rs | 39 +++ src/av/dictionary.rs | 92 +++++++ src/av/dynbuf.rs | 55 +++++ src/av/encoder.rs | 98 ++++++++ src/av/encoder_selector.rs | 39 +++ src/av/format.rs | 282 +++++++++++++++++++++ src/av/frame.rs | 140 +++++++++++ src/av/mod.rs | 334 +++++++++++++++++++++++++ src/av/mov_avc.rs | 332 +++++++++++++++++++++++++ src/av/mov_mux.rs | 254 +++++++++++++++++++ src/av/options.rs | 38 +++ src/av/packet.rs | 104 ++++++++ src/av/resampler.rs | 60 +++++ src/av/scaler.rs | 152 ++++++++++++ src/av/stream.rs | 107 ++++++++ src/av/xcoder.rs | 257 +++++++++++++++++++ src/main.rs | 35 +++ src/transcoder.rs | 488 +++++++++++++++++++++++++++++++++++++ src/utils/mod.rs | 135 ++++++++++ test/data | 1 + test/index.html | 66 +++++ test/test.mpd | 17 ++ test/videojs.html | 15 ++ 30 files changed, 3955 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/av/as_ptr.rs create mode 100644 src/av/avio.rs create mode 100644 src/av/codec_parameters.rs create mode 100644 src/av/decoder.rs create mode 100644 src/av/decoder_selector.rs create mode 100644 src/av/dictionary.rs create mode 100644 src/av/dynbuf.rs create mode 100644 src/av/encoder.rs create mode 100644 src/av/encoder_selector.rs create mode 100644 src/av/format.rs create mode 100644 src/av/frame.rs create mode 100644 src/av/mod.rs create mode 100644 src/av/mov_avc.rs create mode 100644 src/av/mov_mux.rs create mode 100644 src/av/options.rs create mode 100644 src/av/packet.rs create mode 100644 src/av/resampler.rs create mode 100644 src/av/scaler.rs create mode 100644 src/av/stream.rs create mode 100644 src/av/xcoder.rs create mode 100644 src/main.rs create mode 100644 src/transcoder.rs create mode 100644 src/utils/mod.rs create mode 120000 test/data create mode 100644 test/index.html create mode 100644 test/test.mpd create mode 100644 test/videojs.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7b0f9bc --- /dev/null +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..12d66f5 --- /dev/null +++ b/Cargo.toml @@ -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"] diff --git a/src/av/as_ptr.rs b/src/av/as_ptr.rs new file mode 100644 index 0000000..d5d215f --- /dev/null +++ b/src/av/as_ptr.rs @@ -0,0 +1,17 @@ +use std::os::raw::c_void; + +pub trait AsPtr { + fn as_ptr(&self) -> *const T; +} + +pub trait AsMutPtr { + 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; +} diff --git a/src/av/avio.rs b/src/av/avio.rs new file mode 100644 index 0000000..6e3d249 --- /dev/null +++ b/src/av/avio.rs @@ -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 { + ctx: *mut AVIOContext, + inner: Box>, +} + +impl AVIO { + pub fn flush(&self) { + unsafe { + avio_flush(self.as_mut_ptr()); + } + } +} + +#[derive(Clone)] +struct AVIOInner { + data: Box, + read: Option Vec>, + write: Option)>, + seek: Option, +} + +unsafe extern "C" fn read_packet_with_inner( + ctx: *mut c_void, + buffer: *mut u8, + buffer_size: i32, +) -> i32 { + let mut avio: Box>> = 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>> = 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>> = Box::from_raw(ctx.cast()); + avio.data.seek(offset, whence); + + 0 +} + +impl AVIO { + fn new( + buffer_size: usize, + obj: T, + read: Option Vec>, + write: Option)>, + seek: Option, + ) -> AVIO { + 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 = &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 AsMutPtr for AVIO { + fn as_mut_ptr(&self) -> *mut AVIOContext { + self.ctx + } +} + +impl AsPtr for AVIO { + fn as_ptr(&self) -> *const AVIOContext { + self.ctx + } +} + +impl AVIO { + pub fn duplex_with_seek(item: T) -> AVIO { + AVIO::new(4096, item, Some(T::read), Some(T::write), Some(T::seek)) + } +} + +impl AVIO { + pub fn duplex(item: T) -> AVIO { + AVIO::new(4096, item, Some(T::read), Some(T::write), None) + } +} + +impl AVIO { + pub fn reader_with_seek(item: T) -> AVIO { + AVIO::new(4096, item, Some(T::read), None, Some(T::seek)) + } +} + +impl AVIO { + pub fn reader(item: T) -> AVIO { + AVIO::new(4096, item, Some(T::read), None, None) + } +} + +impl AVIO { + pub fn writer_with_seek(item: T) -> AVIO { + AVIO::new(4096, item, None, Some(T::write), Some(T::seek)) + } +} + +impl AVIO { + pub fn writer(item: T) -> AVIO { + 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); +} + +pub trait AVIOReader { + fn read(&mut self, length: i32) -> Vec; +} diff --git a/src/av/codec_parameters.rs b/src/av/codec_parameters.rs new file mode 100644 index 0000000..bb0f48c --- /dev/null +++ b/src/av/codec_parameters.rs @@ -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 { + 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) { + 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(()) + } +} diff --git a/src/av/decoder.rs b/src/av/decoder.rs new file mode 100644 index 0000000..cb55c7b --- /dev/null +++ b/src/av/decoder.rs @@ -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, 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 { + 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(()) + } +} diff --git a/src/av/decoder_selector.rs b/src/av/decoder_selector.rs new file mode 100644 index 0000000..c48a0ae --- /dev/null +++ b/src/av/decoder_selector.rs @@ -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, +} + +impl DecoderSelector { + pub fn for_codec_id(codec_id: AVCodecID) -> DecoderSelector { + DecoderSelector { + decoders: decoders_for_codec(codec_id), + } + } + + pub fn select( + &self, + hwaccel: Option, + config_fn: impl Fn(&Decoder) -> Result<(), String>, + ) -> Result { + 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 { + config_fn(&d)?; + Ok(d) + }) + { + return Ok(decoder); + } + } + + Err("No working decoders found".to_string()) + } +} diff --git a/src/av/dictionary.rs b/src/av/dictionary.rs new file mode 100644 index 0000000..16e3a1c --- /dev/null +++ b/src/av/dictionary.rs @@ -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 { + 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); +} diff --git a/src/av/dynbuf.rs b/src/av/dynbuf.rs new file mode 100644 index 0000000..59e78e7 --- /dev/null +++ b/src/av/dynbuf.rs @@ -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 { + 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 for Dynbuf { + fn as_ptr(&self) -> *const AVIOContext { + self.ptr + } +} + +impl AsMutPtr for Dynbuf { + fn as_mut_ptr(&self) -> *mut AVIOContext { + self.ptr + } +} diff --git a/src/av/encoder.rs b/src/av/encoder.rs new file mode 100644 index 0000000..3b3d208 --- /dev/null +++ b/src/av/encoder.rs @@ -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, 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 { + 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()) } + } +} diff --git a/src/av/encoder_selector.rs b/src/av/encoder_selector.rs new file mode 100644 index 0000000..5c92b90 --- /dev/null +++ b/src/av/encoder_selector.rs @@ -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, +} + +impl EncoderSelector { + pub fn for_codec_id(codec_id: AVCodecID) -> EncoderSelector { + EncoderSelector { + encoders: encoders_for_codec(codec_id), + } + } + + pub fn select( + &self, + hwaccel: Option, + config_fn: impl Fn(&Encoder) -> Result<(), String>, + ) -> Result { + 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 { + config_fn(&d)?; + Ok(d) + }) + { + return Ok(encoder); + } + } + + Err("No working encoders found".to_string()) + } +} diff --git a/src/av/format.rs b/src/av/format.rs new file mode 100644 index 0000000..6cd169f --- /dev/null +++ b/src/av/format.rs @@ -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 { + pub ctx: *mut AVFormatContext, + pub avio: Option>, +} + +impl Debug for Format { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + f.debug_struct("Format<_>").field("ctx", &self.ctx).finish() + } +} + +impl Format { + 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 Format { + pub fn stream(&self, stream_type: AVMediaType, index: Option) -> Option { + 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 { + 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) -> 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 { + 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 Format { + pub fn output_avio<'a>(avio: AVIO, format: &str) -> Result, 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 { + pub fn output( + path: &str, + format: Option<&str>, + options: Option, + ) -> Result, 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) -> Result, 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 AsPtr for Format { + #[inline] + fn as_ptr(&self) -> *const AVFormatContext { + self.ctx + } +} + +impl AsMutPtr for Format { + #[inline] + fn as_mut_ptr(&self) -> *mut AVFormatContext { + self.ctx + } +} + +impl AsVoidPtr for Format { + #[inline] + fn as_void_ptr(&self) -> *const c_void { + self.as_ptr().cast() + } +} + +impl AsMutVoidPtr for Format { + #[inline] + fn as_mut_void_ptr(&self) -> *mut c_void { + self.as_mut_ptr().cast() + } +} + +impl Options for Format {} diff --git a/src/av/frame.rs b/src/av/frame.rs new file mode 100644 index 0000000..33aa2f7 --- /dev/null +++ b/src/av/frame.rs @@ -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 for Frame { + #[inline] + fn as_ptr(&self) -> *const AVFrame { + self.ptr + } +} + +impl AsMutPtr 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) } + } +} diff --git a/src/av/mod.rs b/src/av/mod.rs new file mode 100644 index 0000000..d607395 --- /dev/null +++ b/src/av/mod.rs @@ -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 = None; + +pub fn get_best_encoder(codec: AVCodecID, hw_accel: Option) -> Option { + let encoders = get_cache().encoders.get(&codec)?; + get_best_codec(encoders, hw_accel) +} + +pub fn get_best_codec( + codecs: &Vec, + should_be_hw_accel: Option, +) -> Option { + 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) -> Option { + 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 { + return if let Some(codecs) = get_cache().encoders.get(&codec_id) { + let mut codecs: Vec = codecs.to_vec(); + codecs.sort_by_key(|x| !x.hwaccel); + codecs + } else { + vec![] + }; +} + +pub fn decoders_for_codec(codec_id: AVCodecID) -> Vec { + return if let Some(codecs) = get_cache().decoders.get(&codec_id) { + let mut codecs: Vec = 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, needle: &str, to_remove: Vec<&str>) { + let mut found_deprecated = vec![]; + let mut found_new = false; + let mut i = 0; + for codec in Borrow::>::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 { + if resp < 0 { + Err(format!("{} (code {}, {})", error, resp, av_err2str(resp))) + } else { + Ok(resp) + } +} + +#[derive(Default, Debug)] +pub struct FfmpegInfoCache { + pub decoders: HashMap>, + pub encoders: HashMap>, +} + +#[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 { + Ratio::new(self.num(), self.den()) + } + + fn num(&self) -> i32; + fn den(&self) -> i32; + fn invert(&self) -> Self; + fn trunc(&self) -> Ratio { + self.to_num().trunc() + } + + fn debug(&self) -> String { + return format!( + "Rational[{}]({}/{})", + type_name::(), + self.num(), + self.den() + ); + } + + fn simplify(&self) -> Self; +} + +impl Rational for Ratio { + #[inline] + fn new(num: i32, den: i32) -> Self { + Ratio::new(num, den) + } + + #[inline] + fn to_num(&self) -> Ratio { + *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) + } +} diff --git a/src/av/mov_avc.rs b/src/av/mov_avc.rs new file mode 100644 index 0000000..7c41d2b --- /dev/null +++ b/src/av/mov_avc.rs @@ -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, + pub picture_parameter_set: Vec, +} + +impl AvcDecoderConfigurationRecord { + pub fn from_parameter_sets( + sequence_parameter_set: Vec, + picture_parameter_set: Vec, + ) -> 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, 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::(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::(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 { + 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 { + 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, +} + +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) -> Option { + 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) { + 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::().expect(":O") as u64; + cursor.set_position(index); + while cursor.position() < (size + index - 8) { + let skip = cursor.read_u32::().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, nal_length: u8) -> Result, String> { + let nal_length = nal_length as usize; + let mut result: Vec = 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::().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::(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::(((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 { + 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) -> Result, String> { + let mut i = 0; + let mut buffers = vec![]; + let mut byte_buffer = vec![]; + let mut current: Option = 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 = 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 = vec![0, 0, 0, insert.len() as u8 + 8]; + let mut result: Vec = 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); + } +} diff --git a/src/av/mov_mux.rs b/src/av/mov_mux.rs new file mode 100644 index 0000000..55646a6 --- /dev/null +++ b/src/av/mov_mux.rs @@ -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, +} diff --git a/src/av/options.rs b/src/av/options.rs new file mode 100644 index 0000000..27b1818 --- /dev/null +++ b/src/av/options.rs @@ -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 + } +} diff --git a/src/av/packet.rs b/src/av/packet.rs new file mode 100644 index 0000000..f6ec793 --- /dev/null +++ b/src/av/packet.rs @@ -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(&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 { + 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 { + 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); + } + } +} diff --git a/src/av/resampler.rs b/src/av/resampler.rs new file mode 100644 index 0000000..c5c03fb --- /dev/null +++ b/src/av/resampler.rs @@ -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 { + 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 + } + } +} diff --git a/src/av/scaler.rs b/src/av/scaler.rs new file mode 100644 index 0000000..1a8ea93 --- /dev/null +++ b/src/av/scaler.rs @@ -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 { + 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 for Scaler { + fn as_ptr(&self) -> *const SwsContext { + self.ctx + } +} + +impl AsMutPtr for Scaler { + fn as_mut_ptr(&self) -> *mut SwsContext { + self.ctx + } +} diff --git a/src/av/stream.rs b/src/av/stream.rs new file mode 100644 index 0000000..58b68d9 --- /dev/null +++ b/src/av/stream.rs @@ -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) -> Option { + get_best_decoder(self.codec(), hw_accel) + .and_then(|decoder_name| Decoder::from_name(&decoder_name)) + } + + pub fn decoder_select( + &self, + hw_accel: Option, + select_fn: impl Fn(&Decoder) -> Result<(), String>, + ) -> Result { + DecoderSelector::for_codec_id(self.codec()).select(hw_accel, select_fn) + } + + pub fn encoder(&self, hw_accel: Option) -> Option { + 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, + select_fn: impl Fn(&Encoder) -> Result<(), String>, + ) -> Result { + 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 { + unsafe { (*self.0).time_base.to_num() } + } + + #[inline] + pub fn set_time_base(&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(&self, fmt: &Format) -> Option { + Some(unsafe { av_guess_frame_rate(fmt.as_mut_ptr(), self.as_mut_ptr(), null_mut()) }) + } +} + +impl AsMutPtr for Stream { + #[inline] + fn as_mut_ptr(&self) -> *mut AVStream { + self.0 + } +} + +impl AsPtr for Stream { + #[inline] + fn as_ptr(&self) -> *const AVStream { + self.0 + } +} diff --git a/src/av/xcoder.rs b/src/av/xcoder.rs new file mode 100644 index 0000000..01cd003 --- /dev/null +++ b/src/av/xcoder.rs @@ -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; + 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 { + unsafe { (*self.as_ptr()).time_base.to_num() } + } + + #[inline] + fn set_time_base(&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 { + 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) { + 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 { + 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 { + 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 { + if resp == AVERROR(EAGAIN) || resp == AVERROR_EOF { + Ok(false) + } else { + verify_response(error, resp).and(Ok(true)) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..086ab61 --- /dev/null +++ b/src/main.rs @@ -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"); +} diff --git a/src/transcoder.rs b/src/transcoder.rs new file mode 100644 index 0000000..2f64e7d --- /dev/null +++ b/src/transcoder.rs @@ -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, + video_output: Format, + 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>, + audio: Option, +} + +pub struct AudioTranscoder { + encoder: Encoder, + decoder: Decoder, + frame_buffer: SortedFrameBuffer, + output_stream: Stream, + input_stream: Stream, + format: Format, + 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, + audio_index: Option, + ) -> Result { + 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, +} + +impl Buffer { + fn buffer(&mut self) -> Vec { + std::mem::replace(&mut self.buffer, vec![]) + } +} + +impl AVIOWriter for Buffer { + fn write(&mut self, mut buffer: Vec) { + 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) } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..1bef5a6 --- /dev/null +++ b/src/utils/mod.rs @@ -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 { + lowest_value: Option, + sorted_keys: Vec, + items: HashMap, + open: bool, + buffer_size: usize, +} + +impl Debug for SortedBuffer { + 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; + +impl SortedFrameBuffer { + pub fn insert_frame(&mut self, frame: Frame) -> bool { + self.insert(frame.pts(), frame) + } +} + +impl Default for SortedBuffer { + fn default() -> Self { + SortedBuffer { + lowest_value: None, + sorted_keys: vec![], + items: Default::default(), + open: true, + buffer_size: 32, + } + } +} + +impl SortedBuffer { + 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 { + 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); + } +} diff --git a/test/data b/test/data new file mode 120000 index 0000000..caf692c --- /dev/null +++ b/test/data @@ -0,0 +1 @@ +/tmp/transotf \ No newline at end of file diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..69ac45b --- /dev/null +++ b/test/index.html @@ -0,0 +1,66 @@ + + + + + DASH Test + + + + + + + + + + \ No newline at end of file diff --git a/test/test.mpd b/test/test.mpd new file mode 100644 index 0000000..4b837b0 --- /dev/null +++ b/test/test.mpd @@ -0,0 +1,17 @@ + + + + data/ + + + + + + + \ No newline at end of file diff --git a/test/videojs.html b/test/videojs.html new file mode 100644 index 0000000..fff88e6 --- /dev/null +++ b/test/videojs.html @@ -0,0 +1,15 @@ + + + + + DASH Test + + + + + + + + + +