aa27bbe3e9
Codec negotiation (M2 polish):
- ServerCodecModeSupport now advertises what we encode: H264|HEVC|AV1_MAIN8
= 65793 (flags verified against moonlight-common-c Limelight.h). The old
placeholder 3843 wrongly claimed HEVC Main10 + 4:4:4 and no AV1. Main10
bits stay off on purpose: Moonlight ties 10-bit to HDR, and capture is
8-bit SDR BGRx with no HDR metadata path (av1_nvenc -highbitdepth was
validated working for later).
- RTSP ANNOUNCE: bitStreamFormat 0/1/2 -> H264/HEVC/AV1 (already plumbed to
av1_nvenc; validated e2e via `m0 --codec av1` + ffprobe av01), and a
dynamicRangeMode!=0 request now logs + falls back to 8-bit SDR.
Surround audio (M2 polish):
- ANNOUNCE x-nv-audio.surround.{numChannels,AudioQuality} +
x-nv-aqos.packetDuration -> per-session AudioParams; DESCRIBE advertises
all six Opus configs (normal before HQ per channel count). Normal-quality
mappings are pre-rotated for the client's GFE-order LFE swap
(RtspConnection.c, verified verbatim) so its derived decoder mapping
equals our encoder mapping — including 7.1, where Sunshine's rotate only
covers [3,6) and scrambles LFE/SL/SR.
- 5.1/7.1 encode via libopus multistream (audiopus_sys, the sys layer the
opus crate already links) with Sunshine's layouts/bitrates, RAII wrapper;
the live-validated stereo wire is byte-identical (plain Opus, no FEC).
- Surround sessions add Sunshine-style RS(4,2) audio FEC (packetType 127 +
AUDIO_FEC_HEADER, the OpenFEC parity matrix both ends hardcode, nanors
gemm semantics verified from nanors/rs.c).
- PipeWire capture generalized to the negotiated channel count with explicit
FL FR FC LFE RL RR [SL SR] positions; missing sink channels are zero-
filled by the channel-mixer. PwAudioCapturer now tears down cleanly on
Drop (pipewire channel -> loop quit), so a channel-count change can
reopen without leaking a capture stream.
Tests: serverinfo mask, RTSP codec/audio param parsing, DESCRIBE contents,
surround-params strings + client-swap round trip, FEC parity self-recovery
and packet layout, real-codec 5.1 channel-identity round trip, and an
ignored live test (ran green against a 6ch null sink monitor).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
80 lines
2.8 KiB
Rust
80 lines
2.8 KiB
Rust
//! The `/serverinfo` capability/status XML Moonlight GETs before pairing and each launch.
|
|
|
|
use super::{Host, APP_VERSION, GFE_VERSION, SERVER_CODEC_MODE_SUPPORT};
|
|
|
|
/// Build the `<root status_code="200">…</root>` serverinfo document. `https` selects the
|
|
/// paired-HTTPS variant (real MAC). Element names are case-sensitive and match what
|
|
/// moonlight-common-c parses.
|
|
pub fn serverinfo_xml(host: &Host, https: bool) -> String {
|
|
// MAC is hidden over plain HTTP; PairStatus reflects the pairing store once the HTTPS
|
|
// path carries per-client identity (a hardening follow-up — 0 for now).
|
|
let mac = if https {
|
|
"01:02:03:04:05:06"
|
|
} else {
|
|
"00:00:00:00:00:00"
|
|
};
|
|
// Over the mutual-TLS HTTPS port the peer is an authenticated (paired) client.
|
|
let pair_status = u8::from(https);
|
|
format!(
|
|
r#"<?xml version="1.0" encoding="utf-8"?>
|
|
<root status_code="200">
|
|
<hostname>{hostname}</hostname>
|
|
<appversion>{APP_VERSION}</appversion>
|
|
<GfeVersion>{GFE_VERSION}</GfeVersion>
|
|
<uniqueid>{uniqueid}</uniqueid>
|
|
<HttpsPort>{https_port}</HttpsPort>
|
|
<ExternalPort>{http_port}</ExternalPort>
|
|
<MaxLumaPixelsHEVC>1869449984</MaxLumaPixelsHEVC>
|
|
<mac>{mac}</mac>
|
|
<LocalIP>{local_ip}</LocalIP>
|
|
<ServerCodecModeSupport>{SERVER_CODEC_MODE_SUPPORT}</ServerCodecModeSupport>
|
|
<PairStatus>{pair_status}</PairStatus>
|
|
<currentgame>0</currentgame>
|
|
<state>SUNSHINE_SERVER_FREE</state>
|
|
</root>
|
|
"#,
|
|
hostname = host.hostname,
|
|
uniqueid = host.uniqueid,
|
|
https_port = host.https_port,
|
|
http_port = host.http_port,
|
|
local_ip = host.local_ip,
|
|
)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::gamestream::{SCM_AV1_MAIN8, SCM_H264, SCM_HEVC, SCM_HEVC_MAIN10};
|
|
|
|
/// The advertised codec mask: H.264 + HEVC + AV1 Main8 (= 65793), and explicitly *no*
|
|
/// 10-bit bits — Moonlight gates its HDR mode on those, which we can't deliver (8-bit
|
|
/// SDR capture). Flag values are moonlight-common-c `Limelight.h`.
|
|
#[test]
|
|
fn codec_mode_support_mask() {
|
|
assert_eq!(SERVER_CODEC_MODE_SUPPORT, 0x1 | 0x100 | 0x10000);
|
|
assert_eq!(SERVER_CODEC_MODE_SUPPORT, 65793);
|
|
assert_eq!(
|
|
SERVER_CODEC_MODE_SUPPORT & SCM_HEVC_MAIN10,
|
|
0,
|
|
"no 10-bit/HDR claim"
|
|
);
|
|
assert_eq!(
|
|
SERVER_CODEC_MODE_SUPPORT,
|
|
SCM_H264 | SCM_HEVC | SCM_AV1_MAIN8
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn serverinfo_xml_carries_codec_mask() {
|
|
let host = Host {
|
|
hostname: "test".into(),
|
|
uniqueid: "uid".into(),
|
|
local_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST),
|
|
http_port: 47989,
|
|
https_port: 47984,
|
|
};
|
|
let xml = serverinfo_xml(&host, false);
|
|
assert!(xml.contains("<ServerCodecModeSupport>65793</ServerCodecModeSupport>"));
|
|
}
|
|
}
|