Android音视频学习(三):MediaMuxer

471 阅读2分钟

0.前言

封装操作,就是将已经编码压缩好的视频轨和音频轨按照一定的格式放到一个文件中,常见的封装格式(也叫容器)有mp4,flv,avi等等。
在前面的文章中,我们讨论了Android的解封装组件MediaExtracter,对应的,也有与之相反的封装组件MediaMuxer。

1. MediaMuxer介绍

MediaMuxer是Android多媒体框架中的封装组件,一般与MediaExtracter配合使用,可以将分离的视频流、音频流写入到对应的容器文件中,目前MediaMuxer支持MP4、Webm、3GP、HEIF、OGG格式的输出,并且支持B帧视频的写入。

2. 主要使用流程

graph TD
创建MediaMuxer
--> addTrack添加视频/音频流
--> start
--> writeSampleData写入帧信息 --> writeSampleData写入帧信息
--> stop
--> release释放

3. 常用接口介绍

3.1 MediaMuxer构造方法

public static final class OutputFormat {
    public static final int MUXER_OUTPUT_3GPP = 2;
    public static final int MUXER_OUTPUT_HEIF = 3;
    public static final int MUXER_OUTPUT_MPEG_4 = 0;
    public static final int MUXER_OUTPUT_OGG = 4;
    public static final int MUXER_OUTPUT_WEBM = 1;
}
// path参数是输出文件路径,format参数是上述输出格式的枚举值
public MediaMuxer(String path, int format);

3.2 addTrack()

// 添加输入流信息,需要指定MIME,csd-0,csd-1等信息,对于转封装的任务,可以从原视频流中拷贝过来
public int addTrack(MediaFormat format);

3.3 start()

开始写入

3.4 writeSampleData()

// trackIndex是新写入流的id,可以由addTrack的返回值确认,
// byteBuf是写入Buffer的包装
// bufferInfo是与buffer相关联的一些信息,如size,offset,显示时间等
writeSampleData(int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo)

3.5 stop()

停止写入

3.6 release()

释放底层资源

4. 例子

以转封装为例来说明MeidiaMuxer的用法

protected void remux() {
    String filename = Environment.getExternalStorageDirectory().getPath() + "/" + getString(R.string.test_file);
    try {
        MediaFormat mediaFormat = null;
        extractor = new MediaExtractor();
        extractor.setDataSource(filename);
        // 获取轨道数量
        int trackCount = extractor.getTrackCount();
        int i = 0;
        for(; i < trackCount; ++i) {
            mediaFormat = extractor.getTrackFormat(i);
            String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
            // 选中一路视频流
            if(mime.startsWith("video")) {
                break;
            }
        }

        extractor.selectTrack(i);
        extractor.seekTo(10000000, SEEK_TO_PREVIOUS_SYNC);
        // frameRate用于设定后续封装时帧的播放时间,maxBufferSize确认读取时最大的buffer大小
        int frameRate = mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE);
        int maxBufferSize = mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);

        MediaMuxer muxer = new MediaMuxer(getApplicationContext().getExternalFilesDir(null).getPath() + "/out_" + i +".mp4", MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
        int newTrackIndex = muxer.addTrack(mediaFormat);
        muxer.start();

        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
        info.presentationTimeUs = 0;
        ByteBuffer buffer = ByteBuffer.allocate(maxBufferSize);
        int sampleSize = 0;

        while((sampleSize = extractor.readSampleData(buffer, 0)) > 0) {
            info.offset = 0;
            info.size = sampleSize;
            info.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME;
            info.presentationTimeUs += 1000 * 1000 / frameRate;
            muxer.writeSampleData(newTrackIndex, buffer, info);
            extractor.advance();
        }

        muxer.stop();
        muxer.release();

        extractor.release();

    } catch(Exception e) {
        Log.e(TAG, e.toString());
    }
}