Android之Camera1实现相机开发

一、前言

现在很多app都会有拍照功能,一般调用系统进行拍照裁剪就能满足平时的需求,但有些场景或者特殊情况下如:持续不间断拍多张照片或者是进行人脸识别的时候,这时候之间调用系统原生相机拍照时不能满足自己的开发需求,就需要使用原生Camera来进行自定义开发,本文会采用android.hardware.CameraAPI来进行开发。在Android生态中,Camera是碎片化较为严重的一块,因为现在Android本身有三套API:

  • Camera:Android 5.0以下
  • Camera2:Android 5.0以上
  • CameraX:基于Camera2API实现,极大简化在minsdk21及以上版本的实现过程

下面打算分别采用camera1,camera2,cameraX来实现相机开发。

另外各家厂商(华为,OPPO,VIVO,小米)都对Camera2支持程度各不相同,从而导致需要花很大功夫来做适配工作,很多时候直接采用camera1进行开发。 做过相机的同学都知道,Camera1相机开发一般分为五个步骤:

  • 检测相机资源,如果存在相机资源,就请求访问相机资源,否则就结束
  • 创建预览界面,一般是继承SurfaceView并且实现SurfaceHolder接口的拍摄预览类,并且创建布局文件,将预览界面和用户界面绑定,进行实时显示相机预览图像
  • 创建拍照监听器来响应用户的不同操作,如开始拍照,停止拍照等
  • 拍照成功后保存文件,将拍摄获得的图像文件转成位图文件,并且保存输出需要的格式图片
  • 释放相机资源,当相机不再使用时,进行释放

了解完开发步骤后,因为本文是针对Camera1来进行开发,那下面先了解一些具体的类和方法。

二、Surface、SurfaceView、SurfaceHolder

1.Surface

Surface根据英文直译是表面的意思,在源码中有这样描述的:

/**
 * Handle onto a raw buffer that is being managed by the screen compositor.
 *
 * <p>A Surface is generally created by or from a consumer of image buffers (such as a
 * {@link android.graphics.SurfaceTexture}, {@link android.media.MediaRecorder}, or
 * {@link android.renderscript.Allocation}), and is handed to some kind of producer (such as
 * {@link android.opengl.EGL14#eglCreateWindowSurface(android.opengl.EGLDisplay,android.opengl.EGLConfig,java.lang.Object,int[],int) OpenGL},
 * {@link android.media.MediaPlayer#setSurface MediaPlayer}, or
 * {@link android.hardware.camera2.CameraDevice#createCaptureSession CameraDevice}) to draw
 * into.</p>
 */ 

上面的意思:Surface是用来处理屏幕显示内容合成器所管理的原始缓存区工具,它通常由图像缓冲区的消费者来创建如(SurfaceTexture,MediaRecorder),然后被移交给生产者(如:MediaPlayer)或者显示到其上(如:CameraDevice),从上面可以得知:

  • Surface通常由SurfaceTexture或者MediaRecorder来创建
  • Surface最后显示在MediaPlayer或者CameraDevice上
  • 通过Surface就可以获得管理原始缓存区的数据
  • 原始缓冲区(rawbuffer)是用来保存当前窗口的像素数据

Surface内有一个Canvas成员:

 private final Canvas mCanvas = new CompatibleCanvas(); 

我们知道,画图都是在Canvas对象上来画的,因为Suface持有Canvas,那么我们可以这样认为,Surface是一个句柄,而Canvas是开发者画图的场所,就像黑板,而原生缓冲器(rawbuffer)是用来保存数据的地方,所有得到Surface就能得到其中的Canvas和原生缓冲器等其他内容。

2.SurfaceView

SurfaceView简单理解就是Surface的View。

/**
 * Provides a dedicated drawing surface embedded inside of a view hierarchy.
 * You can control the format of this surface and, if you like, its size; the
 * SurfaceView takes care of placing the surface at the correct location on the
 * screen
 */ 

意思就是SurfaceView提供了嵌入视图层级中的专用surface,你可以控制surface的格式或者大小(通过SurfaceView就可以看到Surface部分或者全部内容),SurfaceView负责把surface显示在屏幕的正确位置。

