《相机焦距缩放》一、相机管理使用指南

HarmonyOS @ohos.multimedia.camera 相机管理使用指南

本指南基于 HarmonyOS ArkTS 开发实践,系统讲解如何使用 @ohos.multimedia.camera 模块实现自定义相机功能,涵盖权限配置、相机初始化、预览、拍照、焦距缩放等完整流程。

效果

一、概述

@ohos.multimedia.camera 是 HarmonyOS 提供的相机管理模块(通过 @kit.CameraKit 引入),支持以下核心能力:

  • 获取设备支持的相机列表
  • 创建相机输入流与预览输出流
  • 配置拍照会话(PhotoSession)
  • 实现拍照、闪光灯控制、对焦模式设置
  • 实现焦距缩放(ZoomRatio)控制

二、环境准备

2.1 权限配置

entry/src/main/module.json5 中声明相机权限:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.CAMERA",
        "reason": "$string:reason_camera",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      }
    ]
  }
}

同时在 resources/base/element/string.json 中添加权限说明:

{
  "string": [
    { "name": "reason_camera", "value": "应用需要使用相机进行拍照" }
  ]
}

2.2 动态权限申请

在页面 aboutToAppear() 生命周期中申请相机权限:

import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

const cameraPermission: Array<Permissions> = ['ohos.permission.CAMERA'];

aboutToAppear(): void {
  const context = this.getUIContext().getHostContext();
  abilityAccessCtrl.createAtManager()
    .requestPermissionsFromUser(context, cameraPermission)
    .then(() => {
      // 权限获取成功后,若 XComponent surface 也已就绪,则初始化相机
      if (surfaceReady) {
        this.initCamera();
      }
      permReady = true;
    })
    .catch((err: BusinessError) => {
      console.error('权限申请失败: ' + err.message);
    });
}

注意: 首次运行时权限弹窗与 XComponent 渲染存在竞态,必须确保权限和 surface 都就绪后才初始化相机(详见第七节常见问题)。

三、核心流程

3.1 获取相机管理器

import { camera } from '@kit.CameraKit';

// 获取相机管理器实例
const cameraManager: camera.CameraManager = camera.getCameraManager(context);

3.2 获取支持的相机设备

// 获取所有支持的相机设备
const cameraArray: camera.CameraDevice[] = cameraManager.getSupportedCameras();

// cameraArray[0] 通常为后置相机
// cameraArray[1] 通常为前置相机(如果存在)

每个 CameraDevice 包含以下关键属性:

  • cameraPosition: 相机位置(前置/后置)
  • cameraType: 相机类型
  • cameraDirection: 相机方向

3.3 获取支持的场景模式

// 获取指定相机支持的场景模式
const sceneModes: camera.SceneMode[] =
  cameraManager.getSupportedSceneModes(cameraDevice);

// 判断是否支持普通拍照模式
const isSupportPhotoMode: boolean =
  sceneModes.indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0;

常见场景模式:

模式说明
NORMAL_PHOTO普通拍照
NORMAL_VIDEO普通录像
PORTRAIT人像模式

3.4 获取输出能力与配置流

// 获取指定场景模式下的输出能力
const cameraOutputCap: camera.CameraOutputCapability =
  cameraManager.getSupportedOutputCapability(cameraDevice, camera.SceneMode.NORMAL_PHOTO);

// 获取预览流配置列表
const previewProfiles: camera.Profile[] = cameraOutputCap.previewProfiles;

// 获取拍照输出配置列表
const photoProfiles: camera.Profile[] = cameraOutputCap.photoProfiles;

camera.Profile 包含:

  • format: 输出格式(数值类型)
  • size: 分辨率 { width, height }

3.5 创建输入输出流

// 创建相机输入流
const cameraInput: camera.CameraInput =
  cameraManager.createCameraInput(cameraDevice);
await cameraInput.open();

// 创建预览输出流(surfaceId 来自 XComponent)
// 注意:预览分辨率应与显示区域宽高比匹配,避免预览变形
const previewOutput: camera.PreviewOutput =
  cameraManager.createPreviewOutput(previewProfile, surfaceId);

// 创建拍照输出流
const photoOutput: camera.PhotoOutput =
  cameraManager.createPhotoOutput(photoProfile);

预览防变形提示: 相机预览流为横向输出,而显示区域为竖向。选择预览分辨率时应使预览宽高比与显示区域宽高比一致(如显示区域为 3:4 竖屏,则选择 1080×1440 而非 1920×1080)。

3.6 配置拍照会话

// 创建拍照会话
const photoSession: camera.PhotoSession =
  cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;

// 开始配置
photoSession.beginConfig();

// 添加输入和输出
photoSession.addInput(cameraInput);
photoSession.addOutput(previewOutput);
photoSession.addOutput(photoOutput);

// 提交配置并启动会话
await photoSession.commitConfig();
await photoSession.start();

3.7 获取焦距缩放范围

// 获取支持的焦距缩放范围 [min, max]
const zoomRatioRange: number[] = photoSession.getZoomRatioRange();
// zoomRatioRange[0] 为最小缩放倍数(如 1.0)
// zoomRatioRange[1] 为最大缩放倍数(如 10.0)

