From 9e27636ae0b72c435515db739c42f48c286396c2 Mon Sep 17 00:00:00 2001 From: Zhiming Wang Date: Mon, 15 Mar 2021 09:59:32 +0000 Subject: [PATCH] Examples from next fork --- examples/dump-frames.rs | 65 +++++++++ examples/remux.rs | 57 ++++++++ examples/transcode-audio.rs | 139 ++++++++++--------- examples/transcode-x264.rs | 262 ++++++++++++++++++++++++++++++++++++ 4 files changed, 462 insertions(+), 61 deletions(-) create mode 100644 examples/dump-frames.rs create mode 100644 examples/remux.rs create mode 100644 examples/transcode-x264.rs diff --git a/examples/dump-frames.rs b/examples/dump-frames.rs new file mode 100644 index 0000000..9950401 --- /dev/null +++ b/examples/dump-frames.rs @@ -0,0 +1,65 @@ +use std::{env, fs::File, io::prelude::*}; + +use ffmpeg::{ + format::{input, Pixel}, + media::Type, + software::scaling::{context::Context, flag::Flags}, + util::frame::video::Video, +}; + +fn main() -> Result<(), ffmpeg::Error> { + ffmpeg::init().unwrap(); + + if let Ok(mut ictx) = input(&env::args().nth(1).expect("Cannot open file.")) { + let input = ictx + .streams() + .best(Type::Video) + .ok_or(ffmpeg::Error::StreamNotFound)?; + let video_stream_index = input.index(); + + let mut decoder = input.codec().decoder().video()?; + + let mut scaler = Context::get( + decoder.format(), + decoder.width(), + decoder.height(), + Pixel::RGB24, + decoder.width(), + decoder.height(), + Flags::BILINEAR, + )?; + + let mut frame_index = 0; + + let mut receive_and_process_decoded_frames = + |decoder: &mut ffmpeg::decoder::Video| -> Result<(), ffmpeg::Error> { + let mut decoded = Video::empty(); + while decoder.receive_frame(&mut decoded).is_ok() { + let mut rgb_frame = Video::empty(); + scaler.run(&decoded, &mut rgb_frame)?; + save_file(&rgb_frame, frame_index).unwrap(); + frame_index += 1; + } + Ok(()) + }; + + for res in ictx.packets() { + let (stream, packet) = res?; + if stream.index() == video_stream_index { + decoder.send_packet(&packet)?; + receive_and_process_decoded_frames(&mut decoder)?; + } + } + decoder.send_eof()?; + receive_and_process_decoded_frames(&mut decoder)?; + } + + Ok(()) +} + +fn save_file(frame: &Video, index: usize) -> std::result::Result<(), std::io::Error> { + let mut file = File::create(format!("frame{}.ppm", index))?; + file.write_all(format!("P6\n{} {}\n255\n", frame.width(), frame.height()).as_bytes())?; + file.write_all(frame.data(0))?; + Ok(()) +} diff --git a/examples/remux.rs b/examples/remux.rs new file mode 100644 index 0000000..6472dab --- /dev/null +++ b/examples/remux.rs @@ -0,0 +1,57 @@ +use std::env; + +use ffmpeg::{codec, encoder, format, media, Rational}; + +fn main() { + let input_file = env::args().nth(1).expect("missing input file"); + let output_file = env::args().nth(2).expect("missing output file"); + + ffmpeg::init().unwrap(); + + let mut ictx = format::input(&input_file).unwrap(); + let mut octx = format::output(&output_file).unwrap(); + + let mut stream_mapping = vec![0; ictx.nb_streams() as _]; + let mut ist_time_bases = vec![Rational(0, 1); ictx.nb_streams() as _]; + let mut ost_index = 0; + for (ist_index, ist) in ictx.streams().enumerate() { + let ist_medium = ist.codec().medium(); + if ist_medium != media::Type::Audio + && ist_medium != media::Type::Video + && ist_medium != media::Type::Subtitle + { + stream_mapping[ist_index] = -1; + continue; + } + stream_mapping[ist_index] = ost_index; + ist_time_bases[ist_index] = ist.time_base(); + ost_index += 1; + let mut ost = octx.add_stream(encoder::find(codec::Id::None)).unwrap(); + ost.set_parameters(ist.parameters()); + // We need to set codec_tag to 0 lest we run into incompatible codec tag + // issues when muxing into a different container format. Unfortunately + // there's no high level API to do this (yet). + unsafe { + (*ost.parameters().as_mut_ptr()).codec_tag = 0; + } + } + + octx.set_metadata(ictx.metadata().to_owned()); + octx.write_header().unwrap(); + + for res in ictx.packets() { + let (stream, mut packet) = res.unwrap(); + let ist_index = stream.index(); + let ost_index = stream_mapping[ist_index]; + if ost_index < 0 { + continue; + } + let ost = octx.stream(ost_index as _).unwrap(); + packet.rescale_ts(ist_time_bases[ist_index], ost.time_base()); + packet.set_position(-1); + packet.set_stream(ost_index as _); + packet.write_interleaved(&mut octx).unwrap(); + } + + octx.write_trailer().unwrap(); +} diff --git a/examples/transcode-audio.rs b/examples/transcode-audio.rs index 4b9cbfd..e3c97db 100644 --- a/examples/transcode-audio.rs +++ b/examples/transcode-audio.rs @@ -54,6 +54,8 @@ struct Transcoder { filter: filter::Graph, decoder: codec::decoder::Audio, encoder: codec::encoder::Audio, + in_time_base: ffmpeg::Rational, + out_time_base: ffmpeg::Rational, } fn transcoder>( @@ -110,14 +112,79 @@ fn transcoder>( let filter = filter(filter_spec, &decoder, &encoder)?; + let in_time_base = decoder.time_base(); + let out_time_base = output.time_base(); + Ok(Transcoder { stream: input.index(), filter, decoder, encoder, + in_time_base, + out_time_base, }) } +impl Transcoder { + fn send_frame_to_encoder(&mut self, frame: &ffmpeg::Frame) { + self.encoder.send_frame(frame).unwrap(); + } + + fn send_eof_to_encoder(&mut self) { + self.encoder.send_eof().unwrap(); + } + + fn receive_and_process_encoded_packets(&mut self, octx: &mut format::context::Output) { + let mut encoded = ffmpeg::Packet::empty(); + while self.encoder.receive_packet(&mut encoded).is_ok() { + encoded.set_stream(0); + encoded.rescale_ts(self.in_time_base, self.out_time_base); + encoded.write_interleaved(octx).unwrap(); + } + } + + fn add_frame_to_filter(&mut self, frame: &ffmpeg::Frame) { + self.filter.get("in").unwrap().source().add(frame).unwrap(); + } + + fn flush_filter(&mut self) { + self.filter.get("in").unwrap().source().flush().unwrap(); + } + + fn get_and_process_filtered_frames(&mut self, octx: &mut format::context::Output) { + let mut filtered = frame::Audio::empty(); + while self + .filter + .get("out") + .unwrap() + .sink() + .frame(&mut filtered) + .is_ok() + { + self.send_frame_to_encoder(&filtered); + self.receive_and_process_encoded_packets(octx); + } + } + + fn send_packet_to_decoder(&mut self, packet: &ffmpeg::Packet) { + self.decoder.send_packet(packet).unwrap(); + } + + fn send_eof_to_decoder(&mut self) { + self.decoder.send_eof().unwrap(); + } + + fn receive_and_process_decoded_frames(&mut self, octx: &mut format::context::Output) { + let mut decoded = frame::Audio::empty(); + while self.decoder.receive_frame(&mut decoded).is_ok() { + let timestamp = decoded.timestamp(); + decoded.set_pts(timestamp); + self.add_frame_to_filter(&decoded); + self.get_and_process_filtered_frames(octx); + } + } +} + // Transcode the `best` audio stream of the input file into a the output file // while applying a given filter. If no filter was specified the stream gets // copied (`anull` filter). @@ -154,73 +221,23 @@ fn main() { octx.set_metadata(ictx.metadata().to_owned()); octx.write_header().unwrap(); - let in_time_base = transcoder.decoder.time_base(); - let out_time_base = octx.stream(0).unwrap().time_base(); - - let mut decoded = frame::Audio::empty(); - let mut encoded = ffmpeg::Packet::empty(); - let mut packets = ictx.packets(); - - while let Some(Ok((stream, mut packet))) = packets.next() { + for res in ictx.packets() { + let (stream, mut packet) = res.unwrap(); if stream.index() == transcoder.stream { - packet.rescale_ts(stream.time_base(), in_time_base); - - if let Ok(true) = transcoder.decoder.decode(&packet, &mut decoded) { - let timestamp = decoded.timestamp(); - decoded.set_pts(timestamp); - - transcoder - .filter - .get("in") - .unwrap() - .source() - .add(&decoded) - .unwrap(); - - while let Ok(..) = transcoder - .filter - .get("out") - .unwrap() - .sink() - .frame(&mut decoded) - { - if let Ok(true) = transcoder.encoder.encode(&decoded, &mut encoded) { - encoded.set_stream(0); - encoded.rescale_ts(in_time_base, out_time_base); - encoded.write_interleaved(&mut octx).unwrap(); - } - } - } + packet.rescale_ts(stream.time_base(), transcoder.in_time_base); + transcoder.send_packet_to_decoder(&packet); + transcoder.receive_and_process_decoded_frames(&mut octx); } } - transcoder - .filter - .get("in") - .unwrap() - .source() - .flush() - .unwrap(); + transcoder.send_eof_to_decoder(); + transcoder.receive_and_process_decoded_frames(&mut octx); - while let Ok(..) = transcoder - .filter - .get("out") - .unwrap() - .sink() - .frame(&mut decoded) - { - if let Ok(true) = transcoder.encoder.encode(&decoded, &mut encoded) { - encoded.set_stream(0); - encoded.rescale_ts(in_time_base, out_time_base); - encoded.write_interleaved(&mut octx).unwrap(); - } - } + transcoder.flush_filter(); + transcoder.get_and_process_filtered_frames(&mut octx); - if let Ok(true) = transcoder.encoder.flush(&mut encoded) { - encoded.set_stream(0); - encoded.rescale_ts(in_time_base, out_time_base); - encoded.write_interleaved(&mut octx).unwrap(); - } + transcoder.send_eof_to_encoder(); + transcoder.receive_and_process_encoded_packets(&mut octx); octx.write_trailer().unwrap(); } diff --git a/examples/transcode-x264.rs b/examples/transcode-x264.rs new file mode 100644 index 0000000..f6f3d0d --- /dev/null +++ b/examples/transcode-x264.rs @@ -0,0 +1,262 @@ +// Given an input file, transcode all video streams into H.264 (using libx264) +// while copying audio and subtitle streams. +// +// Invocation: +// +// transcode-x264 [] +// +// is a comma-delimited list of key=val. default is "preset=medium". +// See https://ffmpeg.org/ffmpeg-codecs.html#libx264_002c-libx264rgb and +// https://trac.ffmpeg.org/wiki/Encode/H.264 for available and commonly used +// options. +// +// Examples: +// +// transcode-x264 input.flv output.mp4 +// transcode-x264 input.mkv output.mkv 'preset=veryslow,crf=18' + +use std::{collections::HashMap, env, time::Instant}; + +use ffmpeg::{ + codec, decoder, encoder, format, frame, media, picture, Dictionary, Packet, Rational, +}; + +const DEFAULT_X264_OPTS: &str = "preset=medium"; + +struct Transcoder { + ost_index: usize, + decoder: decoder::Video, + encoder: encoder::video::Video, + logging_enabled: bool, + frame_count: usize, + last_log_frame_count: usize, + starting_time: Instant, + last_log_time: Instant, +} + +impl Transcoder { + fn new( + ist: &format::stream::Stream, + octx: &mut format::context::Output, + ost_index: usize, + x264_opts: Dictionary, + enable_logging: bool, + ) -> Result { + let global_header = octx.format().flags().contains(format::Flags::GLOBAL_HEADER); + let decoder = ist.codec().decoder().video()?; + let mut ost = octx.add_stream(encoder::find(codec::Id::H264))?; + let mut encoder = ost.codec().encoder().video()?; + encoder.set_height(decoder.height()); + encoder.set_width(decoder.width()); + encoder.set_aspect_ratio(decoder.aspect_ratio()); + encoder.set_format(decoder.format()); + encoder.set_frame_rate(decoder.frame_rate()); + encoder.set_time_base(decoder.frame_rate().unwrap().invert()); + if global_header { + encoder.set_flags(codec::Flags::GLOBAL_HEADER); + } + encoder + .open_with(x264_opts) + .expect("error opening libx264 encoder with supplied settings"); + encoder = ost.codec().encoder().video()?; + ost.set_parameters(encoder); + Ok(Self { + ost_index, + decoder, + encoder: ost.codec().encoder().video()?, + logging_enabled: enable_logging, + frame_count: 0, + last_log_frame_count: 0, + starting_time: Instant::now(), + last_log_time: Instant::now(), + }) + } + + fn send_packet_to_decoder(&mut self, packet: &Packet) { + self.decoder.send_packet(packet).unwrap(); + } + + fn send_eof_to_decoder(&mut self) { + self.decoder.send_eof().unwrap(); + } + + fn receive_and_process_decoded_frames( + &mut self, + octx: &mut format::context::Output, + ost_time_base: Rational, + ) { + let mut frame = frame::Video::empty(); + while self.decoder.receive_frame(&mut frame).is_ok() { + self.frame_count += 1; + let timestamp = frame.timestamp(); + self.log_progress(f64::from( + Rational(timestamp.unwrap_or(0) as i32, 1) * self.decoder.time_base(), + )); + frame.set_pts(timestamp); + frame.set_kind(picture::Type::None); + self.send_frame_to_encoder(&frame); + self.receive_and_process_encoded_packets(octx, ost_time_base); + } + } + + fn send_frame_to_encoder(&mut self, frame: &frame::Video) { + self.encoder.send_frame(frame).unwrap(); + } + + fn send_eof_to_encoder(&mut self) { + self.encoder.send_eof().unwrap(); + } + + fn receive_and_process_encoded_packets( + &mut self, + octx: &mut format::context::Output, + ost_time_base: Rational, + ) { + let mut encoded = Packet::empty(); + while self.encoder.receive_packet(&mut encoded).is_ok() { + encoded.set_stream(self.ost_index); + encoded.rescale_ts(self.decoder.time_base(), ost_time_base); + encoded.write_interleaved(octx).unwrap(); + } + } + + fn log_progress(&mut self, timestamp: f64) { + if !self.logging_enabled + || (self.frame_count - self.last_log_frame_count < 100 + && self.last_log_time.elapsed().as_secs_f64() < 1.0) + { + return; + } + eprintln!( + "time elpased: \t{:8.2}\tframe count: {:8}\ttimestamp: {:8.2}", + self.starting_time.elapsed().as_secs_f64(), + self.frame_count, + timestamp + ); + self.last_log_frame_count = self.frame_count; + self.last_log_time = Instant::now(); + } +} + +fn parse_opts<'a>(s: String) -> Option> { + let mut dict = Dictionary::new(); + for keyval in s.split_terminator(',') { + let tokens: Vec<&str> = keyval.split('=').collect(); + match tokens[..] { + [key, val] => dict.set(key, val), + _ => return None, + } + } + Some(dict) +} + +fn main() { + let input_file = env::args().nth(1).expect("missing input file"); + let output_file = env::args().nth(2).expect("missing output file"); + let x264_opts = parse_opts( + env::args() + .nth(3) + .unwrap_or_else(|| DEFAULT_X264_OPTS.to_string()), + ) + .expect("invalid x264 options string"); + + eprintln!("x264 options: {:?}", x264_opts); + + ffmpeg::init().unwrap(); + + let mut ictx = format::input(&input_file).unwrap(); + let mut octx = format::output(&output_file).unwrap(); + + format::context::input::dump(&ictx, 0, Some(&input_file)); + + let best_video_stream_index = ictx + .streams() + .best(media::Type::Video) + .map(|stream| stream.index()); + let mut stream_mapping: Vec = vec![0; ictx.nb_streams() as _]; + let mut ist_time_bases = vec![Rational(0, 0); ictx.nb_streams() as _]; + let mut ost_time_bases = vec![Rational(0, 0); ictx.nb_streams() as _]; + let mut transcoders = HashMap::new(); + let mut ost_index = 0; + for (ist_index, ist) in ictx.streams().enumerate() { + let ist_medium = ist.codec().medium(); + if ist_medium != media::Type::Audio + && ist_medium != media::Type::Video + && ist_medium != media::Type::Subtitle + { + stream_mapping[ist_index] = -1; + continue; + } + stream_mapping[ist_index] = ost_index; + ist_time_bases[ist_index] = ist.time_base(); + if ist_medium == media::Type::Video { + // Initialize transcoder for video stream. + transcoders.insert( + ist_index, + Transcoder::new( + &ist, + &mut octx, + ost_index as _, + x264_opts.to_owned(), + Some(ist_index) == best_video_stream_index, + ) + .unwrap(), + ); + } + else { + // Set up for stream copy for non-video stream. + let mut ost = octx.add_stream(encoder::find(codec::Id::None)).unwrap(); + ost.set_parameters(ist.parameters()); + // We need to set codec_tag to 0 lest we run into incompatible codec tag + // issues when muxing into a different container format. Unfortunately + // there's no high level API to do this (yet). + unsafe { + (*ost.parameters().as_mut_ptr()).codec_tag = 0; + } + } + ost_index += 1; + } + + octx.set_metadata(ictx.metadata().to_owned()); + format::context::output::dump(&octx, 0, Some(&output_file)); + octx.write_header().unwrap(); + + for (ost_index, _) in octx.streams().enumerate() { + ost_time_bases[ost_index] = octx.stream(ost_index as _).unwrap().time_base(); + } + + for res in ictx.packets() { + let (stream, mut packet) = res.unwrap(); + let ist_index = stream.index(); + let ost_index = stream_mapping[ist_index]; + if ost_index < 0 { + continue; + } + let ost_time_base = ost_time_bases[ost_index as usize]; + match transcoders.get_mut(&ist_index) { + Some(transcoder) => { + packet.rescale_ts(stream.time_base(), transcoder.decoder.time_base()); + transcoder.send_packet_to_decoder(&packet); + transcoder.receive_and_process_decoded_frames(&mut octx, ost_time_base); + } + None => { + // Do stream copy on non-video streams. + packet.rescale_ts(ist_time_bases[ist_index], ost_time_base); + packet.set_position(-1); + packet.set_stream(ost_index as _); + packet.write_interleaved(&mut octx).unwrap(); + } + } + } + + // Flush encoders and decoders. + for (ost_index, transcoder) in transcoders.iter_mut() { + let ost_time_base = ost_time_bases[*ost_index]; + transcoder.send_eof_to_decoder(); + transcoder.receive_and_process_decoded_frames(&mut octx, ost_time_base); + transcoder.send_eof_to_encoder(); + transcoder.receive_and_process_encoded_packets(&mut octx, ost_time_base); + } + + octx.write_trailer().unwrap(); +}