Android用MediaExtractor和MediaMuxer合成音视频

前言:

最近在做类似小咖秀的视频录制功能,也就是俗称的对嘴型表演,录制视频我用的是三方SDK,但是视频合成就需要自己搞了,在网上搜了挺多资料,国内国外网站看了不少,踩了很多坑,总算整出来了,在此分享给大家,希望对以后要做类似功能的兄弟们有所帮助!

需求:

将视频一的音频提取出来,视频二的视频图像提取出来,然后把它们合成新的视频。

工具准备:

视频的分离合成我主要用到了MediaExtractor和MediaMuxer两个类: MediaExtractor是用于提取多路的、通常编码的视频资源的,通过它我们可以选择音频或者视频轨,然后分别对它们进行操作等; MediaMuxer是用于复用基本流的,用它可以将音频和视频合成,目前支持输出MP4,Webm和3GP格式的视频,在Android7.0以后支持多路复用帧的MP4。 MediaFormat封装了描述媒体数据格式的信息,如音频或视频,通过它我们可以取出音频或者视频。

开始搞:

1.提取音视频:

我们将视频一的路径通过setDataSource方法设置给MediaExtractor对象,然后通过方法getTrackCount获取到该视频的轨道数,接着循环轨道数,此时我们可以通过MediaExtractor对象的getTrackFormat方法获取到MediaFormat,然后找到我们想要的音频轨,还记得MediaMuxer类是用来合成的吧,那么我们将找到的音频轨通过addTrack方法设置给MediaMuxer对象,这里有个小细节需要注意,我们需要记录两个音频轨,一个是原视频的(旧轨道),一个是将来合成的视频的(新轨道),之后会用到。 OK,视频一的音频已经提取出来啦,那么我们用相似的方法将视频二的视频图像提取出来,也通过addTrack方法设置给同一个MediaMuxer对象,不同的是我们要获取到视频的帧率,并且在之后合成的时候需要处理一下。

2.合成:

那么到这里,视频一的音频和视频二的视频图像都已经设置给MediaMuxer对象了,我们就可以合成啦,还记得我们在找音频和视频的时候记录下的新旧轨道吧,现在通过MediaExtractor对象的selectTrack(旧轨道)方法选择到原视频的我们想要的音轨或视频轨,并取到样本,设置编码信息,然后通过MediaMuxer对象的 writeSampleData(新轨道,样本,编码信息)方法对音频和视频分别进行写入,到此新的视频就合成啦!

3.释放资源:

最后,不要忘记释放资源哦调用它们的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();
            }
        }
    }

原文发布于微信公众号 - Android机动车(JsAndroidClub)

原文发表时间:2018-01-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏ThoughtWorks

一张漂亮的可视化图表背后|洞见

可视化之根 多年前读过一篇非常震撼的文章,叫《Lisp之根》(英文版:The roots of Lisp),大意是Lisp仅仅通过一种数据结构(列表)和有限的几...

37370
来自专栏智能算法

Python相关机器学习‘武器库’

开始学习Python,之后渐渐成为我学习工作中的第一辅助脚本语言,虽然开发语言是Java,但平时的很多文本数据处理任务都交给了Python。这些年来,接触和...

10730
来自专栏web前端教室

如何学习才能见到效果

几乎每个新入行的人都会问,有没有什么好的或正确的学习方法? 有的。 但误区在于,他们总是把好的或正确的学习方法,当成了“快”的学习方法。好或正确,并不意味着快;...

21680
来自专栏WOLFRAM

美国国家数学博物馆(MoMath)的可计算馆标

23850
来自专栏智能算法

Python相关机器学习‘武器库’

开始学习Python,之后渐渐成为我学习工作中的第一辅助脚本语言,虽然开发语言是Java,但平时的很多文本数据处理任务都交给了Python。这些年来,接触和使...

43460
来自专栏数据科学与人工智能

【Python环境】Python 网页爬虫 &文本处理 & 科学计算 &机器学习 &数据挖掘兵器谱

曾经因为NLTK的缘故开始学习Python,之后渐渐成为我工作中的第一辅助脚本语言,虽然开发语言是C/C++,但平时的很多文本数据处理任务都交给了Python。...

26990
来自专栏图形学与OpenGL

CG实验1 三角形绘制

请参考教材博客网页文章:《WebGL画点程序v1-3》,《 WebGL绘制三角形》,具体见:http://blog.csdn.net/wpxu08

12530
来自专栏郭艺帆的专栏

ARKit 进阶:物理世界

ARKit的渲染能力是由其他框架实现的,除了苹果的SceneKit, Unity3D、UE, 或者其他自定义的OpenGL、Metal渲染引擎都可以与ARKit...

56370
来自专栏漫漫全栈路

数据库E-R模型关系图

早在专科阶段学习SqlServers时就学习过数据库E-R图,但是并没有真正的去了解这个东西,只是知道了大致的概念而已,借这次Oracle课程设计的机会,重新...

66040
来自专栏Data Analysis & Viz

Python网页爬虫&文本处理&科学计算&机器学习&数据挖掘兵器谱

周末时看到这篇不错的文章,其中介绍了诸多python第三方库和工具,与大家分享下,也算是门可罗雀的本号第一次转载文章。后续看到精彩的文章也会继续分享。

17540

扫码关注云+社区

领取腾讯云代金券