鸿蒙 HarmonyOS 6 | 多媒体 (01):相机开发 Camera Kit 拍照、录像与预览流处理
在 鸿蒙 HarmonyOS 6 (API 20) 中,Camera Kit 引入了一套基于 Session 会话机制 的全新开发范式。它将复杂的硬件操作抽象为清晰的 输入流 (Input)、会话管理 (Session) 和 输出流 (Output),极大地降低了开发门槛。
文章目录
前言
在移动互联网时代,相机早已超越了单一的“拍照工具”范畴,深度渗透到扫码支付、人脸识别、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。
- 在 UI 中放置
若 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 特效叠加或专业模式摄影,为用户提供极致的视觉体验。
更多推荐


所有评论(0)