之前,我写过一篇文章,用Camera2和GLSurface实现预览:https://blog.csdn.net/qq_36391075/article/details/81631461。
今天,来实现录制视频:
思路:
- 通过MediaCodec创建一个用于输入的Surface
- .通过通过camera预览时的上下文EGL创建OpenGL的环境,根据上面得到的Surface创建EGLSuface。
- 通过camera预览时的绑定的纹理id,进行纹理绘制。
- 交换数据,让数据输入金Surface。使用AudioReocod进行声音的采集
- 通过Mediacodec和MediaMuxer进行数据的封装
MediaCodec
mediacodec可以用来获得安卓底层的多媒体编码,可以用来编码和解码,它是安卓low-level多媒体基础框架的重要组成部分。它经常和 MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface, AudioTrack一起使用。
通过上图可以看出,mediacodec的作用是处理输入的数据生成输出数据。首先生成一个输入数据缓冲区,将数据填入缓冲区提供给codec,codec会采用异步的方式处理这些输入的数据,然后将填满输出缓冲区提供给消费者,消费者消费完后将缓冲区返还给codec。
关于它的详细介绍可以参考:https://juejin.im/entry/5aa234f751882555712bf210
MediaMuxer
MediaMuxer的作用是生成音频或视频文件;还可以把音频与视频混合成一个音视频文件
相关API介绍:
- MediaMuxer(String path, int format):path:输出文件的名称 format:输出文件的格式;当前只支持MP4格式;
- addTrack(MediaFormat format):添加通道;我们更多的是使用MediaCodec.getOutpurForma()或Extractor.getTrackFormat(int index)来获取MediaFormat;也可以自己创建;
- start():开始合成文件
- writeSampleData(int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo):把ByteBuffer中的数据写入到在构造器设置的文件中;
- stop():停止合成文件
- release():释放资源
关于它的使用可以参考:https://www.cnblogs.com/renhui/p/7474096.html
根据思路,首先,我们得先创建一个关于画面的MeidaCodec:
//在VideoRecordEncode类中
public void prepare(){
Log.d(TAG, "prepare: "+Thread.currentThread().getName());
try {
mEnOS = false;
mViedeoEncode = MediaCodec.createEncoderByType(MIME_TYPE);
MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE,mWidth,mHeight);
format.setInteger(MediaFormat.KEY_BIT_RATE,calcBitRate());
format.setInteger(MediaFormat.KEY_FRAME_RATE,FRAME_RATE);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,10);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
mViedeoEncode.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);
//得到Surface用于编码
mSurface = mViedeoEncode.createInputSurface();
mViedeoEncode.start();
mPrepareLisnter.onPrepare(this);
}catch (IOException e){
e.printStackTrace();
}
}
Surface创建好后,我们绑定EGL的上下文:在`onPrepare(this);回调中:
private onFramPrepareLisnter lisnter = new onFramPrepareLisnter() {
@Override
public void onPrepare(VideoRecordEncode encode) {
mPresenter.setVideoEncode(encode);
}
};
public void setVideoEncode(VideoRecordEncode encode){
mViewController.setVideoEncode(encode);
}
@Override
public void setVideoEncode(VideoRecordEncode encode) {
mRecordView.setVideoEndoer(encode);//mRecodView是GLSrurace的子类
}
public void setVideoEndoer(final VideoRecordEncode endoer){
//获得OpenGL中的线程
queueEvent(new Runnable() {
@Override
public void run() {
synchronized (mRender){
endoer.setEGLContext(EGL14.eglGetCurrentContext(),mTextId);
mRender.mEncode = endoer;
}
}
});
}
public void setEGLContext(EGLContext context,int texId){
mShare_Context = context;
mTexId = texId;
mHandler.setEGLContext(mShare_Context,mSurface,mTexId);//创建上下文
}
与此同时,在VideoRecordEncode类中,在它被创建的同时,让MediaCodec的对象作用在一个线程中,且让用EGL绘制纹理作用于另一个线程中:
public VideoRecordEncode(VideoMediaMuxer muxer,onFramPrepareLisnter prepareLisnter, int width, int height) {
mWidth = width;
mHeight = height;
mPrepareLisnter = prepareLisnter;
mMuxer = muxer;
mBfferInfo = new MediaCodec.BufferInfo();
mHandler = RenderHandler.createRenderHandler();
synchronized (mSync){
new Thread(this).start();
try {
mSync.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public static RenderHandler createRenderHandler(){
RenderHandler handler = new RenderHandler();
new Thread(handler).start();
synchronized (handler.mSyn){
try {
handler.mSyn.wait();
}catch (InterruptedException e){
return null;
}
}
return handler;
}
RenderHanlder是专门用于EGL绘制的一个类:当它启动的时候,进入run方法,这个时候,EGL的环境还没有创建,因此我们用一个标志符来标志是否创建EGLContext:
@Override
public void run() {
synchronized (mSyn){
mRequestRelease= mRequestEGLContext = false;
mRequestDraw = 0;
mSyn.notifyAll();
}
boolean localRequestDraw = false;
for(;;){
synchronized (mSyn){
if(mRequestRelease) break;