public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback {
....
final Surface mSurface = new Surface();       // Current surface in use
....
private final SurfaceHolder mSurfaceHolder = new SurfaceHolder(){
    .....
 }
} 

SurfaceView继承自View,并且其中有两个成员变量,一个是Surface对象,一个是SurfaceHolder对象,SurfaceViewSurface显示在屏幕上,SurfaceView通过SurfaceHolder得知Surface的状态(创建、变化、销毁),可以通过getHolder()方法获得当前SurfaceViewSurfaceHolder对象,然后就可以对SurfaceHolder对象添加回调来监听Surface的状态。

Surface是从Object派生而来,实现了Parcelable接口,看到Parcelable很容易让人想到数据,而SurfaceView就是用来展示Surface数据的,两者的关系可以用下面一张图来描述:

SurfaceView和Suface

Surface是通过SurfaceView才能展示其中内容。

到这里也许大家会有一个疑问,SurfaceView和普通的View有什么区别?相机开发就一定要用SurfaceView吗?

首先普通的View和其派生类都是共享同一个Surface,所有的绘制必须在主线程(UI线程)进行,通过Surface获得对应的Canvas,完成绘制View的工作。

SurfaceView是特殊的View,它不与其他普通的view共享Surface,在自己内部持有Surface可以在独立的线程中进行绘制,在自定义相机预览图像这块,更新速度比较快和帧率要求比较高,如果用普通的View去更新,极大可能会阻塞UI线程,SurfaceView是在一个新起的线程去更新画面并不会阻塞UI线程。还有SurfaceView底层实现了双缓冲机制,双缓冲技术主要是为了解决反复局部刷新带来的闪烁问题,对于像游戏,视频这些画面变化特别频繁,如果前面没有显示完,程序又重新绘制,这样会导致屏幕不停得闪烁,而双缓冲及时会把要处理的图片在内存中处理后,把要画的东西先画到一个内存区域里,然后整体一次行画处理,显示在屏幕上。举例说明: 在Android中,如果自定义View大多数都会重写onDraw方法,onDraw方法并不是绘制一点显示一点,而是绘制完成后一次性显示到屏幕上。因为CPU访问内存的速度远远大于访问屏幕的速度,如果需要绘制大量复杂的图形时,每次都一个个从内存读取图形然后绘制到屏幕就会造成多次访问屏幕,这些效率会很低。为了解决这个问题,我们可以创建一个临时的Canvas对象,将图像都绘制到这个临时的Canvas对象中,绘制完成后通过drawBitmap方法绘制到onDraw方法中的Canvas对象中,这样就相对于Bitmap的拷贝过程,比直接绘制效率要高。

所以相机开发中最适合用SurfaceView来绘制。

3.SurfaceHolder

/**
 * Abstract interface to someone holding a display surface.  Allows you to
 * control the surface size and format, edit the pixels in the surface, and
 * monitor changes to the surface.  This interface is typically available
 * through the {@link SurfaceView} class.
 *
 * <p>When using this interface from a thread other than the one running
 * its {@link SurfaceView}, you will want to carefully read the
 * methods
 * {@link #lockCanvas} and {@link Callback#surfaceCreated Callback.surfaceCreated()}.
 */
 public interface SurfaceHolder {
    ....
     public interface Callback {

        public void surfaceCreated(SurfaceHolder holder);

        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height);

        public void surfaceDestroyed(SurfaceHolder holder);
        ...
    }
 } 

这是一个抽象的接口给持有surface对象使用,允许你控制surface的大小和格式,编辑surface中的像素和监听surface的变化,这个接口通常通过SurfaceView这个类来获得。

另外SurfaceHolder中有一个Callback接口,这个接口有三个方法:

  • public void surfaceCreated(SurfaceHolder holder)

    surface第一次创建回调

  • public void surfaceChanged(SurfaceHolder,int format,int width,int height)

    surface变化的时候会回调

  • public void surfaceDestroyed(SurfaceHolder holder)

    surface销毁的时候回调

