HarmonyOS深入解析相机的功能和使用

网友投稿 921 2022-05-30

HarmonyOS 相机模块支持相机业务的开发,开发者可以通过已开放的接口实现相机硬件的访问、操作和新功能开发,最常见的操作如:预览、拍照、连拍和录像等。

相机静态能力:用于描述相机的固有能力的一系列参数,比如朝向、支持的分辨率等信息。

物理相机:物理相机就是独立的实体摄像头设备。物理相机ID是用于标志每个物理摄像头的唯一字串。

逻辑相机:逻辑相机是多个物理相机组合出来的抽象设备,逻辑相机通过同时控制多个物理相机设备来完成相机某些功能,如大光圈、变焦等功能。逻辑摄像机ID是一个唯一的字符串,标识多个物理摄像机的抽象能力。

帧捕获:相机启动后对帧的捕获动作统称为帧捕获。主要包含单帧捕获、多帧捕获、循环帧捕获。

单帧捕获:指的是相机启动后,在帧数据流中捕获一帧数据,常用于普通拍照。

多帧捕获:指的是相机启动后,在帧数据流中连续捕获多帧数据,常用于连拍。

循环帧捕获:指的是相机启动后,在帧数据流中一直捕获帧数据,常用于预览和录像。

在同一时刻只能有一个相机应用在运行中。

相机模块内部有状态控制,开发者必须按照指导文档中的流程进行接口的顺序调用,否则可能会出现调用失败等问题。

为了开发的相机应用拥有更好的兼容性,在创建相机对象或者参数相关设置前请务必进行能力查询。

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

相机的开发流程如图所示:

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

相机 API 如下所示:

HarmonyOS 根据接口所涉数据的敏感程度或所涉能力的安全威胁影响,定义了不同开放范围与授权方式的权限来保护数据。

当前权限的开放范围分为:

all:所有应用可用

signature:平台签名应用可用

privileged:预制特权应用可用

restricted:证书可控应用可用

应用在使用对应服务的能力或数据时,需要申请对应权限:

已在 config.json 文件中声明的非敏感权限,会在应用安装时自动授予,该类权限的授权方式为系统授权(system_grant)。

敏感权限需要应用动态申请,通过运行时发送弹窗的方式请求用户授权,该类权限的授权方式为用户授权(user_grant)。

当应用调用服务时,服务会对应用进行权限检查,如果没有对应权限则无法使用该服务。

敏感权限的申请需要按照动态申请流程向用户申请授权,敏感权限说明如下:

非敏感权限不涉及用户的敏感数据或危险操作,仅需在 config.json 中声明,应用安装后即被授权。

受限开放的权限通常是不允许三方应用申请的。如果有特殊场景需要使用,请提供相关申请材料到应用市场申请相应权限证书。如果应用未申请相应的权限证书,却试图在 config.json 文件中声明此类权限,将会导致应用安装失败。另外,由于此类权限涉及到用户敏感数据或危险操作,当应用申请到权限证书后,还需按照动态申请权限的流程向用户申请授权。受限开放权限说明如下:

在使用相机之前,需要申请相机的相关权限,保证应用拥有相机硬件及其他功能权限。相机权限列表:

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

在实现一个相机应用之前必须先创建一个独立的相机设备,然后才能继续相机的其他操作。相机设备创建的建议步骤如下:

通过 CameraKit.getInstance(Context context) 方法获取唯一的 CameraKit 对象(如果此步骤操作失败,相机可能被占用或无法使用,如果被占用,必须等到相机释放后才能重新获取 CameraKit 对象):

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

1

2

3

4

5

6

7

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

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

1

2

3

4

5

6

7

8

9

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

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

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

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

CameraInfo 的主要接口:

CameraAbility 的主要接口:

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

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

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

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

1

2

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();

1

2

3

4

5

6

7

8

9

HarmonyOS之深入解析相机的功能和使用

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

import ohos.eventhandler.EventHandler; import ohos.eventhandler.EventRunner; // 执行回调的EventHandler EventHandler eventHandler = new EventHandler(EventRunner.create("CameraCb"));

1

2

3

4

5

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

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

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

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

private void initSurface() { surfaceProvider = new SurfaceProvider(this); DirectionalLayout.LayoutConfig params = new DirectionalLayout.LayoutConfig( ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_PARENT); surfaceProvider.setLayoutConfig(params); surfaceProvider.pinToZTop(false); surfaceProvider.getSurfaceOps().get().addCallback(new SurfaceCallBack()); ((ComponentContainer) findComponentById(ResourceTable.Id_surface_container)).addComponent(surfaceProvider); } private FrameStateCallback frameStateCallbackImpl = new FrameStateCallback(){ @Override public void onFrameStarted(Camera camera, FrameConfig frameConfig, long frameNumber, long timestamp) { ... } @Override public void onFrameProgressed(Camera camera, FrameConfig frameConfig, FrameResult frameResult) { ... } @Override public void onFrameFinished(Camera camera, FrameConfig frameConfig, FrameResult frameResult) { ... } @Override public void onFrameError(Camera camera, FrameConfig frameConfig, int errorCode, FrameResult frameResult) { ... } @Override public void onCaptureTriggerStarted(Camera camera, int captureTriggerId, long firstFrameNumber) { ... } @Override public void onCaptureTriggerFinished(Camera camera, int captureTriggerId, long lastFrameNumber) { ... } @Override public void onCaptureTriggerInterrupted(Camera camera, int captureTriggerId) { ... } }; private final class CameraStateCallbackImpl extends CameraStateCallback { @Override public void onCreated(Camera camera) { cameraDevice = camera; previewSurface = surfaceProvider.getSurfaceOps().get().getSurface(); cameraConfigBuilder = camera.getCameraConfigBuilder(); if (cameraConfigBuilder == null) { HiLog.error(LABEL, "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(LABEL, "Argument Exception"); } catch (IllegalStateException e) { HiLog.error(LABEL, "State Exception"); } } }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

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

CameraConfig.Builder 的主要接口:

Camera 操作类,包括相机预览、录像、拍照等功能接口。Camera 的主要接口如下:

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

启动预览的建议步骤如下:

通过 getFrameConfigBuilder(FRAME_CONFIG_PREVIEW) 方法获取预览配置模板,常用帧配置项见下表:

通过 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(LABEL, "Argument Exception"); } catch (IllegalStateException e) { HiLog.error(LABEL, "State Exception"); } } }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

经过以上的操作,相机应用已经可以正常进行实时预览了。在预览状态下,开发者还可以执行其他操作,比如:当预览帧配置更改时,可以通过 triggerLoopingCapture(FrameConfig) 方法实现预览帧配置的更新;

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

1

2

3

4

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

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

1

2

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

发起拍照的建议步骤如下:

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

拍照前准备图像帧数据的接收实现:

// 图像帧数据接收处理对象 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(LABEL, "save picture occur exception!"); } finally { if (output != null) { try { output.close(); // 关闭流 } catch (IOException e) { HiLog.error(LABEL, "image release occur exception!"); } } myImage.release(); } } } private void takePictureInit() { List 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对象,注意create函数中宽度要大于高度;5为最大支持的图像数,请根据实际设置。 imageReceiver.setImageArrivalListener(imageArrivalListener); }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

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

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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

连拍功能方便用户一次拍照获取多张照片,用于捕捉精彩瞬间。

同普通拍照的实现流程一致,但连拍需要使用 triggerMultiCapture(List frameConfigs) 方法。

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

录像前需要进行音视频模块的配置:

private Source source; // 音视频源 private AudioProperty.Builder audioPropertyBuilder; // 音频属性构造器 private VideoProperty.Builder videoPropertyBuilder; // 视频属性构造器 private StorageProperty.Builder storagePropertyBuilder; // 音视频存储属性构造器 private Recorder mediaRecorder; // 录像操作对象 private String recordName; // 音视频文件名 private void initMediaRecorder() { 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, recordName); // 创建录像文件对象 storagePropertyBuilder.setRecorderFile(file); // 设置存储音视频文件名 mediaRecorder.setStorageProperty(storagePropertyBuilder.build()); audioPropertyBuilder.setRecorderAudioEncoder(Recorder.AudioEncoder.AAC); // 设置音频编码格式 mediaRecorder.setAudioProperty(audioPropertyBuilder.build()); // 设置音频属性 mediaRecorder.setVideoProperty(videoPropertyBuilder.build()); // 设置视频属性 mediaRecorder.prepare(); // 准备录制 HiLog.info(LABEL, "initMediaRecorder end"); }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

配置录像帧,启动录像:

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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

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

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

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

1

2

3

4

5

6

7

8

9

10

11

HarmonyOS之演示如何通过相机模块相关接口实现拍照、录像等功能。

API 开发者

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:索引可不是万能的,来看看什么时候会失效(索引在什么时候会失效)
下一篇:excel表格复制公式数值不变的解决教程(表格公式复制后数值不变)
相关文章