安卓CameraX的使用

如果要在安卓应用中操作相机,有三个库可以选:

  1. Camera(已废弃):Camera是安卓最早的包,目前已废弃,在Android 5.0(API 级别 21)的设备上操作相机可以选择该包,保证兼容性;
  2. Camera2:Camera2在Android 5.0(API 级别 21)开始提供,用于替代Camera,相比Camera,在性能、灵活性等方面有优势,但是使用起来更复杂;
  3. CameraX:CameraX是对Camera2的封装,是Jetpack的一个库,支持Android 5.0(API 级别 21)及更高版本。CameraX降低了操作相机的难度,大多数情况下,使用CameraX已足以完成需求,建议使用CameraX。

注:Jetpack是一个由多个库组成的套件,可帮助开发者遵循最佳做法、减少样板代码并编写可在各种Android版本和设备中一致运行的代码,让开发者可将精力集中于真正重要的编码工作。

谷歌的文档只提供了Kotlin的代码示例,根据Kotlin的代码示例和文档,本文提供Java的实现。

1. CameraX常见用例

您可以使用CameraX,借助名为“用例”的抽象概念与设备的相机进行交互。提供的用例如下:

  • 预览(Preview):在屏幕上查看相机画面;
  • 图片分析(ImageAnalysis):逐帧处理相机捕获到的每一帧画面;
  • 图片拍摄(ImageCapture):拍照;
  • 视频拍摄(VideoCapture):拍视频(和音频)。

CameraX允许同时使用Preview、VideoCapture、ImageAnalysis和ImageCapture各一个实例。此外:

  • 每个用例都可以单独使用。例如,应用可以在不使用预览的情况下录制视频;
  • 启用扩展后,只能保证能够使用ImageCapture和Preview的组合。根据OEM实现情况,可能无法同时添加ImageAnalysis;无法为VideoCapture用例启用扩展。如需了解详情,请参阅扩展参考文档;
  • 对于某些相机而言,在较低分辨率模式下可以支持的组合,在较高的分辨率下将无法支持,这具体取决于相机的功能;
  • 在相机硬件级别为FULL或更低的设备上,组合使用Preview、VideoCapture和ImageCapture或ImageAnalysis可能会迫使CameraX为Preview和VideoCapture复制相机的PRIV数据流。这种重复(称为数据流共享)可让您同时使用这些功能,但代价是增加了处理需求。因此,您可能会遇到略长的延迟时间和缩短的电池续航时间。

2. 代码实现

在build.gradle中添加相关依赖:

// camerax
def camerax_version = "1.3.0"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
implementation "androidx.camera:camera-view:${camerax_version}"
implementation "androidx.camera:camera-video:${camerax_version}"
// 扩展,本文代码未使用
// implementation "androidx.camera:camera-extensions:${camerax_version}"

在AndroidManifest.xml添加相关权限:

<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

添加布局文件activity_camerax_preview.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:fitsSystemWindows="true"><androidx.camera.view.PreviewViewandroid:id="@+id/camerax_preview_view"android:layout_width="match_parent"android:layout_height="match_parent" /><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_alignParentBottom="true"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center"><Buttonandroid:id="@+id/camerax_flash"android:layout_width="110dp"android:layout_height="70dp"android:layout_marginStart="10dp"android:layout_marginTop="5dp"android:layout_marginEnd="10dp"android:layout_marginBottom="5dp"android:text="开灯" /><Buttonandroid:id="@+id/camerax_switch_camera"android:layout_width="110dp"android:layout_height="70dp"android:layout_marginStart="10dp"android:layout_marginTop="5dp"android:layout_marginEnd="10dp"android:layout_marginBottom="5dp"android:text="切换摄像头" /></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center"><Buttonandroid:id="@+id/camerax_image_capture_button"android:layout_width="110dp"android:layout_height="70dp"android:layout_marginStart="10dp"android:layout_marginTop="5dp"android:layout_marginEnd="10dp"android:layout_marginBottom="5dp"android:text="拍照" /><Buttonandroid:id="@+id/camerax_video_capture_button"android:layout_width="110dp"android:layout_height="70dp"android:layout_marginStart="10dp"android:layout_marginTop="5dp"android:layout_marginEnd="10dp"android:layout_marginBottom="5dp"android:text="开始录制" /><Buttonandroid:id="@+id/camerax_video_capture_pause_button"android:layout_width="110dp"android:layout_height="70dp"android:layout_marginStart="10dp"android:layout_marginTop="5dp"android:layout_marginEnd="10dp"android:layout_marginBottom="5dp"android:text="暂停录制"android:visibility="gone" /></LinearLayout></LinearLayout></RelativeLayout>
</RelativeLayout>

