Initial commit

master
eater 5 years ago
commit 75ce22c581
Signed by: eater
GPG Key ID: AD2560A0F84F0759

1
.gitignore vendored

@ -0,0 +1 @@
/target

447
Cargo.lock generated

@ -0,0 +1,447 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "aho-corasick"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
"winapi",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
[[package]]
name = "bindgen"
version = "0.54.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66c0bb6167449588ff70803f4127f0684f9063097eca5016f37eb52b92c2cf36"
dependencies = [
"bitflags",
"cexpr",
"cfg-if",
"clang-sys",
"clap",
"env_logger",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"which",
]
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "byteorder"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]]
name = "cc"
version = "1.0.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311"
[[package]]
name = "cexpr"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "clang-sys"
version = "0.29.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "2.33.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "env_logger"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "ffmpeg-sys-next"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01bba495d04757f7a3e471b3e411759a8968571c4618235bc1c9e1099b4a84d1"
dependencies = [
"bindgen",
"cc",
"libc",
"num_cpus",
"pkg-config",
"regex",
]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "hermit-abi"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909"
dependencies = [
"libc",
]
[[package]]
name = "humantime"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
dependencies = [
"quick-error",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
[[package]]
name = "libc"
version = "0.2.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
[[package]]
name = "libloading"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753"
dependencies = [
"cc",
"winapi",
]
[[package]]
name = "log"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"memchr",
"version_check",
]
[[package]]
name = "num-bigint"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f3fc75e3697059fb1bc465e3d8cca6cf92f56854f201158b3f9c77d5a3cfa0"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138"
dependencies = [
"autocfg",
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "pkg-config"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
[[package]]
name = "proc-macro2"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"thread_local",
]
[[package]]
name = "regex-syntax"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "shlex"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "termcolor"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "thread_local"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
dependencies = [
"lazy_static",
]
[[package]]
name = "transotf"
version = "0.1.0"
dependencies = [
"byteorder",
"ffmpeg-sys-next",
"num-rational",
]
[[package]]
name = "unicode-width"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
name = "which"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
dependencies = [
"libc",
]
[[package]]
name = "winapi"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

