【鸿蒙开发】第二十章 Camera相机服务
1 简介
开发者通过调用Camera Kit(相机服务)
提供的接口可以开发相机应用,应用通过访问和操作相机硬件,实现基础操作,如预览
、拍照
和录像
;还可以通过接口组合完成更多操作,如控制闪光灯和曝光时间
、对焦或调焦
等。
2 开发模型
相机调用摄像头采集、加工图像视频数据,精确控制对应的硬件,灵活输出图像、视频内容,满足多镜头硬件适配(如广角、长焦、TOF)、多业务场景适配(如不同分辨率、不同格式、不同效果)的要求。
相机的工作流程如图所示,可概括为相机输入设备管理、会话管理和相机输出管理三部分。
- 相机设备调用摄像头采集数据,作为相机输入流。
- 会话管理可配置输入流,即选择哪些镜头进行拍摄。另外还可以配置闪光灯、曝光时间、对焦和调焦等参数,实现不同效果的拍摄,从而适配不同的业务场景。应用可以通过切换会话满足不同场景的拍摄需求。
- 配置相机的输出流,即将内容以预览流、拍照流或视频流输出。
相机应用通过控制相机,实现图像显示(预览)、照片保存(拍照)、视频录制(录像)等基础操作。在实现基本操作过程中,相机服务会控制相机设备采集和输出数据,采集的图像数据在相机底层的设备硬件接口(HDI,Hardware Device Interfaces),直接通过BufferQueue
传递到具体的功能模块进行处理。BufferQueue在应用开发中无需关注,用于将底层处理的数据及时送到上层进行图像显示。
以视频录制为例进行说明,相机应用在录制视频过程中,媒体录制服务先创建一个视频Surface
用于传递数据,并提供给相机服务,相机服务可控制相机设备采集视频数据,生成视频流。采集的数据通过底层相机HDI处理后,通过Surface
将视频流传递给媒体录制服务,媒体录制服务对视频数据进行处理后,保存为视频文件,完成视频录制。
3 开发准备
相机应用开发的主要流程包含开发准备
、设备输入
、会话管理
、预览
、拍照和录像
等。
在开发相机应用时,需要先申请相机相关权限
,确保应用拥有访问相机硬件及其他功能的权限,需要的权限如下表。在申请权限前,请保证符合权限使用的基本原则。
以上权限的授权方式均为user_grant(用户授权)
,即开发者在module.json5
文件中配置对应的权限后,需要使用接口abilityAccessCtrl.requestPermissionsFromUser
去校验当前用户是否已授权。如果是,应用可以直接访问/操作目标对象;否则需要弹框向用户申请授权
。
说明: 即使用户曾被授予过权限,应用在调用此权限保护的接口前,也应该先检查是否有权限。不能把之前授予的状态持久化,因为用户在动态授予后可能通过“设置”取消应用权限。
当前相机提供了ArkTS
和C++
两种开发语言的开发指导,如下表所示。
4 相机开发(ArkTS)
4.1 设备输入
在开发一个相机应用前,需要先创建一个独立的相机设备,应用通过调用和控制相机设备,完成预览、拍照和录像等基础操作。
4.1.1 开发步骤
详细的API说明请参考Camera API参考。
// 导入camera接口
import camera from '@ohos.multimedia.camera';
import { BusinessError } from '@ohos.base';
import common from '@ohos.app.ability.common';// 通过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); // 获取相机IDconsole.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 [];}
}// 通过getSupportedOutputCapability方法,获取当前设备支持的所有输出流,如预览流、拍照流等。输出流在CameraOutputCapability中的各个profile字段中。
async function getSupportedOutputCapability(cameraDevice: camera.CameraDevice, cameraManager: camera.CameraManager, sceneMode: camera.SceneMode): Promise<camera.CameraOutputCapability | 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();// 获取相机设备支持的输出流能力let cameraOutputCapability: camera.CameraOutputCapability = cameraManager.getSupportedOutputCapability(cameraDevice, sceneMode);if (!cameraOutputCapability) {console.error("cameraManager.getSupportedOutputCapability error");return undefined;}console.info("outputCapability: " + JSON.stringify(cameraOutputCapability));return cameraOutputCapability;
}
4.1.2 状态监听
在相机应用开发过程中,可以随时监听相机状态,包括新相机的出现
、相机的移除
、相机的可用状态
。在回调函数中,通过相机ID、相机状态这两个参数进行监听,如当有新相机出现时,可以将新相机加入到应用的备用相机中。
通过注册cameraStatus
事件,通过回调返回监听结果,callback
返回CameraStatusInfo
参数,参数的具体内容可参考相机管理器回调接口实例CameraStatusInfo
。
function onCameraStatus(cameraManager: camera.CameraManager): void {cameraManager.on('cameraStatus', (err: BusinessError, cameraStatusInfo: camera.CameraStatusInfo) => {console.info(`camera: ${cameraStatusInfo.camera.cameraId}`);console.info(`status: ${cameraStatusInfo.status}`);});
}
4.2 会话管理
相机使用预览
、拍照
、录像
、元数据
功能前,均需要创建相机会话
。
在会话中,可以完成以下功能:
-
配置相机的输入流和输出流。相机在拍摄前,必须完成输入输出流的配置。 配置输入流即添加设备输入,对用户而言,相当于选择设备的某一摄像头拍摄;配置输出流,即选择数据将以什么形式输出。当应用需要实现拍照时,输出流应配置为预览流和拍照流,预览流的数据将显示在
XComponent
组件上,拍照流的数据将通过ImageReceiver
接口的能力保存到相册中。 -
添加闪光灯、调整焦距等配置。具体支持的配置及接口说明请参考
Camera API
参考。 -
会话切换控制。应用可以通过移除和添加输出流的方式,切换相机模式。如当前会话的输出流为拍照流,应用可以将拍照流移除,然后添加视频流作为输出流,即完成了拍照到录像的切换。
完成会话配置后,应用提交和开启会话,可以开始调用相机相关功能。
4.2.1 开发步骤
// 1. 导入相关接口
import camera from '@ohos.multimedia.camera';
import { BusinessError } from '@ohos.base';// 2. 调用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;
}// 3. 调用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)}`);}
}// 4. 使能。向会话中添加相机的输入流和输出流,调用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)}`);}
}// 5. 会话控制。调用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)}`);}
}
4.3 预览
预览是启动相机后看见的画面,通常在拍照和录像前执行。
4.3.1 开发步骤
详细的API说明请参考Camera API参考。
// 导入camera接口,接口中提供了相机相关的属性和方法,导入方法如下。import camera from '@ohos.multimedia.camera';
import { BusinessError } from '@ohos.base';// 创建Surface。
// XComponent组件为预览流提供的Surface,而XComponent的能力由UI提供,相关介绍可参考XComponent组件参考。
// 说明: 预览流与录像输出流的分辨率的宽高比要保持一致,如示例代码中宽高比为1920:1080 = 16:9,则需要预览流中的分辨率的宽高比也为16:9,如分辨率选择640:360,或960:540,或1920:1080,以此类推。// xxx.ets
// 创建XComponentController
@Component
struct XComponentPage {// 创建XComponentControllermXComponentController: XComponentController = new XComponentController;surfaceId: string = '';build() {Flex() {// 创建XComponentXComponent({id: '',type: 'surface',libraryname: '',controller: this.mXComponentController}).onLoad(() => {// 设置Surface宽高(1920*1080),预览尺寸设置参考前面 previewProfilesArray 获取的当前设备所支持的预览分辨率大小去设置// 预览流与录像输出流的分辨率的宽高比要保持一致this.mXComponentController.setXComponentSurfaceSize({surfaceWidth:1920,surfaceHeight:1080});// 获取Surface IDthis.surfaceId = this.mXComponentController.getXComponentSurfaceId();}).width('1920px').height('1080px')}}
}//通过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方法输出预览流,接口调用失败会返回相应错误码,错误码类型参见CameraErrorCode。
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();
}
4.3.2 状态监听
在相机应用开发过程中,可以随时监听预览输出流状态,包括预览流启动
、预览流结束
、预览流输出错误
。
// 通过注册固定的frameStart回调函数获取监听预览启动结果,previewOutput创建成功时即可监听,预览第一次曝光时触发,有该事件返回结果则认为预览流已启动。
function onPreviewOutputFrameStart(previewOutput: camera.PreviewOutput): void {previewOutput.on('frameStart', () => {console.info('Preview frame started');});
}// 通过注册固定的frameEnd回调函数获取监听预览结束结果,previewOutput创建成功时即可监听,预览完成最后一帧时触发,有该事件返回结果则认为预览流已结束。
function onPreviewOutputFrameEnd(previewOutput: camera.PreviewOutput): void {previewOutput.on('frameEnd', () => {console.info('Preview frame ended');});
}// 通过注册固定的error回调函数获取监听预览输出错误结果,callback返回预览输出接口使用错误时对应的错误码,错误码类型参见CameraErrorCode。
function onPreviewOutputError(previewOutput: camera.PreviewOutput): void {previewOutput.on('error', (previewOutputError: BusinessError) => {console.error(`Preview output error code: ${previewOutputError.code}`);});
}
4.4 拍照
拍照是相机的最重要功能之一,拍照模块基于相机复杂的逻辑,为了保证用户拍出的照片质量,在中间步骤可以设置分辨率
、闪光灯
、焦距
、照片质量
及旋转角度
等信息。
4.4.1 开发步骤
详细的API说明请参考Camera API参考。
// 导入image接口。创建拍照输出流的SurfaceId以及拍照输出的数据,都需要用到系统提供的image接口能力,导入image接口的方法如下。import image from '@ohos.multimedia.image';
import camera from '@ohos.multimedia.camera';
import fs from '@ohos.file.fs';
import PhotoAccessHelper from '@ohos.file.photoAccessHelper';
import { BusinessError } from '@ohos.base';// 创建拍照输出流。
// 通过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的上下文信息。let context = getContext(this);async function savePicture(buffer: ArrayBuffer, img: image.Image) {let photoAccessHelper: PhotoAccessHelper.PhotoAccessHelper = PhotoAccessHelper.getPhotoAccessHelper(context);let options: PhotoAccessHelper.CreateOptions = {title: Date.now().toString()};let photoUri: string = await photoAccessHelper.createAsset(PhotoAccessHelper.PhotoType.IMAGE, 'jpg', options);//createAsset的调用需要ohos.permission.READ_IMAGEVIDEO和ohos.permission.WRITE_IMAGEVIDEO的权限let file: fs.File = fs.openSync(photoUri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);await fs.write(file.fd, buffer);fs.closeSync(file);img.release();
}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;}savePicture(buffer, imageObj);});});
}// 参数配置。
// 配置相机的参数可以调整拍照的一些功能,包括闪光灯、变焦、焦距等。
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中可以设置照片的质量和旋转角度,第二参数为回调函数。function capture(captureLocation: camera.Location, photoOutput: camera.PhotoOutput): void {let settings: camera.PhotoCaptureSetting = {quality: camera.QualityLevel.QUALITY_LEVEL_HIGH, // 设置图片质量高rotation: camera.ImageRotation.ROTATION_0, // 设置图片旋转角度0location: 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.');});
}
4.4.2 状态监听
在相机应用开发过程中,可以随时监听拍照输出流状态,包括拍照流开始、拍照帧的开始与结束、拍照输出流的错误。
// 通过注册固定的captureStart回调函数获取监听拍照开始结果,photoOutput创建成功时即可监听,拍照第一次曝光时触发,该事件返回此次拍照的captureId。
function onPhotoOutputCaptureStart(photoOutput: camera.PhotoOutput): void {photoOutput.on('captureStartWithInfo', (err: BusinessError, captureStartInfo: camera.CaptureStartInfo) => {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) => {console.info(`photo capture end, captureId : ${captureEndInfo.captureId}`);console.info(`frameCount : ${captureEndInfo.frameCount}`);});
}// 通过注册固定的error回调函数获取监听拍照输出流的错误结果。callback返回拍照输出接口使用错误时的对应错误码,错误码类型参见CameraErrorCode。
function onPhotoOutputError(photoOutput: camera.PhotoOutput): void {photoOutput.on('error', (error: BusinessError) => {console.error(`Photo output error code: ${error.code}`);});
}
4.5 录像
录像也是相机应用的最重要功能之一,录像是循环帧的捕获。对于录像的流畅度,开发者可以参考拍照中的步骤4,设置分辨率
、闪光灯
、焦距
、照片质量
及旋转角度
等信息。
4.5.1 开发步骤
详细的API说明请参考Camera API参考。
// 导入media模块。创建拍照输出流的SurfaceId以及拍照输出的数据,都需要用到系统提供的media接口能力,导入media接口的方法如下。import { BusinessError } from '@ohos.base';
import media from '@ohos.multimedia.media';// 创建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,以此类推
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;}// AVRecorderProfilelet aVRecorderProfile: media.AVRecorderProfile = {fileFormat : media.ContainerFormatType.CFT_MPEG_4, // 视频文件封装格式,只支持MP4videoBitrate : 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 // 90°为默认竖屏显示角度,如果由于设备原因或应用期望以其他方式显示等原因,请根据实际情况调整该参数};// 创建avRecorderlet 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)}`);}
}
ts
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.');});
}
4.5.2 状态监听
在相机应用开发过程中,可以随时监听录像输出流状态,包括录像开始、录像结束、录像流输出的错误。
// 通过注册固定的frameStart回调函数获取监听录像开始结果,videoOutput创建成功时即可监听,录像第一次曝光时触发,有该事件返回结果则认为录像开始。
function onVideoOutputFrameStart(videoOutput: camera.VideoOutput): void {videoOutput.on('frameStart', () => {console.info('Video frame started');});
}// 通过注册固定的frameEnd回调函数获取监听录像结束结果,videoOutput创建成功时即可监听,录像完成最后一帧时触发,有该事件返回结果则认为录像流已结束。
function onVideoOutputFrameEnd(videoOutput: camera.VideoOutput): void {videoOutput.on('frameEnd', () => {console.info('Video frame ended');});
}// 通过注册固定的error回调函数获取监听录像输出错误结果,callback返回预览输出接口使用错误时对应的错误码,错误码类型参见CameraErrorCode。
function onVideoOutputError(videoOutput: camera.VideoOutput): void {videoOutput.on('error', (error: BusinessError) => {console.error(`Video output error code: ${error.code}`);});
}
4.6 元数据
元数据(Metadata)是对相机返回的图像信息数据的描述和上下文
,针对图像信息,提供的更详细的数据,如照片或视频中,识别人像的取景框坐标等信息。
Metadata
主要是通过一个TAG(Key)
,去找对应的Data
,用于传递参数
和配置信息
,减少内存拷贝操作
。
4.6.1 开发步骤
详细的API说明请参考Camera API参考。
// 导入相关接口,导入方法如下。
import camera from '@ohos.multimedia.camera';
import { BusinessError } from '@ohos.base';// 调用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}`);});
}
4.6.2 状态监听
在相机应用开发过程中,可以随时监听metadata数据以及输出流的状态。
/// 通过注册监听获取metadata对象,监听事件固定为metadataObjectsAvailable。检测到有效metadata数据时,callback返回相应的metadata数据信息,metadataOutput创建成功时可监听。
function onMetadataObjectsAvailable(metadataOutput: camera.MetadataOutput): void {metadataOutput.on('metadataObjectsAvailable', (err: BusinessError, metadataObjectArr: Array<camera.MetadataObject>) => {console.info('metadata output metadataObjectsAvailable');});
}// 说明:当前的元数据类型仅支持人脸检测(FACE_DETECTION)功能。
// 元数据信息对象为识别到的人脸区域的矩形信息(Rect),
// 包含矩形区域的左上角x坐标、y坐标和矩形的宽高数据。//通过注册回调函数,获取监听metadata流的错误结果,callback返回metadata输出接口使用错误时返回的错误码,错误码类型参见CameraErrorCode。function onMetadataError(metadataOutput: camera.MetadataOutput): void {metadataOutput.on('error', (metadataOutputError: BusinessError) => {console.error(`Metadata output error code: ${metadataOutputError.code}`);});
}
4.7 高性能拍照(仅对系统应用开放)
高性能拍照
是相机的重要功能之一,优化了拍照响应时延,提升用户体验。高性能拍照又名分段式拍照,应用下发拍照请求后,第一阶段系统会很快返回给应用一张缩略图,应用需将该图片及相关信息存入媒体库;第二阶段子服务会根据系统压力及定制化场景进行调度,将后处理好的原图回传给媒体库。
应用开发分段式拍照主要分为以下步骤:
- 查询当前设备的当前模式是否支持分段式拍照。
- 如果支持分段式能力,可以调用相机框架提供的使能接口使能分段式能力。
- 监听缩略图回调,获取缩略图代理类,将缩略图存入媒体库。
说明:
分段式拍照能力是根据设备和模式决定的,不同的设备支持不同的模式,不同的模式下分段式能力也各有不同,所以应用在切换设备或模式后需要重新使能分段式能力。
分段式使能需要在配流期间完成,配流完成后的使能操作不生效。
4.7.1 开发步骤
详细的API说明请参考Camera API参考。
// 导入依赖,需要导入相机框架、媒体库、图片相关领域依赖。import camera from '@ohos.multimedia.camera';
import image from '@ohos.multimedia.image';
import mediaLibrary from '@ohos.multimedia.mediaLibrary';
import fs from '@ohos.file.fs';
import photoAccessHelper from '@ohos.file.photoAccessHelper';
import { BusinessError } from '@ohos.base';// 确定拍照输出流。
// 通过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 isDeferredImageDeliverySupported(photoOutput: camera.PhotoOutput): boolean {let isSupported: boolean = false;if (photoOutput !== null) {isSupported = photoOutput.isDeferredImageDeliverySupported(camera.DeferredDeliveryImageType.PHOTO);}console.info(`isDeferredImageDeliverySupported isSupported: ${isSupported}`);return isSupported;
}// 使能分段式拍照能力。
function EnableDeferredPhotoAbility(photoOutput: camera.PhotoOutput): void {photoOutput.deferImageDelivery(camera.DeferredDeliveryImageType.PHOTO);
}// 查询是否已经成功使能分段式拍照。
function isDeferredImageDeliveryEnabled(photoOutput: camera.PhotoOutput): boolean {let isEnabled: boolean = false;if (photoOutput !== null) {isEnabled = photoOutput.isDeferredImageDeliveryEnabled(camera.DeferredDeliveryImageType.PHOTO);}console.info(`isDeferredImageDeliveryEnabled isEnabled: ${isEnabled}`);return isEnabled;
}
触发拍照,与普通拍照方式相同,请参考拍照。
4.7.2 状态监听
注册缩略图监听回调。
function onPhotoOutputDeferredPhotoProxyAvailable(photoOutput: camera.PhotoOutput): void {photoOutput.on('deferredPhotoProxyAvailable', (err: BusinessError, proxyObj: camera.DeferredPhotoProxy): void => {if (err) {console.info(`deferredPhotoProxyAvailable error: ${JSON.stringify(err)}.`);return;}console.info('photoOutPutCallBack deferredPhotoProxyAvailable');// 获取缩略图 pixelMapproxyObj.getThumbnail().then((thumbnail: image.PixelMap) => {AppStorage.setOrCreate('proxyThumbnail', thumbnail);});// 调用媒体库接口落盘缩略图,详细实现见2。saveDeferredPhoto(proxyObj);});
}// 调用媒体库接口落盘缩略图。
// Context获取方式请参考:获取UIAbility的上下文信息。
let context = getContext(this);
async function saveDeferredPhoto(proxyObj: camera.DeferredPhotoProxy) { try {// 创建 photoAssetlet photoAccessHelper = PhotoAccessHelper.getPhotoAccessHelper(context);let testFileName = 'testFile' + Date.now() + '.jpg';let photoAsset = await photoAccessHelper.createAsset(testFileName);// 将缩略图代理类传递给媒体库let mediaRequest: PhotoAccessHelper.MediaAssetChangeRequest = new PhotoAccessHelper.MediaAssetChangeRequest(photoAsset);mediaRequest.addResource(PhotoAccessHelper.ResourceType.PHOTO_PROXY, proxyObj);let res = await photoAccessHelper.applyChanges(mediaRequest);console.info('saveDeferredPhoto success.');} catch (err) {console.error(`Failed to saveDeferredPhoto. error: ${JSON.stringify(err)}`);}
}
5 相机实践(ArkTS)
5.1 拍照实现方案
当前示例提供完整的拍照流程介绍,方便开发者了解完整的接口调用顺序。
在参考以下示例前,建议开发者查看相机开发指导(ArkTS)的具体章节,了解设备输入、会话管理、拍照等单个流程。
5.1.1 开发流程
在获取到相机支持的输出流能力后,开始创建拍照流,开发流程如下。
5.1.2 完整示例
Context获取方式请参考:获取UIAbility的上下文信息。
import camera from '@ohos.multimedia.camera';
import image from '@ohos.multimedia.image';
import { BusinessError } from '@ohos.base';
import common from '@ohos.app.ability.common';
import fs from '@ohos.file.fs';
import PhotoAccessHelper from '@ohos.file.photoAccessHelper';let context = getContext(this);async function savePicture(buffer: ArrayBuffer, img: image.Image): Promise<void> {let photoAccessHelper: PhotoAccessHelper.PhotoAccessHelper = PhotoAccessHelper.getPhotoAccessHelper(context);let options: PhotoAccessHelper.CreateOptions = {title: Date.now().toString()};let photoUri: string = await photoAccessHelper.createAsset(PhotoAccessHelper.PhotoType.IMAGE, 'jpg', options);//createAsset的调用需要ohos.permission.READ_IMAGEVIDEO和ohos.permission.WRITE_IMAGEVIDEO的权限let file: fs.File = fs.openSync(photoUri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);await fs.write(file.fd, buffer);fs.closeSync(file);img.release();
}function setPhotoOutputCb(photoOutput: camera.PhotoOutput): void {//设置回调之后,调用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 = 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;}savePicture(buffer, imageObj);});});
}async function cameraShootingCase(baseContext: common.BaseContext, surfaceId: string): Promise<void> {// 创建CameraManager对象let cameraManager: camera.CameraManager = camera.getCameraManager(baseContext);if (!cameraManager) {console.error("camera.getCameraManager error");return;}// 监听相机状态变化cameraManager.on('cameraStatus', (err: BusinessError, cameraStatusInfo: camera.CameraStatusInfo) => {console.info(`camera : ${cameraStatusInfo.camera.cameraId}`);console.info(`status: ${cameraStatusInfo.status}`);});// 获取相机列表let cameraArray: Array<camera.CameraDevice> = cameraManager.getSupportedCameras();if (cameraArray.length <= 0) {console.error("cameraManager.getSupportedCameras error");return;}for (let index = 0; index < cameraArray.length; index++) {console.info('cameraId : ' + cameraArray[index].cameraId); // 获取相机IDconsole.info('cameraPosition : ' + cameraArray[index].cameraPosition); // 获取相机位置console.info('cameraType : ' + cameraArray[index].cameraType); // 获取相机类型console.info('connectionType : ' + cameraArray[index].connectionType); // 获取相机连接类型}// 创建相机输入流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;}// 监听cameraInput错误信息let cameraDevice: camera.CameraDevice = cameraArray[0];cameraInput.on('error', cameraDevice, (error: BusinessError) => {console.error(`Camera input error code: ${error.code}`);})// 打开相机await cameraInput.open();// 获取支持的模式类型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 cameraOutputCap: camera.CameraOutputCapability = cameraManager.getSupportedOutputCapability(cameraArray[0], camera.SceneMode.NORMAL_PHOTO);if (!cameraOutputCap) {console.error("cameraManager.getSupportedOutputCapability error");return;}console.info("outputCapability: " + JSON.stringify(cameraOutputCap));let previewProfilesArray: Array<camera.Profile> = cameraOutputCap.previewProfiles;if (!previewProfilesArray) {console.error("createOutput previewProfilesArray == null || undefined");}let photoProfilesArray: Array<camera.Profile> = cameraOutputCap.photoProfiles;if (!photoProfilesArray) {console.error("createOutput photoProfilesArray == null || undefined");}// 创建预览输出流,其中参数 surfaceId 参考上文 XComponent 组件,预览流为XComponent组件提供的surfacelet 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}`);}if (previewOutput === undefined) {return;}// 监听预览输出错误信息previewOutput.on('error', (error: BusinessError) => {console.error(`Preview output error code: ${error.code}`);});// 创建拍照输出流let photoOutput: camera.PhotoOutput | undefined = undefined;try {photoOutput = cameraManager.createPhotoOutput(photoProfilesArray[0]);} catch (error) {let err = error as BusinessError;console.error('Failed to createPhotoOutput errorCode = ' + err.code);}if (photoOutput === undefined) {return;}//调用上面的回调函数来保存图片setPhotoOutputCb(photoOutput);//创建会话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;}// 监听session错误信息photoSession.on('error', (error: BusinessError) => {console.error(`Capture session error code: ${error.code}`);});// 开始配置会话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();// 启动会话await photoSession.start().then(() => {console.info('Promise returned to indicate the session start success.');});// 判断设备是否支持闪光灯let flashStatus: boolean = false;try {flashStatus = photoSession.hasFlash();} catch (error) {let err = error as BusinessError;console.error('Failed to hasFlash. errorCode = ' + err.code);}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. errorCode = ' + err.code);}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. errorCode = ' + err.code);}}}// 判断是否支持连续自动变焦模式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. errorCode = ' + err.code);}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. errorCode = ' + err.code);}}// 获取相机支持的可变焦距比范围let zoomRatioRange: Array<number> = [];try {zoomRatioRange = photoSession.getZoomRatioRange();} catch (error) {let err = error as BusinessError;console.error('Failed to get the zoom ratio range. errorCode = ' + err.code);}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. errorCode = ' + err.code);}let photoCaptureSetting: camera.PhotoCaptureSetting = {quality: camera.QualityLevel.QUALITY_LEVEL_HIGH, // 设置图片质量高rotation: camera.ImageRotation.ROTATION_0 // 设置图片旋转角度0}// 使用当前拍照设置进行拍照photoOutput.capture(photoCaptureSetting, (err: BusinessError) => {if (err) {console.error(`Failed to capture the photo ${err.message}`);return;}console.info('Callback invoked to indicate the photo capture request success.');});// 停止当前会话photoSession.stop();// 释放相机输入流cameraInput.close();// 释放预览输出流previewOutput.release();// 释放拍照输出流photoOutput.release();// 释放会话photoSession.release();// 会话置空photoSession = undefined;
}
5.2 录像实现方案
当前示例提供完整的录像流程介绍,方便开发者了解完整的接口调用顺序。
在参考以下示例前,建议开发者查看相机开发指导(ArkTS)的具体章节,了解设备输入、会话管理、录像等单个流程。
5.2.1 开发流程
在获取到相机支持的输出流能力后,开始创建录像流,开发流程如下。
5.2.2 完整示例
Context获取方式请参考:获取UIAbility的上下文信息。
import camera from '@ohos.multimedia.camera';
import { BusinessError } from '@ohos.base';
import media from '@ohos.multimedia.media';
import common from '@ohos.app.ability.common';
import PhotoAccessHelper from '@ohos.file.photoAccessHelper';
import fs from '@ohos.file.fs';async function videoRecording(context: common.Context, surfaceId: string): Promise<void> {// 创建CameraManager对象let cameraManager: camera.CameraManager = camera.getCameraManager(context);if (!cameraManager) {console.error("camera.getCameraManager error");return;}// 监听相机状态变化cameraManager.on('cameraStatus', (err: BusinessError, cameraStatusInfo: camera.CameraStatusInfo) => {console.info(`camera : ${cameraStatusInfo.camera.cameraId}`);console.info(`status: ${cameraStatusInfo.status}`);});// 获取相机列表let cameraArray: Array<camera.CameraDevice> = [];try {cameraArray = cameraManager.getSupportedCameras();} catch (error) {let err = error as BusinessError;console.error(`getSupportedCameras call failed. error code: ${err.code}`);}if (cameraArray.length <= 0) {console.error("cameraManager.getSupportedCameras error");return;}// 获取支持的模式类型let sceneModes: Array<camera.SceneMode> = cameraManager.getSupportedSceneModes(cameraArray[0]);let isSupportVideoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_VIDEO) >= 0;if (!isSupportVideoMode) {console.error('video mode not support');return;}// 获取相机设备支持的输出流能力let cameraOutputCap: camera.CameraOutputCapability = cameraManager.getSupportedOutputCapability(cameraArray[0], camera.SceneMode.NORMAL_VIDEO);if (!cameraOutputCap) {console.error("cameraManager.getSupportedOutputCapability error")return;}console.info("outputCapability: " + JSON.stringify(cameraOutputCap));let previewProfilesArray: Array<camera.Profile> = cameraOutputCap.previewProfiles;if (!previewProfilesArray) {console.error("createOutput previewProfilesArray == null || undefined");}let photoProfilesArray: Array<camera.Profile> = cameraOutputCap.photoProfiles;if (!photoProfilesArray) {console.error("createOutput photoProfilesArray == null || undefined");}let videoProfilesArray: Array<camera.VideoProfile> = cameraOutputCap.videoProfiles;if (!videoProfilesArray) {console.error("createOutput videoProfilesArray == null || undefined");}// videoProfile的宽高需要与AVRecorderProfile的宽高保持一致,并且需要使用AVRecorderProfile锁支持的宽高let videoSize: camera.Size = {width: 640,height: 480}let videoProfile: undefined | camera.VideoProfile = videoProfilesArray.find((profile: camera.VideoProfile) => {return profile.size.width === videoSize.width && profile.size.height === videoSize.height;});if (!videoProfile) {console.error('videoProfile is not found');return;}// 配置参数以实际硬件设备支持的范围为准let aVRecorderProfile: media.AVRecorderProfile = {audioBitrate: 48000,audioChannels: 2,audioCodec: media.CodecMimeType.AUDIO_AAC,audioSampleRate: 48000,fileFormat: media.ContainerFormatType.CFT_MPEG_4,videoBitrate: 2000000,videoCodec: media.CodecMimeType.VIDEO_AVC,videoFrameWidth: videoSize.width,videoFrameHeight: videoSize.height,videoFrameRate: 30};let options: PhotoAccessHelper.CreateOptions = {title: Date.now().toString()};let photoAccessHelper: PhotoAccessHelper.PhotoAccessHelper = PhotoAccessHelper.getPhotoAccessHelper(context);let videoUri: string = await photoAccessHelper.createAsset(PhotoAccessHelper.PhotoType.VIDEO, 'mp4', options);let file: fs.File = fs.openSync(videoUri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);let aVRecorderConfig: media.AVRecorderConfig = {audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,profile: aVRecorderProfile,url: `fd://${file.fd.toString()}`, // 文件需先由调用者创建,赋予读写权限,将文件fd传给此参数,eg.fd://45--file:///data/media/01.mp4rotation: 0, // 合理值0、90、180、270,非合理值prepare接口将报错location: { latitude: 30, longitude: 130 }};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;}try {await avRecorder.prepare(aVRecorderConfig);} catch (error) {let err = error as BusinessError;console.error(`prepare call failed. error code: ${err.code}`);}let videoSurfaceId: string | undefined = undefined; // 该surfaceID用于传递给相机接口创造videoOutputtry {videoSurfaceId = await avRecorder.getInputSurface();} catch (error) {let err = error as BusinessError;console.error(`getInputSurface call failed. error code: ${err.code}`);}if (videoSurfaceId === undefined) {return;}// 创建VideoOutput对象let videoOutput: camera.VideoOutput | undefined = undefined;try {videoOutput = cameraManager.createVideoOutput(videoProfile, videoSurfaceId);} catch (error) {let err = error as BusinessError;console.error(`Failed to create the videoOutput instance. error: ${JSON.stringify(err)}`);}if (videoOutput === undefined) {return;}// 监听视频输出错误信息videoOutput.on('error', (error: BusinessError) => {console.error(`Preview output error code: ${error.code}`);});//创建会话let videoSession: camera.CaptureSession | undefined = undefined;try {videoSession = cameraManager.createSession(camera.SceneMode.NORMAL_VIDEO) as camera.VideoSession;} catch (error) {let err = error as BusinessError;console.error(`Failed to create the session instance. error: ${JSON.stringify(err)}`);}if (videoSession === undefined) {return;}// 监听session错误信息videoSession.on('error', (error: BusinessError) => {console.error(`Video session error code: ${error.code}`);});// 开始配置会话try {videoSession.beginConfig();} catch (error) {let err = error as BusinessError;console.error(`Failed to beginConfig. error: ${JSON.stringify(err)}`);}// 创建相机输入流let cameraInput: camera.CameraInput | undefined = undefined;try {cameraInput = cameraManager.createCameraInput(cameraArray[0]);} catch (error) {let err = error as BusinessError;console.error(`Failed to createCameraInput. error: ${JSON.stringify(err)}`);}if (cameraInput === undefined) {return;}// 监听cameraInput错误信息let cameraDevice: camera.CameraDevice = cameraArray[0];cameraInput.on('error', cameraDevice, (error: BusinessError) => {console.error(`Camera input error code: ${error.code}`);});// 打开相机try {await cameraInput.open();} catch (error) {let err = error as BusinessError;console.error(`Failed to open cameraInput. error: ${JSON.stringify(err)}`);}// 向会话中添加相机输入流try {videoSession.addInput(cameraInput);} catch (error) {let err = error as BusinessError;console.error(`Failed to add cameraInput. error: ${JSON.stringify(err)}`);}// 创建预览输出流,其中参数 surfaceId 参考下面 XComponent 组件,预览流为XComponent组件提供的surfacelet 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: ${JSON.stringify(err)}`);}if (previewOutput === undefined) {return;}// 向会话中添加预览输出流try {videoSession.addOutput(previewOutput);} catch (error) {let err = error as BusinessError;console.error(`Failed to add previewOutput. error: ${JSON.stringify(err)}`);}// 向会话中添加录像输出流try {videoSession.addOutput(videoOutput);} catch (error) {let err = error as BusinessError;console.error(`Failed to add videoOutput. error: ${JSON.stringify(err)}`);}// 提交会话配置try {await videoSession.commitConfig();} catch (error) {let err = error as BusinessError;console.error(`videoSession commitConfig error: ${JSON.stringify(err)}`);}// 启动会话try {await videoSession.start();} catch (error) {let err = error as BusinessError;console.error(`videoSession start error: ${JSON.stringify(err)}`);}// 启动录像输出流videoOutput.start((err: BusinessError) => {if (err) {console.error(`Failed to start the video output. error: ${JSON.stringify(err)}`);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)}`);}// 停止录像输出流videoOutput.stop((err: BusinessError) => {if (err) {console.error(`Failed to stop the video output. error: ${JSON.stringify(err)}`);return;}console.info('Callback invoked to indicate the video output stop success.');});// 停止录像try {await avRecorder.stop();} catch (error) {let err = error as BusinessError;console.error(`avRecorder stop error: ${JSON.stringify(err)}`);}// 停止当前会话videoSession.stop();// 关闭文件fs.closeSync(file);// 释放相机输入流cameraInput.close();// 释放预览输出流previewOutput.release();// 释放录像输出流videoOutput.release();// 释放会话videoSession.release();// 会话置空videoSession = undefined;
}
参考文献:
[1]OpenHarmoney应用开发文档
相关文章:
【鸿蒙开发】第二十章 Camera相机服务
1 简介 开发者通过调用Camera Kit(相机服务)提供的接口可以开发相机应用,应用通过访问和操作相机硬件,实现基础操作,如预览、拍照和录像;还可以通过接口组合完成更多操作,如控制闪光灯和曝光时间、对焦或调焦等。 2 …...
JS阅读笔记
myweb3.html <video id"video" width"400" height"300" autoplay></video> <button id"capture-btn">拍摄图片</button> <canvas id"canvas" width"400" height"300">&…...
基于spring boot的留守儿童爱心管理系统
基于spring boot的留守儿童爱心管理系统设计与实现 开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7(一定要5.7版本) 数据库工具:Navicat11 开…...
python输入某年某月某日判断这一天是这一年的第几天
如何使用python实现输入某年某月某日判断这一天是这一年的第几天 from datetime import datetime #引入日期类 def is_leap_year(year):"""判断是否为闰年"""return (year % 4 0 and year % 100 ! 0) or (year % 400 0)# 根据年份和月份返回当…...
docker 上达梦导入dump文件报错:本地编码:PG GBK,导入女件编码:PGGB18030
解决方案: 第一步进入达梦数据容器内部 docker exec -it fc316f88caff /bin/bash 第二步:在容器中 /opt/dmdbms/bin目录下 执行命令 cd /opt/dmdbms/bin./dimp USERIDSYSDBA/SYSDBA001 FILE/opt/dmdbms/ZFJG_LJ20240407.dmp SCHEMASZFJG_LJUSERIDSYSD…...
一起学习python——基础篇(19)
今天来说一下python的如何修改文件名称、获取文件大小、读取文中指定的某一行内容。 1、修改文件名称: import os testPath"D:/pythonFile/test.txt" testPath2"D:/pythonFile/test2.txt" #修改文件名称使用rename方法, #第一个参…...
数模 初见数建
文章目录 初见数学建模1.1 数学建模是什么1.2 数学建模的概述1.3 如何学习数学建模---分模块化1.4 数学建模前提了解1.5 数学建模的六个步骤1.6 如何备战建模比赛1.7 数学建模赛题类型1.8 数学建模算法体系概述 初见数学建模 1.1 数学建模是什么 1.原型与模型 原型ÿ…...
windows系统搭建OCR半自动标注工具PaddleOCR
深度学习 文章目录 深度学习前言一、环境搭建准备方式1:安装Anaconda搭建1. Anaconda下载地址: [点击](https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/?CM&OD)2. 创建新的conda环境 方式2. 直接安装python 二、安装CPU版本1. 安装PaddlePaddle2、安装…...
01、ArcGIS For JavaScript 4.29对3DTiles数据的支持
综述 Cesium从1.99版本开始支持I3S服务的加载,到目前位置,已经支持I3S的倾斜模型、3D Object模型以及属性查询的支持。Cesium1.115又对I3S标准的Building数据实现了加载支持。而ArcGIS之前一直没有跨越对3DTiles数据的支持,所以在一些开发过…...
Spark_SparkSql写入Oracle_Undefined function.....将长字符串写入Oracle中方法..
在使用Spark编写代码将读库处理然后写入Oracle中遇到了诸多小bug,很磨人,好在解决了。shit!! 实测1:TO_CLOB(a3) 代码样例 --这是一个sparksql写入hive的一个小逻辑,我脱敏了噻 SELECT a1, a2, TO_CLOB(a3) AS clob_data, TO_DATE(a4) AS …...
2023数据要素白皮书(免费下载)
【1】关注本公众号,转发当前文章到微信朋友圈 【2】私信发送 【2023年数据资源入表白皮书】 【3】获取本方案PDF下载链接,直接下载即可。 如需下载本方案PPT原格式,请加入微信扫描以下方案驿站知识星球,获取上万份PPT解决方案&a…...
kafka学习记录
文章目录 windows单机版kafka搭建步骤主题的增删改查操作消息的生产与消费 Windows集群版kafka搭建步骤 prettyZoo 尚硅谷Kafka教程,2024新版kafka视频,零基础入门到实战 【尚硅谷】Kafka3.x教程(从入门到调优,深入全面࿰…...
无线网络2.4和5G的区别
无线网络2.4和5的区别 无线网络2.4GHz和5GHz的主要区别在于频率、覆盖范围、传输速度、干扰能力和穿透性。以下是详细介绍:12 频率不同。2.4GHz的频率较低,而5GHz的频率较高。频率越低,信号在传播过程中的损失越小,因此覆盖范围…...
大模型笔记:Prompt tuning
1 NLP模型的几个阶段 1.1 第一阶段(在深度学习出现之前) 通常聚焦于特征工程(feature engineering)利用领域知识从数据中提取好的特征 1.2 第二阶段(在深度学习出现之后) 特征可以从数据中习得——>…...
【Ambari】Ansible自动化部署大数据集群
目录 一.版本说明和介绍信息 1.1 大数据组件版本 1.2 Apache Components 1.3 Databases支持版本 二.安装包上传和说明 三.服务器基础环境配置 3.1global配置修改 3.2主机名映射配置 3.3免密用户名密码配置 3.4 ansible安装 四. 安…...
RTSP/Onvif视频安防监控平台EasyNVR调用接口返回匿名用户名和密码的原因排查
视频安防监控平台EasyNVR可支持设备通过RTSP/Onvif协议接入,并能对接入的视频流进行处理与多端分发,包括RTSP、RTMP、HTTP-FLV、WS-FLV、HLS、WebRTC等多种格式。平台拓展性强、支持二次开发与集成,可应用在景区、校园、水利、社区、工地等场…...
opencv基础图行展示
"""试用opencv创建画布并显示矩形框(适用于目标检测图像可视化) """ # 创建一个黑色的画布,图像格式(BGR) img np.zeros((512, 512, 3), np.uint8)# 画一个矩形:给定左上角和右下角坐标࿰…...
GIF在线生成器
上传图片就能生成GIF的前端WEB工具 源码也非常简单 <!DOCTYPE html> <html lang"zh" class"dark"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1, m…...
使用JavaScript制作一个简单的天气应用
随着Web开发技术的不断发展,JavaScript已经成为前端开发中不可或缺的一部分。它不仅可以用于创建动态和交互式的用户界面,还可以用于处理各种复杂的任务,如数据验证、动态内容更新、实时通信等。以下是一个使用JavaScript来创建一个简单天气应…...
说说对WebSocket的理解?应用场景?
文章目录 一、是什么二、特点全双工二进制帧协议名握手优点 三、应用场景参考文献 一、是什么 WebSocket,是一种网络传输协议,位于OSI模型的应用层。可在单个TCP连接上进行全双工通信,能更好的节省服务器资源和带宽并达到实时通迅 客户端和…...
网路维护基础知识
1、路由器 路由器:路由器就是将一个可以接入互联网的网路地址分成若干个网路地址可供终端设备连接的网路设备,设备既可以通过有线连接也可以通过无线连接进入互联网 2、交换机 交换机:个人感觉交换机只是为那些有线网路设计的,…...
【GD32】MQ-3酒精检测传感器
2.31 MQ-3酒精检测传感器 MQ-3气体传感器所使用的气敏材料是在清洁空气中电导率较低的二氧化锡(Sn0)。当传感器所处环境中存在酒精蒸气时,传感器的电导率随空气中酒精蒸气浓度的增加而增大。使用简单的电路即可将电导率的变化转换为与该气体浓度相对应的输出信号。…...
如何在极狐GitLab 启用依赖代理功能
本文作者:徐晓伟 GitLab 是一个全球知名的一体化 DevOps 平台,很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版,专门为中国程序员服务。可以一键式部署极狐GitLab。 本文主要讲述了如何在[极狐GitLab…...
ES6中 Promise的详细讲解
文章目录 一、介绍状态特点流程 二、用法实例方法then()catchfinally() 构造函数方法all()race()allSettled()resolve()reject() 三、使用场景# 参考文献 一、介绍 Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(…...
网站建设也会涉及商标侵权,需要注意些!
以前普推知产老杨碰到建站涉及知识产权侵权的,但是大多数是其它方面的,前几天看到某同行说由于给客户建设网站,由于网站名称涉及商标被起诉要索赔几十万。 当时同行给做网站时还看了下营业执照,上面的主体名称与网站名称也是一致…...
Leetcode算法训练日记 | day25
一、组合总和Ⅲ 1.题目 Leetcode:第 216 题 找出所有相加之和为 n 的 k 个数的组合,且满足下列条件: 只使用数字1到9每个数字 最多使用一次 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺…...
第23次修改了可删除可持久保存的前端html备忘录:增加了百度引擎
第22次修改了可删除可持久保存的前端html备忘录视频背景分离,增加了本地连接,增加了纯CSS做的折叠隐藏修改说明 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport…...
vue3中使用antv-S2表格(基础功能版)
先看展示效果: 可以调整行宽、列宽、自定义字段图标、表头图标、添加排序、显示总计、小计等 首先确保搭建一个vue3项目环境,从0开始的小伙伴着重看第一点: 一、搭建vue3项目环境 首先创建一个vue3vitets项目,可以查看下面相关…...
算数逻辑单元
目录 一、王道考研ppt总结 二、个人理解 一、王道考研ppt总结 二、个人理解 74181是一款经典的ALU 可以进行加减乘除和与或非、异或等计算;还有移位和求补等 输入有一个CU信号,即控制单元信号,有一个M信号,当M为1时,进…...
clickhouse深入浅出
基础知识原理 极致压缩率 极速查询性能 列式数据库管理 ,读请求多 大批次更新或无更新 读很多但用很少 大量的列 列的值小数值/短字符串 一致性要求低 DBMS:动态创建/修改/删除库 表 视图,动态查/增/修/删,用户粒度设库…...
微信咋做自己的网站/宣传软文模板
184行前添加: if (asm.GetType().FullName ! "System.Reflection.RuntimeAssembly") continue;忽略错误 缓存的Provider 不能执行 存储过程,报 NotSupportedException("Command tree type " commandTree.GetType() " is not …...
网站如何看是哪家公司做的/百度搜索关键词热度
使用数据使用JDBC读取和写入数据调整领域对象以适应持久化使用JdbcTemplate定义JDBC repository定义模式和预加载数据插入数据使用Spring Data JPA持久化数据将领域对象标注为实体声明JPA repository自定义JPA repository小结在本章会为Taco Cloud应用添加对数据持久化的支持。…...
国外电商网站如何建立/站长seo软件
我正在尝试使用python构建一个简单的加密。在这是加密:from Crypto.Cipher import AESfrom Crypto.Util.Padding import padfrom Crypto.Util.Padding import unpadBLOCK_SIZE 32def encrypt(message):obj AES.new(bThis is a key123, AES.MODE_CBC, bThis is an …...
百度云 做网站/公司seo是什么级别
http://www.cnblogs.com/skychen1218/背景本公司是.Net项目,在.Net可选的MQ比较少,主要Kafka和RabbitMQ,RabbitMQ我也是使用多年了,最近的Kafka广告与流行度打得使我也是无法无视,因此也是花了点时间收集了资料做了些对…...
网站做好后怎么做seo/免费制作网站app
#!/usr/bin/env python#! _*_ coding:utf-8 _*_ #注:多实例DB数据,my.conf,sock文件目录要统一#每个实例要建有shutdown权限mt_user用户 import os,sysimport socket myd /usr/local/mysql/bin/mysqld#1myadmin /usr/local/mysql/bin/mysqladminm_user rootm_password exsd…...
diy做网站/泰州seo排名扣费
英语学习/词典APP排行五排名: 1.网易有道词典(单词查询翻译类软件). 2.百词斩(单词记忆类软件). 3.沪江开心词场. 4.金山词霸. 5.流利说英语(英语口语APP). 个软件的分析: 1.对网易有单词典的分析ÿ…...