feat(android): the client advertises multi-slice tolerance per-decoder
The P0 gating branch deliberately left Android off VIDEO_CAP_MULTI_SLICE
("embedder-set decoder truth") — this is that deferred item, and the P2
slice-pipeline prerequisite. VideoDecoders.multiSliceTolerant() probes the
pick for every advertised codec: tolerant unless any pick is an Amlogic
decoder (the 0.17.0 device-rebooting wedge) or uninspectable (a null pick =
platform default → conservatively single-slice). nativeConnect carries the
bit; the JNI ORs it into the Hello's video_caps beside the panel-truth
HDR bits. Phones (Qualcomm/Exynos) advertise it; the Amlogic TV boxes the
gate exists for never will.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -49,7 +49,7 @@ suspend fun connectToHost(
|
||||
host, port, w, h, hz,
|
||||
identity.certPem, identity.privateKeyPem, pinHex,
|
||||
settings.bitrateKbps, settings.compositor, gamepadPref,
|
||||
hdrEnabled, settings.audioChannels,
|
||||
hdrEnabled, VideoDecoders.multiSliceTolerant(), settings.audioChannels,
|
||||
// What this device can decode (H.264|HEVC always, AV1 when a real decoder exists) +
|
||||
// the user's soft codec preference — the host resolves the emitted codec from both.
|
||||
VideoDecoders.decodableCodecBits(), settings.preferredCodec(), timeoutMs,
|
||||
|
||||
@@ -47,6 +47,10 @@ object NativeBridge {
|
||||
compositorPref: Int,
|
||||
gamepadPref: Int,
|
||||
hdrEnabled: Boolean,
|
||||
/** Every decoder this device would use tolerates multi-slice AUs
|
||||
* ([VideoDecoders.multiSliceTolerant]) — advertises `VIDEO_CAP_MULTI_SLICE`; false keeps
|
||||
* the host at single-slice frames (the safe pre-0.17 wire shape). */
|
||||
multiSliceOk: Boolean,
|
||||
audioChannels: Int,
|
||||
/** `quic::CODEC_*` bitfield of codecs this device decodes ([VideoDecoders.decodableCodecBits]);
|
||||
* `0` falls back to H.264|HEVC. The host resolves the emitted codec from this ∩ its GPU. */
|
||||
|
||||
@@ -53,6 +53,28 @@ object VideoDecoders {
|
||||
fun decodableCodecBits(): Int =
|
||||
1 or 2 or (if (pickDecoder("video/av01") != null) 4 else 0)
|
||||
|
||||
/**
|
||||
* Whether EVERY decoder this device would use tolerates multi-slice access units — the
|
||||
* `VIDEO_CAP_MULTI_SLICE` advertisement (decoder truth, per the 0.17.0 field regression:
|
||||
* Amlogic HEVC decoders wedge the whole DEVICE on multi-slice frames — Chromecast with
|
||||
* Google TV, onn 4K — which is why Moonlight requests single-slice for every hardware
|
||||
* decoder). We advertise per-family instead: tolerant unless the pick for ANY advertised
|
||||
* codec is an Amlogic decoder or can't be inspected (a null pick = the platform default —
|
||||
* uninspectable, so conservatively single-slice). Probed once at connect time; the host
|
||||
* defaults to >1 slice only toward clients that set the bit.
|
||||
*/
|
||||
fun multiSliceTolerant(): Boolean {
|
||||
val mimes = buildList {
|
||||
add("video/avc")
|
||||
add("video/hevc")
|
||||
if (decodableCodecBits() and 4 != 0) add("video/av01")
|
||||
}
|
||||
return mimes.all { mime ->
|
||||
val name = pickDecoder(mime)?.name?.lowercase() ?: return@all false
|
||||
!name.startsWith("omx.amlogic") && !name.startsWith("c2.amlogic")
|
||||
}
|
||||
}
|
||||
|
||||
fun pickDecoder(mime: String): DecoderChoice? {
|
||||
if (mime.isEmpty()) return null
|
||||
val infos = runCatching { MediaCodecList(MediaCodecList.REGULAR_CODECS).codecInfos }
|
||||
|
||||
@@ -115,6 +115,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeConnect<'lo
|
||||
compositor_pref: jint,
|
||||
gamepad_pref: jint,
|
||||
hdr_enabled: jboolean,
|
||||
multi_slice_ok: jboolean,
|
||||
audio_channels: jint,
|
||||
video_codecs: jint,
|
||||
preferred_codec: jint,
|
||||
@@ -182,11 +183,19 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeConnect<'lo
|
||||
// sends a proper 8-bit BT.709 stream rather than PQ the panel would mis-tone-map. AMediaCodec
|
||||
// decodes Main10 from the SPS and the decode loop signals the Surface HDR dataspace + static
|
||||
// metadata (see crate::decode).
|
||||
if hdr_enabled != 0 {
|
||||
// 10-bit/HDR by panel truth (above) + multi-slice by DECODER truth: Kotlin probes every
|
||||
// decoder this device would use (`VideoDecoders.multiSliceTolerant` — Amlogic wedges the
|
||||
// whole device on multi-slice AUs, the 0.17.0 field regression) and only then may the
|
||||
// host default to >1 slice per frame (its sub-frame readback / the P2 slice pipeline).
|
||||
(if hdr_enabled != 0 {
|
||||
punktfunk_core::quic::VIDEO_CAP_10BIT | punktfunk_core::quic::VIDEO_CAP_HDR
|
||||
} else {
|
||||
0
|
||||
},
|
||||
}) | (if multi_slice_ok != 0 {
|
||||
punktfunk_core::quic::VIDEO_CAP_MULTI_SLICE
|
||||
} else {
|
||||
0
|
||||
}),
|
||||
// Requested surround layout (2 = stereo / 6 = 5.1 / 8 = 7.1). The host clamps to what it can
|
||||
// capture and echoes the resolved count in `connector.audio_channels`, which drives the
|
||||
// decoder + AAudio layout (read in `crate::audio::AudioPlayback::start`). Anything else
|
||||
|
||||
Reference in New Issue
Block a user