Android相机基础基于camera2API

Android相机基础基于camera2API

前言

最近,在使用Android做一个照相机的开发。因为不能使用系统提供的相机应用,所以只能自己写一个。Android以前提供相机的api叫camera,不过在level 21被Google抛弃了。网上的教程,还有很多都是使用camera的,为了好好学习一下camera2,就去扒了Google提供的官方示例。下面给一个github的连接,可以找到全部的源码。

Camera2Basic Sample

源码分析

下述的例子主要提供的功能是:相机预览、拍照、照片保存。具体如下:进入该应用后,可以看到相机的预览画面,提供一个按钮用于拍照,拍完照片后照片会保存在SD卡的根目录下。

架构分析

首先来了解一下camera2的整体结构:

 
图1 camera2整体架构图

如上所示,整个camera2由一个CameraManager来进行统一管理,通过Context的getSystemService方法可以实例化CameraManager,然后该类主要通过三个类来对Camera进行操作。下面分别介绍一下:

  • CameraDevice:描述一个照相机设备,一个Android设备可能会有多个摄像头,通过CameraId可以进行区别。它最主要有一个相机状态的回调函数,当下达打开相机的命令后,若相机正确的打开便会回调该函数。
  • CameraCharacteristic:某个照相机设备的具体参数。本例主要用到它提供的输出格式(即输出数据的格式)。
  • CameraCaptureSession:相机捕获会话,通过这个类可以和相机进行对话(预览还是单张拍照还是录像等)。这里有两个回调函数,捕获状态的回调,和捕获数据的回调(后文会有详述)。

上图左上部分所示的时Android设备和camera设备的通信情况,两者之间通过pipeline(管道)进行数据交换。当需要尽心不同的操作时,将CameraCaptureRequest通过管道传给camera,接收到请求后,camera做出相应的反应,将获取到得数据CmaeraMetadata通过管道传回给Android设备。

注意事项

本例,需要使用比较多的权限,请参看源码AndroidManifest.xml。

Android设备的屏幕方向,与摄像头的原始方向并不一致,需要做方向转换。一般而言,当Android设备横着放时,与摄像头的方向是一致的。

为了避免照片失真(照片被拉长或者压扁),需要保证预览的长宽比例、照片的长宽比例和相机输出格式的长宽比例三者保持一致。

代码流转

本例当中,用一个activity承载一个fragment。所有的代码都写在fragment里面,重写了fragment的几个生命周期函数:

  • onCreateView:加载fragment的布局文件;
  • onViewCreated:实例化布局控件;
  • onActivityCreated:在SD卡的目录下建立jpg文件等待待将拍到的照片写进去;
  • onResume:开始照相机线程,执行一些逻辑判断;
  • onPause:关闭照相机,停止照相机线程;
 
图2 代码整体流程

正常来说,代码的整体流程如图2所示,activity将需要的fragment加载进来后,开始加载显示预览的控件texture,当控件加载完毕会执行一个回调函数onSurfaceTextureAvailable(),在这个回调函数里面,打开摄像头(即执行openCamera())。

openCamera()里面,首先要配置相机的输出,预览图像和拍照的图片要作不同的处理,然后根据当前的设备屏幕环境,判断是否需要进行数据的转换,最后通过cameraManager打开摄像头(调用cameraManager.openCamera()方法)。

更详细的方法请看后面的源码,图2当中,中间是判断屏幕方向的逻辑,右边是自动选择最合适的显示逻辑。

源码

// 代码比较长,请耐心查看,注释可能有不正确的地方,请提出
// 注意,下面代码为了配合我的使用,已经去掉了按钮,但是拍照的方法仍然保留,通过调用方法可以完成拍照
package com.eric_lai.weeding_robot.fragment;

