本文代码示例采用的Kotlin语法,但是没什么高级特性
MediaExtractor:视频文件的提取器,能将视频和音频分离
MediaMuxer:音视频文件合成器,能将提取到的视频和音频合成新的视频
MediaFormat :提取器获取到的媒体格式类,保存了获取到的媒体的信息(媒体类型,
帧率等)
1.设置需要权限
一.如果是本地文件,会涉及到读取和写入,需要在Manifest配置
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
6.0以上要在代码中动态申请,这里就省略了
2.获取需要的视频文件
本文是直接通过contentResolver查询多媒体文件
val cursor = contentResolver.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
null, null, null, null
)
查询到的数据存储到了自建的Song类中,存了文件名(name)和路径(path),然后用ListView展示,
为了省事,直接使用的就是点击的文件和相邻下一个文件做为提取视频和音频的原材料
如果不想麻烦去找文件,也可以直接把视频文件放到res/raw文件夹下,raw要自己创建
然后获取文件,MediaExtractor.setDataSource支持很多方式填充
3.提取音视频
/**
* 配置音视频提取器
* @param position 点击的文件下标
*/
fun configureVideoAndAudioExtractor(position: Int) {
try {
//1.设置要提取视频的文件
//
/**
*
* MediaExtractor反复提示初始化失败
* 1.检查文件访问权限
* 2.检查视频文件大小是否大于0!!!!!!!!!!!!!!!
*3.最好不要用拼接路径,比如
* mVideoMediaExtractor.setDataSource(context.getFilesDir()+"xxx.mp4")
* 最好添加路径变量或完整路径地址,比如下面的变量或
* "mnt/sdcard/.../.../xxx.mp4"这种的
*/
//初始化解析器和合成器对象,合成输入的格式是mp4,
//outputVideoPath是合成后输出的路径,自己构建就好
//我的是fileDir+“video.mp4”
mMediaMuxer = MediaMuxer(outputVideoPath,
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
mVideoMediaExtractor = MediaExtractor()
mAudioMediaExtractor = MediaExtractor()
//songList是一个集合,存着Song类,
//设置要提取出视频的原材料文件
mVideoMediaExtractor.setDataSource(songList[position].path)
//设置要提取出音频的文件
mAudioMediaExtractor.setDataSource(songList[position].path)
//获取轨道,找到视频轨道
for (i in 0 until mVideoMediaExtractor.trackCount) {
val mediaFormat = mVideoMediaExtractor.getTrackFormat(i)
if (mediaFormat.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
//获取到视频轨道
mVideoMediaExtractor.selectTrack(i)
//获取添加到Muxer后生成的新的视频轨道下标
videoMuxerTrackIndex = mMediaMuxer.addTrack(mediaFormat)
//获取视频帧最大值,为了后面合成新视频,读取文件时候设定缓冲区大小
maxFrameSize = mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)
//获取视频帧率,为了后面计算获取到的文件处于的播放时间
frameRate = mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE)
}
}
//找到视频文件中的音频轨道,方法和获取视频差不多
for (j in 0 until mAudioMediaExtractor.trackCount) {
val mediaFormat = mAudioMediaExtractor.getTrackFormat(j)
if (mediaFormat.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
//获取音轨
mAudioMediaExtractor.selectTrack(j)
//添加音轨到Muxer
audioMuxerTrackIndex = mMediaMuxer.addTrack(mediaFormat)
//获取音频最大输入,为了计算缓冲区大小
maxAudioSize = mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)
}
}
//这个方法内执行MediaMuxer的合成操作,下面会贴出来
compoundVideoAndAudioWithPermissionCheck()
} catch (e: IOException) {
Log.i("exception", e.message)
} finally {
//释放资源,切记最后要是放资源,重新添加合成文件时候要新建MediaExtractor和MediaMuxer
if (mMediaMuxer != null) {
mMediaMuxer.release()
}
if (mVideoMediaExtractor != null) {
mVideoMediaExtractor.release()
}
if (mAudioMediaExtractor != null) {
mAudioMediaExtractor.release()
}
}
}
总结一下步骤就是
1.创建MediaExtractor和MediaMuxer对象
2.为MediaExtractor对象添加需要的文件setDataSource()
3.for循环获取视频轨道和音频轨道MediaExtractor#selectTrack(),并添加到MediaMuxer中
MediaMuxer#addTrack()
4.开始准备合并
4.合成音视频
/**
* 合成视频和音频
*/
fun compoundVideoAndAudio() {
//1.开始合成
mMediaMuxer.start()
//2.输入提取到的视频,videoMuxerTrackIndex是之前addTrack生成的下标,
//如果为-1就是添加失败
if (-1 != videoMuxerTrackIndex) {
//描述缓冲区数据信息类,最后Muxer合成要求有的东西
val videoBufferInfo = MediaCodec.BufferInfo()
//创建缓冲区,最后Muxer合成要求有的东西
val videoByteBuffer = ByteBuffer.allocate(maxFrameSize)
while (true) {
/3./获取样本大小
val videoSampleSize = mVideoMediaExtractor.readSampleData(videoByteBuffer, 0)
if (videoSampleSize < 0) {
break
}
//4.设置样本信息
videoBufferInfo.offset = 0 //堆buffer缓冲区写入时的字节偏移
videoBufferInfo.size = videoSampleSize
videoBufferInfo.flags = mVideoMediaExtractor.sampleFlags
//读取到的文件的时间戳,单位是微秒
videoBufferInfo.presentationTimeUs += 1000 * 1000 / frameRate //每次加每帧的微秒数
//MediaMuxer写入样本数据
//videoMuxerTrackIndex 之前加入Muxer的视频轨下标
//videoByteBuffer 上面创建的Buffer对象
//videoBufferInfo 上面创建的BufferInfo对象
mMediaMuxer.writeSampleData(videoMuxerTrackIndex, videoByteBuffer, videoBufferInfo)
//5.推进到下个样本 类似快进
mVideoMediaExtractor.advance()
}
}
/**
* 合成音频,和视频类似
*/
if (-1 != audioMuxerTrackIndex) {
val audioBufferInfo = MediaCodec.BufferInfo()
val audioByteBuffer = ByteBuffer.allocate(maxAudioSize)
while (true) {
val audioSampleSize = mAudioMediaExtractor.readSampleData(audioByteBuffer, 0)
if (audioSampleSize < 0) {
break
}
audioBufferInfo.offset = 0
audioBufferInfo.size = audioSampleSize
audioBufferInfo.flags = mAudioMediaExtractor.sampleFlags
audioBufferInfo.presentationTimeUs += 1000 * 1000 / frameRate
mMediaMuxer.writeSampleData(audioMuxerTrackIndex, audioByteBuffer, audioBufferInfo)
mAudioMediaExtractor.advance()
}
}
}
}
}
这个方法是在之前的方法内调用,所以try…catch写在上面的方法中,这些步骤都可以在子线程中去做,最后可以通过系统的VideoView控件来播放outputVideoPath路径的视频,查看是否合成成功
总结流程
1.创建Buffer对象和MediaCodec#BufferInfo对象
2.读取样本数据,验证数据大小
3.填写bufferInfo的信息
4.将缓冲区内容写入Muxer
5.调用MediaExtractor#advance(),推进到下个样本数据,循环步骤2-5
6.释放资源
需要注意的地方!!!!!!!!!!!!!!!
1.权限获取
2.验证要提取的文件是否合法,比如大小,本人遇到MediaExtractor.setDataSource()一直无法初始化MediaExtractor,结果发现添加的视频大小为0KB
3.时间戳会影响合成后效果,需要注意计算
有疑问和想法或者发现错误之处,欢迎交流