HarmonyOS 6.1 全栈实战录 - 79 驶入视觉新纪元:实战 Camera Kit 全质量未压缩图调度、HDR动态照片与元数据实时增删中控舱
1、引言
在智能移动设备中,相机服务(Camera Kit)的性能与易用性直接决定了社交、扫码、AR 等核心场景的体验。随着影像技术的不断突破,开发者面临着更加极致的硬件调度挑战:如何在保障流畅度的同时获取高达上千万像素的未压缩 YUV 原图?如何让拍摄出来的动态照片(Live Photo)在亮暗对比上展现如临其境的高动态范围(HDR)?如何对复杂的实时图像识别流(元数据)进行动态的算法增减,而无需频繁重启沉重的相机硬件会话?
HarmonyOS NEXT 6.1 (API 23) 为相机的这些深水区难题,递交了全新的满分答卷:
- 全质量与未压缩图统一监听:引入全新的回调直接回传物理照片,为高性能影像处理与定制滤镜提供了原生通道。
- 全链路 HDR 动态照片:静态图与短视频全线高动态化,极致展现高光、暗部与层次感。
- 元数据对象类型实时增删:支持零重建延迟的运行期对象动态调整。
本篇我们将通过构建一个「视觉新纪元中控舱」项目,全景式展示这三项相机底层更新,带你攻克先进摄影的核心逻辑。
2、效果展示与项目结构
2.1 运行效果展示
本节构建的控制舱——视觉新纪元中控舱,包含了三大核心验证区域:
- 全质量图与未压缩图调度舱:点击“注册监听”即可注入回调,模拟相机快门时接收
CapturePhoto对象。当触发“模拟相机拍摄”后,控制舱迅速捕获并高亮显示已收到未压缩物理相片(image.Picture)。 - HDR 动态照片配置控制台:一键开启 HDR 开关,控制舱自动完成参数的相互约束性计算,将流格式切为
CAMERA_FORMAT_YCRCB_P010,将色彩空间升级为BT2020_HLG极宽色域,直观演示“全高动态范围”相片拍摄的自检及配置状态。 - 元数据检测流实时控制台:展示对人脸、条码等不同类型元数据的运行期动态注册与注销,实时显示已激活的监控列表。
2.2 示例项目物理结构
演示工程 CameraKitDemo 结构如下:
CameraKitDemo
├── AppScope
│ └── app.json5
├── entry
│ ├── oh-package.json5
│ └── src
│ └── main
│ ├── ets
│ │ ├── entryability
│ │ │ └── EntryAbility.ets
│ │ └── pages
│ │ ├── Index.ets <-- Camera Kit 亮点展示与中控台导航
│ │ └── CameraDemo.ets <-- 实战舱:三大新特性自检及流程仿真
│ ├── resources
│ │ └── base
│ │ └── profile
│ │ └── main_pages.json
│ └── module.json5
└── build-profile.json53、三项重磅相机新特性深度剖析
3.1 全质量图与未压缩图调度(PhotoOutput 回调革命)
在先前的 API 版本中,获取一张物理拍摄的图片,往往需要配合 imageReceiver 等复杂的媒体中间件进行数据通道转换。特别是在获取未压缩(YUV)的物理底片时,极其容易造成主线程卡顿或时序紊乱。
API 23 针对 PhotoOutput(照片输出)引入了全新的高画质及无损回调标准:
onCapturePhotoAvailable(callback: Callback<CapturePhoto>):用于注册监听全质量图和未压缩图。- 在触发此回调时,应用直接拿到了封装完好的
CapturePhoto实例,其包含的主分量属性main会携带未压缩的image.Picture或image.Image。 - 开发约束:此接口目前不支持在回调方法内部调用
offCapturePhotoAvailable,即严禁“套娃式注销”;且如果拍摄未压缩图(YUV)格式图片,该接口是唯一合法的物理接收通路。
3.2 全链路双 HDR 动态照片拍摄
动态照片(Live Photo)的静态画面与附带的极短小视频极易产生色差与画质撕裂。API 23 引入的“HDR 动态照片”,要求组成动态照片的静态图和短视频全链路都是高动态范围(HDR)内容。
开启 HDR 动态照片,需要系统平台进行特定的能力校验(先查后用):
- 格式核验:通过
getSupportedFullOutputCapability查询预览输出能力,必须从中选择 P010 深度色彩输出流(如CAMERA_FORMAT_YCRCB_P010或CAMERA_FORMAT_YCBCR_P010)。 - 色彩空间自检:通过
getSupportedColorSpaces查询设备支持的色彩空间列表,并显式调用setColorSpace强制约束为BT2020_HLG。
只有当流格式和色彩空间双项 HDR 标志位均正确咬合时,生成的动态照片才能具备极其通透的高光、深邃的暗部与完美的色彩渐变层次。
3.3 实时元数据检测流(MetadataOutput)零延迟切换
在开发高集成度的相机应用时,同一个拍照界面可能需要兼顾“条码识别”与“人脸自动对焦”。如果因为用户从扫码切换到人脸拍摄,就去销毁现有的相机配置流、重建 CameraSession,会造成极为明显的视觉闪烁与耗电激增。
API 23 针对 MetadataOutput 新增了以下方法:
addMetadataObjectTypes(types: Array<MetadataObjectType>):在线增加需要上报的检测对象类型。removeMetadataObjectTypes(types: Array<MetadataObjectType>):在线移除已上报的检测对象类型。
这两个方法允许我们在不需要暂停 CameraSession、不需要重建硬件实例的情况下,实时以非阻塞式控制检测对象的增减。
4、视觉新纪元中控舱源码实战
4.1 Index.ets — 导向页
首页负责概述 API 23 Camera Kit 的三个重磅变化,并开启控制舱:
import { router } from '@kit.ArkUI';
@Entry
@Component
struct Index {
build() {
Column() {
Column() {
Image($r('app.media.startIcon'))
.width(72)
.height(72)
.margin({ bottom: 16 })
.shadow({ radius: 30, color: '#38BDF8', offsetY: 8 })
Text('Camera Kit')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#38BDF8')
.margin({ bottom: 4 })
Text('视觉新纪元中控舱 · API 23 新增全画质未压缩图与 HDR 动态摄影')
.fontSize(12)
.fontColor('#64748B')
}
.width('100%')
.padding({ top: 60, bottom: 40, left: 24, right: 24 })
.backgroundColor('#0B0F19')
.border({ width: { bottom: 1 }, color: '#1E293B' })
Column({ space: 16 }) {
Column({ space: 12 }) {
Text('📸 Camera Kit API 23 摄影新篇章')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor('#38BDF8')
.alignSelf(ItemAlign.Start)
Text('1. 【全质量与未压缩图】新增 onCapturePhotoAvailable 监听,回调直接递交 CapturePhoto 主分量,大幅简化 YUV 未压缩图的开发流程。')
.fontSize(12)
.fontColor('#94A3B8')
.lineHeight(18)
.alignSelf(ItemAlign.Start)
Text('2. 【HDR 动态照片】支持拍摄静态图像与动态短视频均为高动态范围(HDR)内容的动态相片,通过 P010 输出格式与 BT2020_HLG 色彩空间搭配配置。')
.fontSize(12)
.fontColor('#94A3B8')
.lineHeight(18)
.alignSelf(ItemAlign.Start)
Text('3. 【实时检测流控制】新增 addMetadataObjectTypes / removeMetadataObjectTypes,支持对人脸、条码等元数据检测类型的实时动态控制。')
.fontSize(12)
.fontColor('#94A3B8')
.lineHeight(18)
.alignSelf(ItemAlign.Start)
}
.padding(20)
.backgroundColor('#0F172A')
.borderRadius(14)
.border({ width: 1, color: '#1E293B' })
Blank()
Button('📺 激活视觉新纪元中控舱', { type: ButtonType.Normal })
.width('100%')
.height(50)
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
.backgroundColor('#38BDF8')
.borderRadius(10)
.shadow({ radius: 12, color: '#38BDF8', offsetY: 4 })
.onClick(() => {
router.pushUrl({ url: 'pages/CameraDemo' });
})
}
.layoutWeight(1)
.padding(24)
.justifyContent(FlexAlign.SpaceBetween)
}
.width('100%')
.height('100%')
.backgroundColor('#070A13')
}
}4.2 CameraDemo.ets — 核心交互验证舱
import { camera } from '@kit.CameraKit';
import { image } from '@kit.ImageKit';
import { promptAction } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@Component
struct CameraDemo {
@State log: string = '等待中控台指令...\n';
@State isHDRPhotoEnabled: boolean = false;
@State selectedColorSpace: string = 'SRGB';
@State selectedFormat: string = 'CAMERA_FORMAT_YUV_420_SP';
// 元数据对象检测类型列表
@State activeMetadataTypes: string[] = ['FACE_DETECTION'];
@State availableMetadataTypes: string[] = ['FACE_DETECTION', 'BARCODE_DETECTION', 'PORTRAIT_DETECTION'];
// 模拟的捕获图照片基本状态信息
@State photoCapturedCount: number = 0;
@State lastPhotoInfo: string = '暂无拍摄记录';
// 1. 注册监听全质量图和未压缩图回调
private onPhotoAvailableCallback = (capturePhoto: camera.CapturePhoto): void => {
this.photoCapturedCount++;
// 模拟接收到的全质量图或未压缩图(YUV)对象
let picture: image.Image | image.Picture = capturePhoto.main;
let sizeStr = picture ? '已成功载入物理图主分量数据(image.Picture)' : '空数据';
this.lastPhotoInfo = `已捕获全质量未压缩图 (No. ${this.photoCapturedCount}) - ${sizeStr}`;
this.appendLog(`[回调触发] onCapturePhotoAvailable 监听到新相片,主分量: ${sizeStr}`);
};
private registerPhotoAvailableListener(): void {
this.appendLog('[指令] 正在注册全质量与未压缩图监听...');
try {
// 实际开发中调用:photoOutput.onCapturePhotoAvailable(this.onPhotoAvailableCallback)
this.appendLog('✅ 成功调用: photoOutput.onCapturePhotoAvailable(callback)');
promptAction.showToast({ message: '全质量相片监听注册成功' });
} catch (error) {
let err = error as BusinessError;
this.appendLog(`❌ 注册监听失败,错误码: ${err.code}`);
}
}
private unregisterPhotoAvailableListener(): void {
this.appendLog('[指令] 正在注销全质量与未压缩图监听...');
try {
// 实际开发中调用:photoOutput.offCapturePhotoAvailable(this.onPhotoAvailableCallback)
this.appendLog('✅ 成功调用: photoOutput.offCapturePhotoAvailable(callback)');
promptAction.showToast({ message: '全质量相片监听已注销' });
} catch (error) {
let err = error as BusinessError;
this.appendLog(`❌ 注销监听失败,错误码: ${err.code}`);
}
}
// 模拟主动拍摄动作以触发回调
private triggerCaptureSimulation(): void {
this.appendLog('[拍照] 触发相机拍摄动作...');
setTimeout(() => {
// 模拟构造一个 CapturePhoto 传入回调
const mockPicture: image.Picture = {} as image.Picture;
const mockCapturePhoto: camera.CapturePhoto = {
main: mockPicture
};
this.onPhotoAvailableCallback(mockCapturePhoto);
}, 500);
}
// 2. HDR动态照片配置逻辑与状态切换
private toggleHDRPhoto(enabled: boolean): void {
this.isHDRPhotoEnabled = enabled;
if (enabled) {
this.selectedFormat = 'CAMERA_FORMAT_YCRCB_P010';
this.selectedColorSpace = 'BT2020_HLG';
this.appendLog('[配置] 切换至 HDR 动态照片模式:静态图片与动态短视频均配置为高动态范围。');
this.appendLog(' - 预览输出格式设置为: P010 (YCRCB_P010)');
this.appendLog(' - 色彩空间设置为: BT2020_HLG (setColorSpace)');
promptAction.showToast({ message: 'HDR 动态照片模式已开启' });
} else {
this.selectedFormat = 'CAMERA_FORMAT_YUV_420_SP';
this.selectedColorSpace = 'SRGB';
this.appendLog('[配置] 切换至标准 SDR 模式:');
this.appendLog(' - 预览输出格式设置为: YUV_420_SP');
this.appendLog(' - 色彩空间设置为: SRGB');
promptAction.showToast({ message: '已回退至标准 SDR 模式' });
}
}
// 3. 元数据检测类型实时增删逻辑
private toggleMetadataType(type: string): void {
const index = this.activeMetadataTypes.indexOf(type);
if (index > -1) {
// 删除该类型
let temp = [...this.activeMetadataTypes];
temp.splice(index, 1);
this.activeMetadataTypes = temp;
this.appendLog(`[元数据] 正在注销检测类型: ${type}`);
try {
// 实际调用为 metadataOutput.removeMetadataObjectTypes([camera.MetadataObjectType[type]])
this.appendLog(`✅ 成功调用: metadataOutput.removeMetadataObjectTypes(['${type}'])`);
} catch (error) {
let err = error as BusinessError;
this.appendLog(`❌ 移除元数据检测失败: ${err.code}`);
}
} else {
// 新增该类型
this.activeMetadataTypes.push(type);
this.appendLog(`[元数据] 正在注册检测类型: ${type}`);
try {
// 实际调用为 metadataOutput.addMetadataObjectTypes([camera.MetadataObjectType[type]])
this.appendLog(`✅ 成功调用: metadataOutput.addMetadataObjectTypes(['${type}'])`);
} catch (error) {
let err = error as BusinessError;
this.appendLog(`❌ 添加元数据检测失败: ${err.code}`);
}
}
}
private appendLog(line: string): void {
this.log = `${line}\n${this.log}`;
}
build() {
Column() {
Row() {
Text('视觉新纪元中控舱 · Camera Kit')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#38BDF8')
}
.width('100%')
.height(56)
.padding({ left: 20 })
.backgroundColor('#0B0F19')
Scroll() {
Column({ space: 16 }) {
// 模块一:全质量与未压缩图自检舱
Column({ space: 12 }) {
Row() {
Text('📷 全质量图与未压缩图调度舱 (API 23)')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#38BDF8')
}
.width('100%')
Text('从 API version 23 开始,PhotoOutput 提供 onCapturePhotoAvailable 回调用于监听全质量和未压缩物理相片,回调可直接获取完整 Picture 物理数据。')
.fontSize(11)
.fontColor('#94A3B8')
.lineHeight(16)
Row({ space: 10 }) {
Button('注册 PhotoAvailable 监听')
.fontSize(12)
.layoutWeight(1)
.height(40)
.backgroundColor('#0369A1')
.onClick(() => this.registerPhotoAvailableListener())
Button('注销监听')
.fontSize(12)
.layoutWeight(1)
.height(40)
.backgroundColor('#334155')
.onClick(() => this.unregisterPhotoAvailableListener())
}
Button('⚡️ 模拟相机拍摄捕获')
.width('100%')
.height(40)
.fontSize(13)
.fontWeight(FontWeight.Bold)
.backgroundColor('#0284C7')
.onClick(() => this.triggerCaptureSimulation())
Column() {
Text(`捕获统计: 已收到 ${this.photoCapturedCount} 张全画质相片`)
.fontSize(12)
.fontColor('#E2E8F0')
.margin({ bottom: 4 })
Text(this.lastPhotoInfo)
.fontSize(11)
.fontColor('#F472B6')
}
.width('100%')
.padding(12)
.backgroundColor('#1E293B')
.borderRadius(8)
}
.padding(16)
.backgroundColor('#0F172A')
.borderRadius(14)
.border({ width: 1, color: '#1E293B' })
// 模块二:HDR 动态照片配置舱
Column({ space: 12 }) {
Row() {
Text('🌅 HDR 动态照片配置控制台')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#38BDF8')
}
.width('100%')
Text('静态图片与动态短视频均配置为高动态范围(HDR)内容,在高光与暗部细节、色彩层次和整体质感方面优于SDR成片。P010 输出流格式与 HLG 色彩空间需搭配使用。')
.fontSize(11)
.fontColor('#94A3B8')
.lineHeight(16)
Row() {
Text('HDR 动态照片模式')
.fontSize(13)
.fontColor('#F8FAFC')
Toggle({ type: ToggleType.Switch, isOn: this.isHDRPhotoEnabled })
.onChange((isOn: boolean) => this.toggleHDRPhoto(isOn))
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
Column({ space: 6 }) {
Row() {
Text('流格式 (Format): ')
.fontSize(12)
.fontColor('#64748B')
Text(this.selectedFormat)
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(this.isHDRPhotoEnabled ? '#34D399' : '#94A3B8')
}
.width('100%')
Row() {
Text('色彩空间 (ColorSpace): ')
.fontSize(12)
.fontColor('#64748B')
Text(this.selectedColorSpace)
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(this.isHDRPhotoEnabled ? '#34D399' : '#94A3B8')
}
.width('100%')
}
.padding(12)
.backgroundColor('#1E293B')
.borderRadius(8)
}
.padding(16)
.backgroundColor('#0F172A')
.borderRadius(14)
.border({ width: 1, color: '#1E293B' })
// 模块三:元数据实时检测增删舱
Column({ space: 12 }) {
Row() {
Text('🔍 元数据检测流实时控制台')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#38BDF8')
}
.width('100%')
Text('API 23 支持 addMetadataObjectTypes/removeMetadataObjectTypes 对检测类型进行实时增减,无需重建会话或重启 MetadataOutput。')
.fontSize(11)
.fontColor('#94A3B8')
.lineHeight(16)
Column({ space: 8 }) {
ForEach(this.availableMetadataTypes, (type: string) => {
Row() {
Text(type)
.fontSize(12)
.fontColor('#F8FAFC')
Checkbox({ name: type, group: 'metadataGroup' })
.select(this.activeMetadataTypes.indexOf(type) > -1)
.onChange(() => this.toggleMetadataType(type))
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding({ top: 4, bottom: 4 })
})
}
Row() {
Text('当前激活的检测类型:')
.fontSize(12)
.fontColor('#64748B')
Text(this.activeMetadataTypes.join(' , ') || '无')
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor('#F472B6')
}
.width('100%')
.padding(10)
.backgroundColor('#1E293B')
.borderRadius(8)
}
.padding(16)
.backgroundColor('#0F172A')
.borderRadius(14)
.border({ width: 1, color: '#1E293B' })
// 实时日志面板
Column() {
Text('控制台诊断日志')
.fontSize(12)
.fontColor('#64748B')
.alignSelf(ItemAlign.Start)
.margin({ bottom: 8 })
Text(this.log)
.fontSize(11)
.fontColor('#CBD5E1')
.fontFamily('monospace')
.lineHeight(18)
.alignSelf(ItemAlign.Start)
.width('100%')
}
.padding(16)
.backgroundColor('#0F172A')
.borderRadius(12)
.border({ width: 1, color: '#1E293B' })
}
.padding(16)
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor('#070A13')
}
}5、元数据 C API 高阶应用参考
对于极致追求实时性的算法团队(如 AR 渲染、人脸物理追踪),元数据除了能通过 ArkTS 进行上层监听外,API 23 还额外在 NDK 侧新增了元数据流的专属 C API:
#include <multimedia/camera_framework/camera.h>
#include <multimedia/camera_framework/camera_manager.h>
void SetupCMetadataOutput(Camera_Manager* cameraManager) {
// 1. 定义我们所关心的元数据对象类型数组
Camera_MetadataObjectType types[] = {
CAMERA_METADATA_OBJECT_TYPE_FACE,
CAMERA_METADATA_OBJECT_TYPE_BARCODE
};
uint32_t size = sizeof(types) / sizeof(types[0]);
Camera_MetadataOutput* metadataOutput = nullptr;
// 2. 调用 API 23 专属的元数据对象类型数组创建接口
Camera_ErrorCode ret = OH_CameraManager_CreateMetadataOutputWithObjectTypes(
cameraManager,
types,
size,
&metadataOutput
);
if (ret == CAMERA_OK && metadataOutput != nullptr) {
// 创建元数据实例成功,接下来可在 NDK 侧绑定数据回调进行深度帧处理
}
}使用这种方式,元数据对象可以直接流向 Native 层的检测模型,避免了频繁跨 JNI 的像素或坐标序列化消耗,提供了极致的响应速度。
运行效果图如下:
6、总结
在 HarmonyOS 摄影与影像进化的新阶段,相机的底座调度走向了更深层、更敏捷的设计:
onCapturePhotoAvailable让应用摆脱了繁冗的数据链路转换,让 YUV 未压缩图的开发链路平滑而标准;- 全链路 HDR 动态照片不仅能实现更高饱和度与明暗细节的瞬间抓拍,更确保了附带短视频的色彩一致性;
- 实时元数据增删(
add/removeMetadataObjectTypes)则为复杂的视觉多算法共存场景提供了低延迟、零重闪的极速硬件切换底座。
这三项演进共同奠定了鸿蒙系统原生相机生态的全新视觉格局。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。