一、简单介绍
Camera2
是 Android 5.0 后,Google 官方推出新的相机 API 。支持各种新的特性,什么光学防抖啊,相位对焦啊,都提供了支持。
更重要的是,允许程序调整相机的对焦模式,曝光模式 和 快门,还支持 RAW 照片输出。Camera2
使用的请求方式是类似管道的方式,其中主要用到了两个类,CameraRequest
,CameraCaptureSession
。通过建立管道,获取 session , 然后就可以使用
session 来发请求了。
二、主要的类
CameraManager
- 摄像头管理器。这是一个全新的系统管理器,专门用来检测系统摄像头,打开摄像头,可通 过调用
Context
的getSystemService(Context.CAMERA_SERVICE)
来获取。 getCameraCharacteristics(String cameraId)
获取指定摄像头的相关特性。openCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler)
打开摄像头
CameraCharacteristics
- 摄像头特性。通过
CameraManger
获取,存储着摄像头所支持的各种特性。
CameraDevice
- 摄像头设备。该类的功能类似于早期的
Camera
类。
CameraCaptureSession
-
这个类就有点像网络请求中的 session ,一个摄像头对象(
CameraDevice
)最多只能同时存在一个CameraCaptureSession
对象,一旦建立起新的连接,旧的连接就会断开,旧的 session对象就会失效。 -
该对象可以通过
CameraDevice
调用
createCaptureSession(List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler)
来获取。接收三个参数
-
outputs : 一个 Surface 类型 的 List 对象,用来作为输出的集合。
有一点要注意的,CameraRequest.Builder
的addTarget(Surface target)
方法也是传入一个 Surface 对象作为输出对象。那么这两者有什么区别呢?
在官方的文档中有提到一点,target 必须为 outputs 的子集,也就是说 outputs 包含 target 对象。就我的理解而言,outputs 是所有输出的集合,而 target 是某次请求的目标。 -
回调函数,有两个方法。可获取到
session
。 -
用来指定在某个 Handler 执行回调函数。
-
-
capture(CaptureRequest request, CaptureCallback listener, Handler handler)
用来请求捕获一张图片。其中第二个参数 CatpureCallback 是一个静态抽象类,有回调周期的各种回调,只需要选择需要的进行重新就行。 -
setRepeatingRequest(CaptureRequest request, CaptureCallback listener, Handler handler)
用来请求连续的图像,直到调用stopRepeating()
才会停止。一般用来进行预览画面。
CameraRequest
- 向相机发送的请求。一般通过
CameraRequest.Builder
来创建。
CameraRequest.Builder
addTarget(Surface outputTarget)
请求的目标,也就是获取到的图像,传给谁。set(Key<T> key, T value)
设置参数,以 key - value 的形式设置参数。build()
创建 CameraRequest 。
使用流程
权限
使用到了下面几个权限
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
分析
在 Camera2 的 API 中,已经不像 Camera 一样直接提供数据了,而是把数据给到了 Surface 。那么,这就尴尬了。要怎么从 Surface 中获取图片数据呢。
其实在预览的时候,我们根本就不需要管数据,因为 Camera2 会直接把数据塞给 Surface 。
我们知道,Surface 其实是一块原始的图像数据,所有我们在屏幕上看到的东西,都存储在 Surface 中,一个 Surface 一般会有两个缓冲区。WindowManager 会为每一个 window 创建 Surface 。而通过 lockCanvas() 获取到 Canvas , 然后 View 就可以在 Canvas 上面画东西了。
但是,如果要拍照呢?那就用到了 ImageReader
这个类了。
ImageReader
-
这个类从 Android 4.4 就引进了,但是很少用,直到 5.0 的 Camera2 出现,ImageReader 的使用频率才提高了。
-
ImageReader 是什么东西呢?看看官方的解释
The ImageReader class allows direct application access to image data rendered into a Surface
其实就是一个可以直接操作 Surface 的类。
-
那就很舒服了,我们可以通过 ImageReader 来获取图像数据了。
流程
- 获取 CameraManager。
- 获取 CameraCharacteristics 选取要使用的摄像头。 根据获取到的数据,初始化和配置 TextureView (当然,也可以使用 SurfaceView 等其他类似的 View) 和 ImageReader。
- 创建和配置
CameraRequest
,CameraCaptureSession
- 发送请求
- 回调中进行处理
代码
这里先贴两个官方的 Demo ,写得很好,对各种情况都进行了处理。
https://github.com/googlesamples/android-Camera2Basic
https://github.com/googlesamples/android-Camera2Video
好了,接下来对 官方 Demo 进行一波解析
首先,官方整一个过程全都是写在 Fragment 里面。不知道为什么要写在 Fragment ,可能有什么很深层的东西要表达吧,又或者官方觉得,Fragment 的生命周期相对 Activity 来说更复杂一点吧,又或者,官方觉得在 Fragment 上使用的会比较多吧。
我居然YY了这么多。。。
好了,正式看代码
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_camera2_basic, container, false);
}
@Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
view.findViewById(R.id.picture).setOnClickListener(this);
view.findViewById(R.id.info).setOnClickListener(this);
mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mFile = new File(getActivity().getExternalFilesDir(null), "pic.jpg");
}
@Override
public void onResume() {
super.onResume();
startBackgroundThread();
// When the screen is turned off and turned back on, the SurfaceTexture is already
// available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
// a camera and start preview from here (otherwise, we wait until the surface is ready in
// the SurfaceTextureListener).
if (mTextureView.isAvailable()) {
openCamera(mTextureView.getWidth(), mTextureView.getHeight());
} else {
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}
}
在onActivityCreated
中创建了一个文件,是到时候用来存拍照的图片的。
在onResume
中 启动了一个后台线程,然后做了一个判断,如果 mTextureView 可见就打开摄像头开始预览,如果不可见,就设置一个可见时的监听。还写了一段注释,大概是说,当屏幕关闭,或者被放到后台,SurfaceTexture 已经是可见的了,但是,onSurfaceTextureAvailable 不会再回调一次。在这个例子中,我们可以打开摄像头,直接进行预览,否则,我们就要等 SurfaceTextureListener 回调的时候再打开摄像头进行预览。
/**
* Starts a background thread and its {@link Handler}.
* 开启后台线程
*/
private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
先创建了一个 HandlerThread ,这个东西和 Thread 有什么区别呢?这个其实是一个自带 Looper 的 Thread 。然后通过 HandlerThread 创建了一个 Handler 。再说一点,HandlerThread 要 getLooper 前要先跑起来,也就是要先调 start 。
后面就不扯了,直接上代码吧。
由于官方的注释都是英文,看起来也不好理解,于是在简书上找到了一篇文章。
地址如下: