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
This commit is contained in:
2026-09-10 12:19:21 +02:00
parent bbe4f21a3b
commit c6722d164b
7 changed files with 297 additions and 134 deletions
+12 -8
View File
@@ -243,16 +243,20 @@ Platform quirks found while validating (Android 16 / API 36):
- `DatagramSocket.localPort` gives the bound port; `.port` is -1 for
unconnected datagram sockets, and `.localAddress` is an `InetAddress`
(Inet6Address on Android), not an `InetSocketAddress`.
- `MediaCodec`: pass the output Surface to `configure()` (`setOutputSurface`
is illegal after configure); call `start()`; render with
`releaseOutputBuffer(index, render=true)`; the C2 AVC decoder requires a
concrete width/height at configure (use a placeholder the in-band SPS
reconfigures it).
- `MediaCodec`: pass the output Surface to `configure()` — the codec must
start in surface mode (`setOutputSurface` afterwards only switches an
already-surface-mode codec to a new surface; a codec configured without
a surface can never take one, per the API docs); call `start()`; render
with `releaseOutputBuffer(index, render=true)`; the C2 AVC decoder
requires a concrete width/height at configure (use a placeholder — the
in-band SPS reconfigures it).
- `MediaFormat.format()` / `KEY_MIME_TYPE` are not in the API-36 public
surface.
- NSD: API 36 replaced `ResolutionListener` registration with
`registerService(info, flags, RegistrationListener)`; the app falls back
to reflection on older devices.
- NSD: the classic `registerService(info, flags, RegistrationListener)`
API exists from API 16 through 36 (verified with javap on the android-36
SDK jar) — no fallback is needed. A reflection fallback targeting a
pre-36 "ResolutionListener" was removed: that class never existed at
any API level, so the fallback could only ever fail.
- The C2 decoder scales its output to the Surface (`android._video-scaling`)
— letterbox by **sizing the TextureView to the video aspect** (centered
on a black window background), not with a transform matrix (double scale).