Android Camera系列(三):GLSurfaceView+Camera

2024-09-03 21:20

本文主要是介绍Android Camera系列(三):GLSurfaceView+Camera,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

人类的悲欢并不相通—鲁迅

本系列主要讲述Android开发中Camera的相关操作、预览方式、视频录制等,项目结构代码耦合性低,旨在帮助大家能从中有所收获(方便copy :) ),对于个人来说也是一个总结的好机会

Alt

本章我们来讲解GLSurfaceView进行Camera预览,基于第一篇Android Camera系列(一):SurfaceView+Camera的成果,我们已经对Camera进行了封装,CameraManager拿来直接使用就好

一.GLSurfaceView使用

GLSurfaceView实际上就是继承了SurfaceView,并在其内部封装了EGL环境管理和渲染线程,使得我们可以直接使用opengl的API接口对图像进行变换等操作,如:黑白滤镜、美颜等各种复杂的滤镜效果

  1. 自定义CameraGLSurfaceView继承GLSurfaceView
  2. 实现SurfaceTexture.OnFrameAvailableListener接口,并在onFrameAvailable回调中请求每一帧数据进行渲染,也就是说Camera的预览需要我们自己绘制完成
  3. GLSurfaceView提供了绘制接口Renderer,我们需要定义CameraSurfaceRenderer实现该接口,并在GLSurfaceView初始化时设置自定义的渲染类。在onSurfaceCreated回调中创建外部纹理SurfaceTexture,并设置OnFrameAvailableListener监听Camera数据回调
  4. 实现自定义CameraCallback接口,监听Camera状态
  5. 一定要实现onResumeonPause接口,并在对应的Activity生命周期中调用。这是所有使用Camera的bug的源头
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();}@Overrideprotected 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);}}}@Overridepublic 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;}@Overridepublic void onOpen() {}@Overridepublic void onOpenError(int error, String msg) {}@Overridepublic void onPreview(int previewWidth, int previewHeight) {Logs.i(TAG, "onPreview " + previewWidth + " " + previewHeight);queueEvent(() -> mRenderer.setCameraPreviewSize(previewWidth, previewHeight));setAspectRatio();}@Overridepublic void onPreviewError(int error, String msg) {}@Overridepublic 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.* <p>* 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<CameraGLSurfaceView> 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();}@Overridepublic 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.* <p>* 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[] mSTMatrix = new float[16];private FullFrameRect mFullScreen;// width/height of the incoming camera preview framesprivate boolean mIncomingSizeUpdated;private int mIncomingWidth;private int mIncomingHeight;private int mTextureId = -1;private SurfaceTexture mSurfaceTexture;public CameraSurfaceRenderer(CameraGLSurfaceView.CameraHandler cameraHandler) {mCameraHandler = cameraHandler;mTextureId = -1;mIncomingSizeUpdated = false;mIncomingWidth = mIncomingHeight = -1;}/*** Notifies the renderer thread that the activity is pausing.* <p>* 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 (mFullScreen != null) {mFullScreen.release(false);     // assume the GLSurfaceView EGL context is aboutmFullScreen = null;             //  to be destroyed}mIncomingWidth = mIncomingHeight = -1;}@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {Logs.i(TAG, "onSurfaceCreated. " + Thread.currentThread().getName());// Set up the texture blitter that will be used for on-screen display.  This// is *not* applied to the recording, because that uses a separate shader.mFullScreen = new FullFrameRect(new Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_EXT));mTextureId = mFullScreen.createTextureObject();// 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));}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {gl.glViewport(0, 0, width, height);mCameraHandler.sendMessage(mCameraHandler.obtainMessage(CameraHandler.MSG_SURFACE_CHANGED, width, height));}@Overridepublic void onDrawFrame(GL10 gl) {if (mSurfaceTexture == null) return;mSurfaceTexture.updateTexImage();if (mIncomingWidth <= 0 || mIncomingHeight <= 0) {return;}if (mIncomingSizeUpdated) {mFullScreen.getProgram().setTexSize(mIncomingWidth, mIncomingHeight);mIncomingSizeUpdated = false;}mSurfaceTexture.getTransformMatrix(mSTMatrix);mFullScreen.drawFrame(mTextureId, mSTMatrix);}public void setCameraPreviewSize(int width, int height) {mIncomingWidth = width;mIncomingHeight = height;mIncomingSizeUpdated = true;}}
}

1.Camera操作时机

与SurfaceView和TextureView不同,GLSurfaceView中并没有地方获取SurfaceTexture的地方,虽然Renderer接口有onSurfaceCreated回调但是并没有SurfaceTexture,而是要求我们自己创建外部纹理ID用于Camera预览数据的回调。至于这个外部纹理如何又显示到GLSurfaceView上的,这个章节就不先介绍了,后续我们进行完整的opengl环境搭建再进一步讨论。

Renderer中的所有回调接口都是运行在独立的线程中的,这也就是为什么我们要单独定义个类,而不是让CameraGLSurfaceView直接实现该接口,让他和别的接口方法隔离

GLSurfaceViewsetRenderer接口源码可以看到会启动一个GLThread线程,Renderer接口都是运行在该线程中

    public void setRenderer(Renderer renderer) {...mRenderer = renderer;mGLThread = new GLThread(mThisWeakRef);mGLThread.start();}

因此我们要把创建好的外部纹理通过Handler传递给UI线程,UI线程在获取到SurfaceTexture后打开摄像头,记得在onResume中也同样打开一次摄像头

        @Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {mFullScreen = new FullFrameRect(new Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_EXT));// 创建外部纹理IDmTextureId = mFullScreen.createTextureObject();// 在此EGL上下文中创建具有外部纹理的SurfaceTexturemSurfaceTexture = new SurfaceTexture(mTextureId);// 将SurfaceTexture传递给UI线程mCameraHandler.sendMessage(mCameraHandler.obtainMessage(CameraHandler.MSG_SET_SURFACE_TEXTURE, mSurfaceTexture));}

