Android音频学习之利用MediaMuxer从视频中提取视频音频文件和合并视频,音频文件

1 MediaMuxer 说明

前面利用MediaExtractor提取的aac和.h264文件不经过处理没办法播放,这次利用MediaExtractor和MediaMuxer配合提取合并生成可以播放的文件,ps:aac文件和.h264需要首先利用MediaMuxer生成MP4文件,才能进行合并。

MediaMuxer从api18开始提供,可以封装编码后的视频流和音频流到视频文件中。目前MediaMuxer支持的文件输出格式包括MP4,webm和3gp.
构造函数:
MediaMuxer(String path, int format)
MediaMuxer(FileDescriptor fd, int format)
参数说明:
两个构造函数都需要一个文件路径,和一个format。
**path,fd:**用于存放合成的文件的路径,不能为null。
**format:**输出的文件的格式,OutputFormat中的常量标识。
输出格式:
OutputFormat中包含四种:
MUXER_OUTPUT_3GPP:对应3gp文件,
MUXER_OUTPUT_HEIF:定影HEIF文件,
MUXER_OUTPUT_MPEG_4:对应mp4文件,
MUXER_OUTPUT_WEBM:对应webm文件。

不同版本的api对文件格式的支持:
Android音频学习之利用MediaMuxer从视频中提取视频音频文件和合并视频,音频文件_第1张图片
看上面的表的支持程度知道,一般为了支持更低的版本,合称单音频,单视频的mp4文件在api18时就支持。如果是多个音频和视频的track要怎么处理呢,只能先两个两个的合并,最后剩下两个再合并。

主要函数说明:
addTrack(MediaFormat format):利用MediaFormat添加音频或视频轨道。
release():释放MediaMuxer的资源。
setLocation(float latitude,float longitude):设置并存储地理位置信息到生成文件中。
setOrientationHint(int degrees):设置输出视频回放的方向提示。
start() :开始muxer,等待数据的输入。
Stop():停止muxer,调用这个函数后将生成合成的文件。
writeSampleData(int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo):
往muxer中写入编码的数据。
参数说明:
trackIndex:sample(样本)的track index,可以利用MediaExtractor获取。
byteBuf:写入的数据,不可为null;
bufferInfo:当前sample(样本)相关的buffer的信息,不可为null。

MediaCodec.BufferInfo:
主要包括四个数据:
flags:输入buffer相关的buffer flags。
offset:buffer开始的偏移量,通常设为0。
presentationTimeUs:buffer的时间戳。
size:buffer的数据大小。

2 MediaMuxer使用说明

MediaMuxer的使用模板,来自Developer:

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();