activity类:

package org.tao.hetools.activities;import android.Manifest;
import android.content.ContentValues;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.util.Size;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;import androidx.activity.ComponentActivity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.Preview;
import androidx.camera.core.TorchState;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.video.MediaStoreOutputOptions;
import androidx.camera.video.Quality;
import androidx.camera.video.QualitySelector;
import androidx.camera.video.Recorder;
import androidx.camera.video.Recording;
import androidx.camera.video.VideoCapture;
import androidx.camera.view.PreviewView;
import androidx.core.content.ContextCompat;import com.google.common.util.concurrent.ListenableFuture;import org.tao.hetools.R;
import org.tao.hetools.utils.PermissionUtils;import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;public class CameraxPreviewActivity extends ComponentActivity {private static final String TAG = "CameraxPreviewActivity";private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHmmssSSS");private static final Size SIZE = new Size(1080, 1920);private static final List<CameraSelector> CAMERAS = Arrays.asList(CameraSelector.DEFAULT_BACK_CAMERA,CameraSelector.DEFAULT_FRONT_CAMERA);private static final String START_RECORD = "开始录制";private static final String STOP_RECORD = "结束录制";private static final String PAUSE_RECORD = "暂停录制";private static final String RESUME_RECORD = "恢复录制";private PreviewView previewView;private Executor executor;private Preview preview;private ImageAnalysis imageAnalysis;private ImageCapture imageCapture;private VideoCapture<Recorder> videoCapture;private Recording videoRecording;private ProcessCameraProvider cameraProvider;private Camera camera;private boolean isRecordingStart = false;private boolean isRecordingPause = false;private int cameraIndex = 0;private Button flashButton;private Button pauseResumeVideoButton;private Button startShopVideoButton;private Button switchCameraButton;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);this.setContentView(R.layout.activity_camerax_preview);PermissionUtils.checkPermission(this, Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO,Manifest.permission.WRITE_EXTERNAL_STORAGE);previewView = findViewById(R.id.camerax_preview_view);executor = ContextCompat.getMainExecutor(this);initCamerax();initButton();}private void initCamerax() {// 可以将相机生命周期绑定到activity,从而免去打开、关闭相机的任务ListenableFuture<ProcessCameraProvider> cameraProviderListenableFuture = ProcessCameraProvider.getInstance(this);cameraProviderListenableFuture.addListener(() -> {try {cameraProvider = cameraProviderListenableFuture.get();// 预览preview = new Preview.Builder().build();preview.setSurfaceProvider(previewView.getSurfaceProvider());// 图片分析imageAnalysis = new ImageAnalysis.Builder()// 设置输出图片格式.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)// 非阻塞模式,只取最新的图片。若需要每帧图片都处理,使用阻塞模式 ImageAnalysis.STRATEGY_BLOCK_PRODUCER.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).setTargetResolution(SIZE).build();imageAnalysis.setAnalyzer(executor, (imageProxy) -> {// close后才认为处理完成当前帧,所以放到try-with-resources语句中try (imageProxy) {// 在此处处理图片,如对图片进行人脸、条码识别等Log.i(TAG, "接收到一帧图片" + SIMPLE_DATE_FORMAT.format(new Date()));} catch (Exception exception) {Log.w(TAG, exception.getMessage());}});// 拍照imageCapture = new ImageCapture.Builder().setFlashMode(ImageCapture.FLASH_MODE_AUTO).setTargetResolution(SIZE).build();// 录制Recorder recorder = new Recorder.Builder().setQualitySelector(QualitySelector.from(Quality.FHD)).build();videoCapture = VideoCapture.withOutput(recorder);cameraProvider.unbindAll();camera = cameraProvider.bindToLifecycle(this, CAMERAS.get(cameraIndex), preview,imageCapture, videoCapture);} catch (ExecutionException e) {throw new RuntimeException(e);} catch (InterruptedException e) {throw new RuntimeException(e);}}, executor);}private void initButton() {initFlashButton();initSwitchButton();initImageCaptureButton();initVideoCaptureButtons();}/*** 视频操作按钮*/private void initVideoCaptureButtons() {startShopVideoButton = findViewById(R.id.camerax_video_capture_button);pauseResumeVideoButton = findViewById(R.id.camerax_video_capture_pause_button);// 开始/停止录制视频startShopVideoButton.setOnClickListener(view -> {if (videoCapture == null || executor == null) {return;}if (isRecordingStart) {videoRecording.stop();videoRecording = null;isRecordingStart = false;startShopVideoButton.setText(START_RECORD);// 视频录制停止后不显示"暂停/恢复录制"按钮pauseResumeVideoButton.setVisibility(View.GONE);// 录制视频完成后显示切换摄像头按钮switchCameraButton.setVisibility(View.VISIBLE);return;}PermissionUtils.checkPermission(this, Manifest.permission.RECORD_AUDIO);videoRecording = videoCapture.getOutput().prepareRecording(this, getMediaStoreOutputOptions()).withAudioEnabled().start(executor, videoRecordEvent -> {});isRecordingStart = true;startShopVideoButton.setText(STOP_RECORD);pauseResumeVideoButton.setText(PAUSE_RECORD);// 视频录制开始后显示"暂停/恢复录制"按钮pauseResumeVideoButton.setVisibility(View.VISIBLE);// 录制视频期间不允许切换摄像头,切换摄像头会终止录制switchCameraButton.setVisibility(View.GONE);});// 暂停/恢复录制视频pauseResumeVideoButton.setOnClickListener(view -> {if (videoCapture == null || executor == null || videoRecording == null || !isRecordingStart) {return;}if (isRecordingPause) {isRecordingPause = false;videoRecording.resume();pauseResumeVideoButton.setText(PAUSE_RECORD);} else {videoRecording.pause();isRecordingPause = true;pauseResumeVideoButton.setText(RESUME_RECORD);}});}/*** 拍照按钮*/private void initImageCaptureButton() {findViewById(R.id.camerax_image_capture_button).setOnClickListener(view -> {if (imageCapture == null || executor == null) {return;}imageCapture.takePicture(getImageOutputFileOptions(), executor,new ImageCapture.OnImageSavedCallback() {@Overridepublic void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {Log.i(TAG, "save picture success");Toast.makeText(CameraxPreviewActivity.this, "success", Toast.LENGTH_SHORT).show();}@Overridepublic void onError(@NonNull ImageCaptureException exception) {Log.w(TAG, "error" + exception.getMessage());Toast.makeText(CameraxPreviewActivity.this, "fail", Toast.LENGTH_SHORT).show();}});});}/*** 切换摄像头按钮*/private void initSwitchButton() {switchCameraButton = findViewById(R.id.camerax_switch_camera);switchCameraButton.setOnClickListener((view -> {if (cameraProvider == null) {return;}if (++cameraIndex >= CAMERAS.size()) {cameraIndex = 0;}cameraProvider.unbindAll();cameraProvider.bindToLifecycle(this, CAMERAS.get(cameraIndex), preview, imageAnalysis,imageCapture, videoCapture);resetButtonStatus(cameraIndex);}));}/*** 闪光灯按钮*/private void initFlashButton() {flashButton = findViewById(R.id.camerax_flash);flashButton.setOnClickListener(view -> {if (camera == null) {return;}boolean isTorchOff = camera.getCameraInfo().getTorchState().getValue() == TorchState.OFF;camera.getCameraControl().enableTorch(isTorchOff);flashButton.setText(camera.getCameraInfo().getTorchState().getValue() == TorchState.ON ? "关灯" : "开灯");});}/*** 重设按钮状态*/private void resetButtonStatus(int cameraIndex) {flashButton.setText("开灯");flashButton.setVisibility(cameraIndex == 0 ? View.VISIBLE : View.GONE);startShopVideoButton.setText(START_RECORD);pauseResumeVideoButton.setText(PAUSE_RECORD);pauseResumeVideoButton.setVisibility(View.GONE);}private ImageCapture.OutputFileOptions getImageOutputFileOptions() {return new ImageCapture.OutputFileOptions.Builder(getContentResolver(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI,getContentValues("image/png")).build();}private MediaStoreOutputOptions getMediaStoreOutputOptions() {return new MediaStoreOutputOptions.Builder(getContentResolver(),MediaStore.Video.Media.EXTERNAL_CONTENT_URI).setContentValues(getContentValues("video/mp4")).build();}private ContentValues getContentValues(String mimeType) {String name = SIMPLE_DATE_FORMAT.format(new Date());ContentValues contentValues = new ContentValues();contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name);contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/HeTools");}return contentValues;}
}

