From 79a636aab091564b0bb9c222f33b3a91528a461d Mon Sep 17 00:00:00 2001 From: mardaravicius <45588145+mardaravicius@users.noreply.github.com> Date: Sun, 2 Aug 2026 14:31:24 +0200 Subject: [PATCH 1/2] add vaapi support --- README.md | 37 +++++++++- benchmark/src/benchmark_cli.rs | 13 +++- benchmark/src/main.rs | 10 +++ cli/src/cli_util.rs | 26 +++++++ cli/src/supported.rs | 25 ++++++- codecs/src/lib.rs | 3 + codecs/src/vaapi.rs | 121 +++++++++++++++++++++++++++++++ codecs/src/vendor.rs | 1 + engine/src/engine.rs | 8 +- engine/src/permutation_engine.rs | 1 + ffmpeg/src/args.rs | 101 +++++++++++++++++++++++++- permutation/src/permutation.rs | 2 + permutor-cli/src/main.rs | 30 ++++++++ permutor-cli/src/permutor_cli.rs | 8 +- 14 files changed, 377 insertions(+), 9 deletions(-) create mode 100644 codecs/src/vaapi.rs diff --git a/README.md b/README.md index 412e131..fe2487e 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ supported. - ***Nvidia NVENC H264/HEVC** (h264_nvenc, hevc_nvenc) - ***AMD H264/HEVC** (h264_amf, hevc_amf) +- ***VAAPI H264/HEVC/AV1 on Linux** (h264_vaapi, hevc_vaapi, av1_vaapi; commonly used with AMD or Intel GPUs) - **Intel Quick Sync Video H264/HEVC** (h264_qsv, hevc_qsv) - ***Intel Arc AV1** (av1_qsv) - **Apple Silicon H264/HEVC** (h264_videotoolbox, hevc_videotoolbox) @@ -114,6 +115,10 @@ it's highly suggested to use the same version, or at least version `6.*` of ffmp - For Mac, recommended to install via `brew install ffmpeg` (will install ffpmpeg and ffprobe) - For Linux, recommended to install using your distro's package manager. Note: the default one may be a much lower version than `6.0` and may require more setup + - To use VAAPI on Linux, install the VAAPI driver package supplied by your distribution for your GPU vendor. For + example, AMD commonly uses the Mesa VAAPI driver, while current Intel hardware uses `intel-media-driver` (iHD) + and older Intel hardware may require the legacy i965 driver + - Make sure your user can access the DRM render nodes under `/dev/dri` 2) 7-Zip to unzip any downloaded ffmpeg binaries 3) ffmpeg/ffprobe must be available on your path (tool will error out if it can't find either); quick path setup guide for @@ -156,6 +161,36 @@ benchmark is as simple as: Kill the tool at any time by hitting `ctrl-c` in the terminal/console where the tool is running. +### VAAPI on Linux + +VAAPI encoders use a DRM render node instead of the numeric `--gpu` option used by NVENC and AMF. The default device is +`/dev/dri/renderD128`; select a different GPU with `--vaapi-device`: + +`./benchmark -e h264_vaapi -s 1080-60.y4m --vaapi-device /dev/dri/renderD128` + +On a mixed-GPU system, pass the render node belonging to the GPU you want to benchmark, for example +`/dev/dri/renderD129`. VAAPI render-node ordering is independent of the numeric `--gpu` option. + +The user running the benchmark must have permission to access the render device. First inspect the selected render +node and its permissions: + +`ls -l /dev/dri/renderD128` + +If the device belongs to a non-root group such as `render` or `video`, add your user to that group—for example, +`sudo usermod -aG render $USER`—then log out and back in for the new group membership to take effect. If the device's +group is `root`, do not add your user to the `root` group; instead, change the device's group to one your user belongs +to, for example `sudo chgrp "$(id -gn)" /dev/dri/renderD128`. This change may be reset when the device or system restarts. +You can alternatively run the benchmark with `sudo`, but this is not recommended. + +For 10-bit HEVC or AV1, add `--ten-bit`. Ten-bit H264 VAAPI is not supported by this tool. + +VAAPI is separate from vendor-specific FFmpeg backends such as Intel QSV and Nvidia NVENC. Use `h264_vaapi`, +`hevc_vaapi`, or `av1_vaapi` to benchmark VAAPI through a DRM render node. AV1 encoding requires hardware and a VAAPI +driver that exposes AV1 encode support. + +Before running, verify that the FFmpeg build exposes the requested `h264_vaapi`, `hevc_vaapi`, or `av1_vaapi` encoder +with `ffmpeg -encoders`, and inspect available render nodes with `ls /dev/dri/renderD*`. + ### Understanding the results You'll be given the maximum fps statistics possible at a given resolution, with those results logged out to a @@ -398,4 +433,4 @@ An example of what the author did for the nvenc_h264 encoder: Error about unsupported feature If you are getting the following: `#![feature] may not be used on the stable release channel`, this should be fixable by -running `cargo clean`. \ No newline at end of file +running `cargo clean`. diff --git a/benchmark/src/benchmark_cli.rs b/benchmark/src/benchmark_cli.rs index b4536f7..3b7bcf6 100644 --- a/benchmark/src/benchmark_cli.rs +++ b/benchmark/src/benchmark_cli.rs @@ -2,6 +2,7 @@ use clap::Parser; use cli::cli_util::{ are_all_source_files_present, error_with_ack, get_repo_url, standard_cli_check, + validate_vaapi_options, }; use cli::supported::get_supported_inputs; @@ -28,10 +29,13 @@ pub struct BenchmarkCli { /// the GPU you wish to run the encode on; defaults to the first/only GPU found in your system #[arg(short, long, default_value = "0")] pub gpu: u8, + /// DRM render node used by VAAPI encoders + #[arg(long, value_name = "device", default_value = "/dev/dri/renderD128")] + pub vaapi_device: String, /// whether to run decode benchmark as well; defaults to off, as this will take up more storage space #[arg(short, long)] pub decode: bool, - /// adds in '-pix_fmt yuv420p10le' to force 10-bit encoding + /// forces 10-bit encoding; VAAPI HEVC uploads p010 hardware frames #[arg(short, long)] pub ten_bit: bool, // this is an internal option not intended to be exposed to the end user @@ -57,6 +61,7 @@ impl BenchmarkCli { log_output_directory: String::from(""), verbose: false, gpu: 0, + vaapi_device: String::from("/dev/dri/renderD128"), decode: false, ten_bit: false, was_ui_opened: false, @@ -72,6 +77,12 @@ impl BenchmarkCli { &self.files_directory, self.was_ui_opened, ); + validate_vaapi_options( + &self.encoder, + &self.vaapi_device, + self.ten_bit, + self.was_ui_opened, + ); // if you did not provide a source file, we'll be running on all expected files if self.source_file.is_empty() && !are_all_source_files_present(&self.files_directory) { diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index b1d4d5a..c0d1348 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -13,6 +13,7 @@ use codecs::get_vendor_for_codec; use codecs::nvenc::Nvenc; use codecs::permute::Permute; use codecs::qsv::QSV; +use codecs::vaapi::Vaapi; use codecs::vendor::Vendor; use engine::benchmark_engine::BenchmarkEngine; use ffmpeg::metadata::MetaData; @@ -67,6 +68,7 @@ fn benchmark() { permutation.bitrate = bitrate; permutation.encoder_settings = settings; permutation.verbose = cli.verbose; + permutation.vaapi_device = cli.vaapi_device.clone(); // tell this encode run that we'll want to preserve the file output if cli.decode { @@ -282,6 +284,10 @@ fn get_benchmark_settings_for(cli: &BenchmarkCli) -> String { apple_h264.get_benchmark_settings() } } + Vendor::Vaapi => { + let vaapi = Vaapi::new(&cli.encoder, cli.ten_bit); + vaapi.get_benchmark_settings() + } Vendor::Unknown => { // nothing to do here String::from("") @@ -294,6 +300,10 @@ fn get_bitrate_for(metadata: &MetaData, string: String) -> u32 { return *Nvenc::get_resolution_to_bitrate_map(metadata.fps) .get(&metadata.get_res()) .unwrap(); + } else if string.contains("vaapi") { + return *Vaapi::get_resolution_to_bitrate_map(metadata.fps) + .get(&metadata.get_res()) + .unwrap(); } else { return *Amf::get_resolution_to_bitrate_map(metadata.fps) .get(&metadata.get_res()) diff --git a/cli/src/cli_util.rs b/cli/src/cli_util.rs index c981486..8492918 100644 --- a/cli/src/cli_util.rs +++ b/cli/src/cli_util.rs @@ -86,6 +86,32 @@ pub fn standard_cli_check( } } +pub fn validate_vaapi_options( + encoder: &String, + vaapi_device: &String, + ten_bit: bool, + was_ui_opened: bool, +) { + if !encoder.contains("vaapi") { + return; + } + + if !cfg!(target_os = "linux") { + println!("Error: VAAPI encoders are currently supported only on Linux"); + error_with_ack(was_ui_opened); + } + + if !Path::new(vaapi_device).exists() { + println!("Error: VAAPI device [{}] does not exist", vaapi_device); + error_with_ack(was_ui_opened); + } + + if encoder == "h264_vaapi" && ten_bit { + println!("Error: 10-bit encoding is not supported by h264_vaapi"); + error_with_ack(was_ui_opened); + } +} + pub fn error_with_ack(ack: bool) { // want to give the user a chance to acknowledge the error if ack { diff --git a/cli/src/supported.rs b/cli/src/supported.rs index 950f182..449e58d 100644 --- a/cli/src/supported.rs +++ b/cli/src/supported.rs @@ -1,4 +1,4 @@ -const SUPPORTED_ENCODERS: [&'static str; 10] = [ +const SUPPORTED_ENCODERS: [&'static str; 13] = [ "h264_nvenc", "hevc_nvenc", "h264_amf", @@ -9,6 +9,9 @@ const SUPPORTED_ENCODERS: [&'static str; 10] = [ "h264_videotoolbox", "hevc_videotoolbox", "prores_videotoolbox", + "h264_vaapi", + "hevc_vaapi", + "av1_vaapi", ]; const ENCODE_FILES: [&'static str; 8] = [ @@ -26,10 +29,28 @@ pub fn is_encoder_supported(potential_encoder: &String) -> bool { return SUPPORTED_ENCODERS.contains(&potential_encoder.as_str()); } -pub fn get_supported_encoders() -> [&'static str; 10] { +pub fn get_supported_encoders() -> [&'static str; 13] { return SUPPORTED_ENCODERS; } pub fn get_supported_inputs() -> [&'static str; 8] { return ENCODE_FILES; } + +#[cfg(test)] +mod tests { + use super::{get_supported_encoders, is_encoder_supported}; + + #[test] + fn av1_vaapi_is_supported() { + assert!(is_encoder_supported(&"av1_vaapi".to_string())); + } + + #[test] + fn vaapi_encoders_are_listed_last() { + assert_eq!( + &get_supported_encoders()[10..], + &["h264_vaapi", "hevc_vaapi", "av1_vaapi"] + ); + } +} diff --git a/codecs/src/lib.rs b/codecs/src/lib.rs index 99a4833..0fcbbd3 100644 --- a/codecs/src/lib.rs +++ b/codecs/src/lib.rs @@ -7,6 +7,7 @@ pub mod nvenc; pub mod permute; pub mod qsv; mod resolutions; +pub mod vaapi; pub mod vendor; pub fn get_vendor_for_codec(codec: &String) -> Vendor { @@ -19,6 +20,8 @@ pub fn get_vendor_for_codec(codec: &String) -> Vendor { return Vendor::IntelQSV; } else if codec.contains("videotoolbox") { return Vendor::Apple; + } else if codec.contains("vaapi") { + return Vendor::Vaapi; } return Vendor::Unknown; diff --git a/codecs/src/vaapi.rs b/codecs/src/vaapi.rs new file mode 100644 index 0000000..7add137 --- /dev/null +++ b/codecs/src/vaapi.rs @@ -0,0 +1,121 @@ +use std::collections::HashMap; + +use crate::permute::Permute; +use crate::resolutions::map_res_to_bitrate; + +pub struct Vaapi { + profile: &'static str, + permutations: Vec, + index: i32, +} + +impl Vaapi { + pub fn new(encoder: &str, ten_bit: bool) -> Self { + Self { + profile: if encoder == "hevc_vaapi" && ten_bit { + "main10" + } else if encoder == "hevc_vaapi" || encoder == "av1_vaapi" { + "main" + } else { + "high" + }, + permutations: Vec::new(), + index: -1, + } + } + + pub fn get_benchmark_settings(&self) -> String { + format!("-rc_mode CBR -profile:v {}", self.profile) + } + + fn has_next(&self) -> bool { + self.index + 1 < self.permutations.len() as i32 + } +} + +impl Iterator for Vaapi { + type Item = (usize, String); + + fn next(&mut self) -> Option { + if !self.has_next() { + return None; + } + + self.index += 1; + let index = self.index as usize; + Some((index, self.permutations.get(index).unwrap().to_string())) + } +} + +impl Permute for Vaapi { + fn init(&mut self) -> &Vec { + self.run_standard_only() + } + + fn run_standard_only(&mut self) -> &Vec { + self.index = -1; + self.permutations.clear(); + self.permutations.push(self.get_benchmark_settings()); + &self.permutations + } + + fn get_resolution_to_bitrate_map(fps: u32) -> HashMap { + let mut map = HashMap::new(); + let mut bitrates: [u32; 4] = [20, 35, 50, 85]; + + if fps == 120 { + bitrates.iter_mut().for_each(|bitrate| *bitrate *= 2); + } + + map_res_to_bitrate(&mut map, bitrates); + map + } +} + +#[cfg(test)] +mod tests { + use super::Vaapi; + use crate::permute::Permute; + + #[test] + fn h264_uses_high_profile() { + assert_eq!( + Vaapi::new("h264_vaapi", false).get_benchmark_settings(), + "-rc_mode CBR -profile:v high" + ); + } + + #[test] + fn hevc_uses_main_profile() { + assert_eq!( + Vaapi::new("hevc_vaapi", false).get_benchmark_settings(), + "-rc_mode CBR -profile:v main" + ); + } + + #[test] + fn ten_bit_hevc_uses_main10_profile() { + assert_eq!( + Vaapi::new("hevc_vaapi", true).get_benchmark_settings(), + "-rc_mode CBR -profile:v main10" + ); + } + + #[test] + fn av1_uses_main_profile_for_eight_and_ten_bit() { + for ten_bit in [false, true] { + assert_eq!( + Vaapi::new("av1_vaapi", ten_bit).get_benchmark_settings(), + "-rc_mode CBR -profile:v main" + ); + } + } + + #[test] + fn standard_permutation_can_be_iterated() { + let mut vaapi = Vaapi::new("h264_vaapi", false); + vaapi.init(); + assert_eq!(vaapi.next().unwrap().1, vaapi.get_benchmark_settings()); + assert!(vaapi.next().is_none()); + } +} diff --git a/codecs/src/vendor.rs b/codecs/src/vendor.rs index b058b2a..756f3f0 100644 --- a/codecs/src/vendor.rs +++ b/codecs/src/vendor.rs @@ -3,5 +3,6 @@ pub enum Vendor { AMD, IntelQSV, Apple, + Vaapi, Unknown, } diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 78d4459..814fa4d 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -39,6 +39,7 @@ pub fn run_encode( p.bitrate, p.decode_run, p.ten_bit, + p.vaapi_device, ); let encode_start_time = SystemTime::now(); @@ -141,12 +142,17 @@ fn log_header( } println!("[Resolution:\t{}x{}]", metadata.width, metadata.height); - println!("[Encoder:\t{}]", permutation.encoder); + println!("[Encoder:\t{}]", format_encoder_label(&permutation)); println!("[FPS:\t\t{}]", metadata.fps); println!("[Bitrate:\t{}Mb/s]", permutation.bitrate); println!("[{}]", permutation.encoder_settings); } +fn format_encoder_label(permutation: &Permutation) -> String { + let bit_depth = if permutation.ten_bit { 10 } else { 8 }; + format!("{} ({}-bit)", permutation.encoder, bit_depth) +} + pub fn spawn_ffmpeg_child( ffmpeg_args: &FfmpegArgs, verbose: bool, diff --git a/engine/src/permutation_engine.rs b/engine/src/permutation_engine.rs index 865e57a..da21b22 100644 --- a/engine/src/permutation_engine.rs +++ b/engine/src/permutation_engine.rs @@ -178,6 +178,7 @@ fn calc_vmaf_score( p.bitrate.clone(), p.decode_run, p.ten_bit, + p.vaapi_device.clone(), ); println!( diff --git a/ffmpeg/src/args.rs b/ffmpeg/src/args.rs index 47f42db..5c1168d 100644 --- a/ffmpeg/src/args.rs +++ b/ffmpeg/src/args.rs @@ -21,6 +21,7 @@ pub struct FfmpegArgs { pub stats_period: c_float, pub decode: bool, pub ten_bit: bool, + pub vaapi_device: String, } impl Default for FfmpegArgs { @@ -41,6 +42,7 @@ impl Default for FfmpegArgs { stats_period: 0.5, decode: false, ten_bit: false, + vaapi_device: String::from("/dev/dri/renderD128"), } } } @@ -53,6 +55,7 @@ impl FfmpegArgs { current_bitrate: u32, decode: bool, ten_bit: bool, + vaapi_device: String, ) -> FfmpegArgs { let ffmpeg_args = FfmpegArgs { first_input, @@ -61,6 +64,7 @@ impl FfmpegArgs { encoder_args: encoder_args.to_string(), decode, ten_bit, + vaapi_device, ..Default::default() }; @@ -100,6 +104,17 @@ impl FfmpegArgs { // always pass the '-y' flag, for output to be overwritten output.push_str("-y "); + let is_vaapi = self.encoder.contains("vaapi"); + if is_vaapi && !self.decode && !self.is_vmaf { + output.push_str( + format!( + "-init_hw_device vaapi=va:{} -filter_hw_device va ", + self.vaapi_device + ) + .as_str(), + ); + } + if self.decode { let vendor = get_vendor_for_codec(&self.encoder); let hwaccel = match vendor { @@ -108,12 +123,19 @@ impl FfmpegArgs { Vendor::IntelQSV => "qsv", // TODO: implement this Vendor::Apple => "apple", + Vendor::Vaapi => "vaapi", Vendor::Unknown => "error", }; output.push_str("-hwaccel "); output.push_str(hwaccel); - output.push_str(" "); + output.push(' '); + + if is_vaapi { + output.push_str("-hwaccel_device "); + output.push_str(&self.vaapi_device); + output.push(' '); + } } if self.report { @@ -139,6 +161,10 @@ impl FfmpegArgs { if self.is_vmaf { append_vmaf_only_args(&mut output); } else { + if is_vaapi { + let upload_format = if self.ten_bit { "p010" } else { "nv12" }; + output.push_str(format!(" -vf format={},hwupload", upload_format).as_str()); + } append_encode_only_args( &mut output, self.bitrate, @@ -149,7 +175,7 @@ impl FfmpegArgs { } // add in pixel format of 10-bit if specified - if self.ten_bit { + if self.ten_bit && !is_vaapi { output.push(' '); output.push_str("-pix_fmt yuv420p10le"); } @@ -318,6 +344,76 @@ mod tests { ); } + #[test] + fn vaapi_encode_initializes_device_and_uploads_frames() { + let args = FfmpegArgs::build_ffmpeg_args( + INPUT_ONE.to_string(), + "h264_vaapi".to_string(), + &"-rc_mode CBR -profile:v high".to_string(), + BITRATE, + false, + false, + "/dev/dri/renderD129".to_string(), + ); + + assert_eq!( + args.to_string(), + "-progress tcp://localhost:1234 -stats_period 0.5 -y -init_hw_device vaapi=va:/dev/dri/renderD129 -filter_hw_device va -i 1080-60.y4m -vf format=nv12,hwupload -b:v 6M -c:v h264_vaapi -rc_mode CBR -profile:v high -f null -" + ); + } + + #[test] + fn vaapi_ten_bit_encode_uploads_p010_frames() { + let args = FfmpegArgs::build_ffmpeg_args( + INPUT_ONE.to_string(), + "hevc_vaapi".to_string(), + &"-rc_mode CBR -profile:v main10".to_string(), + BITRATE, + false, + true, + "/dev/dri/renderD128".to_string(), + ); + + assert!(args.to_string().contains("-vf format=p010,hwupload")); + assert!(!args.to_string().contains("-pix_fmt yuv420p10le")); + } + + #[test] + fn av1_vaapi_encode_uses_main_profile_and_hardware_upload() { + let args = FfmpegArgs::build_ffmpeg_args( + INPUT_ONE.to_string(), + "av1_vaapi".to_string(), + &"-rc_mode CBR -profile:v main".to_string(), + BITRATE, + false, + false, + "/dev/dri/renderD128".to_string(), + ); + + assert!(args.to_string().contains("-vf format=nv12,hwupload")); + assert!(args + .to_string() + .contains("-c:v av1_vaapi -rc_mode CBR -profile:v main")); + } + + #[test] + fn vaapi_decode_selects_requested_device() { + let args = FfmpegArgs::build_ffmpeg_args( + INPUT_ONE.to_string(), + "h264_vaapi".to_string(), + &String::new(), + BITRATE, + true, + false, + "/dev/dri/renderD129".to_string(), + ); + + assert_eq!( + args.to_string(), + "-progress tcp://localhost:1234 -stats_period 0.5 -y -hwaccel vaapi -hwaccel_device /dev/dri/renderD129 -i 1080-60.y4m -f null -" + ); + } + fn get_one_input_args() -> FfmpegArgs { let args = Cli { encoder: ENCODER.to_string(), @@ -339,6 +435,7 @@ mod tests { args.bitrate, false, false, + String::from("/dev/dri/renderD128"), ); } diff --git a/permutation/src/permutation.rs b/permutation/src/permutation.rs index a07ce3c..cd18ccd 100644 --- a/permutation/src/permutation.rs +++ b/permutation/src/permutation.rs @@ -15,6 +15,7 @@ pub struct Permutation { // determines whether this permutation is the decode run or not pub decode_run: bool, pub ten_bit: bool, + pub vaapi_device: String, // whether we are doing any decoding at all pub is_decoding: bool, } @@ -34,6 +35,7 @@ impl Permutation { decode_run: false, is_decoding: false, ten_bit: false, + vaapi_device: String::from("/dev/dri/renderD128"), } } diff --git a/permutor-cli/src/main.rs b/permutor-cli/src/main.rs index fde74ee..f2b2fa2 100644 --- a/permutor-cli/src/main.rs +++ b/permutor-cli/src/main.rs @@ -8,6 +8,7 @@ use codecs::get_vendor_for_codec; use codecs::nvenc::Nvenc; use codecs::permute::Permute; use codecs::qsv::QSV; +use codecs::vaapi::Vaapi; use codecs::vendor::Vendor; use engine::permutation_engine::PermutationEngine; use permutation::permutation::Permutation; @@ -43,6 +44,9 @@ fn main() { Vendor::Apple => { build_apple_silicon_h264_permutations(&mut engine, &cli, bitrate); } + Vendor::Vaapi => { + build_vaapi_setting_permutations(&mut engine, &cli, bitrate); + } Vendor::Unknown => {} } } @@ -130,6 +134,32 @@ fn build_amf_setting_permutations(engine: &mut PermutationEngine, cli: &Permutor } } +fn build_vaapi_setting_permutations( + engine: &mut PermutationEngine, + cli: &PermutorCli, + bitrate: u32, +) { + let mut vaapi = Vaapi::new(&cli.encoder, cli.ten_bit); + vaapi.init(); + + while let Some((_encoder_index, settings)) = vaapi.next() { + let mut permutation = Permutation::new(cli.source_file.clone(), cli.encoder.clone()); + permutation.encoder_settings = settings; + permutation.bitrate = bitrate; + permutation.check_quality = cli.check_quality; + permutation.verbose = cli.verbose; + permutation.detect_overload = cli.detect_overload; + permutation.allow_duplicates = cli.allow_duplicate_scores; + permutation.ten_bit = cli.ten_bit; + permutation.vaapi_device = cli.vaapi_device.clone(); + engine.add(permutation); + + if cli.test_run { + break; + } + } +} + fn build_intel_av1_permutations(engine: &mut PermutationEngine, cli: &PermutorCli, bitrate: u32) { let mut intel_av1 = AV1QSV::new(); diff --git a/permutor-cli/src/permutor_cli.rs b/permutor-cli/src/permutor_cli.rs index 6185171..bec40bb 100644 --- a/permutor-cli/src/permutor_cli.rs +++ b/permutor-cli/src/permutor_cli.rs @@ -1,6 +1,6 @@ use clap::Parser; -use cli::cli_util::{error_with_ack, standard_cli_check}; +use cli::cli_util::{error_with_ack, standard_cli_check, validate_vaapi_options}; #[derive(Parser)] pub struct PermutorCli { @@ -31,7 +31,7 @@ pub struct PermutorCli { /// runs just the first permutation for given encoder; useful for testing the tool & output #[arg(short, long)] pub test_run: bool, - /// adds in '-pix_fmt yuv420p10le' to force 10-bit encoding + /// forces 10-bit encoding; VAAPI HEVC uploads p010 hardware frames #[arg(long)] pub ten_bit: bool, /// maximum value to increase the bitrate to (in 5Mb/s intervals); if not specified, tool will not permute over bitrate values @@ -46,6 +46,9 @@ pub struct PermutorCli { /// the GPU you wish to run the encode on; defaults to the first/only GPU found in your system #[arg(short, long, default_value = "0")] pub gpu: u8, + /// DRM render node used by VAAPI encoders + #[arg(long, value_name = "device", default_value = "/dev/dri/renderD128")] + pub vaapi_device: String, /// opt-out of using b frames for either H264 or HEVC encoders; currently only supported for Nvidia GPUs #[arg(short, long)] pub no_b_frame: bool, @@ -60,6 +63,7 @@ impl PermutorCli { &self.files_directory, false, ); + validate_vaapi_options(&self.encoder, &self.vaapi_device, self.ten_bit, false); if self.source_file.is_empty() { println!("Error: No source file was provided to run on, please specify an input file"); From 513b125d93224bcf68b1d6f2f12208335c5d2b18 Mon Sep 17 00:00:00 2001 From: mardaravicius <45588145+mardaravicius@users.noreply.github.com> Date: Sun, 2 Aug 2026 21:56:23 +0200 Subject: [PATCH 2/2] add other profiles for permutator --- README.md | 2 +- codecs/src/vaapi.rs | 68 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index fe2487e..2599b52 100644 --- a/README.md +++ b/README.md @@ -59,10 +59,10 @@ supported. - ***Nvidia NVENC H264/HEVC** (h264_nvenc, hevc_nvenc) - ***AMD H264/HEVC** (h264_amf, hevc_amf) -- ***VAAPI H264/HEVC/AV1 on Linux** (h264_vaapi, hevc_vaapi, av1_vaapi; commonly used with AMD or Intel GPUs) - **Intel Quick Sync Video H264/HEVC** (h264_qsv, hevc_qsv) - ***Intel Arc AV1** (av1_qsv) - **Apple Silicon H264/HEVC** (h264_videotoolbox, hevc_videotoolbox) +- ***VAAPI H264/HEVC/AV1 on Linux** (h264_vaapi, hevc_vaapi, av1_vaapi; commonly used with AMD or Intel GPUs) The following have code in the project to 'support it' but currently do not function correctly: diff --git a/codecs/src/vaapi.rs b/codecs/src/vaapi.rs index 7add137..7ea0a21 100644 --- a/codecs/src/vaapi.rs +++ b/codecs/src/vaapi.rs @@ -4,7 +4,7 @@ use crate::permute::Permute; use crate::resolutions::map_res_to_bitrate; pub struct Vaapi { - profile: &'static str, + profiles: Vec<&'static str>, permutations: Vec, index: i32, } @@ -12,12 +12,16 @@ pub struct Vaapi { impl Vaapi { pub fn new(encoder: &str, ten_bit: bool) -> Self { Self { - profile: if encoder == "hevc_vaapi" && ten_bit { - "main10" - } else if encoder == "hevc_vaapi" || encoder == "av1_vaapi" { - "main" + profiles: if encoder == "h264_vaapi" { + vec!["high", "main", "constrained_baseline"] + } else if encoder == "hevc_vaapi" && ten_bit { + vec!["main10"] + } else if encoder == "hevc_vaapi" { + vec!["main"] } else { - "high" + // FFmpeg's VAAPI AV1 encoder currently supports Main for both + // 8-bit and 10-bit 4:2:0 input. + vec!["main"] }, permutations: Vec::new(), index: -1, @@ -25,7 +29,7 @@ impl Vaapi { } pub fn get_benchmark_settings(&self) -> String { - format!("-rc_mode CBR -profile:v {}", self.profile) + format!("-rc_mode CBR -profile:v {}", self.profiles.first().unwrap()) } fn has_next(&self) -> bool { @@ -49,7 +53,14 @@ impl Iterator for Vaapi { impl Permute for Vaapi { fn init(&mut self) -> &Vec { - self.run_standard_only() + self.index = -1; + self.permutations.clear(); + self.permutations.extend( + self.profiles + .iter() + .map(|profile| format!("-rc_mode CBR -profile:v {}", profile)), + ); + &self.permutations } fn run_standard_only(&mut self) -> &Vec { @@ -114,8 +125,47 @@ mod tests { #[test] fn standard_permutation_can_be_iterated() { let mut vaapi = Vaapi::new("h264_vaapi", false); - vaapi.init(); + vaapi.run_standard_only(); assert_eq!(vaapi.next().unwrap().1, vaapi.get_benchmark_settings()); assert!(vaapi.next().is_none()); } + + #[test] + fn h264_permutations_include_all_eight_bit_profiles() { + let mut vaapi = Vaapi::new("h264_vaapi", false); + assert_eq!( + vaapi.init(), + &vec![ + "-rc_mode CBR -profile:v high".to_string(), + "-rc_mode CBR -profile:v main".to_string(), + "-rc_mode CBR -profile:v constrained_baseline".to_string(), + ] + ); + } + + #[test] + fn hevc_permutations_follow_selected_bit_depth() { + let mut eight_bit = Vaapi::new("hevc_vaapi", false); + assert_eq!( + eight_bit.init(), + &vec!["-rc_mode CBR -profile:v main".to_string()] + ); + + let mut ten_bit = Vaapi::new("hevc_vaapi", true); + assert_eq!( + ten_bit.init(), + &vec!["-rc_mode CBR -profile:v main10".to_string()] + ); + } + + #[test] + fn av1_has_only_main_profile_permutation() { + for ten_bit in [false, true] { + let mut vaapi = Vaapi::new("av1_vaapi", ten_bit); + assert_eq!( + vaapi.init(), + &vec!["-rc_mode CBR -profile:v main".to_string()] + ); + } + } }