Capture and encoder children are spawned at their own priority class

Windows hands a priority class down to a child only when it is below
normal, so the mux child's above-normal never reached ffmpeg; on a live
stream with the host at 99.8% CPU the encoder ran at normal, 790 video
chunks per 10 s went missing and the mux loop sat a second behind. Raising
the processes in place took that to 116 lost and a 60-chunk backlog with
zero late frames, and the viewer reported every frame arriving. The mux
child and video encoder now run at high, the audio pipeline at
above-normal, from spawn.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-11 03:41:51 +02:00
commit e2c44ac86c

View file

@ -2008,16 +2008,20 @@ fn activate(options: Options) -> Result<()> {
/// A real-time media producer on a host that is also running a game. At
/// normal priority the mux loop is starved whenever the game wants the CPU:
/// measured 2026-09-11 on Raven at 93% total load, video backed up 1.5 s and
/// expired wholesale. Above-normal is what OBS runs at for the same reason.
/// The encoder and capture children inherit the class when spawned after this.
/// expired wholesale. High is what OBS offers for the same reason.
///
/// Windows hands a priority class down to a child only when it is *below*
/// normal, so the capture and encoder children are raised explicitly at
/// spawn (`media_child_priority`). Found the hard way on a live stream: the
/// mux child was above-normal, its encoder was at normal on a host at 99.8%
/// CPU, and 790 chunks per 10 s went missing with a one-second backlog;
/// raising the encoder in place took that to 116 and 60.
#[cfg(windows)]
fn raise_media_process_priority() {
use windows_sys::Win32::System::Threading::{
ABOVE_NORMAL_PRIORITY_CLASS, GetCurrentProcess, SetPriorityClass,
};
use windows_sys::Win32::System::Threading::{GetCurrentProcess, HIGH_PRIORITY_CLASS, SetPriorityClass};
// SAFETY: the current process handle is always valid; SetPriorityClass
// has no memory-safety preconditions.
let raised = unsafe { SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS) };
let raised = unsafe { SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS) };
if raised == 0 {
eprintln!("Muninn could not raise the media process priority; continuing at normal.");
}
@ -2026,6 +2030,30 @@ fn raise_media_process_priority() {
#[cfg(not(windows))]
fn raise_media_process_priority() {}
/// The priority class a capture or encoder child is spawned with. The video
/// encoder shares the mux loop's class; the audio pipeline runs one below so
/// a PCM pump cannot starve the frames.
#[derive(Clone, Copy)]
enum MediaChildPriority {
Video,
Audio,
}
#[cfg(windows)]
fn media_child_priority(command: &mut Command, priority: MediaChildPriority) -> &mut Command {
use std::os::windows::process::CommandExt;
use windows_sys::Win32::System::Threading::{ABOVE_NORMAL_PRIORITY_CLASS, HIGH_PRIORITY_CLASS};
command.creation_flags(match priority {
MediaChildPriority::Video => HIGH_PRIORITY_CLASS,
MediaChildPriority::Audio => ABOVE_NORMAL_PRIORITY_CLASS,
})
}
#[cfg(not(windows))]
fn media_child_priority(command: &mut Command, _priority: MediaChildPriority) -> &mut Command {
command
}
fn activate_rudp(options: Options) -> Result<()> {
raise_media_process_priority();
ensure_state_dirs(&options)?;
@ -2147,7 +2175,7 @@ fn run_rudp_mux_once(
let mut loopback = if options.capture_audio {
Some(
Command::new("powershell.exe")
media_child_priority(&mut Command::new("powershell.exe"), MediaChildPriority::Audio)
.args(loopback_args(options))
.stdout(Stdio::piped())
.stderr(fs::File::create(&loopback_stderr)?)
@ -2175,7 +2203,7 @@ fn run_rudp_mux_once(
let mut video_ffmpeg = if options.capture_video {
Some(
Command::new(&options.ffmpeg_path)
media_child_priority(&mut Command::new(&options.ffmpeg_path), MediaChildPriority::Video)
.args(rudp_video_ffmpeg_args(options))
.stdin(Stdio::null())
.stdout(Stdio::piped())
@ -2198,7 +2226,7 @@ fn run_rudp_mux_once(
};
let mut audio_ffmpeg = if options.capture_audio {
Some(
Command::new(&options.ffmpeg_path)
media_child_priority(&mut Command::new(&options.ffmpeg_path), MediaChildPriority::Audio)
.args(rudp_audio_ffmpeg_args(options))
.stdin(Stdio::piped())
.stdout(Stdio::piped())