Android Camera系列(一):SurfaceView+Camera

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

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

Alt

一. Camera操作

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

  • android.hardware.Camera:最早用来自定义Camera的API
  • android.hardware.camera2.*:Android5.0之后推荐使用的API,对Camera操作更灵活,功能更丰富
  • CameraX:对Camera2的封装,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:

/*** Camera和Camera2通用接口** @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使用

我们要预览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() {}
}

1.Camera操作时机

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

2.SurfaceView大小计算时机

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

三.最后

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

lib-camera库包结构如下:

说明
cameracamera相关操作功能包,包括Camera和Camera2。以及各种预览视图
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.mzph.cn/bicheng/53152.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue 批量导出pdf 压缩包 zip

vue 批量导出pdf 压缩包 zip 使用插件 html2canvas jspdf jszip &#xff08;百度ai搜出来的是zip-js 这个没法安装&#xff09; file-saver 思路&#xff1a; 1.使用 html2canvasjspdf 将页面转图片转pdf&#xff08;这个怎么转的可以网上搜下很多&#xff09; 2.利用jszipfil…

【免费分享】易上手的STM32多核心开发板资料包一网打尽,教程+课件+视频+项目+源码

想要深入学习STM32开发吗&#xff1f;现在机会来了&#xff01;我们为初学者们准备了一份全面的资料包&#xff0c;包括原理图、教程、课件、视频、项目、源码等&#xff0c;所有资料全部免费领取&#xff0c;课程视频可试看&#xff08;购买后看完整版&#xff09;&#xff0c…

PostgreSQL技术内幕7:PostgreSQL查询编译

文章目录 0.简介1.整体过程2.查询分析2.1 Lex2.2 Yacc2.3 PG词法分析和语法分析介绍2.4 PG语义分析 4.查询优化4.1 预处理4.1.1 提升子链接和子查询4.1.2 预处理表达式4.1.3 处理HAVING子句 4.2 改进查询树4.2.1 路径生成4.2.2 代价估计 4.3 计划生成 0.简介 一次完整的SQL执行…

STM32基础篇:PWR

PWR简介 PWR&#xff08;Power Control&#xff09;&#xff0c;为电源控制模块&#xff0c;负责管理STM32内部的电源供电部分&#xff0c;可以实现可编程电压监测器和低功耗模式的功能。 1、可编程电压监测器 简称PVD&#xff0c;可以监控VDD电源电压。当VDD下降到PVD阀值以…

爬虫入门学习

流程 获取网页内容 HTTP请求 Python Requests解析网页内容 HTML网页结构 Python Beautiful Soup储存或分析数据 HTTP (Hypertext Transfer Protocol) 客户端和服务器之间的请求-响应协议 Get方法&#xff1a;获得数据 POST方法&#xff1a;创建数据 HTTP请求 请求行 方法类型…

rv1126-rv1109-mkcramfs-mkfs.cramfs-打包文件系统

事情是这样的: 定制了文件系统打包功能;然后我是根据这个指令 fakeroot mkfs.cramfs rootfs_glibc_rv1126/ rootfs.img mkfs.cramfs rootfs_glibc_rv1126/ rootfs.img 起因就是这个fakeroot; 不加的话打出来的rootfs.img是没有用户权限的 然后我根据fakeroot mkfs.cramfs ro…

AcWing算法基础课-785快速排序-Java题解

大家好&#xff0c;我是何未来&#xff0c;本篇文章给大家讲解《AcWing算法基础课》785 题——快速排序。这篇文章介绍了使用快速排序算法对整数数列进行排序的方法&#xff0c;包括选择基准元素、分区操作和递归排序子数组。通过详细的步骤和示例&#xff0c;解释了快速排序的…

MySQL之数据库基础

目录 一、数据库 1、基本概念 2、常见的数据库 3、MySQL数据库 连接MySQL服务器 数据逻辑存储 二、数据库和表的本质 三、SQL语句 四、服务器&#xff0c;数据库&#xff0c;表的关系 五、存储引擎 查看存储引擎 一、数据库 1、基本概念 一般来说&#xff0c;数据库…

es映射配置(_mapping)

文章目录 1、创建映射字段2、查看映射关系 1、创建映射字段 PUT /索引库名/_mapping {"properties": {"字段名": {"type": "类型","index": true&#xff0c;"store": true&#xff0c;"analyzer": &q…

视频结构化从入门到精通——视频结构化主要技术介绍

视频结构化主要技术 1 视频接入 “视频接入”是视频结构化管道的起点&#xff08;SRC Point&#xff09;视频接入是视频结构化处理的第一步&#xff0c;它涉及将视频数据从各种采集源获取到系统中进行进一步处理。视频接入的质量和稳定性对后续的数据处理、分析和应用至关重要…

多参数遥测终端科技守护水电站生态流量下泄

随着我国水电事业的蓬勃发展&#xff0c;水电站在推动地方经济快速增长、缓解能源压力方面发挥了不可替代的作用。然而带来的生态环境问题日益凸显&#xff0c;因水电站下泄流量不足造成部分河段减水、脱水甚至干涸&#xff0c;影响了河流的正常生态功能和居民的生产、生活。因…

【硬件操作入门】2--GPIO与门电路、二极管三极管、LED电路与操作

【硬件操作入门】2–GPIO与门电路&#xff08;二极管&三极管&#xff09;、LED电路与操作 文章目录 【硬件操作入门】2--GPIO与门电路&#xff08;二极管&三极管&#xff09;、LED电路与操作一、GPIO与门电路1.1、GPIO的应用1.2、GPIO引脚操作1.2.1 设置引脚为GPIO功能…

day39(8/29)——harbor私有仓库管理

一、harbor私有仓库管理 是python的包管理工具&#xff0c;和yum对redhat的关系是一样的 yum -y install epel-release yum -y install python2-pip pip install --upgrade pip pip list pip 8x pip install --upgrade pip pip install --upgrade pip20.3 -i https://mirror…

RFID光触发标签在文件柜管理中的创新应用

在当今信息化时代&#xff0c;文件管理对于企业和机构的重要性不言而喻。传统的文件柜管理方式存在诸多问题&#xff0c;如查找文件困难、管理效率低下、安全性难以保障等。而 RFID 光触发标签技术的出现&#xff0c;为文件柜管理带来了全新的解决方案。 一、传统文件柜管理的…

Spring扩展点系列-@PostConstruct

简介 spring的Bean在创建的时候会进行初始化&#xff0c;而初始化过程会解析出PostConstruct注解的方法&#xff0c;并反射调用该方法。 PostConstruct 的使用和特点 只有一个非静态方法能使用此注解&#xff1b;被注解的方法不得有任何参数&#xff1b;被注解的方法返回值必…

实际开发中git在IDEA中的使用

相信搜索这个的同学代码都已经拉取到本地了&#xff0c;并且已经在idea中打开了。 1.一般我们从远程colone下来的代码默认是在主分支下的&#xff0c;也就是说我们从远程的主分支拉取的代码并且在本地创建了一个主分支。 2.一般主分支是不允许修改的&#xff0c;所以我们可以基…

SpringBoot2:配置绑定与自动配置功能源码解读

一、配置绑定 1、作用说明 我们在开发springboot项目时&#xff0c;会有个配置文件&#xff0c;application.properties文件。 我们知道&#xff0c;像什么访问端口、上传功能的相关配置&#xff0c;都会在这里进行配置。 而这些&#xff0c;都是springboot自带的或者第三方j…

Linux | 匿名管道和命名管道:进程间通信数据流的桥梁

目录 1、进程间通信目的 2、管道——匿名管道和命名管道 匿名管道 匿名管道的示例代码&#xff1a;将数据写入管道、子进程从管道读取数据并将其输出到bash中 父子进程通过匿名管道建立通信 重点&#xff1a;管道的五个特点 命名管道&#xff08;也称为FIFO&#xff09;…

每日一题,零基础入门FPGA——工程师在线精讲,直播预告

题目传送门&#xff1a;F学社 zzfpga.com/StudentPlatform/Sheet/QuestionBankhttp://zzfpga.com/StudentPlatform/Sheet/QuestionBank 【第Ⅰ期题目 * 5】 请使用D触发器和必要的逻辑门实现此同步时序电路&#xff0c;用Verilog语言描述。 【第Ⅰ期题目 * 4】 请设计一个0…

观测云核心技术揭秘:基于时间的半结构化数据模型

前言 众所周知&#xff0c;真正意义上的统一监控观测平台本质上是一个超大的数据湖&#xff0c;其存储了大量的来自监控指标&#xff0c;各种各样的日志&#xff0c;各种各样的链路追踪以及包括用户访问行为等海量的可观测性数据。 这些海量数据有什么特点呢&#xff1f; 首先…