Skip to content

Commit

Permalink
Add AVInputFomat::find, improveAVFormatContextInput::open (#116)
Browse files Browse the repository at this point in the history
* Add `AVInputFomat::find`

* Add fmt and options parameter for `AVFormatContextInput::open`
  • Loading branch information
ldm0 authored Jul 29, 2023
1 parent 40b0fd8 commit 81fd36c
Show file tree
Hide file tree
Showing 14 changed files with 122 additions and 67 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ use std::error::Error;
use rsmpeg::avformat::AVFormatContextInput;

fn dump_av_info(path: &CStr) -> Result<(), Box<dyn Error>> {
let mut input_format_context = AVFormatContextInput::open(path)?;
let mut input_format_context = AVFormatContextInput::open(path, None, &mut None)?;
input_format_context.dump(0, path)?;
Ok(())
}
Expand Down
4 changes: 2 additions & 2 deletions src/avcodec/bitstream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ mod test {
#[test]
fn test_filter_iterate() {
let mut iter = AVBitStreamFilter::iterate();
while let Some(_) = iter.next() {}
let _iter = iter.next();
for _ in iter.by_ref() {}
assert!(iter.next().is_none());
}
}
67 changes: 62 additions & 5 deletions src/avformat/avformat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,43 @@ wrap! {
impl AVFormatContextInput {
/// Create a [`AVFormatContextInput`] instance of a file, and find info of
/// all streams.
pub fn open(filename: &CStr) -> Result<Self> {
///
/// - `url`: url of the stream to open.
/// - `format`: input format hint. If `format` is some, this parameter forces
/// a specific input format.
/// - `options`: A dictionary filled with AVFormatContext and demuxer-private options.
/// On return this parameter will be destroyed and replaced with a dict containing
/// options that were not found.
pub fn open(
url: &CStr,
fmt: Option<&AVInputFormat>,
options: &mut Option<AVDictionary>,
) -> Result<Self> {
let mut input_format_context = ptr::null_mut();
let fmt = fmt.map(|x| x.as_ptr()).unwrap_or_else(std::ptr::null) as _;
let mut options_ptr = options
.as_mut()
.map(|x| x.as_mut_ptr())
.unwrap_or_else(std::ptr::null_mut);

// GoodToHave: support custom Input format and custom avdictionary
unsafe {
ffi::avformat_open_input(
&mut input_format_context,
filename.as_ptr(),
ptr::null_mut(),
ptr::null_mut(),
url.as_ptr(),
fmt,
&mut options_ptr,
)
}
.upgrade()
.map_err(RsmpegError::OpenInputError)?;

// Forget the old options since it's ownership is transferred.
let mut new_options = options_ptr
.upgrade()
.map(|x| unsafe { AVDictionary::from_raw(x) });
std::mem::swap(options, &mut new_options);
std::mem::forget(new_options);

// Here we can be sure that context is non null, constructing here for
// dropping when `avformat_find_stream_info` fails.
let mut context = unsafe { Self::from_raw(NonNull::new(input_format_context).unwrap()) };
Expand Down Expand Up @@ -377,6 +399,15 @@ impl Drop for AVFormatContextOutput {

wrap_ref!(AVInputFormat: ffi::AVInputFormat);

impl AVInputFormat {
/// Find [`AVInputFormat`] based on the short name of the input format.
pub fn find(short_name: &CStr) -> Option<AVInputFormatRef<'static>> {
unsafe { ffi::av_find_input_format(short_name.as_ptr()) }
.upgrade()
.map(|x| unsafe { AVInputFormatRef::from_raw(x) })
}
}

wrap_ref!(AVOutputFormat: ffi::AVOutputFormat);

impl AVOutputFormat {
Expand Down Expand Up @@ -536,3 +567,29 @@ impl<'stream> AVStreamRefs<'stream> {
self.len as usize
}
}

#[cfg(test)]
mod test {
use super::*;
use cstr::cstr;

#[test]
fn test_find_input_format() {
let name = cstr!("mpeg");
let filter_ref = AVInputFormat::find(name).unwrap();
assert_eq!(
unsafe { CStr::from_ptr(filter_ref.long_name) },
cstr!("MPEG-PS (MPEG-2 Program Stream)")
);

let name = cstr!("asf");
let filter_ref = AVInputFormat::find(name).unwrap();
assert_eq!(
unsafe { CStr::from_ptr(filter_ref.long_name) },
cstr!("ASF (Advanced / Active Streaming Format)")
);

let name = cstr!("__random__");
assert!(AVInputFormat::find(name).is_none());
}
}
2 changes: 1 addition & 1 deletion tests/av_spliter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use std::{
fn av_spliter(file_path: &CStr, out_video: &str, out_audio: &CStr) -> Result<()> {
let mut out_video = File::create(out_video)?;

let mut input_format_context = AVFormatContextInput::open(file_path)?;
let mut input_format_context = AVFormatContextInput::open(file_path, None, &mut None)?;
input_format_context.dump(0, file_path)?;

let video_index = input_format_context
Expand Down
2 changes: 1 addition & 1 deletion tests/avio_writing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use std::{

/// Get `video_stream_index`, `input_format_context`, `decode_context`.
fn open_input_file(filename: &CStr) -> Result<(usize, AVFormatContextInput, AVCodecContext)> {
let mut input_format_context = AVFormatContextInput::open(filename)?;
let mut input_format_context = AVFormatContextInput::open(filename, None, &mut None)?;
input_format_context.dump(0, filename)?;

let (video_index, decoder) = input_format_context
Expand Down
4 changes: 2 additions & 2 deletions tests/decode_audio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ fn decode_audio(audio_path: &str, out_file_path: &str) -> Result<()> {
let (decoder, mut decode_context) = {
// safety, &str ensures no internal null bytes.
let audio_path = CString::new(audio_path).unwrap();
let mut input_format_context =
AVFormatContextInput::open(&audio_path).context("Open audio file failed.")?;
let mut input_format_context = AVFormatContextInput::open(&audio_path, None, &mut None)
.context("Open audio file failed.")?;
let (stream_index, decoder) = input_format_context
.find_best_stream(ffi::AVMediaType_AVMEDIA_TYPE_AUDIO)
.context("Find best stream failed.")?
Expand Down
6 changes: 3 additions & 3 deletions tests/extract_mvs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ fn decode_packet(
*frame_index += 1;

if let Some(raw_motion_vectors) = frame.get_motion_vectors() {
for motion_vector in raw_motion_vectors {
for &motion_vector in raw_motion_vectors {
// framenum,source,blockw,blockh,srcx,srcy,dstx,dsty,flags
motion_vectors.push(MotionVector {
motion_vector,
frame_index: *frame_index,
motion_vector: motion_vector.clone(),
});
}
}
Expand All @@ -48,7 +48,7 @@ fn decode_packet(

/// Extract motion vectors from a video.
fn extract_mvs(video_path: &CStr) -> Result<Vec<MotionVector>> {
let mut input_format_context = AVFormatContextInput::open(video_path)?;
let mut input_format_context = AVFormatContextInput::open(video_path, None, &mut None)?;

let (stream_index, mut decode_context) = {
let (stream_index, decoder) = input_format_context
Expand Down
2 changes: 1 addition & 1 deletion tests/image_dump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::ffi::CStr;

/// Dump video/audio/image info to stdout.
fn image_dump(image_path: &CStr) -> Result<(), Box<dyn std::error::Error>> {
let mut input_format_context = AVFormatContextInput::open(image_path)?;
let mut input_format_context = AVFormatContextInput::open(image_path, None, &mut None)?;
input_format_context.dump(0, image_path)?;
Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion tests/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ fn metadata(file: &str) -> Result<Vec<(String, String)>> {
result.push(("image_path".into(), file.to_string()));

let file = CString::new(file).unwrap();
let input_format_context = AVFormatContextInput::open(&file).unwrap();
let input_format_context = AVFormatContextInput::open(&file, None, &mut None).unwrap();

// Get `duration` and `bit_rate` from `input_format_context`.
result.push(("duration".into(), input_format_context.duration.to_string()));
Expand Down
4 changes: 2 additions & 2 deletions tests/remux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use rsmpeg::avformat::{AVFormatContextInput, AVFormatContextOutput};
use std::ffi::CStr;

fn remux(input_path: &CStr, output_path: &CStr) -> Result<()> {
let mut input_format_context =
AVFormatContextInput::open(input_path).context("Create input format context failed.")?;
let mut input_format_context = AVFormatContextInput::open(input_path, None, &mut None)
.context("Create input format context failed.")?;
input_format_context
.dump(0, input_path)
.context("Dump input format context failed.")?;
Expand Down
2 changes: 1 addition & 1 deletion tests/thumbnail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fn thumbnail(
width: Option<i32>,
height: Option<i32>,
) -> Result<()> {
let mut input_format_context = AVFormatContextInput::open(&input_video_path)?;
let mut input_format_context = AVFormatContextInput::open(input_video_path, None, &mut None)?;

let (video_stream_index, mut decode_context) = {
let (stream_index, decoder) = input_format_context
Expand Down
4 changes: 2 additions & 2 deletions tests/transcode_aac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use std::{ffi::CStr, sync::Mutex};

fn open_input_file(input_file: &CStr) -> Result<(AVFormatContextInput, AVCodecContext, usize)> {
let input_format_context =
AVFormatContextInput::open(input_file).context("Failed to get encoder")?;
AVFormatContextInput::open(input_file, None, &mut None).context("Failed to get encoder")?;
let (audio_index, decoder) = input_format_context
.find_best_stream(ffi::AVMediaType_AVMEDIA_TYPE_AUDIO)?
.context("Failed to find audio stream")?;
Expand Down Expand Up @@ -172,7 +172,7 @@ fn transcode_aac(input_file: &CStr, output_file: &CStr) -> Result<()> {

// Open the output file for writing.
let (mut output_format_context, mut encode_context) =
open_output_file(output_file, &mut decode_context)?;
open_output_file(output_file, &decode_context)?;

// Initialize the resampler to be able to convert audio sample formats.
let resample_context = init_resampler(&mut decode_context, &mut encode_context)?;
Expand Down
86 changes: 42 additions & 44 deletions tests/transcoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ struct TranscodingContext<'graph> {
/// audio, decode context at this index is set to `None`.
fn open_input_file(filename: &CStr) -> Result<(Vec<Option<AVCodecContext>>, AVFormatContextInput)> {
let mut stream_contexts = vec![];
let mut input_format_context = AVFormatContextInput::open(filename)?;
let mut input_format_context = AVFormatContextInput::open(filename, None, &mut None)?;

for input_stream in input_format_context.streams().into_iter() {
let codecpar = input_stream.codecpar();
Expand Down Expand Up @@ -238,10 +238,10 @@ fn init_filter<'graph>(
/// Create transcoding context corresponding to the given `stream_contexts`, the
/// added filter contexts is mutable reference to objects stored in
/// `filter_graphs`.
fn init_filters<'graph>(
filter_graphs: &'graph mut [AVFilterGraph],
fn init_filters(
filter_graphs: &mut [AVFilterGraph],
stream_contexts: Vec<Option<StreamContext>>,
) -> Result<Vec<Option<TranscodingContext<'graph>>>> {
) -> Result<Vec<Option<TranscodingContext>>> {
let mut filter_contexts = vec![];

for (filter_graph, stream_context) in filter_graphs.iter_mut().zip(stream_contexts.into_iter())
Expand Down Expand Up @@ -397,49 +397,47 @@ pub fn transcoding(

let in_stream_index = packet.stream_index as usize;

match transcoding_contexts[in_stream_index].as_mut() {
Some(TranscodingContext {
decode_context,
encode_context,
out_stream_index,
buffer_src_context,
buffer_sink_context,
}) => {
let input_stream = input_format_context.streams().get(in_stream_index).unwrap();
packet.rescale_ts(input_stream.time_base, encode_context.time_base);

decode_context.send_packet(Some(&packet)).unwrap();

loop {
let mut frame = match decode_context.receive_frame() {
Ok(frame) => frame,
Err(RsmpegError::DecoderDrainError)
| Err(RsmpegError::DecoderFlushedError) => break,
Err(e) => bail!(e),
};

let mut best_effort_timestamp = frame.best_effort_timestamp;
if best_effort_timestamp == last_timestamp[in_stream_index] {
best_effort_timestamp += 1;
eprintln!(
"fix timestamp: {} -> {}",
last_timestamp[in_stream_index], best_effort_timestamp
);
if let Some(TranscodingContext {
decode_context,
encode_context,
out_stream_index,
buffer_src_context,
buffer_sink_context,
}) = transcoding_contexts[in_stream_index].as_mut()
{
let input_stream = input_format_context.streams().get(in_stream_index).unwrap();
packet.rescale_ts(input_stream.time_base, encode_context.time_base);

decode_context.send_packet(Some(&packet)).unwrap();

loop {
let mut frame = match decode_context.receive_frame() {
Ok(frame) => frame,
Err(RsmpegError::DecoderDrainError) | Err(RsmpegError::DecoderFlushedError) => {
break
}
last_timestamp[in_stream_index] = best_effort_timestamp;
frame.set_pts(best_effort_timestamp);
filter_encode_write_frame(
Some(frame),
buffer_src_context,
buffer_sink_context,
encode_context,
&mut output_format_context,
*out_stream_index,
)?;
Err(e) => bail!(e),
};

let mut best_effort_timestamp = frame.best_effort_timestamp;
if best_effort_timestamp == last_timestamp[in_stream_index] {
best_effort_timestamp += 1;
eprintln!(
"fix timestamp: {} -> {}",
last_timestamp[in_stream_index], best_effort_timestamp
);
}
last_timestamp[in_stream_index] = best_effort_timestamp;
frame.set_pts(best_effort_timestamp);
filter_encode_write_frame(
Some(frame),
buffer_src_context,
buffer_sink_context,
encode_context,
&mut output_format_context,
*out_stream_index,
)?;
}
// Discard non-av video packets.
None => (),
}
}

Expand Down
2 changes: 1 addition & 1 deletion tests/tutorial01.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ fn pgm_save(frame: &AVFrame, filename: &str) -> Result<()> {

fn _main(file: &CStr, out_dir: &str) -> Result<()> {
fs::create_dir_all(out_dir)?;
let mut input_format_context = AVFormatContextInput::open(file)?;
let mut input_format_context = AVFormatContextInput::open(file, None, &mut None)?;
input_format_context.dump(0, file)?;
let video_stream_index = input_format_context
.streams()
Expand Down

0 comments on commit 81fd36c

Please sign in to comment.