参考文章

  1. 开始使用Android相机

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

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

相关文章

微服务即时通讯系统的实现(客户端)----(2)

目录 1. 将protobuf引入项目当中2. 前后端交互接口定义2.1 核心PB类2.2 HTTP接口定义2.3 websocket接口定义 3. 核心数据结构和PB之间的转换4. 设计数据中心DataCenter类5. 网络通信5.1 定义NetClient类5.2 引入HTTP5.3 引入websocket 6. 小结7. 搭建测试服务器7.1 创建项目7.2…

02_Spring_IoC实现

接下来先简单说一下关于IoC的一些要点,后面我们再详细一步一步讨论。 一、IoC控制反转 IoC控制反转它是一种思想,不是具体的实现控制反转的目的是为了降低程序的耦合度,提高程序的可扩展性,从而满足OCP原则和DIP原则控制反转,那到底反转是什么东西? 我们不再使用某个对象…

凸函数与深度学习调参

问题1&#xff1a;如何区分凸问题和凹问题&#xff1f; 问题2&#xff1a;深度学习如何区分调参&#xff1f;

游戏开发实现简易实用的ui框架

游戏开发实现简易实用的ui框架 本文使用cocos引擎实现&#xff0c;框架代码本质上不依赖某一个引擎&#xff0c;稍作修改也能作为其他引擎的实现 1.1 UI管理框架的核心需求剖析 分层与类型管理 对不同类型UI需要进行分层管理。不同层级的UI需要有不同的父节点&#xff0c;保证渲…

