Android Camera系列(一):SurfaceView+Camera

news/2024/9/16 21:10:03/ 标签: android, Camera, SurfaceView

心行慈善,何需努力看经—《西游记》

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

Alt

Camera_10">一. Camera操作

Android系统存在这么多年,google更新了不少API。光是对摄像头的操作目前就有3种API:

由于Android版本众多,考虑兼容性,本文我们还是对android.hardware.Camera进行讲解,操作Camera具体需要哪些步骤呢?

  1. 设置Camera权限,Android6.0以上请动态申请
  2. 打开相机,Camera.open()
  3. 开始预览,startPreview
  4. 停止预览,stopPreview
  5. 关闭相机,release()

1. 打开相机

申请Camera权限,Android6.0以上记得在打开相机前进行动态申请

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"><uses-permission android:name="android.permission.CAMERA" /><uses-feature android:name="android.hardware.camera" />...
</manifest>

我们对Camera进行了简单的封装,无论是Camera还是Camera2我们预览视图都可以是SurfaceView、TextureView、GLSurfaceView,所以我们将Camera操作和预览视图进行分离,让预览View不在依赖具体的某种Camera实现

定义接口类ICameraManager:

/*** CameraCamera2通用接口** @author xiaozhi* @since 2024/8/15*/
public interface ICameraManager {/*** 打开Camera*/void openCamera();/*** 关闭释放Camera*/void releaseCamera();/*** 开启预览** @param surfaceHolder*/void startPreview(SurfaceHolder surfaceHolder);/*** 开启预览** @param surfaceTexture*/void startPreview(SurfaceTexture surfaceTexture);/*** 停止预览*/void stopPreview();...
}

CameraManager实现ICameraManager,打开摄像头

    /*** 打开Camera*/@Overridepublic synchronized void openCamera() {Logs.i(TAG, "Camera open #" + mCameraId);if (mCamera == null) {if (mCameraId >= Camera.getNumberOfCameras()) {onOpenError(CAMERA_ERROR_NO_ID, "No camera.");return;}try {mCamera = Camera.open(mCameraId);Camera.getCameraInfo(mCameraId, mCameraInfo);mCamera.setErrorCallback(errorCallback);initCamera();onOpen();mOrientationEventListener.enable();} catch (Exception e) {onOpenError(CAMERA_ERROR_OPEN, e.getMessage());}}}

