Android 分段录制不掉帧
项目需求过检项目要控制,分段录制时间间隔需要控制在100ms以内
原理:在使用原生的编解码MediaMuxer合成录制时,创建两个MediaMuxer(检测在new MediaMuxer时间已经超过了100ms),在分段的时候停止当前的MediaMuxer.stop(),立马把另外一个addTrack开始录制
1,在初始化编码器的时候创建两个Muxer
mMuxer[0] = new MediaMuxer(path1, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
mMuxer[1] = new MediaMuxer(path2, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
2,在音频解码的方法中判断是否到了分段时间进行分段操作,在MediaCodec.INFO_TRY_AGAIN_LATER中进行分段,因为进入MediaCodec.INFO_TRY_AGAIN_LATER说明完成一段音频的解码写入,注意有个提前100ms在结束的时候给个关键帧,这样确保接下来的帧是关键帧,因为一个MP4的开头必须要关键帧
public void drainAudioEncoder(boolean endOfStream) {
// Logger.getLogger(TAG).e("drainAudioEncoder");
// Logger.getLogger(TAG).e("drainAudioEncoder:endOfStream "+endOfStream);
if (!isMicSound) return;
if (endOfStream) return;
final int TIMEOUT_USEC = 0;
ByteBuffer[] encoderOutputBuffers = mAudioEncoder.getOutputBuffers();//该方法返回一个 output 缓冲区,包含解码或编码后的数据。
while( true ){
int encoderStatus = mAudioEncoder.dequeueOutputBuffer(mAudioBufferInfo, TIMEOUT_USEC);//获取可用的输出缓冲区
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
// no output available yet
if (!endOfStream) {
if (DEBUG) Log.d(TAG, "Audio no output available, out of while(true) ");{
if (!stopRecording && System.currentTimeMillis() - curTime>= 50000){
curTime = System.currentTimeMillis();
changeFilePath();
}else if (System.currentTimeMillis() - curTime>= (50000-100)){//提前100ms在结束的时候给个关键帧
Bundle params = new Bundle();
params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
mVideoEncoder.setParameters(params);
}
}
break; // out of while(true){}
} else {
if (DEBUG) Log.d(TAG, "Audio no output available, spinning to await EOS");
}
}
mAudioEncoder.releaseOutputBuffer(encoderStatus, false);//释放缓冲区
if ((mAudioBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
if (!endOfStream) {
Log.w(TAG, "reached end of stream unexpectedly");
} else {
if (DEBUG) Log.d(TAG, "Audio end of stream reached");
}
break; // out of while
}
}
}
}
3,changeFile实现
public void changeFilePath(){
//循环存储机制
try {
contoryMuxer++;
Logger.getLogger(TAG).e("changeFilePath stop:"+getWitchOneMuxer()+" start:"+getNewWitchOneMuxer());
// mVideoBufferInfo.presentationTimeUs = 0;
if (mMuxer[getWitchOneMuxer()] !=null) {
// stop() throws an exception if you haven't fed it any data.
// Keep track of frames submitted, and don't call stop() if we haven't written anything.
mAudioTrackIndex =-1;
mVideoTrackIndex =-1;
mMuxer[getWitchOneMuxer()].stop();//关闭当前正在写数据的Muxer
mMuxer[getWitchOneMuxer()].release();
mMuxer[getWitchOneMuxer()] = null;
new Thread(new Runnable() {
@Override
public void run() {//此时mMuxer[getWitchOneMuxer()]已经为null,然后在线程里面重新new MediaMuxer()为下一个分段的文件做准备
try {//结束了 直接start new muxer实例需要100ms以上 所以放在线程里面
int checkValue = CameraSettings.RECROD_DEFAULT.RECORD_TIME_INDEX_VALUE;
mCurFile2 = FileUtils.getOutputFile(FileUtils.MEDIA_TYPE_VIDEO,System.currentTimeMillis()+CameraSettings.RECROD_DEFAULT.RECORD_DEFAULT_DURATION_VALUES[checkValue],CameraSettings.RECROD_DEFAULT.FILE_NAME_FORMAT,false,false);
mMuxer[getWitchOneMuxer()] = new MediaMuxer(mCurFile2.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
//此时当前mMuxer[getNewWitchOneMuxer()]已经在上一次分段中实例化好了然后直接addTrack
mVideoTrackIndex = mMuxer[getNewWitchOneMuxer()].addTrack(videoFormatUsering);
mAudioTrackIndex = mMuxer[getNewWitchOneMuxer()].addTrack(audioFormatUsering);
if(mVideoTrackIndex!=-1 && mAudioTrackIndex!=-1){//确保再次申请关键帧
mMuxer[getNewWitchOneMuxer()].start();
Bundle params = new Bundle();
params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
mVideoEncoder.setParameters(params);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
原理其实很简单,就是新建两个Muxer 第一次的时候同时实例化,当第一个录制完成时,第二个因为已经实例化可以直接addTarck(audio),addTarck(video),同时开线程又new MediaMuxer为下一段做准备。注意因为是预先生成MediaMuxer在SDcard中会先生成一个0KB的.MP4文件记得及时删除。
最后我测试的结果也不是很理想一般是在100MS以内,但是偶尔会超过100ms