Skip to content
Snippets Groups Projects
Unverified Commit 5cc91d71 authored by Dominic Meiser's avatar Dominic Meiser
Browse files

run all transcoding through the ffmpeg helper

parent 2882fb28
Branches
Tags
No related merge requests found
use super::{cmd, filter::Filter}; use super::{cmd, filter::Filter};
use crate::{ use crate::{
render::filter::channel, render::filter::channel,
time::{format_time, Time} time::{format_time, Time},
Resolution
}; };
use anyhow::bail; use anyhow::bail;
use camino::Utf8PathBuf as PathBuf; use camino::Utf8PathBuf as PathBuf;
...@@ -55,6 +56,7 @@ pub(crate) struct FfmpegOutput { ...@@ -55,6 +56,7 @@ pub(crate) struct FfmpegOutput {
pub(crate) duration: Option<Time>, pub(crate) duration: Option<Time>,
pub(crate) time_base: Option<Rational>, pub(crate) time_base: Option<Rational>,
pub(crate) fps_mode_vfr: bool, pub(crate) fps_mode_vfr: bool,
pub(crate) faststart: bool,
pub(crate) path: PathBuf pub(crate) path: PathBuf
} }
...@@ -65,10 +67,16 @@ impl FfmpegOutput { ...@@ -65,10 +67,16 @@ impl FfmpegOutput {
duration: None, duration: None,
time_base: None, time_base: None,
fps_mode_vfr: false, fps_mode_vfr: false,
faststart: false,
path path
} }
} }
pub(crate) fn enable_faststart(mut self) -> Self {
self.faststart = true;
self
}
fn append_to_cmd(self, cmd: &mut Command) { fn append_to_cmd(self, cmd: &mut Command) {
if let Some(fps) = self.fps { if let Some(fps) = self.fps {
cmd.arg("-r").arg(fps.to_string()); cmd.arg("-r").arg(fps.to_string());
...@@ -82,16 +90,27 @@ impl FfmpegOutput { ...@@ -82,16 +90,27 @@ impl FfmpegOutput {
if self.fps_mode_vfr { if self.fps_mode_vfr {
cmd.arg("-fps_mode").arg("vfr"); cmd.arg("-fps_mode").arg("vfr");
} }
if self.faststart {
cmd.arg("-movflags").arg("+faststart"); cmd.arg("-movflags").arg("+faststart");
}
cmd.arg(self.path); cmd.arg(self.path);
} }
} }
enum FfmpegFilter {
None,
Filters {
filters: Vec<Filter>,
output: Cow<'static, str>
},
Loudnorm,
Rescale(Resolution)
}
pub(crate) struct Ffmpeg { pub(crate) struct Ffmpeg {
inputs: Vec<FfmpegInput>, inputs: Vec<FfmpegInput>,
filters: Vec<Filter>, filter: FfmpegFilter,
filters_output: Cow<'static, str>, video_bitrate: Option<&'static str>,
loudnorm: bool,
output: FfmpegOutput, output: FfmpegOutput,
filter_idx: usize filter_idx: usize
...@@ -101,9 +120,8 @@ impl Ffmpeg { ...@@ -101,9 +120,8 @@ impl Ffmpeg {
pub fn new(output: FfmpegOutput) -> Self { pub fn new(output: FfmpegOutput) -> Self {
Self { Self {
inputs: Vec::new(), inputs: Vec::new(),
filters: Vec::new(), filter: FfmpegFilter::None,
filters_output: "0".into(), video_bitrate: None,
loudnorm: false,
output, output,
filter_idx: 0 filter_idx: 0
...@@ -116,22 +134,56 @@ impl Ffmpeg { ...@@ -116,22 +134,56 @@ impl Ffmpeg {
} }
pub fn add_filter(&mut self, filter: Filter) -> &mut Self { pub fn add_filter(&mut self, filter: Filter) -> &mut Self {
assert!(!self.loudnorm); match &mut self.filter {
self.filters.push(filter); FfmpegFilter::None => {
self.filter = FfmpegFilter::Filters {
filters: vec![filter],
output: "0".into()
}
},
FfmpegFilter::Filters { filters, .. } => filters.push(filter),
_ => panic!("An incompatible type of filter has been set before")
}
self self
} }
pub fn set_filter_output<T: Into<Cow<'static, str>>>( pub fn set_filter_output<T: Into<Cow<'static, str>>>(
&mut self, &mut self,
output: T filter_output: T
) -> &mut Self { ) -> &mut Self {
self.filters_output = output.into(); match &mut self.filter {
FfmpegFilter::None => {
self.filter = FfmpegFilter::Filters {
filters: vec![],
output: filter_output.into()
}
},
FfmpegFilter::Filters { output, .. } => *output = filter_output.into(),
_ => panic!("An incompatible type of filter has been set before")
}
self self
} }
pub fn enable_loudnorm(&mut self) -> &mut Self { pub fn enable_loudnorm(&mut self) -> &mut Self {
assert!(self.filters.is_empty()); match &mut self.filter {
self.loudnorm = true; FfmpegFilter::None => self.filter = FfmpegFilter::Loudnorm,
FfmpegFilter::Loudnorm => {},
_ => panic!("An incompatible type of filter has been set before")
}
self
}
pub fn rescale_video(&mut self, res: Resolution) -> &mut Self {
match &mut self.filter {
FfmpegFilter::None => self.filter = FfmpegFilter::Rescale(res),
FfmpegFilter::Loudnorm => {},
_ => panic!("An incompatible type of filter has been set before")
}
self
}
pub fn set_video_bitrate(&mut self, bitrate: &'static str) -> &mut Self {
self.video_bitrate = Some(bitrate);
self self
} }
...@@ -140,15 +192,26 @@ impl Ffmpeg { ...@@ -140,15 +192,26 @@ impl Ffmpeg {
cmd.arg("ffmpeg").arg("-hide_banner").arg("-y"); cmd.arg("ffmpeg").arg("-hide_banner").arg("-y");
// determine whether the video need to be re-encoded // determine whether the video need to be re-encoded
let venc = !self.filters.is_empty(); // vdec is only true if the video should be decoded on hardware
let aenc = !self.filters.is_empty() || self.loudnorm; let (vdec, venc, aenc) = match &self.filter {
FfmpegFilter::None => (false, false, false),
FfmpegFilter::Filters { .. } => (false, true, true),
FfmpegFilter::Loudnorm => (false, false, true),
FfmpegFilter::Rescale(_) => (true, true, false)
};
// initialise a vaapi device if one exists // initialise a vaapi device if one exists
let vaapi_device: PathBuf = "/dev/dri/renderD128".into(); let vaapi_device: PathBuf = "/dev/dri/renderD128".into();
let vaapi = venc && vaapi_device.exists(); let vaapi = vaapi_device.exists();
if vaapi { if vaapi && venc {
if vdec {
cmd.arg("-hwaccel").arg("vaapi");
cmd.arg("-hwaccel_device").arg(vaapi_device);
cmd.arg("-hwaccel_output_format").arg("vaapi");
} else {
cmd.arg("-vaapi_device").arg(&vaapi_device); cmd.arg("-vaapi_device").arg(&vaapi_device);
} }
}
// append all the inputs // append all the inputs
for i in self.inputs { for i in self.inputs {
...@@ -159,30 +222,36 @@ impl Ffmpeg { ...@@ -159,30 +222,36 @@ impl Ffmpeg {
cmd.arg("-async").arg("1"); cmd.arg("-async").arg("1");
// apply filters // apply filters
match (self.loudnorm, self.filters) { match self.filter {
(true, f) if f.is_empty() => { FfmpegFilter::None => {},
cmd.arg("-af").arg("pan=mono|c0=FR,loudnorm,pan=stereo|c0=c0|c1=c0,aformat=sample_rates=48000"); FfmpegFilter::Filters { filters, output } => {
},
(true, _) => panic!("Filters and loudnorm at the same time is not supported"),
(false, f) if f.is_empty() => {},
(false, f) => {
let mut complex = String::new(); let mut complex = String::new();
for filter in f { for filter in filters {
filter.append_to_complex_filter(&mut complex, &mut self.filter_idx); filter.append_to_complex_filter(&mut complex, &mut self.filter_idx);
} }
if vaapi { if vaapi {
write!( write!(complex, "{}format=nv12,hwupload[v]", channel('v', &output));
complex,
"{}format=nv12,hwupload[v]",
channel('v', &self.filters_output)
);
} else { } else {
write!(complex, "{}null[v]", channel('v', &self.filters_output)); write!(complex, "{}null[v]", channel('v', &output));
} }
cmd.arg("-filter_complex").arg(complex); cmd.arg("-filter_complex").arg(complex);
cmd.arg("-map").arg("[v]"); cmd.arg("-map").arg("[v]");
cmd.arg("-map").arg(channel('a', &self.filters_output)); cmd.arg("-map").arg(channel('a', &output));
},
FfmpegFilter::Loudnorm => {
cmd.arg("-af").arg(concat!(
"pan=mono|c0=FR,",
"loudnorm=dual_mono=true:print_format=summary,",
"pan=stereo|c0=c0|c1=c0,",
"aformat=sample_rates=48000"
));
},
FfmpegFilter::Rescale(res) => {
cmd.arg("-vf").arg(if vaapi {
format!("scale_vaapi=w={}:h={}", res.width(), res.height())
} else {
format!("scale=w={}:h={}", res.width(), res.height())
});
} }
} }
...@@ -190,14 +259,21 @@ impl Ffmpeg { ...@@ -190,14 +259,21 @@ impl Ffmpeg {
const QUALITY: &str = "24"; const QUALITY: &str = "24";
if vaapi { if vaapi {
cmd.arg("-c:v").arg("h264_vaapi"); cmd.arg("-c:v").arg("h264_vaapi");
if self.video_bitrate.is_none() {
cmd.arg("-rc_mode").arg("CQP"); cmd.arg("-rc_mode").arg("CQP");
cmd.arg("-global_quality").arg(QUALITY); cmd.arg("-global_quality").arg(QUALITY);
}
} else if venc { } else if venc {
cmd.arg("-c:v").arg("libx264"); cmd.arg("-c:v").arg("libx264");
if self.video_bitrate.is_none() {
cmd.arg("-crf").arg(QUALITY); cmd.arg("-crf").arg(QUALITY);
}
} else { } else {
cmd.arg("-c:v").arg("copy"); cmd.arg("-c:v").arg("copy");
} }
if venc && self.video_bitrate.is_some() {
cmd.arg("-b:v").arg(self.video_bitrate.unwrap());
}
if aenc { if aenc {
cmd.arg("-c:a").arg("aac"); cmd.arg("-c:a").arg("aac");
cmd.arg("-b:a").arg("128000"); cmd.arg("-b:a").arg("128000");
......
...@@ -128,6 +128,7 @@ fn svg2mp4( ...@@ -128,6 +128,7 @@ fn svg2mp4(
duration: Some(duration), duration: Some(duration),
time_base: Some(meta.source_tbn), time_base: Some(meta.source_tbn),
fps_mode_vfr: true, fps_mode_vfr: true,
faststart: false,
path: mp4 path: mp4
}); });
ffmpeg.add_input(FfmpegInput { ffmpeg.add_input(FfmpegInput {
...@@ -452,32 +453,12 @@ impl<'a> Renderer<'a> { ...@@ -452,32 +453,12 @@ impl<'a> Renderer<'a> {
let output = self.video_mp4_res(res); let output = self.video_mp4_res(res);
println!("\x1B[1m ==> Rescaling to {}p\x1B[0m", res.height()); println!("\x1B[1m ==> Rescaling to {}p\x1B[0m", res.height());
let mut ffmpeg = cmd(); let mut ffmpeg =
ffmpeg.arg("ffmpeg").arg("-hide_banner"); Ffmpeg::new(FfmpegOutput::new(output.clone()).enable_faststart());
// TODO do we just always want hwaccel? ffmpeg.add_input(FfmpegInput::new(input));
ffmpeg ffmpeg.rescale_video(res);
.arg("-hwaccel") ffmpeg.set_video_bitrate(res.bitrate());
.arg("vaapi") ffmpeg.run()?;
.arg("-hwaccel_device")
.arg("/dev/dri/renderD128")
.arg("-hwaccel_output_format")
.arg("vaapi");
ffmpeg.arg("-i").arg(input);
ffmpeg.arg("-vf").arg(format!(
"scale_vaapi=w={}:h={}",
res.width(),
res.height()
));
ffmpeg.arg("-c:a").arg("copy").arg("-c:v").arg("h264_vaapi");
ffmpeg.arg("-b:v").arg(res.bitrate());
ffmpeg.arg("-movflags").arg("+faststart");
ffmpeg.arg(&output);
let status = ffmpeg.status()?;
if status.success() {
Ok(output) Ok(output)
} else {
bail!("ffmpeg failed with exit code {:?}", status.code())
}
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment