RX-статистика (потери/джиттер) на главном экране, галочка в скрытых настройках
This commit is contained in:
@@ -4,6 +4,7 @@ import android.app.Activity
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
|
import android.widget.CheckBox
|
||||||
import android.widget.SeekBar
|
import android.widget.SeekBar
|
||||||
import android.widget.Spinner
|
import android.widget.Spinner
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
@@ -78,11 +79,15 @@ class AudioSettingsActivity : Activity() {
|
|||||||
seekRxBuffer.setOnSeekBarChangeListener(listener)
|
seekRxBuffer.setOnSeekBarChangeListener(listener)
|
||||||
seekJitter.setOnSeekBarChangeListener(listener)
|
seekJitter.setOnSeekBarChangeListener(listener)
|
||||||
|
|
||||||
|
val chkRxStats = findViewById<CheckBox>(R.id.chk_rx_stats)
|
||||||
|
chkRxStats.isChecked = Prefs.rxStatsEnabled(this)
|
||||||
|
|
||||||
findViewById<Button>(R.id.btn_audio_save).setOnClickListener {
|
findViewById<Button>(R.id.btn_audio_save).setOnClickListener {
|
||||||
Prefs.setMicGain(this, 1.0f + seekMicGain.progress * 0.1f)
|
Prefs.setMicGain(this, 1.0f + seekMicGain.progress * 0.1f)
|
||||||
Prefs.setRxBufferMs(this, 100 + seekRxBuffer.progress * 20)
|
Prefs.setRxBufferMs(this, 100 + seekRxBuffer.progress * 20)
|
||||||
Prefs.setRxJitterMs(this, seekJitter.progress * 20)
|
Prefs.setRxJitterMs(this, seekJitter.progress * 20)
|
||||||
Prefs.setTxBitrateKbps(this, bitrates[spinBitrate.selectedItemPosition])
|
Prefs.setTxBitrateKbps(this, bitrates[spinBitrate.selectedItemPosition])
|
||||||
|
Prefs.setRxStatsEnabled(this, chkRxStats.isChecked)
|
||||||
Toast.makeText(this, getString(R.string.saved), Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, getString(R.string.saved), Toast.LENGTH_SHORT).show()
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ class MainActivity : Activity(), SvxService.Listener {
|
|||||||
private lateinit var connectZone: View
|
private lateinit var connectZone: View
|
||||||
private lateinit var rxMeter: SMeterView
|
private lateinit var rxMeter: SMeterView
|
||||||
private lateinit var micMeter: SMeterView
|
private lateinit var micMeter: SMeterView
|
||||||
|
private lateinit var lblRxStats: TextView
|
||||||
private lateinit var pttButton: PttButton
|
private lateinit var pttButton: PttButton
|
||||||
private lateinit var btnSettings: ImageButton
|
private lateinit var btnSettings: ImageButton
|
||||||
private lateinit var btnMonitor: ImageButton
|
private lateinit var btnMonitor: ImageButton
|
||||||
@@ -142,6 +143,8 @@ class MainActivity : Activity(), SvxService.Listener {
|
|||||||
rxMeter = findViewById(R.id.rx_meter)
|
rxMeter = findViewById(R.id.rx_meter)
|
||||||
micMeter = findViewById(R.id.mic_meter)
|
micMeter = findViewById(R.id.mic_meter)
|
||||||
micMeter.setColor(0xFFF85149.toInt())
|
micMeter.setColor(0xFFF85149.toInt())
|
||||||
|
lblRxStats = findViewById(R.id.lbl_rx_stats)
|
||||||
|
lblRxStats.visibility = if (Prefs.rxStatsEnabled(this)) View.VISIBLE else View.GONE
|
||||||
pttButton = findViewById(R.id.ptt_button)
|
pttButton = findViewById(R.id.ptt_button)
|
||||||
btnSettings = findViewById(R.id.btn_settings)
|
btnSettings = findViewById(R.id.btn_settings)
|
||||||
btnMonitor = findViewById(R.id.btn_monitor)
|
btnMonitor = findViewById(R.id.btn_monitor)
|
||||||
@@ -498,6 +501,14 @@ class MainActivity : Activity(), SvxService.Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onRxStats(lossPct: Double, jitterMs: Double) {
|
||||||
|
mainHandler.post {
|
||||||
|
if (!Prefs.rxStatsEnabled(this)) return@post
|
||||||
|
lblRxStats.visibility = View.VISIBLE
|
||||||
|
lblRxStats.text = getString(R.string.rx_stats_fmt, lossPct, jitterMs.toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Сбросить таймер молчания: группа жива — продолжаем её слушать. */
|
/** Сбросить таймер молчания: группа жива — продолжаем её слушать. */
|
||||||
private fun resetMonitorIdle() {
|
private fun resetMonitorIdle() {
|
||||||
monitorIdleTask?.let { mainHandler.removeCallbacks(it) }
|
monitorIdleTask?.let { mainHandler.removeCallbacks(it) }
|
||||||
|
|||||||
@@ -31,12 +31,16 @@ class SvxService : Service(), ReflectorClient.Callbacks {
|
|||||||
fun onAudioFlush()
|
fun onAudioFlush()
|
||||||
fun onError(detail: String)
|
fun onError(detail: String)
|
||||||
fun onLevel(source: Int, level: Float)
|
fun onLevel(source: Int, level: Float)
|
||||||
|
fun onRxStats(lossPct: Double, jitterMs: Double)
|
||||||
}
|
}
|
||||||
|
|
||||||
var listener: Listener? = null
|
var listener: Listener? = null
|
||||||
|
|
||||||
private val engine by lazy {
|
private val engine by lazy {
|
||||||
OpusEngine { source, level -> listener?.onLevel(source, level) }
|
OpusEngine(
|
||||||
|
onLevel = { source, level -> listener?.onLevel(source, level) },
|
||||||
|
onRxStats = { lossPct, jitterMs -> listener?.onRxStats(lossPct, jitterMs) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
val client: ReflectorClient by lazy { ReflectorClient(this) }
|
val client: ReflectorClient by lazy { ReflectorClient(this) }
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ import kotlin.math.log10
|
|||||||
* SVXLink: моно, 8 кГц, 16 бит PCM, кадры 20 мс. Поток уровня сигнала 0..1.
|
* SVXLink: моно, 8 кГц, 16 бит PCM, кадры 20 мс. Поток уровня сигнала 0..1.
|
||||||
*/
|
*/
|
||||||
class OpusEngine(
|
class OpusEngine(
|
||||||
private val onLevel: (source: Int, level: Float) -> Unit
|
private val onLevel: (source: Int, level: Float) -> Unit,
|
||||||
|
private val onRxStats: ((lossPct: Double, jitterMs: Double) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -370,6 +371,7 @@ class OpusEngine(
|
|||||||
jitterAccum = 0.0
|
jitterAccum = 0.0
|
||||||
jitterN = 0
|
jitterN = 0
|
||||||
lastEvalTime = now
|
lastEvalTime = now
|
||||||
|
onRxStats?.invoke(lossPct, jitterMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Джиттер-гейт открыт: накопили кадры или вышло 300 мс с первого пакета. */
|
/** Джиттер-гейт открыт: накопили кадры или вышло 300 мс с первого пакета. */
|
||||||
|
|||||||
@@ -170,4 +170,12 @@ object Prefs {
|
|||||||
fun setTxBitrateKbps(context: Context, kbps: Int) {
|
fun setTxBitrateKbps(context: Context, kbps: Int) {
|
||||||
sp(context).edit().putInt("tx_bitrate_kbps", kbps.coerceIn(0, 32)).apply()
|
sp(context).edit().putInt("tx_bitrate_kbps", kbps.coerceIn(0, 32)).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Показывать строку статистики приёма (потери/джиттер) на главном экране. */
|
||||||
|
fun rxStatsEnabled(context: Context): Boolean =
|
||||||
|
sp(context).getBoolean("rx_stats_enabled", false)
|
||||||
|
|
||||||
|
fun setRxStatsEnabled(context: Context, enabled: Boolean) {
|
||||||
|
sp(context).edit().putBoolean("rx_stats_enabled", enabled).apply()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,6 +135,15 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="24dp" />
|
android:layout_marginBottom="24dp" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/chk_rx_stats"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:text="@string/audio_rx_stats"
|
||||||
|
android:textColor="@color/text_primary"
|
||||||
|
android:textSize="15sp" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btn_audio_save"
|
android:id="@+id/btn_audio_save"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -181,6 +181,17 @@
|
|||||||
android:layout_height="52dp"
|
android:layout_height="52dp"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Статистика приёма (скрытые настройки, по умолчанию выключено) -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/lbl_rx_stats"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="@color/text_secondary"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:visibility="gone" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Последний говоривший -->
|
<!-- Последний говоривший -->
|
||||||
|
|||||||
@@ -27,6 +27,8 @@
|
|||||||
<string name="audio_tx_bitrate">Скорость передачи (Opus)</string>
|
<string name="audio_tx_bitrate">Скорость передачи (Opus)</string>
|
||||||
<string name="audio_tx_bitrate_auto">Авто</string>
|
<string name="audio_tx_bitrate_auto">Авто</string>
|
||||||
<string name="audio_tx_bitrate_fmt">%1$d кбит/с</string>
|
<string name="audio_tx_bitrate_fmt">%1$d кбит/с</string>
|
||||||
|
<string name="audio_rx_stats">Показывать статистику приёма (потери/джиттер)</string>
|
||||||
|
<string name="rx_stats_fmt">RX: потери %1$.1f%%, джиттер %2$d мс</string>
|
||||||
<string name="toast_node_joined">Подключился: %1$s%2$s</string>
|
<string name="toast_node_joined">Подключился: %1$s%2$s</string>
|
||||||
<string name="toast_node_left">Отключился: %1$s%2$s</string>
|
<string name="toast_node_left">Отключился: %1$s%2$s</string>
|
||||||
<string name="node_tg_suffix"> (группа %1$d)</string>
|
<string name="node_tg_suffix"> (группа %1$d)</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user