@ -0,0 +1,20 @@
[package]
name = "transotf"
version = "0.1.0"
authors = ["eater <=@eater.me>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
num-rational = "0.3.0"
byteorder = "1.3.4"
[dependencies.ffmpeg-sys-next]
version = "4.3.0"
features = ["avcodec", "avdevice", "avfilter", "avformat", "swresample", "swscale"]
#[dependencies.ffmpeg-next]
#version = "4.3.0"
#default-features = false
#features = ["ffmpeg42","codec", "device", "filter", "format", "software-resampling", "software-scaling"]

@ -0,0 +1,17 @@
use std::os::raw::c_void;
pub trait AsPtr<T> {
fn as_ptr(&self) -> *const T;
}
pub trait AsMutPtr<T> {
fn as_mut_ptr(&self) -> *mut T;
}
pub trait AsVoidPtr {
fn as_void_ptr(&self) -> *const c_void;
}
pub trait AsMutVoidPtr {
fn as_mut_void_ptr(&self) -> *mut c_void;
}

@ -0,0 +1,170 @@
use crate::av::as_ptr::{AsMutPtr, AsPtr};
use ffmpeg_sys_next::{av_malloc, avio_alloc_context, avio_flush, AVIOContext};
use std::cmp::min;
use std::os::raw::c_void;
use std::vec::Vec;
#[derive(Clone)]
pub struct AVIO<T> {
ctx: *mut AVIOContext,
inner: Box<AVIOInner<T>>,
}
impl<T> AVIO<T> {
pub fn flush(&self) {
unsafe {
avio_flush(self.as_mut_ptr());
}
}
}
#[derive(Clone)]
struct AVIOInner<T> {
data: Box<T>,
read: Option<fn(&mut T, i32) -> Vec<u8>>,
write: Option<fn(&mut T, Vec<u8>)>,
seek: Option<fn(&mut T, i64, i32)>,
}
unsafe extern "C" fn read_packet_with_inner(
ctx: *mut c_void,
buffer: *mut u8,
buffer_size: i32,
) -> i32 {
let mut avio: Box<AVIOInner<Box<dyn AVIOReader>>> = Box::from_raw(ctx.cast());
let items = avio.data.read(buffer_size);
let len = min(items.len(), buffer_size as usize);
buffer.copy_from(items.as_ptr().cast(), len);
len as i32
}
unsafe extern "C" fn write_packet_with_inner(ctx: *mut c_void, buffer: *mut u8, size: i32) -> i32 {
let mut avio: Box<AVIOInner<Box<dyn AVIOWriter>>> = Box::from_raw(ctx.cast());
let mut byte_buffer = vec![0; size as usize];
buffer.copy_to(byte_buffer.as_mut_ptr(), size as usize);
let write_fn = avio.write.unwrap();
write_fn(&mut avio.data, byte_buffer);
std::mem::forget(avio);
size
}
unsafe extern "C" fn seek_with_inner(ctx: *mut c_void, offset: i64, whence: i32) -> i64 {
println!("ok");
let mut avio: Box<AVIOInner<Box<dyn AVIOSeekable>>> = Box::from_raw(ctx.cast());
avio.data.seek(offset, whence);
0
}
impl<T> AVIO<T> {
fn new(
buffer_size: usize,
obj: T,
read: Option<fn(&mut T, i32) -> Vec<u8>>,
write: Option<fn(&mut T, std::vec::Vec<u8>)>,
seek: Option<fn(&mut T, i64, i32)>,
) -> AVIO<T> {
let mut opaque = Box::new(AVIOInner {
data: Box::new(obj),
read,
write,
seek,
});
let ctx = unsafe {
let buf: *mut u8 = av_malloc(buffer_size).cast();
let opaque_c: *mut AVIOInner<T> = &mut *opaque;
avio_alloc_context(
buf,
buffer_size as i32,
if write.is_some() { 1 } else { 0 },
opaque_c.cast(),
if read.is_some() {
Some(read_packet_with_inner)
} else {
None
},
if write.is_some() {
Some(write_packet_with_inner)
} else {
None
},
if seek.is_some() {
Some(seek_with_inner)
} else {
None
},
)
};
AVIO { ctx, inner: opaque }
}
pub fn inner(&self) -> &T {
self.inner.data.as_ref()
}
pub fn inner_mut(&mut self) -> &mut T {
self.inner.data.as_mut()
}
}
impl<T> AsMutPtr<AVIOContext> for AVIO<T> {
fn as_mut_ptr(&self) -> *mut AVIOContext {
self.ctx
}
}
impl<T> AsPtr<AVIOContext> for AVIO<T> {
fn as_ptr(&self) -> *const AVIOContext {
self.ctx
}
}
impl<T: AVIOReader + AVIOWriter + AVIOSeekable> AVIO<T> {
pub fn duplex_with_seek(item: T) -> AVIO<T> {
AVIO::new(4096, item, Some(T::read), Some(T::write), Some(T::seek))
}
}
impl<T: AVIOReader + AVIOWriter> AVIO<T> {
pub fn duplex(item: T) -> AVIO<T> {
AVIO::new(4096, item, Some(T::read), Some(T::write), None)
}
}
impl<T: AVIOReader + AVIOSeekable> AVIO<T> {
pub fn reader_with_seek(item: T) -> AVIO<T> {
AVIO::new(4096, item, Some(T::read), None, Some(T::seek))
}
}
impl<T: AVIOReader> AVIO<T> {
pub fn reader(item: T) -> AVIO<T> {
AVIO::new(4096, item, Some(T::read), None, None)
}
}
impl<T: AVIOWriter + AVIOSeekable> AVIO<T> {
pub fn writer_with_seek(item: T) -> AVIO<T> {
AVIO::new(4096, item, None, Some(T::write), Some(T::seek))
}
}
impl<T: AVIOWriter> AVIO<T> {
pub fn writer(item: T) -> AVIO<T> {
AVIO::new(4096, item, None, Some(T::write), None)
}
}
pub trait AVIOSeekable {
fn seek(&mut self, offset: i64, whence: i32);
}
pub trait AVIOWriter {
fn write(&mut self, buffer: Vec<u8>);
}
pub trait AVIOReader {
fn read(&mut self, length: i32) -> Vec<u8>;
}

@ -0,0 +1,102 @@
use crate::av::{verify_response, Rational};
use ffmpeg_sys_next::{
av_malloc, avcodec_parameters_copy, AVCodecID, AVCodecParameters, AVPixelFormat, AVRational,
};
use std::mem;
pub struct CodecParameters(pub *mut AVCodecParameters);
impl CodecParameters {
#[inline]
pub fn as_ptr(&self) -> *const AVCodecParameters {
self.0
}
#[inline]
pub fn as_mut_ptr(&self) -> *mut AVCodecParameters {
self.0
}
pub fn codec(&self) -> AVCodecID {
unsafe { (*self.as_ptr()).codec_id }
}
#[inline]
pub fn width(&self) -> i32 {
unsafe { (*self.as_ptr()).width }
}
#[inline]
pub fn set_width(&self, width: i32) {
unsafe { (*self.as_mut_ptr()).width = width }
}
#[inline]
pub fn height(&self) -> i32 {
unsafe { (*self.as_ptr()).height }
}
#[inline]
pub fn set_height(&self, height: i32) {
unsafe { (*self.as_mut_ptr()).height = height }
}
#[inline]
pub fn pixel_format(&self) -> AVPixelFormat {
unsafe { mem::transmute((*self.as_ptr()).format) }
}
#[inline]
pub fn set_pixel_format(&self, pixel_format: AVPixelFormat) {
unsafe { (*self.as_mut_ptr()).format = mem::transmute(pixel_format) }
}
#[inline]
pub fn extra_data(&self) -> Vec<u8> {
unsafe {
let extra_data_size = (*self.as_ptr()).extradata_size as usize;
let mut extra_data = vec![0u8; extra_data_size];
(*self.as_ptr())
.extradata
.copy_to(extra_data.as_mut_ptr(), extra_data_size);
extra_data
}
}
#[inline]
pub fn set_extra_data(&self, extra_data: &Vec<u8>) {
unsafe {
(*self.as_mut_ptr()).extradata_size = 0;
let new_extra_data: *mut u8 = av_malloc(extra_data.len() + 64).cast();
new_extra_data.copy_from(extra_data.as_ptr(), extra_data.len());
(*self.as_mut_ptr()).extradata = new_extra_data;
(*self.as_mut_ptr()).extradata_size = extra_data.len() as i32;
}
}
#[inline]
pub fn sample_aspect_ratio(&self) -> AVRational {
unsafe { (*self.as_ptr()).sample_aspect_ratio }
}
#[inline]
pub fn set_sample_aspect_ratio(&self, sample_aspect_ratio: impl Rational) {
unsafe { (*self.as_mut_ptr()).sample_aspect_ratio = sample_aspect_ratio.to_av() }
}
pub fn copy_to(&self, to: &CodecParameters) -> Result<(), String> {
Self::copy(&self, to)
}
pub fn copy_from(&self, from: &CodecParameters) -> Result<(), String> {
Self::copy(from, &self)
}
pub fn copy(from: &CodecParameters, to: &CodecParameters) -> Result<(), String> {
verify_response("Failed to copy codec parameters", unsafe {
avcodec_parameters_copy(to.as_mut_ptr(), from.as_ptr())
})?;
Ok(())
}
}

@ -0,0 +1,58 @@
use crate::av::as_ptr::AsMutPtr;
use crate::av::frame::Frame;
use crate::av::packet::Packet;
use crate::av::stream::Stream;
use crate::av::xcoder::{process_return, XCoder};
use crate::av::{decoder_from_name, verify_response};
use crate::av_err2str;
use ffmpeg_sys_next::{
avcodec_parameters_to_context, avcodec_receive_frame, avcodec_send_packet, AVCodecContext,
};
#[derive(Copy, Clone)]
pub struct Decoder(pub *mut AVCodecContext);
impl Decoder {
pub fn send_packet(&self, packet: &Packet) -> Result<(), String> {
verify_response("Failed sending packet to decoder", unsafe {
avcodec_send_packet(self.0, packet.as_mut_ptr())
})
.and(Ok(()))
}
pub fn read_frame(&self) -> Result<Option<Frame>, String> {
let frame = Frame::alloc();
if !process_return("Failed reading frame from decoder", unsafe {
avcodec_receive_frame(self.as_mut_ptr(), frame.as_mut_ptr())
})? {
Ok(None)
} else {
Ok(Some(frame))
}
}
}
impl XCoder for Decoder {
fn from_name(name: &str) -> Option<Self> {
decoder_from_name(name).map(|codec| Decoder(codec))
}
fn as_mut_ptr(&self) -> *mut AVCodecContext {
self.0
}
fn configure(&self, stream: &Stream) -> Result<(), String> {
let resp =
unsafe { avcodec_parameters_to_context(self.as_mut_ptr(), (*stream.0).codecpar) };
if resp < 0 {
return Err(format!(
"Failed configuring decoder from stream (code {}: {})",
resp,
av_err2str(resp)
));
}
Ok(())
}
}

@ -0,0 +1,39 @@
use crate::av::decoder::Decoder;
use crate::av::xcoder::XCoder;
use crate::av::{decoders_for_codec, FfmpegCodec};
use ffmpeg_sys_next::AVCodecID;
pub struct DecoderSelector {
decoders: Vec<FfmpegCodec>,
}
impl DecoderSelector {
pub fn for_codec_id(codec_id: AVCodecID) -> DecoderSelector {
DecoderSelector {
decoders: decoders_for_codec(codec_id),
}
}
pub fn select(
&self,
hwaccel: Option<bool>,
config_fn: impl Fn(&Decoder) -> Result<(), String>,
) -> Result<Decoder, String> {
for decoder in &self.decoders {
if !hwaccel.is_none() && hwaccel != Some(decoder.hwaccel) {
continue;
}
if let Some(Ok(decoder)) =
Decoder::from_name(&decoder.name).map(|d| -> Result<Decoder, String> {
config_fn(&d)?;
Ok(d)
})
{
return Ok(decoder);
}
}
Err("No working decoders found".to_string())
}
}

@ -0,0 +1,92 @@
use ffmpeg_sys_next::{av_dict_free, av_dict_get, av_dict_set, av_dict_set_int, AVDictionary};
use std::ffi::CString;
use std::ptr::{null, null_mut};
pub struct Dictionary {
pub ptr: *mut AVDictionary,
pub owned: bool,
}
impl Dictionary {
#[inline]
pub fn as_ptr(&self) -> *const AVDictionary {
self.ptr
}
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut AVDictionary {
self.ptr
}
pub fn new() -> Dictionary {
Dictionary {
ptr: null_mut(),
owned: false,
}
}
pub fn disown(&mut self) {
self.owned = false;
}
pub fn own(&mut self) {
self.owned = true;
}
pub fn set(&mut self, key: &str, value: &str) {
let key_c = CString::new(key).unwrap();
let value_c = CString::new(value).unwrap();
unsafe {
av_dict_set(&mut self.ptr, key_c.as_ptr(), value_c.as_ptr(), 0);
}
}
pub fn delete(&mut self, key: &str) {
let key_c = CString::new(key).unwrap();
unsafe {
av_dict_set(&mut self.ptr, key_c.as_ptr(), null(), 0);
}
}
pub fn get(&self, key: &str) -> Option<String> {
let key_c = CString::new(key).unwrap();
unsafe {
let entry = av_dict_get(self.ptr, key_c.as_ptr(), null(), 0);
if entry.is_null() {
None
} else {
Some(
CString::from_raw((*entry).value)
.to_str()
.unwrap()
.to_string(),
)
}
}
}
}
impl Drop for Dictionary {
fn drop(&mut self) {
if !self.owned {
return;
}
unsafe {
av_dict_free(&mut self.ptr);
}
}
}
impl DictionaryInt for Dictionary {
fn set(&mut self, key: &str, value: i64) {
let key_c = CString::new(key).unwrap();
unsafe {
av_dict_set_int(&mut self.ptr, key_c.as_ptr(), value, 0);
}
}
}
pub trait DictionaryInt {
fn set(&mut self, key: &str, value: i64);
}

@ -0,0 +1,55 @@
use crate::av::as_ptr::{AsMutPtr, AsPtr};
use ffmpeg_sys_next::{avio_close_dyn_buf, avio_open_dyn_buf, AVIOContext};
use std::ptr::null_mut;
struct Dynbuf {
ptr: *mut AVIOContext,
freed: bool,
}
impl Dynbuf {
fn new() -> Dynbuf {
let mut ctx = null_mut();
unsafe {
avio_open_dyn_buf(&mut ctx);
}
Dynbuf {
ptr: ctx,
freed: false,
}
}
fn close(&mut self) -> Vec<u8> {
let mut buffer = null_mut();
self.freed = true;
unsafe {
let size = avio_close_dyn_buf(self.ptr, &mut buffer);
Vec::from_raw_parts(buffer, size as usize, size as usize)
}
}
}
impl Drop for Dynbuf {
fn drop(&mut self) {
if self.freed {
return;
}
unsafe {
avio_close_dyn_buf(self.ptr, null_mut());
}
}
}
impl AsPtr<AVIOContext> for Dynbuf {
fn as_ptr(&self) -> *const AVIOContext {
self.ptr
}
}
impl AsMutPtr<AVIOContext> for Dynbuf {
fn as_mut_ptr(&self) -> *mut AVIOContext {
self.ptr
}
}

@ -0,0 +1,98 @@
use crate::av::as_ptr::AsMutPtr;
use crate::av::frame::Frame;
use crate::av::packet::Packet;
use crate::av::scaler::Scaler;
use crate::av::stream::Stream;
use crate::av::xcoder::{process_return, XCoder};
use crate::av::{encoder_from_name, verify_response};
use crate::av_err2str;
use ffmpeg_sys_next::{
avcodec_parameters_from_context, avcodec_receive_packet, avcodec_send_frame, sws_freeContext,
AVCodecContext,
};
pub struct Encoder(pub *mut AVCodecContext);
impl Encoder {
pub fn send_video_frame(&self, frame: &Frame) -> Result<(), String> {
if frame.width() != self.width()
|| frame.height() != self.height()
|| frame.pixel_format() != self.pixel_format()
{
return Err(format!(
"Frame and encoder data differ (frame: {}x{} {:?}, encoder: {}x{} {:?})",
frame.width(),
frame.height(),
frame.pixel_format(),
self.width(),
self.height(),
self.pixel_format()
));
}
self.send_frame(frame)
}
pub fn send_audio_frame(&self, frame: &Frame) -> Result<(), String> {
// if frame.sample_format() != self.sample_format() {
// return Err(format!(
// "Frame and encoder data differ (frame: {:?}, encoder: {:?})",
// frame.sample_format(),
// self.sample_format()
// ));
// }
println!("{:?} / {}", frame.nb_samples(), self.frame_size());
self.send_frame(frame)
}
pub fn send_frame(&self, frame: &Frame) -> Result<(), String> {
verify_response("Failed sending frame to encoder", unsafe {
avcodec_send_frame(self.0, frame.as_mut_ptr())
})
.and(Ok(()))
}
pub fn read_packet(&self) -> Result<Option<Packet>, String> {
let packet = Packet::alloc();
let resp = unsafe { avcodec_receive_packet(self.as_mut_ptr(), packet.as_mut_ptr()) };
if !process_return("Failed to read packet from encoder", resp)? {
Ok(None)
} else {
Ok(Some(packet))
}
}
}
impl XCoder for Encoder {
fn from_name(name: &str) -> Option<Self> {
encoder_from_name(name).map(|codec| Encoder(codec))
}
fn as_mut_ptr(&self) -> *mut AVCodecContext {
self.0
}
fn configure(&self, stream: &Stream) -> Result<(), String> {
let resp =
unsafe { avcodec_parameters_from_context((*stream.0).codecpar, self.as_mut_ptr()) };
if resp < 0 {
return Err(format!(
"Failed configuring decoder from stream (code {}: {})",
resp,
av_err2str(resp)
));
}
Ok(())
}
}
impl Drop for Scaler {
fn drop(&mut self) {
unsafe { sws_freeContext(self.as_mut_ptr()) }
}
}

@ -0,0 +1,39 @@
use crate::av::encoder::Encoder;
use crate::av::xcoder::XCoder;
use crate::av::{encoders_for_codec, FfmpegCodec};
use ffmpeg_sys_next::AVCodecID;
pub struct EncoderSelector {
encoders: Vec<FfmpegCodec>,
}
impl EncoderSelector {
pub fn for_codec_id(codec_id: AVCodecID) -> EncoderSelector {
EncoderSelector {
encoders: encoders_for_codec(codec_id),
}
}
pub fn select(
&self,
hwaccel: Option<bool>,
config_fn: impl Fn(&Encoder) -> Result<(), String>,
) -> Result<Encoder, String> {
for encoder in &self.encoders {
if !hwaccel.is_none() && hwaccel != Some(encoder.hwaccel) {
continue;
}
if let Some(Ok(encoder)) =
Encoder::from_name(&encoder.name).map(|d| -> Result<Encoder, String> {
config_fn(&d)?;
Ok(d)
})
{
return Ok(encoder);
}
}
Err("No working encoders found".to_string())
}
}

@ -0,0 +1,282 @@
use crate::av::as_ptr::{AsMutPtr, AsMutVoidPtr, AsPtr, AsVoidPtr};
use crate::av::avio::{AVIOWriter, AVIO};
use crate::av::dictionary::Dictionary;
use crate::av::options::Options;
use crate::av::packet::Packet;
use crate::av::stream::Stream;
use crate::av::{decoder_codec_from_name, verify_response};
use ffmpeg_sys_next::{
av_interleaved_write_frame, av_read_frame, av_write_frame, av_write_trailer,
avformat_alloc_context, avformat_alloc_output_context2, avformat_find_stream_info,
avformat_init_output, avformat_new_stream, avformat_open_input, avformat_write_header,
avio_open2, AVFormatContext, AVMediaType, AVIO_FLAG_WRITE,
};
use std::ffi::CString;
use std::fmt::{Debug, Error, Formatter};
use std::os::raw::{c_int, c_void};
use std::ptr::{null, null_mut};
#[derive(Clone)]
pub struct Format<T> {
pub ctx: *mut AVFormatContext,
pub avio: Option<AVIO<T>>,
}
impl<T> Debug for Format<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
f.debug_struct("Format<_>").field("ctx", &self.ctx).finish()
}
}
impl<T> Format<T> {
pub fn flags(&self) -> c_int {
unsafe { (*self.as_ptr()).flags }
}
pub fn set_flags(&self, flags: c_int) {
unsafe { (*self.as_mut_ptr()).flags = flags }
}
}
impl<T> Format<T> {
pub fn stream(&self, stream_type: AVMediaType, index: Option<i32>) -> Option<Stream> {
unsafe {
let ctx = self.ctx;
let mut stream = (*ctx).streams;
for _ in 0..(*ctx).nb_streams {
let curr_stream = *stream;
if (*(*curr_stream).codecpar).codec_type == stream_type {
if index.is_none() || Some((*curr_stream).index) == index {
return Some(Stream(curr_stream));
}
}
stream = stream.add(1)
}
}
None
}
pub fn as_mut_ptr(&self) -> *mut AVFormatContext {
self.ctx
}
pub fn next_packet(&self) -> Option<Packet> {
let packet = Packet::alloc();
return if unsafe { av_read_frame(self.as_mut_ptr(), packet.as_mut_ptr()) } >= 0 {
Some(packet)
} else {
None
};
}
pub fn write_header(&self, options: Option<Dictionary>) -> Result<(), String> {
verify_response("failed to write header to output", unsafe {
avformat_write_header(
self.as_mut_ptr(),
if let Some(mut options) = options {
&mut options.as_mut_ptr()
} else {
null_mut()
},
)
})?;
Ok(())
}
pub fn write_trailer(&self) -> Result<(), String> {
verify_response("failed to write trailer to output", unsafe {
av_write_trailer(self.as_mut_ptr())
})?;
Ok(())
}
pub fn write_packet_null(&self) -> Result<(), String> {
verify_response("failed to write to output", unsafe {
av_write_frame(self.as_mut_ptr(), null_mut())
})?;
Ok(())
}
pub fn write_packet_interleaved(&self, packet: &Packet) -> Result<(), String> {
verify_response("failed to write interleaved to output", unsafe {
av_interleaved_write_frame(self.as_mut_ptr(), packet.as_mut_ptr())
})?;
Ok(())
}
pub fn write_packet(&self, packet: &Packet) -> Result<(), String> {
verify_response("failed to write to output", unsafe {
av_write_frame(self.as_mut_ptr(), packet.as_mut_ptr())
})?;
Ok(())
}
pub fn new_stream(&self, codec: &str) -> Result<Stream, String> {
let codec =
decoder_codec_from_name(codec).ok_or(format!("No codec found with name {}", codec))?;
let stream = unsafe { avformat_new_stream(self.ctx, codec) };
Ok(Stream(stream))
}
pub fn avio_inner(&self) -> Option<&T> {
self.avio.as_ref().map(|x| x.inner())
}
pub fn avio_inner_mut(&mut self) -> Option<&mut T> {
self.avio.as_mut().map(|x| x.inner_mut())
}
pub fn init_output(&self, mut options: Dictionary) -> Result<(), String> {
verify_response("Failed to init output", unsafe {
avformat_init_output(self.as_mut_ptr(), &mut options.as_mut_ptr())
})?;
Ok(())
}
}
impl<T: AVIOWriter> Format<T> {
pub fn output_avio<'a>(avio: AVIO<T>, format: &str) -> Result<Format<T>, String> {
let mut context = unsafe { avformat_alloc_context() };
let format_c = CString::new(format).unwrap();
verify_response("Failed to open output with AVIO", unsafe {
avformat_alloc_output_context2(&mut context, null_mut(), format_c.as_ptr(), null_mut())
})?;
unsafe {
(*context).pb = avio.as_mut_ptr();
}
Ok(Format {
ctx: context,
avio: Some(avio),
})
}
}
type FS = ();
impl Format<FS> {
pub fn output(
path: &str,
format: Option<&str>,
options: Option<Dictionary>,
) -> Result<Format<FS>, String> {
let mut context = unsafe { avformat_alloc_context() };
let path_c = CString::new(path).unwrap();
let format_c = if let Some(fmt) = format {
CString::new(fmt).unwrap()
} else {
CString::default()
};
let resp = unsafe {
avformat_alloc_output_context2(
&mut context,
null_mut(),
if format.is_none() {
null()
} else {
format_c.as_ptr()
},
path_c.as_ptr(),
)
};
if resp < 0 {
return Err(format!(
"Failed to open output (path: {}, format: {:?})",
path, format
));
}
verify_response("Failed to open output", unsafe {
avio_open2(
&mut (*context).pb,
path_c.as_ptr(),
AVIO_FLAG_WRITE,
null(),
if let Some(mut options) = options {
options.disown();
&mut options.as_mut_ptr()
} else {
null_mut()
},
)
})?;
Ok(Format {
ctx: context,
avio: None,
})
}
pub fn open(path: &str, options: Option<Dictionary>) -> Result<Format<FS>, String> {
unsafe {
let mut context = avformat_alloc_context();
let c_path = CString::new(path).unwrap();
verify_response(
"failed to open file",
avformat_open_input(
&mut context,
c_path.as_ptr(),
null_mut(),
if let Some(mut options) = options {
&mut options.as_mut_ptr()
} else {
null_mut()
},
),
)?;
verify_response(
"failed to find stream info",
avformat_find_stream_info(context, null_mut()),
)?;
Ok(Format {
ctx: context,
avio: None,
})
}
}
}
impl<T> AsPtr<AVFormatContext> for Format<T> {
#[inline]
fn as_ptr(&self) -> *const AVFormatContext {
self.ctx
}
}
impl<T> AsMutPtr<AVFormatContext> for Format<T> {
#[inline]
fn as_mut_ptr(&self) -> *mut AVFormatContext {
self.ctx
}
}
impl<T> AsVoidPtr for Format<T> {
#[inline]
fn as_void_ptr(&self) -> *const c_void {
self.as_ptr().cast()
}
}
impl<T> AsMutVoidPtr for Format<T> {
#[inline]
fn as_mut_void_ptr(&self) -> *mut c_void {
self.as_mut_ptr().cast()
}
}
impl<T> Options for Format<T> {}

