feat: adaptive quality engine + codec indicator UI
Some checks failed
Mirror to GitHub / mirror (push) Failing after 38s
Build Release Binaries / build-amd64 (push) Failing after 2m17s

Wire AdaptiveQualityController into Android engine for auto codec
switching based on network quality reports. Add color-coded TX/RX
codec badges to the in-call screen showing active codecs and Auto mode.

- Recv task: ingest QualityReports, feed to controller, signal profile
  changes via AtomicU8 to send task
- Send task: check for pending profile switch at frame boundaries,
  update encoder/FEC/frame size
- Track peer codec from incoming packet headers
- Kotlin UI: codec badges (blue=studio, green=good, amber=degraded,
  red=catastrophic) with Auto tag
- Add .taskmaster to .gitignore

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-04-08 10:19:11 +04:00
parent f4cc3b1a6b
commit 0abecf7fd8
6 changed files with 214 additions and 12 deletions

View File

@@ -33,6 +33,12 @@ data class CallStats(
val fecRecovered: Long = 0,
/** Current mic audio level (RMS, 0-32767). */
val audioLevel: Int = 0,
/** Our current outgoing codec (e.g. "Opus24k"). */
val currentCodec: String = "",
/** Last seen incoming codec from peers. */
val peerCodec: String = "",
/** Whether auto quality mode is active. */
val autoMode: Boolean = false,
/** Number of participants in the room. */
val roomParticipantCount: Int = 0,
/** Participants in the room (fingerprint + optional alias). */
@@ -76,6 +82,9 @@ data class CallStats(
underruns = obj.optLong("underruns", 0),
fecRecovered = obj.optLong("fec_recovered", 0),
audioLevel = obj.optInt("audio_level", 0),
currentCodec = obj.optString("current_codec", ""),
peerCodec = obj.optString("peer_codec", ""),
autoMode = obj.optBoolean("auto_mode", false),
roomParticipantCount = obj.optInt("room_participant_count", 0),
roomParticipants = parseParticipants(obj.optJSONArray("room_participants"))
)

View File

@@ -463,7 +463,51 @@ fun InCallScreen(
Spacer(modifier = Modifier.height(12.dp))
// Stats
// Codec + Stats
if (stats.currentCodec.isNotEmpty()) {
val codecLabel = formatCodecName(stats.currentCodec)
val peerLabel = if (stats.peerCodec.isNotEmpty()) formatCodecName(stats.peerCodec) else null
val autoTag = if (stats.autoMode) " [Auto]" else ""
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
// Our codec badge
Surface(
shape = RoundedCornerShape(4.dp),
color = codecColor(stats.currentCodec)
) {
Text(
text = "TX $codecLabel$autoTag",
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
style = MaterialTheme.typography.labelSmall.copy(
fontFamily = FontFamily.Monospace,
fontSize = 10.sp
),
color = Color.White
)
}
if (peerLabel != null) {
Spacer(modifier = Modifier.width(6.dp))
Surface(
shape = RoundedCornerShape(4.dp),
color = codecColor(stats.peerCodec)
) {
Text(
text = "RX $peerLabel",
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
style = MaterialTheme.typography.labelSmall.copy(
fontFamily = FontFamily.Monospace,
fontSize = 10.sp
),
color = Color.White
)
}
}
}
Spacer(modifier = Modifier.height(4.dp))
}
Text(
text = "TX: ${stats.framesEncoded} | RX: ${stats.framesDecoded}",
style = MaterialTheme.typography.labelSmall.copy(fontFamily = FontFamily.Monospace),
@@ -825,3 +869,25 @@ private fun DebugReportCard(
}
}
}
/** Map Rust CodecId debug name to a human-readable label. */
private fun formatCodecName(codecId: String): String = when (codecId) {
"Opus64k" -> "Opus 64k"
"Opus48k" -> "Opus 48k"
"Opus32k" -> "Opus 32k"
"Opus24k" -> "Opus 24k"
"Opus16k" -> "Opus 16k"
"Opus6k" -> "Opus 6k"
"Codec2_3200" -> "C2 3.2k"
"Codec2_1200" -> "C2 1.2k"
else -> codecId
}
/** Color-code codec badges by quality tier. */
private fun codecColor(codecId: String): Color = when (codecId) {
"Opus64k", "Opus48k", "Opus32k" -> Color(0xFF0D6EFD) // blue — studio
"Opus24k", "Opus16k" -> Color(0xFF198754) // green — good
"Opus6k" -> Color(0xFFCC8800) // amber — degraded
"Codec2_3200", "Codec2_1200" -> Color(0xFFDC3545) // red — catastrophic
else -> Color(0xFF6C757D) // gray
}