Android MediaMuxer錄像(支持G711音頻)

Android平臺的MediaMuxer是個非常好的錄像庫,它能將H.264視頻+AAC音頻存儲成.mp4格式的文件,而且穩定性、同步效果都非常好。

MediaMuxer在安卓版的EasyPlayer和EasyPusher都用到了該方法來進行本地錄像。作者也寫過兩篇針對性的博客來做介紹,參考:
http://blog.csdn.net/jyt0551/article/details/60152344
http://blog.csdn.net/jyt0551/article/details/58714595

MediaMuxer的接口定義相對而言比較簡單,調用過程如下圖所示。

Created with Raphaël 2.1.0創建MediaMuxerAddVideoTrack、AddAudioTrackstart視頻還是音頻?writeVideoSamplestop releaseMP4 文件writeAudioSampleyesno

簡單來說,就是創建對象、添加音視頻軌道、開始、持續寫入音視頻數據、關閉這樣一個過程。

遺憾的是,MediaMuxer並不支持對除AAC以外的音頻編碼格式的封裝,然而在安防行業裏G711音頻格式的數據是大多數設備的默認編碼格式。

如何支持G711格式的數據呢?其實換種思路就會豁然開朗,我們可以先把G711數據解碼成PCM,再用MediaCodec編碼成AAC,這樣曲線存儲^_^。不光是G711,所有的音頻編碼格式都可以這樣做哈哈。。

所以前面的流程圖裏,writeAudioSample的部分就變成這樣了:

Created with Raphaël 2.1.0音頻AAC格式數據?writeAudioSampledecodepcmencodeaacyesno

下面是將解碼後的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.CodecProfileLevel.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();
    }
}

一切都在代碼中,不再過多解釋,至此結束。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章