在之前一片博客《 EasyPusher實現安卓Android手機直播推送同步錄像功能》(http://blog.csdn.net/jyt0551/article/details/58714595)中,我寫到了EasyPusher推送的同時進行本地存儲的功能,我們今天來介紹下EasyPlayer保存本地錄像的功能。EasyPlayer同樣是運用MediaMuxer進行錄像的,與EasyPusher不同的是,Player要保存的是遠端的音視頻碼流。目前Player支持對H264格式的視頻和AAC格式的音頻進行存儲。
在前一篇博客 ,音視頻碼流的metadata,即MediaFormat,是從MediaCodec取出來的。也就是說硬編碼庫提供了獲取音視頻的metadata的接口。但是很可惜我們在播放端並沒有這樣方便的藉口可以調用。那MediaFormat對象只能我們手動構建了。
MediaFormat這個類的實現非常簡單,它的內部以鍵值對的形式對音視頻的參數進行了封裝,並且向外提供了接口以供讀寫。因而我們可以創建一個MediaFormat對象,並使用特定的參數對其賦值即可。經作者研究發現,在錄像時,對於視頻流,需要的metadata如下表所示。
數據 | 說明 |
---|---|
KEY_MIME | 視頻的MIME,比如video/avc |
width | 寬度 |
height | 高度 |
csd-0 | SPS |
csd-1 | PPS |
對於音頻,需要如下信息:
數據 | 說明 |
---|---|
KEY_MIME | 音頻的MIME,比如audio/mp4a-latm |
KEY_CHANNEL_COUNT | 通道數 |
KEY_SAMPLE_RATE | 採樣率 |
csd-0 | 一些更多的細節信息,比如profile、sample的索引等。參考exoplayer裏的音頻數據的處理 |
csd-1 | 這個。。更多的細節,就不太清楚了。作者也是參考了exoplayer裏面的處理 |
瞭解了這些基本信息後,接下來我們要做的就是從碼流中獲取到這些信息,並構建MediaFormat,用來添加Video或Audio Track.
下面是獲取到視頻相關信息後,添加一個VideoTrack的代碼。
// 添加Video Track
MediaFormat format = new MediaFormat();
format.setInteger(MediaFormat.KEY_WIDTH, mWidth);
format.setInteger(MediaFormat.KEY_HEIGHT, mHeight);
mCSD0.clear();
format.setByteBuffer("csd-0", mCSD0);
mCSD1.clear();
format.setByteBuffer("csd-1", mCSD1);
format.setString(MediaFormat.KEY_MIME, "video/avc");
Log.i(TAG, String.format("addTrack video track :%s", format));
mMuxerVideoTrack = muxer.addTrack(format);
下面是添加AudioTrack的代碼。
int audioObjectType = 2;
byte[] audioSpecificConfig = CodecSpecificDataUtil.buildAacAudioSpecificConfig(audioObjectType, sampleRateIndex, channelConfig);
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(audioSpecificConfig);
// format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0);
format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, audioParams.second);
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, audioParams.first);
List<byte[]> bytes = Collections.singletonList(audioSpecificConfig);
for (int j = 0; j < bytes.size(); j++) {
format.setByteBuffer("csd-" + j, ByteBuffer.wrap(bytes.get(j)));
}
Log.i(TAG, String.format("addTrack audio track :%s", format));
mMuxerAudioTrack = muxer.addTrack(format);
至此,音視頻的通道都已經添加完成,接下來就是要寫數據了。代碼如下:
private synchronized void pumpSample(RTSPClient.FrameInfo frameInfo) {
if (mObject == null) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
MediaMuxer muxer = (MediaMuxer) mObject;
MediaCodec.BufferInfo bi = new MediaCodec.BufferInfo();
bi.offset = frameInfo.offset;
bi.size = frameInfo.length;
ByteBuffer buffer = ByteBuffer.wrap(frameInfo.buffer, bi.offset, bi.size);
bi.presentationTimeUs = frameInfo.stamp;
try {
if (frameInfo.audio) {
bi.offset += 7;
bi.size -= 7;
if (mMuxerAudioTrack > -1)
muxer.writeSampleData(mMuxerAudioTrack, buffer, bi);
} else if (mMuxerVideoTrack > -1) {
if (frameInfo.type != 1) {
bi.flags = 0;
} else {
bi.flags = MediaCodec.BUFFER_FLAG_KEY_FRAME;
}
muxer.writeSampleData(mMuxerVideoTrack, buffer, bi);
}
} catch (IllegalStateException ex) {
ex.printStackTrace();
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
}
}
}
最後,在錄像完成後,關閉MediaMuxer,釋放相關資源。
public synchronized void stopRecord() {
mMuxerAudioTrack = mMuxerVideoTrack = -1;
mRecordingPath = null;
if (mObject == null) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
MediaMuxer muxer = (MediaMuxer) mObject;
try {
muxer.release();
} catch (IllegalStateException ex) {
ex.printStackTrace();
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
}
}
mObject = null;
ResultReceiver rr = mRR;
if (rr != null) {
rr.send(RESULT_RECORD_END, null);
}
}