Skip to content

Commit 6f602a5

Browse files
authored
0.5.0 alpha bugfixes (#9)
Bugfix of Nvidia GPU lookup causing failure on systems w/o an Nvidia GPU or driver, plus verbose & error logging for ffmpeg failures
1 parent 57628f5 commit 6f602a5

8 files changed

Lines changed: 118 additions & 35 deletions

File tree

README.md

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ can:
2323
- help you identify the maximum possible achievable quality at a given bitrate, resolution, and fps for your hardware
2424
- identify maximum capabilities to be applied to OBS Studio, or author's
2525
suggested [Game Streaming Software](#streaming-host--client-software-suggestions) for streaming games anywhere
26-
- identify optimal encoder settings that allow you to squeeze the most quality out of a bitrate limited streaming environment, such as streaming to Twitch or Youtube at low bitrates
26+
- identify optimal encoder settings that allow you to squeeze the most quality out of a bitrate limited streaming
27+
environment, such as streaming to Twitch or Youtube at low bitrates
2728

2829
### The Two Tools
2930

30-
- **benchmark** - one-click pre-configured encoding benchmark that runs on your chosen encoder, useful for a quick-check of your GPU's performance at various resolutions/framerates
31+
- **benchmark** - one-click pre-configured encoding benchmark that runs on your chosen encoder, useful for a quick-check
32+
of your GPU's performance at various resolutions/framerates
3133
- **permutor-cli** - command-line tool to iterate over all possible encoder settings and bitrates to find
3234
encoder limitations, in both performance and quality
3335

@@ -107,9 +109,14 @@ Assuming you have followed the [Installation Setup Requirements](#installation--
107109
benchmark is as simple as:
108110

109111
1) Opening the **benchmark** executable as you would any other program (double-click)
110-
2) Follow the on-screen instructions: select your GPU (if you have more than 1, otherwise it auto-selects your only
111-
card), select your encoder, and whether to run it on all resolutions or just a specific
112-
one
112+
2) Follow the on-screen instructions:
113+
114+
- select your GPU (if you have more than 1, otherwise it auto-selects your only
115+
card)
116+
- select your encoder
117+
- select whether to run the benchmark on all resolutions or just a specific one
118+
- select whether you want to run it in verbose mode for extra logging (useful for error debugging)
119+
113120
3) Wait for the benchmark to finish, which should not take that long
114121

115122
![img.png](docs/benchmark.png)
@@ -228,44 +235,67 @@ your cards for you.
228235

229236
## Applying your Findings
230237

231-
This section details out how to use knowledge you've gained from this tool in software like Sunshine, Moonlight, OBS Studio, and many more.
238+
This section details out how to use knowledge you've gained from this tool in software like Sunshine, Moonlight, OBS
239+
Studio, and many more.
232240

233241
### Updating Encoder Settings in Sunshine
234242

235-
We'll first be discussing how to change encoder settings in Sunshine. Bitrate settings will not be something you can set in Sunshine, but will be something you can change in your Moonlight app on your computer or other streaming device.
243+
We'll first be discussing how to change encoder settings in Sunshine. Bitrate settings will not be something you can set
244+
in Sunshine, but will be something you can change in your Moonlight app on your computer or other streaming device.
236245

237-
As of February 2023, Nvidia is stopping support of it's own home GameStream service bundled with GeForce Experience. Introducing <a href='https://github.com/LizardByte/Sunshine/releases'>Sunshine</a>, the open-source alternative that runs on your gaming rig, and encodes your gameplay footage to be streamed to other devices, like another computer or even your phone. Sunshine, unlike other streaming programs like Nvidia's GameStream, allows you to customize some encoding settings that can often out-perform Nvidia's GameStream program.
246+
As of February 2023, Nvidia is stopping support of it's own home GameStream service bundled with GeForce Experience.
247+
Introducing <a href='https://github.com/LizardByte/Sunshine/releases'>Sunshine</a>, the open-source alternative that
248+
runs on your gaming rig, and encodes your gameplay footage to be streamed to other devices, like another computer or
249+
even your phone. Sunshine, unlike other streaming programs like Nvidia's GameStream, allows you to customize some
250+
encoding settings that can often out-perform Nvidia's GameStream program.
238251

