Skip to content

Commit 7d5e883

Browse files
authored
AMF support, improved result file for permutations, and option to allow duplicate scoring (#7)
1 parent b5b4cb7 commit 7d5e883

22 files changed

Lines changed: 343 additions & 44 deletions

Cargo.lock

Lines changed: 9 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ members = [
88
"permutation",
99
"permutor-cli",
1010
"cli",
11-
"gpus"
11+
"gpus",
12+
"codecs"
1213
]

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ PCI bottlenecking for GPU's not in the primary slot.
5454

5555
- do make sure your SSD drive in Windows does _not_ have drive compression enabled. This can severely affect your
5656
sequential read speeds, which is very important for reading high resolution/fps input files
57-
- the tool does _not_ support multiple Nvidia GPU's in your system, so it's suggested to just have 1 in your system
57+
- the tool supports multiple Nvidia GPU's in your system for both the benchmark & permutor-cli tool, so you can feel
58+
free to have more than 1 for your testing (although the benchmark would only run against one)
59+
- the tool does _not_ support multiple AMD GPU's for the benchmark tool, but you are able to still specify _-gpu_ with
60+
the permutor-cli tool
5861

5962
---
6063

benchmark/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ permutation = { path = "../permutation" }
1313
engine = { path = "../engine" }
1414
ffmpeg = { path = "../ffmpeg" }
1515
cli = { path = "../cli" }
16-
gpus = { path = "../gpus" }
16+
gpus = { path = "../gpus" }
17+
codecs = { path = "../codecs" }

benchmark/src/main.rs

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ use text_io::read;
88

99
use cli::cli_util::{is_dev, pause};
1010
use cli::supported::{get_supported_encoders, get_supported_inputs};
11+
use codecs::amf::Amf;
12+
use codecs::get_vendor_for_codec;
13+
use codecs::nvenc::Nvenc;
14+
use codecs::permute::Permute;
15+
use codecs::vendor::Vendor;
1116
use engine::benchmark_engine::BenchmarkEngine;
12-
use engine::h264_hevc_nvenc::Nvenc;
13-
use engine::permute::Permute;
1417
use ffmpeg::metadata::MetaData;
1518
use gpus::get_gpus;
1619
use permutation::permutation::Permutation;
@@ -41,15 +44,13 @@ fn main() {
4144

4245
cli.validate();
4346

44-
let input_files = get_input_files(cli.source_file);
47+
let input_files = get_input_files(cli.source_file.clone());
4548
let mut engine = BenchmarkEngine::new();
46-
let nvenc = Nvenc::new(cli.encoder == "hevc_nvenc", cli.gpu);
47-
4849
// prepare permutations for the engine to run over
4950
for input in input_files {
5051
let mut permutation = Permutation::new(input, cli.encoder.clone());
51-
let settings = get_settings_for(&nvenc);
52-
let bitrate = get_bitrate_for(&permutation.get_metadata());
52+
let settings = get_benchmark_settings_for(&cli);
53+
let bitrate = get_bitrate_for(&permutation.get_metadata(), cli.encoder.clone());
5354

5455
permutation.bitrate = bitrate;
5556
permutation.encoder_settings = settings;
@@ -151,14 +152,32 @@ fn print_options(input_vec: Vec<&str>) {
151152
}
152153
}
153154

154-
fn get_settings_for(nvenc: &Nvenc) -> String {
155-
// need to support other encoders here
156-
return nvenc.get_benchmark_settings();
155+
fn get_benchmark_settings_for(cli: &BenchmarkCli) -> String {
156+
let vendor = get_vendor_for_codec(&cli.encoder);
157+
158+
return match vendor {
159+
Vendor::Nvidia => {
160+
let nvenc = Nvenc::new(cli.encoder == "hevc_nvenc", cli.gpu);
161+
nvenc.get_benchmark_settings()
162+
}
163+
164+
Vendor::AMD => {
165+
let amf = Amf::new(cli.encoder == "hevc_nvenc", cli.gpu);
166+
amf.get_benchmark_settings()
167+
}
168+
Vendor::Unknown => {
169+
// nothing to do here
170+
String::from("")
171+
}
172+
};
157173
}
158174

159-
fn get_bitrate_for(metadata: &MetaData) -> u32 {
160-
// need to support other encoders here
161-
return *Nvenc::get_resolution_to_bitrate_map(metadata.fps).get(&metadata.get_res()).unwrap();
175+
fn get_bitrate_for(metadata: &MetaData, string: String) -> u32 {
176+
if string.contains("nvenc") {
177+
return *Nvenc::get_resolution_to_bitrate_map(metadata.fps).get(&metadata.get_res()).unwrap();
178+
} else {
179+
return *Amf::get_resolution_to_bitrate_map(metadata.fps).get(&metadata.get_res()).unwrap();
180+
}
162181
}
163182

164183
fn get_input_files(source_file: String) -> Vec<String> {

cli/src/supported.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
const SUPPORTED_ENCODERS: [&'static str; 2] = ["h264_nvenc", "hevc_nvenc"];
1+
const SUPPORTED_ENCODERS: [&'static str; 4] = ["h264_nvenc", "hevc_nvenc", "h264_amf", "hevc_amf"];
22
const DOWNLOAD_URL: &str = "https://www.dropbox.com/sh/x08pkk47lc1v5ex/AADGaoOjOcA0-uPo7I0NaxL-a?dl=0";
33
const ENCODE_FILES: [&'static str; 8] = ["720-60.y4m", "720-120.y4m", "1080-60.y4m", "1080-120.y4m", "2k-60.y4m", "2k-120.y4m", "4k-60.y4m", "4k-120.y4m"];
44

55
pub fn is_encoder_supported(potential_encoder: &String) -> bool {
66
return SUPPORTED_ENCODERS.contains(&potential_encoder.as_str());
77
}
88

9-
pub fn get_supported_encoders() -> [&'static str; 2] {
9+
pub fn get_supported_encoders() -> [&'static str; 4] {
1010
return SUPPORTED_ENCODERS;
1111
}
1212

codecs/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "codecs"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
itertools = "0.10.5"

codecs/src/amf.rs

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
use std::collections::HashMap;
2+
3+
use itertools::Itertools;
4+
5+
use crate::permute::Permute;
6+
use crate::resolutions::map_res_to_bitrate;
7+
8+
pub struct Amf {
9+
usages: Vec<&'static str>,
10+
qualities: Vec<&'static str>,
11+
profiles: Vec<&'static str>,
12+
profile_tiers: Vec<&'static str>,
13+
rate_controls: Vec<&'static str>,
14+
// might be able to make this the size we're expecting
15+
permutations: Vec<String>,
16+
index: i32,
17+
gpu: u8,
18+
}
19+
20+
impl Amf {
21+
pub fn new(is_hevc: bool, gpu: u8) -> Self {
22+
Self {
23+
usages: get_amf_usages(),
24+
qualities: get_amf_quality(),
25+
// this is the only difference between hevc & h264
26+
profiles: if is_hevc { vec!["main"] } else { vec!["main", "high", "constrained_baseline", "constrained_high"] },
27+
// leaving out vbr rate controls as these are not ideal for game streaming
28+
profile_tiers: get_amf_profile_tiers(is_hevc),
29+
rate_controls: vec!["cbr"],
30+
permutations: Vec::new(),
31+
// starts at -1, so that first next() will return the first element
32+
index: -1,
33+
gpu,
34+
}
35+
}
36+
37+
pub fn get_benchmark_settings(&self) -> String {
38+
// both hevc and h264 perform best at main (kinda, h264 it doesn't matter much)
39+
// hevc and h264 both share the same high fps with the same settings, even without profile_tier for hevc
40+
let profile = "main";
41+
return format!("-usage ultralowlatency -quality speed -profile:v {} -rc cbr -cbr true -gpu {}", profile, self.gpu);
42+
}
43+
44+
fn has_next(&self) -> bool {
45+
return self.index != (self.permutations.len() - 1) as i32;
46+
}
47+
}
48+
49+
fn get_amf_profile_tiers(hevc: bool) -> Vec<&'static str> {
50+
if hevc {
51+
return vec!["main", "high"];
52+
}
53+
54+
// there are no amf profile tiers for h264
55+
return vec![];
56+
}
57+
58+
fn get_amf_usages() -> Vec<&'static str> {
59+
return vec!["transcoding", "ultralowlatency", "lowlatency", "webcam"];
60+
}
61+
62+
fn get_amf_quality() -> Vec<&'static str> {
63+
return vec!["balanced", "speed", "quality"];
64+
}
65+
66+
#[derive(Copy, Clone)]
67+
struct AmfSettings {
68+
usage: &'static str,
69+
quality: &'static str,
70+
profile: &'static str,
71+
profile_tier: &'static str,
72+
rate_control: &'static str,
73+
gpu: u8,
74+
}
75+
76+
impl AmfSettings {
77+
fn to_string(&self) -> String {
78+
let mut args = String::new();
79+
args.push_str("-usage ");
80+
args.push_str(self.usage);
81+
args.push_str(" -quality ");
82+
args.push_str(self.quality);
83+
args.push_str(" -profile:v ");
84+
args.push_str(self.profile);
85+
86+
if !self.profile_tier.is_empty() {
87+
args.push_str(" -profile_tier ");
88+
args.push_str(self.profile_tier);
89+
}
90+
91+
args.push_str(" -rc ");
92+
args.push_str(self.rate_control);
93+
// always set this to constant bit rate to ensure reliable stream
94+
args.push_str(" -cbr true");
95+
args.push_str(" -gpu ");
96+
args.push_str(self.gpu.to_string().as_str());
97+
98+
return args;
99+
}
100+
}
101+
102+
impl Iterator for Amf {
103+
type Item = (usize, String);
104+
105+
// maybe we can pull this code out
106+
fn next(&mut self) -> Option<Self::Item> {
107+
if !self.has_next() {
108+
return None;
109+
}
110+
111+
self.index += 1;
112+
113+
let usize_index = self.index as usize;
114+
return Option::from((usize_index as usize, self.permutations.get(usize_index).unwrap().to_string()));
115+
}
116+
}
117+
118+
impl Permute for Amf {
119+
fn init(&mut self) -> &Vec<String> {
120+
// reset index, otherwise we won't be able to iterate at all
121+
self.index = -1;
122+
123+
// clear the vectors if there were entries before
124+
self.permutations.clear();
125+
126+
let mut permutations = if self.profile_tiers.is_empty() { vec![&self.usages, &self.qualities, &self.profiles, &self.rate_controls] } else {
127+
vec![&self.usages, &self.qualities, &self.profiles, &self.profile_tiers, &self.rate_controls]
128+
}
129+
.into_iter().multi_cartesian_product();
130+
131+
loop {
132+
let perm = permutations.next();
133+
if perm.is_none() {
134+
break;
135+
}
136+
137+
let unwrapped_perm = perm.unwrap();
138+
let profile_tier = if !self.profile_tiers.is_empty() { unwrapped_perm.get(3).unwrap() } else { "" };
139+
let rc_index = if !self.profile_tiers.is_empty() { 4 } else { 3 };
140+
let settings = AmfSettings {
141+
usage: unwrapped_perm.get(0).unwrap(),
142+
quality: unwrapped_perm.get(1).unwrap(),
143+
profile: unwrapped_perm.get(2).unwrap(),
144+
profile_tier,
145+
rate_control: unwrapped_perm.get(rc_index).unwrap(),
146+
gpu: self.gpu,
147+
};
148+
149+
self.permutations.push(settings.to_string());
150+
}
151+
152+
return &self.permutations;
153+
}
154+
155+
fn run_standard_only(&mut self) -> &Vec<String> {
156+
// reset index, otherwise we won't be able to iterate at all
157+
self.index = -1;
158+
159+
// clear the vectors if there were entries before
160+
self.permutations.clear();
161+
162+
// note: this only works when hevc/h264 both use just 1 profile, if we add more this will break
163+
self.permutations.push(String::from(self.get_benchmark_settings()));
164+
return &self.permutations;
165+
}
166+
167+
168+
fn get_resolution_to_bitrate_map(fps: u32) -> HashMap<String, u32> {
169+
let mut map: HashMap<String, u32> = HashMap::new();
170+
171+
// bitrates are within 5Mb/s of each other, using higher one
172+
// note: these are the 60fps bitrate values
173+
let mut bitrates: [u32; 4] = [20, 35, 50, 85];
174+
175+
// 120 fps is effectively double the bitrate
176+
if fps == 120 {
177+
bitrates.iter_mut().for_each(|b| *b = *b * 2);
178+
}
179+
180+
map_res_to_bitrate(&mut map, bitrates);
181+
182+
return map;
183+
}
184+
}

codecs/src/lib.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use crate::vendor::Vendor;
2+
3+
pub mod nvenc;
4+
pub mod amf;
5+
pub mod permute;
6+
mod resolutions;
7+
pub mod vendor;
8+
9+
10+
pub fn get_vendor_for_codec(codec: &String) -> Vendor {
11+
if codec.contains("nvenc") {
12+
return Vendor::Nvidia;
13+
} else if codec.contains("amf") {
14+
return Vendor::AMD;
15+
}
16+
17+
return Vendor::Unknown;
18+
}

0 commit comments

Comments
 (0)