Android平台的MediaMuxer是个非常好的录像库,它能将H.264视频+AAC音频存储成.mp4格式的文件,而且稳定性、同步效果都非常好。MediaMuxer在安卓版的EasyPlayerEasyPusher都用到了该方法来进行本地录像。

作者也写过两篇针对性的博客来做介绍,参考:EasyPlayer实现播放时同步录像的功能、
EasyPusher实现安卓Android手机直播推送同步录像功能(源码解析)。对于不知道的网友,本篇文章将详细介绍如何定义MediaMuxer接口。

MediaMuxer的接口定义相对而言比较简单,调用过程如下所示:

```
mermaid
flowchat
e=>end: MP4 文件
op1=>operation: 创建MediaMuxer
op2=>operation: Stuff|current
sub1=>subroutine: My Subroutine|invalid
cond1=>condition: 视频还是音频?
c2=>operation: AddVideoTrack、AddAudioTrack
c4=>operation: writeAudioSample
c5=>operation: writeVideoSample
io=>inputoutput: stop release
io1=>inputoutput: start|future

op1->c2->io1
io1->cond1
cond1(yes)->c5
cond1(no)->c4(right)
c4->io
c5->io
io->e
```

简单来说,就是创建对象、添加音视频轨道、开始、持续写入音视频数据、关闭这样一个过程。

遗憾的是,MediaMuxer并不支持对除AAC以外的音频编码格式的封装,然而在安防行业里G711音频格式的数据是大多数设备的默认编码格式。

如何支持G711格式的数据呢? 换种思路就会豁然开朗,我们可以先把G711数据解码成PCM,再用MediaCodec编码成AAC,这样曲线存储。不光是G711,所有的音频编码格式都可以这样做。

所以前面的流程图里,writeAudioSample的部分就变成这样了:

```mermaid
flowchat
st=>start: 音频
e=>end: writeAudioSample
cond=>condition: AAC格式数据?
c1=>operation: writeAudioSample
c2=>operation: decode
c3=>inputoutput: pcm
c4=>operation: encode
c5=>inputoutput: aac
st->cond
cond(yes)->e
cond(no)->c2
c2(right)->c3
c3(right)->c4
c4(right)->c5
c5->e
```

下面是将解码后的PCM数据塞入Muxer的代码片段:

```
package org.easydarwin.audio;

import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.util.Log;

import org.easydarwin.video.EasyMuxer;

import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * 对EasyMuxer的扩展,支持对PCM格式的音频打包。
 */
public class EasyAACMuxer extends EasyMuxer {
    MediaCodec mMediaCodec;
    String TAG = "EasyAACMuxer";

    protected MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
    protected ByteBuffer[] mBuffers = null;

    private MediaFormat mAudioFormat;

    public EasyAACMuxer(String path, long durationMillis) {
    super(path, durationMillis);
    }

    @Override
    public synchronized void addTrack(MediaFormat format, boolean isVideo) {
    super.addTrack(format, isVideo);
    if (!isVideo){
            mAudioFormat = format;
        }
    }

    public synchronized void pumpPCMStream(byte []pcm, int length, long timeUs) throws IOException {
        if (mMediaCodec == null) {// 启动AAC编码器。这里用MediaCodec来编码
            if (mAudioFormat == null) return;
            mMediaCodec = MediaCodec.createEncoderByType("audio/mp4a-latm");
            Log.i(TAG, String.valueOf(mAudioFormat));
            mAudioFormat.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
            mAudioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE,MediaCodecInfo.CodecProfileLev    el.AACObjectLC);
            mAudioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 16000);
//            mAudioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 320);

            mMediaCodec.configure(mAudioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            mMediaCodec.start();
            mBuffers = mMediaCodec.getOutputBuffers();
        }
        int index = 0;
        // 将pcm编码成AAC
        do {
            index = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 1000);
            if (index >= 0) {
                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                    continue;
                }
                if (mBufferInfo.presentationTimeUs == 0){
                    continue;
                }
                if (VERBOSE) Log.d(TAG,String.format("dequeueOutputBuffer data length:%d,tmUS:%d", mBufferInfo.size, mBufferInfo.presentationTimeUs));
                ByteBuffer outputBuffer = mBuffers[index];
                // ok,编码成功了。将AAC数据写入muxer.
                pumpStream(outputBuffer, mBufferInfo, false);
                mMediaCodec.releaseOutputBuffer(index, false);
            } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                mBuffers = mMediaCodec.getOutputBuffers();
            } else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                Log.v(TAG, "output format changed...");
                MediaFormat newFormat = mMediaCodec.getOutputFormat();
                Log.v(TAG, "output format changed..." + newFormat);
            } else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
                Log.v(TAG, "No buffer available...");
            } else {
                Log.e(TAG, "Message: " + index);
            }
        } while (index >= 0 && !Thread.currentThread().isInterrupted());

        final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
        do {
            index = mMediaCodec.dequeueInputBuffer(1000);
            if (index >= 0) {
                inputBuffers[index].clear();
                inputBuffers[index].put(pcm, 0, length);
                if (VERBOSE) Log.d(TAG,String.format("queueInputBuffer pcm data length:%d,tmUS:%d", length, timeUs));
                mMediaCodec.queueInputBuffer(index, 0, length, timeUs, 0);
            }
        }    
        while (!Thread.currentThread().isInterrupted() && index < 0);
    }

    @Override
    public synchronized void release() {
        if (mMediaCodec != null) mMediaCodec.release();
        mMediaCodec = null;
        super.release();
    }
}

```

一切都在代码中,不再过多解释,至此结束。

MediaMuxer扩展应用

安卓版的EasyPlayerEasyPusher都用到了MediaMuxer来进行本地录像。

EasyPusher推流是EasyDarwin开源流媒体团队开发的一款推送流媒体音/视频流给标准RTSP流媒体服务器(如EasyDarwin、Wowza)的流媒体推送库,全平台支持(包括Windows/Linux(32 & 64),ARM各平台,Android、iOS),通过EasyPusher我们就可以避免接触到稍显复杂的RTSP/RTP/RTCP推送流程,只需要调用EasyPusher的几个API接口,就能轻松、稳定地把流媒体音视频数据推送给RTSP流媒体服务器进行处理和转发,EasyPusher经过长时间的企业用户体验,稳定性非常高。

android exoplayer2 结合seekbar elecardplayer安卓_Android MediaMuxer录像


android exoplayer2 结合seekbar elecardplayer安卓_支持MP4_02


EasyPlayer是由紫鲸团队开发和维护的一个流媒体播放器系列项目,随着多年不断的发展和迭代,不断基于成功的实践经验,发展出包括有:

EasyPlayer RTSP播放器(Windows / Android / iOS)

EasyPlayer RTMP播放器(Windows / Android / iOS)

EasyPlayerPro 全功能播放器(Windows / Android / iOS)