@ -0,0 +1,140 @@
use crate::av::as_ptr::{AsMutPtr, AsPtr};
use crate::av::verify_response;
use ffmpeg_sys_next::{
av_frame_alloc, av_frame_get_buffer, av_frame_unref, AVFrame, AVPictureType, AVPixelFormat,
AVSampleFormat,
};
#[derive(Debug)]
pub struct Frame {
ptr: *mut AVFrame,
owned: bool,
}
impl Frame {
pub fn alloc() -> Frame {
let frame = unsafe { av_frame_alloc() };
if frame.is_null() {
panic!("Can't alloc frame");
}
Frame {
ptr: frame,
owned: true,
}
}
#[inline]
fn own(&mut self) {
self.owned = true
}
#[inline]
fn disown(&mut self) {
self.owned = false
}
#[inline]
pub fn width(&self) -> i32 {
unsafe { (*self.ptr).width }
}
#[inline]
pub fn height(&self) -> i32 {
unsafe { (*self.ptr).height }
}
#[inline]
pub fn pict_type(&self) -> AVPictureType {
unsafe { (*self.ptr).pict_type }
}
#[inline]
pub fn set_pict_type(&self, pict_type: AVPictureType) {
unsafe { (*self.ptr).pict_type = pict_type }
}
#[inline]
pub fn set_width(&self, width: i32) {
unsafe { (*self.ptr).width = width }
}
#[inline]
pub fn set_height(&self, height: i32) {
unsafe { (*self.ptr).height = height }
}
#[inline]
pub fn set_pixel_format(&self, pixel_format: AVPixelFormat) {
unsafe { (*self.ptr).format = std::mem::transmute(pixel_format) }
}
#[inline]
pub fn pixel_format(&self) -> AVPixelFormat {
unsafe { std::mem::transmute::<_, AVPixelFormat>((*self.ptr).format) }
}
#[inline]
pub fn set_sample_format(&self, sample_format: AVSampleFormat) {
unsafe { (*self.ptr).format = std::mem::transmute(sample_format) }
}
#[inline]
pub fn sample_format(&self) -> AVSampleFormat {
unsafe { std::mem::transmute::<_, AVSampleFormat>((*self.ptr).format) }
}
#[inline]
pub fn nb_samples(&self) -> i32 {
unsafe { (*self.ptr).nb_samples }
}
pub fn allocate_video(
&mut self,
pixel_format: AVPixelFormat,
width: i32,
height: i32,
) -> Result<(), String> {
self.set_pixel_format(pixel_format);
self.set_width(width);
self.set_height(height);
verify_response("Failed to allocate frame", unsafe {
av_frame_get_buffer(self.ptr, 32)
})?;
Ok(())
}
#[inline]
pub fn pts(&self) -> i64 {
unsafe { (*self.ptr).pts }
}
#[inline]
pub fn set_pts(&self, pts: i64) {
unsafe { (*self.ptr).pts = pts }
}
}
impl AsPtr<AVFrame> for Frame {
#[inline]
fn as_ptr(&self) -> *const AVFrame {
self.ptr
}
}
impl AsMutPtr<AVFrame> for Frame {
#[inline]
fn as_mut_ptr(&self) -> *mut AVFrame {
self.ptr
}
}
impl Drop for Frame {
fn drop(&mut self) {
if !self.owned {
return;
}
unsafe { av_frame_unref(self.ptr) }
}
}

@ -0,0 +1,334 @@
use crate::av_err2str;
use ffmpeg_sys_next::AVCodecID::AV_CODEC_ID_HEVC;
use ffmpeg_sys_next::{
av_codec_is_decoder, av_codec_is_encoder, av_codec_next, av_inv_q, av_register_all,
avcodec_alloc_context3, avcodec_find_decoder_by_name, avcodec_find_encoder_by_name, AVCodec,
AVCodecContext, AVCodecID, AVRational, AV_CODEC_CAP_HARDWARE,
};
use num_rational::Ratio;
use std::any::type_name;
use std::borrow::Borrow;
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::fmt::Debug;
use std::option::Option::Some;
use std::os::raw::c_int;
use std::ptr::null;
pub mod as_ptr;
pub mod avio;
pub mod codec_parameters;
pub mod decoder;
pub mod decoder_selector;
pub mod dictionary;
pub mod dynbuf;
pub mod encoder;
pub mod encoder_selector;
pub mod format;
pub mod frame;
pub mod mov_avc;
pub mod mov_mux;
pub mod options;
pub mod packet;
pub mod resampler;
pub mod scaler;
pub mod stream;
pub mod xcoder;
static mut CACHE: Option<FfmpegInfoCache> = None;
pub fn get_best_encoder(codec: AVCodecID, hw_accel: Option<bool>) -> Option<String> {
let encoders = get_cache().encoders.get(&codec)?;
get_best_codec(encoders, hw_accel)
}
pub fn get_best_codec(
codecs: &Vec<FfmpegCodec>,
should_be_hw_accel: Option<bool>,
) -> Option<String> {
let hw_accel = codecs.iter().find(|x| x.hwaccel);
if hw_accel.is_some() && should_be_hw_accel != Some(false) {
hw_accel
} else if should_be_hw_accel != Some(true) {
codecs.iter().find(|x| !x.hwaccel)
} else {
None
}
.map(|x| x.name.clone())
}
pub fn get_best_decoder(codec: AVCodecID, hw_accel: Option<bool>) -> Option<String> {
let decoders = get_cache().decoders.get(&codec)?;
get_best_codec(decoders, hw_accel)
}
pub fn decoder_codec_from_name(name: &str) -> Option<*mut AVCodec> {
let codec = unsafe {
let name = CString::new(name).unwrap();
avcodec_find_decoder_by_name(name.as_ptr())
};
if codec.is_null() {
return None;
}
Some(codec)
}
pub fn decoder_from_name(name: &str) -> Option<*mut AVCodecContext> {
let codec = decoder_codec_from_name(name)?;
let xcoder = unsafe { avcodec_alloc_context3(codec) };
if xcoder.is_null() {
return None;
}
Some(xcoder)
}
pub fn encoders_for_codec(codec_id: AVCodecID) -> Vec<FfmpegCodec> {
return if let Some(codecs) = get_cache().encoders.get(&codec_id) {
let mut codecs: Vec<FfmpegCodec> = codecs.to_vec();
codecs.sort_by_key(|x| !x.hwaccel);
codecs
} else {
vec![]
};
}
pub fn decoders_for_codec(codec_id: AVCodecID) -> Vec<FfmpegCodec> {
return if let Some(codecs) = get_cache().decoders.get(&codec_id) {
let mut codecs: Vec<FfmpegCodec> = codecs.to_vec();
codecs.sort_by_key(|x| !x.hwaccel);
codecs
} else {
vec![]
};
}
pub fn encoder_codec_from_name(name: &str) -> Option<*mut AVCodec> {
let codec = unsafe {
let name = CString::new(name).unwrap();
avcodec_find_encoder_by_name(name.as_ptr())
};
if codec.is_null() {
return None;
}
Some(codec)
}
pub fn encoder_from_name(name: &str) -> Option<*mut AVCodecContext> {
let codec = encoder_codec_from_name(name)?;
let encoder = unsafe { avcodec_alloc_context3(codec) };
if encoder.is_null() {
return None;
}
Some(encoder)
}
pub fn get_cache() -> &'static FfmpegInfoCache {
unsafe {
return CACHE.get_or_insert_with(|| create_cache());
}
}
pub fn create_cache() -> FfmpegInfoCache {
let mut info = FfmpegInfoCache::default();
unsafe {
let mut desc = av_codec_next(null());
while !desc.is_null() {
let p_desc = *desc;
let name = CStr::from_ptr(p_desc.name).to_str().unwrap().to_string();
let long_name = CStr::from_ptr(p_desc.long_name)
.to_str()
.unwrap()
.to_string();
if av_codec_is_decoder(desc) == 1 {
info.decoders
.entry(p_desc.id)
.or_default()
.push(FfmpegCodec {
name: name.clone(),
long_name: long_name.clone(),
hwaccel: (p_desc.capabilities & (AV_CODEC_CAP_HARDWARE as i32)) > 0,
});
}
if av_codec_is_encoder(desc) == 1 {
info.encoders
.entry(p_desc.id)
.or_default()
.push(FfmpegCodec {
name,
long_name,
hwaccel: (p_desc.capabilities & (AV_CODEC_CAP_HARDWARE as i32)) > 0,
});
}
desc = av_codec_next(desc);
}
}
// Remove deprecated nvenc codecs if present
info.encoders.entry(AV_CODEC_ID_HEVC).and_modify(|item| {
remove_if_exists(item, "h264_nvenc", vec!["nvenc", "nvenc_h264"]);
remove_if_exists(item, "nvenc_h264", vec!["nvenc"]);
remove_if_exists(item, "hevc_nvenc", vec!["nvenc_hevc"]);
});
info
}
fn remove_if_exists(list: &mut Vec<FfmpegCodec>, needle: &str, to_remove: Vec<&str>) {
let mut found_deprecated = vec![];
let mut found_new = false;
let mut i = 0;
for codec in Borrow::<Vec<_>>::borrow(list) {
if codec.name == needle {
found_new = true;
break;
}
if to_remove.contains(&codec.name.as_str()) {
found_deprecated.push(i)
}
i += 1;
}
if found_new {
found_deprecated.sort();
found_deprecated.reverse();
for index in found_deprecated {
list.remove(index);
}
}
}
pub fn init() {
unsafe {
av_register_all();
}
}
#[inline]
pub(crate) fn verify_response(error: &str, resp: c_int) -> Result<c_int, String> {
if resp < 0 {
Err(format!("{} (code {}, {})", error, resp, av_err2str(resp)))
} else {
Ok(resp)
}
}
#[derive(Default, Debug)]
pub struct FfmpegInfoCache {
pub decoders: HashMap<AVCodecID, Vec<FfmpegCodec>>,
pub encoders: HashMap<AVCodecID, Vec<FfmpegCodec>>,
}
#[derive(Default, Debug, Clone)]
pub struct FfmpegCodec {
pub name: String,
pub long_name: String,
pub hwaccel: bool,
}
pub trait Rational {
fn new(num: i32, den: i32) -> Self;
fn to_av(&self) -> AVRational {
AVRational {
den: self.den(),
num: self.num(),
}
}
fn to_num(&self) -> Ratio<i32> {
Ratio::new(self.num(), self.den())
}
fn num(&self) -> i32;
fn den(&self) -> i32;
fn invert(&self) -> Self;
fn trunc(&self) -> Ratio<i32> {
self.to_num().trunc()
}
fn debug(&self) -> String {
return format!(
"Rational[{}]({}/{})",
type_name::<Self>(),
self.num(),
self.den()
);
}
fn simplify(&self) -> Self;
}
impl Rational for Ratio<i32> {
#[inline]
fn new(num: i32, den: i32) -> Self {
Ratio::new(num, den)
}
#[inline]
fn to_num(&self) -> Ratio<i32> {
*self
}
#[inline]
fn num(&self) -> i32 {
*self.numer()
}
#[inline]
fn den(&self) -> i32 {
*self.denom()
}
#[inline]
fn invert(&self) -> Self {
Ratio::new(self.den(), self.num())
}
fn simplify(&self) -> Self {
Self::new(1, self.den() / self.num())
}
}
impl Rational for AVRational {
#[inline]
fn new(num: i32, den: i32) -> Self {
AVRational { num, den }
}
#[inline]
fn to_av(&self) -> AVRational {
*self
}
#[inline]
fn num(&self) -> i32 {
self.num
}
#[inline]
fn den(&self) -> i32 {
self.den
}
#[inline]
fn invert(&self) -> Self {
unsafe { av_inv_q(*self) }
}
fn simplify(&self) -> Self {
Self::new(1, (self.den() as f64 / self.num() as f64).round() as i32)
}
}

