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的接口定義相對而言比較簡單,調用過程如下圖所示。
簡單來說,就是創建對象、添加音視頻軌道、開始、持續寫入音視頻數據、關閉這樣一個過程。
遺憾的是,MediaMuxer並不支持對除AAC以外的音頻編碼格式的封裝,然而在安防行業裏G711音頻格式的數據是大多數設備的默認編碼格式。
如何支持G711格式的數據呢?其實換種思路就會豁然開朗,我們可以先把G711數據解碼成PCM,再用MediaCodec編碼成AAC,這樣曲線存儲^_^。不光是G711,所有的音頻編碼格式都可以這樣做哈哈。。
所以前面的流程圖裏,writeAudioSample的部分就變成這樣了:
下面是將解碼後的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();
}
}
一切都在代碼中,不再過多解釋,至此結束。