我们重写surfaceDestroyed,在该回调和onPause中关闭摄像头

注意surfaceDestroyedSurfaceHolder.Callback的方法,该方法是运行在UI线程中的

2. GLSurfaceView计算大小

  1. 和SurfaceView和TextureView一样,我们在onPreview回调中设置TextureView的大小和比例
  2. onSurfaceChanged回调中设置GL画布大小,偶发预览变形大多是没有在此调用glViewport方法导致
        @Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {gl.glViewport(0, 0, width, height);mCameraHandler.sendMessage(mCameraHandler.obtainMessage(CameraHandler.MSG_SURFACE_CHANGED, width, height));}

二.最后

本文介绍了Camera+GLSurfaceView的基本操作及关键代码。本章内容也不是很多,介绍了如何用GLSurfaceView预览Camera数据的流程。我们没有对opengl的API进行过多的讲解,以及Renderer接口是如何将数据渲染到GLSurfaceView中的。在后续的章节我会讲解opengl在Android中的使用,希望聪明的你在回看该章会有种醍醐灌顶的感觉吧。

lib-camera库包结构如下:

说明
cameracamera相关操作功能包,包括Camera和Camera2。以及各种预览视图
encoderMediaCdoec录制视频相关,包括对ByteBuffer和Surface的录制
glesopengles操作相关
permission权限相关
util工具类

每个包都可独立使用做到最低的耦合,方便白嫖

github地址:https://github.com/xiaozhi003/AndroidCamera,https://gitee.com/xiaozhi003/android-camera

参考:

  1. https://github.com/afei-cn/CameraDemo
  2. https://github.com/saki4510t/UVCCamera
  3. https://github.com/google/grafika

这篇关于Android Camera系列(三):GLSurfaceView+Camera的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1134076

相关文章

Android NDK版本迭代与FFmpeg交叉编译完全指南

《AndroidNDK版本迭代与FFmpeg交叉编译完全指南》在Android开发中,使用NDK进行原生代码开发是一项常见需求,特别是当我们需要集成FFmpeg这样的多媒体处理库时,本文将深入分析A... 目录一、android NDK版本迭代分界线二、FFmpeg交叉编译关键注意事项三、完整编译脚本示例四

Android与iOS设备MAC地址生成原理及Java实现详解

《Android与iOS设备MAC地址生成原理及Java实现详解》在无线网络通信中,MAC(MediaAccessControl)地址是设备的唯一网络标识符,本文主要介绍了Android与iOS设备M... 目录引言1. MAC地址基础1.1 MAC地址的组成1.2 MAC地址的分类2. android与I

Android 实现一个隐私弹窗功能

《Android实现一个隐私弹窗功能》:本文主要介绍Android实现一个隐私弹窗功能,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 效果图如下:1. 设置同意、退出、点击用户协议、点击隐私协议的函数参数2. 《用户协议》、《隐私政策》设置成可点击的,且颜色要区分出来res/l

Android实现一键录屏功能(附源码)

《Android实现一键录屏功能(附源码)》在Android5.0及以上版本,系统提供了MediaProjectionAPI,允许应用在用户授权下录制屏幕内容并输出到视频文件,所以本文将基于此实现一个... 目录一、项目介绍二、相关技术与原理三、系统权限与用户授权四、项目架构与流程五、环境配置与依赖六、完整

Android 12解决push framework.jar无法开机的方法小结

《Android12解决pushframework.jar无法开机的方法小结》:本文主要介绍在Android12中解决pushframework.jar无法开机的方法,包括编译指令、框架层和s... 目录1. android 编译指令1.1 framework层的编译指令1.2 替换framework.ja

Android开发环境配置避坑指南

《Android开发环境配置避坑指南》本文主要介绍了Android开发环境配置过程中遇到的问题及解决方案,包括VPN注意事项、工具版本统一、Gerrit邮箱配置、Git拉取和提交代码、MergevsR... 目录网络环境:VPN 注意事项工具版本统一:android Studio & JDKGerrit的邮

Android实现定时任务的几种方式汇总(附源码)

《Android实现定时任务的几种方式汇总(附源码)》在Android应用中,定时任务(ScheduledTask)的需求几乎无处不在:从定时刷新数据、定时备份、定时推送通知,到夜间静默下载、循环执行... 目录一、项目介绍1. 背景与意义二、相关基础知识与系统约束三、方案一:Handler.postDel

Android使用ImageView.ScaleType实现图片的缩放与裁剪功能

《Android使用ImageView.ScaleType实现图片的缩放与裁剪功能》ImageView是最常用的控件之一,它用于展示各种类型的图片,为了能够根据需求调整图片的显示效果,Android提... 目录什么是 ImageView.ScaleType?FIT_XYFIT_STARTFIT_CENTE

Android实现在线预览office文档的示例详解

《Android实现在线预览office文档的示例详解》在移动端展示在线Office文档(如Word、Excel、PPT)是一项常见需求,这篇文章为大家重点介绍了两种方案的实现方法,希望对大家有一定的... 目录一、项目概述二、相关技术知识三、实现思路3.1 方案一:WebView + Office Onl

Android实现两台手机屏幕共享和远程控制功能

《Android实现两台手机屏幕共享和远程控制功能》在远程协助、在线教学、技术支持等多种场景下,实时获得另一部移动设备的屏幕画面,并对其进行操作,具有极高的应用价值,本项目旨在实现两台Android手... 目录一、项目概述二、相关知识2.1 MediaProjection API2.2 Socket 网络