大家好,我是程序员kenney,今天给大家介绍一下如何在Android里面实现视频时长的裁剪。
首先我们要知道视频是由一帧一帧的数据构成的,每一帧都有一个时间戳,这个时间戳就是我们在做视频编码的时候,当要编码一帧的时候,会给这一帧设置一个时间戳。 因此做视频裁剪的思路就是把要裁剪的视频的数据一帧一帧地拿出来,然后从想裁剪的开始位置,把视频帧原样地放回去,直到想裁剪的结束位置。
这里值得注意的是,我们如果只做视频的裁剪,那么可以简单地将视频帧取出来,再原样放回去,而不需要经过视频的解码,然后再编码,因为我们不需要改变视频振动内容,视频的解码和编码都是很耗时的操作。而如果我们同时还要进行视频特效的渲染,或者对视频进行缩放等,这些都是对视频帧做了修改,这个时候就需要一个解码、处理,再编码的过程。
那么如何提取视频的帧数据呢?可以用MediaExtractor,这个玩意能帮我们提取视频或者音频中的数据,我们今天讲的是视频的裁剪,因此就要从一个视频文件中提取视频的数据,在一个视频文件中,音频和视频数据是分别存储在不同的轨道中,因此,如果我们要裁剪的这个视频,是包含声音的,那么也要对声音做同样的处理。
可能有人会问,一个视频难道还能没有声音?是的,在看完这篇文章后,可以自己试一试,把一个有声音的视频将其中的视频轨道提取出来,再封装回去,这样就得到了一个只有图像而没有声音的视频,如果大家有看我之前的文章,也会发现我之前生成的视频就是没有声音的,因为我没有把声音加进去,视频里只有视频轨道,没有音频轨道。
这里我的示例中也只处理纯视频。
首先我们创建MediaExtractor并设置数据源:
MediaExtractor().apply {
setDataSource(inputPath)
}
然后是获取视频轨道index:
private fun getTrackIndex(mediaExtractor: MediaExtractor): Int {
for (trackIndex in 0 until mediaExtractor.trackCount) {
mediaExtractor.getTrackFormat(trackIndex).let { trackFormat ->
val mineType = "video"
if (trackFormat.getString(MediaFormat.KEY_MIME).contains(mineType)) {
return trackIndex
}
}
}
return -1
}
让MediaExtractor选中视频轨道并seek到需要裁剪的开始时间:
MediaExtractor().apply {
setDataSource(inputPath)
val extractorTrackIndex = getTrackIndex(this, mediaType)
selectTrack(extractorTrackIndex)
seekTo(startTimeUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC)
}
然后再获取源视频的format,并用这个format来创建MediaMuxer用于视频封装:
MediaExtractor().apply {
setDataSource(inputPath)
val extractorTrackIndex = getTrackIndex(this, mediaType)
selectTrack(extractorTrackIndex)
seekTo(startTimeUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC)
val videoFormat = getTrackFormat(extractorTrackIndex)
videoFormat.setInteger(MediaFormat.KEY_DURATION, (endTimeUs - startTimeUs).toInt())
val mediaMuxer = MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
val muxerTrackIndex = mediaMuxer.addTrack(videoFormat)
mediaMuxer.start()
}
现在MediaExtractor和MediaMuxer都准备好了,接下来就开始不断地从源视频中提取数据并封装进新的视频文件中,直到视频帧时间戳大于了我们设置的裁剪结束时间,另外,每次readSampleData()后要让MediaExtractor往前advance(),这样才能取到后面的数据:
while (true) {
val sampleSize = readSampleData(byteBuffer, 0)
if (sampleSize < 0 || sampleTime > endTimeUs) {
break
}
val bufferInfo = MediaCodec.BufferInfo().apply {
offset = 0
size = sampleSize
flags = sampleFlags
presentationTimeUs = sampleTime
}
mediaMuxer.writeSampleData(muxerTrackIndex, byteBuffer, bufferInfo)
advance()
}
实际上,MediaExtractor和MediaMuxer都是可以处理音频轨道的,因此纯视频裁剪、纯音频裁剪、带音频的视频裁剪都没有问题。
完整代码可以看:github.com/kenneycode/…
感谢阅读!