@ -0,0 +1,332 @@
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::fmt::{Debug, Error, Formatter};
use std::io::{Cursor, Seek, SeekFrom};
#[derive(Debug)]
pub struct AvcDecoderConfigurationRecord {
pub profile_idc: u8,
pub constraint_set_flag: u8,
pub level_idc: u8,
pub sequence_parameter_set: Vec<u8>,
pub picture_parameter_set: Vec<u8>,
}
impl AvcDecoderConfigurationRecord {
pub fn from_parameter_sets(
sequence_parameter_set: Vec<u8>,
picture_parameter_set: Vec<u8>,
) -> AvcDecoderConfigurationRecord {
AvcDecoderConfigurationRecord {
profile_idc: sequence_parameter_set[1],
constraint_set_flag: sequence_parameter_set[2],
level_idc: sequence_parameter_set[3],
sequence_parameter_set,
picture_parameter_set,
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, String> {
let mut buffer = vec![];
// configuration_version
buffer.push(1);
buffer.push(self.profile_idc);
buffer.push(self.constraint_set_flag);
buffer.push(self.level_idc);
// reserved and length_size_minus_one
buffer.push(0b1111_1100 | 0b0000_0011);
// reserved and num_of_sequence_parameter_set_ext
buffer.push(0b1110_0000 | 0b0000_0001);
buffer
.write_u16::<BigEndian>(self.sequence_parameter_set.len() as u16)
.map_err(|x| format!("Failed to write u16 to vec ({})", x))?;
buffer.append(&mut self.sequence_parameter_set.clone());
// num_of_picture_parameter_set_ext
buffer.push(0b0000_0001);
buffer
.write_u16::<BigEndian>(self.picture_parameter_set.len() as u16)
.map_err(|x| format!("Failed to write u16 to vec ({})", x))?;
buffer.append(&mut self.picture_parameter_set.clone());
Ok(buffer)
}
}
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
#[repr(u8)]
pub enum NalUnitType {
CodedSliceOfANonIdrPicture = 1,
CodedSliceDataPartitionA = 2,
CodedSliceDataPartitionB = 3,
CodedSliceDataPartitionC = 4,
CodedSliceOfAnIdrPicture = 5,
SupplementalEnhancementInformation = 6,
SequenceParameterSet = 7,
PictureParameterSet = 8,
AccessUnitDelimiter = 9,
EndOfSequence = 10,
EndOfStream = 11,
FilterData = 12,
SequenceParameterSetExtension = 13,
PrefixNalUnit = 14,
SubsetSequenceParameterSet = 15,
CodedSliceOfAnAuxiliaryCodedPictureWithoutPartitioning = 19,
CodedSliceExtension = 20,
}
impl NalUnitType {
fn from_u8(n: u8) -> Result<Self, String> {
Ok(match n {
1 => NalUnitType::CodedSliceOfANonIdrPicture,
2 => NalUnitType::CodedSliceDataPartitionA,
3 => NalUnitType::CodedSliceDataPartitionB,
4 => NalUnitType::CodedSliceDataPartitionC,
5 => NalUnitType::CodedSliceOfAnIdrPicture,
6 => NalUnitType::SupplementalEnhancementInformation,
7 => NalUnitType::SequenceParameterSet,
8 => NalUnitType::PictureParameterSet,
9 => NalUnitType::AccessUnitDelimiter,
10 => NalUnitType::EndOfSequence,
11 => NalUnitType::EndOfStream,
12 => NalUnitType::FilterData,
13 => NalUnitType::SequenceParameterSetExtension,
14 => NalUnitType::PrefixNalUnit,
15 => NalUnitType::SubsetSequenceParameterSet,
19 => NalUnitType::CodedSliceOfAnAuxiliaryCodedPictureWithoutPartitioning,
20 => NalUnitType::CodedSliceExtension,
_ => return Err(format!("Couldn't find NalUnitType for {:?}", n)),
})
}
}
#[derive(Debug, Copy, Clone)]
pub struct NalUnit {
pub nal_ref_idc: u8,
pub nal_unit_type: NalUnitType,
}
impl NalUnit {
fn from_byte(byte: u8) -> Result<Self, String> {
let nal_ref_idc = (byte >> 5) & 0b11;
let nal_unit_type = NalUnitType::from_u8(byte & 0b1_1111)?;
Ok(NalUnit {
nal_ref_idc,
nal_unit_type,
})
}
fn to_byte(&self) -> u8 {
let nal_ref_idc = self.nal_ref_idc << 5;
let nal_unit_type = self.nal_unit_type as u8;
return nal_unit_type | nal_ref_idc;
}
}
#[derive(Clone)]
pub struct NalBuffer {
pub nal_unit: NalUnit,
pub buffer: Vec<u8>,
}
struct StringyBoi(String);
impl Debug for StringyBoi {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
f.write_str(&self.0)
}
}
impl Debug for NalBuffer {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
if f.alternate() {
f.debug_tuple("NalBuffer")
.field(&self.nal_unit)
.field(&self.buffer)
.finish()
} else {
f.debug_tuple("NalBuffer")
.field(&self.nal_unit)
.field(&StringyBoi(format!("len={}", self.buffer.len())))
.finish()
}
}
}
fn find_mdat(data: &Vec<u8>) -> Option<usize> {
for i in 0..=data.len() - 3 {
if data[i] == 'm' as u8
&& data.get(i + 3).copied() == Some('t' as u8)
&& &data[i..i + 4] == b"mdat"
{
return Some(i + 4);
}
}
None
}
#[cfg(debug_assertions)]
pub fn verify_data(data: Vec<u8>) {
let index = find_mdat(&data).expect("no mdat found") as u64;
let mut cursor = Cursor::new(&data);
cursor.set_position(index - 8);
let size = cursor.read_u32::<BigEndian>().expect(":O") as u64;
cursor.set_position(index);
while cursor.position() < (size + index - 8) {
let skip = cursor.read_u32::<BigEndian>().expect(":C") as i64;
cursor
.seek(SeekFrom::Current(skip))
.expect("Failed to seek");
}
assert_eq!(cursor.position() - index, size - 8);
}
pub fn annex_b_to_avc(mut data: Vec<u8>, nal_length: u8) -> Result<Vec<u8>, String> {
let nal_length = nal_length as usize;
let mut result: Vec<u8> = vec![];
let index = find_mdat(&data).ok_or("No mdat found".to_string())?;
let end = {
let mut cursor = Cursor::new(&data);
cursor.set_position((index - 8) as u64);
index + (cursor.read_u32::<BigEndian>().expect(":O") as usize) - 8
};
result.extend_from_slice(&data[0..index]);
let matcher = vec![0u8; nal_length - 1 as usize];
let mut last_position = index + nal_length - 1;
for i in (index + matcher.len() + nal_length)..end {
if data[i] == 1 && &data[1 + i - nal_length..i] == &matcher[..] {
let block_length = (i - last_position - 4) as u32;
let mut header = &mut data[last_position - 3..last_position + 1];
header
.write_u32::<BigEndian>(block_length)
.map_err(|x| format!("Failed writing to buffer: {}", x))?;
last_position = i;
}
}
let data_len = data.len();
let mut header = &mut data[last_position - 3..=last_position];
header
.write_u32::<BigEndian>(((data_len - 1) - last_position) as u32)
.map_err(|x| format!("Failed writing to buffer: {}", x))?;
#[cfg(debug_assertions)]
verify_data(data.clone());
Ok(data)
}
impl NalBuffer {
pub fn to_vec(&self) -> Vec<u8> {
let mut bytes = vec![0; 1 + self.buffer.len()];
{
let (left, right) = bytes.split_at_mut(1);
left[0] = self.nal_unit.to_byte();
right.copy_from_slice(&self.buffer);
}
bytes
}
pub fn from_stream(stream: Vec<u8>) -> Result<Vec<NalBuffer>, String> {
let mut i = 0;
let mut buffers = vec![];
let mut byte_buffer = vec![];
let mut current: Option<NalBuffer> = None;
while i < stream.len() {
if stream.len() > i + 3
&& stream[i] == 0
&& stream[i + 1] == 0
&& (stream[i + 2] == 1
|| (stream.len() > i + 4 && stream[i + 2] == 0 && stream[i + 3] == 1))
{
if let Some(mut buffer) = current.take() {
buffer.buffer = byte_buffer;
byte_buffer = vec![];
buffers.push(buffer);
}
i += if stream[i + 2] == 1 { 3 } else { 4 };
if i >= stream.len() {
break;
}
current = Some(NalBuffer {
nal_unit: NalUnit::from_byte(stream[i])?,
buffer: vec![],
});
i += 1;
if i >= stream.len() {
break;
}
}
if stream.len() > i + 4
&& stream[i] == 0
&& stream[i + 1] == 0
&& stream[i + 2] == 0
&& stream[i + 3] == 3
{
byte_buffer.append(&mut (&stream[i..i + 2]).to_vec());
i += 4;
if i >= stream.len() {
break;
}
}
byte_buffer.push(stream[i]);
i += 1;
}
if let Some(mut buffer) = current.take() {
buffer.buffer = byte_buffer;
buffers.push(buffer);
}
Ok(buffers)
}
}
#[cfg(test)]
mod test {
use crate::av::mov_avc::{annex_b_to_avc, verify_data};
use std::fs::File;
use std::io::Read;
#[test]
fn test_annex_b_to_avc() {
let mdat = b"mdat".to_vec();
let mut insert: Vec<u8> = vec![
0, 0, 0, 1, 1, 2, 3, 4, 0, 0, 0, 1, 1, 2, 3, 4, 5, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8,
9,
];
let mut buffer: Vec<u8> = vec![0, 0, 0, insert.len() as u8 + 8];
let mut result: Vec<u8> = vec![0, 0, 0, insert.len() as u8 + 8];
buffer.extend_from_slice(&mdat);
buffer.append(&mut insert);
result.extend(mdat);
result.append(&mut vec![
0, 0, 0, 4, 1, 2, 3, 4, 0, 0, 0, 5, 1, 2, 3, 4, 5, 0, 0, 0, 9, 1, 2, 3, 4, 5, 6, 7, 8,
9,
]);
assert_eq!(annex_b_to_avc(buffer, 4), Ok(result.clone()));
verify_data(result);
}
#[test]
fn bs() {
let mut x = File::open("/home/eater/shit/dash-exmp/chunk-stream0-00001.m4s").expect("ok");
let mut xxx = vec![];
x.read_to_end(&mut xxx).unwrap();
verify_data(xxx);
let mut x = File::open("/tmp/transotf/segment-00000.m4s").expect("ok");
let mut xxx = vec![];
x.read_to_end(&mut xxx).unwrap();
verify_data(xxx);
}
}

