在传统智能手机影像系统中,光圈通常是一个固定死的硬件参数。开发者想要实现背景虚化效果,只能依赖多摄测距加AI抠图的方式去模拟。随着华为Pura和Mate系列引入真实机械可变光圈模组,物理叶片的开合让移动端真正拥有了单反级的物理虚化能力。在HarmonyOS 6.1.1(API 24)中,这一底层机械马达控制能力通过Camera Kit正式对开发者开放,既支持ArkTS接口调用,也支持C API极速驱动。
一、物理光圈能力概览
HarmonyOS NEXT 6.1.1的Camera Kit将物理光圈控制完全纳入PhotoSession体系。开发者在ArkTS层可以拿到三个核心接口:getSupportedPhysicalApertures()用于查询设备支持的物理光圈档位列表,返回类型为Array<camera.PhysicalAperture>;getPhysicalAperture()获取当前物理光圈值;setPhysicalAperture(aperture: number)直接驱动机械马达,改变物理叶片收口。
一个容易踩坑的细节是:调用setPhysicalAperture之前,设置的值必须来自getSupportedPhysicalApertures()返回的列表,否则会引发系统异常或抛出7400102错误码。对于固定光圈机型,getSupportedPhysicalApertures()只会返回一个默认值,这一点可以作为运行时能力检测的依据。
在C/C++ Native层,Camera Kit给出了四个原子级接口,且对内存管理极其严格。OH_CaptureSession_GetPhysicalAperture和OH_CaptureSession_SetPhysicalAperture是简单的数值存取接口,使用double类型传递光圈值。而OH_CaptureSession_GetSupportedPhysicalApertures则使用OH_Camera_PhysicalAperture**双指针承接底层分配的物理光圈列表,同时用uint32_t* size输出列表长度。这个接口背后存在底层内存分配,使用完必须有OH_CaptureSession_DeletePhysicalApertures成对释放,否则会造成系统级内存泄漏。
二、ArkTS实现物理光圈控制
以下示例代码构建了一个物理光圈控制演示页面,完整展示了探测硬件光圈能力、切换机械光圈档位以及模拟Native层内存管理验证的过程。
- import { camera } from '@kit.CameraKit';
- import { BusinessError } from '@kit.BasicServicesKit';
- import { router } from '@kit.ArkUI';
- @Entry
- @Component
- struct CameraApertureDemo {
- @State logs: string[] = [];
- @State supportedApertures: number[] = [];
- @State currentAperture: number = 0;
- private appendLog(msg: string): void {
- let now = new Date();
- let timeStr = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}.${now.getMilliseconds()}`;
- this.logs.unshift(`[${timeStr}] ${msg}`);
- }
- // 1. 查询设备硬件支持的光圈档位
- queryApertures(): void {
- this.appendLog('正在探测机械光圈叶片物理能力...');
- try {
- // 模拟调用:photoSession.getSupportedPhysicalApertures()
- let mockApertures = [1.4, 2.0, 2.8, 4.0];
- this.supportedApertures = mockApertures;
- // 模拟调用:photoSession.getPhysicalAperture()
- this.currentAperture = 1.4;
- this.appendLog(`探测成功!当前镜头支持的光圈档位: ${JSON.stringify(this.supportedApertures)}`);
- this.appendLog(`当前生效的物理光圈: F${this.currentAperture.toFixed(1)}`);
- } catch (e) {
- this.appendLog(`探测失败: ${(e as BusinessError).code}`);
- }
- }
- // 2. 驱动机械马达改变叶片
- changeAperture(target: number): void {
- this.appendLog(`正在向VCM马达发送指令,驱动物理叶片至F${target.toFixed(1)} ...`);
- try {
- // 模拟调用:photoSession.setPhysicalAperture(target)
- this.currentAperture = target;
- this.appendLog(`机械光圈切换完毕,当前光圈: F${this.currentAperture.toFixed(1)}`);
- } catch (e) {
- this.appendLog(`光圈切换失败: ${(e as BusinessError).code}`);
- }
- }
- // 3. 模拟C API双指针释放流程
- testNativeApertureAPI(): void {
- this.appendLog('[Native 模拟] 验证C API底层双指针管控防泄漏流程...');
- setTimeout(() => {
- this.appendLog('OH_CaptureSession_GetSupportedPhysicalApertures -> 分配C层Array');
- this.appendLog('解析 OH_Camera_PhysicalAperture** 成功,size = 4');
- this.appendLog('OH_CaptureSession_SetPhysicalAperture(2.8) -> CAMERA_OK');
- this.appendLog('准备归还底层内存池...');
- this.appendLog('OH_CaptureSession_DeletePhysicalApertures -> 已销毁');
- this.appendLog('[Native] C层物理光圈句柄安全释放。');
- }, 600);
- }
- build() {
- Column() {
- // 头部导航栏
- Row() {
- Image($r('app.media.startIcon')).width(24).height(24).onClick(() => router.back())
- Text('物理光圈控制').fontSize(18).fontWeight(FontWeight.Bold).margin({ left: 10 })
- }.width('100%').padding(20).backgroundColor(Color.White)
- // 操作区
- Column({ space: 15 }) {
- Button('获取硬件支持的光圈叶片档位', { type: ButtonType.Normal })
- .width('100%').height(45).borderRadius(8).backgroundColor('#3B82F6')
- .onClick(() => this.queryApertures())
- if (this.supportedApertures.length > 0) {
- Text('快捷驱动机械马达:').fontSize(14).fontColor('#666').alignSelf(ItemAlign.Start)
- Row({ space: 10 }) {
- ForEach(this.supportedApertures, (ap: number) => {
- Button(`F${ap.toFixed(1)}`, { type: ButtonType.Normal })
- .height(40).layoutWeight(1)
- .backgroundColor(this.currentAperture === ap ? '#EF4444' : '#E2E8F0')
- .fontColor(this.currentAperture === ap ? Color.White : '#333')
- .borderRadius(6)
- .onClick(() => this.changeAperture(ap))
- })
- }.width('100%')
- }
- Button('触发Native指令(含内存销毁)', { type: ButtonType.Normal })
- .width('100%').height(45).borderRadius(8).backgroundColor('#059669')
- .onClick(() => this.testNativeApertureAPI())
- }.padding(20)
- // 日志控制台
- Column() {
- Text('控制台 Console')
- .fontSize(14).fontWeight(FontWeight.Bold).fontColor('#666').margin({ bottom: 10 }).alignSelf(ItemAlign.Start)
- List({ space: 8 }) {
- ForEach(this.logs, (item: string) => {
- ListItem() { Text(item).fontSize(12).fontColor('#333').fontFamily('monospace').width('100%') }
- })
- }
- .width('100%').layoutWeight(1).backgroundColor('#F8FAFC').borderRadius(8).padding(10)
- }.padding({ left: 20, right: 20, bottom: 20 }).layoutWeight(1).width('100%')
- }
- .width('100%').height('100%').backgroundColor(Color.White)
- }
- }
复制代码
在实际项目中接入这段代码时,需要把模拟调用替换为真实的photoSession实例方法,并确保Page已在main_pages.json中完成路由注册。
三、C API双指针内存治理
Native层的光圈控制是本次能力开放中最需要警惕的部分。OH_CaptureSession_GetSupportedPhysicalApertures会在底层malloc物理内存,将光圈列表通过双指针传出。如果开发者在渲染循环里逐帧探测却不配对调用OH_CaptureSession_DeletePhysicalApertures,应用会迅速被OOM机制强杀。
正确用法是:获取光圈列表后立即将所需数值拷贝到自有内存,随后第一时间调用OH_CaptureSession_DeletePhysicalApertures释放底层指针。不要把底层返回的指针长期保存,更不要试图跨Session复用。
四、典型问题排查
定焦镜头异常方面,不支持机械光圈的普通定焦机型,如果强行调用setPhysicalAperture会抛出7400102错误。实际开发中建议先调用getSupportedPhysicalApertures获取数组长度,如果长度仅为1且该值与默认光圈一致,则说明设备不具备可变光圈能力,此时应将光圈切换入口隐藏或置灰。
Session状态方面,所有涉及底层的物理设置,无论是光圈、对焦还是ISO,大前提都是PhotoSession已经配置完毕且处于正常激活状态。在Session未就绪时调用物理光圈接口,即使参数合法也可能出现无法预期的行为。建议在Session状态回调确认onConfigurationChanged或等效信号到达后再执行光圈操作。
五、总结
HarmonyOS 6.1.1的Camera Kit将机械可变光圈控制能力开放给上层应用,使得开发者可以基于ArkTS或C API直接驱动物理叶片,实现真单反级的景深控制。在享受这一底层能力的同时,务必重视三个关键点:设置值必须来自Supported列表、C API双指针必须配对释放、Session必须处于激活状态。只有做到这三点,才能让物理光圈功能稳定可靠地运行在鸿蒙生态的相机应用中。 |