239-
Note: we'll assume that you already have a Sunshine server setup and that you have attached at least one client device. Sunshine sets some encoder settings by default, at the time of writing this, for Nvidia encoders the default preset is `p4`. You can view the currently used encoder settings by going to `youripaddress:47990 -> Web UI -> Configuration -> NVIDIA NVENC Encoder / Intel QuickSync Encoder / AMD AMF Encoder`.
252+
Note: we'll assume that you already have a Sunshine server setup and that you have attached at least one client device.
253+
Sunshine sets some encoder settings by default, at the time of writing this, for Nvidia encoders the default preset
254+
is `p4`. You can view the currently used encoder settings by going
255+
to `youripaddress:47990 -> Web UI -> Configuration -> NVIDIA NVENC Encoder / Intel QuickSync Encoder / AMD AMF Encoder`.
240256

241-
Let's say that using the tools in this project, you identified that of all the possible encoder settings for NVENC_H264 on your 3080, the settings that allowed you to encode 4K@120 were:
257+
Let's say that using the tools in this project, you identified that of all the possible encoder settings for NVENC_H264
258+
on your 3080, the settings that allowed you to encode 4K@120 were:
242259

243260
`-preset p1 -tune ll -profile:v high -rc cbr`
244261

245-
To apply these settings in Sunshine (for Nvidia), go to `Web UI -> Configuration -> NVIDIA NVENC Encoder` and change to the following values in the dropdowns:
262+
To apply these settings in Sunshine (for Nvidia), go to `Web UI -> Configuration -> NVIDIA NVENC Encoder` and change to
263+
the following values in the dropdowns:
246264

247265
```
248266
NVENC Preset: p1 -- fastest (lowest quality)
249267
NVENC Tune: ll -- low latency
250268
NVENC Rate Control: cbr -- constant bitrate
251269
```
252270

253-
You may have noticed that you could not set the profile for the encoder in Sunshine. Sunshine does not expose _all_ encoder settings, but exposes the ones that make the most impact to your encode (most likely Sunshine defaults profile to high). Perhaps in a future update you'll be able to specify more settings but, for now you may be limited.
271+
You may have noticed that you could not set the profile for the encoder in Sunshine. Sunshine does not expose _all_
272+
encoder settings, but exposes the ones that make the most impact to your encode (most likely Sunshine defaults profile
273+
to high). Perhaps in a future update you'll be able to specify more settings but, for now you may be limited.
254274

255-
Once you've saved these settings, Sunshine will now encode your game using your specific settings, enabling you to stream at potentially higher framerates, or framerates with higher 1% lows than before. (Author was not able to get higher than 4K@90 with default settings in Sunshine and Nvidia's GameStreaming service, but with the findings from this tool, is able to get stable 4K@120).
275+
Once you've saved these settings, Sunshine will now encode your game using your specific settings, enabling you to
276+
stream at potentially higher framerates, or framerates with higher 1% lows than before. (Author was not able to get
277+
higher than 4K@90 with default settings in Sunshine and Nvidia's GameStreaming service, but with the findings from this
278+
tool, is able to get stable 4K@120).
256279

257280
### Applying Bitrate Knowledge in Moonlight App
258281

259282
When using Moonlight as your game streaming client, it auto-recommends a bitrate for you to stream at. Most of the time
260283
this is pretty accurate for lower resolutions, however depending on your hardware's capabilities you might be able to
261-
get away with less bitrate than it suggests. Even moreso, some AMD GPU's need way more bitrate than Nvidia cards, so you'll want to know if you'll need much higher bitrates.
284+
get away with less bitrate than it suggests. Even moreso, some AMD GPU's need way more bitrate than Nvidia cards, so
285+
you'll want to know if you'll need much higher bitrates.
262286