@ -0,0 +1,254 @@
use ffmpeg_sys_next::{
AVClass, AVCodecParameters, AVFormatContext, AVIOContext, AVPacket, AVStream,
};
use std::fmt::{Debug, Error, Formatter};
use std::os::raw::{c_char, c_float, c_int, c_long, c_uint, c_void};
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone)]
#[repr(i32)]
pub enum MOVEncryptionScheme {
MOV_ENC_NONE = 0,
MOV_ENC_CENC_AES_CTR,
}
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone)]
#[repr(i32)]
pub enum MOVPrftBox {
MOV_PRFT_NONE = 0,
MOV_PRFT_SRC_WALLCLOCK,
MOV_PRFT_SRC_PTS,
MOV_PRFT_NB,
}
const MOV_SYNC_SAMPLE: u32 = 0x0001;
const MOV_PARTIAL_SYNC_SAMPLE: u32 = 0x0002;
const MOV_DISPOSABLE_SAMPLE: u32 = 0x0004;
#[derive(Debug, Clone)]
#[repr(C)]
pub struct AVProducerReferenceTime {
pub wallclock: i64,
pub flags: c_int,
}
#[derive(Debug, Clone)]
#[repr(C)]
pub struct MOVIentry {
pub pos: u64,
pub dts: i64,
pub pts: i64,
pub size: c_uint,
pub samples_in_chunk: c_uint,
///< Chunk number if the current entry is a chunk start otherwise 0
pub chunk_num: c_uint,
pub entries: c_uint,
pub cts: c_int,
pub flags: u32,
pub prft: AVProducerReferenceTime,
}
const MOV_TRACK_CTTS: u32 = 0x0001;
const MOV_TRACK_STPS: u32 = 0x0002;
const MOV_TRACK_ENABLED: u32 = 0x0004;
const MOV_TIMECODE_FLAG_DROPFRAME: u32 = 0x0001;
const MOV_TIMECODE_FLAG_24HOURSMAX: u32 = 0x0002;
const MOV_TIMECODE_FLAG_ALLOWNEGATIVE: u32 = 0x0004;
#[derive(Debug, Clone)]
#[repr(C)]
pub struct HintSample {
pub data: *mut u8,
pub size: c_int,
pub sample_number: c_int,
pub offset: c_int,
pub own_data: c_int,
}
#[derive(Debug, Clone)]
#[repr(C)]
pub struct HintSampleQueue {
pub size: c_int,
pub len: c_int,
pub samples: *mut HintSample,
}
#[derive(Debug, Clone)]
#[repr(C)]
pub struct MOVFragmentInfo {
pub offset: i64,
pub time: i64,
pub duration: i64,
pub tfrf_offset: i64,
pub size: c_int,
}
#[derive(Clone, Debug)]
#[repr(C)]
pub struct MOVMuxCencContext {
aes_ctr: *mut c_void,
pub auxiliary_info: *mut u8,
pub auxiliary_info_size: isize,
pub auxiliary_info_alloc_size: isize,
pub auxiliary_info_entries: u32,
/* subsample support */
pub use_subsamples: c_int,
pub subsample_count: i16,
pub auxiliary_info_subsample_start: isize,
pub auxiliary_info_sizes: *mut u8,
pub auxiliary_info_sizes_alloc_size: isize,
}
#[derive(Clone, Debug)]
#[repr(C)]
pub struct MOVTrack {
pub mode: c_int,
pub entry: c_int,
pub timescale: c_uint,
pub time: u64,
pub track_duration: i64,
pub last_sample_is_subtitle_end: c_int,
pub sample_count: c_long,
pub sample_size: c_long,
pub chunk_count: c_long,
pub has_keyframes: c_int,
pub has_disposable: c_int,
pub flags: u32,
pub timecode_flags: u32,
pub language: c_int,
pub track_id: c_int,
///< stsd fourcc
pub tag: c_int,
pub st: *mut AVStream,
pub par: *mut AVCodecParameters,
pub multichannel_as_mono: c_int,
pub vos_len: c_int,
pub vos_data: *mut u8,
pub cluster: *mut MOVIentry,
pub cluster_capacity: c_uint,
pub audio_vbr: c_int,
///< active picture (w/o VBI) height for D-10/IMX
pub height: c_int,
pub tref_tag: u32,
///< trackID of the referenced track
pub tref_id: c_int,
pub start_dts: i64,
pub start_cts: i64,
pub end_pts: i64,
pub end_reliable: c_int,
pub dts_shift: i64,
///< the track that hints this track, -1 if no hint track is set
pub hint_track: c_int,
///< the track that this hint (or tmcd) track describes
pub src_track: c_int,
///< the format context for the hinting rtp muxer
pub rtp_ctx: *mut AVFormatContext,
pub prev_rtp_ts: u32,
pub cur_rtp_ts_unwrapped: i64,
pub max_packet_size: u32,
pub default_duration: i64,
pub default_sample_flags: u32,
pub default_size: u32,
pub sample_queue: HintSampleQueue,
pub cover_image: AVPacket,
pub mdat_buf: *mut AVIOContext,
pub data_offset: i64,
pub frag_start: i64,
pub frag_discont: c_int,
pub entries_flushed: c_int,
pub nb_frag_info: c_int,
pub frag_info: *mut MOVFragmentInfo,
pub frag_info_capacity: c_uint,
pub vc1_info: VC1Info,
pub eac3_priv: *mut c_void,
pub cenc: MOVMuxCencContext,
pub palette: Palette,
pub pal_done: c_int,
pub is_unaligned_qt_rgb: c_int,
}
#[repr(C)]
#[derive(Clone)]
pub struct Palette([u32; 256]);
impl Debug for Palette {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
f.write_str("Palette")?;
f.debug_list().entries(&self.0[..]).finish()
}
}
#[derive(Debug, Clone)]
#[repr(C)]
pub struct VC1Info {
pub first_packet_seq: c_int,
pub first_packet_entry: c_int,
pub first_packet_seen: c_int,
pub first_frag_written: c_int,
pub packet_seq: c_int,
pub packet_entry: c_int,
pub slices: c_int,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct MOVMuxContext {
pub av_class: *const AVClass,
pub mode: c_int,
pub time: i64,
pub nb_streams: c_int,
///< number of new created tmcd track based on metadata (aka not data copy)
pub nb_meta_tmcd: c_int,
///< qt chapter track number
pub chapter_track: c_int,
pub mdat_pos: i64,
pub mdat_size: u64,
pub tracks: *mut MOVTrack,
pub flags: c_int,
pub rtp_flags: c_int,
pub iods_skip: c_int,
pub iods_video_profile: c_int,
pub iods_audio_profile: c_int,
pub moov_written: c_int,
pub fragments: c_int,
pub max_fragment_duration: c_int,
pub min_fragment_duration: c_int,
pub max_fragment_size: c_int,
pub ism_lookahead: c_int,
pub mdat_buf: *mut AVIOContext,
pub first_trun: c_int,
pub video_track_timescale: c_int,
///< 0 for disabled, -1 for automatic, size otherwise
pub reserved_moov_size: c_int,
pub reserved_header_pos: i64,
pub major_brand: *mut c_char,
pub per_stream_grouping: c_int,
pub fc: *mut AVFormatContext,
pub use_editlist: c_int,
pub gamma: c_float,
pub frag_interleave: c_int,
pub missing_duration_warned: c_int,
pub encryption_scheme_str: *mut c_char,
pub encryption_scheme: MOVEncryptionScheme,
pub encryption_key: *mut u8,
pub encryption_key_len: c_int,
pub encryption_kid: *mut u8,
pub encryption_kid_len: c_int,
pub need_rewrite_extradata: c_int,
pub use_stream_ids_as_track_ids: c_int,
pub track_ids_ok: c_int,
pub write_tmcd: c_int,
pub write_prft: MOVPrftBox,
pub empty_hdlr_name: c_int,
}

@ -0,0 +1,38 @@
use crate::av::as_ptr::AsMutVoidPtr;
use crate::av::dictionary::Dictionary;
use ffmpeg_sys_next::{av_opt_find, av_opt_get_int, av_opt_set_dict, av_opt_set_int};
use std::ffi::CString;
use std::ptr::null;
pub trait Options: AsMutVoidPtr {
fn set_options(&mut self, mut options: Dictionary) {
options.disown();
unsafe {
av_opt_set_dict(self.as_mut_void_ptr(), &mut options.as_mut_ptr());
}
}
fn set_flag(&self, option: &str, flag: &str) -> bool {
let option_c = CString::new(option).unwrap();
let flag_c = CString::new(flag).unwrap();
unsafe {
let option = av_opt_find(self.as_mut_void_ptr(), option_c.as_ptr(), null(), 0, 0);
let flag = av_opt_find(self.as_mut_void_ptr(), flag_c.as_ptr(), null(), 0, 0);
if flag.is_null() || option.is_null() {
return false;
}
let mut val: i64 = 0;
av_opt_get_int(self.as_mut_void_ptr(), option_c.as_ptr(), 0, &mut val);
av_opt_set_int(
self.as_mut_void_ptr(),
option_c.as_ptr(),
val | (*flag).default_val.i64,
0,
);
}
true
}
}

@ -0,0 +1,104 @@
use crate::av::Rational;
use ffmpeg_sys_next::AVPacketSideDataType::AV_PKT_DATA_NEW_EXTRADATA;
use ffmpeg_sys_next::{
av_packet_alloc, av_packet_get_side_data, av_packet_rescale_ts, av_packet_unref, AVPacket,
};
pub struct Packet(pub *mut AVPacket);
impl Packet {
pub fn alloc() -> Packet {
let pkt = unsafe { av_packet_alloc() };
if pkt.is_null() {
panic!("Failed to allocate packet");
}
Packet(pkt)
}
#[inline]
pub fn stream(&self) -> i32 {
return unsafe { (*self.0).stream_index };
}
#[inline]
pub fn set_stream(&self, stream_index: i32) {
unsafe { (*self.0).stream_index = stream_index };
}
#[inline]
pub fn pts(&self) -> i64 {
unsafe { (*self.0).pts }
}
#[inline]
pub fn set_pts(&self, pts: i64) {
unsafe { (*self.0).pts = pts }
}
#[inline]
pub fn dts(&self) -> i64 {
unsafe { (*self.0).dts }
}
#[inline]
pub fn set_dts(&self, dts: i64) {
unsafe { (*self.0).dts = dts }
}
#[inline]
pub fn rescale<R1: Rational, R2: Rational>(&self, from: R1, to: R2) {
unsafe {
av_packet_rescale_ts(self.0, from.to_av(), to.to_av());
}
}
#[inline]
pub fn duration(&self) -> i64 {
unsafe { (*self.0).duration }
}
#[inline]
pub fn set_duration(&self, duration: i64) {
unsafe { (*self.0).duration = duration }
}
pub fn extra_data(&self) -> Vec<u8> {
unsafe {
let mut size = 0;
let data = av_packet_get_side_data(self.as_ptr(), AV_PKT_DATA_NEW_EXTRADATA, &mut size);
let size = size as usize;
let mut extra_data = vec![0u8; size];
data.copy_to(extra_data.as_mut_ptr(), size);
extra_data
}
}
pub fn data(&self) -> Vec<u8> {
unsafe {
let size = (*self.0).size as usize;
let mut data = vec![0u8; size];
(*self.0).data.copy_to(data.as_mut_ptr(), size);
data
}
}
#[inline]
pub fn as_ptr(&self) -> *const AVPacket {
return self.0;
}
#[inline]
pub fn as_mut_ptr(&self) -> *mut AVPacket {
return self.0;
}
}
impl Drop for Packet {
fn drop(&mut self) {
unsafe {
av_packet_unref(self.0);
}
}
}

@ -0,0 +1,60 @@
use crate::av::as_ptr::{AsMutPtr, AsPtr};
use crate::av::decoder::Decoder;
use crate::av::encoder::Encoder;
use crate::av::frame::Frame;
use crate::av::xcoder::XCoder;
use ffmpeg_sys_next::{
swr_alloc, swr_alloc_set_opts, swr_convert_frame, swr_get_delay, AVFrame, SwrContext,
};
use std::ptr::{null, null_mut};
pub struct Resampler {
ptr: *mut SwrContext,
output_sample_rate: i64,
}
impl Resampler {
pub fn from_coder(decoder: &Decoder, encoder: &Encoder) -> Resampler {
unsafe {
let swr = swr_alloc();
swr_alloc_set_opts(
swr,
encoder.channel_layout() as i64,
encoder.sample_format(),
encoder.sample_rate(),
decoder.channel_layout() as i64,
decoder.sample_format(),
decoder.sample_rate(),
0,
null_mut(),
);
Resampler {
ptr: swr,
output_sample_rate: encoder.sample_rate() as i64,
}
}
}
pub fn convert(&self, input: Frame) -> Frame {
let output = Frame::alloc();
unsafe {
swr_convert_frame(self.ptr, output.as_mut_ptr(), input.as_ptr());
}
output
}
pub fn drain(&self) -> Option<Frame> {
if 0 < unsafe { swr_get_delay(self.ptr, self.output_sample_rate) } {
let output = Frame::alloc();
unsafe {
swr_convert_frame(self.ptr, output.as_mut_ptr(), null());
}
Some(output)
} else {
None
}
}
}

@ -0,0 +1,152 @@
use crate::av::as_ptr::{AsMutPtr, AsPtr};
use crate::av::decoder::Decoder;
use crate::av::encoder::Encoder;
use crate::av::frame::Frame;
use crate::av::verify_response;
use crate::av::xcoder::XCoder;
use ffmpeg_sys_next::{sws_getContext, sws_scale, AVPixelFormat, SwsContext};
use std::os::raw::c_int;
use std::ptr::null_mut;
#[derive(Debug)]
pub struct Scaler {
pub ctx: *mut SwsContext,
flags: c_int,
input: VideoInfo,
output: VideoInfo,
pass_through: bool,
}
#[derive(Eq, PartialEq, Debug)]
struct VideoInfo {
width: i32,
height: i32,
pixel_format: AVPixelFormat,
}
impl Scaler {
pub fn new(
from_pixel_format: AVPixelFormat,
from_width: i32,
from_height: i32,
to_pixel_format: AVPixelFormat,
to_width: i32,
to_height: i32,
flags: c_int,
) -> Scaler {
unsafe {
let ptr = sws_getContext(
from_width,
from_height,
from_pixel_format,
to_width,
to_height,
to_pixel_format,
flags,
null_mut(),
null_mut(),
null_mut(),
);
let input = VideoInfo {
width: from_width,
height: from_height,
pixel_format: from_pixel_format,
};
let output = VideoInfo {
width: to_width,
height: to_height,
pixel_format: to_pixel_format,
};
Scaler {
ctx: ptr,
pass_through: input.eq(&output),
flags,
input,
output,
}
}
}
pub fn from_coder(decoder: &Decoder, encoder: &Encoder, flags: i32) -> Scaler {
Scaler::new(
decoder.pixel_format(),
decoder.width(),
decoder.height(),
encoder.pixel_format(),
encoder.width(),
encoder.height(),
flags,
)
}
pub fn scale(&mut self, input: Frame) -> Result<Frame, String> {
if self.input.pixel_format != input.pixel_format()
|| self.input.width != input.width()
|| self.input.height != input.height()
{
self.input = VideoInfo {
pixel_format: input.pixel_format(),
width: input.width(),
height: input.height(),
};
self.pass_through = self.input == self.output;
if !self.pass_through {
unsafe {
self.ctx = sws_getContext(
self.input.width,
self.input.height,
self.input.pixel_format,
self.output.width,
self.output.height,
self.output.pixel_format,
self.flags,
null_mut(),
null_mut(),
null_mut(),
);
}
}
}
if self.pass_through {
return Ok(input);
}
let mut frame = Frame::alloc();
frame.allocate_video(
self.output.pixel_format,
self.output.width,
self.output.height,
)?;
verify_response("Failed to scale frame", unsafe {
sws_scale(
self.as_mut_ptr(),
(*input.as_ptr()).data.as_ptr() as *const *const _,
(*input.as_mut_ptr()).linesize.as_ptr() as *const _,
0,
self.input.height,
(*frame.as_mut_ptr()).data.as_ptr() as *const *mut _,
(*frame.as_mut_ptr()).linesize.as_ptr() as *mut _,
)
})?;
Ok(frame)
}
}
impl AsPtr<SwsContext> for Scaler {
fn as_ptr(&self) -> *const SwsContext {
self.ctx
}
}
impl AsMutPtr<SwsContext> for Scaler {
fn as_mut_ptr(&self) -> *mut SwsContext {
self.ctx
}
}

@ -0,0 +1,107 @@
use crate::av::as_ptr::{AsMutPtr, AsPtr};
use crate::av::codec_parameters::CodecParameters;
use crate::av::decoder::Decoder;
use crate::av::decoder_selector::DecoderSelector;
use crate::av::encoder::Encoder;
use crate::av::encoder_selector::EncoderSelector;
use crate::av::format::Format;
use crate::av::xcoder::XCoder;
use crate::av::{get_best_decoder, get_best_encoder, Rational};
use ffmpeg_sys_next::{av_guess_frame_rate, AVCodecID, AVRational, AVStream};
use num_rational::Ratio;
use std::ptr::null_mut;
pub struct Stream(pub *mut AVStream);
impl Stream {
pub fn as_ptr(&self) -> *const AVStream {
self.0
}
pub fn decoder(&self, hw_accel: Option<bool>) -> Option<Decoder> {
get_best_decoder(self.codec(), hw_accel)
.and_then(|decoder_name| Decoder::from_name(&decoder_name))
}
pub fn decoder_select(
&self,
hw_accel: Option<bool>,
select_fn: impl Fn(&Decoder) -> Result<(), String>,
) -> Result<Decoder, String> {
DecoderSelector::for_codec_id(self.codec()).select(hw_accel, select_fn)
}
pub fn encoder(&self, hw_accel: Option<bool>) -> Option<Encoder> {
let encoder = get_best_encoder(self.codec(), hw_accel);
encoder.and_then(|encoder_name| Encoder::from_name(&encoder_name))
}
pub fn encoder_select(
&self,
hw_accel: Option<bool>,
select_fn: impl Fn(&Encoder) -> Result<(), String>,
) -> Result<Encoder, String> {
EncoderSelector::for_codec_id(self.codec()).select(hw_accel, select_fn)
}
#[inline]
pub fn index(&self) -> i32 {
unsafe { (*self.0).index }
}
#[inline]
pub fn set_index(&self, index: i32) {
unsafe { (*self.0).index = index }
}
pub fn container_id(&self) -> i32 {
unsafe { (*self.0).id }
}
#[inline]
pub fn codec(&self) -> AVCodecID {
unsafe { (*(*self.0).codec).codec_id }
}
pub fn params(&self) -> CodecParameters {
unsafe { CodecParameters((*self.0).codecpar) }
}
#[inline]
pub fn time_base(&self) -> Ratio<i32> {
unsafe { (*self.0).time_base.to_num() }
}
#[inline]
pub fn set_time_base<R: Rational>(&self, time_base: R) {
unsafe { (*self.0).time_base = time_base.to_av() }
}
#[inline]
pub fn sample_aspect_ratio(&self) -> AVRational {
unsafe { (*self.as_ptr()).sample_aspect_ratio }
}
#[inline]
pub fn set_sample_aspect_ratio(&self, sample_aspect_ratio: impl Rational) {
unsafe { (*self.as_mut_ptr()).sample_aspect_ratio = sample_aspect_ratio.to_av() }
}
pub fn avg_frame_rate<T>(&self, fmt: &Format<T>) -> Option<impl Rational> {
Some(unsafe { av_guess_frame_rate(fmt.as_mut_ptr(), self.as_mut_ptr(), null_mut()) })
}
}
impl AsMutPtr<AVStream> for Stream {
#[inline]
fn as_mut_ptr(&self) -> *mut AVStream {
self.0
}
}
impl AsPtr<AVStream> for Stream {
#[inline]
fn as_ptr(&self) -> *const AVStream {
self.0
}
}

@ -0,0 +1,257 @@
use crate::av::dictionary::Dictionary;
use crate::av::stream::Stream;
use crate::av::{verify_response, Rational};
use crate::av_err2str;
use ffmpeg_sys_next::AVPixelFormat::AV_PIX_FMT_NONE;
use ffmpeg_sys_next::{
av_malloc, avcodec_open2, AVCodecContext, AVColorPrimaries, AVColorRange,
AVColorTransferCharacteristic, AVPixelFormat, AVRational, AVSampleFormat, AVERROR, AVERROR_EOF,
EAGAIN,
};
use num_rational::Ratio;
use std::ffi::CStr;
use std::os::raw::c_int;
use std::ptr::null_mut;
pub trait XCoder: Sized {
fn from_name(name: &str) -> Option<Self>;
fn as_mut_ptr(&self) -> *mut AVCodecContext;
#[inline]
fn as_ptr(&self) -> *const AVCodecContext {
self.as_mut_ptr()
}
fn name(&self) -> String {
unsafe {
if let Some(x) = (*(*self.as_ptr()).av_class).item_name {
CStr::from_ptr(x(self.as_mut_ptr().cast()))
.to_str()
.unwrap_or("")
.to_string()
} else {
String::new()
}
}
}
fn configure(&self, stream: &Stream) -> Result<(), String>;
fn open(&self) -> Result<(), String> {
let ctx = self.as_mut_ptr();
let resp = unsafe { avcodec_open2(ctx, (*ctx).codec, null_mut()) };
if resp < 0 {
return Err(format!(
"Failed configuring coder from stream (code {}: {})",
resp,
av_err2str(resp)
));
}
Ok(())
}
#[inline]
fn time_base(&self) -> Ratio<i32> {
unsafe { (*self.as_ptr()).time_base.to_num() }
}
#[inline]
fn set_time_base<R: Rational>(&self, ratio: R) {
unsafe { (*self.as_mut_ptr()).time_base = ratio.to_av() }
}
#[inline]
fn pixel_format(&self) -> AVPixelFormat {
unsafe { (*self.as_ptr()).pix_fmt }
}
#[inline]
fn set_pixel_format(&self, pixel_format: AVPixelFormat) {
unsafe { (*self.as_mut_ptr()).pix_fmt = pixel_format }
}
#[inline]
fn width(&self) -> c_int {
unsafe { (*self.as_ptr()).width }
}
#[inline]
fn set_width(&self, width: c_int) {
unsafe { (*self.as_mut_ptr()).width = width }
}
#[inline]
fn height(&self) -> c_int {
unsafe { (*self.as_ptr()).height }
}
#[inline]
fn set_height(&self, height: c_int) {
unsafe { (*self.as_mut_ptr()).height = height }
}
#[inline]
fn bit_rate(&self) -> i64 {
unsafe { (*self.as_mut_ptr()).bit_rate }
}
#[inline]
fn set_bit_rate(&self, bit_rate: i64) {
unsafe {
(*self.as_mut_ptr()).bit_rate = bit_rate;
}
}
#[inline]
fn sample_aspect_ratio(&self) -> AVRational {
unsafe { (*self.as_ptr()).sample_aspect_ratio }
}
#[inline]
fn color_range(&self) -> AVColorRange {
unsafe { (*self.as_ptr()).color_range }
}
#[inline]
fn set_color_range(&self, color_range: AVColorRange) {
unsafe { (*self.as_mut_ptr()).color_range = color_range }
}
#[inline]
fn color_primaries(&self) -> AVColorPrimaries {
unsafe { (*self.as_ptr()).color_primaries }
}
#[inline]
fn set_color_primaries(&self, color_primaries: AVColorPrimaries) {
unsafe { (*self.as_mut_ptr()).color_primaries = color_primaries }
}
#[inline]
fn color_trc(&self) -> AVColorTransferCharacteristic {
unsafe { (*self.as_ptr()).color_trc }
}
#[inline]
fn set_color_trc(&self, color_trc: AVColorTransferCharacteristic) {
unsafe { (*self.as_mut_ptr()).color_trc = color_trc }
}
#[inline]
fn channels(&self) -> i32 {
unsafe { (*self.as_ptr()).channels }
}
#[inline]
fn set_channels(&self, channels: i32) {
unsafe { (*self.as_mut_ptr()).channels = channels }
}
#[inline]
fn channel_layout(&self) -> u64 {
unsafe { (*self.as_ptr()).channel_layout }
}
#[inline]
fn set_channel_layout(&self, channel_layout: u64) {
unsafe { (*self.as_mut_ptr()).channel_layout = channel_layout }
}
#[inline]
fn sample_format(&self) -> AVSampleFormat {
unsafe { (*self.as_ptr()).sample_fmt }
}
#[inline]
fn set_sample_format(&self, sample_format: AVSampleFormat) {
unsafe { (*self.as_mut_ptr()).sample_fmt = sample_format }
}
#[inline]
fn frame_size(&self) -> i32 {
unsafe { (*self.as_ptr()).frame_size }
}
#[inline]
fn set_frame_size(&self, frame_size: i32) {
unsafe { (*self.as_mut_ptr()).frame_size = frame_size }
}
#[inline]
fn sample_rate(&self) -> i32 {
unsafe { (*self.as_ptr()).sample_rate }
}
#[inline]
fn set_sample_rate(&self, sample_rate: i32) {
unsafe { (*self.as_mut_ptr()).sample_rate = sample_rate }
}
#[inline]
fn extra_data(&self) -> Vec<u8> {
unsafe {
let extra_data_size = (*self.as_ptr()).extradata_size as usize;
let mut extra_data = vec![0u8; extra_data_size];
(*self.as_ptr())
.extradata
.copy_to(extra_data.as_mut_ptr(), extra_data_size);
extra_data
}
}
#[inline]
fn set_extra_data(&self, extra_data: Vec<u8>) {
unsafe {
let new_extra_data: *mut u8 = av_malloc(extra_data.len()).cast();
(*self.as_mut_ptr()).extradata = new_extra_data;
(*self.as_mut_ptr()).extradata_size = extra_data.len() as i32;
}
}
fn set_private_data(&self, key: &str, value: &str) {
unsafe {
Dictionary {
ptr: (*self.as_ptr()).priv_data.cast(),
owned: false,
}
.set(key, value)
}
}
fn pixel_formats(&self) -> Vec<AVPixelFormat> {
let mut pixel_formats = vec![];
unsafe {
let mut current = (*(*self.as_ptr()).codec).pix_fmts;
while *current != AV_PIX_FMT_NONE {
pixel_formats.push(*current);
current = current.add(1);
}
}
pixel_formats
}
fn sample_formats(&self) -> Vec<AVSampleFormat> {
let mut sample_formats = vec![];
unsafe {
let mut current = (*(*self.as_ptr()).codec).sample_fmts;
while *current != AVSampleFormat::AV_SAMPLE_FMT_NONE {
sample_formats.push(*current);
current = current.add(1);
}
}
sample_formats
}
}
pub(crate) fn process_return(error: &str, resp: i32) -> Result<bool, String> {
if resp == AVERROR(EAGAIN) || resp == AVERROR_EOF {
Ok(false)
} else {
verify_response(error, resp).and(Ok(true))
}
}

@ -0,0 +1,35 @@
#![allow(dead_code)]
use crate::av::init;
use crate::transcoder::Transcoder;
use ffmpeg_sys_next::{av_make_error_string, av_malloc, av_version_info};
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int};
pub mod av;
pub mod transcoder;
pub mod utils;
// const INPUT_PATH: &str = "/tank/ephemeral/anime/series/Witch Hunter Robin - 2ndfire/[2ndfire]Witch_Hunter_Robin_-_01[91DCE49A].mkv";
const INPUT_PATH: &str = "/tank/ephemeral/anime/series/Brave Witches - Eila/Extras/[Eila] Brave Witches - NCOP [BD 1080p][DB44ED0B].mkv";
const OUTPUT_PATH: &str = "/tmp/transotf";
fn av_err2str(error_code: c_int) -> String {
unsafe {
let str: *mut c_char = av_malloc(1024).cast();
av_make_error_string(str, 1024, error_code);
CString::from_raw(str).to_str().unwrap().to_string()
}
}
fn main() {
println!("> transotf: ffmpeg {}", unsafe {
CStr::from_ptr(av_version_info()).to_str().unwrap()
});
init();
let mut transcoder = Transcoder::create(INPUT_PATH, OUTPUT_PATH, None, None)
.expect("Failed to create transcoder");
transcoder.transcode().expect("Failed to transcode");
}

