Audio: reduce RX startup clipping
This commit is contained in:
@@ -31,6 +31,7 @@ class OpusEngine(
|
|||||||
const val CHANNELS = 1
|
const val CHANNELS = 1
|
||||||
const val FRAME_SIZE = 160 // 20 мс в 8 кГц
|
const val FRAME_SIZE = 160 // 20 мс в 8 кГц
|
||||||
const val BIT_RATE = 20000
|
const val BIT_RATE = 20000
|
||||||
|
private const val GATE_MIN_WAIT_MS = 100L
|
||||||
// Авто-адаптация битрейта TX (0 = авто включено в настройках)
|
// Авто-адаптация битрейта TX (0 = авто включено в настройках)
|
||||||
const val AUTO_BITRATE_MIN = 8
|
const val AUTO_BITRATE_MIN = 8
|
||||||
const val AUTO_BITRATE_MAX = 24
|
const val AUTO_BITRATE_MAX = 24
|
||||||
@@ -384,56 +385,26 @@ class OpusEngine(
|
|||||||
onRxStats?.invoke(lossPct, jitterMs)
|
onRxStats?.invoke(lossPct, jitterMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Джиттер-гейт открыт: накопили кадры или вышло 300 мс с первого пакета. */
|
/** Джиттер-гейт открыт: накопили кадры или вышел короткий таймаут. */
|
||||||
private fun gateOpen(): Boolean {
|
private fun gateOpen(): Boolean {
|
||||||
if (playStarted) return true
|
if (playStarted) return true
|
||||||
if (rxQueue.size >= jitterFrames) return true
|
if (rxQueue.size >= jitterFrames) return true
|
||||||
return gateStart != 0L && System.currentTimeMillis() - gateStart >= 300
|
val waitMs = maxOf(GATE_MIN_WAIT_MS, jitterFrames * 20L)
|
||||||
|
return gateStart != 0L && System.currentTimeMillis() - gateStart >= waitMs
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onAudioFlush() {
|
fun onAudioFlush() {
|
||||||
try {
|
try {
|
||||||
// MediaCodec.flush() возвращает входные буферы, но уже выданные индексы
|
|
||||||
// остаются у нас в freeInputs и дублируются колбэками «повторной выдачи» —
|
|
||||||
// queueInputBuffer падает (index not owned by client) и кадры теряются.
|
|
||||||
// Надёжнее пересоздать декодер: колбэки старого отсекаются проверкой
|
|
||||||
// codec !== decoder.
|
|
||||||
player?.flush()
|
player?.flush()
|
||||||
rxQueue.clear()
|
rxQueue.clear()
|
||||||
receivedAny = false
|
receivedAny = false
|
||||||
playStarted = false
|
playStarted = false
|
||||||
gateStart = 0L
|
gateStart = 0L
|
||||||
onLevel(SRC_SPEAKER, 0f)
|
onLevel(SRC_SPEAKER, 0f)
|
||||||
restartDecoder()
|
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Пересоздать только декодер (плеер и джиттер-буфер сохраняем). */
|
|
||||||
private fun restartDecoder() {
|
|
||||||
val dec = decoder
|
|
||||||
decoder = null
|
|
||||||
if (dec != null) {
|
|
||||||
try {
|
|
||||||
dec.stop()
|
|
||||||
} catch (_: Exception) {
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
dec.release()
|
|
||||||
} catch (_: Exception) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val format = MediaFormat()
|
|
||||||
format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_OPUS)
|
|
||||||
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, OUT_SAMPLE_RATE)
|
|
||||||
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS)
|
|
||||||
if (startDecoderCodec(format)) {
|
|
||||||
concealLogs = 0
|
|
||||||
dbgOut = 0
|
|
||||||
ptsCounter = 20_000L
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Один кадр тишины (20 мс) для скрытия потерь вместо паузы в звуке.
|
* Один кадр тишины (20 мс) для скрытия потерь вместо паузы в звуке.
|
||||||
* Сгенерирован opuslib: 8 кГц, моно, 20 кбит/с, нули PCM.
|
* Сгенерирован opuslib: 8 кГц, моно, 20 кбит/с, нули PCM.
|
||||||
@@ -664,4 +635,4 @@ class OpusEngine(
|
|||||||
val norm = (db + 60.0) / 48.0
|
val norm = (db + 60.0) / 48.0
|
||||||
return norm.toFloat().coerceIn(0f, 1f)
|
return norm.toFloat().coerceIn(0f, 1f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user