Fix RX audio for subsequent transmissions (recreate decoder on flush)
This commit is contained in:
@@ -75,18 +75,7 @@ class OpusEngine(
|
||||
// delay + seek preroll) ТОЛЬКО из первых входных буферов
|
||||
// (см. queueDecoderConfig); csd-ключи MediaFormat он игнорирует, и без
|
||||
// конфига первый же аудио-кадр ломает декодер (C2_CORRUPTED).
|
||||
decoder = try {
|
||||
MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_OPUS).also {
|
||||
it.setCallback(decoderCallback)
|
||||
it.configure(format, null, null, 0)
|
||||
queueDecoderConfig() // готовим конфиг ДО start: колбэки уйдут после
|
||||
it.start()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "createDecoder failed: ${e.message}")
|
||||
null
|
||||
}
|
||||
if (decoder == null) return
|
||||
if (!startDecoderCodec(format)) return
|
||||
Log.d(TAG, "decoder started, config=${configBuffers.size}B")
|
||||
val minBuf = AudioTrack.getMinBufferSize(
|
||||
OUT_SAMPLE_RATE,
|
||||
@@ -112,13 +101,51 @@ class OpusEngine(
|
||||
Log.d(TAG, "decoder started")
|
||||
}
|
||||
|
||||
/** Создать и запустить только декодер (плеер не трогаем). */
|
||||
private fun startDecoderCodec(format: MediaFormat): Boolean {
|
||||
val dec = try {
|
||||
MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_OPUS).also {
|
||||
it.setCallback(decoderCallback)
|
||||
it.configure(format, null, null, 0)
|
||||
queueDecoderConfig() // готовим конфиг ДО start: колбэки уйдут после
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "createDecoder failed: ${e.message}")
|
||||
null
|
||||
}
|
||||
if (dec == null) return false
|
||||
// decoder назначаем ДО start(): колбэки нового кодека сразу проходят
|
||||
// проверку codec !== decoder, иначе первый конфиг-колбэк отбрасывается
|
||||
// и декодер навсегда остаётся без конфига
|
||||
decoder = dec
|
||||
freeInputs.clear()
|
||||
rxQueue.clear()
|
||||
receivedAny = false
|
||||
playStarted = false
|
||||
gateStart = 0L
|
||||
try {
|
||||
dec.start()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "decoder start failed: ${e.message}")
|
||||
decoder = null
|
||||
try {
|
||||
dec.release()
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** Асинхронные колбэки кодера: подача входных кадров + слив вывода. */
|
||||
private val decoderCallback = object : MediaCodec.Callback() {
|
||||
override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
|
||||
if (codec !== decoder) return // колбэк от уже пересозданного декодера
|
||||
Log.e(TAG, "decoder error: ${e.message} code=${e.errorCode} transient=${e.isTransient}")
|
||||
}
|
||||
|
||||
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
|
||||
if (codec !== decoder) return // колбэк от уже пересозданного декодера
|
||||
// Конфиг (csd) первыми входными буферами — в async-режиме нельзя
|
||||
// использовать dequeueInputBuffer, только колбэки
|
||||
if (configIdx < configBuffers.size) {
|
||||
@@ -156,6 +183,7 @@ class OpusEngine(
|
||||
}
|
||||
|
||||
override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) {
|
||||
if (codec !== decoder) return // колбэк от уже пересозданного декодера
|
||||
try {
|
||||
if (info.size > 0) {
|
||||
val outBuf = ByteBuffer.allocate(info.size)
|
||||
@@ -193,6 +221,7 @@ class OpusEngine(
|
||||
}
|
||||
|
||||
override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
|
||||
if (codec !== decoder) return // колбэк от уже пересозданного декодера
|
||||
Log.d(TAG, "dec out format: sr=${format.getInteger(MediaFormat.KEY_SAMPLE_RATE)} " +
|
||||
"ch=${format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)}")
|
||||
}
|
||||
@@ -262,17 +291,47 @@ class OpusEngine(
|
||||
|
||||
fun onAudioFlush() {
|
||||
try {
|
||||
decoder?.flush()
|
||||
// MediaCodec.flush() возвращает входные буферы, но уже выданные индексы
|
||||
// остаются у нас в freeInputs и дублируются колбэками «повторной выдачи» —
|
||||
// queueInputBuffer падает (index not owned by client) и кадры теряются.
|
||||
// Надёжнее пересоздать декодер: колбэки старого отсекаются проверкой
|
||||
// codec !== decoder.
|
||||
player?.flush()
|
||||
rxQueue.clear()
|
||||
receivedAny = false
|
||||
playStarted = false
|
||||
gateStart = 0L
|
||||
onLevel(SRC_SPEAKER, 0f)
|
||||
restartDecoder()
|
||||
} 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 мс) для скрытия потерь вместо паузы в звуке.
|
||||
* Сгенерирован opuslib: 8 кГц, моно, 20 кбит/с, нули PCM.
|
||||
|
||||
Reference in New Issue
Block a user