文章目录前言一、 流水线思维输入、会话与输出二、 预览流渲染XComponent 与 SurfaceId 的羁绊三、 捕捉光影拍照与录像实现细节1. 拍照 (PhotoOutput)2. 录像 (VideoOutput)四、 实战代码示例五、 总结前言在移动互联网时代相机早已超越了单一的“拍照工具”范畴深度渗透到扫码支付、人脸识别、AR 互动及内容创作等核心场景。对于开发者而言在应用内实现一个功能完备、画面流畅且 UI 高度定制的相机功能面临着硬件生命周期管理、多分辨率适配以及性能平衡等多重挑战。在鸿蒙 HarmonyOS 6 (API 20)中Camera Kit引入了一套基于Session 会话机制的全新开发范式。它将复杂的硬件操作抽象为清晰的输入流 (Input)、会话管理 (Session)和输出流 (Output)极大地降低了开发门槛。本文将脱离简单的 Intent 跳转深入底层手把手带你构建一个支持预览、拍照和录像的自定义相机应用。一、 流水线思维输入、会话与输出驾驭 Camera Kit 的关键在于建立 流水线 思维模型。在 API 20 中相机的运作是一个完整的数据流动过程可类比为摄影棚工作流输入流 (CameraInput)相当于摄影师负责采集光影信号。会话 (CaptureSession)相当于导演控制全场调度启动/停止/配置。输出流 (Output)相当于不同的终端设备。预览输出 (PreviewOutput)送往屏幕显示监视器。拍照输出 (PhotoOutput)送往图像处理引擎生成图片冲印室。录像输出 (VideoOutput)送往编码器生成视频文件录像机。开发流程通常始于camera.getCameraManager。通过相机管理器我们查询设备支持的相机列表前置/后置及其能力集分辨率/帧率。选择合适的相机设备后创建 Input 和 Session并将 Output 像积木一样组装起来。值得注意的是Session 是核心枢纽所有配置修改如变焦、闪光灯必须在Session.commitConfig()后才能生效。二、 预览流渲染XComponent 与 SurfaceId 的羁绊预览是相机开发的第一道难关因为它要求实时、高帧率地渲染。普通的 UI 组件无法通过性能瓶颈鸿蒙为此提供了XComponent。XComponent专为高性能渲染设计提供底层渲染表面 (Surface)允许硬件直接写入显存绕过 UI 层冗余。SurfaceId连接相机与屏幕的纽带。在 UI 中放置XComponent。监听onLoad回调获取唯一的surfaceId。调用cameraManager.createPreviewOutput时传入该 ID。若 ID 传递错误或时序颠倒预览画面将呈现黑屏。三、 捕捉光影拍照与录像实现细节1. 拍照 (PhotoOutput)创建PhotoOutput时需通过PhotoProfile指定分辨率。通常策略是遍历设备能力集选择满足需求如最高像素的配置。调用capture()方法触发快门时可传入单次拍摄参数如地理位置、镜像。2. 录像 (VideoOutput)录像实现更为复杂涉及音频录制、视频编码及文件封装。在鸿蒙中VideoOutput需配合AVRecorder模块初始化AVRecorder配置编码格式 (H.264/H.265)、音频采样率等。从AVRecorder获取输入 Surface 的 ID。将此 ID 传给VideoOutput建立数据通路。注意录像涉及底层硬件编码器资源释放逻辑 (release) 必须严谨否则极易导致后续录像失败或相机卡死。四、 实战代码示例以下代码封装了一个CameraService单例类完整展示了从获取权限 - 初始化相机 - 绑定 XComponent - 启动预览 - 实现拍照 - 资源释放的全流程。import { camera } from kit.CameraKit; import { image } from kit.ImageKit; import { common } from kit.AbilityKit; import { BusinessError } from kit.BasicServicesKit; import { promptAction } from kit.ArkUI; // ------------------------------------------------------------- // 1. 相机服务封装类 (核心逻辑) // ------------------------------------------------------------- class CameraService { private cameraManager: camera.CameraManager | null null; private cameraInput: camera.CameraInput | null null; private captureSession: camera.PhotoSession | null null; // API 20 推荐使用 PhotoSession private previewOutput: camera.PreviewOutput | null null; private photoOutput: camera.PhotoOutput | null null; // 当前的 SurfaceId由 XComponent 提供 private surfaceId: string ; /** * 初始化相机管理器 */ init(context: common.Context) { if (!this.cameraManager) { this.cameraManager camera.getCameraManager(context); } } /** * 启动相机预览 * param surfaceId XComponent 提供的渲染表面 ID */ async startPreview(surfaceId: string) { this.surfaceId surfaceId; if (!this.cameraManager) return; try { // 1. 获取支持的相机设备列表 const cameras this.cameraManager.getSupportedCameras(); if (cameras.length 0) { console.error([Camera] No camera devices found); return; } // 默认选择第一个相机 (通常是后置主摄) const cameraDevice cameras[0]; // 2. 创建相机输入流 (Input) this.cameraInput this.cameraManager.createCameraInput(cameraDevice); await this.cameraInput.open(); // 3. 获取相机能力集选择合适的配置 (Profile) const capability this.cameraManager.getSupportedOutputCapability(cameraDevice, camera.SceneMode.NORMAL_PHOTO); // 简单起见选择预览流的第一个配置 const previewProfile capability.previewProfiles[0]; // 选择拍照流的第一个配置 (实际开发应筛选最高分辨率) const photoProfile capability.photoProfiles[0]; // 4. 创建输出流 (Output) // 预览输出绑定到 XComponent 的 surfaceId this.previewOutput this.cameraManager.createPreviewOutput(previewProfile, this.surfaceId); // 拍照输出 this.photoOutput this.cameraManager.createPhotoOutput(photoProfile); // 5. 创建会话 (Session) 并组装 this.captureSession this.cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession; this.captureSession.beginConfig(); this.captureSession.addInput(this.cameraInput); this.captureSession.addOutput(this.previewOutput); this.captureSession.addOutput(this.photoOutput); await this.captureSession.commitConfig(); await this.captureSession.start(); console.info([Camera] Preview started successfully); } catch (err) { const error err as BusinessError; console.error([Camera] Failed to start preview: ${error.message}); } } /** * 拍照功能 */ async takePhoto() { if (!this.photoOutput) return; try { // 配置拍照参数 const photoCaptureSetting: camera.PhotoCaptureSetting { quality: camera.QualityLevel.QUALITY_LEVEL_HIGH, rotation: camera.ImageRotation.ROTATION_0 }; // 触发拍照 await this.photoOutput.capture(photoCaptureSetting); // 注意PhotoOutput.capture 只是触发动作 // 实际获取图片数据通常需要监听 photoAvailable 事件并配合 PhotoAccessHelper 保存 // 这里仅做触发演示 promptAction.showToast({ message: 咔嚓拍照触发成功 }); } catch (err) { console.error([Camera] Take photo failed: ${(err as BusinessError).message}); } } /** * 释放资源 * 必须在页面销毁时调用否则可能导致相机无法再次打开 */ async release() { console.info([Camera] Releasing resources...); try { await this.captureSession?.stop(); await this.captureSession?.release(); await this.cameraInput?.close(); await this.previewOutput?.release(); await this.photoOutput?.release(); } catch (err) { console.error([Camera] Release failed, err); } finally { this.captureSession null; this.cameraInput null; this.previewOutput null; this.photoOutput null; } } } // 导出单例 const cameraService new CameraService(); // ------------------------------------------------------------- // 2. 相机预览与交互页面 // ------------------------------------------------------------- Entry Component struct CameraPage { private xComponentController: XComponentController new XComponentController(); // 标记 XComponent 是否加载完成 State isSurfaceReady: boolean false; aboutToAppear(): void { const context getContext(this) as common.UIAbilityContext; cameraService.init(context); } aboutToDisappear(): void { // 页面销毁时务必释放相机资源 cameraService.release(); } build() { Stack() { // 1. 相机预览区域 // 使用 XComponent 承载预览流 XComponent({ id: cameraPreview, type: XComponentType.SURFACE, controller: this.xComponentController }) .onLoad(() { // 核心当 XComponent 加载完成获取 surfaceId this.xComponentController.setXComponentSurfaceSize({ surfaceWidth: 1080, surfaceHeight: 1920 }); const surfaceId this.xComponentController.getXComponentSurfaceId(); console.info([UI] Surface created: ${surfaceId}); this.isSurfaceReady true; // 启动预览 cameraService.startPreview(surfaceId); }) .width(100%) .height(100%) // 2. 拍照控制层 (覆盖在预览之上) Column() { // 顶部工具栏 (模拟) Row() { // 仅作为 UI 占位示意 Text(Flash).fontColor(Color.White).fontSize(14) Text(HDR).fontColor(Color.White).fontSize(14) } .width(100%) .justifyContent(FlexAlign.SpaceBetween) .padding({ top: 40, left: 20, right: 20 }) // 底部拍照按钮 Blank() // 占位把按钮顶到底部 Row() { // 拍照快门键 Button() .width(80) .height(80) .borderRadius(40) .backgroundColor(Color.White) .border({ width: 4, color: #CCCCCC }) .onClick(() { cameraService.takePhoto(); }) // 添加一个按压效果动画 .stateEffect(true) } .width(100%) .justifyContent(FlexAlign.Center) .padding({ bottom: 50 }) } .width(100%) .height(100%) // 让点击事件穿透到下层 (除了按钮本身) .hitTestBehavior(HitTestMode.Transparent) } .width(100%) .height(100%) .backgroundColor(Color.Black) // 相机加载前显示黑色背景 } }五、 总结自定义相机开发是鸿蒙多媒体开发中极具挑战也极具价值的领域。通过本文我们掌握了Session 范式理解Input - Session - Output的数据流转模型。渲染机制利用XComponent和SurfaceId实现高性能预览。资源管理严格遵循生命周期正确创建与释放相机资源。一旦掌握了这套基础架构你便可以在此基础上扩展出更丰富的功能如扫码识别、AR 特效叠加或专业模式摄影为用户提供极致的视觉体验。