HarmonyOS 6.0 相机开发——拍照与录像

HarmonyOS的相机API分散在@kit.MultimediaKit里,photoAccessHelper管相册、camera管拍照、media管录像。三条线各自独立,合在一起才是完整相机功能。这篇把拍照和录像的主线走通,顺带说清楚权限和生命周期。

权限声明

相机开发需要三个权限:

json5 复制代码
// module.json5
{
  "requestPermissions": [
    { "name": "ohos.permission.CAMERA" },
    { "name": "ohos.permission.MICROPHONE" },
    { "name": "ohos.permission.READ_IMAGEVIDEO" }
  ]
}

CAMERA和MICROPHONE是用户授权权限,运行时必须动态申请。READ_IMAGEVIDEO用于保存照片到相册。三个权限缺一个功能就残废。

动态申请:

typescript 复制代码
import { abilityAccessCtrl, common } from '@kit.AbilityKit';

async requestPermissions(context: common.UIAbilityContext): Promise<boolean> {
  let atManager: abilityAccessCtrl.AccessManager = abilityAccessCtrl.createAtManager();
  let permissions: string[] = ['ohos.permission.CAMERA', 'ohos.permission.MICROPHONE'];
  let result = await atManager.requestPermissionsFromUser(context, permissions);
  for (let i = 0; i < result.authResults.length; i++) {
    if (result.authResults[i] !== 0) {
      return false;
    }
  }
  return true;
}

authResults为0表示授权通过。不通过的要引导用户去设置页手动开启。

CameraManager初始化

typescript 复制代码
import { camera } from '@kit.MultimediaKit';

let cameraManager: camera.CameraManager = camera.getCameraManager(context);

就一行,但context必须是UIAbilityContext,不是BaseContext。在UIAbility里用this.context,在页面里用getContext(this)。

获取可用相机列表:

typescript 复制代码
let cameras: camera.CameraDevice[] = cameraManager.getSupportedCameras();
// cameras[0]通常是后置, cameras[1]通常是前置

getSupportedCameras返回的数组长度取决于设备。模拟器可能返回空数组------相机API基本都要真机测试。

创建会话

拍照用PhotoSession,录像用VideoSession:

typescript 复制代码
import { camera } from '@kit.MultimediaKit';

// 拍照会话
let photoSession: camera.PhotoSession = cameraManager.createPhotoSession(cameras[0]);

// 录像会话
let videoSession: camera.VideoSession = cameraManager.createVideoSession(cameras[0]);

Session是相机操作的核心。所有配置和操作都挂在Session上。

预览

预览需要XComponent作为输出面:

typescript 复制代码
XComponent({ id: 'cameraPreview', type: 'surface', controller: this.xComponentController })
  .onLoad(() => {
    let surfaceId: string = this.xComponentController.getXComponentSurfaceId();
    // 将surfaceId设置到session的预览输出
    let previewOutput: camera.PreviewOutput = cameraManager.createPreviewOutput(profile, surfaceId);
    photoSession.addOutput(previewOutput);
    photoSession.start();
  })

流程:XComponent提供surface → 创建PreviewOutput → 添加到Session → start。

Profile从cameraManager.createProfiles获取:

typescript 复制代码
let profiles: camera.CameraOutputCapability = cameraManager.getSupportedOutputCapability(cameras[0]);
let previewProfile: camera.Profile = profiles.previewProfiles[0];
let photoProfile: camera.PhotoProfile = profiles.photoProfiles[0];

取第一个Profile是最简单的做法,生产环境要根据分辨率和帧率筛选。

拍照

typescript 复制代码
let photoOutput: camera.PhotoOutput = cameraManager.createPhotoOutput(photoProfile, surfaceId);
photoSession.addOutput(photoOutput);

// 设置拍照回调
photoOutput.on('photoAvailable', (photo: camera.Photo): void => {
  // photo可以保存到相册
});

// 触发拍照
photoSession.capture();

capture()是异步的,拍照结果通过photoAvailable回调返回。回调里拿到的是Photo对象,需要进一步处理才能保存。

保存到相册

typescript 复制代码
import { photoAccessHelper } from '@kit.MediaLibraryKit';