@ -0,0 +1,488 @@
use crate::av::avio::{AVIOWriter, AVIO};
use crate::av::decoder::Decoder;
use crate::av::dictionary::Dictionary;
use crate::av::encoder::Encoder;
use crate::av::format::Format;
use crate::av::mov_avc::{annex_b_to_avc, AvcDecoderConfigurationRecord, NalBuffer, NalUnitType};
use crate::av::packet::Packet;
use crate::av::resampler::Resampler;
use crate::av::scaler::Scaler;
use crate::av::stream::Stream;
use crate::av::xcoder::XCoder;
use crate::av::Rational;
use crate::utils::SortedFrameBuffer;
use ffmpeg_sys_next::AVMediaType::{AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO};
use ffmpeg_sys_next::AVPictureType::AV_PICTURE_TYPE_I;
use ffmpeg_sys_next::AVPixelFormat::AV_PIX_FMT_YUV420P;
use ffmpeg_sys_next::{avio_wb32, AVIOContext, AVPixelFormat, SWS_BILINEAR};
use num_rational::Ratio;
use std::fs::{DirBuilder, File};
use std::io::Write;
use std::option::Option::Some;
pub struct Transcoder {
input: Format<()>,
input_video: Stream,
input_frame_rate: Ratio<i32>,
video_output: Format<Buffer>,
output_path: String,
output_video: Stream,
video_frame_buffer: SortedFrameBuffer,
video_encoder: Encoder,
video_decoder: Decoder,
frame_scaler: Scaler,
last_pts: i64,
current_pts: i64,
video_segment: u32,
extra_data: Option<Vec<u8>>,
audio: Option<AudioTranscoder>,
}
pub struct AudioTranscoder {
encoder: Encoder,
decoder: Decoder,
frame_buffer: SortedFrameBuffer,
output_stream: Stream,
input_stream: Stream,
format: Format<Buffer>,
output_path: String,
resampler: Resampler,
segment: u32,
last_pts: i64,
current_pts: i64,
}
impl AudioTranscoder {
fn process_packet(&mut self, packet: Packet) -> Result<(), String> {
self.decoder
.send_packet(&packet)
.map_err(|err| format!("Failed sending audio packet: {}", err))?;
while let Some(frame) = self.decoder.read_frame()? {
self.frame_buffer.insert_frame(frame);
self.encode()?;
}
Ok(())
}
fn start_segment(&self, force: bool) {
if self.segment == 0 && !force {
return;
}
unsafe {
let pb = (*self.format.as_mut_ptr()).pb;
avio_wb32(pb, 24);
ffio_wfourcc(pb, b"styp");
ffio_wfourcc(pb, b"msdh");
avio_wb32(pb, 0); /* minor */
ffio_wfourcc(pb, b"msdh");
ffio_wfourcc(pb, b"msix");
}
}
fn write_header(&mut self) -> Result<(), String> {
File::create(format!("{}/audio-init.mp4", &self.output_path))
.unwrap()
.write_all(&mut self.format.avio_inner_mut().unwrap().buffer())
.map_err(|_| "Failed to write audio init segment".to_string())?;
self.start_segment(true);
self.format.write_packet_null()?;
Ok(())
}
fn write_segment(&mut self) -> Result<(), String> {
if self.segment == 0 {
self.write_header()?;
}
if let Some(avio) = self.format.avio.as_mut() {
avio.flush()
}
let mut segment = self.format.avio_inner_mut().unwrap().buffer();
File::create(format!(
"{}/audio-segment-{:0>5}.m4s",
self.output_path, self.segment
))
.unwrap()
.write_all(&mut segment)
.map_err(|_| format!("Failed to write audio segment {}", self.segment))?;
self.start_segment(false);
Ok(())
}
fn encode(&mut self) -> Result<(), String> {
while let Some(frame) = self.frame_buffer.pop_first() {
let pts_passed = frame.pts() - self.last_pts;
if (pts_passed + self.current_pts) > (5 * self.input_stream.time_base().den()) as i64 {
self.format.write_packet_null()?;
println!(
"Next audio segment (current: {}, pts: {}/{})!",
self.segment, self.last_pts, self.current_pts,
);
self.write_segment()?;
self.segment += 1;
self.current_pts =
(pts_passed + self.current_pts) % self.input_stream.time_base().den() as i64;
} else {
self.current_pts += pts_passed
}
self.last_pts = frame.pts();
self.encoder.send_audio_frame(&frame)?;
while let Some(new_packet) = self.encoder.read_packet()? {
new_packet.set_pts(frame.pts());
new_packet.set_dts(new_packet.pts());
new_packet.set_duration(pts_passed);
new_packet.rescale(
self.input_stream.time_base(),
self.output_stream.time_base(),
);
new_packet.set_stream(0);
self.format.write_packet(&new_packet)?;
}
}
Ok(())
}
}
impl Transcoder {
pub fn create(
input_path: &str,
output_path: &str,
video_index: Option<i32>,
audio_index: Option<i32>,
) -> Result<Transcoder, String> {
let input = Format::open(input_path, None).expect("Failed to open media");
let output_path = output_path.to_string();
let input_video = input
.stream(AVMEDIA_TYPE_VIDEO, video_index)
.ok_or("Failed to find video stream".to_string())?;
let input_audio = input.stream(AVMEDIA_TYPE_AUDIO, audio_index);
if audio_index.is_some() && input_audio.is_none() {
return Err("Failed to find audio stream".to_string());
}
let input_frame_rate = input_video.avg_frame_rate(&input).unwrap().to_num();
let mut options = Dictionary::new();
options.set("movflags", "+dash+delay_moov+skip_trailer+frag_custom");
let video_avio = AVIO::writer(Buffer::default());
let video_output = Format::output_avio(video_avio, "mp4")?;
let output_video = video_output.new_stream("h264")?;
output_video.params().copy_from(&input_video.params())?;
output_video.set_sample_aspect_ratio(input_video.sample_aspect_ratio());
video_output.set_flags(input.flags());
let audio = if let Some(input_stream) = input_audio {
let audio_avio = AVIO::writer(Buffer::default());
let format = Format::output_avio(audio_avio, "mp4")?;
let output_stream = format.new_stream("aac")?;
format.set_flags(input.flags());
let decoder = input_stream.decoder(None).ok_or(format!(
"Couldn't find encoder for input audio with codec: {:?}",
input_stream.codec()
))?;
decoder.configure(&input_stream)?;
decoder.open()?;
let encoder = output_stream
.encoder(None)
.ok_or("Couldn't find encoder for AAC".to_string())?;
encoder.set_channels(decoder.channels());
encoder.set_channel_layout(decoder.channel_layout());
encoder.set_sample_format(
encoder
.sample_formats()
.first()
.copied()
.ok_or("No sample formats found for AAC")?,
);
encoder.set_sample_rate(decoder.sample_rate());
encoder.set_time_base(input_stream.time_base());
encoder.set_time_base(Ratio::new(1, decoder.sample_rate()));
output_stream.set_time_base(encoder.time_base());
encoder.set_frame_size(decoder.frame_size() * 4);
encoder.open()?;
encoder.configure(&output_stream)?;
println!(
"audio decoder[{}] -> audio encoder[{}]",
decoder.name(),
encoder.name()
);
let resampler = Resampler::from_coder(&decoder, &encoder);
Some(AudioTranscoder {
frame_buffer: SortedFrameBuffer::new(),
encoder,
decoder,
format,
output_stream,
input_stream,
output_path: output_path.to_string(),
current_pts: 0,
last_pts: 0,
segment: 0,
resampler,
})
} else {
None
};
let video_decoder = input_video.decoder_select(None, |d| {
d.configure(&input_video)?;
d.open()?;
Ok(())
})?;
let video_encoder = output_video.encoder_select(None, |encoder| {
encoder.set_pixel_format(AVPixelFormat::AV_PIX_FMT_YUV420P);
encoder.set_color_range(video_decoder.color_range());
encoder.set_color_primaries(video_decoder.color_primaries());
encoder.set_color_trc(video_decoder.color_trc());
encoder.set_height(video_decoder.height());
encoder.set_width(video_decoder.width());
encoder.set_time_base(input_frame_rate.invert());
output_video.set_time_base(encoder.time_base());
encoder.open()?;
encoder.configure(&output_video)?;
Ok(())
})?;
println!(
"video decoder[{}] -> video encoder[{}]",
video_decoder.name(),
video_encoder.name()
);
let param = output_video.params();
param.set_height(video_decoder.height());
param.set_width(video_decoder.width());
param.set_pixel_format(AV_PIX_FMT_YUV420P);
video_output.init_output(options)?;
let frame_scaler = Scaler::from_coder(&video_decoder, &video_encoder, SWS_BILINEAR);
let video_frame_buffer = SortedFrameBuffer::new();
Ok(Transcoder {
input,
input_video,
input_frame_rate,
video_output,
output_video,
audio,
output_path,
video_encoder,
video_decoder,
frame_scaler,
video_frame_buffer,
last_pts: 0,
current_pts: 0,
video_segment: 0,
extra_data: None,
})
}
pub fn transcode(&mut self) -> Result<(), String> {
DirBuilder::new()
.recursive(true)
.create(self.output_path.to_string())
.map_err(|_| "Failed to create target directory".to_string())?;
self.video_output.write_header(None).unwrap();
let input_video_index = self.input_video.index();
let input_audio_index = self.audio.as_ref().map(|ia| ia.input_stream.index());
while let Some(packet) = self.input.next_packet() {
if input_video_index == packet.stream() {
self.video_process_packet(packet)?;
} else if input_audio_index == Some(packet.stream()) {
// Safe assumption
if let Some(ref mut audio) = &mut self.audio {
audio.process_packet(packet)?;
}
}
}
self.encode_video()?;
self.video_output.write_trailer()?;
self.video_write_segment()?;
Ok(())
}
fn video_process_packet(&mut self, packet: Packet) -> Result<(), String> {
self.video_decoder.send_packet(&packet)?;
while let Some(frame) = self.video_decoder.read_frame()? {
let pts = frame.pts();
let frame = self
.frame_scaler
.scale(frame)
.expect("Failed to scale video frame");
frame.set_pts(pts);
self.video_frame_buffer.insert_frame(frame);
self.encode_video()?;
}
Ok(())
}
fn video_write_header(&mut self) -> Result<(), String> {
File::create(format!("{}/video-init.mp4", &self.output_path))
.unwrap()
.write_all(&mut self.video_output.avio_inner_mut().unwrap().buffer())
.map_err(|_| "Failed to write video init segment".to_string())?;
self.start_segment(true);
self.video_output.write_packet_null()?;
Ok(())
}
fn video_write_segment(&mut self) -> Result<(), String> {
if self.video_segment == 0 {
self.video_write_header()?;
}
if let Some(avio) = self.video_output.avio.as_mut() {
avio.flush()
}
let segment = self.video_output.avio_inner_mut().unwrap().buffer();
let mut segment = annex_b_to_avc(segment, 4)?;
File::create(format!(
"{}/video-segment-{:0>5}.m4s",
self.output_path, self.video_segment
))
.unwrap()
.write_all(&mut segment)
.map_err(|_| format!("Failed to write video segment {}", self.video_segment))?;
self.start_segment(false);
Ok(())
}
fn encode_video(&mut self) -> Result<(), String> {
while let Some(frame) = self.video_frame_buffer.pop_first() {
let pts_passed = frame.pts() - self.last_pts;
if (pts_passed + self.current_pts) > (5 * self.input_frame_rate.den()) as i64 {
self.video_output.write_packet_null()?;
println!(
"Next video segment (current: {}, pts: {}/{})!",
self.video_segment, self.last_pts, self.current_pts,
);
self.video_write_segment()?;
self.video_segment += 1;
self.current_pts =
(pts_passed + self.current_pts) % self.input_frame_rate.den() as i64;
frame.set_pict_type(AV_PICTURE_TYPE_I);
} else {
self.current_pts += pts_passed
}
self.last_pts = frame.pts();
self.video_encoder.send_video_frame(&frame)?;
while let Some(new_packet) = self.video_encoder.read_packet()? {
if self.extra_data.is_none() {
let items = NalBuffer::from_stream(new_packet.data())?;
let mut sps = None;
let mut pss = None;
for item in items {
if item.nal_unit.nal_unit_type == NalUnitType::PictureParameterSet {
pss = Some(item.to_vec());
} else if item.nal_unit.nal_unit_type == NalUnitType::SequenceParameterSet {
sps = Some(item.to_vec());
}
}
if let Some(sps) = sps {
if let Some(pss) = pss {
let config =
AvcDecoderConfigurationRecord::from_parameter_sets(sps, pss);
self.extra_data = Some(config.to_bytes()?);
}
}
if let Some(extra_data) = &self.extra_data {
self.output_video.params().set_extra_data(extra_data);
self.output_video
.params()
.set_sample_aspect_ratio(Ratio::new(1, 1));
}
}
new_packet.set_pts(frame.pts());
new_packet.set_dts(new_packet.pts());
new_packet.set_duration(pts_passed);
new_packet.rescale(self.input_video.time_base(), self.output_video.time_base());
new_packet.set_stream(0);
self.video_output.write_packet(&new_packet)?;
}
}
Ok(())
}
fn start_segment(&self, force: bool) {
if self.video_segment == 0 && !force {
return;
}
unsafe {
let pb = (*self.video_output.as_mut_ptr()).pb;
avio_wb32(pb, 24);
ffio_wfourcc(pb, b"styp");
ffio_wfourcc(pb, b"msdh");
avio_wb32(pb, 0); /* minor */
ffio_wfourcc(pb, b"msdh");
ffio_wfourcc(pb, b"msix");
}
}
}
#[derive(Clone, Default, Debug)]
struct Buffer {
buffer: Vec<u8>,
}
impl Buffer {
fn buffer(&mut self) -> Vec<u8> {
std::mem::replace(&mut self.buffer, vec![])
}
}
impl AVIOWriter for Buffer {
fn write(&mut self, mut buffer: Vec<u8>) {
self.buffer.append(&mut buffer)
}
}
#[inline]
fn ffio_wfourcc(pb: *mut AVIOContext, tag: &[u8]) {
let mut nr: u32 = 0;
nr |= tag[3] as u32;
nr |= (tag[2] as u32) << 8;
nr |= (tag[1] as u32) << 16;
nr |= (tag[0] as u32) << 24;
unsafe { avio_wb32(pb, nr) }
}

