H264/AAC實時流 錄製成MP4格式的本地視頻
GITHUB:
https://github.com/chezi008/mp4muxer
建議使用場景
一般視頻流有如下兩種途徑獲取:
- Android攝像頭採集
- 服務端傳輸過來的視頻流
如果數據由本機攝像頭直接採集,建議使用MediaMuxer類去實現mp4的合成。如果是服務端傳輸過來的視頻流可以使用mp4v2的方法實現mp4的合成。
一、MediaMuxer合成Mp4
####官方文檔介紹:http://www.loverobots.cn/android-api/reference/android/media/MediaMuxer.html
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();
MediaMuxer的使用
- 初始化:
mMuxer = new MediaMuxer(outPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
- 根據自己情況添加track,不要方法添加,此操作必須在mMuxer start方法之前調用:
mVideoTrackIndex = mMuxer.addTrack(mediaFormat);
mAudioTrackIndex = mMuxer.addTrack(mediaFormat);
- 寫入數據
outputBuffer.position(bufferInfo.offset);
outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
mMuxer.writeSampleData(track, outputBuffer, bufferInfo);
- 結束
mMuxer.stop();
mMuxer.release();
在結束的時候你可能會遇到幾種異常:
- E/MPEG4Writer: Missing codec specific data:
這是因爲在寫入數據的時候沒有寫入編碼參數,h264的編碼參數包含SPS和PPS。所以當你視頻流遇到這些參數幀的時候,請設置好對應的參數。
//設置sps和pps 如果設置不正確會導致合成的mp4視頻作爲文件預覽的時候,預覽圖片是黑色的
//視頻進度條拖拽畫面會出現綠色,以及塊狀現象
mediaformat.setByteBuffer("csd-0", mCSD0);
mediaformat.setByteBuffer("csd-1", mCSD1);
AAC參數:PCM在用編碼器編碼成AAC格式音頻的時候,編碼器會在自動設置參數。
當outIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED
我們可以獲取到,MediaFormat format = mEnc.getOutputFormat(),
format就包含了CODEC_CONFIG。此時的format可直接作爲addTrack()的參數使用。
- There are no sync frames for video track
這是因爲沒有設置關鍵幀的flag。
switch (nalType & 0x1f) {
case H264Decoder.NAL_TYPE_I:
bufferInfo.flags = MediaCodec.BUFFER_FLAG_KEY_FRAME;
break;
}
- 音視頻同步
方法裏面傳入的時間參數PTS的單位時:microseconds,微妙。
long pts = System.nanoTime() / 1000;
直接使用當前時間戳會有問題,錄製成的MP4總時間會很大。我使用一個相對時間,當前時間相對於開始的時間差。
bufferInfo.presentationTimeUs = System.nanoTime()/1000-startPts;
使用中一些需要注意的地方
1. MediaFormat 可以在初始化編碼器的時候獲取
mediaformat = MediaFormat.createVideoFormat("video/avc", VIDEO_WIDTH, VIDEO_HEIGHT);
2. 寫入數據時候的inputBuffer 和 bufferInfo 需要自己構造
二、Q&S
- 錄製的視頻或快或慢。
在 MP4Encoder::CreateMP4File本地方法中有一個m_nFrameRate變量,控制幀率的,也就是每分鐘多少幀,這裏可以自己去控制,和錄製視頻的幀率一致就行了,這裏默認的是25幀。 - 錄製的本地mp4視頻預覽畫面是黑色或者是綠色的
造成的原因是錄製視頻流的時候第一幀不是關鍵幀(I幀),所以在使用writeH264Data方法的時候,記得第一幀傳入關鍵幀。 - download下載的項目無法運行
。。。這個,你就自己去配置編譯環境了,代碼都在這了。 - 錄製成的MP4第一幀模糊
這是因爲寫數據的時候沒有進行關鍵幀的判斷,第一幀寫入關鍵幀就不會有這個問題了。
三、引用
Android在MediaMuxer和MediaCodec用例:https://www.cnblogs.com/hrhguanli/p/5043610.html
Grafika: https://github.com/google/grafika
HWEncoderExperiments:https://github.com/OnlyInAmerica/HWEncoderExperiments/tree/audioonly/HWEncoderExperiments/src/main/java/net/openwatch/hwencoderexperiments