阅读(2798) (10)

鸿蒙OS 相机设备开发指导

2020-09-17 09:22:42 更新

相机开发流程

相机模块主要工作是给相机应用开发者提供基本的相机API接口,用于使用相机系统的功能,进行相机硬件的访问、操作和新功能开发。相机的开发流程如图所示:

图1 相机开发流程 点击放大

接口说明

相机模块为相机应用开发者提供了3个包的内容,包括方法、枚举、以及常量/变量,方便开发者更容易地实现相机功能。详情请查阅对应开发场景。

包名 功能
ohos.media.camera.CameraKit 相机功能入口类。获取当前支持的相机列表及其静态能力信息,创建相机对象。
ohos.media.camera.device 相机设备操作类。提供相机能力查询、相机配置、相机帧捕获、相机状态回调等功能。
ohos.media.camera.params 相机参数类。提供相机属性、参数和操作结果的定义。

相机权限申请

在使用相机之前,需要申请相机的相关权限,保证应用拥有相机硬件及其他功能权限,应用权限的介绍请参考 权限章节,相机涉及权限如下表。

权限名称 权限属性值 是否必选
相机权限 ohos.permission.CAMERA 必选
录音权限 ohos.permission.MICROPHONE 可选(需要录像时申请)
存储权限 ohos.permission.WRITE_USER_STORAGE 可选(需要保存图像及视频到设备的外部存储时申请)
位置权限 ohos.permission.LOCATION 可选(需要保存图像及视频位置信息时申请)

相机设备创建

CameraKit 类是相机的入口 API 类,用于获取相机设备特性、打开相机,其接口如下表。

接口名 描述
createCamera(String cameraId, CameraStateCallback callback, EventHandler handler) 创建相机对象。
getCameraAbility(String cameraId) 获取指定逻辑相机或物理相机的静态能力。
getCameraIds() 获取当前逻辑相机列表。
getCameraInfo(String cameraId) 获取指定逻辑相机的信息。
getInstance(Context context) 获取CameraKit实例。
registerCameraDeviceCallback(CameraDeviceCallback callback, EventHandler handler) 注册相机使用状态回调。
unregisterCameraDeviceCallback(CameraDeviceCallback callback) 注销相机使用状态回调。

基于 HarmonyOS 实现一个相机应用,无论将来想应用到哪个或者哪些设备上,都必须先创建一个独立的相机设备,然后才能继续相机的其他操作。相机设备创建的建议步骤如下:

  1. 通过 CameraKit.getInstance(Context context) 方法获取唯一的 CameraKit 对象是创建新的相机应用的第一步操作。

   private void openCamera(){
       // 获取CameraKit对象
       CameraKit cameraKit = CameraKit.getInstance(context);
       if (cameraKit == null) {
           // 处理cameraKit获取失败的情况 
       }
   }

如果此步骤操作失败,相机可能被占用或无法使用。如果被占用,必须等到相机释放后才能重新获取CameraKit对象。

  1. 通过getCameraIds()方法,获取当前使用的设备支持的逻辑相机列表。逻辑相机列表中存储了当前设备拥有的所有逻辑相机ID,如果列表不为空,则列表中的每个ID都支持独立创建相机对象;否则,说明正在使用的设备无可用的相机,不能继续后续的操作。

   try {
       // 获取当前设备的逻辑相机列表
       String[] cameraIds = cameraKit.getCameraIds();
       if (cameraIds.length <= 0) {
           HiLog.error("cameraIds size is 0");
       }
   } catch (IllegalStateException e) {
       // 处理异常
   }

还可以继续查询指定相机ID的静态信息:

调用getDeviceLinkType(String physicalId)方法获取物理相机连接方式;

调用getCameraInfo(String cameraId)方法查询相机硬件朝向等信息;

调用getCameraAbility(String cameraId)方法查询相机能力信息(比如支持的分辨率列表等)。