@ -0,0 +1,135 @@
use crate::av::frame::Frame;
use std::collections::HashMap;
use std::fmt::{Debug, Error, Formatter};
use std::hash::Hash;
pub struct SortedBuffer<K: Copy, V> {
lowest_value: Option<K>,
sorted_keys: Vec<K>,
items: HashMap<K, V>,
open: bool,
buffer_size: usize,
}
impl<K: Debug + Copy + Eq + Hash, V: Debug> Debug for SortedBuffer<K, V> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
f.debug_struct("SortedBuffer")
.field("lowest_value", &self.lowest_value)
.field("sorted_keys", &self.sorted_keys)
.field("items", &self.items)
.field("open", &self.open)
.field("buffer_size", &self.buffer_size)
.finish()
}
}
pub type SortedFrameBuffer = SortedBuffer<i64, Frame>;
impl SortedFrameBuffer {
pub fn insert_frame(&mut self, frame: Frame) -> bool {
self.insert(frame.pts(), frame)
}
}
impl<K: Copy + Debug + Eq + Hash, V: Debug> Default for SortedBuffer<K, V> {
fn default() -> Self {
SortedBuffer {
lowest_value: None,
sorted_keys: vec![],
items: Default::default(),
open: true,
buffer_size: 32,
}
}
}
impl<K: Copy + Debug + Eq + Hash + Ord, V: Debug> SortedBuffer<K, V> {
pub fn new() -> Self {
Default::default()
}
pub fn with_buffer_size(buffer_size: usize) -> Self {
let mut buffer: Self = Default::default();
buffer.buffer_size = buffer_size;
buffer
}
pub fn insert(&mut self, key: K, item: V) -> bool {
if self.items.len() >= self.buffer_size
&& self.lowest_value.map(|it| it > key).unwrap_or(false)
{
return false;
}
self.insert_key(key);
self.items.insert(key, item);
true
}
fn insert_key(&mut self, key: K) {
if self.lowest_value.map(|it| it > key).unwrap_or(true) {
self.lowest_value = Some(key)
}
let mut i = 0;
for entry in &self.sorted_keys {
if key < *entry {
break;
}
i += 1;
}
self.sorted_keys.insert(i, key);
}
pub fn close(&mut self) {
self.open = false;
}
pub fn pop_first(&mut self) -> Option<V> {
if self.sorted_keys.len() == 0 || (self.open && self.buffer_size >= self.items.len()) {
return None;
}
let first = self.sorted_keys.remove(0);
if let Some(new_first) = self.sorted_keys.get(0).copied() {
self.lowest_value = Some(new_first);
}
self.items.remove(&first)
}
pub fn len(&self) -> usize {
self.sorted_keys.len()
}
}
#[cfg(test)]
mod test {
use crate::utils::SortedBuffer;
#[test]
fn test_sorted_buffer() {
let mut buffer = SortedBuffer::with_buffer_size(5);
assert!(buffer.insert(2, 2));
assert!(buffer.insert(1, 1));
assert!(buffer.insert(3, 3));
assert!(buffer.insert(4, 4));
assert!(buffer.insert(5, 5));
assert!(!buffer.insert(0, 0));
assert_eq!(buffer.len(), 5);
assert!(buffer.insert(6, 6));
assert_eq!(buffer.len(), 6);
assert_eq!(buffer.pop_first(), Some(1));
assert_eq!(buffer.pop_first(), None);
assert_eq!(buffer.len(), 5);
buffer.close();
assert_eq!(buffer.pop_first(), Some(2));
assert_eq!(buffer.pop_first(), Some(3));
assert_eq!(buffer.pop_first(), Some(4));
assert_eq!(buffer.pop_first(), Some(5));
assert_eq!(buffer.pop_first(), Some(6));
assert_eq!(buffer.pop_first(), None);
}
}

