最近在做类似小咖秀的视频录制功能,也就是俗称的对嘴型表演,录制视频我用的是三方SDK,但是视频合成就需要自己搞了,在网上搜了挺多资料,国内国外网站看了不少,踩了很多坑,总算整出来了,在此分享给大家,希望对以后要做类似功能的兄弟们有所帮助!
将视频一的音频提取出来,视频二的视频图像提取出来,然后把它们合成新的视频。
视频的分离合成我主要用到了MediaExtractor和MediaMuxer两个类: MediaExtractor是用于提取多路的、通常编码的视频资源的,通过它我们可以选择音频或者视频轨,然后分别对它们进行操作等; MediaMuxer是用于复用基本流的,用它可以将音频和视频合成,目前支持输出MP4,Webm和3GP格式的视频,在Android7.0以后支持多路复用帧的MP4。 MediaFormat封装了描述媒体数据格式的信息,如音频或视频,通过它我们可以取出音频或者视频。
我们将视频一的路径通过setDataSource方法设置给MediaExtractor对象,然后通过方法getTrackCount获取到该视频的轨道数,接着循环轨道数,此时我们可以通过MediaExtractor对象的getTrackFormat方法获取到MediaFormat,然后找到我们想要的音频轨,还记得MediaMuxer类是用来合成的吧,那么我们将找到的音频轨通过addTrack方法设置给MediaMuxer对象,这里有个小细节需要注意,我们需要记录两个音频轨,一个是原视频的(旧轨道),一个是将来合成的视频的(新轨道),之后会用到。 OK,视频一的音频已经提取出来啦,那么我们用相似的方法将视频二的视频图像提取出来,也通过addTrack方法设置给同一个MediaMuxer对象,不同的是我们要获取到视频的帧率,并且在之后合成的时候需要处理一下。
那么到这里,视频一的音频和视频二的视频图像都已经设置给MediaMuxer对象了,我们就可以合成啦,还记得我们在找音频和视频的时候记录下的新旧轨道吧,现在通过MediaExtractor对象的selectTrack(旧轨道)方法选择到原视频的我们想要的音轨或视频轨,并取到样本,设置编码信息,然后通过MediaMuxer对象的 writeSampleData(新轨道,样本,编码信息)方法对音频和视频分别进行写入,到此新的视频就合成啦!
最后,不要忘记释放资源哦调用它们的release方法即可。
本人文字水平太烂,讲了一大堆,不知道说清楚没,还是直接上代码吧!
/**
* 合成视频1的音频和视频2的图像
*
* @param audioVideoPath 提供音频的视频
* @param audioStartTime 音频的开始时间
* @param frameVideoPath 提供图像的视频
* @param combinedVideoOutFile 合成后的文件
*/
public static void combineTwoVideos(String audioVideoPath,
long audioStartTime,
String frameVideoPath,
File combinedVideoOutFile) {
MediaExtractor audioVideoExtractor = new MediaExtractor();
int mainAudioExtractorTrackIndex = -1; //提供音频的视频的音频轨(有点拗口)
int mainAudioMuxerTrackIndex = -1; //合成后的视频的音频轨
int mainAudioMaxInputSize = 0; //能获取的音频的最大值
MediaExtractor frameVideoExtractor = new MediaExtractor();
int frameExtractorTrackIndex = -1; //视频轨
int frameMuxerTrackIndex = -1; //合成后的视频的视频轨
int frameMaxInputSize = 0; //能获取的视频的最大值
int frameRate = 0; //视频的帧率
long frameDuration = 0;
MediaMuxer muxer = null; //用于合成音频与视频
try {
muxer = new MediaMuxer(combinedVideoOutFile.getPath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
audioVideoExtractor.setDataSource(audioVideoPath); //设置视频源
//音轨信息
int audioTrackCount = audioVideoExtractor.getTrackCount(); //获取数据源的轨道数
//在此循环轨道数,目的是找到我们想要的音频轨
for (int i = 0; i < audioTrackCount; i++) {
MediaFormat format = audioVideoExtractor.getTrackFormat(i); //得到指定索引的记录格式
String mimeType = format.getString(MediaFormat.KEY_MIME); //主要描述mime类型的媒体格式
if (mimeType.startsWith("audio/")) { //找到音轨
mainAudioExtractorTrackIndex = i;
mainAudioMuxerTrackIndex = muxer.addTrack(format); //将音轨添加到MediaMuxer,并返回新的轨道
mainAudioMaxInputSize = format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE); //得到能获取的有关音频的最大值
// mainAudioDuration = format.getLong(MediaFormat.KEY_DURATION);
}
}
//图像信息
frameVideoExtractor.setDataSource(frameVideoPath); //设置视频源
int trackCount = frameVideoExtractor.getTrackCount(); //获取数据源的轨道数
//在此循环轨道数,目的是找到我们想要的视频轨
for (int i = 0; i < trackCount; i++) {
MediaFormat format = frameVideoExtractor.getTrackFormat(i); //得到指定索引的媒体格式
String mimeType = format.getString(MediaFormat.KEY_MIME); //主要描述mime类型的媒体格式
if (mimeType.startsWith("video/")) { //找到视频轨
frameExtractorTrackIndex = i;
frameMuxerTrackIndex = muxer.addTrack(format); //将视频轨添加到MediaMuxer,并返回新的轨道
frameMaxInputSize = format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE); //得到能获取的有关视频的最大值
frameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE); //获取视频的帧率
frameDuration = format.getLong(MediaFormat.KEY_DURATION); //获取视频时长
}
}
muxer.start(); //开始合成
audioVideoExtractor.selectTrack(mainAudioExtractorTrackIndex); //将提供音频的视频选择到音轨上
MediaCodec.BufferInfo audioBufferInfo = new MediaCodec.BufferInfo();
ByteBuffer audioByteBuffer = ByteBuffer.allocate(mainAudioMaxInputSize);
while (true) {
int readSampleSize = audioVideoExtractor.readSampleData(audioByteBuffer, 0); //检索当前编码的样本并将其存储在字节缓冲区中
if (readSampleSize < 0) { //如果没有可获取的样本则退出循环
audioVideoExtractor.unselectTrack(mainAudioExtractorTrackIndex);
break;
}
long sampleTime = audioVideoExtractor.getSampleTime(); //获取当前展示样本的时间(单位毫秒)
if (sampleTime < audioStartTime) { //如果样本时间小于我们想要的开始时间就快进
audioVideoExtractor.advance(); //推进到下一个样本,类似快进
continue;
}
if (sampleTime > audioStartTime + frameDuration) { //如果样本时间大于开始时间+视频时长,就退出循环
break;
}
//设置样本编码信息
audioBufferInfo.size = readSampleSize;
audioBufferInfo.offset = 0;
audioBufferInfo.flags = audioVideoExtractor.getSampleFlags();
audioBufferInfo.presentationTimeUs = sampleTime - audioStartTime;
muxer.writeSampleData(mainAudioMuxerTrackIndex, audioByteBuffer, audioBufferInfo); //将样本写入
audioVideoExtractor.advance(); //推进到下一个样本,类似快进
}
frameVideoExtractor.selectTrack(frameExtractorTrackIndex); //将提供视频图像的视频选择到视频轨上
MediaCodec.BufferInfo videoBufferInfo = new MediaCodec.BufferInfo();
ByteBuffer videoByteBuffer = ByteBuffer.allocate(frameMaxInputSize);
while (true) {
int readSampleSize = frameVideoExtractor.readSampleData(videoByteBuffer, 0); //检索当前编码的样本并将其存储在字节缓冲区中
if (readSampleSize < 0) { //如果没有可获取的样本则退出循环
frameVideoExtractor.unselectTrack(frameExtractorTrackIndex);
break;
}
//设置样本编码信息
videoBufferInfo.size = readSampleSize;
videoBufferInfo.offset = 0;
videoBufferInfo.flags = frameVideoExtractor.getSampleFlags();
videoBufferInfo.presentationTimeUs += 1000 * 1000 / frameRate;
muxer.writeSampleData(frameMuxerTrackIndex, videoByteBuffer, videoBufferInfo); //将样本写入
frameVideoExtractor.advance(); //推进到下一个样本,类似快进
}
} catch (IOException e) {
Log.e(TAG, "combineTwoVideos: ", e);
} finally {
//释放资源
audioVideoExtractor.release();
frameVideoExtractor.release();
if (muxer != null) {
muxer.release();
}
}
}