feat(audio): end-to-end 5.1/7.1 surround across the native path + all clients
apple / swift (push) Failing after 10s
release / apple (push) Failing after 7s
apple / screenshots (push) Has been skipped
audit / cargo-audit (push) Failing after 1m19s
windows-host / package (push) Failing after 2m44s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Failing after 39s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Failing after 39s
windows / build (aarch64-pc-windows-msvc) (push) Failing after 45s
android / android (push) Successful in 5m17s
windows / build (x86_64-pc-windows-msvc) (push) Failing after 45s
ci / web (push) Successful in 57s
ci / docs-site (push) Successful in 56s
ci / rust (push) Successful in 9m19s
ci / bench (push) Successful in 4m40s
decky / build-publish (push) Successful in 26s
deb / build-publish (push) Successful in 2m57s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 33s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 2m56s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m35s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m20s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 53s
flatpak / build-publish (push) Successful in 4m22s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m51s
docker / deploy-docs (push) Successful in 21s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m50s
apple / swift (push) Failing after 10s
release / apple (push) Failing after 7s
apple / screenshots (push) Has been skipped
audit / cargo-audit (push) Failing after 1m19s
windows-host / package (push) Failing after 2m44s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Failing after 39s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Failing after 39s
windows / build (aarch64-pc-windows-msvc) (push) Failing after 45s
android / android (push) Successful in 5m17s
windows / build (x86_64-pc-windows-msvc) (push) Failing after 45s
ci / web (push) Successful in 57s
ci / docs-site (push) Successful in 56s
ci / rust (push) Successful in 9m19s
ci / bench (push) Successful in 4m40s
decky / build-publish (push) Successful in 26s
deb / build-publish (push) Successful in 2m57s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 33s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 2m56s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m35s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m20s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 53s
flatpak / build-publish (push) Successful in 4m22s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m51s
docker / deploy-docs (push) Successful in 21s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m50s
Adds negotiated 5.1/7.1 surround to the punktfunk/1 protocol and every client (previously stereo-only): - core: new shared `audio` layout table (LAYOUT_51/71 + identity multistream mapping, canonical wire order FL FR FC LFE RL RR SL SR); Hello/Welcome `audio_channels` negotiation via the trailing-byte back-compat pattern (old peers fall back to stereo); C-ABI `punktfunk_connect_ex6`, `punktfunk_connection_audio_channels`, and in-core multistream decode `punktfunk_connection_next_audio_pcm` for embedders without a multistream Opus decoder. Real-libopus channel-identity round-trip test. - host: native audio thread captures + Opus-(multi)stream-encodes at the negotiated count (with a cross-session cached-capturer channel-mismatch fix); GameStream surround unified onto the safe `opus::MSEncoder`, dropping `audiopus_sys` (~4 unsafe blocks) and un-gating Windows GameStream surround; WASAPI loopback capture relaxed to 2/6/8 with the correct dwChannelMask. - clients: Linux (PipeWire), Windows (WASAPI), Android (AAudio) decode via `opus::MSDecoder` + render multichannel; Apple decodes in-core to PCM → AVAudioEngine with an explicit wire-order channel layout; each gains a Stereo/5.1/7.1 setting. `punktfunk-probe --audio-channels N` is the headless validator. Verified on Linux: core/host/linux/probe test suites + the Android Rust (cargo-ndk) build, clippy -D warnings, and rustfmt all green. Windows/Apple builds, all on-glass checks, and the live native loopback are pending (CI / a free box). Also lands the concurrent in-tree HEVC 4:4:4 host work (PUNKTFUNK_444): it shares the same touched files (quic.rs, punktfunk1.rs, encode/*, ...) and so cannot be committed separately from the surround changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,11 @@ pub struct OutputFormat {
|
||||
/// HDR: the capturer converts to 10-bit (IDD-push FP16 → `Rgb10a2`; the DDA secure-desktop HDR hint).
|
||||
/// `false` = 8-bit SDR.
|
||||
pub hdr: bool,
|
||||
/// Full-chroma 4:4:4 session: the capturer must keep full chroma — deliver packed **RGB**
|
||||
/// (`Bgra` / `Rgb10a2`), NOT the subsampled `Nv12`/`P010` the Windows video-engine path produces by
|
||||
/// default — because 4:4:4 can only be recovered from a full-chroma source. NVENC then does the
|
||||
/// RGB→YUV444 CSC at encode (chroma_format_idc=3). `false` on every 4:2:0 session.
|
||||
pub chroma_444: bool,
|
||||
}
|
||||
|
||||
impl OutputFormat {
|
||||
@@ -73,6 +78,8 @@ impl OutputFormat {
|
||||
OutputFormat {
|
||||
gpu: gpu_encode(),
|
||||
hdr,
|
||||
// The GameStream + spike paths are always 4:2:0 (4:4:4 is punktfunk/1-native only).
|
||||
chroma_444: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -361,13 +368,16 @@ pub fn open_portal_monitor() -> Result<Box<dyn Capturer>> {
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn capture_virtual_output(
|
||||
vout: crate::vdisplay::VirtualOutput,
|
||||
_want: OutputFormat,
|
||||
want: OutputFormat,
|
||||
_capture: crate::session_plan::CaptureBackend,
|
||||
) -> Result<Box<dyn Capturer>> {
|
||||
// The Linux host stays 8-bit (HDR is blocked upstream) and the portal negotiates its own format, so
|
||||
// the `OutputFormat` is unused here; the capture backend is always the portal (the `CaptureBackend`
|
||||
// arg is a Windows-only dispatch — ignored here).
|
||||
linux::PortalCapturer::from_virtual_output(vout).map(|c| Box::new(c) as Box<dyn Capturer>)
|
||||
// The Linux host stays 8-bit (HDR is blocked upstream) and the portal negotiates its own pixel
|
||||
// format, so only `want.gpu` is honored here: it gates GPU zero-copy capture (the capture backend
|
||||
// is always the portal — the `CaptureBackend` arg is a Windows-only dispatch). `gpu = false`
|
||||
// (a 4:4:4 NVENC session) forces the CPU mmap path so the encoder gets CPU-resident RGB to swscale
|
||||
// into YUV444P — otherwise it would receive CUDA frames and bail.
|
||||
linux::PortalCapturer::from_virtual_output(vout, want.gpu)
|
||||
.map(|c| Box::new(c) as Box<dyn Capturer>)
|
||||
}
|
||||
|
||||
/// `PUNKTFUNK_NO_WGC=1` forces the pure single-process DDA (Desktop Duplication) path everywhere: it
|
||||
@@ -394,6 +404,14 @@ pub fn capture_virtual_output(
|
||||
})?;
|
||||
let pref = vout.preferred_mode;
|
||||
let keep = vout.keepalive;
|
||||
// Full-chroma 4:4:4 needs a full-chroma RGB source. The IDD-push and WGC paths emit subsampled
|
||||
// NV12/P010 by default, which can't reconstruct 4:4:4; route a 4:4:4 session to DDA, which delivers
|
||||
// RGB (Bgra) when its `chroma_444` flag is set. (IDD-push/WGC 4:4:4 capture is a follow-up.)
|
||||
if want.chroma_444 && capture != CaptureBackend::Dda {
|
||||
tracing::info!("4:4:4 session — using DDA capture (RGB source) instead of {capture:?}");
|
||||
return dxgi::DuplCapturer::open(target, pref, keep, want.gpu, false, want.chroma_444)
|
||||
.map(|c| Box::new(c) as Box<dyn Capturer>);
|
||||
}
|
||||
// P2 direct frame push (kill DDA): consume frames straight from the pf-vdisplay driver's shared
|
||||
// ring — no Desktop Duplication, no win32u reparenting hook. Resolved once in the `SessionPlan`
|
||||
// (was re-derived from `config().idd_push` here); `IddPush` takes the keepalive (owns the virtual
|
||||
@@ -414,8 +432,15 @@ pub fn capture_virtual_output(
|
||||
error = %format!("{e:#}"),
|
||||
"IDD-push open/attach failed — falling back to DDA"
|
||||
);
|
||||
return dxgi::DuplCapturer::open(target, pref, keep, want.gpu, false)
|
||||
.map(|c| Box::new(c) as Box<dyn Capturer>);
|
||||
return dxgi::DuplCapturer::open(
|
||||
target,
|
||||
pref,
|
||||
keep,
|
||||
want.gpu,
|
||||
false,
|
||||
want.chroma_444,
|
||||
)
|
||||
.map(|c| Box::new(c) as Box<dyn Capturer>);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -426,7 +451,7 @@ pub fn capture_virtual_output(
|
||||
// chosen backend (it owns the SudoVDA keepalive), so there's no open-time auto-fallback. The
|
||||
// backend choice (`dda`/`dxgi`/`PUNKTFUNK_NO_WGC` → DDA, else WGC) is now resolved once in the plan.
|
||||
if capture == CaptureBackend::Dda {
|
||||
return dxgi::DuplCapturer::open(target, pref, keep, want.gpu, false)
|
||||
return dxgi::DuplCapturer::open(target, pref, keep, want.gpu, false, want.chroma_444)
|
||||
.map(|c| Box::new(c) as Box<dyn Capturer>);
|
||||
}
|
||||
// WGC default, with a watchdog'd DDA fallback. WGC's Direct3D11CaptureFramePool::CreateFreeThreaded
|
||||
@@ -461,12 +486,12 @@ pub fn capture_virtual_output(
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
tracing::warn!(error = %format!("{e:#}"), "WGC open failed — falling back to DDA");
|
||||
dxgi::DuplCapturer::open(target, pref, keep, want.gpu, false)
|
||||
dxgi::DuplCapturer::open(target, pref, keep, want.gpu, false, want.chroma_444)
|
||||
.map(|c| Box::new(c) as Box<dyn Capturer>)
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::warn!("WGC open timed out (CreateFreeThreaded hang on the virtual display) — falling back to DDA");
|
||||
dxgi::DuplCapturer::open(target, pref, keep, want.gpu, false)
|
||||
dxgi::DuplCapturer::open(target, pref, keep, want.gpu, false, want.chroma_444)
|
||||
.map(|c| Box::new(c) as Box<dyn Capturer>)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user