打开摄像头之后我们可以设置摄像头的一些基本参数,如预览尺寸,拍照尺寸等

    /*** 配置Camera参数*/private void initCamera() {if (mCamera != null) {mParameters = mCamera.getParameters();if (mDisplayOrientation == -1) {setCameraDisplayOrientation(mContext, mCameraId, mCamera);}// 设置预览方向mCamera.setDisplayOrientation(mDisplayOrientation);// 设置拍照方向mParameters.setRotation(mOrientation);// 如果摄像头不支持这些参数都会出错的,所以设置的时候一定要判断是否支持List<String> supportedFlashModes = mParameters.getSupportedFlashModes();if (supportedFlashModes != null && supportedFlashModes.contains(Parameters.FLASH_MODE_OFF)) {mParameters.setFlashMode(Parameters.FLASH_MODE_OFF); // 设置闪光模式}List<String> supportedFocusModes = mParameters.getSupportedFocusModes();if (supportedFocusModes != null && supportedFocusModes.contains(Parameters.FOCUS_MODE_AUTO)) {mParameters.setFocusMode(Parameters.FOCUS_MODE_AUTO); // 设置聚焦模式}mParameters.setPreviewFormat(ImageFormat.NV21); // 设置预览图片格式mParameters.setPictureFormat(ImageFormat.JPEG); // 设置拍照图片格式Camera.Size previewSize = getSuitableSize(mParameters.getSupportedPreviewSizes());mPreviewWidth = previewSize.width;mPreviewHeight = previewSize.height;mPreviewSize = new Size(mPreviewWidth, mPreviewHeight);mParameters.setPreviewSize(mPreviewWidth, mPreviewHeight);Logs.d(TAG, "previewWidth: " + mPreviewWidth + ", previewHeight: " + mPreviewHeight);Camera.Size pictureSize = mParameters.getPictureSize();mParameters.setPictureSize(pictureSize.width, pictureSize.height);Logs.d(TAG, "pictureWidth: " + pictureSize.width + ", pictureHeight: " + pictureSize.height);mCamera.setParameters(mParameters);isSupportZoom = mParameters.isSmoothZoomSupported();}}

2.开始预览

    /*** 使用Surfaceview开启预览** @param holder*/@Overridepublic synchronized void startPreview(SurfaceHolder holder) {Logs.i(TAG, "startPreview...");if (isPreviewing) {return;}if (mCamera != null) {try {mCamera.setPreviewDisplay(holder);if (!mPreviewBufferCallbacks.isEmpty()) {mCamera.addCallbackBuffer(new byte[mPreviewWidth * mPreviewHeight * 3 / 2]);mCamera.setPreviewCallbackWithBuffer(mPreviewCallback);}mCamera.startPreview();onPreview(mPreviewWidth, mPreviewHeight);} catch (Exception e) {onPreviewError(CAMERA_ERROR_PREVIEW, e.getMessage());}}}

3. 停止预览

    /*** 关闭预览*/@Overridepublic synchronized void stopPreview() {Logs.v(TAG, "stopPreview.");if (isPreviewing && null != mCamera) {try {mCamera.setPreviewCallback(null);mCamera.stopPreview();mPreviewBufferCallbacks.clear();} catch (Exception e) {e.printStackTrace();}}isPreviewing = false;}

4. 关闭相机

    /*** 停止预览,释放Camera*/@Overridepublic synchronized void releaseCamera() {Logs.v(TAG, "releaseCamera.");if (null != mCamera) {stopPreview();try {mCamera.release();mCamera = null;mCameraBytes = null;mDisplayOrientation = -1;} catch (Exception e) {}onClose();}}

SurfaceView_218">二.SurfaceView使用

我们要预览Camera数据必须要使用一个视图承接,SurfaceView是最常用的,也是Camera最初的标配

SurfaceView的特点:在自己独立的线程中绘制,内部使用双缓冲机制,画面更流畅。相比于 TextureView,它内存占用低,绘制更及时,耗时也更低,但不支持动画和截图。

Camera预览需要将SurfaceHolder传递给Camera然后开启预览如何获取SurfaceHoler?

  1. 自定义CameraSurfaceView继承SurfaceView
  2. 实现SurfaceHolder.Callback接口,并在CameraSurfaceView初始化时设置回调
  3. 实现自定义CameraCallback接口,监听Camera状态
  4. 一定要实现onResumeonPause接口,并在对应的Activity生命周期中调用。这是所有使用Camera的bug的源头
/*** 摄像头预览SurfaceView** @author xiaozhi* @since 2024/8/22*/
public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback, CameraCallback {private static final String TAG = CameraSurfaceView.class.getSimpleName();SurfaceHolder mSurfaceHolder;private Context mContext;private Handler mHandler;private boolean hasSurface; // 是否存在摄像头显示层private CameraManager mCameraManager;private int mRatioWidth = 0;private int mRatioHeight = 0;private int mSurfaceWidth;private int mSurfaceHeight;public CameraSurfaceView(Context context) {super(context);init(context);}public CameraSurfaceView(Context context, AttributeSet attrs) {super(context, attrs);init(context);}public CameraSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context);}private void init(Context context) {mContext = context;mHandler = new Handler(context.getMainLooper());mSurfaceHolder = getHolder();mSurfaceHolder.setFormat(PixelFormat.TRANSPARENT);//translucent半透明 transparent透明mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);mSurfaceHolder.addCallback(this);mCameraManager = new CameraManager(context);mCameraManager.setCameraCallback(this);}public CameraManager getCameraManager() {return mCameraManager;}@Overridepublic void surfaceCreated(SurfaceHolder holder) {Logs.i(TAG, "surfaceCreated..." + hasSurface);if (!hasSurface && holder != null) {hasSurface = true;openCamera();}}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {Logs.i(TAG, "surfaceChanged [" + width + ", " + height + "]");mSurfaceWidth = width;mSurfaceHeight = height;}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {Logs.v(TAG, "surfaceDestroyed.");closeCamera();hasSurface = false;}public SurfaceHolder getSurfaceHolder() {return mSurfaceHolder;}public void onResume() {if (hasSurface) {// 当activity暂停,但是并未停止的时候,surface仍然存在,所以 surfaceCreated()// 并不会调用,需要在此处初始化摄像头openCamera();}}public void onPause() {closeCamera();}/*** 打开摄像头*/private void openCamera() {if (mSurfaceHolder == null) {Logs.e(TAG, "SurfaceHolder is null.");return;}if (mCameraManager.isOpen()) {Logs.w(TAG, "Camera is opened!");return;}mCameraManager.openCamera();}/*** 关闭摄像头*/private void closeCamera() {mCameraManager.releaseCamera();}private String getString(int resId) {return getResources().getString(resId);}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 onOpen() {mCameraManager.startPreview(getSurfaceHolder());}@Overridepublic void onOpenError(int error, String msg) {}@Overridepublic void onPreview(int previewWidth, int previewHeight) {if (mSurfaceWidth > mSurfaceHeight) {setAspectRatio(previewWidth, previewHeight);} else {setAspectRatio(previewHeight, previewWidth);}}@Overridepublic void onPreviewError(int error, String msg) {}@Overridepublic void onClose() {}
}

Camera_397">1.Camera操作时机

  • surfaceCreated回调中打开Camera,在surfaceDestroyed中关闭摄像头
    ,这基本是所有Camera操作的常识,我们代码中也展示了同样的方式。
  • 但这还不够完美,我们必须将Camera的操作和Activity的生命周期绑定,在onResume中也打开一次摄像头,在onPause中关闭一次摄像头,确保SurfaceHolder不可用以及Activity不在前台时正确关闭Camera

SurfaceView_401">2.SurfaceView大小计算时机

在操作摄像头之前我们并不知道预览的尺寸,只能设置一个我们想要的尺寸,最终预览尺寸只能等到openCamera之后,CameraCallback中提供了回调接口onPreview在此我们可以设置SurfaceView的大小比例来适配Camera预览尺寸,避免预览页面拉升或压缩。

三.最后

本文介绍了Camera+SurfaceView的基本操作及关键代码,但是你如果看github中代码会发现和文中出入很大。其原因在于文章我想用一种简单的方式让没有做过自定义Camera的人也能明白。而github中的项目我已经进行了多次重构、抽象。其中包括对Camera的抽象,定义ICameraManager接口。对预览的View的重构,定义BaseCameraView接口,以及预览视图的众多超类。

lib-camera库包结构如下:

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

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

github地址:https://github.com/xiaozhi003/AndroidCamera

参考:

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

http://www.ppmy.cn/news/1521759.html

相关文章

20240902软考架构-------软考96-100答案解析

每日打卡题96-100答案 96、【2018年真题】 难度&#xff1a;难 CORBA服务端构件模型中&#xff0c; 是CORBA对象的真正实现&#xff0c;负责完成客户端请求。 A.伺服对象&#xff08;Servant&#xff09; B.对象适配器&#xff08;Object Adapter&#xff09; C.对象请求代理&…

回溯——7.子集II

力扣题目链接 给定一个可能包含重复元素的整数数组 nums&#xff0c;返回该数组所有可能的子集&#xff08;幂集&#xff09;。 说明&#xff1a;解集不能包含重复的子集。 示例: 输入: [1,2,2]输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ] 解题思路总结&#xff1a; …

AIStarter改进计划:功能优化与内测预告【欢迎吐槽】

随着技术的不断进步&#xff0c;AIStarter也在持续进化&#xff0c;以更好地满足用户的需求。本文将探讨AIStarter的改进计划&#xff0c;包括应用版本号、市场排序、描述和筛选功能的优化&#xff0c;并预告即将到来的内测消息。此外&#xff0c;还将介绍AIStarter在网络加速、…

东南大学研究生-数值分析上机题(2023)Python 3 线性代数方程组数值解法

列主元Gauss消去法 3.1 题目 对于某电路的分析&#xff0c;归结为就求解线性方程组 R I V \pmb{RIV} RIV&#xff0c;其中 R [ 31 − 13 0 0 0 − 10 0 0 0 − 13 35 − 9 0 − 11 0 0 0 0 0 − 9 31 − 10 0 0 0 0 0 0 0 − 10 79 − 30 0 0 0 − 9 0 0 0 − 30 57 − 7 …

【2024-2025源码+文档+调试讲解】微信小程序的城市公交查询系统

摘 要 当今社会已经步入了科学技术进步和经济社会快速发展的新时期&#xff0c;国际信息和学术交流也不断加强&#xff0c;计算机技术对经济社会发展和人民生活改善的影响也日益突出&#xff0c;人类的生存和思考方式也产生了变化。传统城市公交查询管理采取了人工的管理方法…

怎么摆脱非自然链接?

什么是非自然链接&#xff1f; 非自然链接是人为创建的链接&#xff0c;用于操纵网站在搜索引擎中的排名。非自然链接违反了Google 的准则&#xff0c;网站可能会因此受到惩罚。 它们不是由网站所有者编辑放置或担保的。示例包括带有过度优化锚文本的链接、通过 PR 的广告、嵌…

Nginx部署前端VUE项目

要部署一个Vue项目&#xff0c;可以使用nginx作为web服务器。下面是一些步骤&#xff1a; 确保你已经在本地机器上安装了nginx。如果没有安装&#xff0c;请按照官方文档进行安装。 将Vue项目构建为静态文件。在项目根目录下运行以下命令&#xff1a; npm run build这将在项…

如何在Excel中创建一个VBA宏,并设置一个按钮来执行这个宏

下面是一个详细的步骤指南 步骤1&#xff1a;创建VBA宏 1. 打开Excel并按 Alt F11 打开VBA编辑器。 2. 在VBA编辑器中&#xff0c;选择 Insert > Module 来插入一个新的模块。 3. 将以下代码粘贴到模块中&#xff1a; vba Sub CreateNewSheet() 声明一个工作表对象Dim …

【STM32项目设计】STM32F411健康助手--MPU6050陀螺仪驱动(6)

硬件设计 软件设计 此项目使用的是软件I2C&#xff0c;MPU6050的SCL连接到STM32的PB10&#xff0c;SDA连接到STM32的PB9 mpuiic.c #include "mpuiic.h" #include "delay.h"//MPU IIC 延时函数 void MPU_IIC_Delay(void) {delay_us(2); }//初始化IIC voi…

AWS账号关闭后的影响:您需要知道的一切

亚马逊网络服务&#xff08;AWS&#xff09;作为全球领先的云计算平台&#xff0c;为众多企业和个人提供了便捷、高效的云服务。然而&#xff0c;当用户决定关闭其AWS账号时&#xff0c;可能会对其现有的服务和资源产生重大影响。我们九河云将通过本文将深入探讨AWS账号关闭后的…

SQLite 创建表:一场数据库里的“造物运动”

嘿&#xff0c;各位数据库的“造物主”们&#xff01;今天咱们来聊聊SQLite里的一场有趣活动——创建表。没错&#xff0c;就像上帝创造了世界&#xff0c;我们也可以在SQLite数据库里创造属于我们自己的“小世界”。 一、创建表的“魔法咒语” 在SQLite这个“魔法世界”里&a…

ARM基础知识---CPU---处理器

目录 一、ARM架构 1.1.RAM---随机存储器 1.2.ROM---只读存储器 1.3.flash---闪存存储器 1.4.时钟&#xff08;振晶&#xff09; 1.5.复位 二、CPU---ARM920T 2.1.R0~R12---通用寄存器 2.2.PC程序计数器 2.3.LR连接寄存器 2.4.SP栈指针寄存器 2.5.CPSR当前程序状态寄存…

利用KMeans重新计算自己数据集的anchor

在YOLOv5或YOLOv7中&#xff0c;anchors&#xff08;锚框&#xff09;是预设的一组不同大小、不同长宽比的边界框&#xff0c;它们用于在图像中的每个网格单元上进行偏移和缩放&#xff0c;以生成目标的候选框。这些anchors的设定对于提高目标检测的效率和准确性至关重要。 并…

ArcGIS中怎么合并多个点图层并删除重复点?

最近&#xff0c;我接到了一个怎么合并多个点图层并删除其中的重复点的咨询。 下面是我对这个问题的解决思路&#xff1a; 1、合并图层 在地理处理工具里面 选择合并 并设置好要合并的图层即可 2、接下来在 数据管理工具→常规→删除相同项 即可 希望这些建议能对大家有所帮…

docker国内镜像仓库地址

1.vi /etc/docker/daemon.json 2.配置文件内容修改为&#xff1a; { "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com", "https://docker.mirrors.ustc.edu.cn", &…

Gartner报告解读:如何帮助企业完善数据分析与治理路线图

Gartner服务于全球100多个国家和地区的14,000余家机构&#xff0c;是一家深受客户信赖、观点客观的研究顾问公司。Garnter洞察、建议和工具可帮助您发现创新机遇&#xff0c;完成关键优先任务&#xff0c;助您成为企业不可或缺的战略专家和价值创造者。该公司是标普 500 指数成…

一个好的云渲染,需要具备哪些条件?

​现在云渲染很多设计师和公司都在用&#xff0c;他不仅可以减少设计师和公司的硬件投入&#xff0c;还能提高工作效率&#xff0c;缩短项目周期。一个好的云渲染平台应当具备以下几个关键条件&#xff0c;以确保高效、稳定、灵活且成本效益高的渲染服务&#xff1a; 一、强大…

使用isolation: isolate声明隔离混合模式

在CSS中&#xff0c;isolation 属性与混合模式&#xff08;如 mix-blend-mode 和 background-blend-mode&#xff09;并不直接相关&#xff0c;但它确实可以影响元素如何与其他元素进行渲染&#xff0c;尤其是在涉及到堆叠上下文&#xff08;stacking contexts&#xff09;和复…

代码随想录 刷题记录-24 图论 (1)理论基础 、深搜与广搜

一、理论基础 参考&#xff1a; 图论理论基础 深度优先搜索理论基础 广度优先搜索理论基础 dfs dfs搜索可一个方向&#xff0c;并需要回溯&#xff0c;所以用递归的方式来实现是最方便的。 有递归的地方就有回溯&#xff0c;例如如下代码&#xff1a; void dfs(参数) {…

828华为云征文|采用华为云Flexus云服务器X实例部署YOLOv3算法完成目标检测

文章目录 一、前言1.1 开发需求1.2 Flexus云服务器介绍1.3 YOLOv3目标检测算法1.4 客户端开发思路1.5 客户端运行效果 二、服务器选购2.1 登录官网2.2 选购服务器2.3 选择服务器区域2.4 选择服务器规格2.5 选择系统镜像2.6 选择存储盘2.7 配置密码2.8 配置云备份2.9 确认配置2.…