@ -0,0 +1 @@
/tmp/transotf

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DASH Test</title>
<link href="//vjs.zencdn.net/7.8.2/video-js.min.css" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/shaka-player/3.0.1/shaka-player.ui.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/shaka-player/3.0.1/controls.css">
</head>
<body>
<video id="shaka" width="1280" height="720" controls></video>
<script type="application/javascript">
const manifestUri = 'test.mpd';
function initApp() {
// Install built-in polyfills to patch browser incompatibilities.
shaka.polyfill.installAll();
// Check to see if the browser supports the basic APIs Shaka needs.
if (shaka.Player.isBrowserSupported()) {
// Everything looks good!
initPlayer();
} else {
// This browser does not have the minimum set of APIs we need.
console.error('Browser not supported!');
}
}
async function initPlayer() {
// Create a Player instance.
const video = document.getElementById('shaka');
const player = new shaka.Player(video);
// Attach player to the window to make it easy to access in the JS console.
window.shakaPlayer = player;
// Listen for error events.
player.addEventListener('error', onErrorEvent);
// Try to load a manifest.
// This is an asynchronous process.
try {
await player.load(manifestUri);
// This runs if the asynchronous load is successful.
console.log('The video has now been loaded!');
} catch (e) {
// onError is executed if the asynchronous load fails.
onError(e);
}
}
function onErrorEvent(event) {
// Extract the shaka.util.Error object from the event.
onError(event.detail);
}
function onError(error) {
// Log the error.
console.error('Error code', error.code, 'object', error);
}
document.addEventListener('DOMContentLoaded', initApp);
</script>
</body>
</html>

@ -0,0 +1,17 @@
<?xml version="1.0"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"
profiles="urn:mpeg:dash:profile:isoff-live:2011"
type="static"
minBufferTime="PT6.1S"
mediaPresentationDuration="PT1M30S">
<Period start="PT0.0S">
<BaseURL>data/</BaseURL>
<AdaptationSet contentType="video">
<Representation mimeType="video/mp4" codecs="avc1.640028" id="test" bandwidth="1654197" width="1920" height="1080" frameRate="24000/1001">
<SegmentTemplate media="video-segment-$Number%05d$.m4s"
initialization="video-init.mp4" duration="5005" timescale="1001"
startNumber="0"/>
</Representation>
</AdaptationSet>
</Period>
</MPD>

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DASH Test</title>
<link href="//vjs.zencdn.net/7.8.2/video-js.min.css" rel="stylesheet">
<script src="//vjs.zencdn.net/7.8.2/video.js"></script>
<script src="https://unpkg.com/@videojs/http-streaming@1.13.3/dist/videojs-http-streaming.js"></script>
</head>
<body>
<video-js controls width=1280 height=720 class="video-js" data-setup='{}'>
<source type="application/dash+xml" src="test.mpd">
</video-js>
</body>
</html>
Loading…
Cancel
Save