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).
This commit is contained in:
2026-09-10 12:36:15 +02:00
parent 9a24933203
commit 4f93c7cd20
2 changed files with 21 additions and 1 deletions
+12
View File
@@ -330,6 +330,18 @@ list the phone at 192.168.178.29:5005; a 25s `--send --target monitor
Note: with both the Pi and the phone on the LAN, plain `--send` refuses Note: with both the Pi and the phone on the LAN, plain `--send` refuses
(two receivers found) — target the phone with `--peer`. (two receivers found) — target the phone with `--peer`.
Follow-up from the same on-device session — startup squish eliminated:
`H264Decoder.outputSize()` previously reported the 320x240 configure()
placeholder until the codec parsed the SPS, so `fitVideo` sized the
TextureView 1488x1116 (1.33 aspect) and the first rendered frame(s) of
2.4 content were visibly squished. It now returns null until
`INFO_OUTPUT_FORMAT_CHANGED` fires; the first frame renders into the
fullscreen surface and the correct letterbox (2484x1035) follows within
~40ms. Validated live: no placeholder "video size" line in logcat.
Commits: 9a24933 (PROTOCOL_DNS_SD discovery fix + docs), plus the
outputSize fix (see git log).
Still open (accepted, needs a device or a new test dep): Still open (accepted, needs a device or a new test dep):
- Untested on device: `setOutputSurface` mid-session (surface switch) and - Untested on device: `setOutputSurface` mid-session (surface switch) and
@@ -18,6 +18,11 @@ class H264Decoder {
private var codec: MediaCodec? = null private var codec: MediaCodec? = null
private var configured = false private var configured = false
// True once the codec parsed the in-band SPS (INFO_OUTPUT_FORMAT_CHANGED).
// Before that, outputFormat still carries the configure() placeholder and
// must not drive layout — sizing the view to it squishes the first frame(s).
@Volatile
private var realFormatSeen = false
@Volatile @Volatile
private var renderSurface: Surface? = null private var renderSurface: Surface? = null
@@ -57,6 +62,7 @@ class H264Decoder {
throw e throw e
} }
configured = true configured = true
realFormatSeen = false
} }
/** Points the decoder at a (possibly new) render surface. */ /** Points the decoder at a (possibly new) render surface. */
@@ -117,6 +123,7 @@ class H264Decoder {
} }
index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> { index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
// The codec parsed the SPS size; outputFormat is ready. // The codec parsed the SPS size; outputFormat is ready.
realFormatSeen = true
android.util.Log.i( android.util.Log.i(
"H264Decoder", "output format: " + "H264Decoder", "output format: " +
c.outputFormat.getInteger(MediaFormat.KEY_WIDTH) + c.outputFormat.getInteger(MediaFormat.KEY_WIDTH) +
@@ -141,10 +148,11 @@ class H264Decoder {
} }
} }
/** The decoded resolution once the first keyframe has configured the codec. */ /** The decoded resolution, once the first keyframe configured the codec. */
@Synchronized @Synchronized
fun outputSize(): Pair<Int, Int>? { fun outputSize(): Pair<Int, Int>? {
val c = codec ?: return null val c = codec ?: return null
if (!realFormatSeen) return null // still the configure() placeholder
return try { return try {
val format = c.outputFormat val format = c.outputFormat
val width = format.getInteger(MediaFormat.KEY_WIDTH) val width = format.getInteger(MediaFormat.KEY_WIDTH)