Android音视频-视频分解与合成(MP4文件)

上一篇们通过Camera的API结合MediaRecorder实现了视频的录制,具体的代码也大致的了解了。使用起来不是很难,这次得加大对视频的理解。在视频的基础知识里面我们了解了一些视频的相关的概念和名词,这篇文章我们搞清楚视频的组成,视频分离,视频的合成等概念和实现方法,这里操作的是MP4文件,其他的文件格式我觉得按照这个思路去了解应该也问题不大。

简介

查阅网上各种资料以后解析和分离视频大致有如下三种方式

  • Android系统自带API MediaExtractor和MediaMuxer类
    MediaExtractor是API16之后出来的,MediaMuxer是API18之后出来的

  • Mp4Parser
    一个开源的项目,好想功能很强大

  • FFMepg
    一个终极武器,我们应该都听过。

我们现在了解的是MediaExtractor和MediaMuxer类相关的来实现。

完整示例代码查看

MP4文件简介

维基百科定义

MP4,全称MPEG-4 Part 14,是一种使用MPEG-4的多媒体电脑文件格式,扩展名为.mp4,以存储数字音频及数字视频为主。
这个看了并没啥用,我们现在要关心的是它的一个总体的结构,里面的构成细节实在是太多了。推荐细节介绍文章现在也没去看里面的东西,要细节了解的时候看。

MP4文件结构

有几个基本的概念大概的了解一下

  • box
    MP4文件中的所有数据都封装在box中,MP4文件由若干个box组成,每个box有类型和长度。并且有不同的box类型

MP4文件主要由不同的box组成,ftyp,mdat和moov。fypt指示一些头部信息(知道的MetaData),可以判断文件的类型。mdat存放媒体数据,我们在使用MediaRecorder的时候设置的视频和音频的一些数据就放在它里面。moov包含一个mvhd和若干个track box,它的功能是解析mdat里面的数据,并且拿到每一帧的数据,怎么个原理弄到数据,参考下面的参考文章。

里面的细节现在我们也扣不完,先有一个大致的概念,等要深入了解的时候再入手,上面的几个概念参考自
这里
不错的MP4数据结构细节介绍

分离视频文件

对于一个视频文件它包含视频和音频数据,我们现在把它分离出来得到声音和图像数据。我们使用MediaExtractor可以实现这个功能

MediaExtractor

MediaExtractor便于从数据源中提取解析多媒体数据。我们可以使用这个它强大的API来实现我们的功能。它使用的大致模版参考官网给出的

MediaExtractor extractor = new MediaExtractor();
 extractor.setDataSource(...);
 int numTracks = extractor.getTrackCount();
 for (int i = 0; i < numTracks; ++i) {
   MediaFormat format = extractor.getTrackFormat(i);
   String mime = format.getString(MediaFormat.KEY_MIME);
   if (weAreInterestedInThisTrack) {
     extractor.selectTrack(i);
   }
 }
 ByteBuffer inputBuffer = ByteBuffer.allocate(...)
 while (extractor.readSampleData(inputBuffer, ...) >= 0) {
   int trackIndex = extractor.getSampleTrackIndex();
   long presentationTimeUs = extractor.getSampleTime();
   ...
   extractor.advance();
 }

 extractor.release();
 extractor = null;

MediaMuxer

这个类可以合成音频和视频,同样它还可以把我们从一个MP4中分离出来的音频和视频的轨道信息生成一个单独的视频和音频文件,非常的强大。我们下面的视频合成的例子就是它和MediaExtractor来实现的。

MediaMuxer最多仅支持一个视频track和一个音频track,所以如果有多个音频track可以先把它们混合成为一个音频track然后再使用MediaMuxer封装到mp4容器中。

它的使用大致代码也是参考官网

MediaMuxer muxer = new MediaMuxer("temp.mp4", OutputFormat.MUXER_OUTPUT_MPEG_4);
 // More often, the MediaFormat will be retrieved from MediaCodec.getOutputFormat()
 // or MediaExtractor.getTrackFormat().
 MediaFormat audioFormat = new MediaFormat(...);
 MediaFormat videoFormat = new MediaFormat(...);
 int audioTrackIndex = muxer.addTrack(audioFormat);
 int videoTrackIndex = muxer.addTrack(videoFormat);
 ByteBuffer inputBuffer = ByteBuffer.allocate(bufferSize);
 boolean finished = false;
 BufferInfo bufferInfo = new BufferInfo();

 muxer.start();
 while(!finished) {
   // getInputBuffer() will fill the inputBuffer with one frame of encoded
   // sample from either MediaCodec or MediaExtractor, set isAudioSample to
   // true when the sample is audio data, set up all the fields of bufferInfo,
   // and return true if there are no more samples.
   finished = getInputBuffer(inputBuffer, isAudioSample, bufferInfo);
   if (!finished) {
     int currentTrackIndex = isAudioSample ? audioTrackIndex : videoTrackIndex;
     muxer.writeSampleData(currentTrackIndex, inputBuffer, bufferInfo);
   }
 };
 muxer.stop();
 muxer.release();

分离视频主要代码

