你做的再好,也还是有人指指点点;你即便一塌糊涂,也还是有人唱赞歌。所以不必掉进他人的眼神,你需要讨好的,仅仅是你自己。
- Android Camera系列(一):SurfaceView+Camera
- Android Camera系列(二):TextureView+Camera
- Android Camera系列(三):GLSurfaceView+Camera
- Android Camera系列(四):TextureView+OpenGL ES+Camera
- Android Camera系列(五):Camera2
- Android Camera系列(六):MediaCodec视频编码上-编码YUV
- Android Camera系列(七):MediaCodec视频编码中-OpenGL ES多线程渲染
- Android Camera系列(八):MediaCodec视频编码下-OpenGL ES离屏渲染
本系列主要讲述Android开发中Camera的相关操作、预览方式、视频录制等。项目结构简单、代码耦合性低,旨在帮助大家能从中有所收获(方便copy :) ),对于个人来说也是一个总结的好机会。
引言
上一篇我们讲解了使用MediaCodec
硬编码YUV数据进行视频录制,由于硬件厂商的原因存在一些兼容性的问题。而MediaCodec
提供了COLOR_FormatSurface
数据编码的方式,他是通过将数据渲染到MediaCodec
提供的Surface
中进行编码。该种方式兼容性好API > 4.3即可。本篇我们就详细介绍这种方式录制视频。
渲染数据到Surface?
上面提到要将数据渲染到Surface上,what?貌似我只会将Camera数据显示到SurfaceView或TextureView上呢。如果你仔细看了Android OpenGLES开发:EGL环境搭建、Android Camera系列(四):TextureView+OpenGL ES+Camera、Android Camera系列(五):Camera2这些篇章。那么你至少应该知道我们可以自己构建OpenGL ES
环境,并通过Surface
构建EGLSurface
,然后使用OpenGL ES将数据渲染到Surface上。
还有一种方式就比较简单了,Camera2章节中我们知道预览和获取帧数据都需要Surface参数,而现在要将数据渲染到MediaCodec的Surface上,就需要把MediaCodec的Surface加进去即可,当然这种方式不再本章讨论的范围。
Surface是什么? Surface是Android对后台缓冲区的抽象,它是一块在应用和SurfaceFlinger之间共享的内存区域。所有的Android界面,在往屏幕上绘制内容前,都需要先获得一个或多个Surface,然后使用2D(SKIA)或者3D(OpenGL ES)引擎往这个缓冲区上绘制内容。内容绘制结束后,通知SurfaceFlinger将内容渲染到屏幕(Frame Buffer)上去。
整理下我们现在的需求:
- 创建MediaCodec,设置颜色格式
COLOR_FormatSurface
,并获取输入Surface - 将Camera预览数据渲染到预览Surface(SurfaceView或TextureView)上,同时将预览数据渲染到MediaCodec的Surface上
- 编码Surface上的数据
一. 创建MediaCodec
将上一节中的MediaVideoBufferEncoder
拷贝一份命名为MediaSurfaceEncoder
,需要修改的地方如下:
- 配置颜色格式为
COLOR_FormatSurface
final MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); // API >= 18
- 获取Surface
public class MediaSurfaceEncoder extends MediaEncoder implements IVideoEncoder {
...
private Surface mSurface;
protected void prepare() throws IOException {
...
// 配置好MediaCodec后创建Surface
mSurface = mMediaCodec.createInputSurface(); // API >= 18
mMediaCodec.start();
...
}
}
以上就得到了MediaCodec中的Surface,我们将Camera数据渲染到该Surface中即可进行编码
二. 多Surface渲染
1. OpenGL ES共享上下文
当我们需要在预览的同时还要将预览的画面传递给别的线程的Surface进行处理时(如:MediaCodec进行编码)。这个时候我们需要用到一个技巧共享EGL Context
,通过共享Context
我们可以实现纹理在不同的线程进行共享。
可以共享的资源:
- 纹理
- shader
- program着色器程序
- Buffer 类对象,如 VBO、EBO、RBO等
不可共享的资源:
- FBO 帧缓冲区对象(不属于Buffer类)
- VAO 顶点数组对象(不属于Buffer类)
如何共享EGLContext? 在 EGL_VERSION_1_4 (Android 5.0)版本,在当前渲染线程直接调用 eglGetCurrentContext 就可以直接获取到上下文对象 EGLContext 。
EGL14.eglGetCurrentContext()
我们在新线程中使用EGL创建渲染环境时,通过主渲染线程获取的sharedContext来创建新线程的上下文对象。
EGLContext context = eglCreateContext(mEGLDisplay, config,
sharedContext, attrib2_list);
创建EGLSurface,我们获取MediaCodec中的Surface来创建EGLSurface
EGLSurface eglSurface = eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, surfaceAttribs);
2. 多线程渲染
既然是多线程渲染,那么MediaCodec的渲染就要在一个新的线程中执行,我们创建TextureMovieEncoder1
类实现Runnable
,主要实现如下接口
// 开始录像
public abstract void startRecord(EncoderConfig config);
// 停止录像
public abstract void stopRecord();
// 是否正在录像
public abstract boolean isRecording();
// 更新EGLContext
public abstract void updateSharedContext(EGLContext sharedContext);
// 渲染
public abstract void frameAvailable(SurfaceTexture st);
// 设置纹理id
public abstract void setTextureId(int id);
开始录像中,我们启动一个新的线程,所有的操作都通过Handler发送消息完成
public void startRecord(EncoderConfig config) {
Log.d(TAG, "Encoder: startRecording()");
synchronized (mReadyFence) {
if (mRunning) {
Log.w(TAG, "Encoder thread already running");
return;
}
mRunning = true;
new Thread(this, "TextureMovieEncoder").start();
while (!mReady) {
try {
mReadyFence.wait();
} catch (InterruptedException ie) {
// ignore
}
}
}
mHandler.sendMessage(mHandler.obtainMessage(MSG_START_RECORDING, config));
}
public static class EncoderConfig {
final File mOutputFile;
final int mWidth;
final int mHeight;
final int mBitRate;
final EGLContext mEglContext;
public EncoderConfig(File outputFile, int width, int height, int bitRate,
EGLContext sharedEglContext) {
mOutputFile = outputFile;
mWidth = width;
mHeight = height;
mBitRate = bitRate;
mEglContext = sharedEglContext;
}
}
渲染纹理到MediaCodec中的Surface上,CameraFilter
和渲染到屏幕中的Shader程序一样,现在调用draw
方法将纹理绘制到缓冲区中。记得调用swapBuffers
交换缓冲区,这样绘制的内容就刷新到MediaCodec中的Surface上了
private void handleFrameAvailable(float[] transform, long timestampNanos) {
if (VERBOSE) Log.d(TAG, "handleFrameAvailable tr=" + transform);
busy = true;
if (mEncoder != null) {
mEncoder.frameAvailableSoon();
}
long start = System.currentTimeMillis();
mCameraFilter.draw(mTextureId, transform);
mInputWindowSurface.setPresentationTime(timestampNanos);
mInputWindowSurface.swapBuffers();
busy = false;
}
三. 编码
Surface编码和Buffer的编码是一样的,这里就不在重复贴代码了,可以看下MediaEncoder
类
最后
本篇章我们学习了如何使用MediaCodec
的COLOR_FormatSurface
格式编码视频,其本质主要就是运用OpenGL ES
将同一个纹理渲染到不同的Surface中。而本章我们使用的技巧是通过共享EGLContext
,进而在多个线程共享了纹理,实现多线程渲染到不同的Surface中。
lib-camera库包结构如下:
包 | 说明 |
---|---|
camera | camera相关操作功能包,包括Camera和Camera2。以及各种预览视图 |
encoder | MediaCdoec录制视频相关,包括对ByteBuffer和Surface的录制 |
gles | opengles操作相关 |
permission | 权限相关 |
util | 工具类 |
每个包都可独立使用做到最低的耦合,方便白嫖
github地址:github.com/xiaozhi003/…,如果对你有帮助可以star下,万分感谢^_^
参考: