前语
前面研讨了一下怎么在Android手机上获取超广角镜头:一些获取您的Android设备超广角才能的思路 – ()。发现HUAWEI官方有推出过一个相机库CameraKit,就想着自己接入一下看看作用,顺便记载一些遇到的坑。
流程
运用Gradle集成比较常规,看文档即可:
CameraKit – 相机才能接入预备
官方供给的集成流程如下图:
CameraKit
供给了一个Mode类作为一次摄影流程的相关笼统,可了解为一个Session。
CameraKit
的生命周期:
- 形式创立:
CameraKit
供给了多种相机的形式,譬如:一般摄影、人像、夜景等,当然还有录像相关的。 详情可参阅文档:Mode.Type - 形式装备:主要是装备预览分辨率、摄影分辨率等,还有关于在该形式下的一些操作事情的回调、数据的回调。
- 根据形式的操作:比较好了解的是运用
Mode
类进行预览、摄影、缩放等。 - 操作回调:每当触发一个操作后,会经过在形式装备下注册的回调中回调相关事情或数据。
- 形式开释:不需求时开释资源。
接入
在运用CameraKit
时,一切的前提是需求实例化出CameraKit
目标,它是一个饿汉式的单例,在实例化前会判别一些约束条件,契合条件后才会创立。
CameraKit cameraKit = CameraKit.getInstance(getApplicationContext());
模型创立
在预览的视图预备好之后,就能够开端创立形式了,譬如在TextureView#onSurfaceTextureAvailable
后。在创立前还需求新建一个HandlerThread,作为整个相机运作的线程。
private final ModeStateCallback mModeStateCallback = new ModeStateCallback() {
@Override
public void onCreated(Mode mode) {
super.onCreated(mode);
mMode = mode;
configMode(); // Mode创立成功,能够开端进行形式装备
}
@Override
public void onCreateFailed(String cameraId, int modeType, int errorCode) {
super.onCreateFailed(cameraId, modeType, errorCode);
}
@Override
public void onConfigured(Mode mode) {
super.onConfigured(mode);
mMode.startPreview(); // Mode装备成功,能够开端预览
}
@Override
public void onConfigureFailed(Mode mode, int errorCode) {
super.onConfigureFailed(mode, errorCode);
}
@Override
public void onReleased(Mode mode) {
super.onReleased(mode);
}
@Override
public void onFatalError(Mode mode, int errorCode) {
super.onFatalError(mode, errorCode);
}
};
cameraKit.createMode(CameraInfo.FacingType.CAMERA_FACING_BACK,
Mode.Type.NORMAL_MODE, mModeStateCallback, mHandler);
-
CameraKit
中有关于手机物理摄像头进行笼统,在应用层只会供给前置/后置两个枚举。这儿运用后置摄像头CameraInfo.FacingType.CAMERA_FACING_BACK
。 -
Mode.Type.NORMAL_MODE
为一般摄影形式,假如有拍摄人像、夜景等其他需求,可对应传入。 -
ModeStateCallback
用于监听Mode目标的事情。 - 最终还需求一个归于HandlerThread的Handler,用于消息分发。
形式装备
从上述代码中ModeStateCallback#onCreated
的回调能够看到,在成功创立形式后就能够开端装备了。
比较重要的是预览分辨率和摄影分辨率,可经过以下代码获取设备支撑的
// 预览分辨率
List<Size> supportedPreviewSizes = mMode.getModeCharacteristics()
.getSupportedPreviewSizes(SurfaceTexture.class);
// 摄影分辨率
List<Size> supportedCaptureSizes = mMode.getModeCharacteristics()
.getSupportedCaptureSizes(ImageFormat.JPEG);
由于用的是TextureView
,所以传入SurfaceTexture.class
。预览分辨率还需求设置回TextureView中确保预览画面正常。ps:分辨率的挑选逻辑比较常规就不多赘述了,这儿选一个最大的3:4比例。
textureView.getSurfaceTexture()
.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
ps:mMode.getModeCharacteristics()
能够获取到在该形式下一些支撑的参数,除上述的分辨率外还有支撑的对焦类型、缩放规模等。可参阅:ModeCharacteristics
mMode.getModeConfigBuilder()
.addPreviewSurface(new Surface(textureView.getSurfaceTexture()))
.addCaptureImage(pictureSize, ImageFormat.JPEG);
mMode.getModeConfigBuilder().setDataCallback(mActionDataCallback, mHandler);
mMode.getModeConfigBuilder().setStateCallback(mActionStateCallback, mHandler);
mMode.configure();
装备时还需求传入ActionStateCallback
、ActionDataCallback
目标,用于在该形式下的一些操作事情的回调、数据的回调
private final ActionStateCallback mActionStateCallback = new ActionStateCallback() {
@Override
public void onPreview(Mode mode, int state, @Nullable PreviewResult result) {
super.onPreview(mode, state, result);
// 预览事情回调
}
@Override
public void onTakePicture(Mode mode, int state, @Nullable TakePictureResult result) {
super.onTakePicture(mode, state, result);
// 摄影事情回调
}
@Override
public void onFocus(Mode mode, int state, @Nullable FocusResult result) {
super.onFocus(mode, state, result);
// 对焦事情回调
}
};
private final ActionDataCallback mActionDataCallback = new ActionDataCallback() {
@Override
public void onImageAvailable(Mode mode, int type, Image image) {
super.onImageAvailable(mode, type, image);
// 摄影数据回调
}
@Override
public void onThumbnailAvailable(Mode mode, int type, android.util.Size size, byte[] data) {
super.onThumbnailAvailable(mode, type, size, data);
}
};
开端预览
ModeStateCallback#onConfigured
回调后即可调用Mode#startPreview
开启预览。
// ModeStateCallback
@Override
public void onConfigured(Mode mode) {
super.onConfigured(mode);
mMode.startPreview(); // Mode装备成功,能够开端预览
}
这时您的界面上应该就能看到预览画面了。
摄影
Mode#takePicture
触发摄影
mMode.takePicture();
ActionStateCallback#onTakePicture
会回调摄影相关的事情,包含错误事情
// ActionStateCallback
@Override
public void onTakePicture(Mode mode, int state, @Nullable TakePictureResult result) {
super.onTakePicture(mode, state, result);
if (state == TakePictureResult.State.CAPTURE_COMPLETED) {
// 摄影完成
} else if (state == TakePictureResult.State.ERROR_CAPTURE_NOT_READY
|| state == TakePictureResult.State.ERROR_FILE_IO
|| state == TakePictureResult.State.ERROR_UNKNOWN
|| state == TakePictureResult.State.ERROR_UNSUPPORTED_OPERATION) {
// 摄影犯错
}
}
可参阅:ActionStateCallback.TakePictureResult.State
摄影成功后在ActionDataCallback#onImageAvailable
回调原图的Image目标,数据格式为jpg
// ActionDataCallback
@Override
public void onImageAvailable(Mode mode, int type, Image image) {
super.onImageAvailable(mode, type, image);
if (type == Type.TAKE_PICTURE) {
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
// 转成Bitmap?
image.close();
}
}
这样就会拿到jpg的byte数组了。
资源开释
在结束时,需求开释相关资源,当然还包含创立的HandlerThread,防止内存泄漏。
if (mMode != null) {
mMode.release();
}
超广角才能
由于笔者最初是想研讨超广角的,所以也来看一下
float[] zooms = mMode.getModeCharacteristics().getSupportedZoom();
mMode.setZoom(zooms[0]);
由于CameraKit
现已帮我们笼统了物理摄像头,关于后置摄像头当然也包含那颗超广角摄像头。运用以上代码能够获取到当时形式下所支撑的缩放规模,一般是一个长度为2的数组。在华为P40上zooms[0] = 0.6f
。设置后即可取得超广角的预览。
一些疑难杂症
支撑的分辨率较少
在华为P40上,经过CameraKit
获取支撑的摄影分辨率极少
-
经过
CameraKit
获取 -
经过
原生Camera2
获取 -
即使是超广角镜头,经过
原生Camera2
获取
CameraKit的实例约束条件
这个其实不太算是问题,仅仅约束罢了。CameraKit
实例化前会判别
-
app是否现已获取了摄影权限(ps:个人认为这个这个判别应该交给调用方判别的。。。)
-
设备是否支撑,支撑的规模如下图:
笔者有一台华为MatePad11,是高通芯片的,实测发现并不支撑,所以该库支撑的规模仍是比较窄的。
摄影输出的时刻很慢
一般运用Camera2摄影平均在500ms能够输出,运用CameraKit最快也要2s。假如运用一些更为专业的功能或许会更长,这个没有细测。 在HUAWEI的社区中也有人发问:CameraKit中摄影速度慢的状况下要5、6秒,太慢了,请问怎么优化-华为开发者论坛。
笔者的猜想是,从CameraKit导入的一些类来看,其依赖的仍是Camera2。推测是在输出到调用方之前,CameraKit会调用一些体系的服务对图像进行处理,就比方超广角的输出是处理过畸变的。还有便是上述说的分辨率支撑极少,所能选用的3:4分辨率现已到4096 * 3072
,导致这些处理比较耗时。
无法获取预览帧
接入时发现该库并没有很好的供给获取预览帧的办法,只能经过在装备Mode时添加多一个Surface
。这儿运用ImageReader
完成,详细可参阅Camera2的做法,大同小异。
previewImageReader = ImageReader.newInstance(
previewSize.getWidth(),
previewSize.getHeight(),
ImageFormat.YUV_420_888,
2);
previewImageReader.setOnImageAvailableListener(this, mHandler);
mMode.getModeConfigBuilder()
.addPreviewSurface(new Surface(textureView.getSurfaceTexture()))
.addPreviewSurface(previewImageReader.getSurface())
.addCaptureImage(pictureSize, ImageFormat.JPEG);
mMode.getModeConfigBuilder().setDataCallback(mActionDataCallback, mHandler);
mMode.getModeConfigBuilder().setStateCallback(mActionStateCallback, mHandler);
mMode.configure();
这儿需求运用YUV_420_888
,提高输出效率。还需求注意的是输出的Image转成byte数组的问题。
还有一点,根据社区的一些反馈,并不是一切设备都支撑这样一起注册两个预览流,可经过以下方式获取最大的支撑数
mMode.getModeCharacteristics().getMaxPreviewSurfaceNumber()
该办法尽管能够安稳获取到预览帧,可是随之而来的是加大了摄影输出的时刻。严峻的可到达5、6s以上。
另一种思路
这个又有别的一个思路去处理:上述代码只注册一个ImageReader用于获取YUV_420_888
的预览帧,再经过GLSurfaceView绘制到视图上,一起将Image转成byte数组作为数据层的回调。详细的完成这儿不细说了,推荐一个OPPO的Demo,里边有YUV_420_888
经过OpenGL绘制的逻辑,能够参阅一下:oppo/CameraUnit。
这儿需求注意的是:
-
ImageReader#OnImageAvailableListener
输出和GLSurfaceView.Renderer#onDrawFrame
的绘制在两个线程,所以需求确保同步。 - 由于Image转成byte数组的过程或许存在耗时,这一块主要来源于大内存的请求和gc,可采用全局变量防止频繁的内存请求。但由于采用了全局变量,但又不能由于这个转化导致绘制的掉帧状况,所以需求一些原子性的变量加以辅助,适当做一些丢帧操作。
以上仅仅笔者的设想,里边也有更好的优化空间。
最终
以上便是笔者关于华为CameraKit
的研讨记载。其实都2023年了,在一台鸿蒙手机上做一些Android开发的确有些不太靠谱。在官方文档中最近一次更新是在2021年,现在HUAWEI仍是把焦点放在鸿蒙的更新上,华为社区关于该库的问题看上去也没有得到很好的处理。所以以上说到的那些问题或许不会得到很好的处理了。这篇文章也能够当是一篇冷常识看看吧。