前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android用MediaExtractor和MediaMuxer合成音视频

Android用MediaExtractor和MediaMuxer合成音视频

作者头像
蜻蜓队长
发布2018-08-03 11:29:59
2.9K0
发布2018-08-03 11:29:59
举报

前言:

最近在做类似小咖秀的视频录制功能,也就是俗称的对嘴型表演,录制视频我用的是三方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();
            }
        }
    }
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-01-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android机动车 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言:
  • 需求:
  • 工具准备:
  • 开始搞:
    • 1.提取音视频:
      • 2.合成:
        • 3.释放资源:
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档