Name Muninn low latency RUDP media profile

This commit is contained in:
Meta Vens 2026-06-18 21:35:19 +01:00
commit 487578f8e8
2 changed files with 84 additions and 19 deletions

View file

@ -55,6 +55,11 @@ const IDUNN_HEALTH_RUDP_CONNECTION_ID: u32 = 0x1d0d_0001;
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_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;
const PS_MOVE_LED_REPORT_LEN: usize = 49;
@ -129,6 +134,22 @@ enum MediaTransport {
Rudp,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct MuninnRudpMediaProfile {
profile_id: &'static str,
video_codec: &'static str,
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_b_frames: u8,
sender_queue_deadline_ms: u64,
receiver_assembly_deadline_ms: u64,
receiver_gap_wait_ms: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct MoveStateSource {
move_id: String,
@ -1209,6 +1230,7 @@ fn run_rudp_mux_once(
"typed video/audio access units are publishing over CultNet RUDP media",
)?;
let media_profile = muninn_rudp_media_profile();
let (payload_tx, payload_rx) = mpsc::channel::<Result<QueuedMuninnMediaSendPayload>>();
let video_sender = video_rudp_payload_reader(
payload_tx.clone(),
@ -1216,7 +1238,7 @@ fn run_rudp_mux_once(
VideoAnnexBStreamSendConfig {
stream_id: options.stream_id.clone(),
session_id: format!("{}:{timestamp}:video", options.host_id),
codec: "h264".to_string(),
codec: media_profile.video_codec.to_string(),
first_frame_id: 0,
first_pts_ticks: 0,
frame_duration_ticks: video_frame_duration_ticks(options)?,
@ -1259,7 +1281,7 @@ fn run_rudp_mux_once(
if media_payload_queue_age_exceeded(
queued.queued_at,
Instant::now(),
Duration::from_millis(MUNINN_MEDIA_SEND_QUEUE_DEADLINE_MS),
Duration::from_millis(media_profile.sender_queue_deadline_ms),
) {
payloads_dropped += 1;
poll_rudp_media_receiver_feedback(&mut transport, &mut receiver_feedback)?;
@ -1710,6 +1732,7 @@ fn publish_runtime_boundary_records(
} else {
vec![activation_command.clone()]
};
let media_profile = muninn_rudp_media_profile();
let command_boundary = MuninnCommandBoundaryCompatRecord {
value: json!({
"schema": "muninn.command_boundary.v1",
@ -1773,6 +1796,22 @@ fn publish_runtime_boundary_records(
"provider_advertisement_schema": "gamecult.eve.provider_advertisement.v1",
"command_boundary_schema": "muninn.command_boundary.v1",
"telemetry_surface_schema": "muninn.telemetry_surface.v1",
"media_profile": {
"profile_id": media_profile.profile_id,
"owner": "Muninn capture runtime",
"codec": media_profile.video_codec,
"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_b_frames": media_profile.video_b_frames,
"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,
"recovery": "receiver feedback may request keyframe; sender restarts low-latency encoder on new keyframe edges"
},
"compatibility_mechanisms": compatibility_mechanisms,
"cut_line": "Muninn's telemetry store owns provider advertisement, command boundary, transport profile, telemetry surface, and daemon health state. Local CLI activation and health commands are compatibility/ops witnesses only.",
"updated_at": updated_at,
@ -4981,11 +5020,30 @@ fn srt_endpoint(host: &str, port: u16) -> String {
}
fn rudp_endpoint(host: &str, port: u16, stream_id: &str) -> String {
let profile = muninn_rudp_media_profile();
format!(
"rudp://{host}:{port}/{stream_id}?channel=media&format=muninn-typed-media&connection=0x{MUNINN_MEDIA_RUDP_CONNECTION_ID:08x}"
"rudp://{host}:{port}/{stream_id}?channel=media&format=muninn-typed-media&connection=0x{MUNINN_MEDIA_RUDP_CONNECTION_ID:08x}&profile={}&assembly_deadline_ms={}&gap_wait_ms={}",
profile.profile_id, profile.receiver_assembly_deadline_ms, profile.receiver_gap_wait_ms
)
}
fn muninn_rudp_media_profile() -> MuninnRudpMediaProfile {
MuninnRudpMediaProfile {
profile_id: MUNINN_RUDP_MEDIA_PROFILE_ID,
video_codec: "h264",
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_b_frames: 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 loopback_args(options: &Options) -> Vec<String> {
vec![
"-NoProfile".to_string(),
@ -5005,6 +5063,7 @@ fn loopback_args(options: &Options) -> Vec<String> {
}
fn rudp_video_ffmpeg_args(options: &Options) -> Vec<String> {
let profile = muninn_rudp_media_profile();
vec![
"-hide_banner".to_string(),
"-loglevel".to_string(),
@ -5026,29 +5085,29 @@ fn rudp_video_ffmpeg_args(options: &Options) -> Vec<String> {
"0:v:0".to_string(),
"-an".to_string(),
"-c:v".to_string(),
"h264_nvenc".to_string(),
profile.video_encoder.to_string(),
"-preset".to_string(),
"p1".to_string(),
profile.video_preset.to_string(),
"-tune".to_string(),
"ull".to_string(),
profile.video_tune.to_string(),
"-zerolatency".to_string(),
"1".to_string(),
"-bf".to_string(),
"0".to_string(),
profile.video_b_frames.to_string(),
"-delay".to_string(),
"0".to_string(),
"-b:v".to_string(),
"12000k".to_string(),
profile.video_bitrate.to_string(),
"-maxrate".to_string(),
"12000k".to_string(),
profile.video_maxrate.to_string(),
"-bufsize".to_string(),
"6000k".to_string(),
profile.video_bufsize.to_string(),
"-g".to_string(),
options.framerate.max(1).to_string(),
"-forced-idr".to_string(),
"1".to_string(),
"-f".to_string(),
"h264".to_string(),
profile.video_codec.to_string(),
"pipe:1".to_string(),
]
}
@ -5905,7 +5964,7 @@ mod tests {
assert_eq!(
plan.targets,
vec![
"rudp://10.77.0.2:5204/muninn.raven.av.rudp?channel=media&format=muninn-typed-media&connection=0x6d750001"
"rudp://10.77.0.2:5204/muninn.raven.av.rudp?channel=media&format=muninn-typed-media&connection=0x6d750001&profile=muninn.rudp.low_latency_h264_lan.v1&assembly_deadline_ms=75&gap_wait_ms=8"
]
);
assert!(!plan.command_line.contains("tee"));
@ -5921,11 +5980,13 @@ mod tests {
.unwrap();
let args = rudp_video_ffmpeg_args(&options);
let profile = muninn_rudp_media_profile();
assert!(
args.windows(2)
.any(|pair| pair[0] == "-f" && pair[1] == "h264")
);
assert_eq!(profile.profile_id, MUNINN_RUDP_MEDIA_PROFILE_ID);
assert!(
args.windows(2)
.any(|pair| pair[0] == "-tune" && pair[1] == "ull")
@ -5982,7 +6043,7 @@ mod tests {
#[test]
fn media_payload_queue_deadline_is_strictly_bounded() {
let queued_at = Instant::now();
let deadline = Duration::from_millis(MUNINN_MEDIA_SEND_QUEUE_DEADLINE_MS);
let deadline = Duration::from_millis(muninn_rudp_media_profile().sender_queue_deadline_ms);
assert!(!media_payload_queue_age_exceeded(
queued_at,

View file

@ -135,10 +135,10 @@ transport that understands frame deadlines.
Shipping order:
1. HEVC NVENC low-latency when Raven encode and OBS decode both prove the
1. H.264 NVENC low-latency as the compatibility baseline for broad decoder
support, simple failure analysis, and OBS bridge stability.
2. HEVC NVENC low-latency when Raven encode and OBS decode both prove the
target latency budget.
2. H.264 NVENC low-latency as the compatibility baseline for broad decoder
support and simple failure analysis.
3. AV1 NVENC only after the exact Raven-to-OBS path proves encode, decode, and
queue latency inside budget. AV1 can be the quality/bandwidth winner, but it
must earn the low-latency lane on our hardware instead of getting promoted by
@ -150,9 +150,10 @@ a temporary PCM/AAC compatibility bridge.
Recommended first LAN profile:
- Video: HEVC or H.264 NVENC, low-latency tune, fast preset, CBR/VBR 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.
- 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
intra-refresh, periodic IDR/keyframe on feedback pressure.
- 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
@ -160,6 +161,9 @@ Recommended first LAN profile:
- Control: sender may adapt bitrate, keyframe cadence, chunk size, and playout
budget from receiver feedback; the receiver must not silently stretch latency
to preserve visual perfection.
- Receiver URL policy: RUDP stream URLs carry `profile`, `assembly_deadline_ms`,
and `gap_wait_ms` so the OBS bridge receives playout budget from Muninn's
stream profile instead of hard-coding receiver policy privately.
### Motion Vectors And Reconstruction