Skip to content

fix: send the encoded stream size on the wire, desktop size separately - #64

Merged
tranvuongquocdat merged 1 commit into
tranvuongquocdat:mainfrom
meta-boy:feat/true-stream-size-on-wire
Sep 5, 2026
Merged

tranvuongquocdat merged 1 commit into
tranvuongquocdat:mainfrom
meta-boy:feat/true-stream-size-on-wire

Conversation

@meta-boy

Copy link
Copy Markdown
Contributor

Problem

displayConfig is not only what the client's overlay renders. It is what the client sizes and selects its decoder from:

// VideoDecoder.kt, decoder selection
val supported     = videoCaps.isSizeSupported(width, height)
val rateSupported = videoCaps.areSizeAndRateSupported(width, height, targetRate)

// VideoDecoder.kt, configuration
MediaFormat.createVideoFormat(mime, currentWidth, currentHeight)

With HiDPI on, AppDelegate sends the logical resolution so the overlay matches the Mac's resolution dropdown, while the stream is the doubled frame. Observed on 0.11.2 with HiDPI at 2560x1600:

Sent display config:  2560x1600      <- client sized its decoder from this
Stream configured:    5120x3200      <- client was actually fed this

The client asks "can you decode 2560x1600 at 90fps", is told yes, picks a decoder on that basis, and is handed 5120x3200. The only throughput check that exists on the client runs against a resolution that is never the stream. MediaCodec does adapt from the SPS afterwards, but decoder selection and the capability check have already happened by then.

The intent behind sending the logical size is good and worth keeping: an overlay reading "5120x3200" when the user picked 2560x1600 is confusing. The mistake is carrying it in the field the decoder reads.

Fix

displayConfig always carries the encoded size now. The logical desktop moves to its own message.

Type Direction Payload Meaning
12 client to server none "I read displayConfig as the encoded size and understand type 13"
13 server to client 8 bytes logical desktop size, display only

Type 13 goes only to clients that sent type 12, because older clients disconnect on unknown message types (the same constraint that governs codecSelected). Older hosts consume type 12 as a single unknown byte and carry on. Both directions stay compatible.

The overlay shows the stream size, appending the desktop size only when the two differ:

2304x1440 (desktop 2560x1600)

so a scaled HiDPI desktop is visible rather than quietly standing in for the resolution being sent.

Verification

On device, HiDPI at 1920x1080:

Mac    -> Sent display config:   3840x2160
Mac    -> Sent desktop geometry: 1920x1080
Tablet -> Display config:   3840x2160
Tablet -> Desktop geometry: 1920x1080
Tablet -> setupDecoder: 3840x2160, decoder=c2.exynos.hevc.decoder

The decoder is configured for the frame it will actually receive. Before this change it was configured for 1920x1080 and fed 3840x2160.

Handshake ordering is unchanged: type 12 is advertised alongside types 9 and 11, before type 8, so it lands before finishProtocolStartup runs and the first displayConfig goes out.

macOS builds clean, 35/35 tests pass. Android compiles clean, unit tests pass, ktlint clean.

Note on scope

This removes the unclampedHevc branch in AppDelegate, which existed to keep the logical resolution for unclamped HEVC. With the encoded size always on the wire, that special case has nothing left to do.

Independent of the decode-ceiling work in #62: no shared files, and either can land first. #62 makes the clamp engage more often, which happens to route around this bug in those cases. This PR fixes it outright, including the case #62 does not reach, where the client genuinely can decode the doubled frame and no clamping occurs.

displayConfig is not only what the client's overlay renders. It is what
the client sizes and selects its decoder from:

  val supported     = videoCaps.isSizeSupported(width, height)
  val rateSupported = videoCaps.areSizeAndRateSupported(width, height, rate)
  MediaFormat.createVideoFormat(mime, currentWidth, currentHeight)

With HiDPI on, the host deliberately sent the logical resolution so the
overlay would match the Mac's dropdown, while streaming the doubled
frame. Observed on 0.11.2 with HiDPI at 2560x1600:

  Sent display config:  2560x1600      <- client sized its decoder from this
  Stream configured:    5120x3200      <- client was actually fed this

So the client asked "can you decode 2560x1600 at 90fps", was told yes,
picked a decoder on that basis, and got 5120x3200. The only throughput
check on the client ran against a resolution that was never the stream.
MediaCodec does adapt from the SPS afterwards, but selection and the
capability check have already happened.

displayConfig now always carries the encoded size. The logical desktop
travels in a new message so the overlay can still show it:

  type 12  client -> server, payload-free: "I read displayConfig as the
           encoded size and understand type 13"
  type 13  server -> client, 8-byte payload: logical desktop size

Type 13 is sent only to clients that sent type 12, because older clients
disconnect on unknown message types. Older hosts consume type 12 as one
unknown byte and carry on, so both directions stay compatible.

The overlay shows the stream size, appending the desktop size only when
the two differ, so a scaled HiDPI desktop is visible rather than quietly
standing in for the resolution being sent.

Verified on device with HiDPI at 1920x1080:

  Mac    -> display config:   3840x2160
  Mac    -> desktop geometry: 1920x1080
  Tablet -> setupDecoder: 3840x2160, decoder=c2.exynos.hevc.decoder

The decoder is now configured for the frame it will receive. Before this
it was configured for 1920x1080.
@vercel

vercel Bot commented Aug 28, 2026

Copy link
Copy Markdown

@meta-boy is attempting to deploy a commit to the tranvuongquocdat2-7001's projects Team on Vercel.

A member of the Team first needs to authorize it.

meta-boy added a commit to meta-boy/SideScreen that referenced this pull request Aug 28, 2026
@tranvuongquocdat
tranvuongquocdat merged commit 69756e3 into tranvuongquocdat:main Sep 5, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants