相机开发:CameraKit调用摄像头与拍照预览(36)

在 HarmonyOS 中开发相机应用,开发者可以根据业务需求选择两种不同的技术路线:如果仅需获取即时拍摄的照片或视频,推荐使用 CameraPicker(相机选择器) ;如果需要构建高度自定义的相机应用或实现实时预览分析,则需要使用 Camera Kit 核心 API

以下是这两种方式的完整开发流程与实战代码:

一、 方案一:使用 CameraPicker 快速调用系统相机

CameraPicker 由系统提供交互界面,用户主动点击拍摄并确认后,应用即可获取结果。此方案无需申请相机权限,开发极其简便。

核心代码示例:

javascript 复制代码
import { camera, cameraPicker as picker } from '@kit.CameraKit';
import { fileIo, fileUri } from '@kit.CoreFileKit';

// 1. 配置 PickerProfile(指定保存路径或默认存入媒体库)
let pickerProfile: picker.PickerProfile = {
  cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
  // 若不配置 saveUri,照片/视频默认存入媒体库
};

// 2. 调用 picker 接口获取拍摄结果
async function getPickerResult(context: Context): Promise<picker.PickerResult> {
  let result = await picker.pick(
    context, 
    [picker.PickerMediaType.PHOTO, picker.PickerMediaType.VIDEO], 
    pickerProfile
  );
  console.info(`resultCode: ${result.resultCode}, resultUri: ${result.resultUri}`);
  return result;
}

二、 方案二:使用 Camera Kit 实现自定义预览与拍照

构建自定义相机需要严格遵循"权限申请 -> 创建输入输出流 -> 会话管理 -> 启动预览"的流程。

1. 权限配置与动态申请

相机属于敏感权限,必须在 module.json5 中声明,并在运行时动态申请:

java 复制代码
"requestPermissions": [
  { "name": "ohos.permission.CAMERA" }
]

在代码中使用 abilityAccessCtrl.createAtManager().requestPermissionsFromUser() 向用户申请授权。

2. 获取相机设备与创建输入流
javascript 复制代码
import { camera } from '@kit.CameraKit';

// 获取相机管理器并获取支持的相机设备列表
let cameraManager = camera.getCameraManager(context);
let cameraDevices = cameraManager.getSupportedCameras();

// 获取后置摄像头并创建输入流
let cameraDevice = cameraDevices.find(device => 
  device.cameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK
);
let cameraInput = cameraManager.createCameraInput(cameraDevice);
await cameraInput.open(); // 打开相机
3. 创建预览 Surface 与输出流

预览画面必须通过 XComponent 组件渲染,并获取其 surfaceId 绑定到预览输出流:

javascript 复制代码
// UI 层声明 XComponent
XComponent({
  type: XComponentType.SURFACE,
  controller: this.xComponentController
})
.onLoad(async () => {
  // 获取 surfaceId
  let surfaceId = this.xComponentController.getXComponentSurfaceId();
  
  // 获取设备支持的预览分辨率(注意宽高比需与 XComponent 保持一致)
  let cameraOutputCapability = cameraManager.getSupportedOutputCapability(cameraDevice);
  let previewProfiles = cameraOutputCapability.previewProfiles;
  
  // 创建预览输出流
  let previewOutput = cameraManager.createPreviewOutput(previewProfiles[0], surfaceId);
})
4. 会话管理与启动预览

相机的输入流和输出流必须通过 Session 进行绑定和调度:

javascript 复制代码
// 创建拍照模式的会话
let photoSession = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;

// 配置会话
photoSession.beginConfig();
photoSession.addInput(cameraInput);
photoSession.addOutput(previewOutput);
await photoSession.commitConfig();

// 启动会话,此时 XComponent 上即可看到实时预览画面
await photoSession.start();

三、 进阶:拍照输出与模式切换

当需要在预览的基础上实现拍照功能时,需在会话中额外添加 PhotoOutput

