wireless: send about a third of the cable bitrate at 30fps, so the video keeps up and the music stops cutting out

This commit is contained in:
Seb3thehacker committed 2026-10-13 14:31:03 -07:00
1 parent 909c3549d6
commit 5fe59d350c
3 files changed
+20 -3

No files matched your search

@@ -367,6 +367,7 @@ class GearslipActivity : ComponentActivity(), Projection {
identityProvider = { CertProvider.load(this) }, identityProvider = { CertProvider.load(this) },
projection = this, projection = this,
vehicleProfileFor = { info -> VehicleProfiles.find(VehicleProfiles.load(this), info) }, vehicleProfileFor = { info -> VehicleProfiles.find(VehicleProfiles.load(this), info) },
wireless = true,
) )
this.runner = runner this.runner = runner
worker = Thread { runner.run() }.apply { name = "gearslip"; start() } worker = Thread { runner.run() }.apply { name = "gearslip"; start() }
@@ -36,6 +36,8 @@ class GearslipRunner(
private val identityProvider: () -> CertProvider.Identity, private val identityProvider: () -> CertProvider.Identity,
private val projection: Projection? = null, private val projection: Projection? = null,
private val vehicleProfileFor: (ServiceDiscovery.HeadUnitInfo) -> VehicleProfile? = { null }, private val vehicleProfileFor: (ServiceDiscovery.HeadUnitInfo) -> VehicleProfile? = { null },
/** Over Wi-Fi rather than USB: the video is sent smaller so the link keeps up. */
private val wireless: Boolean = false,
) { ) {
private val log = GearslipLog.tagged("PROTO") private val log = GearslipLog.tagged("PROTO")
@@ -943,8 +945,10 @@ class GearslipRunner(
videoSource = VideoSource( videoSource = VideoSource(
width = width, width = width,
height = height, height = height,
frameRate = if (config.frameRate == 1) 60 else 30, // 60fps only over a cable: over Wi-Fi it doubles the data for little the driver sees.
frameRate = if (config.frameRate == 1 && !wireless) 60 else 30,
mode = if (useProjection) VideoSource.Mode.SURFACE else VideoSource.Mode.TEST_CARD, mode = if (useProjection) VideoSource.Mode.SURFACE else VideoSource.Mode.TEST_CARD,
wireless = wireless,
onCodecConfig = { csd -> onCodecConfig = { csd ->
lastCodecConfig = csd lastCodecConfig = csd
send(MSG_MEDIA_CODEC_CONFIG, csd, encrypted = true, channel = videoChannelId) send(MSG_MEDIA_CODEC_CONFIG, csd, encrypted = true, channel = videoChannelId)
@@ -23,6 +23,8 @@ class VideoSource(
private val height: Int, private val height: Int,
private val frameRate: Int, private val frameRate: Int,
private val mode: Mode = Mode.TEST_CARD, private val mode: Mode = Mode.TEST_CARD,
/** Over Wi-Fi: a fraction of the cable bitrate, so the link keeps up and sound isn't starved. */
private val wireless: Boolean = false,
private val onCodecConfig: (ByteArray) -> Unit, private val onCodecConfig: (ByteArray) -> Unit,
private val onFrame: (data: ByteArray, presentationTimeUs: Long, keyFrame: Boolean) -> Unit, private val onFrame: (data: ByteArray, presentationTimeUs: Long, keyFrame: Boolean) -> Unit,
) { ) {
@@ -46,7 +48,9 @@ class VideoSource(
fun start() { fun start() {
val level = levelFor(width, height, frameRate) val level = levelFor(width, height, frameRate)
val bitRate = defaultBitRate(width, height, frameRate, level) val bitRate = defaultBitRate(width, height, frameRate, level).let {
if (wireless) (it * WIRELESS_SHARE).toInt().coerceAtMost(WIRELESS_MAX_BIT_RATE) else it
}
fun format(tuned: Boolean) = MediaFormat.createVideoFormat(MIME, width, height).apply { fun format(tuned: Boolean) = MediaFormat.createVideoFormat(MIME, width, height).apply {
setInteger( setInteger(
MediaFormat.KEY_COLOR_FORMAT, MediaFormat.KEY_COLOR_FORMAT,
@@ -99,7 +103,7 @@ class VideoSource(
codec = encoder codec = encoder
running = true running = true
log.i("encoder started: ${width}x$height @ ${frameRate}fps, ${bitRate / 1000} kbps, H.264 Baseline level ${level.label}, mode=$mode") log.i("encoder started: ${width}x$height @ ${frameRate}fps, ${bitRate / 1000} kbps${if (wireless) " (wireless)" else ""}, H.264 Baseline level ${level.label}, mode=$mode")
worker = Thread { worker = Thread {
runCatching { pump(encoder) } runCatching { pump(encoder) }
@@ -258,6 +262,14 @@ class VideoSource(
val rate = if (frameRate > 30) 1.5 else 1.0 val rate = if (frameRate > 30) 1.5 else 1.0
return (BASE_BIT_RATE * size * rate).toInt().coerceAtMost(minOf(level.maxBitRate, MAX_BIT_RATE)) return (BASE_BIT_RATE * size * rate).toInt().coerceAtMost(minOf(level.maxBitRate, MAX_BIT_RATE))
} }
/**
* A car's Wi-Fi is shared, noisy and far slower than its nominal rate, and the sound rides
* the same link behind the video. Real Android Auto sends a few Mbps wirelessly; at the
* cable rate (15-20 Mbps for 720p) a laptop hotspot fell seconds behind and the music
* cut in and out. 720p30 comes out near 5.4 Mbps.
*/
const val WIRELESS_SHARE = 0.35
const val WIRELESS_MAX_BIT_RATE = 8_000_000
const val I_FRAME_INTERVAL_S = 5 const val I_FRAME_INTERVAL_S = 5
private const val REPEAT_AFTER_US = 100_000L private const val REPEAT_AFTER_US = 100_000L