接口名 描述
getDeviceLinkType(String physicalId) 获取物理相机连接方式。
getFacingType() 获取相机朝向信息。
getLogicalId() 获取逻辑相机ID。
getPhysicalIdList() 获取对应的物理相机ID列表。
接口名 描述
getSupportedSizes(int format) 根据格式查询输出图像的分辨率列表。
getSupportedSizes(Class<T> clazz) 根据 Class 类型查询分辨率列表。
getParameterRange(ParameterKey.Key<T> parameter) 获取指定参数能够设置的值范围。
getPropertyValue(PropertyKey.Key<T> property) 获取指定属性对应的值。
getSupportedAeMode() 获取当前相机支持的自动曝光模式。
getSupportedAfMode() 获取当前相机支持的自动对焦模式。
getSupportedFaceDetection() 获取相机支持的人脸检测类型范围。
getSupportedFlashMode() 当前相机支持的闪光灯取值范围。
getSupportedParameters() 当前相机支持的参数设置。
getSupportedProperties() 获取当前相机的属性列表。
getSupportedResults() 获取当前相机支持的参数设置可返回的结果列表。
getSupportedZoom() 获取相机支持的变焦范围。

  1. 通过createCamera(String cameraId, CameraStateCallback callback, EventHandler handler)方法,创建相机对象,此步骤执行成功意味着相机系统的硬件已经完成了上电。

   // 创建相机设备
   cameraKit.createCamera(cameraIds[0], cameraStateCallback, eventHandler);

第一个参数 cameraId 可以是上一步获取的逻辑相机列表中的任何一个相机ID。

第二和第三个参数负责相机创建和相机运行时的数据和状态检测,请务必保证在整个相机运行周期内有效。

   private final class CameraStateCallbackImpl extends CameraStateCallback {
       @Override
       public void onCreated(Camera camera) {
            // 创建相机设备
       }

    
       @Override
       public void onConfigured(Camera camera) {
           // 配置相机设备
       }

    
       @Override
       public void onPartialConfigured(Camera camera) {
           // 当使用了 addDeferredSurfaceSize 配置了相机,会接到此回调
       }

    
       @Override
       public void onReleased(Camera camera) { 
          // 释放相机设备
       }
   }

    
   // 相机创建和相机运行时的回调
   CameraStateCallbackImpl cameraStateCallback = new CameraStateCallbackImpl();
   if(cameraStateCallback ==null) {
       HiLog.error("cameraStateCallback is null");
   }

   import ohos.eventhandler.EventHandler;
   import ohos.eventhandler.EventRunner;

    
   // 执行回调的 EventHandler
   EventHandler eventHandler = new EventHandler(EventRunner.create("CameraCb"));
   if(eventHandler ==null) {
       HiLog.error("eventHandler is null");
   }

至此,相机设备的创建已经完成。相机设备创建成功会在 CameraStateCallback 中触发 onCreated(Camera camera)回调。在进入相机设备配置前,请确保相机设备已经创建成功。否则会触发相机设备创建失败的回调,并返回错误码,需要进行错误处理后,重新执行相机设备的创建。

相机设备配置

创建相机设备成功后,在 CameraStateCallback 中会触发 onCreated(Camera camera)回调,并且带回 Camera 对象,用于执行相机设备的操作。

当一个新的相机设备成功创建后,首先需要对相机进行配置,调用 configure(CameraConfig) 方法实现配置。相机配置主要是设置预览、拍照、录像用到的 Surface(详见 ohos.agp.graphics.Surface),没有配置过 Surface,相应的功能不能使用。

为了进行相机帧捕获结果的数据和状态检测,还需要在相机配置时调用 setFrameStateCallback(FrameStateCallback, EventHandler)方法设置帧回调。

private final class CameraStateCallbackImpl extends CameraStateCallback {
    @Override
    public void onCreated(Camera camera) {
        cameraConfigBuilder = camera.getCameraConfigBuilder();
        if (cameraConfigBuilder == null) {
            HiLog.error("onCreated cameraConfigBuilder is null");
            return;
        }
        // 配置预览的Surface
        cameraConfigBuilder.addSurface(previewSurface);
        // 配置拍照的Surface
        cameraConfigBuilder.addSurface(imageReceiver.getRecevingSurface());
        // 配置帧结果的回调
        cameraConfigBuilder.setFrameStateCallback(frameStateCallbackImpl, handler);
        try {
            // 相机设备配置
            camera.configure(cameraConfigBuilder.build());
        } catch (IllegalArgumentException e) {
            HiLog.error("Argument Exception");
        } catch (IllegalStateException e) {
            HiLog.error("State Exception");
        }
    }
}

相机配置成功后,在 CameraStateCallback 中会触发 onConfigured(Camera camera)回调,然后才可以执行相机帧捕获相关的操作。