使用过程介绍:

  • (1)生成MediaMuxer对象
    通过new MediaMuxer(String path, int format)指定视频文件输出路径和文件格式:
    MediaMuxer mMediaMuxer = new MediaMuxer(mOutputVideoPath,
    MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

  • (2)addTrack
    addTrack(MediaFormat format),添加媒体通道,传入MediaFormat对象,通常从MediaExtractor或者MediaCodec中获取,也可以自己创建,后面会有文章说明。
    addTrack会返回trackindex,这个index后面会用到。

  • (3) 调用start函数
    MediaMuxer.start();

  • (4) 写入数据
    调用MediaMuxer.writeSampleData()向mp4文件中写入数据了。每次只能添加一帧视频数据或者单个Sample的音频数据,需要BufferInfo对象作为参数。
    BufferInfo info = new BufferInfo();
    info.offset = 0;
    info.size = sampleSize;
    info.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME;
    info.presentationTimeUs = mVideoExtractor.getSampleTime();
    mMediaMuxer.writeSampleData(videoTrackIndex, buffer, info);
    info.size 必须填入数据的大小
    info.flags 需要给出是否为同步帧/关键帧
    info.presentationTimeUs 必须给出正确的时间戳,注意单位是 us,第二次getSampleTime()和首次getSampleTime()的时间差。

  • (5)释放关闭资源
    结束写入后关闭以及释放资源:
    MediaMuxer.stop();
    MediaMuxer.release();

3 利用MediaMuxer生成文件

3.1从MP4文件中提取视频和音频文件,提取之后的视频可以播放但是没有声音,提取之后的声音可以播放但是没有视频画面。

private void muxerdata() {
    String srcPath = Environment.getExternalStorageDirectory()
            .getPath() + "/video/video.mp4";

    String dirP = Environment.getExternalStorageDirectory()
            .getPath() + "/demo2";
    String fPath1 = Environment.getExternalStorageDirectory()
            .getPath() + "/demo2/demo1.mp4";
    String fPath2 = Environment.getExternalStorageDirectory()
            .getPath() + "/demo2/demo2.mp4";
    File file = new File(dirP);
    if (!file.exists()){
        file.mkdir();
    }

    File file1 = new File(fPath1);
    File file2 = new File(fPath2);
    try {
        if (file1.exists()){
            file1.delete();

        }
        file1.createNewFile();
    } catch (IOException e) {
        e.printStackTrace();
    }

    try {
        if (file2.exists()){
            file2.delete();
        }
        file2.createNewFile();
    } catch (IOException e) {
        e.printStackTrace();
    }

    MediaMuxer mMediaMuxer;
    int mVideoTrackIndex = 0;
    int mAudioTrackIndex = 0;
    long frameRate;

    try {
        mediaExtractor = new MediaExtractor();//此类可分离视频文件的音轨和视频轨道
        mediaExtractor.setDataSource(srcPath);//媒体文件的位置
        System.out.println("==========getTrackCount()===================" + mediaExtractor.getTrackCount());
        for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {
            MediaFormat format = mediaExtractor.getTrackFormat(i);
            String mime = format.getString(MediaFormat.KEY_MIME);
            if (mime.startsWith("audio")) {//获取音频轨道
                ByteBuffer buffer = ByteBuffer.allocate(100 * 1024);
                {   mediaExtractor.selectTrack(i);//选择此音频轨道
                mediaExtractor.readSampleData(buffer, 0);
                long first_sampletime = mediaExtractor.getSampleTime();
                mediaExtractor.advance();
                long second_sampletime = mediaExtractor.getSampleTime();
                frameRate = Math.abs(second_sampletime - first_sampletime);//时间戳
                mediaExtractor.unselectTrack(i);
               }
                mediaExtractor.selectTrack(i);
                System.out.println("==============frameRate111=============="+frameRate+"");
                mMediaMuxer = new MediaMuxer(fPath2, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
                mAudioTrackIndex = mMediaMuxer.addTrack(format);
                mMediaMuxer.start();

                MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
                info.presentationTimeUs = 0;

                int sampleSize = 0;
                while ((sampleSize = mediaExtractor.readSampleData(buffer, 0)) > 0) {
                    info.offset = 0;
                    info.size = sampleSize;
                    info.flags = mediaExtractor.getSampleFlags();
                    info.presentationTimeUs += frameRate;
                    mMediaMuxer.writeSampleData(mAudioTrackIndex, buffer, info);
                    mediaExtractor.advance();
                }

                mMediaMuxer.stop();
                mMediaMuxer.release();

            }

            if (mime.startsWith("video")){
                mediaExtractor.selectTrack(i);//选择此视频轨道
                frameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
                System.out.println("==============frameRate222=============="+ 1000 * 1000 / frameRate+"");
                mMediaMuxer = new MediaMuxer(fPath1, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
                mVideoTrackIndex = mMediaMuxer.addTrack(format);
                mMediaMuxer.start();

                MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
                info.presentationTimeUs = 0;
                ByteBuffer buffer = ByteBuffer.allocate(100 * 1024);
                int sampleSize = 0;
                while ((sampleSize = mediaExtractor.readSampleData(buffer, 0)) > 0) {
                    info.offset = 0;
                    info.size = sampleSize;
                    info.flags = mediaExtractor.getSampleFlags();
                    info.presentationTimeUs += 1000 * 1000 / frameRate;
                    mMediaMuxer.writeSampleData(mVideoTrackIndex, buffer, info);
                    mediaExtractor.advance();
                }

                mMediaMuxer.stop();
                mMediaMuxer.release();

            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        mediaExtractor.release();
        mediaExtractor = null;
    }

}

用到的MP4文件和提取后生成的mp4文件:
https://pan.baidu.com/s/1xyeJEpC_Zff6hgIoZBKt-w

3.2把提取出来的视频和音频合并:

private void muxermergedata() {
    String desPath = Environment.getExternalStorageDirectory()
            .getPath() + "/demo2/video.mp4";

    String dirP = Environment.getExternalStorageDirectory()
            .getPath() + "/demo2";
    String fPath1 = Environment.getExternalStorageDirectory()
            .getPath() + "/demo2/demo1.mp4";
    String fPath2 = Environment.getExternalStorageDirectory()
            .getPath() + "/demo2/demo2.mp4";
    File file = new File(dirP);
    if (!file.exists()){
        file.mkdir();
    }

    File file1 = new File(fPath1);
    File file2 = new File(fPath2);
    File filedes = new File(desPath);

    try {
        if (filedes.exists()){
            filedes.delete();
        }
        filedes.createNewFile();
    } catch (IOException e) {
        e.printStackTrace();
    }

    MediaMuxer mMediaMuxer = null;

    try {
        mMediaMuxer = new  MediaMuxer(desPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
    } catch (IOException e) {
        e.printStackTrace();
    }
    int mVideoTrackIndex = 0;
    int mAudioTrackIndex = 0;
    long frameRate1 = 0;
    long frameRate2 = 0;

    MediaFormat format1;
    MediaFormat format2;
    try {
        mediaExtractor = new MediaExtractor();//此类可分离视频文件的音轨和视频轨道
        mediaExtractor.setDataSource(fPath1);//媒体文件的位置
        System.out.println("==========getTrackCount()===================" + mediaExtractor.getTrackCount());
        for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {
            format1 = mediaExtractor.getTrackFormat(i);
            String mime = format1.getString(MediaFormat.KEY_MIME);

            if (mime.startsWith("video")){
                mediaExtractor.selectTrack(i);//选择此视频轨道
                frameRate1 = format1.getInteger(MediaFormat.KEY_FRAME_RATE);
                System.out.println("==============frameRate222=============="+ 1000 * 1000 / frameRate1+"");
                mVideoTrackIndex = mMediaMuxer.addTrack(format1);

            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    try {
        mediaExtractor2 = new MediaExtractor();//此类可分离视频文件的音轨和视频轨道
        mediaExtractor2.setDataSource(fPath2);//媒体文件的位置
        System.out.println("==========getTrackCount()===================" + mediaExtractor2.getTrackCount());
        for (int i = 0; i < mediaExtractor2.getTrackCount(); i++) {
            format2 = mediaExtractor2.getTrackFormat(i);
            String mime = format2.getString(MediaFormat.KEY_MIME);
            if (mime.startsWith("audio")) {//获取音频轨道
                ByteBuffer buffer = ByteBuffer.allocate(100 * 1024);
                {
                    mediaExtractor2.selectTrack(i);//选择此音频轨道
                    mediaExtractor2.readSampleData(buffer, 0);
                    long first_sampletime = mediaExtractor2.getSampleTime();
                    mediaExtractor2.advance();
                    long second_sampletime = mediaExtractor2.getSampleTime();
                    frameRate2 = Math.abs(second_sampletime - first_sampletime);//时间戳
                    mediaExtractor2.unselectTrack(i);
                }
                mediaExtractor2.selectTrack(i);
                System.out.println("==============frameRate111==============" + frameRate2 + "");
                mAudioTrackIndex = mMediaMuxer.addTrack(format2);

            }

        }
    } catch (IOException e) {
        e.printStackTrace();
    }


    mMediaMuxer.start();
    MediaCodec.BufferInfo info1 = new MediaCodec.BufferInfo();
    info1.presentationTimeUs = 0;
    ByteBuffer buffer = ByteBuffer.allocate(100 * 1024);
    int sampleSize1 = 0;
    while ((sampleSize1 = mediaExtractor.readSampleData(buffer, 0)) > 0) {
        info1.offset = 0;
        info1.size = sampleSize1;
        info1.flags = mediaExtractor.getSampleFlags();
        info1.presentationTimeUs += 1000 * 1000 / frameRate1;
        mMediaMuxer.writeSampleData(mVideoTrackIndex, buffer, info1);
        mediaExtractor.advance();
    }


    MediaCodec.BufferInfo info2 = new MediaCodec.BufferInfo();
    info2.presentationTimeUs = 0;

    int sampleSize2 = 0;
    while ((sampleSize2 = mediaExtractor2.readSampleData(buffer, 0)) > 0) {
        info2.offset = 0;
        info2.size = sampleSize2;
        info2.flags = mediaExtractor2.getSampleFlags();
        info2.presentationTimeUs += frameRate2;
        mMediaMuxer.writeSampleData(mAudioTrackIndex, buffer, info2);
        mediaExtractor2.advance();
    }

    try {
        mediaExtractor.release();
        mediaExtractor = null;
        mediaExtractor2.release();
        mediaExtractor2 = null;
        mMediaMuxer.stop();
        mMediaMuxer.release();
    } catch (Exception e) {
        e.printStackTrace();
    }

}

如何利用MediaCodec对视频原始数据进行编解码,敬请期待后面的文章。

你可能感兴趣的:(Android音视频学习,Android,音视频处理学习专题)