使用可视化工具kafkatool连接docker的kafka集群,查看消息内容和offset

1、下载kafkatool 下载地址Offset Explorer&#xff0c;下载对应系统的offset explorer 下载完&#xff0c;傻瓜安装即可&#xff08;建议放D盘&#xff09;&#xff0c;在开始菜单输入offset找到该应用打开 打开 2、连接kafka 点击File > add new connection Bootstrap…

关于Java使用ueditor上传图片的一些总结

1.如何配置ueditor让上传的图片到项目之外&#xff1f; 因为图片上传到web项目中,重新部署项目可能会丢失图片。 解决方法&#xff1a;下载ueditor.1.1.2.jar. 地址&#xff1a;ueditor-1.1.2项目源码及jar包.zip 链接: https://pan.baidu.com/s/1Bhumfw8OX16n0MTO9ur73g 提…

React可以做全栈开发吗

React可以做全栈开发吗? 答案是肯定的&#xff0c;而且还比较完美 React可以用于全栈开发&#xff0c;以下是具体的介绍&#xff1a; 前端部分 构建用户界面 React是一个用于构建用户界面的JavaScript库&#xff0c;它通过组件化的方式让开发者能够高效地创建交互式的UI。例…

【前端学习笔记】Javascript学习二(运算符、数组、函数)

一、运算符 运算符&#xff08;operator&#xff09;也被称为操作符&#xff0c;是用于实现赋值、比较和执行算数运算等功能的符号。 JavaScript中常用的运算符有&#xff1a; 算数运算符、递增和递减运算符、比较运算符、逻辑运算符、赋值运算符 算数运算符&#xff1a; 、-…

Redis五大基本类型——List列表命令详解(命令用法详解+思维导图详解)

目录 一、List列表类型介绍 二、常见命令 1、LPUSH 2、LPUSHX 3、RPUSH 4、RPUSHX 5、LRANGE 6、LPOP 7、RPOP 8、LREM 9、LSET 10、LINDEX 11、LINSERT 12、LLEN 13、阻塞版本命令 BLPOP BRPOP 三、命令小结 相关内容&#xff1a; Redis五大基本类型——Ha…

