Skip to content

Commit

Permalink
Add --vmaf-fps
Browse files Browse the repository at this point in the history
  • Loading branch information
alexheretic committed Dec 22, 2024
1 parent 1c17a7f commit f45176d
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Unreleased (0.8.1)
* Support negative `--preset` args.
* Add `--vmaf-fps`: Frame rate override used to analyse both reference & distorted videos.

# v0.8.0
* crf-search: Tweak 2nd iteration logic that slices the crf range at the 25% or 75% crf point.
Expand Down
76 changes: 54 additions & 22 deletions src/command/args/vmaf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use clap::Parser;
use std::{borrow::Cow, fmt::Display, sync::Arc, thread};

/// Common vmaf options.
#[derive(Parser, Clone, Hash)]
#[derive(Debug, Default, Parser, Clone)]
pub struct Vmaf {
/// Additional vmaf arg(s). E.g. --vmaf n_threads=8 --vmaf n_subsample=4
///
Expand All @@ -29,9 +29,18 @@ pub struct Vmaf {
/// to post input/reference vfilter dimensions.
///
/// Scaling happens after any input/reference vfilters.
#[arg(long, default_value_t = VmafScale::Auto, value_parser = parse_vmaf_scale)]
#[arg(long, default_value_t, value_parser = parse_vmaf_scale)]
pub vmaf_scale: VmafScale,

/// Frame rate override used to analyse both reference & distorted videos.
/// Maps to ffmpeg `-r` input arg.
///
/// Setting to a value, e.g. 25, can workaround issues with some videos.
///
/// By default no override is set.
#[arg(long)]
pub vmaf_fps: Option<f32>,

/// Ffmpeg video filter applied to the VMAF reference before analysis.
/// E.g. --reference-vfilter "scale=1280:-1,fps=24".
///
Expand All @@ -40,6 +49,15 @@ pub struct Vmaf {
pub reference_vfilter: Option<String>,
}

impl std::hash::Hash for Vmaf {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.vmaf_args.hash(state);
self.vmaf_scale.hash(state);
self.vmaf_fps.map(|f| f.to_ne_bytes()).hash(state);
self.reference_vfilter.hash(state);
}
}

fn parse_vmaf_arg(arg: &str) -> anyhow::Result<Arc<str>> {
Ok(arg.to_owned().into())
}
Expand All @@ -49,6 +67,7 @@ impl Vmaf {
let Self {
vmaf_args,
vmaf_scale,
vmaf_fps: _,
reference_vfilter,
} = self;
vmaf_args.is_empty() && *vmaf_scale == VmafScale::Auto && reference_vfilter.is_none()
Expand Down Expand Up @@ -147,11 +166,15 @@ fn minimally_scale((from_w, from_h): (u32, u32), (target_w, target_h): (u32, u32
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VmafScale {
None,
#[default]
Auto,
Custom { width: u32, height: u32 },
Custom {
width: u32,
height: u32,
},
}

fn parse_vmaf_scale(vs: &str) -> anyhow::Result<VmafScale> {
Expand Down Expand Up @@ -208,8 +231,7 @@ impl VmafModel {
fn vmaf_lavfi() {
let vmaf = Vmaf {
vmaf_args: vec!["n_threads=5".into(), "n_subsample=4".into()],
vmaf_scale: VmafScale::Auto,
reference_vfilter: None,
..<_>::default()
};
assert_eq!(
vmaf.ffmpeg_lavfi(None, PixelFormat::Yuv420p, Some("scale=1280:-1,fps=24")),
Expand All @@ -224,6 +246,7 @@ fn vmaf_lavfi_override_reference_vfilter() {
let vmaf = Vmaf {
vmaf_args: vec!["n_threads=5".into(), "n_subsample=4".into()],
vmaf_scale: VmafScale::Auto,
vmaf_fps: None,
reference_vfilter: Some("scale=2560:-1".into()),
};
assert_eq!(
Expand All @@ -240,10 +263,25 @@ fn vmaf_lavfi_override_reference_vfilter() {

#[test]
fn vmaf_lavfi_default() {
let vmaf = Vmaf::default();
let expected = format!(
"[0:v]format=yuv420p10le,setpts=PTS-STARTPTS,settb=AVTB[dis];\
[1:v]format=yuv420p10le,setpts=PTS-STARTPTS,settb=AVTB[ref];\
[dis][ref]libvmaf=shortest=true:ts_sync_mode=nearest:n_threads={}",
thread::available_parallelism().map_or(1, |p| p.get())
);
assert_eq!(
vmaf.ffmpeg_lavfi(None, PixelFormat::Yuv420p10le, None),
expected
);
}

/// `vmaf_fps` shouldn't affect lavfi
#[test]
fn vmaf_fps_lavfi() {
let vmaf = Vmaf {
vmaf_args: vec![],
vmaf_scale: VmafScale::Auto,
reference_vfilter: None,
vmaf_fps: Some(25.0),
..<_>::default()
};
let expected = format!(
"[0:v]format=yuv420p10le,setpts=PTS-STARTPTS,settb=AVTB[dis];\
Expand All @@ -261,8 +299,7 @@ fn vmaf_lavfi_default() {
fn vmaf_lavfi_include_n_threads() {
let vmaf = Vmaf {
vmaf_args: vec!["log_path=output.xml".into()],
vmaf_scale: VmafScale::Auto,
reference_vfilter: None,
..<_>::default()
};
let expected = format!(
"[0:v]format=yuv420p,setpts=PTS-STARTPTS,settb=AVTB[dis];\
Expand All @@ -281,8 +318,7 @@ fn vmaf_lavfi_include_n_threads() {
fn vmaf_lavfi_small_width() {
let vmaf = Vmaf {
vmaf_args: vec!["n_threads=5".into(), "n_subsample=4".into()],
vmaf_scale: VmafScale::Auto,
reference_vfilter: None,
..<_>::default()
};
assert_eq!(
vmaf.ffmpeg_lavfi(Some((1280, 720)), PixelFormat::Yuv420p, None),
Expand All @@ -297,8 +333,7 @@ fn vmaf_lavfi_small_width() {
fn vmaf_lavfi_4k() {
let vmaf = Vmaf {
vmaf_args: vec!["n_threads=5".into(), "n_subsample=4".into()],
vmaf_scale: VmafScale::Auto,
reference_vfilter: None,
..<_>::default()
};
assert_eq!(
vmaf.ffmpeg_lavfi(Some((3840, 2160)), PixelFormat::Yuv420p, None),
Expand All @@ -313,8 +348,7 @@ fn vmaf_lavfi_4k() {
fn vmaf_lavfi_3k_upscale_to_4k() {
let vmaf = Vmaf {
vmaf_args: vec!["n_threads=5".into()],
vmaf_scale: VmafScale::Auto,
reference_vfilter: None,
..<_>::default()
};
assert_eq!(
vmaf.ffmpeg_lavfi(Some((3008, 1692)), PixelFormat::Yuv420p, None),
Expand All @@ -333,8 +367,7 @@ fn vmaf_lavfi_small_width_custom_model() {
"n_threads=5".into(),
"n_subsample=4".into(),
],
vmaf_scale: VmafScale::Auto,
reference_vfilter: None,
..<_>::default()
};
assert_eq!(
vmaf.ffmpeg_lavfi(Some((1280, 720)), PixelFormat::Yuv420p, None),
Expand All @@ -357,7 +390,7 @@ fn vmaf_lavfi_custom_model_and_width() {
width: 123,
height: 720,
},
reference_vfilter: None,
..<_>::default()
};
assert_eq!(
vmaf.ffmpeg_lavfi(Some((1280, 720)), PixelFormat::Yuv420p, None),
Expand All @@ -371,8 +404,7 @@ fn vmaf_lavfi_custom_model_and_width() {
fn vmaf_lavfi_1080p() {
let vmaf = Vmaf {
vmaf_args: vec!["n_threads=5".into(), "n_subsample=4".into()],
vmaf_scale: VmafScale::Auto,
reference_vfilter: None,
..<_>::default()
};
assert_eq!(
vmaf.ffmpeg_lavfi(Some((1920, 1080)), PixelFormat::Yuv420p, None),
Expand Down
1 change: 1 addition & 0 deletions src/command/sample_encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ pub fn run(
.max(input_pixel_format.unwrap_or(PixelFormat::Yuv444p10le)),
args.vfilter.as_deref(),
),
vmaf.vmaf_fps,
)?;
let mut vmaf = pin!(vmaf);
let mut logger = ProgressLogger::new("ab_av1::vmaf", Instant::now());
Expand Down
1 change: 1 addition & 0 deletions src/command/vmaf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ pub async fn vmaf(
dpix_fmt.max(rpix_fmt),
vmaf.reference_vfilter.as_deref(),
),
vmaf.vmaf_fps,
)?);
let mut logger = ProgressLogger::new(module_path!(), Instant::now());
let mut vmaf_score = None;
Expand Down
3 changes: 3 additions & 0 deletions src/vmaf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub fn run(
reference: &Path,
distorted: &Path,
filter_complex: &str,
fps: Option<f32>,
) -> anyhow::Result<impl Stream<Item = VmafOut>> {
info!(
"vmaf {} vs reference {}",
Expand All @@ -22,7 +23,9 @@ pub fn run(

let mut cmd = Command::new("ffmpeg");
cmd.kill_on_drop(true)
.arg2_opt("-r", fps)
.arg2("-i", distorted)
.arg2_opt("-r", fps)
.arg2("-i", reference)
.arg2("-filter_complex", filter_complex)
.arg2("-f", "null")
Expand Down

0 comments on commit f45176d

Please sign in to comment.