You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 03:02:02