快速入门消息队列MQ、RabbitMQ

目录 一、MQ简介 1.同步调用 2.异步调用 3.技术选型 二、RabbitMQ 1.安装 2.控制台的使用说明 2.1交换机 2.2队列​编辑 2.3绑定关系 3.AMQP 3.1快速入门 3.2WorkQueues模型 3.3交换机 3.3.1 Fanout交换机 3.3.2 Direct交换机 3.3.3 Topic交换机 3.4 声明交换机…

CRM系统安全性排名:数据保护能力评估

安全性对CRM系统厂商和企业来说至关重要&#xff0c;因为它直接关系到企业的客户数据、商业机密和品牌信誉。CRM系统通常存储了大量敏感信息&#xff0c;包括客户联系详情、交易记录和个人身份信息&#xff0c;一旦发生数据泄露或安全事件&#xff0c;不仅可能导致客户信任丧失…

Spark SQL大数据分析快速上手-完全分布模式安装

【图书介绍】《Spark SQL大数据分析快速上手》-CSDN博客 《Spark SQL大数据分析快速上手》【摘要 书评 试读】- 京东图书 大数据与数据分析_夏天又到了的博客-CSDN博客 Hadoop完全分布式环境搭建步骤-CSDN博客,前置环境安装参看此博文 完全分布模式也叫集群模式。将Spark目…

《现代网络技术》读书笔记:NFV功能

本文部分内容来源于《现代网络技术&#xff1a;SDN,NFV,QoE、物联网和云计算&#xff1a;SDN,NFV,QoE,IoT,andcloud》 NFV基础设施 NFV体系结构的核心是资源与功能集合&#xff0c;也为称为NFV基础设施(NFVI)。NFVI包括以下三个域&#xff1a; 计算域&#xff1a;提供商用的大…

MySQL数据库2——SQL语句

一.SQL基础 1.SQL通用语法 1.SQL语句可以单行或多行书写&#xff0c;以分号结尾。2.SOL语句可以使用空格/缩进来增强语句的可读性。3.MySQL数据库的SQL语句不区分大小写&#xff0c;关键字建议使用大写 注释&#xff1a; 单行注释&#xff1a;-- 注释内容或#注释内容(MySQL…

会员等级经验问题

问题描述 会员从一级完成任务升级到二级以后&#xff0c;一级显示还差经验&#xff0c;这里差的其实是二级到三级的经验&#xff0c;如下图所示 修复方法 1、前端需要修改&#xff1a; 路径&#xff1a;/pages/users/user_vip/index.vue 方便复制&#xff1a; v-if"i…

【Apache Paimon】-- 6 -- 清理过期数据

目录 1、简要介绍 2、操作方式和步骤 2.1、调整快照文件过期时间 2.2、设置分区过期时间 2.2.1、举例1 2.2.2、举例2 2.3、清理废弃文件 3、参考 1、简要介绍 清理 paimon &#xff08;表&#xff09;过期数据可以释放存储空间&#xff0c;优化资源利用并提升系统运行效…

前端项目支持tailwindcss写样式

安装 npm install -D tailwindcss npx tailwindcss init配置 tailwind.config.js //根据个人需求填写&#xff0c;比如vue简单配置 /** type {import(tailwindcss).Config} */ module.exports {darkMode: "class",corePlugins: {preflight: false},content: [&quo…

Spring Boot整合Kafka,实现单条消费和批量消费,示例教程

如何安装Kafka&#xff0c;可以参考docker搭载Kafka集群&#xff0c;一个文件搞定&#xff0c;超简单&#xff0c;亲试可行-CSDN博客 1、在pom.xml中加入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-sta…

ora.LISTENER.lsnr : Not All Endpoints Registered

进行了几次VIP转移 发现该问题无法解决 $ crsctl status res -t -------------------------------------------------------------------------------- NAME TARGET STATE SERVER STATE_DETAILS -----------------------------…

django基于Python的农产品销售系统的设计与实现

摘 要 随着现代人们的快速发展&#xff0c;农产品销售系统已成为农产品的需求。该平台采用Python技术和django搭建系统框架&#xff0c;后台使用MySQL数据库进行信息管理&#xff1b;通过个人中心、用户管理、商家管理、产品类型管理、农产品管理、系统管理、订单管理等功能&a…