feat(client): AV1 is advertised on hardware, not on a decoder existing
The standing open item M7 was meant to close. `decodable_codecs` answered the AV1 bit from `ffmpeg::decoder::find(AV1)`, which says yes on every build that links libdav1d — a software decoder. So the client told the host "send me AV1" on machines that would then decode a 4K stream on the CPU, and codec negotiation happens once at Welcome, so there is nothing to fall back to afterwards. A promise the client cannot keep is worse than not making it. `av1_hardware_decodable` answers from device facts only: the presenter's Vulkan device listing DECODE_AV1 among its decode queue family's codec operations, or — on Windows — the D3D11 import path, which is the same gate the D3D11VA rung sits behind and that rung decodes AV1 Profile 0 today. VAAPI is deliberately not consulted: asking libva costs opening a display, and this is called too early and too often for that. The Vulkan bit covers the Mesa devices where VAAPI AV1 exists in practice, and a machine with VAAPI AV1 but no Vulkan AV1 loses the advertisement, not a working path. The test pins what the gate must not accept: a device that decodes H.264 and H.265 but lists no AV1 operation, and a device whose caps word claims AV1 while it has no decode queue at all. Gates: macOS fmt/clippy, container clippy -D warnings over six crates, 805 tests, workspace check.
This commit is contained in:
@@ -809,6 +809,11 @@ const VIDEO_CODEC_OP_DECODE_H264: u32 = 0x0000_0001;
|
||||
/// would only invite a gate that admits a session nothing can decode.)
|
||||
const VIDEO_CODEC_OP_DECODE_H265: u32 = 0x0000_0002;
|
||||
|
||||
/// `VK_VIDEO_CODEC_OPERATION_DECODE_AV1_BIT_KHR`. The Deck's VanGogh advertises
|
||||
/// it alongside H.264/H.265/VP9, and it is what
|
||||
/// [`av1_hardware_decodable`] reads.
|
||||
const VIDEO_CODEC_OP_DECODE_AV1: u32 = 0x0000_0004;
|
||||
|
||||
/// The native decoder for a negotiated wire codec, plus the
|
||||
/// `VkVideoCodecOperationFlagBitsKHR` the presenter's decode family must advertise
|
||||
/// for it — or `None` for a codec pf-vkdecode cannot decode natively.
|
||||
@@ -990,6 +995,9 @@ pub(crate) unsafe fn codec_name(codec: *const ffmpeg::ffi::AVCodec) -> String {
|
||||
|
||||
/// The `quic` codec bitfield this client can decode — whatever FFmpeg has a decoder for (HEVC/H.264
|
||||
/// always; AV1 when built in). Advertised to the host so it never emits a codec we can't decode.
|
||||
///
|
||||
/// ⚠ **AV1 here is a decoder EXISTING, not a decoder that can keep up.** Use
|
||||
/// [`decodable_codecs_for`], which gates it on hardware — see [`av1_hardware_decodable`].
|
||||
pub fn decodable_codecs() -> u8 {
|
||||
let _ = ffmpeg::init();
|
||||
let mut bits = 0u8;
|
||||
@@ -1005,12 +1013,56 @@ pub fn decodable_codecs() -> u8 {
|
||||
bits
|
||||
}
|
||||
|
||||
/// Can this machine decode AV1 in HARDWARE?
|
||||
///
|
||||
/// The question exists because `ffmpeg::decoder::find(AV1)` answers yes on every
|
||||
/// build that links libdav1d — a SOFTWARE decoder — so advertising AV1 off that
|
||||
/// answer tells the host "send me AV1" on machines that will then try to decode a
|
||||
/// 4K stream on the CPU. That is the standing open item M7 closes: the wire's codec
|
||||
/// negotiation is a promise about capability, and a promise the client cannot keep
|
||||
/// is worse than not making it, because the host has no other codec to fall back to
|
||||
/// once the session is running.
|
||||
///
|
||||
/// Answered from device facts only, never from a decoder registry:
|
||||
///
|
||||
/// * the presenter's Vulkan device advertises `DECODE_AV1` in its decode queue
|
||||
/// family's codec operations, or
|
||||
/// * (Windows) the presenter can import D3D11 textures, which is the same gate the
|
||||
/// D3D11VA rung itself sits behind — that rung decodes AV1 Profile 0 today
|
||||
/// (`video_d3d11.rs`'s profile table), so a machine reaching it has hardware AV1.
|
||||
///
|
||||
/// ⚠ Deliberately NOT consulted: VAAPI. Asking libva costs opening a display, which
|
||||
/// this function is called too early and too often to do; the Vulkan bit covers the
|
||||
/// Mesa devices where VAAPI AV1 exists in practice, and a machine with VAAPI AV1 but
|
||||
/// no Vulkan AV1 loses only the ADVERTISEMENT, not a working path.
|
||||
pub fn av1_hardware_decodable(vk: Option<&VulkanDecodeDevice>) -> bool {
|
||||
if vk.is_some_and(|v| v.video_decode && v.decode_video_caps & VIDEO_CODEC_OP_DECODE_AV1 != 0) {
|
||||
return true;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
return vk.is_some_and(|v| v.d3d11_import);
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
false
|
||||
}
|
||||
|
||||
/// [`decodable_codecs`] plus the PyroWave bit when the presenter's device passed the
|
||||
/// compute-feature probe. Advertisement-only: `resolve_codec` never auto-picks PyroWave —
|
||||
/// the session must also name it `preferred_codec` (plan §3), which the client does only
|
||||
/// under its explicit opt-in.
|
||||
pub fn decodable_codecs_for(vk: Option<&VulkanDecodeDevice>) -> u8 {
|
||||
let bits = decodable_codecs();
|
||||
let mut bits = decodable_codecs();
|
||||
// AV1 is hardware-gated (M7). Without this the bit rides on libdav1d's mere
|
||||
// presence and the host is told to send AV1 to a machine that would decode it on
|
||||
// the CPU — and once the session is negotiated there is nothing to fall back to.
|
||||
if bits & punktfunk_core::quic::CODEC_AV1 != 0 && !av1_hardware_decodable(vk) {
|
||||
tracing::info!(
|
||||
"AV1 not advertised: no hardware AV1 decode on this device (a software \
|
||||
decoder exists, but a 4K AV1 stream is not survivable on it)"
|
||||
);
|
||||
bits &= !punktfunk_core::quic::CODEC_AV1;
|
||||
}
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
if vk.map(|v| v.pyrowave_decode).unwrap_or(false) {
|
||||
return bits | punktfunk_core::quic::CODEC_PYROWAVE;
|
||||
@@ -2150,6 +2202,42 @@ mod tests {
|
||||
/// decision and again by M3 WP-2's HEVC wiring): the pin AND the auto family
|
||||
/// admit on a capable H.264 or HEVC session — native sits immediately above
|
||||
/// FFmpeg-Vulkan because the program is dropping FFmpeg — while every explicit
|
||||
/// AV1 is advertised on a HARDWARE fact, never on a decoder existing.
|
||||
///
|
||||
/// The standing open item M7 closes. `ffmpeg::decoder::find(AV1)` says yes
|
||||
/// wherever libdav1d is linked, so the old advertisement told the host "send me
|
||||
/// AV1" on machines that would then decode it on the CPU — and codec negotiation
|
||||
/// happens once, so there is no falling back afterwards.
|
||||
#[test]
|
||||
fn av1_is_advertised_only_where_hardware_can_decode_it() {
|
||||
// No device at all: no claim.
|
||||
assert!(!av1_hardware_decodable(None));
|
||||
|
||||
// A decode-capable device that does NOT list AV1 among its codec
|
||||
// operations. `video_decode` alone is not the question — plenty of devices
|
||||
// decode H.264 and H.265 and no AV1.
|
||||
let mut dev = decode_device(0x10de, "no-av1");
|
||||
dev.decode_video_caps = VIDEO_CODEC_OP_DECODE_H264 | VIDEO_CODEC_OP_DECODE_H265;
|
||||
#[cfg(not(windows))]
|
||||
assert!(
|
||||
!av1_hardware_decodable(Some(&dev)),
|
||||
"H.264+H.265 decode support says nothing about AV1"
|
||||
);
|
||||
|
||||
// The AV1 operation bit is the yes.
|
||||
let mut dev = decode_device(0x1002, "vangogh-ish");
|
||||
dev.decode_video_caps =
|
||||
VIDEO_CODEC_OP_DECODE_H264 | VIDEO_CODEC_OP_DECODE_H265 | VIDEO_CODEC_OP_DECODE_AV1;
|
||||
assert!(av1_hardware_decodable(Some(&dev)));
|
||||
|
||||
// A device whose decode queue is absent cannot be taken at its caps word.
|
||||
let mut dev = decode_device(0x1002, "no-decode-queue");
|
||||
dev.decode_video_caps = VIDEO_CODEC_OP_DECODE_AV1;
|
||||
dev.video_decode = false;
|
||||
#[cfg(not(windows))]
|
||||
assert!(!av1_hardware_decodable(Some(&dev)));
|
||||
}
|
||||
|
||||
/// backend pin refuses (`vulkan` names the FFmpeg-Vulkan backend specifically and
|
||||
/// must keep meaning exactly that), and the codec/device legs still refuse for
|
||||
/// every choice. The codec's OWN caps bit is the device leg: admitting HEVC on an
|
||||
|
||||
Reference in New Issue
Block a user