аудио: убрать потери кадров (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)
|
applyAudioMode(msg.mode)
|
||||||
}
|
}
|
||||||
is ScreenControl -> {
|
is ScreenControl -> {
|
||||||
Log.i(TAG, "ScreenControl: sleep=${msg.sleep}")
|
val wantOn = !msg.sleep
|
||||||
controller.setScreen(msg.sleep)
|
val isOn = currentScreenOn()
|
||||||
// Экран переключается чуть позже инжекта — сообщаем телефону актуальное состояние.
|
Log.i(TAG, "ScreenControl: sleep=${msg.sleep} (сейчас on=$isOn)")
|
||||||
lifecycleScope.launch {
|
if (isOn == wantOn) {
|
||||||
kotlinx.coroutines.delay(600)
|
// POWER — переключатель; если цель уже достигнута, инжектить нельзя (моргнём).
|
||||||
pushScreenState()
|
lifecycleScope.launch { pushScreenState() }
|
||||||
|
} else {
|
||||||
|
controller.setScreen(msg.sleep)
|
||||||
|
// Экран переключается чуть позже инжекта — сообщаем телефону актуальное состояние.
|
||||||
|
lifecycleScope.launch {
|
||||||
|
kotlinx.coroutines.delay(600)
|
||||||
|
pushScreenState()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is SetText -> {
|
is SetText -> {
|
||||||
@@ -319,15 +326,26 @@ class VmService : LifecycleService() {
|
|||||||
private fun startAudioPump() {
|
private fun startAudioPump() {
|
||||||
lifecycleScope.launch(kotlinx.coroutines.Dispatchers.IO) {
|
lifecycleScope.launch(kotlinx.coroutines.Dispatchers.IO) {
|
||||||
val packet = ByteArray(AudioConfig.MAX_DATAGRAM)
|
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) {
|
for (pcm in audioFrames) {
|
||||||
val server = audioServer ?: continue
|
val server = audioServer ?: continue
|
||||||
if (audioSessions.isEmpty()) continue
|
if (audioSessions.isEmpty()) continue
|
||||||
|
val t0 = System.nanoTime()
|
||||||
val payload = try {
|
val payload = try {
|
||||||
audioEncoder.encode(pcm, 0, AudioConfig.FRAME_SAMPLES)
|
audioEncoder.encode(pcm, 0, AudioConfig.FRAME_SAMPLES)
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
Log.w(TAG, "opus encode failed: ${t.message}")
|
Log.w(TAG, "opus encode failed: ${t.message}")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
val t1 = System.nanoTime()
|
||||||
val len = AudioDatagram.pack(audioSeq++, payload, payload.size, packet)
|
val len = AudioDatagram.pack(audioSeq++, payload, payload.size, packet)
|
||||||
val datagram = packet.copyOf(len)
|
val datagram = packet.copyOf(len)
|
||||||
for (connId in audioSessions) {
|
for (connId in audioSessions) {
|
||||||
@@ -337,10 +355,19 @@ class VmService : LifecycleService() {
|
|||||||
Log.w(TAG, "audio send failed: ${t.message}")
|
Log.w(TAG, "audio send failed: ${t.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val t2 = System.nanoTime()
|
||||||
|
encNanos += t1 - t0
|
||||||
|
sndNanos += t2 - t1
|
||||||
val encoded = audioEncoded.incrementAndGet()
|
val encoded = audioEncoded.incrementAndGet()
|
||||||
if (encoded % 200 == 0) {
|
if (encoded % 200 == 0) {
|
||||||
val captured = audioCaptured.get()
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ navCompose = "2.10.2"
|
|||||||
mercury = "0.1.1-SNAPSHOT"
|
mercury = "0.1.1-SNAPSHOT"
|
||||||
mercuryTransport = "0.1.2-SNAPSHOT"
|
mercuryTransport = "0.1.2-SNAPSHOT"
|
||||||
mercuryBinocular = "0.1.0-SNAPSHOT"
|
mercuryBinocular = "0.1.0-SNAPSHOT"
|
||||||
mercuryShizuku = "0.1.1-SNAPSHOT"
|
mercuryShizuku = "0.1.2-SNAPSHOT"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
# kotlinx
|
# kotlinx
|
||||||
|
|||||||
@@ -26,11 +26,12 @@ object AudioConfig {
|
|||||||
const val BITRATE = 64_000
|
const val BITRATE = 64_000
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Сложность Opus-кодера. Чуть ниже максимума (10): Concentus — JVM-порт, а кодирует
|
* Сложность Opus-кодера. Concentus — JVM-порт, кодирует на очках в реальном времени;
|
||||||
* он на очках в реальном времени; при 8 качество на 64 кбит/с практически то же,
|
* при complexity 8 один 10-мс stereo-кадр не укладывался в 10 мс, кодер не успевал
|
||||||
* зато есть запас по CPU.
|
* (~60 кадров/с при 100 захваченных → 40% кадров терялось в очереди = «хрип»). На 3
|
||||||
|
* запас по CPU большой, а на 64 кбит/с разница в качестве незначительна.
|
||||||
*/
|
*/
|
||||||
const val COMPLEXITY = 8
|
const val COMPLEXITY = 3
|
||||||
|
|
||||||
/** Ожидаемый процент потерь — для настройки inband FEC/PLC Opus. */
|
/** Ожидаемый процент потерь — для настройки inband FEC/PLC Opus. */
|
||||||
const val PACKET_LOSS_PERCENT = 10
|
const val PACKET_LOSS_PERCENT = 10
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ class OpusAudioEncoder(
|
|||||||
this.bitrate = bitrate
|
this.bitrate = bitrate
|
||||||
this.complexity = complexity
|
this.complexity = complexity
|
||||||
useVBR = true
|
useVBR = true
|
||||||
useInbandFEC = true
|
// InbandFEC в Concentus удваивает стоимость кодирования (кодер считает ещё и
|
||||||
|
// резервную копию предыдущего кадра). Потери по UDP у нас ~0, а пропуски seq и так
|
||||||
|
// прячем на декодере (PLC, см. [OpusAudioDecoder.decodeLost]) — FEC только жжёт CPU.
|
||||||
|
useInbandFEC = false
|
||||||
packetLossPercent = AudioConfig.PACKET_LOSS_PERCENT
|
packetLossPercent = AudioConfig.PACKET_LOSS_PERCENT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user