赞
踩
开发者通过调用Camera Kit(相机服务)
提供的接口可以开发相机应用,应用通过访问和操作相机硬件,实现基础操作,如预览
、拍照
和录像
;还可以通过接口组合完成更多操作,如控制闪光灯和曝光时间
、对焦或调焦
等。
相机调用摄像头采集、加工图像视频数据,精确控制对应的硬件,灵活输出图像、视频内容,满足多镜头硬件适配(如广角、长焦、TOF)、多业务场景适配(如不同分辨率、不同格式、不同效果)的要求。
相机的工作流程如图所示,可概括为相机输入设备管理、会话管理和相机输出管理三部分。
相机应用通过控制相机,实现图像显示(预览)、照片保存(拍照)、视频录制(录像)等基础操作。在实现基本操作过程中,相机服务会控制相机设备采集和输出数据,采集的图像数据在相机底层的设备硬件接口(HDI,Hardware Device Interfaces),直接通过BufferQueue
传递到具体的功能模块进行处理。BufferQueue在应用开发中无需关注,用于将底层处理的数据及时送到上层进行图像显示。
以视频录制为例进行说明,相机应用在录制视频过程中,媒体录制服务先创建一个视频Surface
用于传递数据,并提供给相机服务,相机服务可控制相机设备采集视频数据,生成视频流。采集的数据通过底层相机HDI处理后,通过Surface
将视频流传递给媒体录制服务,媒体录制服务对视频数据进行处理后,保存为视频文件,完成视频录制。
相机应用开发的主要流程包含开发准备
、设备输入
、会话管理
、预览
、拍照和录像
等。
在开发相机应用时,需要先申请相机相关权限
,确保应用拥有访问相机硬件及其他功能的权限,需要的权限如下表。在申请权限前,请保证符合权限使用的基本原则。
以上权限的授权方式均为user_grant(用户授权)
,即开发者在module.json5
文件中配置对应的权限后,需要使用接口abilityAccessCtrl.requestPermissionsFromUser
去校验当前用户是否已授权。如果是,应用可以直接访问/操作目标对象;否则需要弹框向用户申请授权
。
说明: 即使用户曾被授予过权限,应用在调用此权限保护的接口前,也应该先检查是否有权限。不能把之前授予的状态持久化,因为用户在动态授予后可能通过“设置”取消应用权限。
当前相机提供了ArkTS
和C++
两种开发语言的开发指导,如下表所示。
在开发一个相机应用前,需要先创建一个独立的相机设备,应用通过调用和控制相机设备,完成预览、拍照和录像等基础操作。
详细的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); // 获取相机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 []; } } // 通过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; }
在相机应用开发过程中,可以随时监听相机状态,包括新相机的出现
、相机的移除
、相机的可用状态
。在回调函数中,通过相机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}`);
});
}
相机使用预览
、拍照
、录像
、元数据
功能前,均需要创建相机会话
。
在会话中,可以完成以下功能:
配置相机的输入流和输出流。相机在拍摄前,必须完成输入输出流的配置。 配置输入流即添加设备输入,对用户而言,相当于选择设备的某一摄像头拍摄;配置输出流,即选择数据将以什么形式输出。当应用需要实现拍照时,输出流应配置为预览流和拍照流,预览流的数据将显示在XComponent
组件上,拍照流的数据将通过ImageReceiver
接口的能力保存到相册中。
添加闪光灯、调整焦距等配置。具体支持的配置及接口说明请参考Camera API
参考。
会话切换控制。应用可以通过移除和添加输出流的方式,切换相机模式。如当前会话的输出流为拍照流,应用可以将拍照流移除,然后添加视频流作为输出流,即完成了拍照到录像的切换。
完成会话配置后,应用提交和开启会话,可以开始调用相机相关功能。
// 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)}`); } }
预览是启动相机后看见的画面,通常在拍照和录像前执行。
详细的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 { // 创建XComponentController mXComponentController: XComponentController = new XComponentController; surfaceId: string = ''; build() { Flex() { // 创建XComponent XComponent({ id: '', type: 'surface', libraryname: '', controller: this.mXComponentController }) .onLoad(() => { // 设置Surface宽高(1920*1080),预览尺寸设置参考前面 previewProfilesArray 获取的当前设备所支持的预览分辨率大小去设置 // 预览流与录像输出流的分辨率的宽高比要保持一致 this.mXComponentController.setXComponentSurfaceSize({surfaceWidth:1920,surfaceHeight:1080}); // 获取Surface ID this.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(); }
在相机应用开发过程中,可以随时监听预览输出流状态,包括预览流启动
、预览流结束
、预览流输出错误
。
// 通过注册固定的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}`); }); }
拍照是相机的最重要功能之一,拍照模块基于相机复杂的逻辑,为了保证用户拍出的照片质量,在中间步骤可以设置分辨率
、闪光灯
、焦距
、照片质量
及旋转角度
等信息。
详细的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, // 设置图片旋转角度0 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.'); }); }
在相机应用开发过程中,可以随时监听拍照输出流状态,包括拍照流开始、拍照帧的开始与结束、拍照输出流的错误。
// 通过注册固定的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,设置分辨率
、闪光灯
、焦距
、照片质量
及旋转角度
等信息。
详细的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; } // 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 // 90°为默认竖屏显示角度,如果由于设备原因或应用期望以其他方式显示等原因,请根据实际情况调整该参数 }; // 创建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)}`); } } 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.'); }); }
在相机应用开发过程中,可以随时监听录像输出流状态,包括录像开始、录像结束、录像流输出的错误。
// 通过注册固定的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}`); }); }
元数据(Metadata)是对相机返回的图像信息数据的描述和上下文
,针对图像信息,提供的更详细的数据,如照片或视频中,识别人像的取景框坐标等信息。
Metadata
主要是通过一个TAG(Key)
,去找对应的Data
,用于传递参数
和配置信息
,减少内存拷贝操作
。
详细的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}`); }); }
在相机应用开发过程中,可以随时监听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}`); }); }
高性能拍照
是相机的重要功能之一,优化了拍照响应时延,提升用户体验。高性能拍照又名分段式拍照,应用下发拍照请求后,第一阶段系统会很快返回给应用一张缩略图,应用需将该图片及相关信息存入媒体库;第二阶段子服务会根据系统压力及定制化场景进行调度,将后处理好的原图回传给媒体库。
应用开发分段式拍照主要分为以下步骤:
说明:
分段式拍照能力是根据设备和模式决定的,不同的设备支持不同的模式,不同的模式下分段式能力也各有不同,所以应用在切换设备或模式后需要重新使能分段式能力。
分段式使能需要在配流期间完成,配流完成后的使能操作不生效。
详细的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; }
触发拍照,与普通拍照方式相同,请参考拍照。
注册缩略图监听回调。
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'); // 获取缩略图 pixelMap proxyObj.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 { // 创建 photoAsset let 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)}`); } }
当前示例提供完整的拍照流程介绍,方便开发者了解完整的接口调用顺序。
在参考以下示例前,建议开发者查看相机开发指导(ArkTS)的具体章节,了解设备输入、会话管理、拍照等单个流程。
在获取到相机支持的输出流能力后,开始创建拍照流,开发流程如下。
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); // 获取相机ID console.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组件提供的surface 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}`); } 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; }
当前示例提供完整的录像流程介绍,方便开发者了解完整的接口调用顺序。
在参考以下示例前,建议开发者查看相机开发指导(ArkTS)的具体章节,了解设备输入、会话管理、录像等单个流程。
在获取到相机支持的输出流能力后,开始创建录像流,开发流程如下。
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.mp4 rotation: 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用于传递给相机接口创造videoOutput try { 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组件提供的surface 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: ${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应用开发文档
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。