1.概述

Android自带camera API有两种,Camera1和Camera2。其中,

Camera1始于Android最初版本,仅为java层实现。

Camera2则有两种实现。

Java层Camera2始于andorid 5.0。

Native Camera2则开始于android7.0(API level24)。

本文将对Native Camera2的使用(仅预览功能)进行总结。总结将针对于如何使用和逻辑流程,不纠结于代码,示例可参照google ndk samples(可从github查找)。

2.使用流程

通过Camera2实现预览,需要三步。

第一步,开启设备。

第二步,发送请求。

第三步,接收视频数据,并转换为图像格式显示。

本文的讲解基于UML Component图,描述Camera2核心对象及API的调用生成关系。受版面所限,拆为三图。如缩略图不清晰,可点击放大。

Component图中,模块为函数,节点为对象。横向(左或右)箭头为资源释放操作调用。调用过程与顺序/时机无关。

2.1开启设备

该阶段目的,在于获取设备指针,以及获取设备相应的信息,例如分别率,旋转角度等。

通过系统所提供的ACameraManager_create方法,可获得ACameraManager指针。

进而通过ACameraManager_getCameraIdList方法,可从ACameraManager中获取所有Camera的ID。

获取ID后,辅助以属性TAG,可以通过ACameraMetadata_getConstEntry方法获取对应Camera的属性。

例如,传入ACAMERA_SENSOR_ORIENTATION的TAG,可以获取Camera的旋转角度;ACAMERA_LENS_FACING,可以判断是前置(ACAMERA_LENS_FACING_FRONT)或后置(ACAMERA_LENS_FACING_BACK)摄像头;而ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS,可以获得摄像头的分辨率。

其它TAG,可参照NdkCameraMetadataTags.h。

此处需要注意的是,

(1)ACameraMetadata_getConstEntry方法返回的是一个ACameraMetadata_const_entry结构体,需根据type的值读取data。

typedef struct ACameraMetadata_const_entry {    /**     * The tag identifying the entry.     * See {@link NdkCameraMetadataTags.h} for more details. 

*/ uint32_t tag; /** * The data type of this metadata entry. */ uint8_t type; /** * Count of elements (NOT count of bytes) in this metadata entry. */ uint32_t count; /** * Pointer to the data held in this metadata entry. */ union { const uint8_t *u8; const int32_t *i32; const float *f; const int64_t *i64; const double *d; const ACameraMetadata_rational* r; } data;} ACameraMetadata_const_entry;

(2)不是所有API LEVEL>=24的android os都支持Camera2,所以有时通过ACameraManager_getCameraIdList方法获取都ID数量可能为0。

在获取到Camera ID之后,通过ACameraManager_openCamera方法,可以开启Camera,获得一个ACameraDevice对象指针。

该指针将在发送请求阶段使用。在调用ACameraManager_openCamera方法时,ACameraDevice_StateCallbacks可以为null。

2.2发送请求

在获取设备(指针)后,需要通过不断的发送请求(ACaptureRequest),以得到图像信息。没有请求,便没有数据。没有数据,便无法生成之后的预览图像。因此,该步骤是最关键的一步,也是最为复杂的一步。

对于整个发送请求(ACaptureRequest)过程,可以理解为,

(1)生成一个向ACameraDevice发送的,类型为ACameraDevice_request_template的ACaptureRequest。

(2)该ACaptureRequest被不断重复发送。

(3)ACaptureRequest所返回的响应数据,由ACaptureSessionOutputContainer中的ACaptureSessionOutput接收。

(4)ACaptureRequest通过ACameraOutputTarget,将ACaptureSessionOutput与AImageReader绑定。

在预览阶段,AImageReader将用于数据的读取。

不断的发送请求,是如何完成的?

只需要调用ACameraCaptureSession_setRepeatingRequest方法即可。

camera_status_t ACameraCaptureSession_setRepeatingRequest(        ACameraCaptureSession* session,        /*optional*/ACameraCaptureSession_captureCallbacks* callbacks,        int numRequests, ACaptureRequest** requests,        /*optional*/int* captureSequenceId);

而当需要停止发送请求时,调用ACameraCaptureSession_stopRepeating方法即可。

camera_status_t ACameraCaptureSession_stopRepeating(ACameraCaptureSession* session);

题外话:预览需要不断的发送请求,获取实时画面。而拍照就不同了,仅需要发送单个(一次)请求即可。

ACameraCaptureSession_setRepeatingRequest方法中核心的传参有三个,ACameraCaptureSession,ACaptureRequest和numRequests。接下来,开始详细讲述发送请求的过程。

2.2.1创建请求

requests,是一个ACaptureRequest列表,numRequests当然指的是ACaptureRequest的数量了。可见,可以同时发送多个请求。但对于单一的预览,一个请求足已。

那么,ACaptureRequest是向谁发出的请求?