async savePhoto(photo: camera.Photo, context: common.UIAbilityContext): Promise<string> {
  let helper: photoAccessHelper.PhotoAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
  let uri: string = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg');
  let file: fileIo.File = fileIo.openSync(uri, fileIo.OpenMode.WRITE_ONLY);
  // 从photo中读取数据并写入file
  // ...
  fileIo.closeSync(file);
  return uri;
}

createAsset创建相册条目,返回uri。然后把照片数据写入uri对应的文件。

录像

录像需要AVRecorder配合VideoSession:

typescript 复制代码
import { media } from '@kit.MultimediaKit';

// 创建VideoOutput
let videoOutput: camera.VideoOutput = cameraManager.createVideoOutput(videoProfile, surfaceId);
videoSession.addOutput(videoOutput);

// 创建AVRecorder
let avRecorder: media.AVRecorder = await media.createAVRecorder();
let config: media.AVRecorderConfig = {
  videoSourceType: media.VideoSourceType.CAMERA_SOURCE,
  profile: {
    fileFormat: media.ContainerFormatType.CAMERA_MP4,
    videoBitrate: 2000000,
    videoCodec: media.VideoCodecType.H264,
    videoFrameWidth: 1920,
    videoFrameHeight: 1080,
    videoFrameRate: 30
  },
  url: 'fd://' + fileDescriptor
};
await avRecorder.prepare(config);

// 关联videoOutput和avRecorder
videoOutput.start();
await avRecorder.start();

录像流程比较长:createAVRecorder → prepare → videoOutput.start → avRecorder.start。每一步都必须等上一步完成。

停止录像:

typescript 复制代码
await avRecorder.stop();
await avRecorder.release();
videoOutput.stop();

切换前后摄像头

typescript 复制代码
async switchCamera(session: camera.PhotoSession, cameraManager: camera.CameraManager,
  newCamera: camera.CameraDevice): Promise<void> {
  await session.stop();
  session.removeOutput(previewOutput);
  session.removeOutput(photoOutput);
  // 重新创建session和output
  let newSession: camera.PhotoSession = cameraManager.createPhotoSession(newCamera);
  // 重新addOutput和start
}

切换摄像头不能直接替换camera,必须停止session、移除旧output、创建新session。简单粗暴但有效。

闪光灯

typescript 复制代码
// 检查是否支持闪光灯
let hasFlash: boolean = photoSession.hasFlash();

// 设置闪光灯模式
photoSession.setFlashMode(camera.FlashMode.FLASH_MODE_ALWAYS_ON);   // 常开
photoSession.setFlashMode(camera.FlashMode.FLASH_MODE_AUTO);        // 自动
photoSession.setFlashMode(camera.FlashMode.FLASH_MODE_CLOSED);      // 关闭

// 获取当前模式
let mode: camera.FlashMode = photoSession.getFlashMode();

前置摄像头通常不支持闪光灯,hasFlash()返回false。这种情况下调setFlashMode会报错。

变焦

typescript 复制代码
// 获取变焦范围
let zoomRange: Array<number> = photoSession.getZoomRatioRange();
let minZoom: number = zoomRange[0]; // 如1.0
let maxZoom: number = zoomRange[1]; // 如6.0

// 设置变焦倍数
photoSession.setZoomRatio(2.0);

// 获取当前变焦
let currentZoom: number = photoSession.getZoomRatio();

setZoomRatio的值必须在zoomRatioRange范围内。超出范围直接报参数错误。

对焦

typescript 复制代码
// 设置对焦模式
photoSession.setFocusMode(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO); // 连续自动对焦
photoSession.setFocusMode(camera.FocusMode.FOCUS_MODE_MANUAL);          // 手动对焦

// 手动对焦指定点
photoSession.setFocusPoint({ x: 0.5, y: 0.5 }); // 归一化坐标(0-1)

手动对焦需要先设FOCUS_MODE_MANUAL,再设焦点坐标。坐标是归一化的------(0,0)左上角,(1,1)右下角。

分辨率选择

typescript 复制代码
let outputCapability: camera.CameraOutputCapability = cameraManager.getSupportedOutputCapability(cameraDevice);

// 遍历可用的photoProfile
let photoProfiles: camera.PhotoProfile[] = outputCapability.photoProfiles;
for (let i = 0; i < photoProfiles.length; i++) {
  let profile: camera.PhotoProfile = photoProfiles[i];
  let width: number = profile.size.width;
  let height: number = profile.size.height;
  // 选择需要的分辨率
}

photoProfiles按分辨率从高到低排列。取0通常是最高分辨率。

状态监听

typescript 复制代码
// Session状态
photoSession.on('stateChange', (state: camera.SessionState) => {
  switch (state) {
    case camera.SessionState.SESSION_CONFIGURED:
      // 配置完成
      break;
    case camera.SessionState.SESSION_STARTED:
      // 已启动
      break;
    case camera.SessionState.SESSION_STOPPED:
      // 已停止
      break;
  }
});

// 错误监听
photoSession.on('error', (error: camera.BusinessError) => {
  // 处理相机错误
});

SessionState有CONFIGURING、CONFIGURED、STARTED、STOPPED四种状态。createPhotoSession后进入CONFIGURING,addOutput后到CONFIGURED,start后到STARTED。

生命周期管理

相机资源必须正确释放,否则其他应用无法使用相机:

typescript 复制代码
aboutToDisappear(): void {
  if (this.photoSession) {
    this.photoSession.stop();
    this.photoSession.release();
  }
  if (this.cameraManager) {
    this.cameraManager.release();
  }
}

release顺序:Session → Output → CameraManager。每个release都是异步的,应该await确保完成。

踩坑清单

问题 原因 解决
模拟器getSupportedCameras返回空 模拟器没有摄像头 用真机测试
getCameraManager报错 context类型不对 传UIAbilityContext
预览黑屏 surfaceId没传或session没start 检查XComponent onLoad流程
capture()无回调 没addOutput(photoOutput) 必须把photoOutput加入session
切换摄像头crash 直接替换camera stop→removeOutput→新建session
闪光灯报错 前置不支持 先hasFlash()检查
变焦报错 超出zoomRatioRange 在范围内设值
录像文件打不开 AVRecorder没prepare直接start 按prepare→start顺序
相机被占用 上次没release aboutToDisappear中释放
保存相册失败 缺READ_IMAGEVIDEO权限 module.json5声明权限

相机开发最大的痛点是调试------模拟器几乎不能跑,真机每次部署又慢。建议先把权限和初始化流程写对,再逐步加功能。拍照→保存→预览→录像,一个功能调通再加下一个。

相关推荐
FrameNotWork8 小时前
HarmonyOS 6.0 自定义TabBar——从基础到动效
华为·harmonyos
世人万千丶8 小时前
鸿蒙Flutter Flex布局性能优化
学习·flutter·性能优化·harmonyos·鸿蒙·鸿蒙系统
●VON8 小时前
鸿蒙 PC Markdown 编辑器质量流水线:Web 构建、回归与 Release 门禁
前端·华为·编辑器·harmonyos·鸿蒙
ZZZMMM.zip9 小时前
断舍离清单 —— 鸿蒙AI智能助手开发全流程解析
人工智能·华为·harmonyos·鸿蒙·鸿蒙系统
listening7779 小时前
HarmonyOS 6.1 跨设备数据库实战:分布式账本的落地与一致性校验
数据库·harmonyos·分布式账本
●VON9 小时前
鸿蒙 PC Markdown 编辑器文件系统:Core File Kit 与安全保存
安全·华为·编辑器·harmonyos·鸿蒙
YM52e9 小时前
鸿蒙 Flutter 渐变效果详解:LinearGradient、RadialGradient、SweepGradient
android·学习·flutter·华为·harmonyos·鸿蒙
YM52e9 小时前
鸿蒙 Flutter BoxDecoration装饰:打造精美UI效果
学习·flutter·ui·华为·harmonyos·鸿蒙
●VON9 小时前
鸿蒙 PC Markdown 编辑器性能工程:中文输入与 10MiB 文档
华为·架构·编辑器·harmonyos·鸿蒙