diff --git a/examples/ffmpeg_recorder.rs b/examples/ffmpeg_recorder.rs index 8590ce1..600800e 100644 --- a/examples/ffmpeg_recorder.rs +++ b/examples/ffmpeg_recorder.rs @@ -7,12 +7,13 @@ use std::path::{Path, PathBuf}; use std::pin::Pin; use structopt::StructOpt; +use failure::Fallible; use ferretro::retro; use ferretro::retro::ffi::{PixelFormat, GameGeometry, SystemAvInfo, SystemInfo}; use ferretro::retro::wrapper::{LibretroWrapper, Handler}; -use ffmpeg::{format, frame, media, ChannelLayout}; +use ffmpeg::{format, filter, frame, media, ChannelLayout}; use ffmpeg::util::rational::Rational; struct MyEmulator { @@ -23,9 +24,71 @@ struct MyEmulator { video_frames: VecDeque, video_encoder: ffmpeg::encoder::Video, audio_encoder: ffmpeg::encoder::Audio, + video_filter: filter::Graph, + audio_filter: filter::Graph, sys_path: Option, } +fn video_filter( + video_encoder: &ffmpeg::encoder::video::Video, + av_info: &SystemAvInfo, + pix_fmt: PixelFormat, +) -> Result { + let mut vfilter = filter::Graph::new(); + let pix_fmt = match pix_fmt { + PixelFormat::ARGB1555 => if cfg!(target_endian = "big") { "rgb555be" } else { "rgb555le" }, + PixelFormat::ARGB8888 => "argb", + PixelFormat::RGB565 => if cfg!(target_endian = "big") { "rgb565be" } else { "rgb565le" }, + }; + let pixel_aspect = av_info.geometry.aspect_ratio / (av_info.geometry.base_width as f32 / av_info.geometry.base_height as f32); + let args = format!( + "buffer=width={}:height={}:pix_fmt={}:frame_rate={}:pixel_aspect={}", + av_info.geometry.base_width, + av_info.geometry.base_height, + pix_fmt, + av_info.timing.fps, + pixel_aspect, + ); + vfilter.add(&filter::find("buffer").unwrap(), "in", &args); + //scale? + vfilter.add(&filter::find("buffersink").unwrap(), "out", ""); + + { + let mut out = vfilter.get("out").unwrap(); + out.set_pixel_format(video_encoder.format()); + } + + vfilter.output("in", 0)? + .input("out", 0)?; + vfilter.validate()?; + + Ok(vfilter) +} + +fn audio_filter( + audio_encoder: &ffmpeg::encoder::audio::Audio, + sample_rate: f64, +) -> Result { + let mut afilter = filter::Graph::new(); + let args = format!("sample_rate={}:sample_fmt=s16:channel_layout=stereo", sample_rate); + afilter.add(&filter::find("abuffer").unwrap(), "in", &args); + //aresample? + afilter.add(&filter::find("abuffersink").unwrap(), "out", ""); + + { + let mut out = afilter.get("out").unwrap(); + out.set_sample_format(audio_encoder.format()); + out.set_channel_layout(audio_encoder.channel_layout()); + out.set_sample_rate(audio_encoder.rate()); + } + + afilter.output("in", 0)? + .input("out", 0)?; + afilter.validate()?; + + Ok(afilter) +} + impl MyEmulator { pub fn new( core_path: impl AsRef, @@ -44,16 +107,15 @@ impl MyEmulator { video_encoder.set_frame_rate(av_info.timing.fps.into()); video_encoder.set_width(av_info.geometry.base_width); video_encoder.set_height(av_info.geometry.base_height); - video_encoder.set_format(format::Pixel::YUV420P); video_encoder.set_aspect_ratio(av_info.geometry.aspect_ratio as f64); - audio_encoder.set_rate(44100); - audio_encoder.set_format(audio_encoder.codec().unwrap().audio().unwrap().formats().unwrap().nth(0).unwrap()); - audio_encoder.set_channels(2); - audio_encoder.set_channel_layout(ChannelLayout::STEREO); + audio_encoder.set_rate(system_av_info.timing.sample_rate.round() as i32); - let video_encoder_2 = video_encoder.open().unwrap(); - let audio_encoder_2 = audio_encoder.open().unwrap(); + let video_filter = video_filter(&video_encoder, &av_info).unwrap(); + let audio_filter = audio_filter(&audio_encoder, av_info.timing.sample_rate).unwrap(); + + let video_encoder = video_encoder.open().unwrap(); + let audio_encoder = audio_encoder.open().unwrap(); let emu = MyEmulator { retro, @@ -61,8 +123,10 @@ impl MyEmulator { audio_buf: Default::default(), video_pixel_format: format::Pixel::RGB555, video_frames: Default::default(), - video_encoder: video_encoder_2, - audio_encoder: audio_encoder_2, + video_encoder, + audio_encoder, + video_filter, + audio_filter, sys_path: sys_path.as_ref().map(|x| x.as_ref().to_path_buf()), }; @@ -77,7 +141,6 @@ impl MyEmulator { self.retro.run(); let vframe = self.video_frames.pop_front().unwrap(); - eprintln!("video {}x{}, {} audio samples", vframe.width(), vframe.height(), self.audio_buf.len()); let mut aframe = frame::Audio::new( format::Sample::I16(format::sample::Type::Packed), self.audio_buf.len(), @@ -194,25 +257,29 @@ struct Opt { system: Option, } -fn main() { +fn main() -> Fallible<()> { let opt: Opt = Opt::from_args(); ffmpeg::init().unwrap(); - let mut octx = format::output(&opt.video).unwrap(); - let vcodec = ffmpeg::encoder::find(octx.format().codec(&opt.video, media::Type::Video)) - .unwrap() - .video() - .unwrap(); - let acodec = ffmpeg::encoder::find(octx.format().codec(&opt.video, media::Type::Audio)) - .unwrap() - .audio() - .unwrap(); + let mut octx = format::output(&opt.video)?; - let mut video_encoder = octx.add_stream(vcodec).unwrap().codec().encoder().video().unwrap(); - let mut audio_encoder = octx.add_stream(acodec).unwrap().codec().encoder().audio().unwrap(); + let detected_vcodec = octx.format().codec(&opt.video, media::Type::Video); + let detected_acodec = octx.format().codec(&opt.video, media::Type::Audio); + + let vcodec = ffmpeg::encoder::find(detected_vcodec).unwrap().video()?; + let acodec = ffmpeg::encoder::find(detected_acodec).unwrap().audio()?; + + let mut video_encoder = octx.add_stream(vcodec)?.codec().encoder().video()?; + let mut audio_encoder = octx.add_stream(acodec)?.codec().encoder().audio()?; video_encoder.set_bit_rate(64000); + video_encoder.set_format(video_encoder.codec().unwrap().video()?.formats().unwrap().nth(0).unwrap()); + audio_encoder.set_bit_rate(64000); + audio_encoder.set_rate(44100); + audio_encoder.set_format(audio_encoder.codec().unwrap().audio()?.formats().unwrap().nth(0).unwrap()); + audio_encoder.set_channels(2); + audio_encoder.set_channel_layout(ChannelLayout::STEREO); let mut emu = MyEmulator::new(opt.core, &opt.system, video_encoder, audio_encoder); emu.load_game(opt.rom); @@ -224,4 +291,5 @@ fn main() { } octx.write_trailer().unwrap(); + Ok(()) }