From b7a015c79a3c6300bf76d50b44cdff9d0eb11305 Mon Sep 17 00:00:00 2001 From: meh Date: Tue, 12 May 2015 03:57:17 +0200 Subject: [PATCH] format: add Format, format::Context and Stream --- src/format/context.rs | 392 ++++++++++++++++++++++++++++++++++++++++++ src/format/format.rs | 194 +++++++++++++++++++++ src/format/mod.rs | 27 +++ src/format/stream.rs | 132 ++++++++++++++ src/lib.rs | 2 + 5 files changed, 747 insertions(+) create mode 100644 src/format/context.rs create mode 100644 src/format/format.rs create mode 100644 src/format/stream.rs diff --git a/src/format/context.rs b/src/format/context.rs new file mode 100644 index 0000000..fd04c02 --- /dev/null +++ b/src/format/context.rs @@ -0,0 +1,392 @@ +use std::ffi::CString; +use std::ptr; +use std::path::Path; +use std::marker::PhantomData; +use std::ops::Deref; + +use libc::{c_int, c_uint}; +use ffi::*; +use ::{Error, Dictionary, Codec, Stream, Format}; +use ::device; + +pub struct Context<'a> { + pub ptr: *mut AVFormatContext, + + input: bool, + _marker: PhantomData<&'a i32>, +} + +impl<'a> Context<'a> { + pub fn new() -> Self { + unsafe { + Context { + ptr: avformat_alloc_context(), + + input: false, + _marker: PhantomData, + } + } + } + + pub fn input(ptr: *mut AVFormatContext) -> Self { + Context { + ptr: ptr, + + input: true, + _marker: PhantomData, + } + } + + pub fn streams(&'a self) -> StreamIter<'a> { + StreamIter::new(self.ptr) + } + + pub fn devices(&'a self) -> Result, Error> { + DeviceIter::new(self.ptr) + } + + pub fn probe_score(&self) -> i32 { + unsafe { + av_format_get_probe_score(self.ptr) + } + } + + pub fn video_codec(&'a self) -> Option> { + unsafe { + let ptr = av_format_get_video_codec(self.ptr); + + if ptr == ptr::null_mut() { + None + } + else { + Some(Codec::wrap(ptr)) + } + } + } + + pub fn set_video_codec(&'a mut self, value: Codec<'a>) { + unsafe { + av_format_set_video_codec(self.ptr, value.ptr); + } + } + + pub fn audio_codec(&'a self) -> Option> { + unsafe { + let ptr = av_format_get_audio_codec(self.ptr); + + if ptr == ptr::null_mut() { + None + } + else { + Some(Codec::wrap(ptr)) + } + } + } + + pub fn set_audio_codec(&'a mut self, value: Codec<'a>) { + unsafe { + av_format_set_audio_codec(self.ptr, value.ptr); + } + } + + pub fn subtitle_codec(&'a self) -> Option> { + unsafe { + let ptr = av_format_get_subtitle_codec(self.ptr); + + if ptr == ptr::null_mut() { + None + } + else { + Some(Codec::wrap(ptr)) + } + } + } + + pub fn set_subtitle_codec(&'a mut self, value: Codec<'a>) { + unsafe { + av_format_set_subtitle_codec(self.ptr, value.ptr); + } + } + + pub fn data_codec(&'a self) -> Option> { + unsafe { + let ptr = av_format_get_data_codec(self.ptr); + + if ptr == ptr::null_mut() { + None + } + else { + Some(Codec::wrap(ptr)) + } + } + } + + pub fn set_data_codec(&'a mut self, value: Codec<'a>) { + unsafe { + av_format_set_data_codec(self.ptr, value.ptr); + } + } + + pub fn packet(&'a self) -> Packet<'a> { + Packet::new(self.ptr) + } +} + +impl<'a> Drop for Context<'a> { + fn drop(&mut self) { + unsafe { + if self.input { + avformat_close_input(&mut self.ptr); + } + else { + avformat_free_context(self.ptr); + } + } + } +} + +pub struct Packet<'a> { + ptr: *mut AVFormatContext, + pkt: ::Packet, + + _marker: PhantomData<&'a Context<'a>>, +} + +impl<'a> Packet<'a> { + pub fn new(ptr: *mut AVFormatContext) -> Self { + Packet { ptr: ptr, pkt: ::Packet::new(), _marker: PhantomData } + } + + pub fn stream(&'a self) -> Stream<'a> { + unsafe { + Stream::wrap(*(*self.ptr).streams.offset(self.pkt.val.stream_index as isize)) + } + } + + pub fn read(&mut self) -> Result<(), Error> { + unsafe { + match av_read_frame(self.ptr, &mut self.pkt.val) { + 0 => Ok(()), + e => Err(Error::new(e)) + } + } + } + + pub fn write(&mut self) -> Result { + unsafe { + match av_write_frame(self.ptr, &mut self.pkt.val) { + 1 => Ok(true), + 0 => Ok(false), + e => Err(Error::new(e)) + } + } + } +} + +impl<'a> Deref for Packet<'a> { + type Target = ::Packet; + + fn deref<'b>(&'b self) -> &'b ::Packet { + &self.pkt + } +} + +pub struct StreamIter<'a> { + ptr: *const AVFormatContext, + cur: c_uint, + + _marker: PhantomData<&'a Context<'a>>, +} + +impl<'a> StreamIter<'a> { + pub fn new(ptr: *const AVFormatContext) -> Self { + StreamIter { ptr: ptr, cur: 0, _marker: PhantomData } + } +} + +impl<'a> Iterator for StreamIter<'a> { + type Item = Stream<'a>; + + fn next(&mut self) -> Option<::Item> { + unsafe { + if self.cur >= (*self.ptr).nb_streams { + None + } + else { + self.cur += 1; + Some(Stream::wrap(*(*self.ptr).streams.offset((self.cur - 1) as isize))) + } + } + } +} + +pub struct DeviceIter<'a> { + ptr: *mut AVDeviceInfoList, + cur: c_int, + + _marker: PhantomData<&'a i32>, +} + +impl<'a> DeviceIter<'a> { + pub fn new(ctx: *mut AVFormatContext) -> Result { + unsafe { + let mut ptr: *mut AVDeviceInfoList = ptr::null_mut(); + + match avdevice_list_devices(ctx, &mut ptr) { + n if n < 0 => + Err(Error::new(n)), + + _ => + Ok(DeviceIter { ptr: ptr, cur: 0, _marker: PhantomData }) + } + } + } + + pub fn default(&self) -> usize { + unsafe { + (*self.ptr).default_device as usize + } + } +} + +impl<'a> Drop for DeviceIter<'a> { + fn drop(&mut self) { + unsafe { + avdevice_free_list_devices(&mut self.ptr); + } + } +} + +impl<'a> Iterator for DeviceIter<'a> { + type Item = device::Info<'a>; + + fn next(&mut self) -> Option<::Item> { + unsafe { + if self.cur >= (*self.ptr).nb_devices { + None + } + else { + self.cur += 1; + Some(device::Info::wrap(*(*self.ptr).devices.offset((self.cur - 1) as isize))) + } + } + } +} + +pub fn open<'a>(path: &Path) -> Result, Error> { + unsafe { + let mut ps = ptr::null_mut(); + let path = path.as_os_str().to_cstring().unwrap().as_ptr(); + let status = avformat_open_input(&mut ps, path, ptr::null_mut(), ptr::null_mut()); + + match status { + 0 => { + let ctx = Context::input(ps); + + match avformat_find_stream_info(ps, ptr::null_mut()) { + 0 => Ok(ctx), + e => Err(Error::new(e)) + } + }, + + e => Err(Error::new(e)) + } + } +} + +pub fn open_with<'a>(path: &Path, mut options: Dictionary) -> Result, Error> { + unsafe { + let mut ps = ptr::null_mut(); + let path = path.as_os_str().to_cstring().unwrap().as_ptr(); + let opts = &mut options.ptr; + let status = avformat_open_input(&mut ps, path, ptr::null_mut(), opts); + + av_dict_free(opts); + + match status { + 0 => { + let ctx = Context::input(ps); + + match avformat_find_stream_info(ps, ptr::null_mut()) { + 0 => Ok(ctx), + e => Err(Error::new(e)) + } + }, + + e => Err(Error::new(e)) + } + } +} + +pub fn open_as<'a>(path: &Path, format: &Format) -> Result, Error> { + if let &Format::Input(ref format) = format { + unsafe { + let mut ps = ptr::null_mut(); + let path = path.as_os_str().to_cstring().unwrap().as_ptr(); + let status = avformat_open_input(&mut ps, path, format.ptr, ptr::null_mut()); + + match status { + 0 => { + let ctx = Context::input(ps); + + match avformat_find_stream_info(ps, ptr::null_mut()) { + 0 => Ok(ctx), + e => Err(Error::new(e)) + } + }, + + e => Err(Error::new(e)) + } + } + } + else { + Err(Error::new(AVERROR_BUG)) + } +} + +pub fn open_as_with<'a>(path: &Path, format: &Format, mut options: Dictionary) -> Result, Error> { + if let &Format::Input(ref format) = format { + unsafe { + let mut ps = ptr::null_mut(); + let path = path.as_os_str().to_cstring().unwrap().as_ptr(); + let opts = &mut options.ptr; + let status = avformat_open_input(&mut ps, path, format.ptr, opts); + + av_dict_free(opts); + + match status { + 0 => { + let ctx = Context::input(ps); + + match avformat_find_stream_info(ps, ptr::null_mut()) { + 0 => Ok(ctx), + e => Err(Error::new(e)) + } + }, + + e => Err(Error::new(e)) + } + } + } + else { + Err(Error::new(AVERROR_BUG)) + } +} + +pub fn dump(ctx: &Context, index: i32, url: Option<&str>) { + let url = if let Some(url) = url { + CString::new(url).unwrap().as_ptr() + } + else { + ptr::null() + }; + + unsafe { + if ctx.input { + av_dump_format(ctx.ptr, index, url, 0); + } + else { + av_dump_format(ctx.ptr, index, url, 1); + } + } +} diff --git a/src/format/format.rs b/src/format/format.rs new file mode 100644 index 0000000..643b130 --- /dev/null +++ b/src/format/format.rs @@ -0,0 +1,194 @@ +use std::ptr; +use std::ffi::CStr; +use std::str::from_utf8_unchecked; + +use ffi::*; + +pub enum Format { + Input(Input), + Output(Output), +} + +impl Format { + pub fn name<'a>(&'a self) -> &'a str { + match self { + &Format::Input(ref f) => f.name(), + &Format::Output(ref f) => f.name() + } + } + + pub fn description<'a>(&'a self) -> &'a str { + match self { + &Format::Input(ref f) => f.description(), + &Format::Output(ref f) => f.description() + } + } + + pub fn extensions<'a>(&'a self) -> Vec<&'a str> { + match self { + &Format::Input(ref f) => f.extensions(), + &Format::Output(ref f) => f.extensions() + } + } + + pub fn mime_types<'a>(&'a self) -> Vec<&'a str> { + match self { + &Format::Input(ref f) => f.mime_types(), + &Format::Output(ref f) => f.mime_types() + } + } +} + +pub struct Input { + pub ptr: *mut AVInputFormat, +} + +impl Input { + pub fn wrap(ptr: *mut AVInputFormat) -> Self { + Input { ptr: ptr } + } + + pub fn name<'a>(&'a self) -> &'a str { + unsafe { + from_utf8_unchecked(CStr::from_ptr((*self.ptr).name).to_bytes()) + } + } + + pub fn description<'a>(&'a self) -> &'a str { + unsafe { + from_utf8_unchecked(CStr::from_ptr((*self.ptr).name).to_bytes()) + } + } + + pub fn extensions<'a>(&'a self) -> Vec<&'a str> { + unsafe { + let ptr = (*self.ptr).extensions; + + if ptr == ptr::null() { + vec!() + } + else { + from_utf8_unchecked(CStr::from_ptr(ptr).to_bytes()).split(',').collect() + } + } + } + + pub fn mime_types<'a>(&'a self) -> Vec<&'a str> { + unsafe { + let ptr = (*self.ptr).mime_type; + + if ptr == ptr::null() { + vec!() + } + else { + from_utf8_unchecked(CStr::from_ptr(ptr).to_bytes()).split(',').collect() + } + } + } +} + +pub struct Output { + pub ptr: *mut AVOutputFormat, +} + +impl Output { + pub fn wrap(ptr: *mut AVOutputFormat) -> Self { + Output { ptr: ptr } + } + + pub fn name<'a>(&'a self) -> &'a str { + unsafe { + from_utf8_unchecked(CStr::from_ptr((*self.ptr).name).to_bytes()) + } + } + + pub fn description<'a>(&'a self) -> &'a str { + unsafe { + from_utf8_unchecked(CStr::from_ptr((*self.ptr).name).to_bytes()) + } + } + + pub fn extensions<'a>(&'a self) -> Vec<&'a str> { + unsafe { + let ptr = (*self.ptr).extensions; + + if ptr == ptr::null() { + vec!() + } + else { + from_utf8_unchecked(CStr::from_ptr(ptr).to_bytes()).split(',').collect() + } + } + } + + pub fn mime_types<'a>(&'a self) -> Vec<&'a str> { + unsafe { + let ptr = (*self.ptr).mime_type; + + if ptr == ptr::null() { + vec!() + } + else { + from_utf8_unchecked(CStr::from_ptr(ptr).to_bytes()).split(',').collect() + } + } + } +} + +pub fn list() -> FormatIter { + FormatIter::new() +} + +pub struct FormatIter { + input: *mut AVInputFormat, + output: *mut AVOutputFormat, + step: usize, +} + +impl FormatIter { + pub fn new() -> Self { + FormatIter { input: ptr::null_mut(), output: ptr::null_mut(), step: 0 } + } +} + +impl Iterator for FormatIter { + type Item = Format; + + fn next(&mut self) -> Option<::Item> { + unsafe { + match self.step { + 0 => { + let ptr = av_iformat_next(self.input); + + if ptr == ptr::null_mut() && self.input != ptr::null_mut() { + self.step = 1; + + self.next() + } + else { + self.input = ptr; + + Some(Format::Input(Input::wrap(ptr))) + } + }, + + 1 => { + let ptr = av_oformat_next(self.output); + + if ptr == ptr::null_mut() && self.output != ptr::null_mut() { + self.step = 2; + + self.next() + } + else { + self.output = ptr; + + Some(Format::Output(Output::wrap(ptr))) + } + }, + + _ => None + } + } + } +} diff --git a/src/format/mod.rs b/src/format/mod.rs index 14f414f..c63c30b 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -1,12 +1,39 @@ pub use ::util::sample_format::SampleFormat as Sample; pub use ::util::pixel_format::PixelFormat as Pixel; +pub mod stream; + +pub mod context; +pub use self::context::{Context, open, open_with, open_as, open_as_with, dump}; + +pub mod format; +pub use self::format::{Input, Output, list}; + pub mod network; use std::ffi::CStr; use std::str::from_utf8_unchecked; use ffi::*; +use ::Format; + +pub fn register_all() { + unsafe { + av_register_all(); + } +} + +pub fn register(format: &Format) { + match format { + &Format::Input(ref format) => unsafe { + av_register_input_format(format.ptr); + }, + + &Format::Output(ref format) => unsafe { + av_register_output_format(format.ptr); + } + } +} pub fn version() -> u32 { unsafe { diff --git a/src/format/stream.rs b/src/format/stream.rs new file mode 100644 index 0000000..5569189 --- /dev/null +++ b/src/format/stream.rs @@ -0,0 +1,132 @@ +use std::marker::PhantomData; + +use libc::c_int; +use ffi::*; +use ::format; +use ::codec::{self, packet}; +use ::{Rational, Discard}; + +bitflags! { + flags Disposition: i32 { + const DISPOSITION_DEFAULT = AV_DISPOSITION_DEFAULT, + const DISPOSITION_DUB = AV_DISPOSITION_DUB, + const DISPOSITION_ORIGINAL = AV_DISPOSITION_ORIGINAL, + const DISPOSITION_COMMENT = AV_DISPOSITION_COMMENT, + const DISPOSITION_LYRICS = AV_DISPOSITION_LYRICS, + const DISPOSITION_KARAOKE = AV_DISPOSITION_KARAOKE, + const DISPOSITION_FORCED = AV_DISPOSITION_FORCED, + const DISPOSITION_HEARING_IMPAIRED = AV_DISPOSITION_HEARING_IMPAIRED, + const DISPOSITION_VISUAL_IMPAIRED = AV_DISPOSITION_VISUAL_IMPAIRED, + const DISPOSITION_CLEAN_EFFECTS = AV_DISPOSITION_CLEAN_EFFECTS, + const DISPOSITION_ATTACHED_PIC = AV_DISPOSITION_ATTACHED_PIC, + const DISPOSITION_CAPTIONS = AV_DISPOSITION_CAPTIONS, + const DISPOSITION_DESCRIPTIONS = AV_DISPOSITION_DESCRIPTIONS, + const DISPOSITION_METADATA = AV_DISPOSITION_METADATA, + } +} + +#[derive(Eq, PartialEq)] +pub struct Stream<'a> { + ptr: *mut AVStream, + + _marker: PhantomData<&'a format::Context<'a>>, +} + +impl<'a> Stream<'a> { + pub fn wrap(ptr: *mut AVStream) -> Self { + Stream { ptr: ptr, _marker: PhantomData } + } + + pub fn codec(&'a self) -> codec::Context<'a> { + unsafe { + codec::Context::wrap((*self.ptr).codec) + } + } + + pub fn index(&self) -> usize { + unsafe { + (*self.ptr).index as usize + } + } + + pub fn time_base(&self) -> Rational { + unsafe { + Rational((*self.ptr).time_base) + } + } + + pub fn start_time(&self) -> i64 { + unsafe { + (*self.ptr).start_time + } + } + + pub fn duration(&self) -> i64 { + unsafe { + (*self.ptr).duration + } + } + + pub fn frames(&self) -> i64 { + unsafe { + (*self.ptr).nb_frames + } + } + + pub fn disposition(&self) -> Disposition { + unsafe { + Disposition::from_bits_truncate((*self.ptr).disposition) + } + } + + pub fn discard(&self) -> Discard { + unsafe { + Discard::from((*self.ptr).discard) + } + } + + pub fn side_data(&'a self) -> SideDataIter<'a> { + SideDataIter::new(self.ptr) + } + + pub fn frame_rate(&self) -> Rational { + unsafe { + Rational(av_stream_get_r_frame_rate(self.ptr)) + } + } + + pub fn set_frame_rate(&self, value: Rational) { + unsafe { + av_stream_set_r_frame_rate(self.ptr, value.0); + } + } +} + +pub struct SideDataIter<'a> { + ptr: *mut AVStream, + cur: c_int, + + _marker: PhantomData<&'a Stream<'a>>, +} + +impl<'a> SideDataIter<'a> { + pub fn new(ptr: *mut AVStream) -> Self { + SideDataIter { ptr: ptr, cur: 0, _marker: PhantomData } + } +} + +impl<'a> Iterator for SideDataIter<'a> { + type Item = packet::SideData<'a>; + + fn next(&mut self) -> Option<::Item> { + unsafe { + if self.cur >= (*self.ptr).nb_side_data { + None + } + else { + self.cur += 1; + Some(packet::SideData::wrap((*self.ptr).side_data.offset((self.cur - 1) as isize))) + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index f36d76d..caa3eca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,8 @@ pub use util::media; pub use util::frame::{self, Frame}; pub mod format; +pub use format::format::Format; +pub use format::stream::Stream; pub mod codec; pub use codec::packet::{self, Packet};