аудио: убрать потери кадров (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:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user