Fix RX audio for subsequent transmissions (recreate decoder on flush)
This commit is contained in:
@@ -75,18 +75,7 @@ class OpusEngine(
|
|||||||
// delay + seek preroll) ТОЛЬКО из первых входных буферов
|
// delay + seek preroll) ТОЛЬКО из первых входных буферов
|
||||||
// (см. queueDecoderConfig); csd-ключи MediaFormat он игнорирует, и без
|
// (см. queueDecoderConfig); csd-ключи MediaFormat он игнорирует, и без
|
||||||
// конфига первый же аудио-кадр ломает декодер (C2_CORRUPTED).
|
// конфига первый же аудио-кадр ломает декодер (C2_CORRUPTED).
|
||||||
decoder = try {
|
if (!startDecoderCodec(format)) return
|
||||||
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
|
|
||||||
Log.d(TAG, "decoder started, config=${configBuffers.size}B")
|
Log.d(TAG, "decoder started, config=${configBuffers.size}B")
|
||||||
val minBuf = AudioTrack.getMinBufferSize(
|
val minBuf = AudioTrack.getMinBufferSize(
|
||||||
OUT_SAMPLE_RATE,
|
OUT_SAMPLE_RATE,
|
||||||
@@ -112,13 +101,51 @@ class OpusEngine(
|
|||||||
Log.d(TAG, "decoder started")
|
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() {
|
private val decoderCallback = object : MediaCodec.Callback() {
|
||||||
override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
|
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}")
|
Log.e(TAG, "decoder error: ${e.message} code=${e.errorCode} transient=${e.isTransient}")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
|
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
|
||||||
|
if (codec !== decoder) return // колбэк от уже пересозданного декодера
|
||||||
// Конфиг (csd) первыми входными буферами — в async-режиме нельзя
|
// Конфиг (csd) первыми входными буферами — в async-режиме нельзя
|
||||||
// использовать dequeueInputBuffer, только колбэки
|
// использовать dequeueInputBuffer, только колбэки
|
||||||
if (configIdx < configBuffers.size) {
|
if (configIdx < configBuffers.size) {
|
||||||
@@ -156,6 +183,7 @@ class OpusEngine(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) {
|
override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) {
|
||||||
|
if (codec !== decoder) return // колбэк от уже пересозданного декодера
|
||||||
try {
|
try {
|
||||||
if (info.size > 0) {
|
if (info.size > 0) {
|
||||||
val outBuf = ByteBuffer.allocate(info.size)
|
val outBuf = ByteBuffer.allocate(info.size)
|
||||||
@@ -193,6 +221,7 @@ class OpusEngine(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
|
override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
|
||||||
|
if (codec !== decoder) return // колбэк от уже пересозданного декодера
|
||||||
Log.d(TAG, "dec out format: sr=${format.getInteger(MediaFormat.KEY_SAMPLE_RATE)} " +
|
Log.d(TAG, "dec out format: sr=${format.getInteger(MediaFormat.KEY_SAMPLE_RATE)} " +
|
||||||
"ch=${format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)}")
|
"ch=${format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)}")
|
||||||
}
|
}
|
||||||
@@ -262,17 +291,47 @@ class OpusEngine(
|
|||||||
|
|
||||||
fun onAudioFlush() {
|
fun onAudioFlush() {
|
||||||
try {
|
try {
|
||||||
decoder?.flush()
|
// 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.
|
||||||
|
|||||||
Reference in New Issue
Block a user