3.8 设置焦距缩放

// 设置焦距缩放倍数
const targetZoom: number = 2.0; // 2倍缩放
if (targetZoom >= zoomRatioRange[0] && targetZoom <= zoomRatioRange[1]) {
  photoSession.setZoomRatio(targetZoom);
}

// 获取当前焦距缩放倍数
const currentZoom: number = photoSession.getZoomRatio();

3.9 闪光灯与对焦控制

// 检查是否支持闪光灯
const hasFlash: boolean = photoSession.hasFlash();
if (hasFlash) {
  // 设置闪光灯模式
  photoSession.setFlashMode(camera.FlashMode.FLASH_MODE_CLOSE);  // 关闭
  // photoSession.setFlashMode(camera.FlashMode.FLASH_MODE_OPEN);    // 单次闪光
  // photoSession.setFlashMode(camera.FlashMode.FLASH_MODE_AUTO);    // 自动
  // photoSession.setFlashMode(camera.FlashMode.FLASH_MODE_ALWAYS_OPEN); // 常亮
}

// 检查是否支持连续自动对焦
const isFocusSupported: boolean =
  photoSession.isFocusModeSupported(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
if (isFocusSupported) {
  photoSession.setFocusMode(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
}

3.10 执行拍照

const captureSetting: camera.PhotoCaptureSetting = {
  quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,
  rotation: camera.ImageRotation.ROTATION_0,
  mirror: false // 前置相机时设为 true
};

photoOutput.capture(captureSetting);

3.11 监听拍照结果

import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { image } from '@kit.ImageKit';

// 定义缩略图回调(用于传递给 UI 层)
onThumbnailReady: ((thumb: PixelMap) => void) | undefined = undefined;

photoOutput.on('photoAssetAvailable',
  async (err: BusinessError, photoAsset: photoAccessHelper.PhotoAsset): Promise<void> => {
    // 保存到相册
    const accessHelper = photoAccessHelper.getPhotoAccessHelper(context);
    const assetChangeRequest = new photoAccessHelper.MediaAssetChangeRequest(photoAsset);
    assetChangeRequest.saveCameraPhoto();
    await accessHelper.applyChanges(assetChangeRequest);

    // 生成缩略图并通过回调传递给 UI(用于左下角预览)
    const thumbnail: image.PixelMap = await photoAsset.getThumbnail();
    if (this.onThumbnailReady) {
      this.onThumbnailReady(thumbnail);
    }
  });

注意:@ComponentV2 中不能使用 @StorageLink,应使用回调函数将缩略图从引擎传递到 UI 层。

3.12 释放资源

async function releaseCamera(
  photoSession: camera.PhotoSession | undefined,
  cameraInput: camera.CameraInput | undefined,
  previewOutput: camera.PreviewOutput | undefined,
  photoOutput: camera.PhotoOutput | undefined
): Promise<void> {
  if (photoSession) {
    photoSession.stop();
    photoSession.release();
  }
  if (cameraInput) {
    cameraInput.close();
  }
  if (previewOutput) {
    previewOutput.release();
  }
  if (photoOutput) {
    photoOutput.release();
  }
}

四、完整示例代码

以下是一个最小化的自定义相机示例(使用状态管理 V2,含竞态修复和生命周期管理):

import { camera } from '@kit.CameraKit';
import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';

// 模块级标志位:解决权限与 surface 竞态
let permReady: boolean = false;
let surfaceReady: boolean = false;

@Entry
@ComponentV2
struct SimpleCameraPage {
  @Local isReady: boolean = false;
  private cameraInput: camera.CameraInput | undefined = undefined;
  private photoSession: camera.PhotoSession | undefined = undefined;
  private previewOutput: camera.PreviewOutput | undefined = undefined;
  private photoOutput: camera.PhotoOutput | undefined = undefined;
  private xComponentController: XComponentController = new XComponentController();
  private surfaceId: string = '';
  private eventHub: common.EventHub | undefined = undefined;

  aboutToAppear(): void {
    const context = this.getUIContext().getHostContext()!;
    const permissions: Array<Permissions> = ['ohos.permission.CAMERA'];
    abilityAccessCtrl.createAtManager()
      .requestPermissionsFromUser(context, permissions)
      .then(() => {
        permReady = true;
        if (surfaceReady) { this.initCamera(); }
      })
      .catch((err: BusinessError) => {
        console.error('权限申请失败: ' + err.message);
      });

    // 订阅应用前后台事件,处理从图库等返回后相机失效的问题
    const abilityCtx = context as common.UIAbilityContext;
    this.eventHub = abilityCtx.eventHub;
    this.eventHub!.on('appForeground', () => {
      // 回到前台时重启相机
      if (this.surfaceId !== '' && permReady) {
        setTimeout(() => { this.initCamera(); }, 300);
      }
    });
    this.eventHub!.on('appBackground', () => {
      // 进入后台时释放相机
      this.releaseAll();
    });
  }

  async initCamera(): Promise<void> {
    const context = this.getUIContext().getHostContext()!;
    const cameraManager = camera.getCameraManager(context);
    const cameraArray = cameraManager.getSupportedCameras();
    if (cameraArray.length === 0) return;

    const cameraDevice = cameraArray[0];
    this.cameraInput = cameraManager.createCameraInput(cameraDevice);
    await this.cameraInput.open();

    const outputCap = cameraManager.getSupportedOutputCapability(
      cameraDevice, camera.SceneMode.NORMAL_PHOTO);
    // 选择与显示区域宽高比匹配的预览分辨率(竖屏 3:4)
    const previewProfile = outputCap.previewProfiles.find((p) =>
      p.size.width === 1080 && p.size.height === 1440
    ) ?? outputCap.previewProfiles[0];
    const photoProfile = outputCap.photoProfiles[0];

    this.previewOutput = cameraManager.createPreviewOutput(previewProfile, this.surfaceId);
    this.photoOutput = cameraManager.createPhotoOutput(photoProfile);

    this.photoSession = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
    this.photoSession.beginConfig();
    this.photoSession.addInput(this.cameraInput);
    this.photoSession.addOutput(this.previewOutput);
    this.photoSession.addOutput(this.photoOutput);
    await this.photoSession.commitConfig();
    await this.photoSession.start();

    if (this.photoSession.isFocusModeSupported(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO)) {
      this.photoSession.setFocusMode(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
    }
    this.isReady = true;
  }

  async releaseAll(): Promise<void> {
    if (this.photoSession) { this.photoSession.stop(); this.photoSession.release(); }
    if (this.cameraInput) { this.cameraInput.close(); }
    if (this.previewOutput) { this.previewOutput.release(); }
    if (this.photoOutput) { this.photoOutput.release(); }
    this.photoSession = undefined;
    this.cameraInput = undefined;
    this.previewOutput = undefined;
    this.photoOutput = undefined;
  }

  aboutToDisappear(): void {
    if (this.eventHub) {
      this.eventHub.off('appForeground', ...);
      this.eventHub.off('appBackground', ...);
    }
    this.releaseAll();
  }

  build() {
    Column() {
      XComponent({
        type: XComponentType.SURFACE,
        controller: this.xComponentController
      })
        .width('100%')
        .height(480) // 匹配 3:4 预览比例
        .onAttach(() => {
          this.xComponentController.setXComponentSurfaceRect({
            surfaceWidth: 1080,
            surfaceHeight: 1440 // 竖屏 3:4
          });
          this.surfaceId = this.xComponentController.getXComponentSurfaceId();
          surfaceReady = true;
          if (permReady) { this.initCamera(); }
        })

      Button('拍照')
        .margin({ top: 20 })
        .onClick(() => {
          this.photoOutput?.capture({
            quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,
            rotation: camera.ImageRotation.ROTATION_0,
            mirror: false
          });
        })
    }
    .width('100%')
    .height('100%')
  }
}

五、关键注意事项

事项说明
权限申请时机必须在 aboutToAppear 中申请,且需与 surface 就绪状态做竞态同步
Surface 绑定预览流必须绑定到 XComponent 的 surfaceId
会话配置顺序beginConfig()addInput/addOutputcommitConfig()start()
资源释放顺序stop() 会话,再依次 close/release 各流
焦距缩放范围必须通过 getZoomRatioRange() 获取,不能硬编码
前后相机切换需要重新释放并创建所有流和会话
预览分辨率必须与显示区域宽高比匹配,否则预览会拉伸变形
前后台切换进入后台应释放相机,回前台需重启,推荐用 EventHub
缩略图传递@ComponentV2 中不能用 @StorageLink,应使用回调函数
boot() 异常处理相机初始化必须 try-catch,否则异常会被静默吞掉

六、API 速查表

API说明
camera.getCameraManager(context)获取相机管理器
cameraManager.getSupportedCameras()获取支持的相机列表
cameraManager.getSupportedSceneModes(device)获取支持的场景模式
cameraManager.getSupportedOutputCapability(device, mode)获取输出能力
cameraManager.createCameraInput(device)创建相机输入
cameraManager.createPreviewOutput(profile, surfaceId)创建预览输出
cameraManager.createPhotoOutput(profile)创建拍照输出
cameraManager.createSession(mode)创建拍照会话
photoSession.setZoomRatio(ratio)设置焦距缩放
photoSession.getZoomRatio()获取当前焦距缩放
photoSession.getZoomRatioRange()获取焦距缩放范围
photoSession.hasFlash()检查闪光灯支持
photoSession.setFlashMode(mode)设置闪光灯模式
photoSession.setFocusMode(mode)设置对焦模式
photoOutput.capture(setting)执行拍照
photoOutput.on('photoAssetAvailable', cb)监听拍照结果

七、总结

使用 @ohos.multimedia.camera 开发自定义相机的核心流程为:

申请权限 → 获取相机管理器 → 获取相机设备 → 获取输出能力
→ 创建输入/输出流 → 配置会话 → 启动预览 → 拍照/缩放控制
→ 释放资源

掌握以上流程后,可以进一步扩展实现录像、人像模式、手动对焦等高级功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值