diff --git a/examples/cashu.svg b/examples/cashu.svg
new file mode 100644
index 0000000..3230c9b
--- /dev/null
+++ b/examples/cashu.svg
@@ -0,0 +1,66 @@
+
+
diff --git a/examples/main.rs b/examples/main.rs
index c8f45a4..b0f6c64 100644
--- a/examples/main.rs
+++ b/examples/main.rs
@@ -1,26 +1,11 @@
use ffmpeg_rs_raw::{Decoder, Demuxer};
use ffmpeg_sys_the_third::{av_frame_free, av_packet_free};
+use std::collections::HashMap;
use std::env::args;
use std::fs::File;
-use std::io::Read;
+use std::io::{Cursor, Read};
use std::path::PathBuf;
-struct DropTest {
- inner: File,
-}
-
-impl Read for DropTest {
- fn read(&mut self, buf: &mut [u8]) -> std::io::Result {
- self.inner.read(buf)
- }
-}
-
-impl Drop for DropTest {
- fn drop(&mut self) {
- println!("Dropped!");
- }
-}
-
fn main() {
let name = args().next().unwrap_or("main".to_string());
let path = if let Some(path) = args().skip(1).next() {
@@ -35,11 +20,27 @@ fn main() {
let fd = read_as_file(path.clone());
scan_input(fd);
+
+ let mut opt = HashMap::new();
+ opt.insert("analyzeduration".to_string(), "999".to_string());
+ let svg = include_bytes!("./cashu.svg");
+ let mut dx = Demuxer::new_custom_io(svg.as_slice(), Some("cashu.svg".to_string()));
+ dx.set_opt(opt.clone()).unwrap();
+ unsafe {
+ let mut decoder = Decoder::new();
+ let info = dx.probe_input().expect("probe failed");
+ for chan in &info.channels {
+ decoder.setup_decoder(chan, None).expect("setup failed");
+ }
+ loop_decoder(dx, decoder);
+ }
}
fn read_as_custom_io(path: PathBuf) -> Demuxer {
- let file = File::open(path).unwrap();
- Demuxer::new_custom_io(DropTest { inner: file })
+ let mut data: Vec = Vec::new();
+ File::open(path).unwrap().read_to_end(&mut data).unwrap();
+ let reader = Cursor::new(data);
+ Demuxer::new_custom_io(reader, None)
}
fn read_as_file(path_buf: PathBuf) -> Demuxer {
@@ -50,20 +51,27 @@ fn scan_input(mut demuxer: Demuxer) {
unsafe {
let info = demuxer.probe_input().expect("demuxer failed");
println!("{}", info);
-
- let mut decoder = Decoder::new();
- loop {
- let (mut pkt, stream) = demuxer.get_packet().expect("demuxer failed");
- if pkt.is_null() {
- break; // EOF
- }
- if let Ok(frames) = decoder.decode_pkt(pkt, stream) {
- for (mut frame, _stream) in frames {
- // do nothing but decode entire stream
- av_frame_free(&mut frame);
- }
- }
- av_packet_free(&mut pkt);
- }
+ decode_input(demuxer);
+ }
+}
+
+unsafe fn decode_input(demuxer: Demuxer) {
+ let decoder = Decoder::new();
+ loop_decoder(demuxer, decoder);
+}
+
+unsafe fn loop_decoder(mut demuxer: Demuxer, mut decoder: Decoder) {
+ loop {
+ let (mut pkt, stream) = demuxer.get_packet().expect("demuxer failed");
+ if pkt.is_null() {
+ break; // EOF
+ }
+ if let Ok(frames) = decoder.decode_pkt(pkt, stream) {
+ for (mut frame, _stream) in frames {
+ // do nothing but decode entire stream
+ av_frame_free(&mut frame);
+ }
+ }
+ av_packet_free(&mut pkt);
}
}
diff --git a/src/decode.rs b/src/decode.rs
index 3cc22ce..2035265 100644
--- a/src/decode.rs
+++ b/src/decode.rs
@@ -1,3 +1,4 @@
+use crate::{options_to_dict, StreamInfoChannel};
use std::collections::HashMap;
use std::ffi::CStr;
use std::ptr;
@@ -12,12 +13,23 @@ use ffmpeg_sys_the_third::{
};
use libc::memcpy;
-struct CodecContext {
+pub struct DecoderCodecContext {
pub context: *mut AVCodecContext,
pub codec: *const AVCodec,
}
-impl Drop for CodecContext {
+impl DecoderCodecContext {
+ /// Set [AVCodecContext] options
+ pub fn set_opt(&mut self, options: HashMap) -> Result<(), Error> {
+ crate::set_opts(self.context as *mut libc::c_void, options)
+ }
+
+ pub fn list_opts(&self) -> Result, Error> {
+ crate::list_opts(self.context as *mut libc::c_void)
+ }
+}
+
+impl Drop for DecoderCodecContext {
fn drop(&mut self) {
unsafe {
avcodec_free_context(&mut self.context);
@@ -27,14 +39,13 @@ impl Drop for CodecContext {
}
}
+unsafe impl Send for DecoderCodecContext {}
+unsafe impl Sync for DecoderCodecContext {}
+
pub struct Decoder {
- codecs: HashMap,
+ codecs: HashMap,
}
-unsafe impl Send for Decoder {}
-
-unsafe impl Sync for Decoder {}
-
impl Decoder {
pub fn new() -> Self {
Self {
@@ -42,6 +53,62 @@ impl Decoder {
}
}
+ /// Set up a decoder for a given channel
+ pub fn setup_decoder(
+ &mut self,
+ channel: &StreamInfoChannel,
+ options: Option>,
+ ) -> Result<&mut DecoderCodecContext, Error> {
+ unsafe { self.setup_decoder_for_stream(channel.stream, options) }
+ }
+
+ /// Set up a decoder from an [AVStream]
+ pub unsafe fn setup_decoder_for_stream(
+ &mut self,
+ stream: *mut AVStream,
+ options: Option>,
+ ) -> Result<&mut DecoderCodecContext, Error> {
+ if stream.is_null() {
+ anyhow::bail!("stream is null");
+ }
+
+ let codec_par = (*stream).codecpar;
+ assert_ne!(
+ codec_par,
+ ptr::null_mut(),
+ "Codec parameters are missing from stream"
+ );
+
+ if let std::collections::hash_map::Entry::Vacant(e) = self.codecs.entry((*stream).index) {
+ let codec = avcodec_find_decoder((*codec_par).codec_id);
+ if codec.is_null() {
+ anyhow::bail!(
+ "Failed to find codec: {}",
+ CStr::from_ptr(avcodec_get_name((*codec_par).codec_id)).to_str()?
+ )
+ }
+ let context = avcodec_alloc_context3(codec);
+ if context.is_null() {
+ anyhow::bail!("Failed to alloc context")
+ }
+ if avcodec_parameters_to_context(context, (*stream).codecpar) != 0 {
+ anyhow::bail!("Failed to copy codec parameters to context")
+ }
+ let mut dict = if let Some(options) = options {
+ options_to_dict(options)?
+ } else {
+ ptr::null_mut()
+ };
+
+ if avcodec_open2(context, codec, &mut dict) < 0 {
+ anyhow::bail!("Failed to open codec")
+ }
+ Ok(e.insert(DecoderCodecContext { context, codec }))
+ } else {
+ anyhow::bail!("Decoder already setup");
+ }
+ }
+
pub unsafe fn decode_pkt(
&mut self,
pkt: *mut AVPacket,
@@ -54,34 +121,6 @@ impl Decoder {
"Passed stream reference does not match stream_index of packet"
);
- let codec_par = (*stream).codecpar;
- assert_ne!(
- codec_par,
- ptr::null_mut(),
- "Codec parameters are missing from stream"
- );
-
- if let std::collections::hash_map::Entry::Vacant(e) = self.codecs.entry(stream_index) {
- let codec = avcodec_find_decoder((*codec_par).codec_id);
- if codec.is_null() {
- return Err(Error::msg(format!(
- "Failed to find codec: {}",
- CStr::from_ptr(avcodec_get_name((*codec_par).codec_id)).to_str()?
- )));
- }
- let context = avcodec_alloc_context3(ptr::null());
- if context.is_null() {
- return Err(Error::msg("Failed to alloc context"));
- }
- if avcodec_parameters_to_context(context, (*stream).codecpar) != 0 {
- return Err(Error::msg("Failed to copy codec parameters to context"));
- }
- if avcodec_open2(context, codec, ptr::null_mut()) < 0 {
- return Err(Error::msg("Failed to open codec"));
- }
- e.insert(CodecContext { context, codec });
- }
-
if let Some(ctx) = self.codecs.get_mut(&stream_index) {
// subtitles don't need decoding, create a frame from the pkt data
if (*ctx.codec).type_ == AVMediaType::AVMEDIA_TYPE_SUBTITLE {
diff --git a/src/demux.rs b/src/demux.rs
index c2731c8..4a1c6fb 100644
--- a/src/demux.rs
+++ b/src/demux.rs
@@ -1,14 +1,11 @@
+use crate::return_ffmpeg_error;
+use crate::{get_ffmpeg_error_msg, DemuxerInfo, StreamChannelType, StreamInfoChannel};
use anyhow::Error;
use ffmpeg_sys_the_third::*;
-use std::ffi::CStr;
-use std::{ptr, slice};
-
-use crate::get_ffmpeg_error_msg;
-use crate::return_ffmpeg_error;
use slimbox::{slimbox_unsize, SlimBox, SlimMut};
-use std::fmt::{Display, Formatter};
+use std::collections::HashMap;
use std::io::Read;
-use std::mem::transmute;
+use std::{ptr, slice};
#[no_mangle]
unsafe extern "C" fn read_data(
@@ -27,131 +24,9 @@ unsafe extern "C" fn read_data(
}
}
-#[derive(Clone, Debug, PartialEq)]
-pub struct DemuxerInfo {
- pub bitrate: usize,
- pub duration: f32,
- pub channels: Vec,
-}
-
-unsafe impl Send for DemuxerInfo {}
-unsafe impl Sync for DemuxerInfo {}
-
-impl DemuxerInfo {
- pub fn best_stream(&self, t: StreamChannelType) -> Option<&StreamInfoChannel> {
- self.channels
- .iter()
- .filter(|a| a.channel_type == t)
- .reduce(|acc, channel| {
- if channel.best_metric() > acc.best_metric() {
- channel
- } else {
- acc
- }
- })
- }
-
- pub fn best_video(&self) -> Option<&StreamInfoChannel> {
- self.best_stream(StreamChannelType::Video)
- }
-
- pub fn best_audio(&self) -> Option<&StreamInfoChannel> {
- self.best_stream(StreamChannelType::Audio)
- }
-
- pub fn best_subtitle(&self) -> Option<&StreamInfoChannel> {
- self.best_stream(StreamChannelType::Subtitle)
- }
-
- pub unsafe fn is_best_stream(&self, stream: *mut AVStream) -> bool {
- match (*(*stream).codecpar).codec_type {
- AVMediaType::AVMEDIA_TYPE_VIDEO => {
- (*stream).index == self.best_video().map_or(usize::MAX, |r| r.index) as libc::c_int
- }
- AVMediaType::AVMEDIA_TYPE_AUDIO => {
- (*stream).index == self.best_audio().map_or(usize::MAX, |r| r.index) as libc::c_int
- }
- AVMediaType::AVMEDIA_TYPE_SUBTITLE => {
- (*stream).index
- == self.best_subtitle().map_or(usize::MAX, |r| r.index) as libc::c_int
- }
- _ => false,
- }
- }
-}
-
-impl Display for DemuxerInfo {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- write!(f, "Demuxer Info:")?;
- for c in &self.channels {
- write!(f, "\n{}", c)?;
- }
- Ok(())
- }
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub enum StreamChannelType {
- Video,
- Audio,
- Subtitle,
-}
-
-impl Display for StreamChannelType {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- write!(
- f,
- "{}",
- match self {
- StreamChannelType::Video => "video",
- StreamChannelType::Audio => "audio",
- StreamChannelType::Subtitle => "subtitle",
- }
- )
- }
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub struct StreamInfoChannel {
- pub index: usize,
- pub channel_type: StreamChannelType,
- pub codec: usize,
- pub width: usize,
- pub height: usize,
- pub fps: f32,
- pub sample_rate: usize,
- pub format: usize,
-}
-
-impl StreamInfoChannel {
- pub fn best_metric(&self) -> f32 {
- match self.channel_type {
- StreamChannelType::Video => self.width as f32 * self.height as f32 * self.fps,
- StreamChannelType::Audio => self.sample_rate as f32,
- StreamChannelType::Subtitle => 999. - self.index as f32,
- }
- }
-}
-
-impl Display for StreamInfoChannel {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- let codec_name = unsafe { CStr::from_ptr(avcodec_get_name(transmute(self.codec as i32))) };
- write!(
- f,
- "{} #{}: codec={},size={}x{},fps={}",
- self.channel_type,
- self.index,
- codec_name.to_str().unwrap(),
- self.width,
- self.height,
- self.fps
- )
- }
-}
-
pub enum DemuxerInput {
Url(String),
- Reader(Option>),
+ Reader(Option>, Option),
}
pub struct Demuxer {
@@ -160,28 +35,35 @@ pub struct Demuxer {
}
impl Demuxer {
+ /// Create a new [Demuxer] from a file path or url
pub fn new(input: &str) -> Self {
unsafe {
- let ps = avformat_alloc_context();
+ let ctx = avformat_alloc_context();
Self {
- ctx: ps,
+ ctx,
input: DemuxerInput::Url(input.to_string()),
}
}
}
- pub fn new_custom_io(reader: R) -> Self {
+ /// Create a new [Demuxer] from an object that implements [Read]
+ pub fn new_custom_io(reader: R, url: Option) -> Self {
unsafe {
- let ps = avformat_alloc_context();
- (*ps).flags |= AVFMT_FLAG_CUSTOM_IO;
+ let ctx = avformat_alloc_context();
+ (*ctx).flags |= AVFMT_FLAG_CUSTOM_IO;
Self {
- ctx: ps,
- input: DemuxerInput::Reader(Some(slimbox_unsize!(reader))),
+ ctx,
+ input: DemuxerInput::Reader(Some(slimbox_unsize!(reader)), url),
}
}
}
+ /// Set [AVFormatContext] options
+ pub fn set_opt(&mut self, options: HashMap) -> Result<(), Error> {
+ crate::set_opts(self.ctx as *mut libc::c_void, options)
+ }
+
unsafe fn open_input(&mut self) -> libc::c_int {
match &mut self.input {
DemuxerInput::Url(input) => avformat_open_input(
@@ -190,7 +72,7 @@ impl Demuxer {
ptr::null_mut(),
ptr::null_mut(),
),
- DemuxerInput::Reader(input) => {
+ DemuxerInput::Reader(input, url) => {
let input = input.take().expect("input stream already taken");
const BUFFER_SIZE: usize = 4096;
let pb = avio_alloc_context(
@@ -206,7 +88,11 @@ impl Demuxer {
(*self.ctx).pb = pb;
avformat_open_input(
&mut self.ctx,
- ptr::null_mut(),
+ if let Some(url) = url {
+ format!("{}\0", url).as_ptr() as *const libc::c_char
+ } else {
+ ptr::null_mut()
+ },
ptr::null_mut(),
ptr::null_mut(),
)
@@ -221,7 +107,6 @@ impl Demuxer {
if avformat_find_stream_info(self.ctx, ptr::null_mut()) < 0 {
return Err(Error::msg("Could not find stream info"));
}
- //av_dump_format(self.ctx, 0, ptr::null_mut(), 0);
let mut channel_infos = vec![];
@@ -230,6 +115,7 @@ impl Demuxer {
match (*(*stream).codecpar).codec_type {
AVMediaType::AVMEDIA_TYPE_VIDEO => {
channel_infos.push(StreamInfoChannel {
+ stream,
index: (*stream).index as usize,
codec: (*(*stream).codecpar).codec_id as usize,
channel_type: StreamChannelType::Video,
@@ -242,6 +128,7 @@ impl Demuxer {
}
AVMediaType::AVMEDIA_TYPE_AUDIO => {
channel_infos.push(StreamInfoChannel {
+ stream,
index: (*stream).index as usize,
codec: (*(*stream).codecpar).codec_id as usize,
channel_type: StreamChannelType::Audio,
@@ -254,6 +141,7 @@ impl Demuxer {
}
AVMediaType::AVMEDIA_TYPE_SUBTITLE => {
channel_infos.push(StreamInfoChannel {
+ stream,
index: (*stream).index as usize,
codec: (*(*stream).codecpar).codec_id as usize,
channel_type: StreamChannelType::Subtitle,
@@ -296,12 +184,14 @@ impl Demuxer {
impl Drop for Demuxer {
fn drop(&mut self) {
- unsafe {
- if let DemuxerInput::Reader(_) = self.input {
- drop(SlimBox::::from_raw((*(*self.ctx).pb).opaque));
+ if !self.ctx.is_null() {
+ unsafe {
+ if let DemuxerInput::Reader(_, _) = self.input {
+ drop(SlimBox::::from_raw((*(*self.ctx).pb).opaque));
+ }
+ avformat_free_context(self.ctx);
+ self.ctx = ptr::null_mut();
}
- avformat_free_context(self.ctx);
- self.ctx = ptr::null_mut();
}
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 41cb5a2..755a798 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,10 +1,17 @@
-use ffmpeg_sys_the_third::av_make_error_string;
+use anyhow::Error;
+use ffmpeg_sys_the_third::{
+ av_dict_set, av_make_error_string, av_opt_next, av_opt_set, AVDictionary, AVOption,
+ AV_OPT_SEARCH_CHILDREN,
+};
+use std::collections::HashMap;
use std::ffi::CStr;
+use std::ptr;
mod decode;
mod demux;
mod resample;
mod scale;
+mod stream_info;
#[macro_export]
macro_rules! return_ffmpeg_error {
@@ -24,8 +31,59 @@ fn get_ffmpeg_error_msg(ret: libc::c_int) -> String {
}
}
+unsafe fn options_to_dict(options: HashMap) -> Result<*mut AVDictionary, Error> {
+ let mut dict = ptr::null_mut();
+ for (key, value) in options {
+ let ret = av_dict_set(
+ &mut dict,
+ format!("{}\0", key).as_ptr() as *const libc::c_char,
+ format!("{}\0", value).as_ptr() as *const libc::c_char,
+ 0,
+ );
+ return_ffmpeg_error!(ret);
+ }
+ Ok(dict)
+}
+
+fn list_opts(ctx: *mut libc::c_void) -> Result, Error> {
+ let mut opt_ptr: *const AVOption = ptr::null_mut();
+
+ let mut ret = vec![];
+ unsafe {
+ loop {
+ opt_ptr = av_opt_next(ctx, opt_ptr);
+ if opt_ptr.is_null() {
+ break;
+ }
+
+ ret.push(
+ CStr::from_ptr((*opt_ptr).name)
+ .to_string_lossy()
+ .to_string(),
+ );
+ }
+ }
+ Ok(ret)
+}
+
+fn set_opts(ctx: *mut libc::c_void, options: HashMap) -> Result<(), Error> {
+ unsafe {
+ for (key, value) in options {
+ let ret = av_opt_set(
+ ctx,
+ format!("{}\0", key).as_ptr() as *const libc::c_char,
+ format!("{}\0", value).as_ptr() as *const libc::c_char,
+ AV_OPT_SEARCH_CHILDREN,
+ );
+ return_ffmpeg_error!(ret);
+ }
+ }
+ Ok(())
+}
+
pub use decode::*;
pub use demux::*;
pub use ffmpeg_sys_the_third;
pub use resample::*;
pub use scale::*;
+pub use stream_info::*;
diff --git a/src/stream_info.rs b/src/stream_info.rs
new file mode 100644
index 0000000..478ec7c
--- /dev/null
+++ b/src/stream_info.rs
@@ -0,0 +1,129 @@
+use ffmpeg_sys_the_third::{avcodec_get_name, AVMediaType, AVStream};
+use std::ffi::CStr;
+use std::fmt::{Display, Formatter};
+use std::intrinsics::transmute;
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct DemuxerInfo {
+ pub bitrate: usize,
+ pub duration: f32,
+ pub channels: Vec,
+}
+
+impl DemuxerInfo {
+ pub fn best_stream(&self, t: StreamChannelType) -> Option<&StreamInfoChannel> {
+ self.channels
+ .iter()
+ .filter(|a| a.channel_type == t)
+ .reduce(|acc, channel| {
+ if channel.best_metric() > acc.best_metric() {
+ channel
+ } else {
+ acc
+ }
+ })
+ }
+
+ pub fn best_video(&self) -> Option<&StreamInfoChannel> {
+ self.best_stream(StreamChannelType::Video)
+ }
+
+ pub fn best_audio(&self) -> Option<&StreamInfoChannel> {
+ self.best_stream(StreamChannelType::Audio)
+ }
+
+ pub fn best_subtitle(&self) -> Option<&StreamInfoChannel> {
+ self.best_stream(StreamChannelType::Subtitle)
+ }
+
+ pub unsafe fn is_best_stream(&self, stream: *mut AVStream) -> bool {
+ match (*(*stream).codecpar).codec_type {
+ AVMediaType::AVMEDIA_TYPE_VIDEO => {
+ (*stream).index == self.best_video().map_or(usize::MAX, |r| r.index) as libc::c_int
+ }
+ AVMediaType::AVMEDIA_TYPE_AUDIO => {
+ (*stream).index == self.best_audio().map_or(usize::MAX, |r| r.index) as libc::c_int
+ }
+ AVMediaType::AVMEDIA_TYPE_SUBTITLE => {
+ (*stream).index
+ == self.best_subtitle().map_or(usize::MAX, |r| r.index) as libc::c_int
+ }
+ _ => false,
+ }
+ }
+}
+
+impl Display for DemuxerInfo {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(f, "Demuxer Info:")?;
+ for c in &self.channels {
+ write!(f, "\n{}", c)?;
+ }
+ Ok(())
+ }
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum StreamChannelType {
+ Video,
+ Audio,
+ Subtitle,
+}
+
+impl Display for StreamChannelType {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ StreamChannelType::Video => "video",
+ StreamChannelType::Audio => "audio",
+ StreamChannelType::Subtitle => "subtitle",
+ }
+ )
+ }
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct StreamInfoChannel {
+ pub index: usize,
+ pub channel_type: StreamChannelType,
+ pub codec: usize,
+ pub width: usize,
+ pub height: usize,
+ pub fps: f32,
+ pub sample_rate: usize,
+ pub format: usize,
+
+ // private stream pointer
+ pub(crate) stream: *mut AVStream,
+}
+
+unsafe impl Send for StreamInfoChannel {}
+unsafe impl Sync for StreamInfoChannel {}
+
+impl StreamInfoChannel {
+ pub fn best_metric(&self) -> f32 {
+ match self.channel_type {
+ StreamChannelType::Video => self.width as f32 * self.height as f32 * self.fps,
+ StreamChannelType::Audio => self.sample_rate as f32,
+ StreamChannelType::Subtitle => 999. - self.index as f32,
+ }
+ }
+}
+
+impl Display for StreamInfoChannel {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ let codec_name = unsafe { CStr::from_ptr(avcodec_get_name(transmute(self.codec as i32))) };
+ write!(
+ f,
+ "{} #{}: codec={},size={}x{},fps={}",
+ self.channel_type,
+ self.index,
+ codec_name.to_str().unwrap(),
+ self.width,
+ self.height,
+ self.fps
+ )
+ }
+}