除了上面Callback接口比较重要外,另外还有以下几个方法也比较重要:

  • public void addCallback(Callback callback)

    为SurfaceHolder添加回调接口

  • public void removeCallback(Callback callback)

    对SurfaceHolder移除回调接口

  • public Canvas lockCanvas()

    获取Canvas对象并且对它上锁

  • public Canvas lockCanvas(Rect dirty)

    获取一个Canvas对象,并且对它上锁,但是所动的内容是dirty所指定的矩形区域

  • public void unlockCanvasAndPost(Canvas canvas)

    当修改Surface中的数据完成后,释放同步锁,并且提交改变,将新的数据进行展示,同时Surface中的数据会丢失,加锁的目的就是为了在绘制的过程中,Surface数据不会被改变。

  • public void setType(int type)

    设置Surface的类型,类型有以下几种:

    SURFACE_TYPE_NORMAL:用RAM缓存原生数据的普通Surface

    SURFACE_TYPE_HARDWARE:适用于DMA(Direct memory access)引擎和硬件加速的Surface

    SURFACE_TYPE_GPU:适用于GPU加速的Surface

    SURFACE_TYPE_PUSH_BUFFERS:表明该Surface不包含原生数据,Surface用到的数据由其他对象提供,在Camera图像预览中就使用该类型的Surface,有Camera负责提供给预览Surface数据,这样图像预览会比较流畅,如果设置这种类型就不能调用lockCanvas来获取Canvas对象。

    到这里,会发现SurfaceSurfaceViewSurfaceHolder就是典型的MVC模型。

    • Surface:原始数据缓冲区,MVC中的M
    • SurfaceView:用来绘制Surface的数据,MVC中的V
    • SurfaceHolder:控制Surface尺寸格式,并且监听Surface的更改,MVC中的C

上面三者的关系可以用下面一张图来表示:

三者关系图

三、Camera

查看源码时,发现android.hardware.cameragoogle不推荐使用了:

camera

下面讲讲Camera最主要的成员和一些接口:

Camera核心类

1.CameraInfo

Camera类里,CameraInfo是静态内部类:

 /**
     * Information about a camera
     * 用来描述相机信息
     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
     *             applications.
     * 推荐在新的应用使用{android.hardware.camera2}API
     */
    @Deprecated
    public static class CameraInfo {
        /**
         * The facing of the camera is opposite to that of the screen.
         * 相机正面和屏幕正面相反,意思是后置摄像头
         */
        public static final int CAMERA_FACING_BACK = 0;

        /**
         * The facing of the camera is the same as that of the screen.
         * 相机正面和屏幕正面一致,意思是前置摄像头
         */
        public static final int CAMERA_FACING_FRONT = 1;

        /**
         * The direction that the camera faces. It should be
         * CAMERA_FACING_BACK or CAMERA_FACING_FRONT.
         * 摄像机面对的方向,它只能是CAMERA_FACING_BACK或者CAMERA_FACING_FRONT
         *
         */
        public int facing;

        /**
         * <p>The orientation of the camera image. The value is the angle that the
         * camera image needs to be rotated clockwise so it shows correctly on
         * the display in its natural orientation. It should be 0, 90, 180, or 270.</p>
         * orientation是相机收集图片的角度,这个值是相机采集的图片需要顺时针旋转才能正确显示自
         * 然方向的图像,它必须是0,90,180,270中
         *
         *
         * <p>For example, suppose a device has a naturally tall screen. The
         * back-facing camera sensor is mounted in landscape. You are looking at
         * the screen. If the top side of the camera sensor is aligned with the
         * right edge of the screen in natural orientation, the value should be
         * 90. If the top side of a front-facing camera sensor is aligned with
         * the right of the screen, the value should be 270.</p>
         * 举个例子:假设现在竖着拿着手机,后面摄像头传感器是横向(水平方向)的,你现在正在看屏幕
         * 如果摄像机传感器的顶部在自然方向上右边,那么这个值是90度(手机是竖屏,传感器是横屏的)* 
         * 如果前置摄像头的传感器顶部在手机屏幕的右边,那么这个值就是270度,也就是说这个值是相机图像顺时针
         * 旋转到设备自然方向一致时的角度。
         *
         */
        public int orientation;

        /**
         * <p>Whether the shutter sound can be disabled.</p>
         * 是否禁用开门声音
         */
        public boolean canDisableShutterSound;
    }; 
1.1.orientation

可能很多人对上面orientation解释有点懵,这里重点讲一下orientation,首先先知道四个方向:屏幕坐标方向自然方向图像传感器方向相机预览方向

1.1.1.屏幕坐标方向

屏幕方向

在Android系统中,以屏幕左上角为坐标系统的原点(0,0)坐标,向右延伸是X轴的正方向,向下延伸是y轴的正方向,如上图所示。

1.1.2.自然方向

每个设备都有一个自然方向,手机和平板自然方向不一样,在Android应用程序中,android:screenOrientation来控制activity启动时的方向,默认值unspecified即为自然方向,当然可以取值为:

  • unspecified,默认值,自然方向
  • landscape,强制横屏显示,正常拿设备的时候,宽比高长,这是平板的自然方向
  • portrait,正常拿着设备的时候,宽比高短,这是手机的自然方向
  • behind:和前一个Activity方向相同
  • sensor:根据物理传感器方向转动,用户90度,180度,270度旋转手机方向
  • sensorLandScape:横屏选择,一般横屏游戏会这样设置
  • sensorPortait:竖屏旋转
  • nosensor:旋转设备的时候,界面不会跟着旋转,初始化界面方向由系统控制
  • user:用户当前设置的方向

默认的话:平板的自然方向是横屏,而手机的自然方向是竖屏方向。

1.1.3.图像传感器方向

手机相机的图像数据都是来自于摄像头硬件的图像传感器,这个传感器在被固定到手机上后有一个默认的取景方向,方向一般是和手机横屏方向一致,如下图:

传感器方向

和竖屏应用方向呈90度。

1.1.4.相机预览方向

将图像传感器捕获的图像,显示在屏幕上的方向。在默认情况下,和图像传感器方向一致,在相机API中可以通过setDisplayOrientation(int degrees)设置预览方向(顺时针设置,不是逆时针)。默认情况下,这个值是0,在注释文档中:

 /**
     * Set the clockwise rotation of preview display in degrees. This affects
     * the preview frames and the picture displayed after snapshot. This method
     * is useful for portrait mode applications. Note that preview display of
     * front-facing cameras is flipped horizontally before the rotation, that
     * is, the image is reflected along the central vertical axis of the camera
     * sensor. So the users can see themselves as looking into a mirror.
     *
     * <p>This does not affect the order of byte array passed in {@link
     * PreviewCallback#onPreviewFrame}, JPEG pictures, or recorded videos. This
     * method is not allowed to be called during preview.
     *
     * 设置预览显示的顺时针旋转角度,会影响预览帧和拍拍照后显示的图片,这个方法对竖屏模式的应用 * 很有用,前置摄像头进行角度旋转之前,图像会进行一个水平的镜像翻转,用户在看预览图像的时候* 就像镜子一样了,这个不影响PreviewCallback的回调,生成JPEG图片和录像文件的方向。
     *
     */ 
1.1.4.1.后置

注意,对于手机来说:

  • 横屏下:因为屏幕方向和相机预览方向一致,所以预览图像和看到的实物方向一致
  • 竖屏下:屏幕方向和预览方向垂直,会造成旋转90度现象,无论怎么旋转手机,UI预览界面和实物始终是90度,为了得到一致的预览界面需要将相机预览方向旋转90度(setDisplayOrientation(90)),这样预览界面和实物方向一致。

下面举个简单例子:

相机预览界面坐标解析

这里重点讲解一下竖屏下:

相机和图像传感器方向

后置相机预览图像

需要结合上下两张图来看:

  • 当图像传感器获得图像后,就会知道这幅图像每个坐标的像素值,但是要显示到屏幕上就要根据屏幕自然方向的坐标来显示(竖屏下屏幕自然方向坐标系和后置相机图像传感器方向呈90度),所以图像会逆时针旋转旋转90度,显示到屏幕坐标系上。
  • 那么收集的图像时逆时针旋转了90度,那么这时候需要顺时针旋转90度才能和收集的自然方向保持一致,也就是和实物图方向一样。
1.1.4.2.前置

Android中,对于前置摄像头,有以下规定:

  • 在预览图像是真实物体的镜像
  • 拍出的照片和真实场景一样

