目录
[1 Camera Kit简介](#1 Camera Kit简介)
[1.1 开发模型](#1.1 开发模型)
[2 开发准备](#2 开发准备)
[2.1 申请权限](#2.1 申请权限)
[2.2 开发指导](#2.2 开发指导)
[3. 相机开发](#3. 相机开发)
[3.1 相机管理(ArkTS)](#3.1 相机管理(ArkTS))
[3.1.1 开发步骤](#3.1.1 开发步骤)
[3.1.2 状态监听](#3.1.2 状态监听)
[3.2 设备输入(ArkTS)](#3.2 设备输入(ArkTS))
[3.2.1 开发步骤](#3.2.1 开发步骤)
[3.3 会话管理(ArkTS)](#3.3 会话管理(ArkTS))
[3.3.1 开发步骤](#3.3.1 开发步骤)
[3.4 预览(ArkTS)](#3.4 预览(ArkTS))
[3.4.1 开发步骤](#3.4.1 开发步骤)
[3.4.2 状态监听](#3.4.2 状态监听)
[3.5 拍照(ArkTS)](#3.5 拍照(ArkTS))
[3.5.1 开发步骤](#3.5.1 开发步骤)
[3.5.2 状态监听](#3.5.2 状态监听)
[3.6 录像(ArkTS)](#3.6 录像(ArkTS))
[3.6.1 开发步骤](#3.6.1 开发步骤)
[3.6.2 状态监听](#3.6.2 状态监听)
[3.7 元数据(ArkTS)](#3.7 元数据(ArkTS))
[3.7.1 开发步骤](#3.7.1 开发步骤)
[3.7.2 状态监听](#3.7.2 状态监听)
[3.8 对焦(ArkTS)](#3.8 对焦(ArkTS))
[3.8.1 开发步骤](#3.8.1 开发步骤)
[3.8.2 状态监听](#3.8.2 状态监听)
[3.9 手电筒使用(ArkTS)](#3.9 手电筒使用(ArkTS))
[3.9.1 开发步骤](#3.9.1 开发步骤)
[3.9.2 状态监听](#3.9.2 状态监听)
[3.10 适配不同折叠状态的摄像头变更(ArkTS)](#3.10 适配不同折叠状态的摄像头变更(ArkTS))
[3.10.1 创建XComponent](#3.10.1 创建XComponent)
[3.10.2 获取设备折叠状态](#3.10.2 获取设备折叠状态)
[3.11 分段式拍照(ArkTS)](#3.11 分段式拍照(ArkTS))
[3.11.1 开发步骤](#3.11.1 开发步骤)
[3.11.2 状态监听](#3.11.2 状态监听)
[3.12 动态照片(ArkTS)](#3.12 动态照片(ArkTS))
[3.12.1 开发步骤](#3.12.1 开发步骤)
[3.12.2 状态监听](#3.12.2 状态监听)
[3.13 相机基础动效(ArkTS)](#3.13 相机基础动效(ArkTS))
[3.13.1 闪黑动效](#3.13.1 闪黑动效)
[3.13.2 模糊动效](#3.13.2 模糊动效)
[3.14 在Worker线程中使用相机(ArkTS)](#3.14 在Worker线程中使用相机(ArkTS))
[3.14.1 开发步骤](#3.14.1 开发步骤)
[3.14.2 trace对比](#3.14.2 trace对比)
[3.15 适配相机旋转角度(ArkTS)](#3.15 适配相机旋转角度(ArkTS))
[3.15.1 创建会话](#3.15.1 创建会话)
[3.15.2 预览](#3.15.2 预览)
[3.15.3 录像](#3.15.3 录像)
[3.15.4 计算设备旋转角度](#3.15.4 计算设备旋转角度)
[3.16 安全相机(ArkTS)](#3.16 安全相机(ArkTS))
[3.16.1 开发步骤](#3.16.1 开发步骤)
[3.17 动态调整预览帧率(ArkTS)](#3.17 动态调整预览帧率(ArkTS))
[3.17.1 约束与限制](#3.17.1 约束与限制)
[3.17.2 开发流程](#3.17.2 开发流程)
[3.17.3 创建Session会话并指定模式](#3.17.3 创建Session会话并指定模式)
[3.17.4 调整帧率](#3.17.4 调整帧率)
[3.18 使用相机预配置(ArkTS)](#3.18 使用相机预配置(ArkTS))
[3.18.1 规格说明](#3.18.1 规格说明)
[3.18.2 开发步骤](#3.18.2 开发步骤)
1 Camera Kit简介
通过调用Camera Kit(相机服务)提供的接口可以开发相机应用,应用通过访问和操作相机硬件,实现基础操作,如预览、拍照和录像;还可以通过接口组合完成更多操作,如控制闪光灯和曝光时间、对焦或调焦等。
1.1 开发模型
相机调用摄像头采集、加工图像视频数据,精确控制对应的硬件,灵活输出图像、视频内容,满足多镜头硬件适配(如广角、长焦、TOF)、多业务场景适配(如不同分辨率、不同格式、不同效果)的要求。
相机的工作流程如图所示,可概括为相机输入设备管理、会话管理和相机输出管理三部分。
相机设备调用摄像头采集数据,作为相机输入流。
会话管理可配置输入流,即选择哪些镜头进行拍摄。另外还可以配置闪光灯、曝光时间、对焦和调焦等参数,实现不同效果的拍摄,从而适配不同的业务场景。应用可以通过切换会话满足不同场景的拍摄需求。
配置相机的输出流,即将内容以预览流、拍照流或视频流输出。
图1 相机工作流程

图2 相机开发模型

相机应用通过控制相机,实现图像显示(预览)、照片保存(拍照)、视频录制(录像)等基础操作。在实现基本操作过程中,相机服务会控制相机设备采集和输出数据,采集的图像数据在相机底层的设备硬件接口(HDI,Hardware Device Interfaces),直接通过BufferQueue传递到具体的功能模块进行处理。BufferQueue在应用开发中无需关注,用于将底层处理的数据及时送到上层进行图像显示。
以视频录制为例进行说明,相机应用在录制视频过程中,媒体录制服务先创建一个视频Surface用于传递数据,并提供给相机服务,相机服务可控制相机设备采集视频数据,生成视频流。采集的数据通过底层相机HDI处理后,通过Surface将视频流传递给媒体录制服务,媒体录制服务对视频数据进行处理后,保存为视频文件,完成视频录制。
2 开发准备
相机应用开发的主要流程包含开发准备、设备输入、会话管理、预览、拍照和录像等。
2.1 申请权限
在开发相机应用时,需要先申请相机相关权限,确保应用拥有访问相机硬件及其他功能的权限,需要的权限如下表。
- 使用相机拍摄前,需要申请ohos.permission.CAMERA相机权限。
- 当需要使用麦克风同时录制音频时,需要申请ohos.permission.MICROPHONE麦克风权限。
- 当需要拍摄的图片/视频显示地理位置信息时,需要申请ohos.permission.MEDIA_LOCATION,来访问用户媒体文件中的地理位置信息。
以上权限均需要通过弹窗向用户申请授权,具体申请方式及校验方式,请参考向用户申请授权。
- 当需要读取图片或视频文件时,请优先使用媒体库Picker选择媒体资源。
- 当需要保存图片或视频文件时,请优先使用安全控件保存媒体资源。
说明仅应用需要克隆、备份或同步用户公共目录的图片、视频类文件时,可申请ohos.permission.READ_IMAGEVIDEO、ohos.permission.WRITE_IMAGEVIDEO权限来读写音频文件,申请方式请参考申请受控权限,通过AGC审核后才能使用。为避免应用的上架申请被驳回,开发者应优先使用Picker/控件等替代方案,仅少量符合特殊场景的应用被允许申请受限权限。
2.2 开发指导
当前相机提供了ArkTS和C++两种开发语言的开发指导,如下表所示。
开发流程 | ArkTS开发指导 | C++开发指导 |
---|---|---|
设备输入 | 设备输入(ArkTS) | 设备输入(C/C++) |
会话管理 | 会话管理(ArkTS) | 会话管理(C/C++) |
预览 | 预览(ArkTS) | 预览(C/C++) |
预览流二次处理 | - | 预览流二次处理(C/C++) |
拍照 | 拍照(ArkTS) | 拍照(C/C++) |
分段式拍照 | 分段式拍照(ArkTS) | - |
动态照片 | 动态照片(ArkTS) | - |
录像 | 录像(ArkTS) | 录像(C/C++) |
元数据 | 元数据(ArkTS) | 元数据(C/C++) |
3. 相机开发
3.1 相机管理(ArkTS)
3.1.1 开发步骤
-
导入camera接口,接口中提供了相机相关的属性和方法,导入方法如下。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit'; -
通过getCameraManager方法,获取cameraManager对象。
Context获取方式请参考:获取UIAbility的上下文信息。
function getCameraManager(context: common.BaseContext): camera.CameraManager {
let cameraManager: camera.CameraManager = camera.getCameraManager(context);
return cameraManager;
}
说明
如果获取对象失败,说明相机可能被占用或无法使用。如果被占用,须等到相机被释放后才能重新获取。
-
通过CameraManager类中的getSupportedCameras方法,获取当前设备支持的相机列表,列表中存储了设备支持的所有相机ID。若列表不为空,则说明列表中的每个ID都支持独立创建相机对象;否则,说明当前设备无可用相机,不可继续后续操作。
function getCameraDevices(cameraManager: camera.CameraManager): Array<camera.CameraDevice> {
let cameraArray: Array<camera.CameraDevice> = cameraManager.getSupportedCameras();
if (cameraArray != undefined && cameraArray.length > 0) {
for (let index = 0; index < cameraArray.length; index++) {
console.info('cameraId : ' + cameraArray[index].cameraId); // 获取相机ID。
console.info('cameraPosition : ' + cameraArray[index].cameraPosition); // 获取相机位置。
console.info('cameraType : ' + cameraArray[index].cameraType); // 获取相机类型。
console.info('connectionType : ' + cameraArray[index].connectionType); // 获取相机连接类型。
}
return cameraArray;
} else {
console.error("cameraManager.getSupportedCameras error");
return [];
}
}
3.1.2 状态监听
在相机应用开发过程中,可以随时监听相机状态,包括新相机的出现、相机的移除、相机的可用状态。在回调函数中,通过相机ID、相机状态这两个参数进行监听,如当有新相机出现时,可以将新相机加入到应用的备用相机中。
通过注册cameraStatus事件,通过回调返回监听结果,callback返回CameraStatusInfo参数,参数的具体内容可参考相机管理器回调接口实例CameraStatusInfo。
function onCameraStatusChange(cameraManager: camera.CameraManager): void {
cameraManager.on('cameraStatus', (err: BusinessError, cameraStatusInfo: camera.CameraStatusInfo) => {
if (err !== undefined && err.code !== 0) {
console.error(`Callback Error, errorCode: ${err.code}`);
return;
}
// 如果当通过USB连接相机设备时,回调函数会返回新的相机出现状态。
if (cameraStatusInfo.status == camera.CameraStatus.CAMERA_STATUS_APPEAR) {
console.info(`New Camera device appear.`);
}
// 如果当断开相机设备USB连接时,回调函数会返回相机被移除状态。
if (cameraStatusInfo.status == camera.CameraStatus.CAMERA_STATUS_DISAPPEAR) {
console.info(`Camera device has been removed.`);
}
// 相机被关闭时,回调函数会返回相机可用状态。
if (cameraStatusInfo.status == camera.CameraStatus.CAMERA_STATUS_AVAILABLE) {
console.info(`Current Camera is available.`);
}
// 相机被打开/占用时,回调函数会返回相机不可用状态。
if (cameraStatusInfo.status == camera.CameraStatus.CAMERA_STATUS_UNAVAILABLE) {
console.info(`Current Camera has been occupied.`);
}
console.info(`camera: ${cameraStatusInfo.camera.cameraId}`);
console.info(`status: ${cameraStatusInfo.status}`);
});
}
3.2 设备输入(ArkTS)
3.2.1 开发步骤
-
导入camera接口,接口中提供了相机相关的属性和方法,导入方法如下。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit'; -
通过cameraManager类中的createCameraInput方法创建相机输入流。
async function createInput(cameraDevice: camera.CameraDevice, cameraManager: camera.CameraManager): Promise<camera.CameraInput | undefined> {
// 创建相机输入流。
let cameraInput: camera.CameraInput | undefined = undefined;
try {
cameraInput = cameraManager.createCameraInput(cameraDevice);
} catch (error) {
let err = error as BusinessError;
console.error('Failed to createCameraInput errorCode = ' + err.code);
}
if (cameraInput === undefined) {
return undefined;
}
// 监听cameraInput错误信息。
cameraInput.on('error', cameraDevice, (error: BusinessError) => {
console.error(Camera input error code: ${error.code}
);
});
// 打开相机。
await cameraInput.open();
return cameraInput;
} -
通过getSupportedSceneModes方法,获取当前相机设备支持的模式列表,列表中存储了相机设备支持的所有模式SceneMode。
function getSupportedSceneMode(cameraDevice: camera.CameraDevice, cameraManager: camera.CameraManager): Array<camera.SceneMode> {
// 获取相机设备支持的模式列表。
let sceneModeArray: Array<camera.SceneMode> = cameraManager.getSupportedSceneModes(cameraDevice);
if (sceneModeArray != undefined && sceneModeArray.length > 0) {
for (let index = 0; index < sceneModeArray.length; index++) {
console.info('Camera SceneMode : ' + sceneModeArray[index]);
}
return sceneModeArray;
} else {
console.error("cameraManager.getSupportedSceneModes error");
return [];
}
} -
通过getSupportedOutputCapability方法,获取当前相机设备支持的所有输出流,如预览流、拍照流、录像流等。输出流在CameraOutputCapability中的各个profile字段中,根据相机设备指定模式SceneMode的不同,需要添加不同类型的输出流。
async function getSupportedOutputCapability(cameraDevice: camera.CameraDevice, cameraManager: camera.CameraManager, sceneMode: camera.SceneMode): Promise<camera.CameraOutputCapability | undefined> {
// 获取相机设备支持的输出流能力。
let cameraOutputCapability: camera.CameraOutputCapability = cameraManager.getSupportedOutputCapability(cameraDevice, sceneMode);
if (!cameraOutputCapability) {
console.error("cameraManager.getSupportedOutputCapability error");
return undefined;
}
console.info("outputCapability: " + JSON.stringify(cameraOutputCapability));
// 以NORMAL_PHOTO模式为例,需要添加预览流、拍照流。
// previewProfiles属性为获取当前设备支持的预览输出流。
let previewProfilesArray: Array<camera.Profile> = cameraOutputCapability.previewProfiles;
if (!previewProfilesArray) {
console.error("createOutput previewProfilesArray == null || undefined");
}
//photoProfiles属性为获取当前设备支持的拍照输出流。
let photoProfilesArray: Array<camera.Profile> = cameraOutputCapability.photoProfiles;
if (!photoProfilesArray) {
console.error("createOutput photoProfilesArray == null || undefined");
}
return cameraOutputCapability;
}
3.3 会话管理(ArkTS)
相机使用预览、拍照、录像、元数据功能前,均需要创建相机会话。
在会话中,可以完成以下功能:
配置相机的输入流和输出流。相机在拍摄前,必须完成输入输出流的配置。
配置输入流即添加设备输入,对用户而言,相当于选择设备的某一摄像头拍摄;配置输出流,即选择数据将以什么形式输出。当应用需要实现拍照时,输出流应配置为预览流和拍照流,预览流的数据将显示在XComponent组件上,拍照流的数据将通过ImageReceiver接口的能力保存到相册中。
添加闪光灯、调整焦距等配置。具体支持的配置及接口说明请参考Camera API参考。
会话切换控制。应用可以通过移除和添加输出流的方式,切换相机模式。如当前会话的输出流为拍照流,应用可以将拍照流移除,然后添加视频流作为输出流,即完成了拍照到录像的切换。
完成会话配置后,应用提交和开启会话,可以开始调用相机相关功能。
3.3.1 开发步骤
-
导入相关接口,导入方法如下。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit'; -
调用cameraManager类中的createSession方法创建一个会话。
function getSession(cameraManager: camera.CameraManager): camera.Session | undefined {
let session: camera.Session | undefined = undefined;
try {
session = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
} catch (error) {
let err = error as BusinessError;
console.error(Failed to create the session instance. error: ${JSON.stringify(err)}
);
}
return session;
} -
调用PhotoSession类中的beginConfig方法配置会话。
function beginConfig(photoSession: camera.PhotoSession): void {
try {
photoSession.beginConfig();
} catch (error) {
let err = error as BusinessError;
console.error(Failed to beginConfig. error: ${JSON.stringify(err)}
);
}
} -
使能。向会话中添加相机的输入流和输出流,调用addInput添加相机的输入流;调用addOutput添加相机的输出流。以下示例代码以添加预览流previewOutput和拍照流photoOutput为例,即当前模式支持拍照和预览。
调用PhotoSession类中的commitConfig和start方法提交相关配置,并启动会话。
async function startSession(photoSession: camera.PhotoSession, cameraInput: camera.CameraInput, previewOutput: camera.PreviewOutput, photoOutput: camera.PhotoOutput): Promise<void> {
try {
photoSession.addInput(cameraInput);
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to addInput. error: ${JSON.stringify(err)}`);
}
try {
photoSession.addOutput(previewOutput);
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to add previewOutput. error: ${JSON.stringify(err)}`);
}
try {
photoSession.addOutput(photoOutput);
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to add photoOutput. error: ${JSON.stringify(err)}`);
}
try {
await photoSession.commitConfig();
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to commitConfig. error: ${JSON.stringify(err)}`);
}
try {
await photoSession.start();
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to start. error: ${JSON.stringify(err)}`);
}
}
-
会话控制。调用PhotoSession类中的stop方法可以停止当前会话。调用removeOutput和addOutput方法可以完成会话切换控制。以下示例代码以移除拍照流photoOutput,添加视频流videoOutput为例,完成了拍照到录像的切换。
async function switchOutput(photoSession: camera.PhotoSession, videoOutput: camera.VideoOutput, photoOutput: camera.PhotoOutput): Promise<void> {
try {
await photoSession.stop();
} catch (error) {
let err = error as BusinessError;
console.error(Failed to stop. error: ${JSON.stringify(err)}
);
}try {
photoSession.beginConfig();
} catch (error) {
let err = error as BusinessError;
console.error(Failed to beginConfig. error: ${JSON.stringify(err)}
);
}
// 从会话中移除拍照输出流。
try {
photoSession.removeOutput(photoOutput);
} catch (error) {
let err = error as BusinessError;
console.error(Failed to remove photoOutput. error: ${JSON.stringify(err)}
);
}
// 向会话中添加视频输出流。
try {
photoSession.addOutput(videoOutput);
} catch (error) {
let err = error as BusinessError;
console.error(Failed to add videoOutput. error: ${JSON.stringify(err)}
);
}
}
3.4 预览(ArkTS)
预览是启动相机后看见的画面,通常在拍照和录像前执行。
3.4.1 开发步骤
-
导入camera接口,接口中提供了相机相关的属性和方法,导入方法如下。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit'; -
创建Surface。
XComponent组件为预览流提供的Surface(获取surfaceId请参考getXcomponentSurfaceId方法),而XComponent的能力由UI提供,相关介绍可参考XComponent组件参考。
说明
预览流与录像输出流的分辨率的宽高比要保持一致,如果设置XComponent组件中的Surface显示区域宽高比为1920:1080 = 16:9,则需要预览流中的分辨率的宽高比也为16:9,如分辨率选择640:360,或960:540,或1920:1080,以此类推。
-
通过CameraOutputCapability类中的previewProfiles属性获取当前设备支持的预览能力,返回previewProfilesArray数组 。通过createPreviewOutput方法创建预览输出流,其中,createPreviewOutput方法中的两个参数分别是previewProfilesArray数组中的第一项和步骤二中获取的surfaceId。
function getPreviewOutput(cameraManager: camera.CameraManager, cameraOutputCapability: camera.CameraOutputCapability, surfaceId: string): camera.PreviewOutput | undefined {
let previewProfilesArray: Array<camera.Profile> = cameraOutputCapability.previewProfiles;
let previewOutput: camera.PreviewOutput | undefined = undefined;
try {
previewOutput = cameraManager.createPreviewOutput(previewProfilesArray[0], surfaceId);
} catch (error) {
let err = error as BusinessError;
console.error("Failed to create the PreviewOutput instance. error code: " + err.code);
}
return previewOutput;
} -
使能。通过Session.start方法输出预览流,接口调用失败会返回相应错误码,错误码类型参见Camera错误码。
async function startPreviewOutput(cameraManager: camera.CameraManager, previewOutput: camera.PreviewOutput): Promise<void> {
let cameraArray: Array<camera.CameraDevice> = [];
cameraArray = cameraManager.getSupportedCameras();
if (cameraArray.length == 0) {
console.error('no camera.');
return;
}
// 获取支持的模式类型。
let sceneModes: Array<camera.SceneMode> = cameraManager.getSupportedSceneModes(cameraArray[0]);
let isSupportPhotoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0;
if (!isSupportPhotoMode) {
console.error('photo mode not support');
return;
}
let cameraInput: camera.CameraInput | undefined = undefined;
cameraInput = cameraManager.createCameraInput(cameraArray[0]);
if (cameraInput === undefined) {
console.error('cameraInput is undefined');
return;
}
// 打开相机。
await cameraInput.open();
let session: camera.PhotoSession = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
session.beginConfig();
session.addInput(cameraInput);
session.addOutput(previewOutput);
await session.commitConfig();
await session.start();
}
3.4.2 状态监听
在相机应用开发过程中,可以随时监听预览输出流状态,包括预览流启动、预览流结束、预览流输出错误。
-
通过注册固定的frameStart回调函数获取监听预览启动结果,previewOutput创建成功时即可监听,预览第一次曝光时触发,有该事件返回结果则认为预览流已启动。
function onPreviewOutputFrameStart(previewOutput: camera.PreviewOutput): void {
previewOutput.on('frameStart', (err: BusinessError) => {
if (err !== undefined && err.code !== 0) {
return;
}
console.info('Preview frame started');
});
} -
通过注册固定的frameEnd回调函数获取监听预览结束结果,previewOutput创建成功时即可监听,预览完成最后一帧时触发,有该事件返回结果则认为预览流已结束。
function onPreviewOutputFrameEnd(previewOutput: camera.PreviewOutput): void {
previewOutput.on('frameEnd', (err: BusinessError) => {
if (err !== undefined && err.code !== 0) {
return;
}
console.info('Preview frame ended');
});
} -
通过注册固定的error回调函数获取监听预览输出错误结果,回调返回预览输出接口使用错误时对应的错误码,错误码类型参见Camera错误码。
function onPreviewOutputError(previewOutput: camera.PreviewOutput): void {
previewOutput.on('error', (previewOutputError: BusinessError) => {
console.error(Preview output error code: ${previewOutputError.code}
);
});
}
3.5 拍照(ArkTS)
拍照是相机的最重要功能之一,拍照模块基于相机复杂的逻辑,为了保证用户拍出的照片质量,在中间步骤可以设置分辨率、闪光灯、焦距、照片质量及旋转角度等信息。
3.5.1 开发步骤
-
导入image接口。创建拍照输出流的SurfaceId以及拍照输出的数据,都需要用到系统提供的image接口能力,导入image接口的方法如下。
import { image } from '@kit.ImageKit';
import { camera } from '@kit.CameraKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { BusinessError } from '@kit.BasicServicesKit'; -
创建拍照输出流。
通过CameraOutputCapability类中的photoProfiles属性,可获取当前设备支持的拍照输出流,通过createPhotoOutput方法传入支持的某一个输出流及步骤一获取的SurfaceId创建拍照输出流。
function getPhotoOutput(cameraManager: camera.CameraManager, cameraOutputCapability: camera.CameraOutputCapability): camera.PhotoOutput | undefined {
let photoProfilesArray: Array<camera.Profile> = cameraOutputCapability.photoProfiles;
if (!photoProfilesArray) {
console.error("createOutput photoProfilesArray == null || undefined");
}
let photoOutput: camera.PhotoOutput | undefined = undefined;
try {
photoOutput = cameraManager.createPhotoOutput(photoProfilesArray[0]);
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to createPhotoOutput. error: ${JSON.stringify(err)}`);
}
return photoOutput;
}
- 设置拍照photoAvailable的回调,并将拍照的buffer保存为图片。
Context获取方式请参考:获取UIAbility的上下文信息。
如需要在图库中看到所保存的图片、视频资源,需要将其保存到媒体库,保存方式请参考:保存媒体库资源。
需要在photoOutput.on('photoAvailable')接口获取到buffer时,将buffer在安全控件中保存到媒体库。
let context = getContext(this);
function setPhotoOutputCb(photoOutput: camera.PhotoOutput) {
//设置回调之后,调用photoOutput的capture方法,就会将拍照的buffer回传到回调中。
photoOutput.on('photoAvailable', (errCode: BusinessError, photo: camera.Photo): void => {
console.info('getPhoto start');
console.info(`err: ${JSON.stringify(errCode)}`);
if (errCode || photo === undefined) {
console.error('getPhoto failed');
return;
}
let imageObj: image.Image = photo.main;
imageObj.getComponent(image.ComponentType.JPEG, (errCode: BusinessError, component: image.Component): void => {
console.info('getComponent start');
if (errCode || component === undefined) {
console.error('getComponent failed');
return;
}
let buffer: ArrayBuffer;
if (component.byteBuffer) {
buffer = component.byteBuffer;
} else {
console.error('byteBuffer is null');
return;
}
// 如需要在图库中看到所保存的图片、视频资源,请使用用户无感的安全控件创建媒体资源。
// buffer处理结束后需要释放该资源,如果未正确释放资源会导致后续拍照获取不到buffer。
imageObj.release();
});
});
}
- 参数配置。
配置相机的参数可以调整拍照的一些功能,包括闪光灯、变焦、焦距等。
function configuringSession(photoSession: camera.PhotoSession): void {
// 判断设备是否支持闪光灯。
let flashStatus: boolean = false;
try {
flashStatus = photoSession.hasFlash();
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to hasFlash. error: ${JSON.stringify(err)}`);
}
console.info(`Returned with the flash light support status: ${flashStatus}`);
if (flashStatus) {
// 判断是否支持自动闪光灯模式。
let flashModeStatus: boolean = false;
try {
let status: boolean = photoSession.isFlashModeSupported(camera.FlashMode.FLASH_MODE_AUTO);
flashModeStatus = status;
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to check whether the flash mode is supported. error: ${JSON.stringify(err)}`);
}
if (flashModeStatus) {
// 设置自动闪光灯模式。
try {
photoSession.setFlashMode(camera.FlashMode.FLASH_MODE_AUTO);
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to set the flash mode. error: ${JSON.stringify(err)}`);
}
}
}
// 判断是否支持连续自动变焦模式。
let focusModeStatus: boolean = false;
try {
let status: boolean = photoSession.isFocusModeSupported(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
focusModeStatus = status;
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to check whether the focus mode is supported. error: ${JSON.stringify(err)}`);
}
if (focusModeStatus) {
// 设置连续自动变焦模式。
try {
photoSession.setFocusMode(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to set the focus mode. error: ${JSON.stringify(err)}`);
}
}
// 获取相机支持的可变焦距比范围。
let zoomRatioRange: Array<number> = [];
try {
zoomRatioRange = photoSession.getZoomRatioRange();
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to get the zoom ratio range. error: ${JSON.stringify(err)}`);
}
if (zoomRatioRange.length <= 0 ) {
return;
}
// 设置可变焦距比。
try {
photoSession.setZoomRatio(zoomRatioRange[0]);
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to set the zoom ratio value. error: ${JSON.stringify(err)}`);
}
}
- 触发拍照。
通过photoOutput类的capture方法,执行拍照任务。该方法有两个参数,第一个参数为拍照设置参数的setting,setting中可以设置照片的质量和旋转角度,第二参数为回调函数。
获取拍照旋转角度的方法为,通过通过PhotoOutput类中的getPhotoRotation方法获取rotation实际的值。
function capture(captureLocation: camera.Location, photoOutput: camera.PhotoOutput): void {
let settings: camera.PhotoCaptureSetting = {
quality: camera.QualityLevel.QUALITY_LEVEL_HIGH, // 设置图片质量高。
rotation: camera.ImageRotation.ROTATION_0, // 设置图片旋转角度的camera.ImageRotation.ROTATION_0是通过说明中获取拍照角度的getPhotoRotation方法获取的值进行设置。
location: captureLocation, // 设置图片地理位置。
mirror: false // 设置镜像使能开关(默认关)。
};
photoOutput.capture(settings, (err: BusinessError) => {
if (err) {
console.error(`Failed to capture the photo. error: ${JSON.stringify(err)}`);
return;
}
console.info('Callback invoked to indicate the photo capture request success.');
});
}
3.5.2 状态监听
在相机应用开发过程中,可以随时监听拍照输出流状态,包括拍照流开始、拍照帧的开始与结束、拍照输出流的错误。
-
通过注册固定的captureStart回调函数获取监听拍照开始结果,photoOutput创建成功时即可监听,相机设备已经准备开始这次拍照时触发,该事件返回此次拍照的captureId。
function onPhotoOutputCaptureStart(photoOutput: camera.PhotoOutput): void {
photoOutput.on('captureStartWithInfo', (err: BusinessError, captureStartInfo: camera.CaptureStartInfo) => {
if (err !== undefined && err.code !== 0) {
return;
}
console.info(photo capture started, captureId : ${captureStartInfo.captureId}
);
});
} -
通过注册固定的captureEnd回调函数获取监听拍照结束结果,photoOutput创建成功时即可监听,该事件返回结果为拍照完全结束后的相关信息CaptureEndInfo。
function onPhotoOutputCaptureEnd(photoOutput: camera.PhotoOutput): void {
photoOutput.on('captureEnd', (err: BusinessError, captureEndInfo: camera.CaptureEndInfo) => {
if (err !== undefined && err.code !== 0) {
return;
}
console.info(photo capture end, captureId : ${captureEndInfo.captureId}
);
console.info(frameCount : ${captureEndInfo.frameCount}
);
});
} -
通过注册固定的captureReady回调函数获取监听可拍下一张结果,photoOutput创建成功时即可监听,当下一张可拍时触发,该事件返回结果为下一张可拍的相关信息。
function onPhotoOutputCaptureReady(photoOutput: camera.PhotoOutput): void {
photoOutput.on('captureReady', (err: BusinessError) => {
if (err !== undefined && err.code !== 0) {
return;
}
console.info(photo capture ready
);
});
} -
通过注册固定的error回调函数获取监听拍照输出流的错误结果。回调返回拍照输出接口使用错误时的对应错误码,错误码类型参见Camera错误码。
function onPhotoOutputError(photoOutput: camera.PhotoOutput): void {
photoOutput.on('error', (error: BusinessError) => {
console.error(Photo output error code: ${error.code}
);
});
}
3.6 录像(ArkTS)
3.6.1 开发步骤
- 导入media模块。
创建录像输出流的SurfaceId以及录像输出的数据,都需要用到系统提供的media接口能力,导入media接口的方法如下。
import { BusinessError } from '@kit.BasicServicesKit';
import { camera } from '@kit.CameraKit';
import { media } from '@kit.MediaKit';
- 创建Surface。
系统提供的media接口可以创建一个录像AVRecorder实例,通过该实例的getInputSurface方法获取SurfaceId,与录像输出流做关联,处理录像输出流输出的数据。
async function getVideoSurfaceId(aVRecorderConfig: media.AVRecorderConfig): Promise<string | undefined> { // aVRecorderConfig可参考下一章节。
let avRecorder: media.AVRecorder | undefined = undefined;
try {
avRecorder = await media.createAVRecorder();
} catch (error) {
let err = error as BusinessError;
console.error(`createAVRecorder call failed. error code: ${err.code}`);
}
if (avRecorder === undefined) {
return undefined;
}
avRecorder.prepare(aVRecorderConfig, (err: BusinessError) => {
if (err == null) {
console.info('prepare success');
} else {
console.error('prepare failed and error is ' + err.message);
}
});
let videoSurfaceId = await avRecorder.getInputSurface();
return videoSurfaceId;
}
- 创建录像输出流。
通过CameraOutputCapability类中的videoProfiles属性,可获取当前设备支持的录像输出流。然后,定义创建录像的参数,通过createVideoOutput方法创建录像输出流。
说明
预览流与录像输出流的分辨率的宽高比要保持一致,如示例代码中宽高比为640:480 = 4:3,则需要预览流中的分辨率的宽高比也为4:3,如分辨率选择640:480,或960:720,或1440:1080,以此类推。
获取录像旋转角度的方法:通过VideoOutput类中的getVideoRotation方法获取rotation实际的值。
async function getVideoOutput(cameraManager: camera.CameraManager, videoSurfaceId: string, cameraOutputCapability: camera.CameraOutputCapability): Promise<camera.VideoOutput | undefined> {
let videoProfilesArray: Array<camera.VideoProfile> = cameraOutputCapability.videoProfiles;
if (!videoProfilesArray) {
console.error("createOutput videoProfilesArray == null || undefined");
return undefined;
}
// AVRecorderProfile.
let aVRecorderProfile: media.AVRecorderProfile = {
fileFormat : media.ContainerFormatType.CFT_MPEG_4, // 视频文件封装格式,只支持MP4。
videoBitrate : 100000, // 视频比特率。
videoCodec : media.CodecMimeType.VIDEO_AVC, // 视频文件编码格式,支持avc格式。
videoFrameWidth : 640, // 视频分辨率的宽。
videoFrameHeight : 480, // 视频分辨率的高。
videoFrameRate : 30 // 视频帧率。
};
// 创建视频录制的参数,预览流与录像输出流的分辨率的宽(videoFrameWidth)高(videoFrameHeight)比要保持一致。
let aVRecorderConfig: media.AVRecorderConfig = {
videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,
profile: aVRecorderProfile,
url: 'fd://35',
rotation: 90 // rotation的值90,是通过getPhotoRotation接口获取到的值,具体请参考说明中获取录像旋转角度的方法。
};
// 创建avRecorder。
let avRecorder: media.AVRecorder | undefined = undefined;
try {
avRecorder = await media.createAVRecorder();
} catch (error) {
let err = error as BusinessError;
console.error(`createAVRecorder call failed. error code: ${err.code}`);
}
if (avRecorder === undefined) {
return undefined;
}
// 设置视频录制的参数。
avRecorder.prepare(aVRecorderConfig);
// 创建VideoOutput对象。
let videoOutput: camera.VideoOutput | undefined = undefined;
// createVideoOutput传入的videoProfile对象的宽高需要和aVRecorderProfile保持一致。
let videoProfile: undefined | camera.VideoProfile = videoProfilesArray.find((profile: camera.VideoProfile) => {
return profile.size.width === aVRecorderProfile.videoFrameWidth && profile.size.height === aVRecorderProfile.videoFrameHeight;
});
if (!videoProfile) {
console.error('videoProfile is not found');
return;
}
try {
videoOutput = cameraManager.createVideoOutput(videoProfile, videoSurfaceId);
} catch (error) {
let err = error as BusinessError;
console.error('Failed to create the videoOutput instance. errorCode = ' + err.code);
}
return videoOutput;
}
- 开始录像。
先通过videoOutput的start方法启动录像输出流,再通过avRecorder的start方法开始录像。
async function startVideo(videoOutput: camera.VideoOutput, avRecorder: media.AVRecorder): Promise<void> {
videoOutput.start(async (err: BusinessError) => {
if (err) {
console.error(`Failed to start the video output ${err.message}`);
return;
}
console.info('Callback invoked to indicate the video output start success.');
});
try {
await avRecorder.start();
} catch (error) {
let err = error as BusinessError;
console.error(`avRecorder start error: ${JSON.stringify(err)}`);
}
}
- 停止录像。
先通过avRecorder的stop方法停止录像,再通过videoOutput的stop方法停止录像输出流。
async function stopVideo(videoOutput: camera.VideoOutput, avRecorder: media.AVRecorder): Promise<void> {
try {
await avRecorder.stop();
} catch (error) {
let err = error as BusinessError;
console.error(`avRecorder stop error: ${JSON.stringify(err)}`);
}
videoOutput.stop((err: BusinessError) => {
if (err) {
console.error(`Failed to stop the video output ${err.message}`);
return;
}
console.info('Callback invoked to indicate the video output stop success.');
});
}
3.6.2 状态监听
在相机应用开发过程中,可以随时监听录像输出流状态,包括录像开始、录像结束、录像流输出的错误。
-
通过注册固定的frameStart回调函数获取监听录像开始结果,videoOutput创建成功时即可监听,录像第一次曝光时触发,有该事件返回结果则认为录像开始。
function onVideoOutputFrameStart(videoOutput: camera.VideoOutput): void {
videoOutput.on('frameStart', (err: BusinessError) => {
if (err !== undefined && err.code !== 0) {
return;
}
console.info('Video frame started');
});
} -
通过注册固定的frameEnd回调函数获取监听录像结束结果,videoOutput创建成功时即可监听,录像完成最后一帧时触发,有该事件返回结果则认为录像流已结束。
function onVideoOutputFrameEnd(videoOutput: camera.VideoOutput): void {
videoOutput.on('frameEnd', (err: BusinessError) => {
if (err !== undefined && err.code !== 0) {
return;
}
console.info('Video frame ended');
});
} -
通过注册固定的error回调函数获取监听录像输出错误结果,callback返回预览输出接口使用错误时对应的错误码,错误码类型参见Camera错误码。
function onVideoOutputError(videoOutput: camera.VideoOutput): void {
videoOutput.on('error', (error: BusinessError) => {
console.error(Video output error code: ${error.code}
);
});
}
3.7 元数据(ArkTS)
元数据(Metadata)是对相机返回的图像信息数据的描述和上下文,针对图像信息,提供的更详细的数据,如照片或视频中,识别人像的取景框坐标等信息。
Metadata主要是通过一个TAG(Key),去找对应的Data,用于传递参数和配置信息,减少内存拷贝操作。
3.7.1 开发步骤
-
导入相关接口,导入方法如下。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit'; -
调用CameraOutputCapability类中的supportedMetadataObjectTypes属性,获取当前设备支持的元数据类型,并通过createMetadataOutput方法创建元数据输出流。
function getMetadataOutput(cameraManager: camera.CameraManager, cameraOutputCapability: camera.CameraOutputCapability): camera.MetadataOutput | undefined {
let metadataObjectTypes: Array<camera.MetadataObjectType> = cameraOutputCapability.supportedMetadataObjectTypes;
let metadataOutput: camera.MetadataOutput | undefined = undefined;
try {
metadataOutput = cameraManager.createMetadataOutput(metadataObjectTypes);
} catch (error) {
let err = error as BusinessError;
console.error(Failed to createMetadataOutput, error code: ${err.code}
);
}
return metadataOutput;
} -
调用Session.start方法开启metadata数据输出,再通过监听事件metadataObjectsAvailable回调拿到数据,接口调用失败时,会返回相应错误码,错误码类型参见Camera错误码。
previewOutput获取方式请参考相机预览开发步骤。
async function startMetadataOutput(previewOutput: camera.PreviewOutput, metadataOutput: camera.MetadataOutput, cameraManager: camera.CameraManager): Promise<void> {
let cameraArray: Array<camera.CameraDevice> = [];
cameraArray = cameraManager.getSupportedCameras();
if (cameraArray.length == 0) {
console.error('no camera.');
return;
}
// 获取支持的模式类型。
let sceneModes: Array<camera.SceneMode> = cameraManager.getSupportedSceneModes(cameraArray[0]);
let isSupportPhotoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0;
if (!isSupportPhotoMode) {
console.error('photo mode not support');
return;
}
let cameraInput: camera.CameraInput | undefined = undefined;
cameraInput = cameraManager.createCameraInput(cameraArray[0]);
if (cameraInput === undefined) {
console.error('cameraInput is undefined');
return;
}
// 打开相机。
await cameraInput.open();
let session: camera.PhotoSession = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
session.beginConfig();
session.addInput(cameraInput);
session.addOutput(previewOutput);
session.addOutput(metadataOutput);
await session.commitConfig();
await session.start();
}
-
调用Session.stop方法停止输出metadata数据,接口调用失败会返回相应错误码,错误码类型参见Camera错误码。
function stopMetadataOutput(session: camera.Session): void {
session.stop().then(() => {
console.info('Callback returned with session stopped.');
}).catch((err: BusinessError) => {
console.error(Failed to session stop, error code: ${err.code}
);
});
}
3.7.2 状态监听
在相机应用开发过程中,可以随时监听metadata数据以及输出流的状态。
-
通过注册监听获取metadata对象,监听事件固定为metadataObjectsAvailable。检测到有效metadata数据时,callback返回相应的metadata数据信息,metadataOutput创建成功时可监听。
function onMetadataObjectsAvailable(metadataOutput: camera.MetadataOutput): void {
metadataOutput.on('metadataObjectsAvailable', (err: BusinessError, metadataObjectArr: Array<camera.MetadataObject>) => {
if (err !== undefined && err.code !== 0) {
return;
}
console.info('metadata output metadataObjectsAvailable');
});
}
说明
当前的元数据类型仅支持人脸检测(FACE_DETECTION)功能。元数据信息对象为识别到的人脸区域的矩形信息(Rect),包含矩形区域的左上角x坐标、y坐标和矩形的宽高数据。
-
通过注册回调函数,获取监听metadata流的错误结果,callback返回metadata输出接口使用错误时返回的错误码,错误码类型参见Camera错误码。
function onMetadataError(metadataOutput: camera.MetadataOutput): void {
metadataOutput.on('error', (metadataOutputError: BusinessError) => {
console.error(Metadata output error code: ${metadataOutputError.code}
);
});
}
3.8 对焦(ArkTS)
相机框架提供对设备对焦的能力,业务应用可以根据使用场景进行对焦模式和对焦点的设置。
3.8.1 开发步骤
-
导入相关接口,导入方法如下。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit'; -
在设置对焦模式前,需要先调用isFocusModeSupported检查设备是否支持指定的焦距模式。
说明
需要在Session调用commitConfig完成配流之后调用。
function isFocusModeSupported(photoSession: camera.PhotoSession): boolean {
let status: boolean = false;
try {
// 以检查是否支持连续自动对焦模式为例
status = photoSession.isFocusModeSupported(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
} catch (error) {
// 失败返回错误码error.code并处理
let err = error as BusinessError;
console.error(`The isFocusModeSupported call failed. error code: ${err.code}`);
}
return status;
}
- 调用setFocusMode设置对焦模式。
若设置为自动对焦模式,支持调用setFocusPoint设置对焦点,根据对焦点执行一次自动对焦。
说明
需要在Session调用commitConfig完成配流之后调用。
function setFocusMode(photoSession: camera.PhotoSession): void {
const focusPoint: camera.Point = {x: 1, y: 1};
try {
// 设置自动对焦模式
photoSession.setFocusMode(camera.FocusMode.FOCUS_MODE_AUTO);
// 设置对焦点
photoSession.setFocusPoint(focusPoint);
} catch (error) {
// 失败返回错误码error.code并处理
let err = error as BusinessError;
console.error(`The setFocusMode and setFocusPoint call failed. error code: ${err.code}`);
}
}
3.8.2 状态监听
在相机应用开发过程中,可以随时监听相机聚焦的状态变化。
-
通过注册focusStateChange的回调函数获取监听结果,仅当自动对焦模式时,且相机对焦状态发生改变时触发该事件。
function onFocusStateChange(photoSession: camera.PhotoSession): void {
photoSession.on('focusStateChange', (err: BusinessError, focusState: camera.FocusState) => {
if (err !== undefined && err.code !== 0) {
console.error(focusStateChange error code: ${err.code}
);
return;
}
console.info(focusStateChange focusState: ${focusState}
);
// 为保证对焦功能的用户体验,在自动对焦成功后,可将对焦模式设置为连续自动对焦
if (focusState === camera.FocusState.FOCUS_STATE_FOCUSED) {
photoSession.setFocusMode(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
}
});
}
3.9 手电筒使用(ArkTS)
手电筒模式的使用是通过操作手机启用手电筒功能,使设备的手电筒功能持续保持常亮状态。
在使用相机应用并操作手电筒功能时,存在以下几种情况说明:
- 当使用后置摄像头并设置闪光灯模式FlashMode关闭时,手电筒功能无法启用。
- 当使用前置摄像头时,手电筒可以正常启用并保持常亮状态。
- 从前置摄像头切换至后置摄像头时,如果手电筒原本处于开启状态,它将会被自动关闭。
3.9.1 开发步骤
-
导入camera接口,接口中提供了相机相关的属性和方法,导入方法如下。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit'; -
通过CameraManager类中的isTorchSupported方法,检测当前设备是否支持手电筒功能。
function isTorchSupported(cameraManager: camera.CameraManager) : boolean {
let torchSupport: boolean = false;
try {
torchSupport = cameraManager.isTorchSupported();
} catch (error) {
let err = error as BusinessError;
console.error('Failed to torch. errorCode = ' + err.code);
}
console.info('Returned with the torch support status:' + torchSupport);
return torchSupport;
}
3 通过CameraManager类中的isTorchModeSupported方法,检测是否支持指定的手电筒模式TorchMode。
function isTorchModeSupported(cameraManager: camera.CameraManager, torchMode: camera.TorchMode) : boolean {
let isTorchModeSupport: boolean = false;
try {
isTorchModeSupport = cameraManager.isTorchModeSupported(torchMode);
} catch (error) {
let err = error as BusinessError;
console.error('Failed to set the torch mode. errorCode = ' + err.code);
}
return isTorchModeSupport;
}
- 通过CameraManager类中的setTorchMode方法,设置当前设备的手电筒模式。以及通过CameraManager类中的getTorchMode方法,获取当前设备的手电筒模式。
说明
在使用getTorchMode方法前,需要先注册监听手电筒的状态变化,请参考状态监听。
function setTorchModeSupported(cameraManager: camera.CameraManager, torchMode: camera.TorchMode) : void {
cameraManager.setTorchMode(torchMode);
let isTorchMode = cameraManager.getTorchMode();
console.info(`Returned with the torch mode supportd mode: ${isTorchMode}`);
}
3.9.2 状态监听
在相机应用开发过程中,可以随时监听手电筒状态,包括手电筒打开、手电筒关闭、手电筒不可用、手电筒恢复可用。手电筒状态发生变化,可通过回调函数获取手电筒模式的变化。
通过注册torchStatusChange事件,通过回调返回监听结果,callback返回TorchStatusInfo参数,参数的具体内容可参考相机管理器回调接口实例TorchStatusInfo。
function onTorchStatusChange(cameraManager: camera.CameraManager): void {
cameraManager.on('torchStatusChange', (err: BusinessError, torchStatusInfo: camera.TorchStatusInfo) => {
if (err !== undefined && err.code !== 0) {
console.error(`Callback Error, errorCode: ${err.code}`);
return;
}
console.info(`onTorchStatusChange, isTorchAvailable: ${torchStatusInfo.isTorchAvailable}, isTorchActive: ${torchStatusInfo.
isTorchActive}, level: ${torchStatusInfo.torchLevel}`);
});
}
3.10 适配不同折叠状态的摄像头变更(ArkTS)
一台可折叠设备在不同折叠状态下,可使用不同的摄像头,应用可调用CameraManager.on('foldStatusChange')或display.on('foldStatusChange')监听设备的折叠状态变化,并调用CameraManager.getSupportedCameras获取当前状态下可用摄像头,完成相应适配,确保应用在折叠状态变更时的用户体验。
3.10.1 创建XComponent
使用两个XComponent分别展示折叠态和展开态,防止切换折叠屏状态亮屏的时候上一个摄像头还未关闭,残留上一个摄像头的画面。
@Entry
@Component
struct Index {
@State reloadXComponentFlag: boolean = false;
@StorageLink('foldStatus') @Watch('reloadXComponent') foldStatus: number = 0;
private mXComponentController: XComponentController = new XComponentController();
private mXComponentOptions: XComponentOptions = {
type: XComponentType.SURFACE,
controller: this.mXComponentController
}
reloadXComponent() {
this.reloadXComponentFlag = !this.reloadXComponentFlag;
}
async loadXComponent() {
//初始化XComponent。
}
build() {
Stack() {
if (this.reloadXComponentFlag) {
XComponent(this.mXComponentOptions)
.onLoad(async () => {
await this.loadXComponent();
})
.width(px2vp(1080))
.height(px2vp(1920))
} else {
XComponent(this.mXComponentOptions)
.onLoad(async () => {
await this.loadXComponent();
})
.width(px2vp(1080))
.height(px2vp(1920))
}
}
.size({ width: '100%', height: '100%' })
.backgroundColor(Color.Black)
}
}
3.10.2 获取设备折叠状态
此处提供两种方案供开发者选择。
-
方案一:使用相机框架提供的CameraManager.on('foldStatusChange')监听设备折叠态变化。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';let cameraManager = camera.getCameraManager(getContext())
function registerFoldStatusChanged(err: BusinessError, foldStatusInfo: camera.FoldStatusInfo) {
// foldStatus 变量用来控制显示XComponent组件。
AppStorage.setOrCreate<number>('foldStatus', foldStatusInfo.foldStatus);
}cameraManager.on('foldStatusChange', registerFoldStatusChanged);
//cameraManager.off('foldStatusChange', registerFoldStatusChanged); -
方案二:使用图形图像的display.on('foldStatusChange')监听设备折叠态变化。
import { display } from '@kit.ArkUI';
let preFoldStatus: display.FoldStatus = display.getFoldStatus();
display.on('foldStatusChange', (foldStatus: display.FoldStatus) => {
// 从半折叠态(FOLD_STATUS_HALF_FOLDED)和展开态(FOLD_STATUS_EXPANDED),相机框架返回所支持的摄像头是一致的,所以从半折叠态到展开态不需要重新配流,从展开态到半折叠态也是一样的。
if ((preFoldStatus === display.FoldStatus.FOLD_STATUS_HALF_FOLDED &&
foldStatus === display.FoldStatus.FOLD_STATUS_EXPANDED) ||
(preFoldStatus === display.FoldStatus.FOLD_STATUS_EXPANDED &&
foldStatus === display.FoldStatus.FOLD_STATUS_HALF_FOLDED)) {
preFoldStatus = foldStatus;
return;
}
preFoldStatus = foldStatus;
// foldStatus 变量用来控制显示XComponent组件。
AppStorage.setOrCreate<number>('foldStatus', foldStatus);
})
3.11 分段式拍照(ArkTS)
分段式拍照是相机的重要功能之一,即应用下发拍照任务后,系统将分多阶段上报不同质量的图片。
- 在第一阶段,系统快速上报轻量处理的图片,轻量处理的图片比全质量图低,出图速度快。应用通过回调会收到一个PhotoAsset对象,通过该对象可调用媒体库接口,读取图片或落盘图片。
- 在第二阶段,相机框架会根据应用的请求图片诉求或者在系统闲时,进行图像增强处理得到全质量图,将处理好的图片传回给媒体库,替换轻量处理的图片。
通过分段式拍照,优化了系统的拍照响应时延,从而提升用户体验。
应用开发分段式拍照主要分为以下步骤:
通过PhotoOutput,监听photoAssetAvailable回调,获取photoAccessHelper的PhotoAsset对象。
通过PhotoAsset对象,调用媒体库相关接口,读取或落盘图片。
说明分段式拍照能力是根据设备 和模式决定的,不同的设备支持不同的模式,不同的模式下分段式能力也各有不同,所以应用在切换设备或模式后分段式能力可能会发生变化。
分段式拍照能力应用无需主动使能,相机框架会在配流期间判断设备和模式是否支持分段式,如果支持会使能分段式拍照。
3.11.1 开发步骤
-
导入依赖,需要导入相机框架、媒体库、图片相关领域依赖。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit'; -
确定拍照输出流。
通过CameraOutputCapability类中的photoProfiles属性,可获取当前设备支持的拍照输出流,通过createPhotoOutput方法创建拍照输出流。
function getPhotoOutput(cameraManager: camera.CameraManager,
cameraOutputCapability: camera.CameraOutputCapability): camera.PhotoOutput | undefined {
let photoProfilesArray: Array<camera.Profile> = cameraOutputCapability.photoProfiles;
if (!photoProfilesArray) {
console.error("createOutput photoProfilesArray == null || undefined");
}
let photoOutput: camera.PhotoOutput | undefined = undefined;
try {
photoOutput = cameraManager.createPhotoOutput(photoProfilesArray[0]);
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to createPhotoOutput. error: ${JSON.stringify(err)}`);
}
return photoOutput;
}
- 设置拍照photoAssetAvailable的回调。
注意
如果已经注册了photoAssetAvailable回调,并且在Session开始之后又注册了photoAvailable回调,photoAssetAvailable和photoAvailable同时注册,会导致流被重启,仅photoAssetAvailable生效。
不建议开发者同时注册photoAvailable和photoAssetAvailable。
function photoAssetAvailableCallback(err: BusinessError, photoAsset: photoAccessHelper.PhotoAsset): void {
if (err) {
console.error(`photoAssetAvailable error: ${JSON.stringify(err)}.`);
return;
}
console.info('photoOutPutCallBack photoAssetAvailable');
// 开发者可通过photoAsset调用媒体库相关接口,自定义处理图片。
// 处理方式一:调用媒体库落盘接口保存一阶段图,二阶段图就绪后媒体库会主动帮应用替换落盘图片。
mediaLibSavePhoto(photoAsset);
// 处理方式二:调用媒体库接口请求图片并注册一阶段图或二阶段图buffer回调,自定义使用。
mediaLibRequestBuffer(photoAsset);
}
function onPhotoOutputPhotoAssetAvailable(photoOutput: camera.PhotoOutput): void {
photoOutput.on('photoAssetAvailable', photoAssetAvailableCallback);
}
let context = getContext(this);
let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
async function mediaLibSavePhoto(photoAsset: photoAccessHelper.PhotoAsset): Promise<void> {
try {
let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest = new photoAccessHelper.MediaAssetChangeRequest(photoAsset);
assetChangeRequest.saveCameraPhoto();
await phAccessHelper.applyChanges(assetChangeRequest);
console.info('apply saveCameraPhoto successfully');
} catch (err) {
console.error(`apply saveCameraPhoto failed with error: ${err.code}, ${err.message}`);
}
}
class MediaDataHandler implements photoAccessHelper.MediaAssetDataHandler<ArrayBuffer> {
onDataPrepared(data: ArrayBuffer) {
if (data === undefined) {
console.error('Error occurred when preparing data');
return;
}
// 应用获取到图片buffer后可自定义处理。
console.info('on image data prepared');
}
}
async function mediaLibRequestBuffer(photoAsset: photoAccessHelper.PhotoAsset) {
let requestOptions: photoAccessHelper.RequestOptions = {
// 按照业务需求配置回图模式。
// FAST_MODE:仅接收一阶段低质量图回调。
// HIGH_QUALITY_MODE:仅接收二阶段全质量图回调。
// BALANCE_MODE:接收一阶段及二阶段图片回调。
deliveryMode: photoAccessHelper.DeliveryMode.FAST_MODE,
}
const handler = new MediaDataHandler();
await photoAccessHelper.MediaAssetManager.requestImageData(context, photoAsset, requestOptions, handler);
console.info('requestImageData successfully');
}
落盘图片参考媒体库接口:saveCameraPhoto
请求图片参考媒体库接口:requestImageData 和 onDataPrepared
- 拍照时的会话配置及触发拍照的方式,与普通拍照相同,请参考拍照的步骤4-5。
3.11.2 状态监听
在相机应用开发过程中,可以随时监听拍照输出流状态,包括拍照流开始、拍照帧的开始与结束、拍照输出流的错误。
-
通过注册固定的captureStart回调函数获取监听拍照开始结果,photoOutput创建成功时即可监听,相机设备已经准备开始这次拍照时触发,该事件返回此次拍照的captureId。
function onPhotoOutputCaptureStart(photoOutput: camera.PhotoOutput): void {
photoOutput.on('captureStartWithInfo', (err: BusinessError, captureStartInfo: camera.CaptureStartInfo) => {
if (err !== undefined && err.code !== 0) {
return;
}
console.info(photo capture started, captureId : ${captureStartInfo.captureId}
);
});
} -
通过注册固定的captureEnd回调函数获取监听拍照结束结果,photoOutput创建成功时即可监听,该事件返回结果为拍照完全结束后的相关信息CaptureEndInfo。
function onPhotoOutputCaptureEnd(photoOutput: camera.PhotoOutput): void {
photoOutput.on('captureEnd', (err: BusinessError, captureEndInfo: camera.CaptureEndInfo) => {
if (err !== undefined && err.code !== 0) {
return;
}
console.info(photo capture end, captureId : ${captureEndInfo.captureId}
);
console.info(frameCount : ${captureEndInfo.frameCount}
);
});
} -
通过注册固定的captureReady回调函数获取监听可拍下一张结果,photoOutput创建成功时即可监听,当下一张可拍时触发,该事件返回结果为下一张可拍的相关信息。
function onPhotoOutputCaptureReady(photoOutput: camera.PhotoOutput): void {
photoOutput.on('captureReady', (err: BusinessError) => {
if (err !== undefined && err.code !== 0) {
return;
}
console.info(photo capture ready
);
});
} -
通过注册固定的error回调函数获取监听拍照输出流的错误结果。callback返回拍照输出接口使用错误时的对应错误码,错误码类型参见Camera错误码。
function onPhotoOutputError(photoOutput: camera.PhotoOutput): void {
photoOutput.on('error', (error: BusinessError) => {
console.error(Photo output error code: ${error.code}
);
});
}
3.12 动态照片(ArkTS)
相机框架提供动态照片拍摄能力,业务应用可以类似拍摄普通照片一样,一键式拍摄得到动态照片。
应用开发动态照片主要分为以下步骤:
- 查询当前设备的当前模式是否支持拍摄动态照片。
- 如果支持动态照片,可以调用相机框架提供的使能接口使能动态照片能力。
- 监听照片回调,将照片存入媒体库。可参考MediaLibrary Kit-访问和管理动态照片资源。
3.12.1 开发步骤
说明
-
导入依赖,需要导入相机框架、媒体库、图片相关领域依赖。
import { camera } from '@kit.CameraKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { BusinessError } from '@kit.BasicServicesKit'; -
确定拍照输出流。
通过CameraOutputCapability类中的photoProfiles属性,可获取当前设备支持的拍照输出流,通过createPhotoOutput方法创建拍照输出流。
function getPhotoOutput(cameraManager: camera.CameraManager,
cameraOutputCapability: camera.CameraOutputCapability): camera.PhotoOutput | undefined {
let photoProfilesArray: Array<camera.Profile> = cameraOutputCapability.photoProfiles;
if (!photoProfilesArray) {
console.error("createOutput photoProfilesArray == null || undefined");
}
let photoOutput: camera.PhotoOutput | undefined = undefined;
try {
photoOutput = cameraManager.createPhotoOutput(photoProfilesArray[0]);
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to createPhotoOutput. error: ${JSON.stringify(err)}`);
}
return photoOutput;
}
- 查询当前设备当前模式是否支持动态照片能力。
说明
查询是否支持动态照片前需要先完成相机会话配置、提交和启动会话,详细开发步骤请参考会话管理。
function isMovingPhotoSupported(photoOutput: camera.PhotoOutput): boolean {
let isSupported: boolean = false;
try {
isSupported = photoOutput.isMovingPhotoSupported();
} catch (error) {
// 失败返回错误码error.code并处理。
let err = error as BusinessError;
console.error(`The isMovingPhotoSupported call failed. error code: ${err.code}`);
}
return isSupported;
}
-
使能动态照片拍照能力。
function enableMovingPhoto(photoOutput: camera.PhotoOutput): void {
try {
photoOutput.enableMovingPhoto(true);
} catch (error) {
// 失败返回错误码error.code并处理。
let err = error as BusinessError;
console.error(The enableMovingPhoto call failed. error code: ${err.code}
);
}
} -
触发拍照,与普通拍照方式相同
3.12.2 状态监听
在相机应用开发过程中,可以随时监听动态照片拍照输出流状态。通过注册photoAsset的回调函数获取监听结果,photoOutput创建成功时即可监听。
let context = getContext(this);
let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
async function mediaLibSavePhoto(photoAsset: photoAccessHelper.PhotoAsset): Promise<void> {
try {
let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest = new photoAccessHelper.MediaAssetChangeRequest(photoAsset);
assetChangeRequest.saveCameraPhoto();
await phAccessHelper.applyChanges(assetChangeRequest);
console.info('apply saveCameraPhoto successfully');
} catch (err) {
console.error(`apply saveCameraPhoto failed with error: ${err.code}, ${err.message}`);
}
}
function onPhotoOutputPhotoAssetAvailable(photoOutput: camera.PhotoOutput): void {
photoOutput.on('photoAssetAvailable', (err: BusinessError, photoAsset: photoAccessHelper.PhotoAsset): void => {
if (err) {
console.info(`photoAssetAvailable error: ${JSON.stringify(err)}.`);
return;
}
console.info('photoOutPutCallBack photoAssetAvailable');
// 调用媒体库落盘接口保存一阶段图和动态照片视频。
mediaLibSavePhoto(photoAsset);
});
}
3.13 相机基础动效(ArkTS)
在使用相机过程中,如相机模式切换,前后置镜头切换等场景,不可避免出现预览流替换,为优化用户体验,可合理使用动效过渡。本文主要介绍如何使用预览流截图,并通过ArkUI提供的显示动画能力实现下方三种核心场景动效:
-
模式切换动效,使用预览流截图做模糊动效过渡。
图片为从录像模式切换为拍照模式的效果。
-
前后置切换动效,使用预览流截图做翻转模糊动效过渡。
图片为从前置摄像头切换为后置摄像头的效果。
-
拍照闪黑动效,使用闪黑组件覆盖预览流实现闪黑动效过渡。
图片为点击完成拍摄的效果。
3.13.1 闪黑动效
使用组件覆盖的形式实现闪黑效果。
-
导入依赖,需要导入相机框架、图片、ArkUI相关领域依赖。
import { curves } from '@kit.ArkUI';
-
构建闪黑组件。
此处定义一个闪黑组件,在拍照闪黑及前后置切换时显示,用来遮挡XComponent组件。
属性定义:
@State isShowBlack: boolean = false; // 是否显示闪黑组件。
@StorageLink('captureClick') @Watch('onCaptureClick') captureClickFlag: number = 0; // 拍照闪黑动效入口。
@State flashBlackOpacity: number = 1; // 闪黑组件透明度。
闪黑组件的实现逻辑参考:
// 拍照闪黑及前后置切换时显示,用来遮挡XComponent组件。
if (this.isShowBlack) {
Column()
.key('black')
.width(px2vp(1080)) // 与预览流XComponent宽高保持一致,图层在预览流之上,截图组件之下。
.height(px2vp(1920))
.backgroundColor(Color.Black)
.opacity(this.flashBlackOpacity)
}
-
实现闪黑动效。
function flashBlackAnim() {
console.info('flashBlackAnim E');
this.flashBlackOpacity = 1; // 闪黑组件不透明。
this.isShowBlack = true; // 显示闪黑组件。
animateToImmediately({
curve: curves.interpolatingSpring(1, 1, 410, 38),
delay: 50, // 延时50ms,实现黑屏。
onFinish: () => {
this.isShowBlack = false; // 闪黑组件下树。
this.flashBlackOpacity = 1;
console.info('flashBlackAnim X');
}
}, () => {
this.flashBlackOpacity = 0; // 闪黑组件从不透明到透明。
})
} -
触发闪黑动效。
点击或触控拍照按钮,更新StorageLink绑定CaptureClick的值,触发onCaptureClick方法,动效开始播放。
onCaptureClick(): void {
console.info('onCaptureClick');
console.info('onCaptureClick');
this.flashBlackAnim();
}
3.13.2 模糊动效
通过预览流截图,实现模糊动效,从而完成模式切换,或是前后置切换的动效。
-
导入依赖,需要导入相机框架、图片、ArkUI相关领域依赖。
import { camera } from '@kit.CameraKit';
import { image } from '@kit.ImageKit';
import { curves } from '@kit.ArkUI'; -
获取预览流截图。
预览流截图通过图形提供的image.createPixelMapFromSurface接口实现,surfaceId为当前预览流的surfaceId,size为当前预览流profile的宽高。创建截图工具类(ts文件),导入依赖,导出获取截图方法供页面使用,截图工具类实现参考:
import { image } from '@kit.ImageKit';
export class BlurAnimateUtil {
public static surfaceShot: image.PixelMap;
/**
* 获取surface截图
* @param surfaceId
* @returns
*/
public static async doSurfaceShot(surfaceId: string) {
console.info(`doSurfaceShot surfaceId:${surfaceId}.`);
if (surfaceId === '') {
console.error('surface not ready!');
return;
}
try {
if (this.surfaceShot) {
await this.surfaceShot.release();
}
this.surfaceShot = await image.createPixelMapFromSurface(surfaceId, {
size: { width: 1920, height: 1080 }, // 取预览流profile的宽高。
x: 0,
y: 0
});
let imageInfo: image.ImageInfo = await this.surfaceShot.getImageInfo();
console.info('doSurfaceShot surfaceShot:' + JSON.stringify(imageInfo.size));
} catch (err) {
console.error(JSON.stringify(err));
}
}
/**
* 获取doSurfaceShot得到的截图
* @returns
*/
public static getSurfaceShot(): image.PixelMap {
return this.surfaceShot;
}
}
- 构建截图组件。
此处定义一个截图组件,置于预览流XComponent组件之上,用来遮挡XComponent组件。
属性定义:
@State isShowBlur: boolean = false; // 是否显示截图组件。
@StorageLink('modeChange') @Watch('onModeChange') modeChangeFlag: number = 0; // 模式切换动效触发入口。
@StorageLink('switchCamera') @Watch('onSwitchCamera') switchCameraFlag: number = 0;// 前后置切换动效触发入口。
@StorageLink('frameStart') @Watch('onFrameStart') frameStartFlag: number = 0; // 动效消失入口。
@State screenshotPixelMap: image.PixelMap | undefined = undefined; // 截图组件PixelMap。
@State surfaceId: string = ''; // 当前预览流XComponent的surfaceId。
@StorageLink('curPosition') curPosition: number = 0; // 当前镜头前后置状态。
@State shotImgBlur: number = 0; // 截图组件模糊度。
@State shotImgOpacity: number = 1; // 截图组件透明度。
@State shotImgScale: ScaleOptions = { x: 1, y: 1 }; // 截图组件比例。
@State shotImgRotation: RotateOptions = { y: 0.5, angle: 0 } // 截图组件旋转角度。
截图组件的实现参考:
// 截图组件,置于预览流XComponent组件之上。
if (this.isShowBlur) {
Column() {
Image(this.screenshotPixelMap)
.blur(this.shotImgBlur)
.opacity(this.shotImgOpacity)
.rotate(this.shotImgRotation)// ArkUI提供,用于组件旋转。
.scale(this.shotImgScale)
.width(px2vp(1080)) // 与预览流XComponent宽高保持一致,图层在预览流之上。
.height(px2vp(1920))
.syncLoad(true)
}
.width(px2vp(1080))
.height(px2vp(1920))
}
- (按实际情况选择)实现模糊出现动效。
模式切换动效分两段实现,模糊出现动效和模糊消失动效。
模糊出现动效:用户点击或触控事件触发预览流截图,显示截图组件,截图清晰到模糊,覆盖旧预览流。
注意:由于图形提供的image.createPixelMapFromSurface接口是截取surface内容获取PixelMap,其内容和XComponent组件绘制逻辑不同,需要根据前后置 镜头做不同的图片内容旋转补偿 和组件旋转补偿。
async function showBlurAnim() {
console.info('showBlurAnim E');
// 获取已完成的surface截图。
let shotPixel = BlurAnimateUtil.getSurfaceShot();
// 后置。
if (this.curPosition === 0) {
console.info('showBlurAnim BACK');
// 直板机后置截图初始内容旋转补偿90°。
await shotPixel.rotate(90); //ImageKit提供,用于图片内容旋转。
// 直板机后置截图初始组件旋转补偿0°。
this.shotImgRotation = { y: 0.5, angle: 0 };
} else {
console.info('showBlurAnim FRONT');
// 直板机前置截图内容旋转补偿270°。
await shotPixel.rotate(270);
// 直板机前置截图组件旋转补偿180°。
this.shotImgRotation = { y: 0.5, angle: 180 };
}
this.screenshotPixelMap = shotPixel;
// 初始化动效参数。
this.shotImgBlur = 0; // 无模糊。
this.shotImgOpacity = 1; // 不透明。
this.isShowBlur = true; // 显示截图组件。
animateToImmediately(
{
duration: 200,
curve: Curve.Friction,
onFinish: async () => {
console.info('showBlurAnim X');
}
},
() => {
this.shotImgBlur = 48; // 截图组件模糊度变化动效。
}
);
}
- 实现模糊消失动效。
模糊消失动效:由新模式预览流首帧回调on('frameStart')触发,截图组件模糊到清晰,显示新预览流。
function hideBlurAnim(): void {
this.isShowBlack = false;
console.info('hideBlurAnim E');
animateToImmediately({
duration: 200,
curve: Curve.FastOutSlowIn,
onFinish: () => {
this.isShowBlur = false; // 模糊组件下树。
this.shotImgBlur = 0;
this.shotImgOpacity = 1;
console.info('hideBlurAnim X');
}
}, () => {
// 截图透明度变化动效。
this.shotImgOpacity = 0; // 截图组件透明度变化动效。
});
}
- (按实际情况选择)实现模糊翻转动效。
模糊翻转动效分两段实现,模糊翻转动效和模糊消失动效,其中模糊消失动效同第5步。
模糊翻转动效:分两段组件翻转实现,先向外翻转90°再向内翻转90°,同时还执行了模糊度、透明度、比例缩放等动效。
为保证预览流在翻转时不露出,需要构建一个闪黑组件用于遮挡XComponent组件,构建方式参考闪黑动效-步骤2。
/**
* 先向外翻转90°,前后置切换触发
*/
async function rotateFirstAnim() {
console.info('rotateFirstAnim E');
// 获取已完成的surface截图。
let shotPixel = BlurAnimateUtil.getSurfaceShot();
// 后置切前置。
if (this.curPosition === 1) {
console.info('rotateFirstAnim BACK');
// 直板机后置切前置截图初始内容旋转补偿90°。
await shotPixel.rotate(90); //ImageKit提供,用于图片内容旋转。
// 直板机后置切前置截图初始组件旋转补偿0°。
this.shotImgRotation = { y: 0.5, angle: 0 };
} else {
console.info('rotateFirstAnim FRONT');
// 直板机前置切后置截图初始内容旋转补偿270°。
await shotPixel.rotate(270);
// 直板机前置切后置截图初始组件旋转补偿180°。
this.shotImgRotation = { y: 0.5, angle: 180 };
}
this.screenshotPixelMap = shotPixel;
this.isShowBlack = true; // 显示闪黑组件,覆盖预览流保证视觉效果。
this.isShowBlur = true; // 显示截图组件。
animateToImmediately(
{
duration: 200,
delay: 50, // 时延保证组件缩放模糊动效先行,再翻转,视觉效果更好。
curve: curves.cubicBezierCurve(0.20, 0.00, 0.83, 1.00),
onFinish: () => {
console.info('rotateFirstAnim X');
// 在onFinish后触发二段翻转。
this.rotateSecondAnim();
}
},
() => {
// 截图向外翻转动效。
if (this.curPosition === 1) {
this.shotImgRotation = { y: 0.5, angle: 90 };
} else {
this.shotImgRotation = { y: 0.5, angle: 270 };
}
}
)
}
/**
* 再向内翻转90°
*/
async function rotateSecondAnim() {
console.info('rotateSecondAnim E');
// 获取已完成的surface截图。
let shotPixel = BlurAnimateUtil.getSurfaceShot();
// 后置。
if (this.curPosition === 1) {
// 直板机后置镜头内容旋转补偿90°。
await shotPixel.rotate(90);
// 组件旋转调整为-90°,保证二段翻转后,图片不是镜像的。
this.shotImgRotation = { y: 0.5, angle: 90 };
} else { // 前置。
// 直板机前置截图内容旋转补偿270°。
await shotPixel.rotate(270);
// 直板机前置截图组件旋转补偿180°。
this.shotImgRotation = { y: 0.5, angle: 180 };
}
this.screenshotPixelMap = shotPixel;
animateToImmediately(
{
duration: 200,
curve: curves.cubicBezierCurve(0.17, 0.00, 0.20, 1.00),
onFinish: () => {
console.info('rotateSecondAnim X');
}
},
() => {
// 截图向内翻转动效,翻转至初始状态。
if (this.curPosition === 1) {
this.shotImgRotation = { y: 0.5, angle: 0 };
} else {
this.shotImgRotation = { y: 0.5, angle: 180 };
}
}
)
}
/**
* 向外翻转90°同时
*/
function blurFirstAnim() {
console.info('blurFirstAnim E');
// 初始化动效参数。
this.shotImgBlur = 0; //无模糊。
this.shotImgOpacity = 1; //不透明。
this.shotImgScale = { x: 1, y: 1 };
animateToImmediately(
{
duration: 200,
curve: Curve.Sharp,
onFinish: () => {
console.info('blurFirstAnim X');
this.blurSecondAnim();
}
},
() => {
// 截图模糊动效。
this.shotImgBlur = 48;
// 截图比例缩小动效。
this.shotImgScale = { x: 0.75, y: 0.75 };
}
);
}
/**
* 向内翻转90°同时
*/
function blurSecondAnim() {
console.info('blurSecondAnim E');
animateToImmediately(
{
duration: 200,
curve: Curve.Sharp,
onFinish: () => {
console.info('blurSecondAnim X');
}
},
() => {
// 截图比例恢复动效。
this.shotImgScale = { x: 1, y: 1 };
}
)
}
- 按需触发动效。
模式切换动效触发:点击或触控模式按钮立即执行doSurfaceShot截图方法,更新StorageLink绑定modeChange的值,触发onModeChange方法,开始动效。
onModeChange(): void {
console.info('onModeChange');
this.showBlurAnim();
}
前后置切换动效触发:点击或触控前后置切换按钮立即执行doSurfaceShot截图方法,更新StorageLink绑定switchCamera的值,触发onSwitchCamera方法,开始动效。
onSwitchCamera(): void {
console.info('onSwitchCamera');
this.blurFirstAnim();
this.rotateFirstAnim();
}
模糊消失动效触发:监听预览流首帧回调on('frameStart'),更新StorageLink绑定frameStart的值,触发onFrameStart方法,开始动效。
onFrameStart(): void {
console.info('onFrameStart');
this.hideBlurAnim();
}
3.14 在Worker线程中使用相机(ArkTS)
Worker主要作用是为应用程序提供一个多线程的运行环境,可满足应用程序在执行过程中与主线程分离,在后台线程中运行一个脚本进行耗时操作,极大避免类似于计算密集型或高延迟的任务阻塞主线程的运行。
通常开发者使用相机功能需要创建相机会话,并持续接收处理预览流、拍照流、录像流等从而实现相关相机功能,这些密集型操作如果都放在主线程即UI线程,可能会阻塞UI绘制,推荐开发者在worker线程中实现相机功能。
3.14.1 开发步骤
- 创建worker线程文件,配置worker。
DevEco Studio支持一键生成Worker,在对应的{moduleName}目录下任意位置,点击鼠标右键 > New > Worker,即可自动生成Worker的模板文件及配置信息,无需再手动在build-profile.json5中进行相关配置 。
CameraWorker.ets实现参考:
import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
import CameraService from '../CameraService';
const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
// 自定义消息格式。
interface MessageInfo {
hasResolve: boolean;
type: string;
context: Context; // 注意worker线程中无法使用getContext()直接获取宿主线程context,需要通过消息从宿主线程通信到worker线程使用。
surfaceId: string;
}
workerPort.onmessage = async (e: MessageEvents) => {
const messageInfo: MessageInfo = e.data;
console.info(`worker onmessage type:${messageInfo.type}`)
if ('initCamera' === messageInfo.type) {
// 在worker线程中收到宿主线程初始化相机的消息。
console.info(`worker initCamera surfaceId:${messageInfo.surfaceId}`)
// 在worker线程中初始化相机。
await CameraService.initCamera(messageInfo.context, messageInfo.surfaceId);
} else if ('releaseCamera' === messageInfo.type) {
// 在worker线程中收到宿主线程释放相机的消息。
console.info('worker releaseCamera.');
// 在worker线程中释放相机。
await CameraService.releaseCamera();
}
}
workerPort.onmessageerror = (e: MessageEvents) => {
}
workerPort.onerror = (e: ErrorEvent) => {
}
-
创建相机服务代理类,调用CameraKit方法都放在这个类里执行。
import { BusinessError } from '@kit.BasicServicesKit';
import { camera } from '@kit.CameraKit';class CameraService {
private imageWidth: number = 1920;
private imageHeight: number = 1080;
private cameraManager: camera.CameraManager | undefined = undefined;
private cameras: Array<camera.CameraDevice> | Array<camera.CameraDevice> = [];
private cameraInput: camera.CameraInput | undefined = undefined;
private previewOutput: camera.PreviewOutput | undefined = undefined;
private photoOutput: camera.PhotoOutput | undefined = undefined;
private session: camera.PhotoSession | camera.VideoSession | undefined = undefined;// 初始化相机。
async initCamera(context: Context, surfaceId: string): Promise<void> {
console.info(initCamera surfaceId: ${surfaceId}
);
try {
await this.releaseCamera();
// 获取相机管理器实例。
this.cameraManager = camera.getCameraManager(context);
if (this.cameraManager === undefined) {
console.error('cameraManager is undefined');
return;
}
this.cameras = this.cameraManager.getSupportedCameras();// 创建cameraInput输出对象。 this.cameraInput = this.cameraManager.createCameraInput(this.cameras[0]); if (this.cameraInput === undefined) { console.error('Failed to create the camera input.'); return; } // 打开相机。 await this.cameraInput.open(); let previewProfile: camera.Profile = { format: camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP, size: { width: this.imageWidth, height: this.imageHeight } }; // 创建预览流输出。 this.previewOutput = this.cameraManager.createPreviewOutput(previewProfile, surfaceId); if (this.previewOutput === undefined) { console.error('Failed to create the preview stream.'); return; } let photoProfile: camera.Profile = { format: camera.CameraFormat.CAMERA_FORMAT_JPEG, size: { width: this.imageWidth, height: this.imageHeight } }; // 创建拍照流输出。 this.photoOutput = this.cameraManager.createPhotoOutput(photoProfile); if (this.photoOutput === undefined) { console.error('Failed to create the photoOutput.'); return; } // 创建相机会话,启动会话。 this.session = this.cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession; this.session.beginConfig(); this.session.addInput(this.cameraInput); this.session.addOutput(this.previewOutput); this.session.addOutput(this.photoOutput); await this.session.commitConfig(); await this.session.start(); } catch (error) { let err = error as BusinessError; console.error(`initCamera fail: ${JSON.stringify(err)}`); }
}
// 释放相机资源。
async releaseCamera(): Promise<void> {
console.info('releaseCamera is called');
try {
await this.previewOutput?.release();
await this.photoOutput?.release();
await this.session?.release();
await this.cameraInput?.close();
} catch (error) {
let err = error as BusinessError;
console.error(releaseCamera fail: error: ${JSON.stringify(err)}
);
} finally {
this.previewOutput = undefined;
this.photoOutput = undefined;
this.cameraManager = undefined;
this.session = undefined;
this.cameraInput = undefined;
}
console.info('releaseCamera success');
}
}export default new CameraService();
-
创建组件,用于显示预览流,在页面相关生命周期中构造ThreadWorker实例,在worker线程中完成相机初始化和释放。
import { worker } from '@kit.ArkTS';
@Entry
@Component
struct Index {
private mXComponentController: XComponentController = new XComponentController();
private surfaceId: string = '';
@State imageWidth: number = 1920;
@State imageHeight: number = 1080;
// 创建ThreadWorker对象获取worker实例。
private workerInstance: worker.ThreadWorker = new worker.ThreadWorker('entry/ets/workers/CameraWorker.ets');onPageShow(): void {
if ('' !== this.surfaceId) {
// 通过worker实例向worker线程发送消息初始化相机。
this.workerInstance.postMessage({
type: 'initCamera',
context: getContext(this),
surfaceId: this.surfaceId,
})
}
}onPageHide(): void {
// 通过worker实例向worker线程发送消息销毁相机。
this.workerInstance.postMessage({
type: 'releaseCamera',
})
}build() {
Column() {
Column() {
XComponent({
id: 'componentId',
type: XComponentType.SURFACE,
controller: this.mXComponentController
})
.onLoad(async () => {
console.info('onLoad is called');
// 初始化XComponent获取预览流surfaceId。
this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
let surfaceRect: SurfaceRect = {
surfaceWidth: this.imageHeight,
surfaceHeight: this.imageWidth
};
this.mXComponentController.setXComponentSurfaceRect(surfaceRect);
console.info(onLoad surfaceId: ${this.surfaceId}
);
if (!this.workerInstance) {
console.error('create stage worker failed');
return;
}
// 宿主线程向worker线程发送初始化相机消息。
this.workerInstance.postMessage({
type: 'initCamera',
context: getContext(this), // 将宿主线程的context传给worker线程使用。
surfaceId: this.surfaceId, // 将surfaceId传给worker线程使用。
})
})// The width and height of the surface are opposite to those of the XComponent.
.width(px2vp(this.imageHeight))
.height(px2vp(this.imageWidth))}.justifyContent(FlexAlign.Center) .height('90%') Text('WorkerDemo') .fontSize(36) } .justifyContent(FlexAlign.End) .height('100%') .width('100%')
}
}
3.14.2 trace对比
不使用worker:

使用woker:

3.15 适配相机旋转角度(ArkTS)
屏幕处于不同的屏幕状态时,原始图像需旋转不同的角度,以确保图像在合适的方向显示,效果如图所示。

开发者在预览、拍照、录像等不同场景下,如何适配相机的旋转角度。
3.15.1 创建会话
-
导入相机等相关模块。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit'; -
创建Session会话。
相机使用预览等功能前,均需创建相机会话,调用CameraManager类中的createSession方法创建一个会话,创建会话时需指定创建SceneMode为NORMAL_PHOTO或NORMAL_VIDEO,创建的session处于拍照或者录像模式。
function createPhotoSession(cameraManager: camera.CameraManager): camera.Session | undefined {
let session: camera.Session | undefined = undefined;
try {
session = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
} catch (error) {
let err = error as BusinessError;
hilog.error(`Failed to create the session instance. error: ${JSON.stringify(err)}`);
}
return session;
}
function createVideoSession(cameraManager: camera.CameraManager): camera.Session | undefined {
let session: camera.Session | undefined = undefined;
try {
session = cameraManager.createSession(camera.SceneMode.NORMAL_VIDEO) as camera.PhotoSession;
} catch (error) {
let err = error as BusinessError;
hilog.error(`Failed to create the session instance. error: ${JSON.stringify(err)}`);
}
return session;
}
3.15.2 预览
完成会话创建后,可根据实际需求,配置输出流。
- 调用PreviewOutput类中的getPreviewRotation接口,获取预览旋转角度。
displayRotation:显示设备的屏幕旋转角度,可通过display.getDefaultDisplaySync获取Display对象并读取其rotation属性值,并将对应角度填入。
例:Display.rotation = 1,表示显示设备屏幕顺时针旋转为90°,此处displayRotation填入90。
import { display } from '@kit.ArkUI';
let initDisplayRotation = display.getDefaultDisplaySync().rotation;
let imageRotation = initDisplayRotation * camera.ImageRotation.ROTATION_90;
该接口需要在session调用commitConfig完成配流后调用,如果存在异步执行的情况,previewOutput未添加到session里或者已调用的session.release,导致两者关系未绑定,此时调用getPreviewRotation,则会调用失败,并抛出错误码CameraErrorCode.SERVICE_FATAL_ERROR。
function getPreviewRotation(previewOutput: camera.PreviewOutput, imageRotation : camera.ImageRotation): camera.ImageRotation {
let previewRotation: camera.ImageRotation = camera.ImageRotation.ROTATION_0;
try {
previewRotation = previewOutput.getPreviewRotation(imageRotation);
hilog.log(`Preview rotation is: ${previewRotation}`);
} catch (error) {
// 失败返回错误码error.code并处理
let err = error as BusinessError;
hilog.error(`The previewOutput.getPreviewRotation call failed. error code: ${err.code}`);
}
return previewRotation;
}
- 调用PreviewOutput类中的setPreviewRotation,设置图像的预览旋转角度。
该接口需要在session调用commitConfig完成配流后调用,如果多次。
- previewRotation:预览旋转角度,取上一步getPreviewRotation的返回值。
- isDisplayLocked:可选入参,默认为false。当设置为false,即屏幕方向未锁定,预览旋转角度将根据相机镜头角度+屏幕显示旋转角度的值计算;当设置为true,Surface旋转锁定,不跟随窗口变化,旋转角度仅取相机镜头角度计算。
function setPreviewRotation(previewOutput: camera.PreviewOutput, previewRotation : camera.ImageRotation, isDisplayLocked: boolean): void {
try {
previewOutput.setPreviewRotation(previewRotation, isDisplayLocked);
} catch (error) {
// 失败返回错误码error.code并处理
let err = error as BusinessError;
hilog.error(`The previewOutput.setPreviewRotation call failed. error code: ${err.code}`);
}
}
3.15.3 录像
完成会话创建后,开发者可根据实际需求,配置输出流。录像的旋转角度与重力方向(即设备旋转角度)相关。
- 调用VideoOutput类中的getVideoRotation可以获取到录像的旋转角度。
该接口需要在session调用commitConfig完成配流后调用。
deviceDegree:设备旋转角度。获取方式请见计算设备旋转角度。
function getVideoRotation(videoOutput: camera.VideoOutput, deviceDegree: number): camera.ImageRotation {
let videoRotation: camera.ImageRotation = camera.ImageRotation.ROTATION_0;
try {
videoRotation = videoOutput.getVideoRotation(deviceDegree);
hilog.info(`Video rotation is: ${videoRotation}`);
} catch (error) {
// 失败返回错误码error.code并处理
let err = error as BusinessError;
hilog.error(`The videoOutput.getVideoRotation call failed. error code: ${err.code}`);
}
return videoRotation;
}
-
将录像的旋转角度写入AVMetadata.videoOrientation。
-
其余参数的配置及启动录像。
3.15.4 计算设备旋转角度
当前可通过调用once(type: SensorId.GRAVITY, callback: Callback<GravityResponse>)获取一次重力传感器在x、y、z三个方向上的数据,计算得出设备旋转角度deviceDegree,示例如下所示。
如果无法获得重力传感器数据,需要申请重力传感器权限ohos.permission.ACCELEROMETER。权限申请请参考声明权限,如何获取传感器数据请参考传感器开发指导。
import { Decimal } from '@kit.ArkTS';
import { sensor } from '@kit.SensorServiceKit';
import { BusinessError } from '@ohos.base';
getRealData(data: sensor.GravityResponse): number {
let getDeviceDegree: number = 0;
hilog.info('Succeeded in invoki e. X-coordinate component: ' + data.x);
hilog.info('Succeeded in invoking once. Y-coordinate component: ' + data.y);
hilog.info('Succeeded in invoking once. Z-coordinate component: ' + data.z);
let x = data.x;
let y = data.y;
let z = data.z;
if ((x * x + y * y) * 3 < z * z) {
return getDeviceDegree;
} else {
let sd: Decimal = Decimal.atan2(y, -x);
let sc: Decimal = Decimal.round(Number(sd) / 3.141592653589 * 180)
getDeviceDegree = 90 - Number(sc);
getDeviceDegree = getDeviceDegree >= 0 ? getDeviceDegree % 360 : getDeviceDegree % 360 + 360;
}
return getDeviceDegree;
}
getGravity() : Promise<number> {
sensor.getSensorList((error: BusinessError, data: Array<sensor.Sensor>) => {
for (let i = 0; i < data.length; i++) {
if (data[i].sensorId === sensor.SensorId.GRAVITY) {
this.isSupported = true;
break;
}
}});
if (this.isSupported === true) {
const promise: Promise<number> = new Promise((resolve, reject) => {
sensor.once(sensor.SensorId.GRAVITY, (data: sensor.GravityResponse) => {
resolve(this.getRealData(data));
});
})
return promise;
} else {
const promise: Promise<number> = new Promise((resolve, reject) => {
sensor.once(sensor.SensorId.ACCELEROMETER, (data: sensor.AccelerometerResponse) => {
resolve(this.getRealData(data as sensor.GravityResponse));
});
})
return promise;
}
}
3.16 安全相机(ArkTS)
安全相机主要为银行等有活体检测等安全诉求的应用提供,安全相机的使用需要加密算法框架及可信应用服务。
应用具体使用步骤如下:
- 通过Camera Kit打开安全摄像头,成功打开安全摄像头后,Camera Kit会返回给应用一个安全摄像头序列号。
- 通过DeviceSecurity Kit来创建证明密钥(安全摄像头序列号会作为入参)、初始化证明会话。DeviceSecurity Kit初始化证明会话完成后会返回给应用匿名证书链。
- 通过Camera Kit配置安全相机输入输出流,重点是配置安全数据流,注册安全数据流每帧安全图像回调监听。
- 解析安全数据流每帧安全图像,在服务器侧完成安全图像的签名验证。
说明
当前文档主要说明通过Camera Kit完成的步骤,证明会话相关步骤需通过DeviceSecurity Kit完成,具体可参考可信应用服务-安全摄像头。

3.16.1 开发步骤
-
导入依赖,需要导入相机框架相关领域依赖。
import { camera } from '@kit.CameraKit';
import { image } from '@kit.ImageKit'; -
选择支持安全相机的设备。
通过CameraManager类中的getSupportedSceneModes方法,可以获取当前设备支持的所有模式,如果当前设备支持安全相机模式,即可使用该设备做后续安全相机操作。
当前安全相机仅支持手机前置镜头。
function isSecureCamera(cameraManager: camera.CameraManager, cameraDevice: camera.CameraDevice): boolean {
let sceneModes: Array<camera.SceneMode> = cameraManager.getSupportedSceneModes(cameraDevice);
const secureMode = sceneModes.find(mode => mode === camera.SceneMode.SECURE_PHOTO);
if (secureMode) {
console.info('current device support secure camera!');
return true;
} else {
console.info('current device not support secure camera!');
return false;
}
}
let secureCamera: camera.CameraDevice;
function getSecureCamera(cameraManager: camera.CameraManager): void {
let cameraArray: Array<camera.CameraDevice> = cameraManager.getSupportedCameras();
for (let index = 0; index < cameraArray.length; index++) {
if (isSecureCamera(cameraManager, cameraArray[index])) {
secureCamera = cameraArray[index];
}
}
}
- 查询相机设备在安全模式下支持的输出能力。
通过CameraManager类getSupportedOutputCapability方法,可获取设备在安全模式下支持的输出能力。
当前安全相机仅支持输出预览流,推荐预览流使用640 * 480分辨率。
function getSupportedOutputCapability(cameraManager: camera.CameraManager, secureCamera: camera.CameraDevice): void {
let outputCap: camera.CameraOutputCapability =
cameraManager.getSupportedOutputCapability(secureCamera, camera.SceneMode.SECURE_PHOTO);
let previewProfilesArray: Array<camera.Profile> = outputCap.previewProfiles;
}
- 创建设备输入输出。
安全相机需要创建两路输出流:
- 一路是普通的预览流,用于界面显示,普通预览流的创建流程请参考预览开发指导。
- 一路是安全数据流,用于安全服务校验,安全数据流需要通过image.createImageReceiver创建图像接收类ImageReceiver,再通过其getReceivingSurfaceId方法获取surfaceId。
说明安全数据流没有单独的数据类型,同属于预览流,输出能力与预览流保持一致,创建ImageReceiver仅支持JPEG格式。
async function createInputAndOutputs(cameraManager: camera.CameraManager,
secureCamera: camera.CameraDevice,
previewProfile: camera.Profile,
previewSurfaceId: string): Promise<void> {
// 创建输入流
let cameraInput: camera.CameraInput = cameraManager.createCameraInput(secureCamera);
// 创建普通预览输出流
let previewOutput: camera.PreviewOutput = cameraManager.createPreviewOutput(previewProfile, previewSurfaceId);
// 创建安全数据输出流
const receiver: image.ImageReceiver =
image.createImageReceiver({ width: previewProfile.size.width, height: previewProfile.size.height },
image.ImageFormat.JPEG, 8);
const secureSurfaceId: string = await receiver.getReceivingSurfaceId();
let secureOutput: camera.PreviewOutput = cameraManager.createPreviewOutput(previewProfile, secureSurfaceId);
}
- 打开安全设备。
CameraInput提供了open(isSecureEnabled)方法用于打开安全相机并返回安全摄像头序列号,该序列号是安全模块创建证明会话的必须参数。
仅当isSecureEnabled为true时,才会打开安全相机,并有安全序列号返回。
async function openCamera(cameraInput: camera.CameraInput) {
const seqId: bigint = await cameraInput.open(true);
}
-
使用Device Security Kit的能力,创建证明密钥、打开证明会话。请参考Device Security Kit(设备安全服务)的开发指导:可信应用服务-安全摄像头。
-
创建安全相机会话,配流启流。
创建安全相机模式的会话,将输入流、输出流加入会话,需要将安全数据流通过SecureSession的addSecureOutput方法标记成安全输出。
async function openSession(cameraManager: camera.CameraManager,
cameraInput: camera.CameraInput,
previewOutput: camera.PreviewOutput,
secureOutput: camera.PreviewOutput): Promise<void> {
try {
let secureSession: camera.SecureSession = cameraManager.createSession(camera.SceneMode.SECURE_PHOTO);
if (secureSession === undefined) {
console.error('create secureSession failed!');
}
secureSession.beginConfig();
secureSession.addInput(cameraInput);
secureSession.addOutput(previewOutput);
secureSession.addOutput(secureOutput);
secureSession.addSecureOutput(secureOutput); // 把secureOutput标记成安全输出
await secureSession.commitConfig();
await secureSession.start();
} catch (err) {
console.error('openSession failed!');
}
}
-
安全模式会话正常启动后,预览流和安全数据流数据逐帧上报,安全数据流每帧数据可以通过ImageReceiver注册imageArrival回调获取。
function onBuffer(receiver: image.ImageReceiver): void {
receiver.on('imageArrival', () => {
// 从ImageReceiver读取下一张图片
receiver.readNextImage().then((img: image.Image) => {
// 从图像中获取组件缓存
img.getComponent(image.ComponentType.JPEG).then((component: image.Component) => {
// 安全数据流内容,应用通过解析该buffer内容完成签名认证
const buffer = component.byteBuffer;
console.info('Succeeded in getting component byteBuffer.');
})
})
})
}
解析安全数据流每帧安全图像,在服务器侧完成安全图像的签名验证。
如果有在端侧验证图像数据或地理位置数据签名的需求,可参考验证签名中与安全图像相关的部分。
3.17 动态调整预览帧率(ArkTS)
动态调整帧率是直播、视频等场景下控制预览效果的重要能力之一。应用可通过此能力,显性地控制流输出帧率,以适应不同帧率下的业务目标。
某些场景下降低帧率可在相机设备启用时降低功耗。
3.17.1 约束与限制
支持的帧率范围及帧率的设置依赖于硬件能力的实现,不同的硬件平台可能拥有不同的默认帧率。
3.17.2 开发流程
相机使用预览功能前,均需要创建相机会话。完成会话配置后,应用提交和开启会话,才可以开始调用相机相关功能。
流程图如下所示:

与普通的预览流程相比,动态调整预览帧率的注意点如图上标识:
-
调用createSession创建会话(Session)时,需要指定模式为NORMAL_PHOTO或NORMAL_VIDEO。
仅当Session处于NORMAL_PHOTO或NORMAL_VIDEO模式时,支持调整预览流帧率。调整帧率的创建会话方式见创建Session会话并指定模式。
-
动态调整帧率的操作,可在启动预览前后任意时刻调用。
-
动态调整帧率在预览里属于可选操作,可以完成:
- 查询当前支持调整的帧率范围
- 设置当前帧率
- 获取当前生效的帧率设置
如何配置会话(Session)、释放资源,请参考会话管理 > 预览,或是完整流程示例。
3.17.3 创建Session会话并指定模式
相机使用预览等功能前,均需创建相机会话,调用CameraManager的createSession创建一个会话。
创建会话时需指定SceneMode为NORMAL_PHOTO或NORMAL_VIDEO,创建出的Session处于拍照或录像模式。
以创建Session会话并指定为NORMAL_PHOTO模式为例:
function createPhotoSession(cameraManager: camera.CameraManager): camera.Session | undefined {
let session: camera.Session | undefined = undefined;
try {
// 创建Session会话并指定为NORMAL_PHOTO模式
session = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to create the session instance. error: ${JSON.stringify(err)}`);
}
return session;
}
3.17.4 调整帧率
- 调用PreviewOutput的getSupportedFrameRates,查询当前支持的帧率范围。
说明
需要在Session调用commitConfig完成配流之后调用。
function getSupportedFrameRange(previewOutput: camera.PreviewOutput): Array<camera.FrameRateRange> {
// 获取支持的帧率范围,不同的硬件平台可能提供不同的帧率范围
return previewOutput.getSupportedFrameRates();
}
- 根据实际开发需求,调用PreviewOutput类提供的setFrameRate接口对帧率进行动态调整。
说明
- 需要在Session调用commitConfig完成配流之后调用。
- 可在Session调用start启动预览前后任意时刻调用。
function setFrameRate(previewOutput: camera.PreviewOutput, minFps: number, maxFps: number): void {
try {
previewOutput.setFrameRate(minFps, maxFps);
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to setFrameRate for previewOutput. error: ${JSON.stringify(err)}`);
}
}
- (可选)通过PreviewOutput类提供的getActiveFrameRate接口查询已设置过并生效的帧率。
仅通过setFrameRate接口显性设置过帧率才可查询当前生效帧率信息。
function getActiveFrameRange(previewOutput: camera.PreviewOutput): camera.FrameRateRange {
return previewOutput.getActiveFrameRate();
}
3.18 使用相机预配置(ArkTS)
相机预配置(Preconfig),对常用的场景和分辨率进行了预配置集成,可简化开发相机应用流程,提高应用的开发效率。
系统针对拍照(PhotoSession)、录像(VideoSession)两类场景,提供了preconfig接口帮助开发者快速完成相机参数配置。推荐仅需要自定义拍照界面的无需开发专业相机应用的开发者,使用相机预配置功能快速开发应用。
以拍照(PhotoSession)为例,与遵循通用流程开发,有以下差异:

其他相关能力:
- CameraPicker:无需开发相机功能,拉起系统相机获取照片或视频。
- 调用全量相机接口开发:可开发自定义界面、分辨率、图像效果的专业相机应用。
3.18.1 规格说明
系统提供了4种预配置类型(PreconfigType):
- PRECONFIG_720P
- PRECONFIG_1080P
- PRECONFIG_4K
- PRECONFIG_HIGH_QUALITY。
3种画幅比例规格(PreconfigRatio):
- 1:1画幅(PRECONFIG_RATIO_1_1)
- 4:3画幅(PRECONFIG_RATIO_4_3)
- 16:9画幅(PRECONFIG_RATIO_16_9)。
注意由于不同的设备所支持的能力不同。使用相机预配置(preconfig)功能时,需要先调用canPreconfig检查对应的PreconfigType和PreconfigRatio的组合在当前设备上是否支持。
在不同的画幅比例下,其分辨率规格不同,详见下表。
-
普通拍照模式下的预览输出
预配置类型PreconfigType PRECONFIG_RATIO_1_1 PRECONFIG_RATIO_4_3 PRECONFIG_RATIO_16_9 PRECONFIG_720P 720x720 960x720 1280x720 PRECONFIG_1080P 1080x1080 1440x1080 1920x1080 PRECONFIG_4K 1080x1080 1440x1080 1920x1080 PRECONFIG_HIGH_QUALITY 1440x1440 1920x1440 2560x1440 -
普通拍照模式下的拍照输出
预配置类型PreconfigType PRECONFIG_RATIO_1_1 PRECONFIG_RATIO_4_3 PRECONFIG_RATIO_16_9 PRECONFIG_720P 720x720 960x720 1280x720 PRECONFIG_1080P 1080x1080 1440x1080 1920x1080 PRECONFIG_4K 2160x2160 2880x2160 3840x2160 PRECONFIG_HIGH_QUALITY 跟随Sensor(镜头)最大能力 跟随Sensor(镜头)最大能力 跟随Sensor(镜头)最大能力 -
普通录像模式下的预览输出
预配置类型PreconfigType PRECONFIG_RATIO_1_1 PRECONFIG_RATIO_4_3 PRECONFIG_RATIO_16_9 PRECONFIG_720P 720x720 960x720 1280x720 PRECONFIG_1080P 1080x1080 1440x1080 1920x1080 PRECONFIG_4K 1080x1080 1440x1080 1920x1080 PRECONFIG_HIGH_QUALITY 1080x1080 1440x1080 1920x1080 -
普通录像模式下的录像输出
预配置类型PreconfigType PRECONFIG_RATIO_1_1 PRECONFIG_RATIO_4_3 PRECONFIG_RATIO_16_9 PRECONFIG_720P 720x720 960x720 1280x720 PRECONFIG_1080P 1080x1080 1440x1080 1920x1080 PRECONFIG_4K 2160x2160 2880x2160 3840x2160 PRECONFIG_HIGH_QUALITY 2160x2160 2880x2160 3840x2160 -
普通录像模式下的拍照输出
预配置类型PreconfigType PRECONFIG_RATIO_1_1 PRECONFIG_RATIO_4_3 PRECONFIG_RATIO_16_9 PRECONFIG_720P 跟随Sensor(镜头)最大能力 跟随Sensor(镜头)最大能力 跟随Sensor(镜头)最大能力 PRECONFIG_1080P 跟随Sensor(镜头)最大能力 跟随Sensor(镜头)最大能力 跟随Sensor(镜头)最大能力 PRECONFIG_4K 跟随Sensor(镜头)最大能力 跟随Sensor(镜头)最大能力 跟随Sensor(镜头)最大能力 PRECONFIG_HIGH_QUALITY 跟随Sensor(镜头)最大能力 跟随Sensor(镜头)最大能力 跟随Sensor(镜头)最大能力
3.18.2 开发步骤
-
导入相关接口。
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit'; -
创建输出流Output。
此处以创建预览流和拍照流为例。
创建预览输出流时,涉及参数surfaceId。XComponent组件为预览流提供Surface
// 创建预览输出流
let previewOutput: camera.PreviewOutput | undefined = undefined;
try {
previewOutput = cameraManager.createPreviewOutput(surfaceId);
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to create the PreviewOutput instance. error code: ${err.code}`);
}
if (previewOutput === undefined) {
return;
}
// 创建拍照输出流
let photoOutput: camera.PhotoOutput | undefined = undefined;
try {
photoOutput = cameraManager.createPhotoOutput();
} catch (error) {
let err = error as BusinessError;
console.error('Failed to createPhotoOutput errorCode = ' + err.code);
}
if (photoOutput === undefined) {
return;
}
-
调用CameraManager类中的createCameraInput方法,创建输入流Input。
let cameraInput: camera.CameraInput | undefined = undefined;
try {
cameraInput = cameraManager.createCameraInput(cameraArray[0]);
} catch (error) {
let err = error as BusinessError;
console.error('Failed to createCameraInput errorCode = ' + err.code);
}
if (cameraInput === undefined) {
return;
}
// 打开相机
await cameraInput.open(); -
调用createSession创建会话(Session)。
说明
SceneMode需要指定为NORMAL_PHOTO或NORMAL_VIDEO,对应拍照场景PhotoSession和录像场景VideoSession。
//创建会话
let photoSession: camera.PhotoSession | undefined = undefined;
try {
photoSession = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
} catch (error) {
let err = error as BusinessError;
console.error('Failed to create the session instance. errorCode = ' + err.code);
}
if (photoSession === undefined) {
return;
}
-
调用canPreconfig检查对应的PreconfigType和PreconfigRatio的组合在当前设备上是否支持。确认支持后,调用preconfig启用Preconfig配置。
// 查询Preconfig能力
try {
let isPreconfigSupport = photoSession.canPreconfig(camera.PreconfigType.PRECONFIG_1080P);
if (!isPreconfigSupport) {
console.error('PhotoSession canPreconfig check fail.');
return;
}
} catch (error) {
let err = error as BusinessError;
console.error('Failed to call canPreconfig. errorCode = ' + err.code);
return;
}// 配置Preconfig
try {
photoSession.preconfig(camera.PreconfigType.PRECONFIG_1080P);
} catch (error) {
let err = error as BusinessError;
console.error('Failed to call preconfig. errorCode = ' + err.code);
return;
} -
Session添加Input和Output。
说明
Session调用preconfig接口成功之后,Session内部会将预置数据准备好,如果向Session中进行添加未配置Profile的Output,Session则会对相应的Output进行配置对应Profile。如果向Session中添加已配置Profile的Output,则Session的预配置数据不生效。
// 开始配置会话
try {
photoSession.beginConfig();
} catch (error) {
let err = error as BusinessError;
console.error('Failed to beginConfig. errorCode = ' + err.code);
}
// 向会话中添加相机输入流
try {
photoSession.addInput(cameraInput);
} catch (error) {
let err = error as BusinessError;
console.error('Failed to addInput. errorCode = ' + err.code);
}
// 向会话中添加预览输出流
try {
photoSession.addOutput(previewOutput);
} catch (error) {
let err = error as BusinessError;
console.error('Failed to addOutput(previewOutput). errorCode = ' + err.code);
}
// 向会话中添加拍照输出流
try {
photoSession.addOutput(photoOutput);
} catch (error) {
let err = error as BusinessError;
console.error('Failed to addOutput(photoOutput). errorCode = ' + err.code);
}
// 提交会话配置
await photoSession.commitConfig();
-
启动Session。
// 启动会话
await photoSession.start().then(() => {
console.info('Promise returned to indicate the session start success.');
});