/** * Created by ERIC_LAI on 16/3/18. */ import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.Fragment; import android.content.Context; import android.content.DialogInterface; import android.content.res.Configuration; import android.graphics.ImageFormat; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.RectF; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.Image; import android.media.ImageReader; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.support.annotation.NonNull; import android.util.Log; import android.util.Size; import android.util.SparseIntArray; import android.view.LayoutInflater; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; import com.eric_lai.weeding_robot.R; import com.eric_lai.weeding_robot.view.AutoFitTextureView; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; public class CameraFragment extends Fragment { /** * Conversion from screen rotation to JPEG orientation. */ private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); private static final String FRAGMENT_DIALOG = "dialog"; static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); ORIENTATIONS.append(Surface.ROTATION_180, 270); ORIENTATIONS.append(Surface.ROTATION_270, 180); } /** * 调试用TAG */ private static final String TAG = "CameraFragment"; /** * 相机状态: * 0: 预览 * 1: 等待上锁(拍照片前将预览锁上保证图像不在变化) * 2: 等待预拍照(对焦, 曝光等操作) * 3: 等待非预拍照(闪光灯等操作) * 4: 已经获取照片 */ private static final int STATE_PREVIEW = 0; private static final int STATE_WAITING_LOCK = 1; private static final int STATE_WAITING_PRECAPTURE = 2; private static final int STATE_WAITING_NON_PRECAPTURE = 3; private static final int STATE_PICTURE_TAKEN = 4; /** * Camera2 API提供的最大预览宽度和高度 */ private static final int MAX_PREVIEW_WIDTH = 1920; private static final int MAX_PREVIEW_HEIGHT = 1080; /** * SurfaceTexture监听器 */ private final TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) { // SurfaceTexture就绪后回调执行打开相机操作 openCamera(width, height); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) { // 预览方向改变时, 执行转换操作 configureTransform(width, height); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) { return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture texture) { } }; /** * 正在使用的相机id */ private String mCameraId; /** * 预览使用的自定义TextureView控件 */ private AutoFitTextureView mTextureView; /** * 预览用的获取会话 */ private CameraCaptureSession mCaptureSession; /** * 正在使用的相机 */ private CameraDevice mCameraDevice; /** * 预览数据的尺寸 */ private Size mPreviewSize; /** * 相机状态改变的回调函数 */ private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice cameraDevice) { // 当相机打开执行以下操作: // 1. 释放访问许可 // 2. 将正在使用的相机指向将打开的相机 // 3. 创建相机预览会话 mCameraOpenCloseLock.release(); mCameraDevice = cameraDevice; createCameraPreviewSession(); } @Override public void onDisconnected(@NonNull CameraDevice cameraDevice) { // 当相机失去连接时执行以下操作: // 1. 释放访问许可 // 2. 关闭相机 // 3. 将正在使用的相机指向null mCameraOpenCloseLock.release(); cameraDevice.close(); mCameraDevice = null; } @Override public void onError(@NonNull CameraDevice cameraDevice, int error) { // 当相机发生错误时执行以下操作: // 1. 释放访问许可 // 2. 关闭相机 // 3, 将正在使用的相机指向null // 4. 获取当前的活动, 并结束它 mCameraOpenCloseLock.release(); cameraDevice.close(); mCameraDevice = null; Activity activity = getActivity(); if (null != activity) { activity.finish(); } } }; /** * 处理拍照等工作的子线程 */ private HandlerThread mBackgroundThread; /** * 上面定义的子线程的处理器 */ private Handler mBackgroundHandler; /** * 静止页面捕获(拍照)处理器 */ private ImageReader mImageReader; /** * 输出照片的文件 */ private File mFile; /** * ImageReader的回调函数, 其中的onImageAvailable会在照片准备好可以被保存时调用 */ private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile)); } }; /** * 预览请求构建器, 用来构建"预览请求"(下面定义的)通过pipeline发送到Camera device */ private CaptureRequest.Builder mPreviewRequestBuilder; /** * 预览请求, 由上面的构建器构建出来 */ private CaptureRequest mPreviewRequest; /** * 当前的相机状态, 这里初始化为预览, 因为刚载入这个fragment时应显示预览 */ private int mState = STATE_PREVIEW; /** * 信号量控制器, 防止相机没有关闭时退出本应用(若没有关闭就退出, 会造成其他应用无法调用相机) * 当某处获得这个许可时, 其他需要许可才能执行的代码需要等待许可被释放才能获取 */ private Semaphore mCameraOpenCloseLock = new Semaphore(1); /** * 捕获会话回调函数 * */ private CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) { process(partialResult); } @Override 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值