人类的悲欢并不相通---鲁迅
- Android Camera系列(一):SurfaceView+Camera
- Android Camera系列(二):TextureView+Camera
- Android Camera系列(三):GLSurfaceView+Camera
本系列主要讲述Android开发中Camera的相关操作、预览方式、视频录制等。项目结构简单、代码耦合性低,旨在帮助大家能从中有所收获(方便copy :) ),对于个人来说也是一个总结的好机会
本章我们来讲解GLSurfaceView
进行Camera预览,基于第一篇Android Camera系列(一):SurfaceView+Camera的成果,我们已经对Camera进行了封装,CameraManager拿来直接使用就好
一.GLSurfaceView使用
GLSurfaceView实际上就是继承了SurfaceView,并在其内部封装了EGL环境管理和渲染线程,不了解EGL的请移步至Android OpenGLES开发:EGL环境搭建,使得我们可以直接使用OpenGL ES的API对图像进行变换等操作,如:黑白滤镜、美颜等各种复杂的滤镜效果
- 自定义
CameraGLSurfaceView
继承GLSurfaceView
- 实现
SurfaceTexture.OnFrameAvailableListener
接口,并在onFrameAvailable
回调中请求每一帧数据进行渲染,也就是说Camera的预览需要我们自己绘制完成 - GLSurfaceView提供了绘制接口
Renderer
,我们需要定义CameraSurfaceRenderer
实现该接口,并在GLSurfaceView初始化时设置自定义的渲染类。在onSurfaceCreated
回调中创建外部纹理SurfaceTexture
,并设置OnFrameAvailableListener
监听Camera数据回调 - 实现自定义CameraCallback接口,监听Camera状态
- 一定要实现
onResume
和onPause
接口,并在对应的Activity生命周期中调用。这是所有使用Camera的bug的源头
java 代码解读复制代码public class CameraGLSurfaceView extends GLSurfaceView implements SurfaceTexture.OnFrameAvailableListener, CameraCallback {
private static final String TAG = CameraGLSurfaceView.class.getSimpleName();
private Context mContext;
private SurfaceTexture mSurfaceTexture;
private CameraHandler mCameraHandler;
private boolean hasSurface; // 是否存在摄像头显示层
private CameraManager mCameraManager;
private int mRatioWidth = 0;
private int mRatioHeight = 0;
private int mGLSurfaceWidth;
private int mGLSurfaceHeight;
private CameraSurfaceRenderer mRenderer;
public CameraGLSurfaceView(Context context) {
super(context);
init(context);
}
public CameraGLSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
mContext = context;
mCameraHandler = new CameraHandler(this);
mCameraManager = new CameraManager(context);
mCameraManager.setCameraCallback(this);
setEGLContextClientVersion(2);
mRenderer = new CameraSurfaceRenderer(mCameraHandler);
setRenderer(mRenderer);
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
public SurfaceTexture getSurfaceTexture() {
return mSurfaceTexture;
}
private void setAspectRatio(int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
mRatioWidth = width;
mRatioHeight = height;
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height * mRatioWidth / mRatioHeight) {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
requestRender();
}
/**
* Connects the SurfaceTexture to the Camera preview output, and starts the preview.
*/
private void handleSetSurfaceTexture(SurfaceTexture st) {
Logs.i(TAG, "handleSetSurfaceTexture.");
mSurfaceTexture = st;
hasSurface = true;
mSurfaceTexture.setOnFrameAvailableListener(this);
openCamera();
}
/**
*
* @param width
* @param height
*/
private void handleSurfaceChanged(int width, int height) {
Logs.i(TAG, "handleSurfaceChanged.");
mGLSurfaceWidth = width;
mGLSurfaceHeight = height;
setAspectRatio();
}
/**
* 打开摄像头并预览
*/
public void onResume() {
super.onResume();
if (hasSurface) {
// 当activity暂停,但是并未停止的时候,surface仍然存在,所以 surfaceCreated()
// 并不会调用,需要在此处初始化摄像头
openCamera();
}
}
/**
* 停止预览并关闭摄像头
*/
public void onPause() {
super.onPause();
closeCamera();
}
public void onDestroy() {
mCameraHandler.invalidateHandler();
}
/**
* 打开摄像头
*/
private void openCamera() {
if (mSurfaceTexture == null) {
Logs.e(TAG, "mSurfaceTexture is null.");
return;
}
if (mCameraManager.isOpen()) {
Logs.w(TAG, "Camera is opened!");
return;
}
mCameraManager.openCamera();
if (mCameraManager.isOpen()) {
mCameraManager.startPreview(mSurfaceTexture);
}
}
private void closeCamera() {
mCameraManager.releaseCamera();
queueEvent(() -> mRenderer.notifyPausing());
mSurfaceTexture = null;
}
@Override
public void onOpen() {
}
@Override
public void onOpenError(int error, String msg) {
}
@Override
public void onPreview(int previewWidth, int previewHeight) {
Logs.i(TAG, "onPreview " + previewWidth + " " + previewHeight);
queueEvent(() -> mRenderer.setCameraPreviewSize(previewWidth, previewHeight));
setAspectRatio();
}
@Override
public void onPreviewError(int error, String msg) {
}
@Override
public void onClose() {
}
private void setAspectRatio() {
int previewWidth = mCameraManager.getPreviewWidth();
int previewHeight = mCameraManager.getPreviewHeight();
if (mGLSurfaceWidth > mGLSurfaceHeight) {
setAspectRatio(previewWidth, previewHeight);
} else {
setAspectRatio(previewHeight, previewWidth);
}
}
/**
* Handles camera operation requests from other threads. Necessary because the Camera
* must only be accessed from one thread.
*
* The object is created on the UI thread, and all handlers run there. Messages are
* sent from other threads, using sendMessage().
*/
static class CameraHandler extends Handler {
public static final int MSG_SET_SURFACE_TEXTURE = 0;
public static final int MSG_SURFACE_CHANGED = 1;
private WeakReference mWeakGLSurfaceView;
public CameraHandler(CameraGLSurfaceView view) {
mWeakGLSurfaceView = new WeakReference<>(view);
}
/**
* Drop the reference to the activity. Useful as a paranoid measure to ensure that
* attempts to access a stale Activity through a handler are caught.
*/
public void invalidateHandler() {
mWeakGLSurfaceView.clear();
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
int what = msg.what;
CameraGLSurfaceView view = mWeakGLSurfaceView.get();
if (view == null) {
return;
}
switch (what) {
case MSG_SET_SURFACE_TEXTURE:
view.handleSetSurfaceTexture((SurfaceTexture) msg.obj);
break;
case MSG_SURFACE_CHANGED:
view.handleSurfaceChanged(msg.arg1, msg.arg2);
break;
default:
throw new RuntimeException("unknown msg " + what);
}
}
}
/**
* Renderer object for our GLSurfaceView.
*
* Do not call any methods here directly from another thread -- use the
* GLSurfaceView#queueEvent() call.
*/
static class CameraSurfaceRenderer implements GLSurfaceView.Renderer {
private CameraGLSurfaceView.CameraHandler mCameraHandler;
private final float[] mDisplayProjectionMatrix = new float[16];
private CameraFilter mCameraFilter;
// width/height of the incoming camera preview frames
private boolean mSizeUpdated;
private int mCameraPreviewWidth;
private int mCameraPreviewHeight;
private int mTextureId;
private SurfaceTexture mSurfaceTexture;
public CameraSurfaceRenderer(CameraGLSurfaceView.CameraHandler cameraHandler) {
mCameraHandler = cameraHandler;
mTextureId = -1;
mSizeUpdated = false;
mCameraPreviewWidth = mCameraPreviewHeight = -1;
mCameraFilter = new CameraFilter();
}
/**
* Notifies the renderer thread that the activity is pausing.
*
* For best results, call this *after* disabling Camera preview.
*/
public void notifyPausing() {
if (mSurfaceTexture != null) {
Logs.d(TAG, "renderer pausing -- releasing SurfaceTexture");
mSurfaceTexture.release();
mSurfaceTexture = null;
}
if (mCameraFilter != null) {
mCameraFilter.release(); // assume the GLSurfaceView EGL context is about
}
mCameraPreviewWidth = mCameraPreviewHeight = -1;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
Logs.i(TAG, "onSurfaceCreated. " + Thread.currentThread().getName());
mTextureId = GLESUtils.createOESTexture();
mCameraFilter.surfaceCreated();
// Create a SurfaceTexture, with an external texture, in this EGL context. We don't
// have a Looper in this thread -- GLSurfaceView doesn't create one -- so the frame
// available messages will arrive on the main thread.
mSurfaceTexture = new SurfaceTexture(mTextureId);
mCameraHandler.sendMessage(mCameraHandler.obtainMessage(CameraHandler.MSG_SET_SURFACE_TEXTURE, mSurfaceTexture));
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
mCameraFilter.surfaceChanged(width, height);
mCameraHandler.sendMessage(mCameraHandler.obtainMessage(CameraHandler.MSG_SURFACE_CHANGED, width, height));
}
@Override
public void onDrawFrame(GL10 gl) {
if (mSurfaceTexture == null) return;
// 把摄像头的数据先输出来
// 更新纹理,然后我们才能够使用opengl从SurfaceTexture当中获得数据 进行渲染
mSurfaceTexture.updateTexImage();
if (mCameraPreviewWidth <= 0 || mCameraPreviewHeight <= 0) {
return;
}
if (mSizeUpdated) {
mSizeUpdated = false;
}
mSurfaceTexture.getTransformMatrix(mDisplayProjectionMatrix);
mCameraFilter.draw(mTextureId, mDisplayProjectionMatrix);
}
public void setCameraPreviewSize(int width, int height) {
mCameraPreviewWidth = width;
mCameraPreviewHeight = height;
mSizeUpdated = true;
}
}
}
1.Camera操作时机
与SurfaceView和TextureView不同,GLSurfaceView中并没有地方获取SurfaceTexture的地方,虽然Renderer
接口有onSurfaceCreated
回调但是并没有SurfaceTexture,而是要求我们自己创建外部纹理ID用于Camera预览数据的回调。至于这个外部纹理如何又显示到GLSurfaceView上的,这个章节就不先介绍了,后续我们进行完整的opengl环境搭建再进一步讨论。
Renderer中的所有回调接口都是运行在独立的线程中的,这也就是为什么我们要单独定义个类,而不是让CameraGLSurfaceView直接实现该接口,让他和别的接口方法隔离
GLSurfaceView
中setRenderer
接口源码可以看到会启动一个GLThread线程,Renderer接口都是运行在该线程中
java 代码解读复制代码 public void setRenderer(Renderer renderer) {
...
mRenderer = renderer;
mGLThread = new GLThread(mThisWeakRef);
mGLThread.start();
}
因此我们要把创建好的外部纹理通过Handler传递给UI线程,UI线程在获取到SurfaceTexture后打开摄像头,记得在onResume
中也同样打开一次摄像头
java 代码解读复制代码 @Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 创建外部纹理ID
mTextureId = GLESUtils.createOESTexture();
// 着色器程序执行相应的方法
mCameraFilter.surfaceCreated();
// 在此EGL上下文中创建具有外部纹理的SurfaceTexture
mSurfaceTexture = new SurfaceTexture(mTextureId);
// 将SurfaceTexture传递给UI线程
mCameraHandler.sendMessage(mCameraHandler.obtainMessage(CameraHandler.MSG_SET_SURFACE_TEXTURE, mSurfaceTexture));
}
我们重写surfaceDestroyed
,在该回调和onPause
中关闭摄像头
注意
surfaceDestroyed
是SurfaceHolder.Callback
的方法,该方法是运行在UI线程中的
2. GLSurfaceView计算大小
- 和SurfaceView和TextureView一样,我们在
onPreview
回调中设置TextureView的大小和比例 - 在
onSurfaceChanged
回调中设置GL画布大小,调用着色器生对应方法,着色器的surfaceChanged
方法中设置画布大小,偶发预览变形大多是没有在此调用glViewport
方法导致
java 代码解读复制代码 @Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
mCameraFilter.surfaceChanged(width, height);
mCameraHandler.sendMessage(mCameraHandler.obtainMessage(CameraHandler.MSG_SURFACE_CHANGED, width, height));
}
二.最后
本文介绍了Camera+GLSurfaceView
的基本操作及关键代码。本章内容也不是很多,介绍了如何用GLSurfaceView预览Camera数据的流程。我们没有对opengl的API进行过多的讲解,以及Renderer
接口是如何将数据渲染到GLSurfaceView
中的。在后续的章节我会讲解opengl在Android中的使用,希望聪明的你在回看该章会有种醍醐灌顶的感觉吧。
lib-camera库包结构如下:
包 | 说明 |
---|---|
camera | camera相关操作功能包,包括Camera和Camera2。以及各种预览视图 |
encoder | MediaCdoec录制视频相关,包括对ByteBuffer和Surface的录制 |
gles | opengles操作相关 |
permission | 权限相关 |
util | 工具类 |
每个包都可独立使用做到最低的耦合,方便白嫖
github地址:github.com/xiaozhi003/…,如果对你有帮助可以star下,万分感谢^_^
参考:
评论记录:
回复评论: