HarmonyOS开发中的多摄像头:多摄像头切换、前后摄像头切换、双摄协同、多摄预览、摄像头能力查询
核心要点:掌握 HarmonyOS 多摄像头管理机制,实现前后摄像头无缝切换、双摄协同拍摄、多摄同时预览,以及摄像头能力动态查询,打造专业级多摄相机应用。
一、背景与动机
你有没有想过,为什么现在手机后面要放三颗、四颗甚至五颗摄像头?
想象一下这个场景:你正在旅行,面前是一片壮丽的雪山风景。你掏出手机想拍下来——如果只有一颗摄像头,要么拍不全(广角不够),要么拍不清晰(长焦拉胯)。但有了超广角+主摄+长焦的组合,你就能轻松应对各种拍摄场景。
再想想视频通话的场景。你正用前置摄像头和朋友聊天,突然想给对方看看你身边的小猫,这时候就需要前后摄像头无缝切换——画面不能中断,切换要丝滑。
还有更酷的:双摄协同。主摄拍高清画面的同时,副摄采集景深信息,实时虚化背景,让你用手机也能拍出单反级的虚化效果。
HarmonyOS 提供了完整的 Camera Kit 多摄管理能力,让我们可以灵活地查询、切换、协同多个摄像头。今天就来深入聊聊这些能力怎么用。
二、核心原理
2.1 多摄像头架构总览
HarmonyOS 的相机框架采用了分层架构,从底层硬件抽象到上层应用接口,层层封装:
2.2 摄像头切换原理
摄像头切换的核心流程是:释放当前摄像头 → 打开目标摄像头 → 重新建立数据流。但这个过程如果处理不好,就会出现画面闪烁、黑屏等问题。HarmonyOS 提供了流复用机制,可以在切换时尽量复用已有的输出流,减少重建开销。
2.3 双摄协同原理
双摄协同是指同时打开两个摄像头,各自输出数据流,然后在应用层进行合成处理。典型场景包括:
- 景深虚化:主摄 + 景深摄像头,主摄提供画面,景深摄像头提供深度图
- 广角+长焦同框:超广角 + 长焦,实现画中画效果
- 前后同拍:前置 + 后置同时预览,适合 Vlog 拍摄
2.4 摄像头能力查询
不同设备的摄像头配置差异很大——有的只有前后两颗,有的有四颗。在开发多摄应用时,必须先查询设备支持哪些摄像头及其能力,而不是硬编码。Camera Manager 提供了完整的查询接口。
三、代码实战
3.1 摄像头能力查询:摸清家底
在开始任何相机操作之前,第一步永远是查询设备有哪些摄像头、每个摄像头支持什么能力。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 摄像头能力查询工具类
* 负责查询设备上所有摄像头的详细信息和支持的能力
*/
class CameraCapabilityQuery {
private cameraManager: camera.CameraManager | null = null;
/**
* 初始化相机管理器
*/
async initCameraManager(): Promise<void> {
try {
this.cameraManager = camera.getCameraManager(getContext(this));
console.info('[CameraQuery] 相机管理器初始化成功');
} catch (error) {
const err = error as BusinessError;
console.error(`[CameraQuery] 初始化失败: ${err.code} - ${err.message}`);
}
}
/**
* 查询所有摄像头设备
* 返回设备上可用的摄像头列表
*/
async queryAllCameras(): Promise<camera.CameraDevice[]> {
if (!this.cameraManager) {
await this.initCameraManager();
}
try {
const cameras = this.cameraManager!.getSupportedCameras();
console.info(`[CameraQuery] 检测到 ${cameras.length} 个摄像头`);
// 遍历打印每个摄像头的信息
cameras.forEach((cam, index) => {
console.info(`[CameraQuery] 摄像头 ${index}:`);
console.info(` - ID: ${cam.cameraId}`);
console.info(` - 位置: ${this.getCameraPositionLabel(cam.cameraPosition)}`);
console.info(` - 类型: ${this.getCameraTypeLabel(cam.cameraType)}`);
console.info(` - 连接类型: ${cam.connectionType}`);
});
return cameras;
} catch (error) {
const err = error as BusinessError;
console.error(`[CameraQuery] 查询摄像头失败: ${err.code} - ${err.message}`);
return [];
}
}
/**
* 查询指定摄像头的输出能力
* 包括支持的预览尺寸、拍照尺寸、视频尺寸等
*/
queryCameraOutputCapability(cameraDevice: camera.CameraDevice): camera.CameraOutputCapability | null {
if (!this.cameraManager) return null;
try {
const capability = this.cameraManager.getSupportedOutputCapability(cameraDevice);
console.info(`[CameraQuery] 摄像头 ${cameraDevice.cameraId} 的输出能力:`);
// 预览能力
const previewProfiles = capability.previewProfiles;
console.info(` - 支持预览配置: ${previewProfiles.length} 种`);
previewProfiles.forEach((profile, i) => {
console.info(` [${i}] ${profile.size.width}x${profile.size.height}, FPS: ${profile.frameRateRange.min}-${profile.frameRateRange.max}`);
});
// 拍照能力
const photoProfiles = capability.photoProfiles;
console.info(` - 支持拍照配置: ${photoProfiles.length} 种`);
// 视频能力
const videoProfiles = capability.videoProfiles;
console.info(` - 支持视频配置: ${videoProfiles.length} 种`);
return capability;
} catch (error) {
const err = error as BusinessError;
console.error(`[CameraQuery] 查询输出能力失败: ${err.code} - ${err.message}`);
return null;
}
}
/**
* 判断是否支持双摄协同
* 检查设备是否可以同时打开多个摄像头
*/
async checkDualCameraSupport(): Promise<boolean> {
if (!this.cameraManager) return false;
try {
const cameras = this.cameraManager.getSupportedCameras();
// 至少需要两个摄像头才可能支持双摄
if (cameras.length < 2) {
console.info('[CameraQuery] 设备摄像头数量不足,不支持双摄协同');
return false;
}
// 尝试同时创建两个 camera input 来验证
// 实际支持情况需要通过 isCameraMuted 等接口或实际打开来确认
const hasFront = cameras.some(c => c.cameraPosition === camera.CameraPosition.CAMERA_POSITION_FRONT);
const hasBack = cameras.some(c => c.cameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK);
if (hasFront && hasBack) {
console.info('[CameraQuery] 设备同时拥有前后摄像头,可能支持双摄协同');
return true;
}
// 检查是否有多个后置摄像头
const backCameras = cameras.filter(c => c.cameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK);
if (backCameras.length >= 2) {
console.info('[CameraQuery] 设备拥有多个后置摄像头,可能支持双摄协同');
return true;
}
return false;
} catch (error) {
console.error('[CameraQuery] 双摄支持检查失败');
return false;
}
}
/** 获取摄像头位置标签 */
private getCameraPositionLabel(position: camera.CameraPosition): string {
const labels: Map<number, string> = new Map([
[camera.CameraPosition.CAMERA_POSITION_FRONT, '前置'],
[camera.CameraPosition.CAMERA_POSITION_BACK, '后置'],
[camera.CameraPosition.CAMERA_POSITION_UNSPECIFIED, '未指定'],
]);
return labels.get(position) ?? '未知';
}
/** 获取摄像头类型标签 */
private getCameraTypeLabel(type: camera.CameraType): string {
const labels: Map<number, string> = new Map([
[camera.CameraType.CAMERA_TYPE_WIDE_ANGLE, '广角'],
[camera.CameraType.CAMERA_TYPE_ULTRA_WIDE, '超广角'],
[camera.CameraType.CAMERA_TYPE_TELEPHOTO, '长焦'],
[camera.CameraType.CAMERA_TYPE_TRUE_DEPTH, '深感'],
[camera.CameraType.CAMERA_TYPE_DEFAULT, '默认'],
]);
return labels.get(type) ?? '未知';
}
}3.2 前后摄像头切换:丝滑不卡顿
这是最常见的需求——视频通话时切换前后摄像头,要求画面不中断、切换流畅。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 摄像头切换管理器
* 实现前后摄像头无缝切换,支持切换动画和状态回调
*/
class CameraSwitchManager {
private cameraManager: camera.CameraManager | null = null;
private currentCameraInput: camera.CameraInput | null = null;
private currentCameraDevice: camera.CameraDevice | null = null;
private previewOutput: camera.PreviewOutput | null = null;
private photoOutput: camera.PhotoOutput | null = null;
private captureSession: camera.CaptureSession | null = null;
private surfaceId: string = '';
private isSwitching: boolean = false;
/** 切换状态回调 */
onSwitchStart?: () => void;
onSwitchComplete?: (isFront: boolean) => void;
onSwitchError?: (error: BusinessError) => void;
/**
* 初始化相机管理器和会话
*/
async init(surfaceId: string): Promise<void> {
this.surfaceId = surfaceId;
try {
// 获取相机管理器
this.cameraManager = camera.getCameraManager(getContext(this));
// 默认打开后置摄像头
const cameras = this.cameraManager.getSupportedCameras();
const backCamera = cameras.find(
c => c.cameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK
);
if (backCamera) {
await this.openCamera(backCamera);
console.info('[CameraSwitch] 初始化完成,默认使用后置摄像头');
}
} catch (error) {
const err = error as BusinessError;
console.error(`[CameraSwitch] 初始化失败: ${err.code} - ${err.message}`);
}
}
/**
* 打开指定摄像头并建立预览流
*/
private async openCamera(cameraDevice: camera.CameraDevice): Promise<void> {
if (!this.cameraManager) return;
// 创建 Camera Input
this.currentCameraInput = this.cameraManager.createCameraInput(cameraDevice);
await this.currentCameraInput.open();
this.currentCameraDevice = cameraDevice;
// 获取输出能力
const outputCapability = this.cameraManager.getSupportedOutputCapability(cameraDevice);
// 创建预览输出
const previewProfile = outputCapability.previewProfiles[0];
this.previewOutput = this.cameraManager.createPreviewOutput(previewProfile, this.surfaceId);
// 创建拍照输出
const photoProfile = outputCapability.photoProfiles[0];
this.photoOutput = this.cameraManager.createPhotoOutput(photoProfile);
// 创建捕获会话并配置
this.captureSession = this.cameraManager.createCaptureSession();
this.captureSession.beginConfig();
this.captureSession.addInput(this.currentCameraInput);
this.captureSession.addOutput(this.previewOutput);
// 提交配置并启动
await this.captureSession.commitConfig();
await this.captureSession.start();
console.info(`[CameraSwitch] 摄像头 ${cameraDevice.cameraId} 已打开并开始预览`);
}
/**
* 切换前后摄像头
* 核心流程:停止会话 → 释放旧输入 → 创建新输入 → 重新配置会话
*/
async switchCamera(): Promise<void> {
// 防止重复切换
if (this.isSwitching) {
console.warn('[CameraSwitch] 正在切换中,请勿重复操作');
return;
}
if (!this.cameraManager || !this.currentCameraDevice || !this.captureSession) {
console.error('[CameraSwitch] 相机未初始化');
return;
}
this.isSwitching = true;
this.onSwitchStart?.();
try {
// 确定目标摄像头
const isCurrentFront = this.currentCameraDevice.cameraPosition === camera.CameraPosition.CAMERA_POSITION_FRONT;
const targetPosition = isCurrentFront
? camera.CameraPosition.CAMERA_POSITION_BACK
: camera.CameraPosition.CAMERA_POSITION_FRONT;
const cameras = this.cameraManager.getSupportedCameras();
const targetCamera = cameras.find(c => c.cameraPosition === targetPosition);
if (!targetCamera) {
console.error('[CameraSwitch] 找不到目标摄像头');
this.isSwitching = false;
return;
}
console.info(`[CameraSwitch] 开始切换: ${isCurrentFront ? '前置→后置' : '后置→前置'}`);
// 第一步:停止当前会话
await this.captureSession.stop();
// 第二步:开始重新配置
this.captureSession.beginConfig();
// 第三步:移除旧输入
if (this.currentCameraInput) {
this.captureSession.removeInput(this.currentCameraInput);
this.currentCameraInput.close();
}
// 第四步:创建新输入
this.currentCameraInput = this.cameraManager.createCameraInput(targetCamera);
await this.currentCameraInput.open();
this.currentCameraDevice = targetCamera;
this.captureSession.addInput(this.currentCameraInput);
// 第五步:提交配置并启动
await this.captureSession.commitConfig();
await this.captureSession.start();
const isNowFront = targetPosition === camera.CameraPosition.CAMERA_POSITION_FRONT;
this.onSwitchComplete?.(isNowFront);
console.info('[CameraSwitch] 切换完成');
} catch (error) {
const err = error as BusinessError;
console.error(`[CameraSwitch] 切换失败: ${err.code} - ${err.message}`);
this.onSwitchError?.(err);
// 切换失败时尝试恢复
try {
await this.captureSession.start();
} catch (e) {
console.error('[CameraSwitch] 恢复预览失败');
}
} finally {
this.isSwitching = false;
}
}
/**
* 获取当前是否为前置摄像头
*/
get isFrontCamera(): boolean {
return this.currentCameraDevice?.cameraPosition === camera.CameraPosition.CAMERA_POSITION_FRONT;
}
/**
* 释放所有资源
*/
async release(): Promise<void> {
try {
if (this.captureSession) {
await this.captureSession.stop();
this.captureSession.release();
}
if (this.currentCameraInput) {
this.currentCameraInput.close();
}
if (this.previewOutput) {
this.previewOutput.release();
}
if (this.photoOutput) {
this.photoOutput.release();
}
console.info('[CameraSwitch] 资源已释放');
} catch (error) {
console.error('[CameraSwitch] 释放资源失败');
}
}
}3.3 双摄协同:景深虚化效果
双摄同时工作,一个拍画面一个拍景深,实时合成虚化效果。这是手机人像模式的核心技术。
import { camera } from '@kit.CameraKit';
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 双摄协同管理器
* 同时管理主摄和景深摄像头,实现实时景深虚化
*/
class DualCameraManager {
private cameraManager: camera.CameraManager | null = null;
private mainCameraInput: camera.CameraInput | null = null;
private depthCameraInput: camera.CameraInput | null = null;
private mainPreviewOutput: camera.PreviewOutput | null = null;
private depthPreviewOutput: camera.PreviewOutput | null = null;
private captureSession: camera.CaptureSession | null = null;
/** 虚化强度 0-1 */
private blurIntensity: number = 0.5;
/**
* 初始化双摄协同
* @param mainSurfaceId 主摄预览 Surface ID
* @param depthSurfaceId 景深摄预览 Surface ID
*/
async init(mainSurfaceId: string, depthSurfaceId: string): Promise<boolean> {
try {
this.cameraManager = camera.getCameraManager(getContext(this));
const cameras = this.cameraManager.getSupportedCameras();
// 查找后置主摄
const mainCamera = cameras.find(c =>
c.cameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK &&
c.cameraType === camera.CameraType.CAMERA_TYPE_WIDE_ANGLE
);
// 查找景深摄像头
const depthCamera = cameras.find(c =>
c.cameraType === camera.CameraType.CAMERA_TYPE_TRUE_DEPTH
);
if (!mainCamera) {
console.error('[DualCamera] 找不到后置主摄');
return false;
}
// 创建主摄输入
this.mainCameraInput = this.cameraManager.createCameraInput(mainCamera);
await this.mainCameraInput.open();
// 获取主摄输出能力并创建预览
const mainCapability = this.cameraManager.getSupportedOutputCapability(mainCamera);
const mainPreviewProfile = mainCapability.previewProfiles[0];
this.mainPreviewOutput = this.cameraManager.createPreviewOutput(mainPreviewProfile, mainSurfaceId);
// 创建捕获会话
this.captureSession = this.cameraManager.createCaptureSession();
this.captureSession.beginConfig();
this.captureSession.addInput(this.mainCameraInput);
this.captureSession.addOutput(this.mainPreviewOutput);
// 如果有景深摄像头,添加到同一会话
if (depthCamera) {
this.depthCameraInput = this.cameraManager.createCameraInput(depthCamera);
await this.depthCameraInput.open();
const depthCapability = this.cameraManager.getSupportedOutputCapability(depthCamera);
const depthPreviewProfile = depthCapability.previewProfiles[0];
this.depthPreviewOutput = this.cameraManager.createPreviewOutput(depthPreviewProfile, depthSurfaceId);
this.captureSession.addInput(this.depthCameraInput);
this.captureSession.addOutput(this.depthPreviewOutput);
console.info('[DualCamera] 双摄协同模式已启用');
} else {
console.warn('[DualCamera] 未找到景深摄像头,仅使用单摄模式');
}
await this.captureSession.commitConfig();
await this.captureSession.start();
return true;
} catch (error) {
const err = error as BusinessError;
console.error(`[DualCamera] 初始化失败: ${err.code} - ${err.message}`);
return false;
}
}
/**
* 设置虚化强度
* @param intensity 虚化强度 0.0 - 1.0
*/
setBlurIntensity(intensity: number): void {
this.blurIntensity = Math.max(0, Math.min(1, intensity));
console.info(`[DualCamera] 虚化强度设置为: ${this.blurIntensity}`);
}
/**
* 获取当前虚化强度
*/
getBlurIntensity(): number {
return this.blurIntensity;
}
/**
* 拍照并合成虚化效果
* 将主摄照片与景深信息合成,生成虚化照片
*/
async captureWithBlur(): Promise<image.PixelMap | null> {
if (!this.mainPreviewOutput) {
console.error('[DualCamera] 预览未启动');
return null;
}
try {
// 这里简化处理,实际需要通过 PhotoOutput 获取照片
// 然后结合景深数据进行虚化处理
console.info('[DualCamera] 正在拍摄虚化照片...');
// 实际项目中,虚化处理通常使用 NDK 或 GPU 算子
// 此处仅展示流程框架
return null; // 返回合成后的 PixelMap
} catch (error) {
const err = error as BusinessError;
console.error(`[DualCamera] 拍照失败: ${err.code} - ${err.message}`);
return null;
}
}
/**
* 释放双摄资源
*/
async release(): Promise<void> {
try {
if (this.captureSession) {
await this.captureSession.stop();
this.captureSession.release();
}
if (this.mainCameraInput) {
this.mainCameraInput.close();
}
if (this.depthCameraInput) {
this.depthCameraInput.close();
}
console.info('[DualCamera] 资源已释放');
} catch (error) {
console.error('[DualCamera] 释放资源失败');
}
}
}3.4 多摄预览 UI 组件:画中画效果
在 Vlog 场景中,经常需要同时预览前后摄像头——大画面显示后置,小窗口显示前置。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@Component
struct MultiCameraPreviewPage {
// 主预览 Surface ID(后置)
private mainSurfaceId: string = '';
// 小窗预览 Surface ID(前置)
private pipSurfaceId: string = '';
private cameraManager: camera.CameraManager | null = null;
private captureSession: camera.CaptureSession | null = null;
private isRecording: boolean = false;
private showPip: boolean = true;
@State currentMode: string = '后置主摄';
@State isFrontMain: boolean = false; // 前置是否为主画面
aboutToAppear(): void {
this.initCameras();
}
/**
* 初始化双摄预览
*/
async initCameras(): Promise<void> {
try {
this.cameraManager = camera.getCameraManager(getContext(this));
const cameras = this.cameraManager.getSupportedCameras();
// 后置主摄
const backCamera = cameras.find(c =>
c.cameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK
);
// 前置摄像头
const frontCamera = cameras.find(c =>
c.cameraPosition === camera.CameraPosition.CAMERA_POSITION_FRONT
);
if (!backCamera || !frontCamera) {
console.error('[MultiPreview] 设备不支持前后双摄');
return;
}
// 创建后置输入和预览
const backInput = this.cameraManager.createCameraInput(backCamera);
await backInput.open();
const backCapability = this.cameraManager.getSupportedOutputCapability(backCamera);
const backPreviewProfile = backCapability.previewProfiles[0];
// 创建前置输入和预览
const frontInput = this.cameraManager.createCameraInput(frontCamera);
await frontInput.open();
const frontCapability = this.cameraManager.getSupportedOutputCapability(frontCamera);
const frontPreviewProfile = frontCapability.previewProfiles[0];
// 创建双摄会话
this.captureSession = this.cameraManager.createCaptureSession();
this.captureSession.beginConfig();
this.captureSession.addInput(backInput);
this.captureSession.addInput(frontInput);
// 等待 Surface ID 就绪后再创建输出
// 注意:实际开发中需要确保 XComponent 的 surfaceId 已获取
console.info('[MultiPreview] 双摄预览初始化完成');
} catch (error) {
const err = error as BusinessError;
console.error(`[MultiPreview] 初始化失败: ${err.code} - ${err.message}`);
}
}
build() {
Column() {
// 标题栏
Row() {
Text('多摄预览')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
Blank()
Text(this.currentMode)
.fontSize(14)
.fontColor('#AAAAAA')
}
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
// 主预览区域
Stack() {
// 后置主摄预览(大画面)
XComponent({
id: 'mainCameraPreview',
type: XComponentType.SURFACE,
libraryname: ''
})
.width('100%')
.aspectRatio(3 / 4)
.borderRadius(12)
.onLoad(() => {
// 获取 surfaceId 并启动预览
console.info('[MultiPreview] 主预览 XComponent 加载完成');
})
// 前置小窗预览(画中画)
if (this.showPip) {
Column() {
XComponent({
id: 'pipCameraPreview',
type: XComponentType.SURFACE,
libraryname: ''
})
.width(120)
.height(160)
.borderRadius(8)
.border({ width: 2, color: '#FFFFFF' })
// 小窗切换按钮
Button() {
Text('切换')
.fontSize(10)
.fontColor('#FFFFFF')
}
.width(50)
.height(24)
.backgroundColor('#80000000')
.borderRadius(12)
.margin({ top: 4 })
.onClick(() => {
this.isFrontMain = !this.isFrontMain;
this.currentMode = this.isFrontMain ? '前置主摄' : '后置主摄';
})
}
.position({ x: '70%', y: 16 })
.alignItems(HorizontalAlign.Center)
}
}
.width('100%')
.layoutWeight(1)
// 底部控制栏
Row() {
// 画中画开关
Button() {
Text(this.showPip ? '关闭画中画' : '开启画中画')
.fontSize(14)
.fontColor('#FFFFFF')
}
.backgroundColor(this.showPip ? '#4FC3F7' : '#555555')
.borderRadius(20)
.onClick(() => {
this.showPip = !this.showPip;
})
Blank()
// 录制按钮
Button() {
Text(this.isRecording ? '停止' : '录制')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
}
.width(64)
.height(64)
.borderRadius(32)
.backgroundColor(this.isRecording ? '#EF5350' : '#FFFFFF')
.onClick(() => {
this.isRecording = !this.isRecording;
})
Blank()
// 前后切换
Button() {
Text('翻转')
.fontSize(14)
.fontColor('#FFFFFF')
}
.backgroundColor('#555555')
.borderRadius(20)
.onClick(() => {
this.isFrontMain = !this.isFrontMain;
this.currentMode = this.isFrontMain ? '前置主摄' : '后置主摄';
})
}
.width('100%')
.padding({ left: 24, right: 24, top: 16, bottom: 32 })
}
.width('100%')
.height('100%')
.backgroundColor('#000000')
}
}四、踩坑与注意事项
4.1 切换时的黑屏闪烁
问题:前后摄像头切换时,画面会出现短暂黑屏或闪烁。
原因:切换过程需要释放旧摄像头、打开新摄像头、重建数据流,这个过程需要时间。
解决方案:
- 在切换开始时显示最后一帧的截图作为过渡
- 使用
beginConfig()→removeInput()→addInput()→commitConfig()的方式复用会话,而非完全重建 - 切换期间禁用按钮,防止用户连续点击
4.2 双摄同时打开的限制
问题:不是所有设备都支持同时打开两个摄像头。
原因:硬件 ISP 通道有限,低端设备可能只支持一个摄像头同时工作。
解决方案:
- 在打开第二个摄像头前,先通过
getSupportedCameras()查询设备能力 - 如果
createCameraInput()抛出异常,说明不支持双摄,需要降级为单摄模式 - 在 UI 上给出明确提示,不要让用户误以为功能坏了
4.3 Surface ID 时序问题
问题:创建 PreviewOutput 时需要 Surface ID,但 XComponent 的 onLoad 回调可能还没执行。
解决方案:
- 在
XComponent.onLoad()回调中获取 surfaceId 并保存 - 确保在 surfaceId 获取后再创建 PreviewOutput
- 使用 Promise 或回调机制保证时序正确
4.4 摄像头权限与隐私
问题:多摄应用需要 CAMERA 权限,但用户可能拒绝。
解决方案:
- 在
module.json5中声明ohos.permission.CAMERA权限 - 运行时通过
abilityAccessCtrl请求权限 - 权限被拒绝时,展示友好的引导页面,说明为什么需要这个权限
4.5 后台切换摄像头
问题:应用切换到后台再回来,摄像头可能已经释放。
解决方案:
- 在
onPageShow()中重新检查摄像头状态 - 如果摄像头已释放,重新初始化
- 在
onPageHide()中主动释放资源,避免后台占用摄像头
五、HarmonyOS 6 适配
5.1 API 变更
| 功能 | HarmonyOS 5 | HarmonyOS 6 |
|---|---|---|
| 相机管理器获取 | camera.getCameraManager() | 同上,但增加了异步版本camera.getCameraManagerAsync() |
| 多摄同时预览 | 通过CaptureSession 添加多个 Input | 新增MultiCameraSession 专用类,更简洁 |
| 摄像头能力查询 | getSupportedOutputCapability() | 新增getSupportedOutputCapabilityV2(),返回更详细的能力信息 |
| 切换回调 | 无专用回调 | 新增onCameraSwitchComplete 回调 |
5.2 迁移指南
// HarmonyOS 5 写法
const cameraManager = camera.getCameraManager(context);
// HarmonyOS 6 推荐写法
const cameraManager = await camera.getCameraManagerAsync(context);
// 异步获取,避免阻塞主线程5.3 新增特性
- 智能摄像头选择:HarmonyOS 6 新增
CameraSelectorAPI,可以根据拍摄场景自动推荐最佳摄像头组合 - 低延迟切换:切换延迟从 300ms 优化到 100ms 以内
- 双摄同步增强:前后摄像头帧同步精度提升,适合双摄直播场景
六、总结
核心知识点回顾:
- 能力查询是第一步:永远先查询设备有哪些摄像头、支持什么能力,不要硬编码摄像头 ID
- 切换要复用会话:通过
beginConfig()→removeInput()→addInput()→commitConfig()复用 CaptureSession,减少切换延迟 - 双摄需要硬件支持:不是所有设备都能同时开两个摄像头,要做好降级方案
- Surface ID 时序很重要:确保 XComponent 加载完成后再创建 PreviewOutput
- 权限和生命周期管理:正确处理权限请求、后台切换、资源释放
多摄像头开发是相机应用的核心能力之一。掌握了这些,你就能开发出像系统相机一样专业的多摄应用了!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。