263287
For example: Moonlight auto-selects `80Mb/s` for streaming 4K@60 game content. However from our testing, you really only
264-
need `50Mb/s` when encoding using H264_NVENC. Notice that this applies to _nvenc_ encoders on Nvidia GPU's, and may or may not apply for other vendor GPU's, even using the same H264 algorithm.
288+
need `50Mb/s` when encoding using H264_NVENC. Notice that this applies to _nvenc_ encoders on Nvidia GPU's, and may or
289+
may not apply for other vendor GPU's, even using the same H264 algorithm.
265290

266-
After running the tool on a 4K@60 input file, we know we can get a visually lossless streaming experience with just 50Mb/s on our Nvidia GPU. We also know that, if we are attempting to stream our games outside our home network, we know that our cellular connection speeds or wifi speeds should be at least 50Mb/s to get a clean 4K@60. In addition to this, our gaming rig (and home network upload speeds) should also be capable of 50Mb/s.
291+
After running the tool on a 4K@60 input file, we know we can get a visually lossless streaming experience with just
292+
50Mb/s on our Nvidia GPU. We also know that, if we are attempting to stream our games outside our home network, we know
293+
that our cellular connection speeds or wifi speeds should be at least 50Mb/s to get a clean 4K@60. In addition to this,
294+
our gaming rig (and home network upload speeds) should also be capable of 50Mb/s.
267295

268-
The tools here enable you to know whether you can actually stream to where you are, or if you are bitrate limited, encoder hardware limited, or somewhere in-between. It's easier to know if you can stream games to your phone while on cellular data, and know what resolution & framerate to set your stream to given that you are bitrate limited.
296+
The tools here enable you to know whether you can actually stream to where you are, or if you are bitrate limited,
297+
encoder hardware limited, or somewhere in-between. It's easier to know if you can stream games to your phone while on
298+
cellular data, and know what resolution & framerate to set your stream to given that you are bitrate limited.
269299

270300
### Streaming with OBS Studio
271301

@@ -276,7 +306,9 @@ TBD
276306
## Author's Research Findings and Discussion
277307

278308
A _lot_ of research has gone into the development of this tool, and some decisions were made along the way that might
279-
not be obvious to why some conclusions were drawn. This section is also for you if you are interested in some nitty-gritty details of video encoding, from SSD i/o read speed limitations, framerate statistics, and some design decisions of the tool made by the author during development.
309+
not be obvious to why some conclusions were drawn. This section is also for you if you are interested in some
310+
nitty-gritty details of video encoding, from SSD i/o read speed limitations, framerate statistics, and some design
311+
decisions of the tool made by the author during development.
280312

281313
### Streaming Host & Client Software Suggestions
282314

benchmark/src/main.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ fn main() {
5454

5555
permutation.bitrate = bitrate;
5656
permutation.encoder_settings = settings;
57+
permutation.verbose = cli.verbose;
5758
engine.add(permutation);
5859
}
5960

@@ -139,6 +140,20 @@ fn read_user_input(cli: &mut BenchmarkCli, gpus: Vec<String>) {
139140
}
140141
}
141142

143+
loop {
144+
print!("\nRun with verbose mode? [y/n]: ");
145+
let full: String = read!("{}");
146+
if full != "n" && full != "y" {
147+
println!("Invalid input, try again...");
148+
} else {
149+
if full == "y" {
150+
cli.verbose = true;
151+
}
152+
153+
break;
154+
}
155+
}
156+
142157
println!();
143158
}
144159

engine/src/benchmark_engine.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ impl BenchmarkEngine {
3131
let permutation = self.permutations[i].clone();
3232
// benchmark will not log ETA since every encode will be different
3333
log_benchmark_header(i, &self.permutations, calc_time);
34-
self.results.push(run_encode(permutation, &ctrl_channel));
34+
self.results.push(run_encode(permutation.clone(), &ctrl_channel));
3535
calc_time = Option::from(permutation_start_time.elapsed().unwrap());
3636
}
3737