javascript 复制代码
// 创建拍照输出流
let photoOutput = cameraManager.createPhotoOutput();

// 在会话中添加拍照输出流(需先判断是否支持添加)
if (photoSession.canAddOutput(photoOutput)) {
  photoSession.addOutput(photoOutput);
}
await photoSession.commitConfig();

// 调用拍照
photoOutput.capture();

如果需要从"拍照模式"切换到"录像模式",可以通过 stop() 停止当前会话,移除 photoOutput,添加 videoOutput 后重新 commitConfig()start()

1、 高级参数控制(闪光灯、对焦与变焦)

在会话处于 prepared 或运行状态下,开发者可以动态调整相机的各项参数:

  1. 闪光灯控制 :通过 photoSession.hasFlash() 判断设备是否支持闪光灯,若支持,可调用 setFlashMode() 设置自动、开启或关闭模式。
  2. 对焦模式 :通过 isFocusModeSupported() 检查是否支持连续自动对焦(FOCUS_MODE_CONTINUOUS_AUTO),若支持则调用 setFocusMode() 启用。
  3. 平滑变焦 :通过 getZoomRatioRange() 获取设备支持的可变焦距比范围,然后调用 setZoomRatio() 设置具体的缩放比例。注意:传入的缩放值必须在支持范围内,否则系统会直接报错。
2、 折叠屏设备的深度适配

在折叠屏设备上,随着设备的展开或折叠,可用的摄像头可能会发生变化(例如前置镜头被折入内部)。

  1. 开启自动切换 :通过 isAutoDeviceSwitchSupported() 检查设备是否支持自动切换镜头能力。若支持,调用 enableAutoDeviceSwitch(true)。系统会自动完成镜头切换、会话配置和参数接续,避免黑屏。
  2. 监听能力变更 :当系统自动切换镜头时,由于不同镜头的变焦范围可能不一致,系统会通过 on('autoDeviceSwitchStatusChange') 回调通知应用。应用需根据 isDeviceCapabilityChanged 字段重新获取变焦范围并更新 UI。
  3. UI 布局适配 :除了镜头切换,还需监听折叠状态变化(foldStatusChange),并配合 orientation: follow_desktop 动态调整相机的 UI 布局。
3、 严格的资源释放与生命周期管理

相机是极其消耗系统资源的硬件。如果释放不当,极易导致内存泄漏或下次打开相机时黑屏。

  1. 安全释放顺序 :必须严格按照 stop() -> output.release() -> cameraInput.close() -> session.release() 的顺序进行,且每一步都必须包裹在 try/catch 中,确保即使某一步报错也不会中断后续的释放流程。
  2. 后台释放机制 :当应用退至后台时,应立即释放相机资源;当应用回到前台时,再重新创建和启动会话。可通过监听 applicationContext.on('applicationStateChange') 来实现这一机制。
  3. 模式切换恢复 :每次切换拍照或录像模式(即创建新 Session)后,所有的参数设置都会回到默认值。必须在模式切换后主动调用类似 syncButtonSettings() 的逻辑,将用户的自定义设置重新应用到新会话中。
4、 高级拍照特性与底层细节处理
  1. HDR 与颜色空间 :若需拍摄高动态范围照片,需在 commitConfig() 之前调用 session.setColorSpace() 设置合适的颜色空间(如 DISPLAY_P3BT2020_HLG_LIMIT)。
  2. 动图(Moving Photo)支持 :实现类似 Live Photo 的动图功能需满足三个条件:调用 isMovingPhotoSupported() 确认支持、调用 enableMovingPhoto(true) 开启、并在 photoAssetAvailable 回调中处理生成的资源。
  3. 图像 Stride 对齐处理 :在进行底层图像数据处理(如 AI 识别)时,需注意 image.Component.rowStride 可能大于图像的实际 width。如果不进行裁剪对齐处理,解析出的图像会出现竖条纹。