接口名 描述
addSurface(Surface surface) 相机配置中增加 Surface。
build() 相机配置的构建类。
removeSurface(Surface surface) 移除先前添加的 Surface。
setFrameStateCallback(FrameStateCallback callback, EventHandler handler) 设置用于相机帧结果返回的 FrameStateCallback 和 Handler。
addDeferredSurfaceSize(Size surfaceSize, Class<T> clazz) 添加延迟 Surface 的尺寸、类型。
addDeferredSurface(Surface surface) 设置延迟的 Surface,此 Surface 的尺寸和类型必须和使用 addDeferredSurfaceSize 配置的一致。

相机帧捕获

Camera 操作类,包括相机预览、录像、拍照等功能接口。

接口名 描述
triggerSingleCapture(FrameConfig frameConfig) 启动相机帧的单帧捕获。
triggerMultiCapture(List<FrameConfig> frameConfigs) 启动相机帧的多帧捕获。
configure(CameraConfig config) 配置相机。
flushCaptures() 停止并清除相机帧的捕获,包括循环帧/单帧/多帧捕获。
getCameraConfigBuilder() 获取相机配置构造器对象。
getCameraId() 获取当前相机的 ID。
getFrameConfigBuilder(int type) 获取指定类型的相机帧配置构造器对象。
release() 释放相机对象及资源。
triggerLoopingCapture(FrameConfig frameConfig) 启动或者更新相机帧的循环捕获。
stopLoopingCapture() 停止当前相机帧的循环捕获。

启动预览(循环帧捕获)

用户一般都是先看见预览画面才执行拍照或者其他功能,所以对于一个普通的相机应用,预览是必不可少的。启动预览的建议步骤如下:

  1. 通过getFrameConfigBuilder(FRAME_CONFIG_PREVIEW)方法获取预览配置模板,常用帧配置项见下表,更多的帧配置项以及详细使用方法请参考 API 接口说明的 FrameConfig.Builder 部分。

接口名 描述 是否必选
addSurface(Surface surface) 配置预览 surface 和帧的绑定。
setAfMode(int afMode, Rect rect) 配置对焦模式。
setAeMode(int aeMode, Rect rect) 配置曝光模式。
setZoom(float value) 配置变焦值。
setFlashMode(int flashMode) 配置闪光灯模式。
setFaceDetection(int type, boolean isEnable) 配置人脸检测或者笑脸检测。
setParameter(Key<T> key, T value) 配置其他属性(如自拍镜像等)。
setMark(Object mark) 配置一个标签,后续可以从 FrameConfig 中通过 Object getMark() 拿到标签,判断两个是否相等,相等就说明是同一个配置。
setCoordinateSurface(Surface surface) 配置坐标系基准 Surface,后续计算 Ae/Af 等区域都会基于此 Surface 为基本的中心坐标系,不设置默认使用添加的第一个 Surface。

  1. 通过 triggerLoopingCapture(FrameConfig)方法实现循环帧捕获(如预览/录像)。

   private final class CameraStateCallbackImpl extends CameraStateCallback {
       @Override
       public void onConfigured(Camera camera) {
           // 获取预览配置模板
           frameConfigBuilder = camera.getFrameConfigBuilder(FRAME_CONFIG_PREVIEW);
           // 配置预览 Surface
           frameConfigBuilder.addSurface(previewSurface);
           previewFrameConfig = frameConfigBuilder.build();
           try {
               // 启动循环帧捕获
               int triggerId = camera.triggerLoopingCapture(previewFrameConfig);
           } catch (IllegalArgumentException e) {
               HiLog.error("Argument Exception");
           } catch (IllegalStateException e) {
               HiLog.error("State Exception");
            }
       }
   }

经过以上的操作,相机应用已经可以正常进行实时预览了。在预览状态下,开发者还可以执行其他操作,比如:

当预览帧配置更改时,可以通过triggerLoopingCapture(FrameConfig)方法实现预览帧配置的更新;

   // 预览帧变焦值变更
   frameConfigBuilder.setZoom(1.2f);
   // 调用triggerLoopingCapture方法实现预览帧配置更新
   triggerLoopingCapture(frameConfigBuilder.build());

通过stopLoopingCapture()方法停止循环帧捕获(停止预览)。

   // 停止预览帧捕获
   camera.stopLoopingCapture(frameConfigBuilder.build())

实现拍照(单帧捕获)

拍照功能属于相机应用的最重要功能之一,而且照片质量对用户至关重要。相机模块基于相机复杂的逻辑,从应用接口层到器件驱动层都已经默认的做好了最适合用户的配置,这些默认配置尽可能地保证用户拍出的每张照片的质量。发起拍照的建议步骤如下:

  1. 通过getFrameConfigBuilder(FRAME_CONFIG_PICTURE)方法获取拍照配置模板,并且设置拍照帧配置,如下表:

接口名 描述 是否必选
FrameConfig.Builder addSurface(Surface) 实现拍照 Surface 和帧的绑定。 必选
FrameConfig.Builder setImageRotation(int) 设置图片旋转角度。 可选
FrameConfig.Builder setLocation(Location) 设置图片地理位置信息。 可选
FrameConfig.Builder setParameter(Key<T>, T) 配置其他属性(如自拍镜像等)。 可选

  1. 拍照前准备图像帧数据的接收实现。

   // 图像帧数据接收处理对象       
   private ImageReceiver imageReceiver;
   // 执行回调的 EventHandler
   private EventHandler eventHandler = new EventHandler(EventRunner.create("CameraCb"));
   // 拍照支持分辨率
   private Size pictureSize;

    
   // 单帧捕获生成图像回调 Listener
   private final ImageReceiver.IImageArrivalListener imageArrivalListener = new ImageReceiver.IImageArrivalListener() {
       @Override
       public void onImageArrival(ImageReceiver imageReceiver) {
           StringBuffer fileName = new StringBuffer("picture_"); 
           fileName.append(UUID.randomUUID()).append(".jpg"); // 定义生成图片文件名
           File myFile = new File(dirFile, fileName.toString()); // 创建图片文件
           imageSaver = new ImageSaver(imageReceiver.readNextImage(), myFile); // 创建一个读写线程任务用于保存图片
           eventHandler.postTask(imageSaver); // 执行读写线程任务生成图片
       }
   };

    
   // 保存图片, 图片数据读写,及图像生成见 run 方法
   class ImageSaver implements Runnable {
       private final Image myImage;
       private final File myFile;

    
       ImageSaver(Image image, File file) {
           myImage = image;
           myFile = file;
       }

    
       @Override
       public void run() {
           Image.Component component = myImage.getComponent(ImageFormat.ComponentType.JPEG);
           byte[] bytes = new byte[component.remaining()];
           component.read(bytes);
           FileOutputStream output = null;
           try {
               output = new FileOutputStream(myFile);
               output.write(bytes); // 写图像数据
           } catch (IOException e) {
               HiLog.error("save picture occur exception!");
           } finally {
               myImage.release();
               if (output != null) {
                   try {
                       output.close(); // 关闭流
                   } catch (IOException e) {
                       HiLog.error("image release occur exception!");
                   }
               }
           }
       }
   }
   private void takePictureInit() {
       List<Size> pictureSizes = cameraAbility.getSupportedSizes(ImageFormat.JPEG); // 获取拍照支持分辨率列表
       pictureSize = getpictureSize(pictureSizes) // 根据拍照要求选择合适的分辨率
       imageReceiver = ImageReceiver.create(Math.max(pictureSize.width, pictureSize.height),
           Math.min(pictureSize.width, pictureSize.height), ImageFormat.JPEG, 5); // 创建 ImageReceiver 对象,注意 creat 函数中宽度要大于高度;5 为最大支持的图像数,请根据实际设置。
       imageReceiver.setImageArrivalListener(imageArrivalListener);
   }

  1. 通过 triggerSingleCapture(FrameConfig)方法实现单帧捕获(如拍照)。

   private void capture() {
       // 获取拍照配置模板
       framePictureConfigBuilder = cameraDevice.getFrameConfigBuilder(FRAME_CONFIG_PICTURE);
       // 配置拍照 Surface
       framePictureConfigBuilder.addSurface(imageReceiver.getRecevingSurface());
       // 配置拍照其他参数
       framePictureConfigBuilder.setImageRotation(90);
       try {
           // 启动单帧捕获(拍照)
           camera.triggerSingleCapture(framePictureConfigBuilder.build());
       } catch (IllegalArgumentException e) {
           HiLog.error("Argument Exception");
       } catch (IllegalStateException e) {
           HiLog.error("State Exception");
       }
   }

为了捕获到质量更高和效果更好的图片,还可以在帧结果中实时监测自动对焦和自动曝光的状态,一般而言,在自动对焦完成,自动曝光收敛后的瞬间是发起单帧捕获的最佳时机。

实现连拍(多帧捕获)

