diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamCaptureEngine.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamCaptureEngine.kt index 5f53e36c..adf05e2a 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamCaptureEngine.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamCaptureEngine.kt @@ -166,8 +166,7 @@ class StreamCaptureEngine( buffer.position(info.offset); buffer.limit(info.offset + info.size) val bytes = ByteArray(info.size).also { buffer.get(it) } if (info.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0) { - val codecConfig = mode.videoCodec.normalize(bytes) - check(codecConfig.size <= StreamVideoCodec.MAX_CONFIG); csd = codecConfig + csd = mode.videoCodec.mergeConfig(csd, bytes) } else encoded(bytes, info, config, rotation, token) } if (info.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) problem = "unexpected encoder EOS" diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamVideoCodec.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamVideoCodec.kt index 0d26b5f2..679f8aee 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamVideoCodec.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamVideoCodec.kt @@ -21,6 +21,17 @@ enum class StreamVideoCodec(val id: Int, val label: String, val mime: String, va require(bytes.isNotEmpty() && bytes.size <= H264AnnexB.MAX_FRAME) return if (annexB) H264AnnexB.normalize(bytes) else bytes.copyOf() } + /** Parameter sets могут прийти отдельными CODEC_CONFIG callbacks; сохраняем полный набор текущей revision. */ + fun mergeConfig(previous: ByteArray, update: ByteArray): ByteArray { + val next = normalize(update) + val result = if (!annexB || previous.isEmpty()) next else { + val units = H264AnnexB.nals(previous).map { it.second }.toMutableList() + for ((_, unit) in H264AnnexB.nals(next)) if (units.none { it.contentEquals(unit) }) units += unit + units.fold(byteArrayOf()) { a, unit -> a + unit } + } + require(result.size <= MAX_CONFIG) + return result + } private fun nalTypes(bytes: ByteArray): Set = H264AnnexB.nals(bytes).map { (_, nal) -> val at = if (nal[2] == 1.toByte()) 3 else 4 when (this) { diff --git a/tools/chatgui-android/app/src/test/java/com/utun/chat/data/StreamVideoCodecTest.kt b/tools/chatgui-android/app/src/test/java/com/utun/chat/data/StreamVideoCodecTest.kt index 5e27a8ec..6271c553 100644 --- a/tools/chatgui-android/app/src/test/java/com/utun/chat/data/StreamVideoCodecTest.kt +++ b/tools/chatgui-android/app/src/test/java/com/utun/chat/data/StreamVideoCodecTest.kt @@ -22,6 +22,13 @@ class StreamVideoCodecTest { val decoded = StreamVideoCodec.VP9.unpackKey(StreamVideoCodec.VP9.keyFrame(byteArrayOf(1), byteArrayOf())) assertTrue(decoded.first.isEmpty()); assertArrayEquals(byteArrayOf(1), decoded.second) } + @Test fun separateParameterSetsRemainCompleteWithoutDuplicates() { + val sps = nal(0x67, 1); val pps = nal(0x68, 2) + val config = StreamVideoCodec.H264.mergeConfig(sps, pps) + assertArrayEquals(sps + pps, config) + assertArrayEquals(config, StreamVideoCodec.H264.mergeConfig(config, sps)) + StreamVideoCodec.H264.unpackKey(StreamVideoCodec.H264.keyFrame(nal(0x65, 3), config)) + } @Test fun codecAndBitrateSurviveCameraModeSelection() { val base = StreamCaptureMode("a", 1280, 720, 30, 30, false, "encoder", 2_000_000) val hevc = base.copy(videoCodec = StreamVideoCodec.H265, minimumBitrate = 500000, maximumBitrate = 3000000)