аудио: убрать потери кадров (Concentus не успевал); экран — POWER

- Opus complexity 8->3, отключён inband FEC: кодер выдавал ~60 кадров/с при 100
  захваченных -> 40% кадров терялось в очереди (DROP_OLDEST) = «хрип».
  Стало enc_us~3мс на 10-мс кадр, dropped перестал расти.
- Прогрев кодера тишиной на старте (JIT Concentus) — убирает стартовый всплеск потерь.
- Диагностика в логе: enc_us / snd_us.
- ScreenControl идемпотентен (POWER — переключатель): сверяемся с реальным isInteractive.
- Зависимость shizuku-vd 0.1.1 -> 0.1.2 (POWER-инъекция).
This commit is contained in:
2026-10-01 12:10:30 +03:00
parent 4743427756
commit 20d0d05fb4
4 changed files with 44 additions and 13 deletions
@@ -246,12 +246,19 @@ class VmService : LifecycleService() {
applyAudioMode(msg.mode)
}
is ScreenControl -> {
Log.i(TAG, "ScreenControl: sleep=${msg.sleep}")
controller.setScreen(msg.sleep)
// Экран переключается чуть позже инжекта — сообщаем телефону актуальное состояние.
lifecycleScope.launch {
kotlinx.coroutines.delay(600)
pushScreenState()
val wantOn = !msg.sleep
val isOn = currentScreenOn()
Log.i(TAG, "ScreenControl: sleep=${msg.sleep} (сейчас on=$isOn)")
if (isOn == wantOn) {
// POWER — переключатель; если цель уже достигнута, инжектить нельзя (моргнём).
lifecycleScope.launch { pushScreenState() }
} else {
controller.setScreen(msg.sleep)
// Экран переключается чуть позже инжекта — сообщаем телефону актуальное состояние.
lifecycleScope.launch {
kotlinx.coroutines.delay(600)
pushScreenState()
}
}
}
is SetText -> {
@@ -319,15 +326,26 @@ class VmService : LifecycleService() {
private fun startAudioPump() {
lifecycleScope.launch(kotlinx.coroutines.Dispatchers.IO) {
val packet = ByteArray(AudioConfig.MAX_DATAGRAM)
// Прогреваем JIT и состояние кодера тишиной: без этого первые кадры кодировались
// в разы дольше 10 мс (Concentus — JVM, холодный профиль), очередь успевала
// переполниться, и стартовые ~секунды звука терялись («хрип при включении»).
runCatching {
val silence = ShortArray(AudioConfig.FRAME_SHORTS)
repeat(30) { audioEncoder.encode(silence, 0, AudioConfig.FRAME_SAMPLES) }
}
var encNanos = 0L
var sndNanos = 0L
for (pcm in audioFrames) {
val server = audioServer ?: continue
if (audioSessions.isEmpty()) continue
val t0 = System.nanoTime()
val payload = try {
audioEncoder.encode(pcm, 0, AudioConfig.FRAME_SAMPLES)
} catch (t: Throwable) {
Log.w(TAG, "opus encode failed: ${t.message}")
continue
}
val t1 = System.nanoTime()
val len = AudioDatagram.pack(audioSeq++, payload, payload.size, packet)
val datagram = packet.copyOf(len)
for (connId in audioSessions) {
@@ -337,10 +355,19 @@ class VmService : LifecycleService() {
Log.w(TAG, "audio send failed: ${t.message}")
}
}
val t2 = System.nanoTime()
encNanos += t1 - t0
sndNanos += t2 - t1
val encoded = audioEncoded.incrementAndGet()
if (encoded % 200 == 0) {
val captured = audioCaptured.get()
Log.i(TAG, "audio: captured=$captured encoded=$encoded dropped=${captured - encoded} sessions=${audioSessions.size}")
Log.i(
TAG,
"audio: captured=$captured encoded=$encoded dropped=${captured - encoded} " +
"sessions=${audioSessions.size} enc_us=${encNanos / 200 / 1000} snd_us=${sndNanos / 200 / 1000}",
)
encNanos = 0
sndNanos = 0
}
}
}