前语

前面研讨了一下怎么在Android手机上获取超广角镜头:一些获取您的Android设备超广角才能的思路 – ()。发现HUAWEI官方有推出过一个相机库CameraKit,就想着自己接入一下看看作用,顺便记载一些遇到的坑。

流程

运用Gradle集成比较常规,看文档即可:

CameraKit – 相机才能接入预备

官方供给的集成流程如下图:

用华为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();

装备时还需求传入ActionStateCallbackActionDataCallback目标,用于在该形式下的一些操作事情的回调、数据的回调

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获取

    用华为CameraKit实现预览和拍照

  • 经过原生Camera2获取

    用华为CameraKit实现预览和拍照

  • 即使是超广角镜头,经过原生Camera2获取

    用华为CameraKit实现预览和拍照

CameraKit的实例约束条件

这个其实不太算是问题,仅仅约束罢了。CameraKit实例化前会判别

  • app是否现已获取了摄影权限(ps:个人认为这个这个判别应该交给调用方判别的。。。)

  • 设备是否支撑,支撑的规模如下图:

    用华为CameraKit实现预览和拍照
    笔者有一台华为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仍是把焦点放在鸿蒙的更新上,华为社区关于该库的问题看上去也没有得到很好的处理。所以以上说到的那些问题或许不会得到很好的处理了。这篇文章也能够当是一篇冷常识看看吧。

用华为CameraKit实现预览和拍照