使用Android MediaMuxer截取带音频视频失败:音频无法嵌入
Android MediaMuxer导出带声音视频片段的问题修复
你的代码存在几个核心问题,导致音频无法正确写入导出的视频文件,下面是问题分析和修复方案:
问题根源
- 多轨道样本读取混乱:
MediaExtractor同时选中音视频轨道时,readSampleData只能读取当前单个轨道的样本,但你把同一个buffer同时写给音视频轨道,相当于把视频数据重复写入音频轨道,音频数据根本没被正确读取。 - 硬编码样本Flags:音频样本没有关键帧概念,但你硬设置
bufferInfo.flags = MediaCodec.BUFFER_FLAG_KEY_FRAME,导致播放器无法解析音频流。 - 时间戳未适配:原视频的时间戳直接写入,播放器可能因时间戳起始值过大无法识别轨道。
- 资源未释放:
MediaMetadataRetriever使用后未释放,存在内存泄漏风险。
修复后的完整代码
private fun splitVideo(inputPath: String, outputPath: String, startTimeMs: Long, endTimeMs: Long) { val outputFile = File(outputPath) if (outputFile.exists()) { outputFile.delete() } val mediaExtractor = MediaExtractor() val mediaMuxer = MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) try { mediaExtractor.setDataSource(inputPath) // 处理视频方向和尺寸 var newVideoWidth = 0 var newVideoHeight = 0 val inputVideoRetriever = MediaMetadataRetriever() inputVideoRetriever.setDataSource(inputPath) val rotation = inputVideoRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION) if (rotation == null || rotation == "0" || rotation == "180") { newVideoWidth = inputVideoRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toIntOrNull() ?: 0 newVideoHeight = inputVideoRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toIntOrNull() ?: 0 } else { newVideoWidth = inputVideoRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toIntOrNull() ?: 0 newVideoHeight = inputVideoRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toIntOrNull() ?: 0 } val orientationHint = when (rotation) { "90" -> 90 "270" -> 270 else -> 0 } mediaMuxer.setOrientationHint(orientationHint) inputVideoRetriever.release() // 释放资源 // 获取音视频轨道索引 val videoTrackIndex = selectTrack(mediaExtractor, "video/") val audioTrackIndex = selectTrack(mediaExtractor, "audio/") println("#20230807X videoTrackIndex: $videoTrackIndex") println("#20230807X audioTrackIndex: $audioTrackIndex") val startTimeUs = startTimeMs * 1000 val endTimeUs = endTimeMs * 1000 // 原轨道索引 -> Muxer轨道索引的映射 val trackMap = mutableMapOf<Int, Int>() val buffer = ByteBuffer.allocate(2 * 1024 * 1024) val bufferInfo = MediaCodec.BufferInfo() // 配置要处理的轨道 val tracksToProcess = mutableListOf<Int>() if (videoTrackIndex >= 0) { tracksToProcess.add(videoTrackIndex) val videoFormat = mediaExtractor.getTrackFormat(videoTrackIndex) if (newVideoWidth > 0 && newVideoHeight > 0) { videoFormat.setInteger(MediaFormat.KEY_WIDTH, newVideoWidth) videoFormat.setInteger(MediaFormat.KEY_HEIGHT, newVideoHeight) } trackMap[videoTrackIndex] = mediaMuxer.addTrack(videoFormat) } if (audioTrackIndex >= 0) { tracksToProcess.add(audioTrackIndex) val audioFormat = mediaExtractor.getTrackFormat(audioTrackIndex) trackMap[audioTrackIndex] = mediaMuxer.addTrack(audioFormat) } if (tracksToProcess.isEmpty()) { throw IllegalArgumentException("No valid tracks found in input video") } mediaMuxer.start() // 单独处理每个轨道的样本 for (trackIndex in tracksToProcess) { mediaExtractor.selectTrack(trackIndex) mediaExtractor.seekTo(startTimeUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC) var firstSampleTimeUs = -1L while (true) { val sampleSize = mediaExtractor.readSampleData(buffer, 0) if (sampleSize < 0) break val currentSampleTimeUs = mediaExtractor.sampleTime if (currentSampleTimeUs > endTimeUs) break // 计算相对时间戳,从0开始 if (firstSampleTimeUs == -1L) { firstSampleTimeUs = currentSampleTimeUs } val adjustedTimeUs = currentSampleTimeUs - firstSampleTimeUs bufferInfo.apply { presentationTimeUs = adjustedTimeUs offset = 0 size = sampleSize flags = mediaExtractor.sampleFlags // 使用原样本的Flags } // 写入对应轨道 mediaMuxer.writeSampleData(trackMap[trackIndex]!!, buffer, bufferInfo) mediaExtractor.advance() } mediaExtractor.unselectTrack(trackIndex) } mediaMuxer.stop() } catch (e: Exception) { e.printStackTrace() } finally { mediaExtractor.release() mediaMuxer.release() } } // 轨道选择辅助方法 private fun selectTrack(extractor: MediaExtractor, mimePrefix: String): Int { for (i in 0 until extractor.trackCount) { val format = extractor.getTrackFormat(i) val mime = format.getString(MediaFormat.KEY_MIME) ?: continue if (mime.startsWith(mimePrefix)) { return i } } return -1 }
关键修复说明
- 分开处理音视频轨道:遍历每个轨道单独读取样本,避免多轨道选中导致的样本混淆。
- 保留原样本Flags:使用
mediaExtractor.sampleFlags代替硬编码,确保音视频样本的正确标识。 - 重计算时间戳:将样本时间戳调整为相对于片段起始时间的偏移量,从0开始计数,兼容播放器要求。
- 释放资源:添加
inputVideoRetriever.release()清理未关闭的资源。 - 移除硬编码Duration:让MediaMuxer自动计算导出片段的时长,避免与实际样本时长冲突。
内容的提问来源于stack exchange,提问作者Jeff Bootsholz
相关产品推荐
相关产品推荐

