4 Commits

Author SHA1 Message Date
fegger 4f93c7cd20 fix(android): don't letterbox against the configure placeholder
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).
2026-09-10 12:36:15 +02:00
fegger 9a24933203 fix(android): pass PROTOCOL_DNS_SD so mDNS registration works
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.
2026-09-10 12:35:00 +02:00
fegger c6722d164b fix(android): harden the receiver after the review pass
- 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
2026-09-10 12:19:21 +02:00
fegger 455ba6055d feat(android): native receiver app (Kotlin, minSdk 30)
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.
2026-09-10 11:31:13 +02:00