diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/GearslipRunner.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/GearslipRunner.kt index 6c27449..91f38a4 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/GearslipRunner.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/GearslipRunner.kt @@ -631,15 +631,11 @@ class GearslipRunner( "configuration_indices=$indices", ) if (maxUnackedFromConfig != null) maxUnacked = maxOf(1, maxUnackedFromConfig) - // Prefer the smallest offered resolution for the first attempt: fewer pixels, - // fewer ways for a first encode to go wrong. val offered = indices.map { it.toInt() } val preferred = vehicleProfile?.resolution?.let { want -> offered.firstOrNull { videoConfigs.getOrNull(it)?.resolutionName == want } } - selectedConfigIndex = preferred ?: offered.minByOrNull { idx -> - videoConfigs.getOrNull(idx)?.pixelCount() ?: Int.MAX_VALUE - } ?: offered.firstOrNull() + selectedConfigIndex = preferred ?: fitToScreen(offered) ?: offered.firstOrNull() log.i("selected config index $selectedConfigIndex from offered $offered") SessionReport.video( "${videoConfigs.getOrNull(selectedConfigIndex ?: -1)?.resolutionName ?: "?"} " + @@ -685,6 +681,20 @@ class GearslipRunner( // -> Data (8-byte big-endian timestamp, then the H.264 access unit) // <- Ack { session_id, ack } - at most max_unacked frames may be outstanding + /** + * The offered config that best fills the car's screen: the smallest whose visible width + * reaches most of the touchscreen's, else the largest there is. Always taking the smallest + * put 800x480 on a 1540x720 Audi screen and stretched it. With no touchscreen size to go + * by, the smallest is still the safe choice. + */ + private fun fitToScreen(offered: List): Int? { + val known = offered.filter { videoConfigs.getOrNull(it)?.pixelSize() != null } + fun pixels(idx: Int) = videoConfigs[idx].pixelCount() + if (touchWidth <= 0) return known.minByOrNull(::pixels) + val wide = known.filter { (videoConfigs[it].visibleSize()?.first ?: 0) >= touchWidth * 8 / 10 } + return wide.minByOrNull(::pixels) ?: known.maxByOrNull(::pixels) + } + private fun startVideoSource() { lastKeyframeSentAt = System.currentTimeMillis() // frame #1 is always a keyframe val index = selectedConfigIndex ?: 0 @@ -702,6 +712,13 @@ class GearslipRunner( Protobuf.varintField(2, index.toLong()) send(MSG_MEDIA_START, start, encrypted = true, channel = videoChannelId) log.i("-> Start(session_id=$SESSION_ID, configuration_index=$index -> $config)") + // The car crops its margins off the frame, so keep the UI out of them unless a vehicle + // profile has its own tuned insets. + if (vehicleProfile?.insets.let { it == null || it == Insets.NONE }) { + val insets = config.marginInsets() + CarEnvironment.setInsets(insets) + if (insets != Insets.NONE) log.i("keeping the UI clear of the car's margins: $insets") + } val useProjection = projection != null videoSource = VideoSource( diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/ServiceDiscovery.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/ServiceDiscovery.kt index 84c61b3..dfd20bb 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/ServiceDiscovery.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/ServiceDiscovery.kt @@ -20,8 +20,8 @@ object ServiceDiscovery { /** * [widthMargin]/[heightMargin] are the head unit's own numbers (VideoConfiguration fields 3 - * and 4): pixels of the frame it does not treat as usable UI. Logged, and available to a - * vehicle profile, but not applied automatically - what they mean varies by unit. + * and 4): pixels of the frame it crops away, split evenly between the two edges, to fit the + * frame to a screen of a different shape. A matched vehicle profile's insets override them. */ class VideoConfig( val resolution: Int, @@ -38,6 +38,15 @@ object ServiceDiscovery { fun pixelCount(): Int = pixelSize()?.let { it.first * it.second } ?: Int.MAX_VALUE + /** What's left on the car's screen once the margins are cropped off. */ + fun visibleSize(): Pair? = pixelSize()?.let { (w, h) -> (w - widthMargin) to (h - heightMargin) } + + /** The margins as the strips of the frame the car UI keeps clear. */ + fun marginInsets(): Insets = Insets( + top = heightMargin / 2, bottom = heightMargin - heightMargin / 2, + left = widthMargin / 2, right = widthMargin - widthMargin / 2, + ) + override fun toString() = "$resolutionName @ $frameRateName, density=$density, margin=${widthMargin}x$heightMargin" }