camera_status_t ACameraDevice_createCaptureRequest(        const ACameraDevice* device, ACameraDevice_request_template templateId,        /*out*/ACaptureRequest** request);

ACaptureRequest由ACameraDevice生成,因此,是向ACameraDevice发送的请求。ACameraDevice在开启设备阶段已被获取。

请求的类型,由枚举ACameraDevice_request_template决定。参见NdkCameraDevice.h。对于预览,需选择TEMPLATE_PREVIEW。

因此,创建请求的过程,可以理解为:

生成一个向ACameraDevice发送的,类型为ACameraDevice_request_template的请求。

2.2.2建立会话

ACameraCaptureSession是一个会话。它也是由ACameraDevice生成,同时还绑定来一个ACaptureSessionOutputContainer。

camera_status_t ACameraDevice_createCaptureSession(        ACameraDevice* device,        const ACaptureSessionOutputContainer*       outputs,        const ACameraCaptureSession_stateCallbacks* callbacks,        /*out*/ACameraCaptureSession** session);

ACaptureSessionOutputContainer,通过ACaptureSessionOutputContainer_create方法创建。

camera_status_t ACaptureSessionOutputContainer_create(        /*out*/ACaptureSessionOutputContainer** container);

ACaptureSessionOutputContainer是一个可以容纳多个ACaptureSessionOutput的集合。

ACaptureSessionOutput是一个输出流,用于接收预览所用的图像数据。该对象由AImageReader间接创建。关于AImageReader,见图片读取章节。间接创建步骤如下

(1)AImageReader通过AImageReader_getWindow方法,获得一个ANativeWindow对象(注意,该ANativeWindow对象与此后预览图像章节所述的ANativeWindow对象,并非同一对象)。

(2)ANativeWindow对象通过ACaptureSessionOutput_create方法,获得一个ACaptureSessionOutput对象。

因此,一个ACameraCaptureSession包含了以下信息:

(1)与哪个ACameraDevice进行会话。

(2)用哪些ACaptureSessionOutput来接收数据。

而ACameraCaptureSession又通过ACameraCaptureSession_setRepeatingRequest方法,与ACaptureRequest建立关联。

因此,ACaptureRequest与ACaptureSessionOutput,ACameraDevice都是有关联的。

那么ACaptureRequest如何确定数据格式,大小?AImageReader即为关键。

ACaptureRequest需要与AImageReader建立关联。

2.2.3接收图片数据

在创建AImageReader时,需要提供四个参数:图像宽度,高度,图像格式,最大图片存储量。

media_status_t AImageReader_new(        int32_t width, int32_t height, int32_t format, int32_t maxImages,        /*out*/AImageReader** reader);

其中,图像宽度和高度的单位为像素。

图像格式,可参见枚举类型AIMAGE_FORMATS(NdkImage.h)。由于预览时,前端为视频格式,此处应使用AIMAGE_FORMAT_YUV_420_888。

最大图片存储量maxImages,需要特别注意。若数量少(例如1,2),则会在预览时,产生卡顿。而数量多,则会引起延时。例如 数量40,宽高为640*480时,将引起3至4秒的延时。

AImageReader如何与ACaptureRequest建立关联呢?也是通过间接的途径。

(1)AImageReader通过AImageReader_getWindow方法,获得一个ANativeWindow对象(注意,该ANativeWindow对象与此后预览图像章节所述的ANativeWindow对象,并非同一对象)。

(2)ANativeWindow对象通过ACameraOutputTarget_create方法,创建一个ACameraOutputTarget对象。

(3)通过ACaptureRequest_addTarget方法,将ACameraOutputTarget与AImageReader建立关联。

至此,ACaptureRequest可以通过AImageReader确定数据格式,高宽,及最大数量,并将图像数据写入AImageReader所对应的ACaptureSessionOutput中。

接下来,只剩下如何显示的问题了。

2.3预览图像

2.3.1关键因素

AImageReader,ANativeWindow和ANativeWindow_Buffer是预览阶段的关键因素。

其中AImageReader起到一个承上启下的作用。

在发送请求阶段,绑定请求(ACaptureRequest)和输出(ACaptureSessionOutput),以获取实时视频数据。

在预览阶段,作为数据源,用于数据转换(YUV数据转ARGB等图像数据类型)。

ANativeWindow做为画布(由外部传入,并非由AImageReader,与发送请求阶段的ANativeWindow对象不同),用于显示预览图像。

ANativeWindow_Buffer则可以看作是画布(ANativeWindow)的显存。当数据以某一图片格式写入ANativeWindow_Buffer时,图像便呈现在画布(ANativeWindow)上。

2.3.2获取图片

在ACaptureRequest被发出后,将会有AImage不断的存入AImageReader对应的缓冲区。缓冲区内存储的图片数量上限,在创建AImageReader时已被设定。

题外话:

