一、前言
现在很多app
都会有拍照功能,一般调用系统进行拍照裁剪就能满足平时的需求,但有些场景或者特殊情况下如:持续不间断拍多张照片或者是进行人脸识别的时候,这时候之间调用系统原生相机拍照时不能满足自己的开发需求,就需要使用原生Camera
来进行自定义开发,本文会采用android.hardware.Camera
API来进行开发。在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
对象,SurfaceView
将Surface
显示在屏幕上,SurfaceView
通过SurfaceHolder
得知Surface
的状态(创建、变化、销毁),可以通过getHolder()
方法获得当前SurfaceView
的SurfaceHolder
对象,然后就可以对SurfaceHolder
对象添加回调来监听Surface
的状态。
Surface
是从Object
派生而来,实现了Parcelable
接口,看到Parcelable
很容易让人想到数据,而SurfaceView
就是用来展示Surface
数据的,两者的关系可以用下面一张图来描述:
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对象。
到这里,会发现
Surface
、SurfaceView
和SurfaceHolder
就是典型的MVC模型。- Surface:原始数据缓冲区,MVC中的M
- SurfaceView:用来绘制Surface的数据,MVC中的V
- SurfaceHolder:控制Surface尺寸格式,并且监听Surface的更改,MVC中的C
上面三者的关系可以用下面一张图来表示:
三、Camera
查看源码时,发现android.hardware.camera
google不推荐使用了:
下面讲讲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
是一个接口,用于在相机自动对焦完成后时通知回调,第一个参数是