Android Studio Kotlin中Media Codec压缩视频遇invalid trackIndex错误
解决MediaMuxer.writeSampleData的"invalid trackIndex"错误及视频压缩实现修正
核心错误原因
你直接使用MediaExtractor的trackIndex调用mediaMuxer.writeSampleData(),但MediaMuxer的轨道索引是独立维护的,必须通过mediaMuxer.addTrack()方法添加编码器输出格式后,获取返回的索引才能用于写入采样数据。Extractor的轨道索引和Muxer的轨道索引没有对应关系,哪怕数值都是0,Muxer也不认可——因为你根本没在Muxer中注册这个轨道。
完整修正步骤及代码
1. 正确获取Muxer轨道索引
编码器启动后,会通过INFO_OUTPUT_FORMAT_CHANGED回调返回输出格式,此时调用mediaMuxer.addTrack()获取Muxer的合法轨道索引,且必须在addTrack()后调用mediaMuxer.start()。
2. 移除错误的"压缩"逻辑
直接截断ByteBuffer的方式会破坏视频流结构,导致文件损坏。真正的视频压缩应通过调整编码器的KEY_BIT_RATE等参数实现,编码器会输出合法的压缩数据。
3. 处理Codec特殊标记与Buffer状态
编码器输出的BUFFER_FLAG_CODEC_CONFIG是配置信息(如SPS/PPS),需要写入Muxer;同时要调用flip()将ByteBuffer从写模式切换为读模式,确保数据可读。
4. 异常安全的资源释放
使用try-finally确保所有资源在异常情况下也能正确释放,且释放顺序符合规范(先停止再释放)。
以下是修正后的完整代码:
private fun reduceVideoSize(inputDescriptor: FileDescriptor, outputDescriptor: FileDescriptor, targetBitRate: Int) { lifecycleScope.launch(Dispatchers.IO) { // 切换到IO线程,避免阻塞主线程 Log.d("reduceVideoSize", "STARTED") var mediaExtractor: MediaExtractor? = null var mediaCodec: MediaCodec? = null var mediaMuxer: MediaMuxer? = null try { mediaExtractor = MediaExtractor() mediaExtractor.setDataSource(inputDescriptor) val extractorTrackIndex = selectTrack(mediaExtractor, false) if (extractorTrackIndex == -1) { Log.e("reduceVideoSize", "未找到视频轨道") return@launch } Log.d("EXTRACTOR TRACK INDEX", extractorTrackIndex.toString()) mediaExtractor.selectTrack(extractorTrackIndex) val inputFormat = mediaExtractor.getTrackFormat(extractorTrackIndex) // 获取原视频参数,避免硬编码 val originalWidth = inputFormat.getInteger(MediaFormat.KEY_WIDTH) val originalHeight = inputFormat.getInteger(MediaFormat.KEY_HEIGHT) val originalFrameRate = inputFormat.getInteger(MediaFormat.KEY_FRAME_RATE, 30) // 配置编码器格式,通过比特率控制压缩程度 val encoderFormat = MediaFormat.createVideoFormat("video/avc", originalWidth, originalHeight) encoderFormat.setInteger(MediaFormat.KEY_BIT_RATE, targetBitRate) encoderFormat.setInteger(MediaFormat.KEY_FRAME_RATE, originalFrameRate) encoderFormat.setInteger( MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible ) encoderFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1) Log.d("EncoderFormat", encoderFormat.toString()) val mimeType = encoderFormat.getString(MediaFormat.KEY_MIME) ?: run { Log.e("reduceVideoSize", "无效的MIME类型") return@launch } mediaCodec = MediaCodec.createEncoderByType(mimeType) mediaCodec.configure(encoderFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) mediaCodec.start() mediaMuxer = MediaMuxer(outputDescriptor, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) var muxerTrackIndex = -1 val bufferInfo = MediaCodec.BufferInfo() var isEOS = false val timeoutUs = 10000L while (!isEOS) { // 处理输入数据 val inputBufferIndex = mediaCodec.dequeueInputBuffer(timeoutUs) if (inputBufferIndex >= 0) { val inputBuffer = mediaCodec.getInputBuffer(inputBufferIndex) ?: continue val sampleSize = mediaExtractor.readSampleData(inputBuffer, 0) if (sampleSize < 0) { // 输入流结束 mediaCodec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM) isEOS = true } else { mediaCodec.queueInputBuffer( inputBufferIndex, 0, sampleSize, mediaExtractor.sampleTime, mediaExtractor.sampleFlags ) mediaExtractor.advance() } } // 处理输出数据 val outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, timeoutUs) when (outputBufferIndex) { MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> { // 编码器输出格式确定,添加到Muxer val outputFormat = mediaCodec.outputFormat muxerTrackIndex = mediaMuxer.addTrack(outputFormat) mediaMuxer.start() // 必须在addTrack后调用start Log.d("MUXER TRACK INDEX", muxerTrackIndex.toString()) } MediaCodec.INFO_TRY_AGAIN_LATER -> { // 暂时无输出,继续循环 } else -> { if (muxerTrackIndex == -1) { // 未获取到Muxer轨道索引,跳过 mediaCodec.releaseOutputBuffer(outputBufferIndex, false) continue } val outputBuffer = mediaCodec.getOutputBuffer(outputBufferIndex) ?: run { mediaCodec.releaseOutputBuffer(outputBufferIndex, false) continue } // 处理配置数据(如SPS/PPS) if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0) { bufferInfo.size = 0 } if (bufferInfo.size > 0) { // 切换buffer为读模式 outputBuffer.flip() mediaMuxer.writeSampleData(muxerTrackIndex, outputBuffer, bufferInfo) // 恢复buffer状态 outputBuffer.position(bufferInfo.offset) outputBuffer.limit(bufferInfo.offset + bufferInfo.size) } mediaCodec.releaseOutputBuffer(outputBufferIndex, false) if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) { isEOS = true } } } } } catch (e: Exception) { Log.e("reduceVideoSize", "压缩过程出错", e) } finally { // 按顺序释放资源 mediaMuxer?.let { try { it.stop() } catch (e: Exception) { Log.e("reduceVideoSize", "停止Muxer出错", e) } it.release() } mediaCodec?.let { it.stop() it.release() } mediaExtractor?.release() Log.d("reduceVideoSize", "压缩完成") } } } private fun selectTrack(extractor: MediaExtractor, audio: Boolean): Int { val numTracks = extractor.trackCount Log.d("NUMTRACKS", numTracks.toString()) for (i in 0 until numTracks) { val format = extractor.getTrackFormat(i) val mime = format.getString(MediaFormat.KEY_MIME) ?: continue if (audio) { if (mime.startsWith("audio/")) { Log.d("TRACK $i", "找到音频轨道") return i } } else { if (mime.startsWith("video/")) { Log.d("TRACK $i", "找到视频轨道") return i } } } return -1 }
关键修正点说明
- 轨道索引合法性:通过编码器输出格式注册Muxer轨道,获取的索引才是
writeSampleData的有效参数。 - 正确的压缩逻辑:通过调整
KEY_BIT_RATE控制压缩率,编码器输出合法的H.264视频流。 - Buffer状态处理:
flip()确保ByteBuffer可读,写入后恢复状态避免后续操作异常。 - 线程优化:压缩逻辑放在IO线程,避免主线程阻塞导致ANR。
- 异常安全:try-finally块保证所有资源在异常场景下也能正确释放。
内容的提问来源于stack exchange,提问作者LOBSTERVOVA
相关产品推荐
相关产品推荐

