mirror of
https://github.com/twinksoftware/Gearslip.git
synced 2026-11-12 16:52:24 +01:00
- Weather from Open-Meteo with a place name, a forecast screen and a nav bar icon; a fresh location fix is requested when the last one is stale - Map controls are solid, one size, and fade out after four seconds; pinch to zoom works because every finger now reaches the app - Map templates draw their content in a floating card; toggle and radio rows show their state, the Back and Pan actions work, and details text and buttons are larger - Home player gains Lyrics and Minimise buttons; minimised, it lives in the nav bar - Media apps that hide their library show what they are and can still be controlled; apps that refuse Gearslip as a host say why - Debug mode switch, session report in the log, and log sharing as a file attachment - Launcher, phone apps, weather and calendar load at startup - Web app opens DuckDuckGo by default - Home screen lists the setup steps; the launcher marks apps tested and working
98 lines
3.8 KiB
Kotlin
98 lines
3.8 KiB
Kotlin
package app.seb3thehacker.gearslip
|
|
|
|
import java.util.concurrent.CountDownLatch
|
|
import java.util.concurrent.TimeUnit
|
|
|
|
/**
|
|
* Proves the H.264 encoder actually produces a bitstream on this device, before a drive.
|
|
*
|
|
* Without this, an unsupported colour format, a rejected Baseline profile, or a null
|
|
* getInputImage() would all present in the car as "the head unit shows nothing" - the same
|
|
* symptom as a protocol fault, and impossible to tell apart from the log. Same reasoning as
|
|
* the TLS self-test: make our own failures loud on the bench.
|
|
*/
|
|
object EncoderSelfTest {
|
|
private val log = GearslipLog.tagged("VIDEO")
|
|
|
|
fun run(width: Int = 800, height: Int = 480, frameRate: Int = 30): Boolean {
|
|
log.i("=== encoder self-test starting (${width}x$height @ ${frameRate}fps) ===")
|
|
|
|
var codecConfig: ByteArray? = null
|
|
var frames = 0
|
|
var sawKeyFrame = false
|
|
var sawResyncKeyFrame = false
|
|
var resyncRequestedAtFrame = -1
|
|
val done = CountDownLatch(1)
|
|
|
|
val source = VideoSource(
|
|
width = width,
|
|
height = height,
|
|
frameRate = frameRate,
|
|
onCodecConfig = { codecConfig = it },
|
|
onFrame = { _, _, keyFrame ->
|
|
frames++
|
|
if (keyFrame) sawKeyFrame = true
|
|
// At frame 5, ask for a resync keyframe - the same call sendVideoFrame makes
|
|
// after a drop - and confirm THIS encoder actually honours it. Trusting an
|
|
// API we have never exercised on this device is exactly the mistake that
|
|
// cost two drives already (the CONTROL message-type bit, the touch scaling).
|
|
if (frames == 5) {
|
|
resyncRequestedAtFrame = frames
|
|
}
|
|
if (resyncRequestedAtFrame in 1 until frames && keyFrame) {
|
|
sawResyncKeyFrame = true
|
|
}
|
|
if (frames >= 12) done.countDown()
|
|
},
|
|
)
|
|
|
|
return try {
|
|
source.start()
|
|
// Give it a couple of frames before requesting resync, matching how it is used
|
|
// for real: the request always follows at least one already-encoded frame.
|
|
Thread {
|
|
while (resyncRequestedAtFrame == -1) Thread.sleep(20)
|
|
source.requestSyncFrame()
|
|
}.apply { isDaemon = true; start() }
|
|
done.await(8, TimeUnit.SECONDS)
|
|
source.stop()
|
|
|
|
val csd = codecConfig
|
|
when {
|
|
csd == null -> {
|
|
log.e("encoder self-test FAILED: no CODEC_CONFIG (SPS/PPS) produced")
|
|
false
|
|
}
|
|
frames == 0 -> {
|
|
log.e("encoder self-test FAILED: no frames produced")
|
|
false
|
|
}
|
|
!sawKeyFrame -> {
|
|
log.e("encoder self-test FAILED: no keyframe - the car needs one first")
|
|
false
|
|
}
|
|
!sawResyncKeyFrame -> {
|
|
log.e(
|
|
"encoder self-test FAILED: requestSyncFrame() produced no keyframe - " +
|
|
"the resync-after-drop fix would silently do nothing on this device",
|
|
)
|
|
false
|
|
}
|
|
else -> {
|
|
log.i(
|
|
"SPS/PPS = ${csd.size} bytes, $frames frames, keyframe present, " +
|
|
"requestSyncFrame() confirmed working",
|
|
)
|
|
log.hex(" csd", csd, limit = 48)
|
|
log.i("=== encoder self-test PASSED ===")
|
|
true
|
|
}
|
|
}
|
|
} catch (t: Throwable) {
|
|
runCatching { source.stop() }
|
|
log.e("encoder self-test FAILED with an exception", t)
|
|
false
|
|
}
|
|
}
|
|
}
|