主要过程为

  • 构建源文件和MediaExtractor关联
  • 获取源文件Mp4中的视频和音频信道并且和MediaMuxer关联
  • 开始解析音频或者视频文件
  • 解析文件两帧数据的时间
  • 解析写入数据
  • 释放资源

通常视频编码使用H.264(AVC)编码,音频编码使用AAC编码,我们的MP4文件也是这两种编码方式。

public void divideMedia(AssetFileDescriptor inputFile, File outAudioFile, File outVideoFile) {
        MediaExtractor mediaExtractor = new MediaExtractor();
        try {
            //设置要分离的原始MP4文件
            mediaExtractor.setDataSource(inputFile);
            //获取文件信道数目
            int trackCount = mediaExtractor.getTrackCount();
            for (int i = 0; i < trackCount; i++) {
                //获取当前轨道的媒体格式
                MediaFormat mediaFormat = mediaExtractor.getTrackFormat(i);
                //获取媒体格式的mime type(我们的为video/avc 和audio/)
                String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
                //获取轨道文件最大值
                int maxInputSize = mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
                Log.d(TAG, "maxInputSize:" + maxInputSize);
                ByteBuffer byteBuffer = ByteBuffer.allocate(maxInputSize);
                if (mime.startsWith(AUDIO_MIME)) {
                    Log.d(TAG, "divide audio media to file +");
                    //构建音频文件合成对象MediaMuxer
                    MediaMuxer mediaMuxer = new MediaMuxer(outAudioFile.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
                    //给MediaMuxer添加信道的MediaFormat
                    int audioTrack = mediaMuxer.addTrack(mediaFormat);
                    mediaMuxer.start();

                    divideToOutputAudio(mediaExtractor, mediaMuxer, byteBuffer, mediaFormat, audioTrack, i);

                    //停止MediaMuxer释放资源
                    mediaMuxer.stop();
                    mediaMuxer.release();
                    mediaMuxer = null;
                    Log.d(TAG, "divide audio media to file -");
                } else if (mime.startsWith(VIDEO_MIME)) {
                    Log.d(TAG, "divide video media to file +");
                    MediaMuxer mediaMuxer = new MediaMuxer(outVideoFile.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
                    int videoTrack = mediaMuxer.addTrack(mediaFormat);
                    mediaMuxer.start();

                    divideToOutputVideo(mediaExtractor, mediaMuxer, byteBuffer, mediaFormat, videoTrack, i);

                    mediaMuxer.stop();
                    mediaMuxer.release();
                    mediaMuxer = null;
                    Log.d(TAG, "divide video media to file -");
                }
            }
            mediaExtractor.release();
            mediaExtractor = null;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

主要通过一个MediaExtractor来引入Mp4源文件,然后要分离的音频和视频文件连接到各自的MediaMuxer对象,操作过程较为固定。

合成视频文件

合成视频的过程和分离视频的过程很类似

  • 要输出的MP4文件和MediaMuxer关联
  • 根据要解析的音频和视频构建个字的MediaExtractor对象关联
  • 获取两个文件各自的信道信息
  • MediaMuxer添加信道
  • 开始解析帧数据
  • 解析文件两帧数据的时间
  • 解析写入数据
  • 释放资源

主要代码如下:

public void combineVideo(File inputVideoFile, File inputAudioFile, File outputVideoFile) {
        MediaExtractor videoExtractor = new MediaExtractor();
        MediaExtractor audioExtractor = new MediaExtractor();
        MediaMuxer mediaMuxer = null;
        try {
            mediaMuxer = new MediaMuxer(outputVideoFile.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

            // set data source
            videoExtractor.setDataSource(inputVideoFile.getAbsolutePath());
            audioExtractor.setDataSource(inputAudioFile.getAbsolutePath());

            // get video or audio 取出视频或音频的信号
            int videoTrack = getTrack(videoExtractor, true);
            int audioTrack = getTrack(audioExtractor, false);

            // change to video oraudio track 切换道视频或音频信号的信道
            videoExtractor.selectTrack(videoTrack);
            MediaFormat videoFormat = videoExtractor.getTrackFormat(videoTrack);
            audioExtractor.selectTrack(audioTrack);
            MediaFormat audioFormat = audioExtractor.getTrackFormat(audioTrack);

            //追踪此信道
            int writeVideoIndex = mediaMuxer.addTrack(videoFormat);
            int writeAudioIndex = mediaMuxer.addTrack(audioFormat);
            mediaMuxer.start();

            // 读取写入帧数据
            writeSampleData(videoExtractor, mediaMuxer, writeVideoIndex, videoTrack);
            writeSampleData(audioExtractor, mediaMuxer, writeAudioIndex, audioTrack);
        } catch (IOException e) {
            Log.w(TAG, "combineMedia ex", e);
        } finally {
            try {
                if (mediaMuxer != null) {
                    mediaMuxer.stop();
                    mediaMuxer.release();
                }
                if (videoExtractor != null) {
                    videoExtractor.release();
                }
                if (audioExtractor != null) {
                    audioExtractor.release();
                }
            } catch (Exception e) {
                Log.w(TAG, "combineMedia release ex", e);
            }
        }
    }

参考链接:主要代码参考

猜你喜欢

转载自blog.csdn.net/lyman_ye/article/details/78990405