连拍功能方便用户一次拍照获取多张照片,用于捕捉精彩瞬间。同普通拍照的实现流程一致,但连拍需要使用 triggerMultiCapture(List<FrameConfig> frameConfigs)方法。

启动录像(循环帧捕获)

启动录像和启动预览类似,但需要另外配置录像 Surface 才能使用。

  1. 录像前需要进行音视频模块的配置。

   private Source source; // 音视频源
   private AudioProperty.Builder audioPropertyBuilder; // 音频属性
   private VideoProperty.Builder videoPropertyBuilder; // 视频属性
   private StorageProperty.Builder storagePropertyBuilder; // 音视频存储属性
   private Recorder mediaRecorder; // 录像操作对象
   private String recordName; // 音视频文件名

    
   private void initMediaRecorder() {
       HiLog.info("initMediaRecorder begin");
       videoPropertyBuilder.setRecorderBitRate(10000000); // 设置录制比特率
       int rotation = DisplayManager.getInstance().getDefaultDisplay(this).get().getRotation();
       videoPropertyBuilder.setRecorderDegrees(getOrientation(rotation)); // 设置录像方向
       videoPropertyBuilder.setRecorderFps(30); // 设置录制采样率
       videoPropertyBuilder.setRecorderHeight(Math.min(recordSize.height, recordSize.width)); // 设置录像支持的分辨率,需保证 width > height
       videoPropertyBuilder.setRecorderWidth(Math.max(recordSize.height, recordSize.width));
       videoPropertyBuilder.setRecorderVideoEncoder(Recorder.VideoEncoder.H264); // 设置视频编码方式
       videoPropertyBuilder.setRecorderRate(30); // 设置录制帧率
       source.setRecorderAudioSource(Recorder.AudioSource.MIC); // 设置录制音频源
       source.setRecorderVideoSource(Recorder.VideoSource.SURFACE); // 设置视频窗口
       mediaRecorder.setSource(source); // 设置音视频源
       mediaRecorder.setOutputFormat(Recorder.OutputFormat.MPEG_4); // 设置音视频输出格式
       StringBuffer fileName = new StringBuffer("record_"); // 生成随机文件名
       fileName.append(UUID.randomUUID()).append(".mp4");
       recordName = fileName.toString();
       File file = new File(dirFile, fileName.toString()); // 创建录像文件对象
       storagePropertyBuilder.setRecorderFile(file); // 设置存储音视频文件名
       mediaRecorder.setStorageProperty(storagePropertyBuilder.build());
       audioPropertyBuilder.setRecorderAudioEncoder(Recorder.AudioEncoder.AAC); // 设置音频编码格式
       mediaRecorder.setAudioProperty(audioPropertyBuilder.build()); // 设置音频属性
       mediaRecorder.setVideoProperty(videoPropertyBuilder.build()); // 设置视频属性
       mediaRecorder.prepare(); // 准备录制
       HiLog.info("initMediaRecorder end");
   }

  1. 配置录像帧,启动录像。

   private final class CameraStateCallbackImpl extends CameraStateCallback {
       @Override
       public void onConfigured(Camera camera) {
           // 获取预览配置模板
           frameConfigBuilder = camera.getFrameConfigBuilder(FRAME_CONFIG_PREVIEW);
           // 配置预览Surface
           frameConfigBuilder.addSurface(previewSurface);
           // 配置录像的Surface
           mRecorderSurface = mediaRecorder.getVideoSurface();
           cameraConfigBuilder.addSurface(mRecorderSurface);
           previewFrameConfig = frameConfigBuilder.build();
           try {
               // 启动循环帧捕获
               int triggerId = camera.triggerLoopingCapture(previewFrameConfig);
           } catch (IllegalArgumentException e) {
               HiLog.error("Argument Exception");
           } catch (IllegalStateException e) {
               HiLog.error("State Exception");
           }
       }
   }

  1. 通过 camera.stopLoopingCapture()方法停止循环帧捕获(录像)。

相机设备释放

使用完相机后,必须通过 release() 来关闭相机和释放资源,否则可能导致其他相机应用无法启动。一旦相机被释放,它所提供的操作就不能再被调用,否则会导致不可预期的结果,或是会引发状态异常。

相机设备释放的示例代码如下:

private void releaseCamera() {
    if (camera != null) {
        // 关闭相机和释放资源
        camera.release();
        camera = null;
    }
    // 拍照配置模板置空
    framePictureConfigBuilder = null;
    // 预览配置模板置空
    previewFrameConfig = null;
}