diff --git a/crates/punktfunk-host/src/encode.rs b/crates/punktfunk-host/src/encode.rs index 03bf241a..311fab2c 100644 --- a/crates/punktfunk-host/src/encode.rs +++ b/crates/punktfunk-host/src/encode.rs @@ -1033,6 +1033,10 @@ mod nvenc_cuda; #[cfg(all(any(target_os = "linux", target_os = "windows"), feature = "nvenc"))] #[path = "encode/nvenc_status.rs"] mod nvenc_status; +// Shared libavcodec glue (`pixel_to_av`, swscale consts) for the three libav backends — Linux +// NVENC + VAAPI and Windows AMF/QSV — so the byte-identical pieces live once (plan §2.2, Tier 2). +#[cfg(any(target_os = "linux", all(target_os = "windows", feature = "amf-qsv")))] +mod libav; // Software (openh264) H.264 encoder — the GPU-less path on BOTH Windows and Linux (a headless / // GPU-less test box, or a fallback when no hardware encoder is available). Platform-agnostic: it // consumes CPU RGB `CapturedFrame`s and the statically-bundled openh264 build. diff --git a/crates/punktfunk-host/src/encode/libav.rs b/crates/punktfunk-host/src/encode/libav.rs new file mode 100644 index 00000000..a078cb3b --- /dev/null +++ b/crates/punktfunk-host/src/encode/libav.rs @@ -0,0 +1,23 @@ +//! Shared libavcodec (FFmpeg) glue for the three libav encode backends — Linux NVENC +//! (`encode/linux/mod.rs`), VAAPI (`encode/linux/vaapi.rs`), and Windows AMF/QSV +//! (`encode/windows/ffmpeg_win.rs`) — so the byte-identical pieces live once (plan §2.2, the Tier-2 +//! gap). Free functions and consts over borrowed handles; nothing here is per-frame `dyn`, +//! allocating, or on the zero-copy ingest path. +use ffmpeg_next::ffi; // = ffmpeg_sys_next +use ffmpeg_next::format::Pixel; +use std::os::raw::c_int; + +/// swscale: nearest-neighbour scaler flag (`SWS_POINT`). We never rescale (src dims == dst dims), so +/// the resampler choice only governs the colour-conversion path; POINT is the cheapest. +pub(crate) const SWS_POINT: c_int = 0x10; +/// swscale colorspace id for ITU-R BT.709 (`SWS_CS_ITU709`) — the CSC coefficients for our RGB→YUV. +pub(crate) const SWS_CS_ITU709: c_int = 1; +/// swscale colorspace id for ITU-R BT.2020 non-constant-luminance (`SWS_CS_BT2020`) — the CSC +/// coefficients for the HDR X2BGR10→P010 path (Windows only today). +pub(crate) const SWS_CS_BT2020: c_int = 9; + +/// `Pixel` → `AVPixelFormat`. `Pixel` is `#[repr(i32)]`-compatible with `AVPixelFormat` (the bindgen +/// enum) via this documented conversion in ffmpeg-next. +pub(crate) fn pixel_to_av(p: Pixel) -> ffi::AVPixelFormat { + ffi::AVPixelFormat::from(p) +} diff --git a/crates/punktfunk-host/src/encode/linux/mod.rs b/crates/punktfunk-host/src/encode/linux/mod.rs index d495c0be..b4620788 100644 --- a/crates/punktfunk-host/src/encode/linux/mod.rs +++ b/crates/punktfunk-host/src/encode/linux/mod.rs @@ -21,14 +21,9 @@ use ffmpeg_next as ffmpeg; use std::os::raw::c_int; use std::ptr; +use super::libav::{pixel_to_av, SWS_CS_ITU709, SWS_POINT}; use ffmpeg::ffi; // = ffmpeg_sys_next -/// swscale: nearest-neighbour scaler flag (`SWS_POINT`). We never rescale (src dims == dst dims), so -/// the resampler choice only governs the colour-conversion path; POINT is the cheapest. -const SWS_POINT: c_int = 0x10; -/// swscale colorspace id for ITU-R BT.709 (`SWS_CS_ITU709`) — the CSC coefficients for our RGB→YUV. -const SWS_CS_ITU709: c_int = 1; - /// The swscale *source* pixel format for a captured packed RGB/BGR layout (the real byte order, not /// the NVENC-padded `*0` form). Used by the 4:4:4 RGB→YUV444P conversion path. Mirrors the VAAPI /// CPU-input mapping; YUV/10-bit inputs can't feed this path (the 4:4:4 session forces packed RGB). @@ -118,13 +113,6 @@ impl Drop for CudaHw { } } -/// `ffmpeg::format::Pixel` → raw `AVPixelFormat`. -fn pixel_to_av(p: Pixel) -> ffi::AVPixelFormat { - // `Pixel` is `#[repr(i32)]`-compatible with `AVPixelFormat` (the bindgen enum) via this - // documented conversion in ffmpeg-next. - ffi::AVPixelFormat::from(p) -} - /// Map a captured layout to the NVENC input pixel format, and whether a 3→4 byte expand is /// needed (packed RGB/BGR have no padding byte; the NVENC `*0` formats do). fn nvenc_input(format: PixelFormat) -> (Pixel, bool) { diff --git a/crates/punktfunk-host/src/encode/linux/vaapi.rs b/crates/punktfunk-host/src/encode/linux/vaapi.rs index 7a0903b1..94444539 100644 --- a/crates/punktfunk-host/src/encode/linux/vaapi.rs +++ b/crates/punktfunk-host/src/encode/linux/vaapi.rs @@ -34,18 +34,9 @@ use std::os::raw::c_int; use std::ptr; use std::sync::atomic::{AtomicU8, Ordering}; +use super::libav::{pixel_to_av, SWS_CS_ITU709, SWS_POINT}; use ffmpeg::ffi; // = ffmpeg_sys_next -// libswscale scaler-flag + colour-space constants (not exported as Rust consts by the bindings; -// these are the stable `` #defines). No-rescale → POINT is cheapest. -const SWS_POINT: c_int = 0x10; -const SWS_CS_ITU709: c_int = 1; - -/// `ffmpeg::format::Pixel` → raw `AVPixelFormat` (the documented ffmpeg-next conversion). -fn pixel_to_av(p: Pixel) -> ffi::AVPixelFormat { - ffi::AVPixelFormat::from(p) -} - /// `fourcc(a,b,c,d)` — DRM FourCC packing (`a | b<<8 | c<<16 | d<<24`). const fn fourcc(a: u8, b: u8, c: u8, d: u8) -> u32 { (a as u32) | ((b as u32) << 8) | ((c as u32) << 16) | ((d as u32) << 24) diff --git a/crates/punktfunk-host/src/encode/windows/ffmpeg_win.rs b/crates/punktfunk-host/src/encode/windows/ffmpeg_win.rs index 39fd3da7..60c02e57 100644 --- a/crates/punktfunk-host/src/encode/windows/ffmpeg_win.rs +++ b/crates/punktfunk-host/src/encode/windows/ffmpeg_win.rs @@ -60,14 +60,9 @@ use windows::Win32::Graphics::Dxgi::Common::{ DXGI_FORMAT_R10G10B10A2_UNORM, DXGI_SAMPLE_DESC, }; +use super::libav::{pixel_to_av, SWS_CS_BT2020, SWS_CS_ITU709, SWS_POINT}; use ffmpeg::ffi; // = ffmpeg_sys_next -// libswscale scaler-flag + colour-space constants (not exported as Rust consts by the bindings — -// the stable `` #defines, same as the VAAPI path uses). -const SWS_POINT: c_int = 0x10; -const SWS_CS_ITU709: c_int = 1; -const SWS_CS_BT2020: c_int = 9; - /// `AVD3D11VADeviceContext` (libavutil/hwcontext_d3d11va.h) — mirrored (the ffmpeg-sys bindings /// don't allowlist that header). We set `device` to the capturer's `ID3D11Device` so AMF/QSV share /// it; `av_hwdevice_ctx_init` fills `device_context`/`video_device`/`video_context`/the default @@ -149,11 +144,6 @@ fn is_10bit_format(format: PixelFormat, bit_depth: u8) -> bool { bit_depth >= 10 || matches!(format, PixelFormat::P010 | PixelFormat::Rgb10a2) } -/// `ffmpeg::format::Pixel` → raw `AVPixelFormat`. -fn pixel_to_av(p: Pixel) -> ffi::AVPixelFormat { - ffi::AVPixelFormat::from(p) -} - /// Build the FFmpeg encoder context shared by both inner paths: name, mode, low-latency RC, /// infinite GOP, the BT.709-limited (SDR) or BT.2020-PQ (HDR) VUI, the given `pix_fmt`, and the /// optional hw device/frames contexts (null for the system path). Returns the opened encoder.