对于性能较低的设备,当缓冲区内的图片上限过大,则会出现预览延时。因为有太多的旧图片在排队等待处理。

而缓冲区内图片上限过小,不论设备的性能好坏,则都会出现预览卡顿,因为没有连续的图片可以被连续的画到画布上。

AImageReader通过AImageReader_acquireNextImage方法,获得最早进入缓冲区的AImage。

在获取到AImage后,可以通过AImage_getPlaneRowStride和AImage_getPlaneData方法,获取YUV格式的视频数据。

关于YUV格式可参照之前的博文:YUV简介,这里不在详述。

2.3.3初始化ANativeWindow_Buffer

在获取ANativeWindow_Buffer前,需要通过ANativeWindow_setBuffersGeometry方法,对其属性进行初始化。

int32_t ANativeWindow_setBuffersGeometry(ANativeWindow* window,        int32_t width, int32_t height, int32_t format);

其中,width和height为ANativeWindow_Buffer的宽高。

ANativeWindow_Buffer的宽高不同于画布View的宽高。例如画布View宽高为1080*2018,ANativeWindow_Buffer宽高为480*640,ANativeWindow_Buffer会自动被映射到画布View上,由硬件或底层完成,无需干预。

ANativeWindow_Buffer的宽高也不同于AImage的宽高。AImage带有ACameraDevice的偏转角度,需要调整为自然0度(会造成宽高的数值交换)后,再做格式转换。

format为YUV转换后的图片格式(例如WINDOW_FORMAT_RGBA_8888等,可参见Native_Window.h)。

2.3.4获取ANativeWindow_Buffer

获取过程相对程序化。需要依次调用ANativeWindow_acquire和ANativeWindow_lock方法。ANativeWindow_acquire及其对应的ANativeWindow_release方法,可以理解为安全策略,为技术层次,无业务逻辑解释。可参见其官方注解。

/** * Acquire a reference on the given {@link ANativeWindow} object. This prevents the object * from being deleted until the reference is removed. */void ANativeWindow_acquire(ANativeWindow* window);
/** * Remove a reference that was previously acquired with {@link ANativeWindow_acquire()}. */void ANativeWindow_release(ANativeWindow* window);

ANativeWindow_lock方法将返回一个ANativeWindow_Buffer。

该ANativeWindow_Buffer,即为ANativeWindow需要显示的下一个画面所对应的显存。

2.3.5写入ANativeWindow_Buffer

通过实现自定义方法TODO_YUV_2_ImageFormat,将数据进行转换,并映射到上一步所获得的ANativeWindow_Buffer中。

TODO_YUV_2_ImageFormat方法,至少应包含三个功能,

(1)角度的修正。将图像由翻转角度,矫正为自然0度。

(2)格式转换。YUV转ImageFormat。其中ImageFormat为ANativeWindow_Buffer初始化时,所设置的格式。例如ARGB_8888。

(3)坐标映射。将宽高为A*B的AImage,映射到宽高为C*D的ANativeWindow_Buffer中。

2.3.6释放ANativeWindow_Buffer

在每次写入完成后,需要释放ANativeWindow_Buffer。步骤十分简单,与获取时相反,依次调用

(1)ANativeWindow_unlockAndPost

(2)ANativeWindow_release

在释放完ANativeWindow_Buffer后,一定要通过AImage_delete方法,将AImage移除缓冲区。

至此,整个预览完成。

2.4关闭Camera

关闭Camera,有一点需要强调。

ACameraDevice_close较为耗时,在MI 8和Samsung SM G9500上测试,需要约200ms左右。而Activity onPause/onStop/onDestory的单个超时上限为500ms。若有其它耗时操作与ACameraDevice_close一起进行,将有可能引起Timeout。

其它不再详细描述,只需要调用相关的free,delete,close方法即可。

3.结束语

相对于Camera1而言,NDK Camera2的逻辑较为复杂。但随着API Level的提升,Camera1终将被替换。Google最新提出的CameraX其实也是对Camera2的封装。因此,Camera2在未来还会存在一段很长的时间。

更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. Python list sort方法的具体使用
  3. python list.sort()根据多个关键字排序的方法实现
  4. android上一些方法的区别和用法的注意事项
  5. android实现字体闪烁动画的方法
  6. Android中dispatchDraw分析
  7. Android四大基本组件介绍与生命周期
  8. Android(安卓)MediaPlayer 常用方法介绍
  9. 在Fragment中设置控件点击方法,执行失败。

随机推荐

  1. php变量与常量
  2. 数据类型及类型转换
  3. zy0803
  4. php变量的类型与转换及变量与常量的区别
  5. 第一节课markdown
  6. 【前端 · 面试 】HTTP 总结(四)—— HTTP
  7. php0803 认识变量的作业
  8. 8.3日作业
  9. 原生相册功能
  10. 0803 PHP编程作业