H264Decoder.outputSize() reported the 320x240 configure() placeholder
until the codec parsed the in-band SPS, so fitVideo sized the view to
1488x1116 (1.33 aspect) and the first rendered frame(s) of 2.4 content
were visibly squished before the real size corrected it ~40ms later.
Report no size until INFO_OUTPUT_FORMAT_CHANGED delivers the real one:
the first frame renders into the fullscreen surface and the correct
letterbox follows immediately. Validated live on the Fairphone 6 (the
placeholder 'video size' line is gone from logcat; 25/25 JVM tests).
Android 16 added NsdManager.checkProtocol(): registerService with protocol
0 threw IllegalArgumentException: Unsupported protocol — silently, since
the old code swallowed the exception into a dead reflection fallback and
start() overwrote the failure status with the Listening line. mDNS never
actually advertised (the Phase 8 session streamed via --peer, masking it).
Also advertise after the listening status so a registration failure stays
visible, and log the exception and the registration success.
Validated live on the Fairphone 6: the desktop --discover lists the phone
and a full --send session decodes (in-band SPS 320x240 -> 2496x1040) and
renders.
- SignalingServer.send() never throws: a broken signaling connection
drops the peer instead of killing the RTP reader thread via requestPli()
- H264Decoder: assign the codec before configure/start (no orphaned
instance on failure); feed() reports input-queue timeouts so the
pipeline requests a keyframe instead of dropping the frame silently
- Park offers that arrive without a surface as pendingOffer and configure
on attachSurface(): a codec configured without a surface can never take
one (setOutputSurface refuses it); the late configure sends a PLI
- Forget destroyed surfaces (onSurfaceTextureDestroyed -> detachSurface)
- Show the status overlay again on later messages
- Accumulate NAL bytes in ByteArrayOutputStreams, not boxed ArrayList<Int>
- Delete the dead NSD reflection fallback (ResolutionListener never
existed; the classic API is present from API 16 through 36)
- Hold decoderLock around all MediaCodec calls (not thread-safe) and make
requestPli thread-safe
- Update RUNBOOK quirks (NSD, setOutputSurface) and MEMORY notes
Phone-as-receiver for the USB-C -> HDMI mirroring use case. Speaks the
existing signaling + RTP wire protocol, so the C++ sender needs no
changes. AGP 9 built-in Kotlin (no separate plugin), no androidx.
- rtp/: RtpHeader, RtpPacket, JitterBuffer (16 pkt / 60 ms, straggler
discard), H264Depacketizer (single-NAL + FU-A, Annex-B out) — JVM tests
- signaling/: newline-JSON server (offer -> answer, PLI, 500 ms rate cap)
- decode/H264Decoder: MediaCodec -> Surface; size from in-band SPS (offer
is 0x0); surface passed to configure(); releaseOutputBuffer(render)
- pipeline/ReceiverPipeline: NSD advertise (API-36 RegistrationListener
+ reflection fallback), UDP RTP, jitter, decode, PLI recovery
- ReceiverActivity: fullscreen TextureView letterboxed by sizing the
view to the video aspect (C2 scales output to the surface)
Validated end-to-end on a Fairphone 6 (API 36): offer/answer, first
frame, letterbox fit, PLI on loss.