engine/src/engine.rs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,25 +74,48 @@ fn log_header(index: usize, permutations: &Vec<Permutation>, calc_time: Option<D
7474
println!("[{}]", permutation.encoder_settings);
7575
}
7676

77-
pub fn spawn_ffmpeg_child(ffmpeg_args: &FfmpegArgs) -> Child {
78-
return Command::new("ffmpeg")
79-
.args(ffmpeg_args.to_vec())
80-
.stdout(Stdio::null())
81-
.stderr(Stdio::null())
82-
.spawn().expect("Failed to start instance of ffmpeg");
77+
pub fn spawn_ffmpeg_child(ffmpeg_args: &FfmpegArgs, verbose: bool, log_error_output: Option<bool>) -> Child {
78+
// log the full ffmpeg command to be spawned
79+
if verbose {
80+
println!("V: ffmpeg args: {:?}", ffmpeg_args.encoder_args);
81+
let mut cloned = ffmpeg_args.clone();
82+
cloned.set_no_output_for_error();
83+
println!("V: ffmpeg args no network calls (copy this and run locally, minus the quotes): {:?}", cloned.encoder_args);
84+
}
85+
86+
let mut effective_ffmpeg_args = ffmpeg_args.clone();
87+
if log_error_output.is_some() && log_error_output.unwrap() {
88+
effective_ffmpeg_args.set_no_output_for_error();
89+
}
90+
91+
let mut command = Command::new("ffmpeg");
92+
let child = command.args(effective_ffmpeg_args.to_vec());
93+
94+
if log_error_output.is_some() && log_error_output.unwrap() {
95+
child.stdout(Stdio::inherit())
96+
.stderr(Stdio::inherit());
97+
} else {
98+
child.stdout(Stdio::null())
99+
.stderr(Stdio::null());
100+
}
101+
102+
return child.spawn().expect("Failed to start instance of ffmpeg");
83103
}
84104

85105
fn run_overload_benchmark(metadata: &MetaData, ffmpeg_args: &FfmpegArgs, verbose: bool, detect_overload: bool, ctrl_channel: &Result<Receiver<()>, Error>) -> TrialResult {
86-
let mut child = spawn_ffmpeg_child(ffmpeg_args);
106+
let mut child = spawn_ffmpeg_child(ffmpeg_args, verbose, None);
87107
if verbose {
88-
println!("Successfully spawned encoding child")
108+
println!("V: Successfully spawned encoding child");
89109
}
90110

91111
let trial_result = progressbar::watch_encode_progress(metadata.frames, detect_overload, metadata.fps, verbose, ffmpeg_args.stats_period, ctrl_channel);
92112

93113
if trial_result.ffmpeg_error && !was_ctrl_c_received(&ctrl_channel) {
94114
let _ = child.kill();
95-
println!("Ffmpeg encountered an error when attempting to run, double-check that your environment is setup correctly. If so, open an issue in github!");
115+
eprintln!("Ffmpeg encountered an error when attempting to run, double-check that your environment is setup correctly. If so, open an issue in github!");
116+
// spawn the ffmpeg command, with output logged so we can troubleshoot better
117+
// modifying the command just a little bit so that it fails immediately
118+
spawn_ffmpeg_child(&ffmpeg_args, verbose, Option::from(true));
96119
} else if trial_result.was_overloaded && !was_ctrl_c_received(&ctrl_channel) {
97120
let _ = child.kill();
98121
println!("Encoder was overloaded and could not encode the video file in realtime, stopping...");

engine/src/permutation_engine.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ impl PermutationEngine {
6565

6666
if !result.was_overloaded && permutation.check_quality.clone() {
6767
let vmaf_start_time = SystemTime::now();
68-
result.vmaf_score = check_encode_quality(permutation.clone(), &ctrl_channel);
68+
result.vmaf_score = check_encode_quality(permutation.clone(), &ctrl_channel, permutation.verbose);
6969
result.vmaf_calculation_time = vmaf_start_time.elapsed().unwrap().as_secs();
7070

7171
// if this is higher than the target quality, stop at this bitrate during benchmark
@@ -122,7 +122,7 @@ impl PermutationEngine {
122122
}
123123
}
124124

125-
fn check_encode_quality(mut p: Permutation, ctrl_channel: &Result<Receiver<()>, Error>) -> c_float {
125+
fn check_encode_quality(mut p: Permutation, ctrl_channel: &Result<Receiver<()>, Error>, verbose: bool) -> c_float {
126126
let ffmpeg_args = FfmpegArgs::build_ffmpeg_args(p.video_file.clone(), p.encoder.clone(), &p.encoder_settings, p.bitrate.clone());
127127

128128
println!("Calculating vmaf score; might take longer than original encode depending on your CPU...");
@@ -131,21 +131,21 @@ fn check_encode_quality(mut p: Permutation, ctrl_channel: &Result<Receiver<()>,
131131
// first spawn the ffmpeg instance to listen for incoming encode
132132
let vmaf_args = ffmpeg_args.map_to_vmaf(metadata.fps);
133133
if p.verbose {
134-
println!("Vmaf args calculating quality: {}", vmaf_args.to_string());
134+
println!("V: Vmaf args calculating quality: {}", vmaf_args.to_string());
135135
}
136136

137-
let mut vmaf_child = spawn_ffmpeg_child(&vmaf_args);
137+
let mut vmaf_child = spawn_ffmpeg_child(&vmaf_args, verbose, None);
138138

139139
// then spawn the ffmpeg instance to perform the encoding
140140
let mut encoder_args = ffmpeg_args.clone();
141141

142142
encoder_args.output_args = String::from(insert_format_from(TCP_OUTPUT, &ffmpeg_args.encoder));
143143

144144
if p.verbose {
145-
println!("Encoder fmmpeg args sending to vmaf: {}", encoder_args.to_string());
145+
println!("V: Encoder fmmpeg args sending to vmaf: {}", encoder_args.to_string());
146146
}
147147

148-
spawn_ffmpeg_child(&encoder_args);
148+
spawn_ffmpeg_child(&encoder_args, verbose, None);
149149

150150
// not the cleanest way to do this but oh well
151151
progressbar::watch_encode_progress(metadata.frames, false, metadata.fps, false, ffmpeg_args.stats_period, ctrl_channel);

ffmpeg/src/args.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ impl FfmpegArgs {
105105
return output;
106106
}
107107

108+
pub fn set_no_output_for_error(&mut self) {
109+
self.output_args = NO_OUTPUT.to_string();
110+
self.send_progress = false;
111+
}
112+
108113
pub fn to_vec(&self) -> Vec<String> {
109114
return self.to_string().split(" ").map(|s| s.to_string()).collect();
110115
}

gpus/src/lib.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@ use nvml_wrapper::Nvml;
33
pub mod device;
44

55
pub fn get_gpus() -> Vec<String> {
6-
let nvml = Nvml::init().unwrap();
6+
let nvml = match Nvml::init() {
7+
Ok(nvml) => { nvml }
8+
Err(_) => {
9+
println!("Warning: Unable to auto-detect multiple GPU's, falling back to using first GPU or provided one via '-gpu' option if specified");
10+
return Vec::new();
11+
}
12+
};
13+
714
let device_count = nvml.device_count().unwrap();
815

916
let mut list = Vec::new();

permutor-cli/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ fn build_nvenc_setting_permutations(engine: &mut PermutationEngine, cli: &Permut
7575
permutation.verbose = cli.verbose;
7676
permutation.detect_overload = cli.detect_overload;
7777
permutation.allow_duplicates = cli.allow_duplicate_scores;
78+
permutation.verbose = cli.verbose;
7879
engine.add(permutation);
7980

8081
// break out early here to just make 1 permutation

0 commit comments

Comments
 (0)