最近在研究音视频混合录制。作为零基础起步的学习,研究了google的grafika(地址为 https://github.com/google/grafika)其中的视频录制部分,随手记下学习笔记。
grafika视频录制详细流程分析:
CameraCaptureActivity
mRecordingEnabled记录当前是否在录制
触发recording:翻转mRecordingEnabled,并在GL的rendering thread中执行CameraSurfaceRenderer.changRecordingState(mRecordingEnabled)
CameraSurfaceRenderer中也有一个变量mRecordingEnabled,更新,并在onDrawFrame()和onSurfaceCreated()中根据mRecordingEnabled维护mRecordingStatus状态机
CameraSurfaceRenderer内部维护的recording status状态机:
on/off/resumed
TextureMovieEncoder.startRecording()
在TextureMovieEncoder内部new一个工作Thread,以TextureMovieEncoder自身为Runnable,run()的逻辑是创建handler,并且开启消息循环Looper.loop(),然后向handler发送一个start recording msg,触发在工作线程中执行TextureMovieEncoder.handleStartRecording()。
TextureMovieEncoder.stopRecording()
向工作线程的handler顺序发两个消息:stop recording msg,quit msg
stop recording msg触发在工作线程中执行TextureMovieEncoder.handleStopRecording()
quit msg触发在工作线程中退出消息循环
Handler对于TextureMovieEncoder使用弱引用
调试Handler msg发现,从开始录制到结束录制,收到的消息为:
开始:MSG_START_RECORDING
录制中:
(循环,由CameraSurfaceRenderer.onDrawFrame()中通过TextureMovieEncoder.setTextureId()/frameAvailable()来触发)
MSG_SET_TEXTURE_ID
MSG_FRAME_AVAILABLE
结束:
MSG_STOP_RECORDING
MSG_QUIT
下面深入到TextureMovieEncoder内部来探究以下三个关键逻辑点都录制视频做了什么(包括encode和mux):
(1)开始录制
-mFrameNum归零
-prepareEncoder,包含初始化配置(接收外部传入的参数),初始化实际的encoder对象,即VideoEncoderCore
(2)录制进行中
-设置mTextureId,为滤镜效果,与录制本身无直接关系(MSG_SET_TEXTURE_ID)
-mVideoEncoder.drainEncoder(false):提取encoder中所有pending的数据,并且发给muxer(MSG_FRAME_AVAILABLE)
(3)结束录制
-mVideoEncoder.drainEncoder(true):提取encoder中所有pending的数据,并且发给muxer,并告知这是end of stream
-释放所有的encoder相关资源
下面集中研究drainEncoder的逻辑,关注如何与muxer联合实现写文件,为集成audio做准备
(1)VideoEncoderCore中的编码器如何与TextureMovieEncoder中的数据源建立联系?
VideoEncoderCore构造方法中:
// Create a MediaCodec encoder, and configure it with our format. Get a Surface
// we can use for input and wrap it with a class that handles the EGL work.
mEncoder= MediaCodec.createEncoderByType(MIME_TYPE);
mEncoder.configure(format,null,null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mInputSurface= mEncoder.createInputSurface();
mEncoder.start();
TextureMovieEncoder中prepareEncoder中:
mEglCore =new EglCore(sharedContext, EglCore.FLAG_RECORDABLE);
mInputWindowSurface= newWindowSurface(mEglCore,mVideoEncoder.getInputSurface(),true);
mInputWindowSurface.makeCurrent();
通过mInputWindowSurface与编码器侧建立联系
(2)drainEncoder的逻辑
VideoEncoderCore主要的代码是drainEncoder()
第一步,取到enCoder outputbuffer
ByteBuffer[] encoderOutputBuffers =mEncoder.getOutputBuffers();
第二步,出列output buffer
int encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
返回值有可能是错误码,这里先省略错误处理逻辑
第三步,根据encoderStatus和mBufferInfo两个信息来取到编码好的数据发给Muxer
mMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo);
(3)时间戳问题
第一步,TextureMovieEncoder.frameAvailable()中从SurfaceTexture中取到上次update TexImage的时间戳,单位是纳秒(10^-9秒)。
在CameraSurfaceRenderer.onDrawFrame()中update:
mSurfaceTexture.updateTexImage();
第二步,将时间戳发送到工作线程,通过TextureMovieEncoder.handleFrameAvailable()中会将时间戳写到mInputWindowSurface
mInputWindowSurface.setPresentationTime(timestampNanos);
第三步,(2)中,发给Muxer时,时间戳会取到这个时间的微妙数(10^-6秒)