前置相机预览界面坐标解析

同理这里重点讲一下,前置竖屏

前置相机和图像传感器方向

前置相机收集图像方向

前置相机预览界面坐标解析

前置相机预览图像方向

在前置相机中,预览图像相机收集图像是镜像关系,上面图中Android图标中前置收集图像预览图像时相反的,前置相机图像传感器方向和前置相机预览图像方向是左右相反的,上图也有体现。

  • 前置摄像头收集到图像后(没有经过镜像处理),但是要显示到屏幕上,就要按照屏幕自然方向的坐标系来进行显示,需要顺时针旋转270度(API没有提供逆时针90度的方法),才能和手机自然方向一致。
  • 在预览的时候,做了镜像处理,所以只需要顺时针旋转90度,就能和自然方向一致,因为摄像图像没有做水平翻转,所以前置摄像头拍出来的图片,你会发现跟预览的时候是左右翻转的,自己可以根据需求做处理。 上面把角度知识梳理了,后面会通过代码一步一步验证,下面按照最开始的思维导图继续看Camera内的方法:
1.2.facing

facing代表相机方向,可取值有二:

  • CAMREA_FACING_BACK,值为0,表示是后置摄像头
  • CAMERA_FACING_FRONT,值为1,表示是前置摄像头
1.3.canDisableShutterSound

是否禁用快门声音

2.PreviewCallback

2.1.void onPreviewFrame(byte[] data, Camera camera)

PreviewCallback是一个接口,可以给Camera设置Camrea.PreviewCallback,并且实现这个onPreviewFrame(byte[] data, Camera camera)这个方法,就可以去Camera预览图片时的数据,如果设置Camera.setPreviewCallback(callback)onPreviewFrame这个方法会被一直调用,可以在摄像头对焦成功后设置camera.setOneShotPreviewCallback(previewCallback),这样设置onPreviewFrame这个方法就会被调用异常,处理data数据,data是相机预览到的原始数据,可以保存下来当做一张照片。

3.AutoFocusCallback

3.1.onAutoFocus(boolean success,Camera camera)

AutoFocusCallback是一个接口,用于在相机自动对焦完成后时通知回调࿰

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
相机是手机中最常用的硬件之一,因此在Android开发中学习如何使用相机是非常重要的。在Android中,相机服务以及相机驱动程序都已经内置在系统中,我们只需要使用它们提供的API就可以轻松地在应用中实现相机功能。 在使用相机之前,需要先检查设备是否具备相机硬件,可以通过检查系统是否具有相机设备来判断: ```java private boolean checkCameraHardware(Context context) { if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) { // 设备具有相机设备 return true; } else { // 设备没有相机设备 return false; } } ``` 如果设备有相机硬件,那么我们就可以开始使用相机了。首先,我们需要获取相机实例: ```java private Camera getCameraInstance() { Camera camera = null; try { camera = Camera.open(); } catch (Exception e) { // 相机不可用(被占用或者不存在) } return camera; } ``` 获取相机实例之后,我们需要对相机进行一些设置,例如设置预览界面、设置拍照参数等。这些设置都可以通过Camera类提供的API来实现。 设置预览界面: ```java // 设置预览界面 private void setPreviewDisplay(SurfaceHolder holder) { try { mCamera.setPreviewDisplay(holder); mCamera.startPreview(); } catch (IOException e) { e.printStackTrace(); } } ``` 设置拍照参数: ```java // 设置拍照参数 private void setCameraParameters() { Camera.Parameters parameters = mCamera.getParameters(); // 设置拍照格式 parameters.setPictureFormat(PixelFormat.JPEG); // 设置拍照质量 parameters.setJpegQuality(100); // 设置相机参数 mCamera.setParameters(parameters); } ``` 拍照: ```java // 拍照 private void takePicture() { mCamera.takePicture(null, null, mPictureCallback); } // 拍照回调 private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { // 处理拍照结果 } }; ``` 以上就是相机的基本使用方法,在实际开发中,还需要注意相机资源的释放、相机的方向调整等问题。因为相机在不同的设备上可能会有不同的表现,所以在使用相机时一定要充分测试。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值