Use frame-budgeted VBV for Muninn RUDP video
This commit is contained in:
parent
487578f8e8
commit
5cecf2effb
2 changed files with 79 additions and 16 deletions
|
|
@ -56,8 +56,7 @@ const MUNINN_COMMAND_RUDP_CONNECTION_ID: u32 = 0x6d75_0002;
|
|||
const MUNINN_MEDIA_RUDP_CONNECTION_ID: u32 = 0x6d75_0001;
|
||||
const MUNINN_MEDIA_SEND_QUEUE_DEADLINE_MS: u64 = 75;
|
||||
const MUNINN_RUDP_MEDIA_PROFILE_ID: &str = "muninn.rudp.low_latency_h264_lan.v1";
|
||||
const MUNINN_RUDP_MEDIA_VIDEO_BITRATE: &str = "12000k";
|
||||
const MUNINN_RUDP_MEDIA_VIDEO_BUFFER: &str = "6000k";
|
||||
const MUNINN_RUDP_MEDIA_VIDEO_BITRATE_KBPS: u32 = 12_000;
|
||||
const MUNINN_RUDP_MEDIA_RECEIVER_ASSEMBLY_DEADLINE_MS: u64 = 75;
|
||||
const MUNINN_RUDP_MEDIA_RECEIVER_GAP_WAIT_MS: u64 = 8;
|
||||
const MUNINN_MEDIA_RECEIVER_KEYFRAME_RESTART_DELAY_MS: u64 = 100;
|
||||
|
|
@ -141,10 +140,9 @@ struct MuninnRudpMediaProfile {
|
|||
video_encoder: &'static str,
|
||||
video_preset: &'static str,
|
||||
video_tune: &'static str,
|
||||
video_bitrate: &'static str,
|
||||
video_maxrate: &'static str,
|
||||
video_bufsize: &'static str,
|
||||
video_bitrate_kbps: u32,
|
||||
video_b_frames: u8,
|
||||
video_rc_lookahead: u8,
|
||||
sender_queue_deadline_ms: u64,
|
||||
receiver_assembly_deadline_ms: u64,
|
||||
receiver_gap_wait_ms: u64,
|
||||
|
|
@ -1803,10 +1801,13 @@ fn publish_runtime_boundary_records(
|
|||
"encoder": media_profile.video_encoder,
|
||||
"encoder_preset": media_profile.video_preset,
|
||||
"encoder_tune": media_profile.video_tune,
|
||||
"video_bitrate": media_profile.video_bitrate,
|
||||
"video_maxrate": media_profile.video_maxrate,
|
||||
"video_bufsize": media_profile.video_bufsize,
|
||||
"video_bitrate_kbps": media_profile.video_bitrate_kbps,
|
||||
"video_bitrate": muninn_rudp_video_bitrate_arg(&media_profile),
|
||||
"video_maxrate": muninn_rudp_video_bitrate_arg(&media_profile),
|
||||
"video_bufsize": muninn_rudp_video_vbv_buffer_arg(options, &media_profile),
|
||||
"video_b_frames": media_profile.video_b_frames,
|
||||
"video_rate_control": "cbr",
|
||||
"video_rc_lookahead": media_profile.video_rc_lookahead,
|
||||
"sender_queue_deadline_ms": media_profile.sender_queue_deadline_ms,
|
||||
"receiver_assembly_deadline_ms": media_profile.receiver_assembly_deadline_ms,
|
||||
"receiver_gap_wait_ms": media_profile.receiver_gap_wait_ms,
|
||||
|
|
@ -5034,16 +5035,25 @@ fn muninn_rudp_media_profile() -> MuninnRudpMediaProfile {
|
|||
video_encoder: "h264_nvenc",
|
||||
video_preset: "p1",
|
||||
video_tune: "ull",
|
||||
video_bitrate: MUNINN_RUDP_MEDIA_VIDEO_BITRATE,
|
||||
video_maxrate: MUNINN_RUDP_MEDIA_VIDEO_BITRATE,
|
||||
video_bufsize: MUNINN_RUDP_MEDIA_VIDEO_BUFFER,
|
||||
video_bitrate_kbps: MUNINN_RUDP_MEDIA_VIDEO_BITRATE_KBPS,
|
||||
video_b_frames: 0,
|
||||
video_rc_lookahead: 0,
|
||||
sender_queue_deadline_ms: MUNINN_MEDIA_SEND_QUEUE_DEADLINE_MS,
|
||||
receiver_assembly_deadline_ms: MUNINN_RUDP_MEDIA_RECEIVER_ASSEMBLY_DEADLINE_MS,
|
||||
receiver_gap_wait_ms: MUNINN_RUDP_MEDIA_RECEIVER_GAP_WAIT_MS,
|
||||
}
|
||||
}
|
||||
|
||||
fn muninn_rudp_video_bitrate_arg(profile: &MuninnRudpMediaProfile) -> String {
|
||||
format!("{}k", profile.video_bitrate_kbps)
|
||||
}
|
||||
|
||||
fn muninn_rudp_video_vbv_buffer_arg(options: &Options, profile: &MuninnRudpMediaProfile) -> String {
|
||||
let framerate = options.framerate.max(1);
|
||||
let frame_budget_kbits = profile.video_bitrate_kbps.div_ceil(framerate).max(1);
|
||||
format!("{frame_budget_kbits}k")
|
||||
}
|
||||
|
||||
fn loopback_args(options: &Options) -> Vec<String> {
|
||||
vec![
|
||||
"-NoProfile".to_string(),
|
||||
|
|
@ -5096,12 +5106,16 @@ fn rudp_video_ffmpeg_args(options: &Options) -> Vec<String> {
|
|||
profile.video_b_frames.to_string(),
|
||||
"-delay".to_string(),
|
||||
"0".to_string(),
|
||||
"-rc".to_string(),
|
||||
"cbr".to_string(),
|
||||
"-rc-lookahead".to_string(),
|
||||
profile.video_rc_lookahead.to_string(),
|
||||
"-b:v".to_string(),
|
||||
profile.video_bitrate.to_string(),
|
||||
muninn_rudp_video_bitrate_arg(&profile),
|
||||
"-maxrate".to_string(),
|
||||
profile.video_maxrate.to_string(),
|
||||
muninn_rudp_video_bitrate_arg(&profile),
|
||||
"-bufsize".to_string(),
|
||||
profile.video_bufsize.to_string(),
|
||||
muninn_rudp_video_vbv_buffer_arg(options, &profile),
|
||||
"-g".to_string(),
|
||||
options.framerate.max(1).to_string(),
|
||||
"-forced-idr".to_string(),
|
||||
|
|
@ -5995,6 +6009,26 @@ mod tests {
|
|||
args.windows(2)
|
||||
.any(|pair| pair[0] == "-bf" && pair[1] == "0")
|
||||
);
|
||||
assert!(
|
||||
args.windows(2)
|
||||
.any(|pair| pair[0] == "-rc" && pair[1] == "cbr")
|
||||
);
|
||||
assert!(
|
||||
args.windows(2)
|
||||
.any(|pair| pair[0] == "-rc-lookahead" && pair[1] == "0")
|
||||
);
|
||||
assert!(
|
||||
args.windows(2)
|
||||
.any(|pair| pair[0] == "-b:v" && pair[1] == "12000k")
|
||||
);
|
||||
assert!(
|
||||
args.windows(2)
|
||||
.any(|pair| pair[0] == "-maxrate" && pair[1] == "12000k")
|
||||
);
|
||||
assert!(
|
||||
args.windows(2)
|
||||
.any(|pair| pair[0] == "-bufsize" && pair[1] == "200k")
|
||||
);
|
||||
assert!(
|
||||
args.windows(2)
|
||||
.any(|pair| pair[0] == "-g" && pair[1] == "60")
|
||||
|
|
@ -6006,6 +6040,32 @@ mod tests {
|
|||
assert_eq!(args.last().map(String::as_str), Some("pipe:1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rudp_video_vbv_buffer_tracks_single_frame_budget() {
|
||||
let thirty_fps = Options::parse(
|
||||
["activate", "--media-transport", "rudp", "--framerate", "30"]
|
||||
.into_iter()
|
||||
.map(String::from),
|
||||
)
|
||||
.unwrap();
|
||||
let sixty_fps = Options::parse(
|
||||
["activate", "--media-transport", "rudp", "--framerate", "60"]
|
||||
.into_iter()
|
||||
.map(String::from),
|
||||
)
|
||||
.unwrap();
|
||||
let profile = muninn_rudp_media_profile();
|
||||
|
||||
assert_eq!(
|
||||
muninn_rudp_video_vbv_buffer_arg(&thirty_fps, &profile),
|
||||
"400k"
|
||||
);
|
||||
assert_eq!(
|
||||
muninn_rudp_video_vbv_buffer_arg(&sixty_fps, &profile),
|
||||
"200k"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rudp_audio_encoder_outputs_adts_for_packetizer() {
|
||||
let options = Options::parse(
|
||||
|
|
|
|||
|
|
@ -151,9 +151,12 @@ a temporary PCM/AAC compatibility bridge.
|
|||
Recommended first LAN profile:
|
||||
|
||||
- Profile id: `muninn.rudp.low_latency_h264_lan.v1`.
|
||||
- Video: H.264 NVENC, `p1`, ultra-low-latency tune, CBR/VBR high enough that
|
||||
LAN bandwidth is not the constraint, no B-frames, no lookahead, short GOP or
|
||||
- Video: H.264 NVENC, `p1`, ultra-low-latency tune, CBR high enough that LAN
|
||||
bandwidth is not the constraint, no B-frames, no lookahead, short GOP or
|
||||
intra-refresh, periodic IDR/keyframe on feedback pressure.
|
||||
- VBV: derive `bufsize` from roughly one frame of bitrate
|
||||
(`bitrate_kbps / framerate`) instead of a large fixed encoder reservoir.
|
||||
The point is bounded latency, not hoarding compressed video in a nicer hat.
|
||||
- Audio: Opus low-delay when OBS lowering can consume it directly; otherwise a
|
||||
temporary PCM/AAC bridge with separate packet identity and clock.
|
||||
- Transport: payload elementary access units as typed media records, never a
|
||||
|
|
|
|||
Loading…
Reference in a new issue