use std::{
ffi::CString,
io::Write,
os::raw::{c_char, c_int, c_uint, c_void},
ptr,
};
use crate::{
codec::CodecParameters,
format::{io::IO, stream::Stream},
packet::Packet,
Error,
};
extern "C" {
fn ffw_guess_output_format(
short_name: *const c_char,
file_name: *const c_char,
mime_type: *const c_char,
) -> *const c_void;
fn ffw_muxer_new() -> *mut c_void;
fn ffw_muxer_get_nb_streams(muxer: *const c_void) -> c_uint;
fn ffw_muxer_get_stream(muxer: *mut c_void, stream_index: c_uint) -> *mut c_void;
fn ffw_muxer_new_stream(muxer: *mut c_void, params: *const c_void) -> c_int;
fn ffw_muxer_init(muxer: *mut c_void, io_context: *mut c_void, format: *const c_void) -> c_int;
fn ffw_muxer_set_initial_option(
muxer: *mut c_void,
key: *const c_char,
value: *const c_char,
) -> c_int;
fn ffw_muxer_set_option(muxer: *mut c_void, key: *const c_char, value: *const c_char) -> c_int;
fn ffw_muxer_set_url(muxer: *mut c_void, url: *const c_char) -> c_int;
fn ffw_muxer_set_metadata(
stream: *mut c_void,
key: *const c_char,
value: *const c_char,
) -> c_int;
fn ffw_muxer_write_frame(
muxer: *mut c_void,
packet: *mut c_void,
tb_num: u32,
tb_den: u32,
) -> c_int;
fn ffw_muxer_interleaved_write_frame(
muxer: *mut c_void,
packet: *mut c_void,
tb_num: u32,
tb_den: u32,
) -> c_int;
fn ffw_muxer_free(muxer: *mut c_void) -> c_int;
}
pub struct MuxerBuilder {
ptr: *mut c_void,
streams: Vec<Stream>,
interleaved: bool,
}
impl MuxerBuilder {
fn new() -> MuxerBuilder {
let ptr = unsafe { ffw_muxer_new() };
if ptr.is_null() {
panic!("unable to allocate a muxer context");
}
MuxerBuilder {
ptr,
streams: Vec::new(),
interleaved: false,
}
}
pub fn add_stream(&mut self, params: &CodecParameters) -> Result<usize, Error> {
let stream_index = unsafe { ffw_muxer_new_stream(self.ptr, params.as_ptr()) };
if stream_index < 0 {
return Err(Error::from_raw_error_code(stream_index));
}
let stream = unsafe { ffw_muxer_get_stream(self.ptr, stream_index as _) };
if stream.is_null() {
panic!("stream was not created");
}
let stream = unsafe { Stream::from_raw_ptr(stream) };
self.streams.push(stream);
Ok(stream_index as usize)
}
pub fn streams(&self) -> &[Stream] {
&self.streams
}
pub fn streams_mut(&mut self) -> &mut [Stream] {
&mut self.streams
}
pub fn set_option<V>(self, name: &str, value: V) -> MuxerBuilder
where
V: ToString,
{
let value = CString::new(value.to_string()).expect("invalid option value");
if name == "url" {
let ret = unsafe { ffw_muxer_set_url(self.ptr, value.as_ptr()) };
if ret < 0 {
panic!("unable to allocate URL")
}
} else {
let name = CString::new(name).expect("invalid option name");
let ret =
unsafe { ffw_muxer_set_initial_option(self.ptr, name.as_ptr(), value.as_ptr()) };
if ret < 0 {
panic!("unable to allocate an option");
}
}
self
}
#[doc(hidden)]
#[deprecated(since = "0.17.0", note = "Use `set_option(\"url\", ...)` instead.")]
pub fn set_url(self, url: &str) -> MuxerBuilder {
self.set_option("url", url)
}
pub fn set_metadata<V>(self, key: &str, value: V) -> Self
where
V: ToString,
{
let key = CString::new(key).expect("invalid metadata key");
let value = CString::new(value.to_string()).expect("invalid metadata value");
let ret = unsafe { ffw_muxer_set_metadata(self.ptr, key.as_ptr(), value.as_ptr()) };
if ret < 0 {
panic!("unable to allocate metadata");
}
self
}
pub fn interleaved(mut self, interleaved: bool) -> MuxerBuilder {
self.interleaved = interleaved;
self
}
pub fn build<T>(mut self, mut io: IO<T>, format: OutputFormat) -> Result<Muxer<T>, Error>
where
T: Write,
{
let io_context_ptr = io.io_context_mut().as_mut_ptr();
let format_ptr = format.ptr;
let ret = unsafe { ffw_muxer_init(self.ptr, io_context_ptr, format_ptr) };
if ret < 0 {
return Err(Error::from_raw_error_code(ret));
}
let muxer_ptr = self.ptr;
self.ptr = ptr::null_mut();
let res = Muxer {
ptr: muxer_ptr,
io: Some(io),
interleaved: self.interleaved,
};
Ok(res)
}
}
impl Drop for MuxerBuilder {
fn drop(&mut self) {
unsafe {
ffw_muxer_free(self.ptr);
}
}
}
unsafe impl Send for MuxerBuilder {}
unsafe impl Sync for MuxerBuilder {}
pub struct Muxer<T> {
ptr: *mut c_void,
io: Option<IO<T>>,
interleaved: bool,
}
impl Muxer<()> {
pub fn builder() -> MuxerBuilder {
MuxerBuilder::new()
}
}
impl<T> Muxer<T> {
pub fn set_option<V>(&mut self, name: &str, value: V) -> Result<(), Error>
where
V: ToString,
{
let name = CString::new(name).expect("invalid option name");
let value = CString::new(value.to_string()).expect("invalid option value");
let ret =
unsafe { ffw_muxer_set_option(self.ptr, name.as_ptr() as _, value.as_ptr() as _) };
if ret < 0 {
Err(Error::from_raw_error_code(ret))
} else {
Ok(())
}
}
pub fn push(&mut self, mut packet: Packet) -> Result<(), Error> {
let nb_streams = unsafe { ffw_muxer_get_nb_streams(self.ptr) as usize };
assert!(packet.stream_index() < nb_streams);
let tb = packet.time_base();
let ret = unsafe {
if self.interleaved {
ffw_muxer_interleaved_write_frame(self.ptr, packet.as_mut_ptr(), tb.num(), tb.den())
} else {
ffw_muxer_write_frame(self.ptr, packet.as_mut_ptr(), tb.num(), tb.den())
}
};
if ret < 0 {
Err(Error::from_raw_error_code(ret))
} else {
Ok(())
}
}
pub fn flush(&mut self) -> Result<(), Error> {
let ret = unsafe {
if self.interleaved {
ffw_muxer_interleaved_write_frame(self.ptr, ptr::null_mut(), 1, 1_000_000)
} else {
ffw_muxer_write_frame(self.ptr, ptr::null_mut(), 1, 1_000_000)
}
};
if ret < 0 {
Err(Error::from_raw_error_code(ret))
} else {
Ok(())
}
}
pub fn close(mut self) -> Result<IO<T>, Error> {
let ret = unsafe { ffw_muxer_free(self.ptr) };
self.ptr = ptr::null_mut();
if ret != 0 {
Err(Error::from_raw_error_code(ret))
} else {
Ok(self.io.take().unwrap())
}
}
pub fn io(&self) -> &IO<T> {
self.io.as_ref().unwrap()
}
pub fn io_mut(&mut self) -> &mut IO<T> {
self.io.as_mut().unwrap()
}
}
impl<T> Drop for Muxer<T> {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe {
ffw_muxer_free(self.ptr);
}
}
}
}
unsafe impl<T> Send for Muxer<T> where T: Send {}
unsafe impl<T> Sync for Muxer<T> where T: Sync {}
pub struct OutputFormat {
ptr: *const c_void,
}
impl OutputFormat {
pub fn find_by_name(name: &str) -> Option<OutputFormat> {
let name = CString::new(name).expect("invalid format name");
let ptr =
unsafe { ffw_guess_output_format(name.as_ptr() as *const _, ptr::null(), ptr::null()) };
if ptr.is_null() {
return None;
}
let res = OutputFormat { ptr };
Some(res)
}
pub fn find_by_mime_type(mime_type: &str) -> Option<OutputFormat> {
let mime_type = CString::new(mime_type).expect("invalid MIME type");
let ptr = unsafe {
ffw_guess_output_format(ptr::null(), ptr::null(), mime_type.as_ptr() as *const _)
};
if ptr.is_null() {
return None;
}
let res = OutputFormat { ptr };
Some(res)
}
pub fn guess_from_file_name(file_name: &str) -> Option<OutputFormat> {
let file_name = CString::new(file_name).expect("invalid file name");
let ptr = unsafe {
ffw_guess_output_format(ptr::null(), file_name.as_ptr() as *const _, ptr::null())
};
if ptr.is_null() {
return None;
}
let res = OutputFormat { ptr };
Some(res)
}
}
unsafe impl Send for OutputFormat {}
unsafe impl Sync for OutputFormat {}