鸿蒙媒体开发【相机数据采集保存】拍照和图片

avatar
作者
猴君
阅读量:0

相机数据采集保存

介绍

本示例主要展示了相机的相关功能 接口实现相机的预览拍照功能。

效果预览

1

使用说明

  1. 弹出是否允许“CameraSample”使用相机?点击“允许”
  2. 弹出是否允许“CameraSample”使用麦克风?点击“允许”
  3. 进入预览界面,预览正常,点击拍照按钮,跳转到图片预览页面,跳转正常,图片预览页面显示当前所拍照的图片,显示正常
  4. 进入预览界面,预览正常,点击拍照按钮,跳转到图片预览页面,跳转正常,图片预览页面显示当前所拍照的图片,显示正常,退出应用并进入图库应用,第一张图片显示为刚刚拍照的图片,拍照正常
  5. 点击图片预览页面的左上角返回按钮,重新进入预览页面,预览正常
  6. 进入预览界面,预览正常,滑动变焦条,变焦条上方显示变焦值,显示正常,并且预览界面随着变焦条的滑动放大或缩小,预览正常
  7. 进入预览界面,预览正常,点击预览显示区域进行对焦,对焦正常
  8. 进入预览界面,预览正常,点击“拍照”旁边的“录像”切换到录像模式,预览正常,点击录像按钮,开始录像,录像正常,点击停止录像按钮,跳转到录像预览界面,跳转正常,点击视频播放按钮,播放正常

具体实现

  • 相机功能接口实现在CameraService.ets,源码参考:[CameraService.ets]
/*  * Copyright (c) 2024 Huawei Device Co., Ltd.  * Licensed under the Apache License, Version 2.0 (the 'License');  * you may not use this file except in compliance with the License.  * You may obtain a copy of the License at  *  *     http://www.apache.org/licenses/LICENSE-2.0  *  * Unless required by applicable law or agreed to in writing, software  * distributed under the License is distributed on an 'AS IS' BASIS,  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  * See the License for the specific language governing permissions and  * limitations under the License.  */  import { photoAccessHelper } from '@kit.MediaLibraryKit'; import { camera } from '@kit.CameraKit'; import { media } from '@kit.MediaKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { JSON } from '@kit.ArkTS'; import { fileIo } from '@kit.CoreFileKit'; import { GlobalContext } from '../common/utils/GlobalContext'; import Logger from '../common/utils/Logger'; import { Constants } from '../common/Constants';  const TAG: string = 'CameraService';  export class SliderValue {   min: number = 1;   max: number = 6;   step: number = 0.1; }  class CameraService {   private cameraManager: camera.CameraManager | undefined = undefined;   private cameras: Array<camera.CameraDevice> | Array<camera.CameraDevice> = [];   private cameraInput: camera.CameraInput | undefined = undefined;   private previewOutput: camera.PreviewOutput | undefined = undefined;   private photoOutput: camera.PhotoOutput | undefined = undefined;   private videoOutput: camera.VideoOutput | undefined = undefined;   private avRecorder: media.AVRecorder | undefined = undefined;   private session: camera.PhotoSession | camera.VideoSession | undefined = undefined;   private handlePhotoAssetCb: (photoAsset: photoAccessHelper.PhotoAsset) => void = () => {   };   private curCameraDevice: camera.CameraDevice | undefined = undefined;   private isRecording: boolean = false;   // 推荐拍照分辨率之一   private photoProfileObj: camera.Profile = {     format: 2000,     size: {       width: 1920,       height: 1080     }   };   // 推荐预览分辨率之一   private previewProfileObj: camera.Profile = {     format: 1003,     size: {       width: 1920,       height: 1080     }   };   // 推荐录像分辨率之一   private videoProfileObj: camera.VideoProfile = {     format: 1003,     size: {       width: 1920,       height: 1080     },     frameRateRange: {       min: 30,       max: 60     }   };   private curSceneMode: camera.SceneMode = camera.SceneMode.NORMAL_PHOTO;    constructor() {   }    setSavePictureCallback(callback: (photoAsset: photoAccessHelper.PhotoAsset) => void): void {     this.handlePhotoAssetCb = callback;   }    setSceneMode(sceneMode: camera.SceneMode): void {     this.curSceneMode = sceneMode;   }    getSceneMode(): camera.SceneMode {     return this.curSceneMode;   }    getPreviewProfile(cameraOutputCapability: camera.CameraOutputCapability): camera.Profile | undefined {     let previewProfiles = cameraOutputCapability.previewProfiles;     if (previewProfiles.length < 1) {       return undefined;     }     let index = previewProfiles.findIndex((previewProfile: camera.Profile) => {       return previewProfile.size.width === this.previewProfileObj.size.width &&         previewProfile.size.height === this.previewProfileObj.size.height &&         previewProfile.format === this.previewProfileObj.format;     });     if (index === -1) {       return undefined;     }     return previewProfiles[index];   }    getPhotoProfile(cameraOutputCapability: camera.CameraOutputCapability): camera.Profile | undefined {     let photoProfiles = cameraOutputCapability.photoProfiles;     if (photoProfiles.length < 1) {       return undefined;     }     let index = photoProfiles.findIndex((photoProfile: camera.Profile) => {       return photoProfile.size.width === this.photoProfileObj.size.width &&         photoProfile.size.height === this.photoProfileObj.size.height &&         photoProfile.format === this.photoProfileObj.format;     });     if (index === -1) {       return undefined;     }     return photoProfiles[index];   }    getVideoProfile(cameraOutputCapability: camera.CameraOutputCapability): camera.VideoProfile | undefined {     let videoProfiles = cameraOutputCapability.videoProfiles;     if (videoProfiles.length < 1) {       return undefined;     }     for (let i = 0; i < videoProfiles.length; i++) {       Logger.info(TAG, `getVideoProfile: ${JSON.stringify(videoProfiles[i])}`);     }     let index = videoProfiles.findIndex((videoProfile: camera.VideoProfile) => {       return videoProfile.size.width === this.videoProfileObj.size.width &&         videoProfile.size.height === this.videoProfileObj.size.height &&         videoProfile.format === this.videoProfileObj.format &&         videoProfile.frameRateRange.min <= Constants.MAX_VIDEO_FRAME &&         videoProfile.frameRateRange.max <= Constants.MAX_VIDEO_FRAME;     });     if (index === -1) {       return undefined;     }     return videoProfiles[index];   }    isSupportedSceneMode(cameraManager: camera.CameraManager, cameraDevice: camera.CameraDevice): boolean {     let sceneModes = cameraManager.getSupportedSceneModes(cameraDevice);     if (sceneModes === undefined) {       return false;     }     let index = sceneModes.findIndex((sceneMode: camera.SceneMode) => {       return sceneMode === this.curSceneMode;     });     if (index === -1) {       return false;     }     return true;   }    /**    * 初始化相机功能    * @param surfaceId - Surface 的 ID    * @param cameraDeviceIndex - 相机设备索引    * @returns 无返回值    */   async initCamera(surfaceId: string, cameraDeviceIndex: number): Promise<void> {     Logger.debug(TAG, `initCamera cameraDeviceIndex: ${cameraDeviceIndex}`);     try {       await this.releaseCamera();       // 获取相机管理器实例       this.cameraManager = this.getCameraManagerFn();       if (this.cameraManager === undefined) {         Logger.error(TAG, 'cameraManager is undefined');         return;       }       // 获取支持指定的相机设备对象       this.cameras = this.getSupportedCamerasFn(this.cameraManager);       if (this.cameras.length < 1 || this.cameras.length < cameraDeviceIndex + 1) {         return;       }       this.curCameraDevice = this.cameras[cameraDeviceIndex];       let isSupported = this.isSupportedSceneMode(this.cameraManager, this.curCameraDevice);       if (!isSupported) {         Logger.error(TAG, 'The current scene mode is not supported.');         return;       }       let cameraOutputCapability =         this.cameraManager.getSupportedOutputCapability(this.curCameraDevice, this.curSceneMode);       let previewProfile = this.getPreviewProfile(cameraOutputCapability);       if (previewProfile === undefined) {         Logger.error(TAG, 'The resolution of the current preview stream is not supported.');         return;       }       this.previewProfileObj = previewProfile;       // 创建previewOutput输出对象       this.previewOutput = this.createPreviewOutputFn(this.cameraManager, this.previewProfileObj, surfaceId);       if (this.previewOutput === undefined) {         Logger.error(TAG, 'Failed to create the preview stream.');         return;       }       // 监听预览事件       this.previewOutputCallBack(this.previewOutput);       if (this.curSceneMode === camera.SceneMode.NORMAL_PHOTO) {         let photoProfile = this.getPhotoProfile(cameraOutputCapability);         if (photoProfile === undefined) {           Logger.error(TAG, 'The resolution of the current photo stream is not supported.');           return;         }         this.photoProfileObj = photoProfile;         // 创建photoOutPut输出对象         this.photoOutput = this.createPhotoOutputFn(this.cameraManager, this.photoProfileObj);         if (this.photoOutput === undefined) {           Logger.error(TAG, 'Failed to create the photo stream.');           return;         }       } else if (this.curSceneMode === camera.SceneMode.NORMAL_VIDEO) {         let videoProfile = this.getVideoProfile(cameraOutputCapability);         if (videoProfile === undefined) {           Logger.error(TAG, 'The resolution of the current video stream is not supported.');           return;         }         this.videoProfileObj = videoProfile;         this.avRecorder = await this.createAVRecorder();         if (this.avRecorder === undefined) {           Logger.error(TAG, 'Failed to create the avRecorder.');           return;         }         await this.prepareAVRecorder();         let videoSurfaceId = await this.avRecorder.getInputSurface();         // 创建videoOutPut输出对象         this.videoOutput = this.createVideoOutputFn(this.cameraManager, this.videoProfileObj, videoSurfaceId);         if (this.videoOutput === undefined) {           Logger.error(TAG, 'Failed to create the video stream.');           return;         }       }       // 创建cameraInput输出对象       this.cameraInput = this.createCameraInputFn(this.cameraManager, this.curCameraDevice);       if (this.cameraInput === undefined) {         Logger.error(TAG, 'Failed to create the camera input.');         return;       }       // 打开相机       let isOpenSuccess = await this.cameraInputOpenFn(this.cameraInput);       if (!isOpenSuccess) {         Logger.error(TAG, 'Failed to open the camera.');         return;       }       // 镜头状态回调       this.onCameraStatusChange(this.cameraManager);       // 监听CameraInput的错误事件       this.onCameraInputChange(this.cameraInput, this.curCameraDevice);       // 会话流程       await this.sessionFlowFn(this.cameraManager, this.cameraInput, this.previewOutput, this.photoOutput,         this.videoOutput);     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `initCamera fail: ${JSON.stringify(err)}`);     }   }    /**    * 获取可变焦距范围    */   getZoomRatioRange(): Array<number> {     let zoomRatioRange: Array<number> = [];     if (this.session !== undefined) {       zoomRatioRange = this.session.getZoomRatioRange();     }     return zoomRatioRange;   }    /**    * 变焦    */   setZoomRatioFn(zoomRatio: number): void {     Logger.info(TAG, `setZoomRatioFn value ${zoomRatio}`);     // 获取支持的变焦范围     try {       let zoomRatioRange = this.getZoomRatioRange();       Logger.info(TAG, `getZoomRatioRange success: ${JSON.stringify(zoomRatioRange)}`);     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `getZoomRatioRange fail: ${JSON.stringify(err)}`);     }      try {       this.session?.setZoomRatio(zoomRatio);       Logger.info(TAG, 'setZoomRatioFn success');     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `setZoomRatioFn fail: ${JSON.stringify(err)}`);     }   }    /**    * 以指定参数触发一次拍照    */   async takePicture(): Promise<void> {     Logger.info(TAG, 'takePicture start');     let cameraDeviceIndex = GlobalContext.get().getT<number>('cameraDeviceIndex');     let photoSettings: camera.PhotoCaptureSetting = {       quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,       mirror: cameraDeviceIndex ? true : false     };     await this.photoOutput?.capture(photoSettings);     Logger.info(TAG, 'takePicture end');   }    /**    * 释放会话及其相关参数    */   async releaseCamera(): Promise<void> {     Logger.info(TAG, 'releaseCamera is called');     try {       await this.previewOutput?.release();     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `previewOutput release fail: error: ${JSON.stringify(err)}`);     } finally {       this.previewOutput = undefined;     }     try {       await this.photoOutput?.release();     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `photoOutput release fail: error: ${JSON.stringify(err)}`);     } finally {       this.photoOutput = undefined;     }     try {       await this.avRecorder?.release();     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `avRecorder release fail: error: ${JSON.stringify(err)}`);     } finally {       this.avRecorder = undefined;     }      try {       await this.videoOutput?.release();     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `videoOutput release fail: error: ${JSON.stringify(err)}`);     } finally {       this.videoOutput = undefined;     }     try {       await this.session?.release();     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `captureSession release fail: error: ${JSON.stringify(err)}`);     } finally {       this.session = undefined;     }     try {       await this.cameraInput?.close();     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `cameraInput close fail: error: ${JSON.stringify(err)}`);     } finally {       this.cameraInput = undefined;     }     this.offCameraStatusChange();     Logger.info(TAG, 'releaseCamera success');   }    /**    * 获取相机管理器实例    */   getCameraManagerFn(): camera.CameraManager | undefined {     if (this.cameraManager) {       return this.cameraManager;     }     let cameraManager: camera.CameraManager | undefined = undefined;     try {       cameraManager = camera.getCameraManager(GlobalContext.get().getCameraSettingContext());       Logger.info(TAG, `getCameraManager success: ${cameraManager}`);     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `getCameraManager failed: ${JSON.stringify(err)}`);     }     return cameraManager;   }    /**    * 获取支持指定的相机设备对象    */   getSupportedCamerasFn(cameraManager: camera.CameraManager): Array<camera.CameraDevice> {     let supportedCameras: Array<camera.CameraDevice> = [];     try {       supportedCameras = cameraManager.getSupportedCameras();       Logger.info(TAG, `getSupportedCameras success: ${this.cameras}, length: ${this.cameras?.length}`);     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `getSupportedCameras failed: ${JSON.stringify(err)}`);     }     return supportedCameras;   }    /**    * 创建previewOutput输出对象    */   createPreviewOutputFn(cameraManager: camera.CameraManager, previewProfileObj: camera.Profile,     surfaceId: string): camera.PreviewOutput | undefined {     let previewOutput: camera.PreviewOutput | undefined = undefined;     try {       previewOutput = cameraManager.createPreviewOutput(previewProfileObj, surfaceId);       Logger.info(TAG, `createPreviewOutput success: ${previewOutput}`);     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `createPreviewOutput failed: ${JSON.stringify(err)}`);     }     return previewOutput;   }    /**    * 创建photoOutPut输出对象    */   createPhotoOutputFn(cameraManager: camera.CameraManager,     photoProfileObj: camera.Profile): camera.PhotoOutput | undefined {     let photoOutput: camera.PhotoOutput | undefined = undefined;     try {       photoOutput = cameraManager.createPhotoOutput(photoProfileObj);       Logger.info(TAG, `createPhotoOutputFn success: ${photoOutput}`);     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `createPhotoOutputFn failed: ${JSON.stringify(err)}`);     }     return photoOutput;   }    /**    * 创建videoOutPut输出对象    */   createVideoOutputFn(cameraManager: camera.CameraManager, videoProfileObj: camera.VideoProfile,     surfaceId: string): camera.VideoOutput | undefined {     let videoOutput: camera.VideoOutput | undefined = undefined;     try {       videoOutput = cameraManager.createVideoOutput(videoProfileObj, surfaceId);       Logger.info(TAG, `createVideoOutputFn success: ${videoOutput}`);     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `createVideoOutputFn failed: ${JSON.stringify(err)}`);     }     return videoOutput;   }    /**    * 创建cameraInput输出对象    */   createCameraInputFn(cameraManager: camera.CameraManager,     cameraDevice: camera.CameraDevice): camera.CameraInput | undefined {     Logger.info(TAG, 'createCameraInputFn is called.');     let cameraInput: camera.CameraInput | undefined = undefined;     try {       cameraInput = cameraManager.createCameraInput(cameraDevice);       Logger.info(TAG, 'createCameraInputFn success');     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `createCameraInputFn failed: ${JSON.stringify(err)}`);     }     return cameraInput;   }    /**    * 打开相机    */   async cameraInputOpenFn(cameraInput: camera.CameraInput): Promise<boolean> {     let isOpenSuccess = false;     try {       await cameraInput.open();       isOpenSuccess = true;       Logger.info(TAG, 'cameraInput open success');     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `createCameraInput failed : ${JSON.stringify(err)}`);     }     return isOpenSuccess;   }    /**    * 会话流程    */   async sessionFlowFn(cameraManager: camera.CameraManager, cameraInput: camera.CameraInput,     previewOutput: camera.PreviewOutput, photoOutput: camera.PhotoOutput | undefined,     videoOutput: camera.VideoOutput | undefined): Promise<void> {     try {       // 创建CaptureSession实例       if (this.curSceneMode === camera.SceneMode.NORMAL_PHOTO) {         this.session = cameraManager.createSession(this.curSceneMode) as camera.PhotoSession;       } else if (this.curSceneMode === camera.SceneMode.NORMAL_VIDEO) {         this.session = cameraManager.createSession(this.curSceneMode) as camera.VideoSession;       }       if (this.session === undefined) {         return;       }       this.onSessionErrorChange(this.session);       // 开始配置会话       this.session.beginConfig();       // 把CameraInput加入到会话       this.session.addInput(cameraInput);       // 把previewOutput加入到会话       this.session.addOutput(previewOutput);       if (this.curSceneMode === camera.SceneMode.NORMAL_PHOTO) {         if (photoOutput === undefined) {           return;         }         // 拍照监听事件         this.photoOutputCallBack(photoOutput);         // 把photoOutPut加入到会话         this.session.addOutput(photoOutput);       } else if (this.curSceneMode === camera.SceneMode.NORMAL_VIDEO) {         if (videoOutput === undefined) {           return;         }         // 把photoOutPut加入到会话         this.session.addOutput(videoOutput);       }       // 提交配置信息       await this.session.commitConfig();       if (this.curSceneMode === camera.SceneMode.NORMAL_VIDEO) {         this.setVideoStabilizationFn(this.session as camera.VideoSession, camera.VideoStabilizationMode.MIDDLE);       }       this.updateSliderValue();       this.setFocusMode(camera.FocusMode.FOCUS_MODE_AUTO);       // 开始会话工作       await this.session.start();       Logger.info(TAG, 'sessionFlowFn success');     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `sessionFlowFn fail : ${JSON.stringify(err)}`);     }   }    setVideoStabilizationFn(session: camera.VideoSession, videoStabilizationMode: camera.VideoStabilizationMode): void {     // 查询是否支持指定的视频防抖模式     let isVideoStabilizationModeSupported: boolean = session.isVideoStabilizationModeSupported(videoStabilizationMode);     if (isVideoStabilizationModeSupported) {       session.setVideoStabilizationMode(videoStabilizationMode);     }     Logger.info(TAG, 'setVideoStabilizationFn success');   }    /**    * 更新滑动条数据    */   updateSliderValue(): void {     let zoomRatioRange = this.getZoomRatioRange();     if (zoomRatioRange.length !== 0) {       let zoomRatioMin = zoomRatioRange[0];       let zoomRatioMax = zoomRatioRange[1] > Constants.ZOOM_RADIO_MAX ? Constants.ZOOM_RADIO_MAX : zoomRatioRange[1];       let sliderStep =         zoomRatioRange[1] > Constants.ZOOM_RADIO_MAX ? Constants.ZOOM_RADIO_MAX_STEP : Constants.ZOOM_RADIO_MIN_STEP;       AppStorage.set<SliderValue>('sliderValue', {         min: zoomRatioMin,         max: zoomRatioMax,         step: sliderStep       });     }   }    /**    * 监听拍照事件    */   photoOutputCallBack(photoOutput: camera.PhotoOutput): void {     try {       // 监听拍照开始       photoOutput.on('captureStartWithInfo', (err: BusinessError, captureStartInfo: camera.CaptureStartInfo): void => {         Logger.info(TAG, `photoOutputCallBack captureStartWithInfo success: ${JSON.stringify(captureStartInfo)}`);       });       // 监听拍照帧输出捕获       photoOutput.on('frameShutter', (err: BusinessError, frameShutterInfo: camera.FrameShutterInfo): void => {         Logger.info(TAG, `photoOutputCallBack frameShutter captureId:           ${frameShutterInfo.captureId}, timestamp: ${frameShutterInfo.timestamp}`);       });       // 监听拍照结束       photoOutput.on('captureEnd', (err: BusinessError, captureEndInfo: camera.CaptureEndInfo): void => {         Logger.info(TAG, `photoOutputCallBack captureEnd captureId:           ${captureEndInfo.captureId}, frameCount: ${captureEndInfo.frameCount}`);       });       // 监听拍照异常       photoOutput.on('error', (data: BusinessError): void => {         Logger.info(TAG, `photoOutPut data: ${JSON.stringify(data)}`);       });       photoOutput.on('photoAssetAvailable', (err: BusinessError, photoAsset: photoAccessHelper.PhotoAsset) => {         Logger.info(TAG, 'photoAssetAvailable begin');         if (photoAsset === undefined) {           Logger.error(TAG, 'photoAsset is undefined');           return;         }         this.handlePhotoAssetCb(photoAsset);       });     } catch (err) {       Logger.error(TAG, 'photoOutputCallBack error');     }   }    /**    * 监听预览事件    */   previewOutputCallBack(previewOutput: camera.PreviewOutput): void {     Logger.info(TAG, 'previewOutputCallBack is called');     try {       previewOutput.on('frameStart', (): void => {         Logger.debug(TAG, 'Preview frame started');       });       previewOutput.on('frameEnd', (): void => {         Logger.debug(TAG, 'Preview frame ended');       });       previewOutput.on('error', (previewOutputError: BusinessError): void => {         Logger.info(TAG, `Preview output previewOutputError: ${JSON.stringify(previewOutputError)}`);       });     } catch (err) {       Logger.error(TAG, 'previewOutputCallBack error');     }   }    /**    * 注册相机状态变化的回调函数    * @param err - 错误信息(如果有)    * @param cameraStatusInfo - 相机状态信息    * @returns 无返回值    */   registerCameraStatusChange(err: BusinessError, cameraStatusInfo: camera.CameraStatusInfo): void {     Logger.info(TAG, `cameraId: ${cameraStatusInfo.camera.cameraId},status: ${cameraStatusInfo.status}`);   }    /**    * 监听相机状态变化    * @param cameraManager - 相机管理器对象    * @returns 无返回值    */   onCameraStatusChange(cameraManager: camera.CameraManager): void {     Logger.info(TAG, 'onCameraStatusChange is called');     try {       cameraManager.on('cameraStatus', this.registerCameraStatusChange);     } catch (error) {       Logger.error(TAG, 'onCameraStatusChange error');     }   }    /**    * 停止监听相机状态变化    * @returns 无返回值    */   offCameraStatusChange(): void {     Logger.info(TAG, 'offCameraStatusChange is called');     this.cameraManager?.off('cameraStatus', this.registerCameraStatusChange);   }    /**    * 监听相机输入变化    * @param cameraInput - 相机输入对象    * @param cameraDevice - 相机设备对象    * @returns 无返回值    */   onCameraInputChange(cameraInput: camera.CameraInput, cameraDevice: camera.CameraDevice): void {     Logger.info(TAG, `onCameraInputChange is called`);     try {       cameraInput.on('error', cameraDevice, (cameraInputError: BusinessError): void => {         Logger.info(TAG, `onCameraInputChange cameraInput error code: ${cameraInputError.code}`);       });     } catch (error) {       Logger.error(TAG, 'onCameraInputChange error');     }   }    /**    * 监听捕获会话错误变化    * @param session - 相机捕获会话对象    * @returns 无返回值    */   onSessionErrorChange(session: camera.PhotoSession | camera.VideoSession): void {     try {       session.on('error', (captureSessionError: BusinessError): void => {         Logger.info(TAG,           'onCaptureSessionErrorChange captureSession fail: ' + JSON.stringify(captureSessionError.code));       });     } catch (error) {       Logger.error(TAG, 'onCaptureSessionErrorChange error');     }   }    async createAVRecorder(): Promise<media.AVRecorder | undefined> {     let avRecorder: media.AVRecorder | undefined = undefined;     try {       avRecorder = await media.createAVRecorder();     } catch (error) {       Logger.error(TAG, `createAVRecorder error: ${error}`);     }     return avRecorder;   }    initFd(): number {     Logger.info(TAG, 'initFd is called');     let filesDir = getContext().filesDir;     let filePath = filesDir + `/${Date.now()}.mp4`;     AppStorage.setOrCreate<string>('filePath', filePath);     let file: fileIo.File = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);     return file.fd;   }    async prepareAVRecorder(): Promise<void> {     Logger.info(TAG, 'prepareAVRecorder is called');     let fd = this.initFd();     let videoConfig: media.AVRecorderConfig = {       audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,       videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,       profile: {         audioBitrate: Constants.AUDIO_BITRATE,         audioChannels: Constants.AUDIO_CHANNELS,         audioCodec: media.CodecMimeType.AUDIO_AAC,         audioSampleRate: Constants.AUDIO_SAMPLE_RATE,         fileFormat: media.ContainerFormatType.CFT_MPEG_4,         videoBitrate: Constants.VIDEO_BITRATE,         videoCodec: media.CodecMimeType.VIDEO_AVC,         videoFrameWidth: this.videoProfileObj.size.width,         videoFrameHeight: this.videoProfileObj.size.height,         videoFrameRate: this.videoProfileObj.frameRateRange.max       },       url: `fd://${fd.toString()}`,       rotation: this.curCameraDevice?.cameraOrientation     };     Logger.info(TAG, `prepareAVRecorder videoConfig: ${JSON.stringify(videoConfig)}`);     await this.avRecorder?.prepare(videoConfig).catch((err: BusinessError): void => {       Logger.error(TAG, `prepareAVRecorder prepare err: ${JSON.stringify(err)}`);     });   }    /**    * 启动录制    */   async startVideo(): Promise<void> {     Logger.info(TAG, 'startVideo is called');     try {       await this.videoOutput?.start();       await this.avRecorder?.start();       this.isRecording = true;     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `startVideo err: ${JSON.stringify(err)}`);     }     Logger.info(TAG, 'startVideo End of call');   }    /**    * 停止录制    */   async stopVideo(): Promise<void> {     Logger.info(TAG, 'stopVideo is called');     if (!this.isRecording) {       Logger.info(TAG, 'not in recording');       return;     }     try {       if (this.avRecorder) {         await this.avRecorder.stop();       }       if (this.videoOutput) {         await this.videoOutput.stop();       }       this.isRecording = false;     } catch (error) {       let err = error as BusinessError;       Logger.error(TAG, `stopVideo err: ${JSON.stringify(err)}`);     }     Logger.info(TAG, 'stopVideo End of call');   }    /**    * 闪关灯    */   hasFlashFn(flashMode: camera.FlashMode): void {     // 检测是否有闪关灯     let hasFlash = this.session?.hasFlash();     Logger.debug(TAG, `hasFlash success, hasFlash: ${hasFlash}`);     // 检测闪光灯模式是否支持     let isFlashModeSupported = this.session?.isFlashModeSupported(flashMode);     Logger.debug(TAG, `isFlashModeSupported success, isFlashModeSupported: ${isFlashModeSupported}`);     // 设置闪光灯模式     this.session?.setFlashMode(flashMode);   }    /**    * 焦点    */   setFocusPoint(point: camera.Point): void {     // 设置焦点     this.session?.setFocusPoint(point);     Logger.info(TAG, `setFocusPoint success point: ${JSON.stringify(point)}`);     // 获取当前的焦点     let nowPoint: camera.Point | undefined = undefined;     nowPoint = this.session?.getFocusPoint();     Logger.info(TAG, `getFocusPoint success, nowPoint: ${JSON.stringify(nowPoint)}`);   }    /**    * 曝光区域    */   isMeteringPoint(point: camera.Point): void {     // 获取当前曝光模式     let exposureMode: camera.ExposureMode | undefined = undefined;     exposureMode = this.session?.getExposureMode();     Logger.info(TAG, `getExposureMode success, exposureMode: ${exposureMode}`);     this.session?.setMeteringPoint(point);     let exposurePoint: camera.Point | undefined = undefined;     exposurePoint = this.session?.getMeteringPoint();     Logger.info(TAG, `getMeteringPoint exposurePoint: ${JSON.stringify(exposurePoint)}`);   }    /**    * 曝光补偿    */   isExposureBiasRange(exposureBias: number): void {     Logger.debug(TAG, `setExposureBias value ${exposureBias}`);     // 查询曝光补偿范围     let biasRangeArray: Array<number> | undefined = [];     biasRangeArray = this.session?.getExposureBiasRange();     Logger.debug(TAG, `getExposureBiasRange success, biasRangeArray: ${JSON.stringify(biasRangeArray)}`);     // 设置曝光补偿     this.session?.setExposureBias(exposureBias);   }    /**    * 对焦模式    */   setFocusMode(focusMode: camera.FocusMode): void {     // 检测对焦模式是否支持     Logger.info(TAG, `setFocusMode is called`);     let isSupported = this.session?.isFocusModeSupported(focusMode);     Logger.info(TAG, `setFocusMode isSupported: ${isSupported}`);     // 设置对焦模式     if (!isSupported) {       return;     }     this.session?.setFocusMode(focusMode);   } }  export default new CameraService(); 
  • 在CameraService的initCamera函数里完成一个相机生命周期初始化的过程,包括调用getCameraMananger获取CameraMananger,调用getSupportedCameras获取支持的camera设备,调用getSupportedOutputCapability获取支持的camera设备能力集,调用createPreviewOutput创建预览输出,调用createCameraInput创建相机输入,调用CameraInput的open打开相机输入,调用onCameraStatusChange创建CameraManager注册回调,最后调用sessionFlowFn创建并开启Session。

  • 其中sessionFlowFn是一个创建session并开启预览的动作,主要流程包括:调用createSession创建Session,调用beginConfig开始配置会话,调用addInput把CameraInput加入到会话,调用addPreviewOutput把previewOutput加入到会话,调用commitConfig提交配置信息,调用start开始会话工作。

  • 在CameraService的releaseCamera函数里完成对相机生命周期释放的过程,调用output的release方法释放流,调用CameraInput的close方法关闭相机,再调用session的release释放当前会话。

  • 回调接口设置:

  • onCameraStatusChange:监听相机状态回调,在打开、退出相机,相机摄像头切换时会触发

  • onCameraInputChange:相机输入发生错误时触发回调

  • photoOutputCallBack:开启拍照时触发回调

  • previewOutputCallBack:开启预览时触发回调

  • onCaptureSessionErrorChange:session出现异常时触发回调

  • 相机预览、拍照,录像功能实现调用侧位于Index.ets,ModeComponent.ets中,

  • 源码参考:[Index.ets]

/*  * Copyright (c) 2024 Huawei Device Co., Ltd.  * Licensed under the Apache License, Version 2.0 (the 'License');  * you may not use this file except in compliance with the License.  * You may obtain a copy of the License at  *  *     http://www.apache.org/licenses/LICENSE-2.0  *  * Unless required by applicable law or agreed to in writing, software  * distributed under the License is distributed on an 'AS IS' BASIS,  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  * See the License for the specific language governing permissions and  * limitations under the License.  */  import { camera } from '@kit.CameraKit'; import CameraService from '../mode/CameraService'; import Logger from '../common/utils/Logger'; import { ModeComponent } from '../views/ModeComponent'; import { SlideComponent } from '../views/SlideComponent'; import { GlobalContext } from '../common/utils/GlobalContext'; import { Constants } from '../common/Constants'; import { FocusAreaComponent } from '../views/FocusAreaComponent'; import { FocusComponent } from '../views/FocusComponent'; import { FlashingLightComponent } from '../views/FlashingLightComponent';  const TAG = 'Index';  @Entry @Component struct Index {   // 主页面是否显示   @StorageLink('isShow') isShow: boolean = false;   @StorageLink('isOpenEditPage') isOpenEditPage: boolean = false;   // Flash Mode   @State flashMode: camera.FlashMode = camera.FlashMode.FLASH_MODE_CLOSE;   @State focusPointBol: boolean = false;   // 曝光区域手指点击坐标   @State focusPointVal: Array<number> = [0, 0];   @State xComponentAspectRatio: number = 1;   private mXComponentController: XComponentController = new XComponentController();   private defaultCameraDeviceIndex = 0;   private surfaceId = '';    aboutToAppear(): void {     Logger.info(TAG, 'aboutToAppear');   }    async aboutToDisAppear(): Promise<void> {     Logger.info(TAG, 'aboutToDisAppear');     this.flashMode = camera.FlashMode.FLASH_MODE_CLOSE;     await CameraService.releaseCamera();   }    async onPageShow(): Promise<void> {     Logger.info(TAG, 'onPageShow');     if (this.surfaceId !== '' && !this.isOpenEditPage) {       await CameraService.initCamera(this.surfaceId, GlobalContext.get().getT<number>('cameraDeviceIndex'));     }     this.isOpenEditPage = false;   }    async onPageHide(): Promise<void> {     Logger.info(TAG, 'onPageHide');   }    build() {     Stack() {       if (this.isShow) {         XComponent({           id: 'componentId',           type: 'surface',           controller: this.mXComponentController         })           .onLoad(async () => {             Logger.info(TAG, 'onLoad is called');             this.surfaceId = this.mXComponentController.getXComponentSurfaceId();             GlobalContext.get().setObject('cameraDeviceIndex', this.defaultCameraDeviceIndex);             GlobalContext.get().setObject('xComponentSurfaceId', this.surfaceId);             let surfaceRect: SurfaceRect = {               surfaceWidth: Constants.X_COMPONENT_SURFACE_HEIGHT,               surfaceHeight: Constants.X_COMPONENT_SURFACE_WIDTH             };             this.mXComponentController.setXComponentSurfaceRect(surfaceRect);             Logger.info(TAG, `onLoad surfaceId: ${this.surfaceId}`);             await CameraService.initCamera(this.surfaceId, this.defaultCameraDeviceIndex);           })           .border({             width: {               top: Constants.X_COMPONENT_BORDER_WIDTH,               bottom: Constants.X_COMPONENT_BORDER_WIDTH             },             color: Color.Black           })           // The width and height of the surface are opposite to those of the Xcomponent.           .width(px2vp(Constants.X_COMPONENT_SURFACE_HEIGHT))           .height(px2vp(Constants.X_COMPONENT_SURFACE_WIDTH))       }       // 曝光框和对焦框       FocusComponent({         focusPointBol: $focusPointBol,         focusPointVal: $focusPointVal       })        // 曝光对焦手指点击区域       FocusAreaComponent({         focusPointBol: $focusPointBol,         focusPointVal: $focusPointVal,         xComponentWidth: px2vp(Constants.X_COMPONENT_SURFACE_HEIGHT),         xComponentHeight: px2vp(Constants.X_COMPONENT_SURFACE_WIDTH)       })       // slide       SlideComponent()        // 拍照       ModeComponent()        Row({ space: Constants.ROW_SPACE_24 }) {         // 闪光灯         FlashingLightComponent({           flashMode: $flashMode         })       }       .margin({ left: Constants.CAPTURE_BUTTON_COLUMN_MARGIN })       .alignItems(VerticalAlign.Top)       .justifyContent(FlexAlign.Start)       .position({         x: Constants.FLASH_POSITION_X,         y: Constants.FLASH_POSITION_Y       })     }     .size({       width: Constants.FULL_PERCENT,       height: Constants.FULL_PERCENT     })     .backgroundColor(Color.Black)   } } 
  • 源码参考:[ModeComponent.ets]
/*  * Copyright (c) 2024 Huawei Device Co., Ltd.  * Licensed under the Apache License, Version 2.0 (the "License");  * you may not use this file except in compliance with the License.  * You may obtain a copy of the License at  *  *     http://www.apache.org/licenses/LICENSE-2.0  *  * Unless required by applicable law or agreed to in writing, software  * distributed under the License is distributed on an "AS IS" BASIS,  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  * See the License for the specific language governing permissions and  * limitations under the License.  */  import { router } from '@kit.ArkUI'; import { camera } from '@kit.CameraKit'; import { photoAccessHelper } from '@kit.MediaLibraryKit'; import CameraService from '../mode/CameraService'; import Logger from '../common/utils/Logger'; import { GlobalContext } from '../common/utils/GlobalContext'; import { Constants } from '../common/Constants';  const TAG: string = 'ModeComponent';  @Component export struct ModeComponent {   @StorageLink('isOpenEditPage') @Watch('changePageState') isOpenEditPage: boolean = false;   @State sceneMode: camera.SceneMode = camera.SceneMode.NORMAL_PHOTO;   @State isRecording: boolean = false;    changePageState(): void {     if (this.isOpenEditPage) {       this.onJumpClick();     }   }    aboutToAppear(): void {     Logger.info(TAG, 'aboutToAppear');     CameraService.setSavePictureCallback(this.handleSavePicture);   }    handleSavePicture = (photoAsset: photoAccessHelper.PhotoAsset): void => {     Logger.info(TAG, 'handleSavePicture');     this.setImageInfo(photoAsset);     AppStorage.set<boolean>('isOpenEditPage', true);     Logger.info(TAG, 'setImageInfo end');   }    setImageInfo(photoAsset: photoAccessHelper.PhotoAsset): void {     Logger.info(TAG, 'setImageInfo');     GlobalContext.get().setObject('photoAsset', photoAsset);   }    onJumpClick(): void {     GlobalContext.get().setObject('sceneMode', this.sceneMode);     // 目标url     router.pushUrl({ url: 'pages/EditPage' }, router.RouterMode.Single, (err) => {       if (err) {         Logger.error(TAG, `Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`);         return;       }       Logger.info(TAG, 'Invoke pushUrl succeeded.');     });   }    build() {     Column() {       Row({ space: Constants.COLUMN_SPACE_24 }) {         Column() {           Text('拍照')             .fontSize(Constants.FONT_SIZE_14)             .fontColor(Color.White)         }         .width(Constants.CAPTURE_COLUMN_WIDTH)         .backgroundColor(this.sceneMode === camera.SceneMode.NORMAL_PHOTO ? $r('app.color.theme_color') : '')         .borderRadius(Constants.BORDER_RADIUS_14)         .onClick(async () => {           if (this.sceneMode === camera.SceneMode.NORMAL_PHOTO) {             return;           }           this.sceneMode = camera.SceneMode.NORMAL_PHOTO;           CameraService.setSceneMode(this.sceneMode);           let cameraDeviceIndex = GlobalContext.get().getT<number>('cameraDeviceIndex');           let surfaceId = GlobalContext.get().getT<string>('xComponentSurfaceId');           await CameraService.initCamera(surfaceId, cameraDeviceIndex);         })          // 录像         Column() {           Text('录像')             .fontSize(Constants.FONT_SIZE_14)             .fontColor(Color.White)         }         .width(Constants.CAPTURE_COLUMN_WIDTH)         .backgroundColor(this.sceneMode === camera.SceneMode.NORMAL_VIDEO ? $r('app.color.theme_color') : '')         .borderRadius(Constants.BORDER_RADIUS_14)         .onClick(async () => {           if (this.sceneMode === camera.SceneMode.NORMAL_VIDEO) {             return;           }           this.sceneMode = camera.SceneMode.NORMAL_VIDEO;           CameraService.setSceneMode(this.sceneMode);           let cameraDeviceIndex = GlobalContext.get().getT<number>('cameraDeviceIndex');           let surfaceId = GlobalContext.get().getT<string>('xComponentSurfaceId');           await CameraService.initCamera(surfaceId, cameraDeviceIndex);         })       }       .height(Constants.CAPTURE_ROW_HEIGHT)       .width(Constants.FULL_PERCENT)       .justifyContent(FlexAlign.Center)       .alignItems(VerticalAlign.Center)        Row() {         Column() {         }         .width($r('app.string.200px'))          // 拍照-录像 按键         Column() {           if (!this.isRecording) {             Row() {               Button() {                 Text()                   .width($r('app.string.120px'))                   .height($r('app.string.120px'))                   .borderRadius($r('app.string.40px'))                   .backgroundColor(this.sceneMode === camera.SceneMode.NORMAL_VIDEO ?                     $r('app.color.theme_color') : Color.White)               }               .border({                 width: Constants.CAPTURE_BUTTON_BORDER_WIDTH,                 color: $r('app.color.border_color'),                 radius: Constants.CAPTURE_BUTTON_BORDER_RADIUS               })               .width($r('app.string.200px'))               .height($r('app.string.200px'))               .backgroundColor(Color.Black)               .onClick(async () => {                 if (this.sceneMode === camera.SceneMode.NORMAL_PHOTO) {                   await CameraService.takePicture();                 } else {                   await CameraService.startVideo();                   this.isRecording = true;                 }               })             }           } else {             Row() {               // 录像停止键               Button() {                 Image($r('app.media.ic_camera_video_close'))                   .size({ width: Constants.IMAGE_SIZE, height: Constants.IMAGE_SIZE })               }               .width($r('app.string.120px'))               .height($r('app.string.120px'))               .backgroundColor($r('app.color.theme_color'))               .onClick(() => {                 this.isRecording = !this.isRecording;                 CameraService.stopVideo().then(() => {                   this.isOpenEditPage = true;                   Logger.info(TAG, 'stopVideo success');                 })               })             }             .width($r('app.string.200px'))             .height($r('app.string.200px'))             .borderRadius($r('app.string.60px'))             .backgroundColor($r('app.color.theme_color'))             .justifyContent(FlexAlign.SpaceAround)           }         }          // 前后置摄像头切换         Column() {           Row() {             Button() {               Image($r('app.media.switch_camera'))                 .width($r('app.string.120px'))                 .height($r('app.string.120px'))             }             .width($r('app.string.200px'))             .height($r('app.string.200px'))             .backgroundColor($r('app.color.flash_background_color'))             .borderRadius($r('app.string.40px'))             .onClick(async () => {               let cameraDeviceIndex = GlobalContext.get().getT<number>('cameraDeviceIndex');               let surfaceId = GlobalContext.get().getT<string>('xComponentSurfaceId');               cameraDeviceIndex ? cameraDeviceIndex = 0 : cameraDeviceIndex = 1;               GlobalContext.get().setObject('cameraDeviceIndex', cameraDeviceIndex);               await CameraService.initCamera(surfaceId, cameraDeviceIndex);             })           }         }         .visibility(this.isRecording ? Visibility.Hidden : Visibility.Visible)       }       .padding({         left: Constants.CAPTURE_BUTTON_COLUMN_PADDING,         right: Constants.CAPTURE_BUTTON_COLUMN_PADDING       })       .width(Constants.FULL_PERCENT)       .justifyContent(FlexAlign.SpaceBetween)       .alignItems(VerticalAlign.Center)     }     .justifyContent(FlexAlign.End)     .height(Constants.TEN_PERCENT)     .padding({       left: Constants.CAPTURE_BUTTON_COLUMN_PADDING,       right: Constants.CAPTURE_BUTTON_COLUMN_PADDING     })     .margin({ bottom: Constants.CAPTURE_BUTTON_COLUMN_MARGIN })     .position({       x: Constants.ZERO_PERCENT,       y: Constants.EIGHTY_FIVE_PERCENT     })   } }  
  • 预览:开启预览位于Index.ets下的XComponent组件的onLoad接口中,其中调用CameraService.initCamera方法,将预览的surfaceId,摄像头设备作为入参啊传下去,完成开启相机的操作,开启预览。

  • 拍照:开启拍照位于ModeComponent.ets下的拍照按钮的onClick接口,调用CameraManager对象下的takePicture方法开启拍照操作。

  • 相机变焦功能实现调用侧位于SlideComponent.ets中,源码参考:[SlideComponent.ets]

/*  * Copyright (c) 2023 Huawei Device Co., Ltd.  * Licensed under the Apache License, Version 2.0 (the "License");  * you may not use this file except in compliance with the License.  * You may obtain a copy of the License at  *  *     http://www.apache.org/licenses/LICENSE-2.0  *  * Unless required by applicable law or agreed to in writing, software  * distributed under the License is distributed on an "AS IS" BASIS,  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  * See the License for the specific language governing permissions and  * limitations under the License.  */  import CameraService, { SliderValue } from '../mode/CameraService'; import Logger from '../common/utils/Logger'; import { Constants } from '../common/Constants';  const TAG: string = 'SlideComponent';  // 变焦组件 @Component export struct SlideComponent {   // slide滑块   @StorageLink('zoomRatio') zoomRatio: number = 1;   @StorageLink('sliderValue') sliderValue: SliderValue | undefined = undefined;   private fractionDigits = 2;   private xString = 'x';    aboutToDisappear(): void {   }    slideChange(value: number): void {     CameraService.setZoomRatioFn(value);   }    build() {     if (this.sliderValue) {       Column() {         Row() {           Text(this.zoomRatio + this.xString)             .fontColor($r('app.color.slide_text_font_color'))             .width($r('app.string.120px'))             .height($r('app.string.50px'))             .borderRadius(Constants.TEXT_BORDER_RADIUS)             .backgroundColor(Color.White)             .fontSize(Constants.FONT_SIZE_14)             .textAlign(TextAlign.Center)         }         .justifyContent(FlexAlign.Center)         .width(Constants.FULL_PERCENT)          Row() {           Text(this.sliderValue?.min + this.xString).fontColor(Color.White)           Text(this.sliderValue?.max + this.xString).fontColor(Color.White)         }         .justifyContent(FlexAlign.SpaceBetween).width(Constants.FULL_PERCENT)          Row() {           Slider({             value: this.zoomRatio,             min: this.sliderValue?.min,             max: this.sliderValue?.max,             step: this.sliderValue?.step,             style: SliderStyle.OutSet           })             .showSteps(false)             .trackColor($r('app.color.slider_track_color'))             .selectedColor($r('app.color.theme_color'))             .onChange((value: number) => {               Logger.info(TAG, 'onChange');               let val = Number(value.toFixed(this.fractionDigits));               this.slideChange(val);               this.zoomRatio = val;             })         }         .width(Constants.FULL_PERCENT)       }       .height($r('app.string.60px'))       .width(Constants.FORTY_PERCENT)       .position({         x: Constants.THIRTY_PERCENT,         y: Constants.SEVENTY_FIVE_PERCENT       })     }   } }  
  • 变焦:变焦功能位于SlideComponent.ets,通过Slider组件的onChange接口将变焦率通过CameraService.setZoomRatioFn方法调整预览显示的画面。

  • 相机对焦功能实现调用侧位于FocusAreaComponent.ets,FocusComponent.ets中,

  • 源码参考:[FocusAreaComponent.ets]

/*  * Copyright (c) 2024 Huawei Device Co., Ltd.  * Licensed under the Apache License, Version 2.0 (the "License");  * you may not use this file except in compliance with the License.  * You may obtain a copy of the License at  *  *     http://www.apache.org/licenses/LICENSE-2.0  *  * Unless required by applicable law or agreed to in writing, software  * distributed under the License is distributed on an "AS IS" BASIS,  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  * See the License for the specific language governing permissions and  * limitations under the License.  */  import Logger from '../common/utils/Logger'; import CameraService from '../mode/CameraService';  const TAG: string = 'FocusAreaComponent';  // 对焦区域 @Component export struct FocusAreaComponent {   @Link focusPointBol: boolean;   @Link focusPointVal: Array<number>;   @Prop xComponentWidth: number;   @Prop xComponentHeight: number;   // 对焦区域显示框定时器   private areaTimer: number = -1;   private focusFrameDisplayDuration: number = 3500;    build() {     Row() {     }     .width(this.xComponentWidth)     .height(this.xComponentHeight)     .opacity(1)     .onTouch((e: TouchEvent) => {       if (e.type === TouchType.Down) {         this.focusPointBol = true;         this.focusPointVal[0] = e.touches[0].windowX;         this.focusPointVal[1] = e.touches[0].windowY;         // 归一化焦点。 设置的焦点与相机sensor角度和窗口方向有关(相机sensor角度可通过CameraDevice的cameraOrientation属性获取),下面焦点是以竖屏窗口,相机sensor角度为90度场景下的焦点设置         CameraService.setFocusPoint({           x: e.touches[0].y / this.xComponentHeight,           y: 1 - (e.touches[0].x / this.xComponentWidth)         });       }       if (e.type === TouchType.Up) {         if (this.areaTimer) {           clearTimeout(this.areaTimer);         }         this.areaTimer = setTimeout(() => {           this.focusPointBol = false;         }, this.focusFrameDisplayDuration);       }     })     .onClick((event: ClickEvent) => {       Logger.info(TAG, 'onClick is called');     })   } }  
  • 源码参考:[FocusComponent.ets]
/*  * Copyright (c) 2024 Huawei Device Co., Ltd.  * Licensed under the Apache License, Version 2.0 (the "License");  * you may not use this file except in compliance with the License.  * You may obtain a copy of the License at  *  *     http://www.apache.org/licenses/LICENSE-2.0  *  * Unless required by applicable law or agreed to in writing, software  * distributed under the License is distributed on an "AS IS" BASIS,  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  * See the License for the specific language governing permissions and  * limitations under the License.  */  // 曝光选择 @Component export struct FocusComponent {   @Link focusPointBol: boolean;   @Link focusPointVal: Array<number>;   private mBorderWidth = 1.6;   private mBorderRadius = 10;   private mRowSize = 40;   private mFocusPoint = 60;   private focusFrameSize = 120;    build() {     if (this.focusPointBol) {       Row() {         // 对焦框         Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween }) {           Flex({ justifyContent: FlexAlign.SpaceBetween }) {             Row() {             }             .border({               width: {                 left: this.mBorderWidth,                 top: this.mBorderWidth               },               color: Color.White,               radius: { topLeft: this.mBorderRadius }             })             .size({ width: this.mRowSize, height: this.mRowSize })              Row() {             }             .border({               width: {                 right: this.mBorderWidth,                 top: this.mBorderWidth               },               color: Color.White,               radius: { topRight: this.mBorderRadius }             })             .size({ width: this.mRowSize, height: this.mRowSize })           }            Flex({ justifyContent: FlexAlign.SpaceBetween }) {             Row() {             }             .border({               width: {                 left: this.mBorderWidth,                 bottom: this.mBorderWidth               },               color: Color.White,               radius: { bottomLeft: this.mBorderRadius }             })             .size({ width: this.mRowSize, height: this.mRowSize })              Row() {             }             .border({               width: {                 right: this.mBorderWidth,                 bottom: this.mBorderWidth               },               color: Color.White,               radius: { bottomRight: this.mBorderRadius }             })             .size({ width: this.mRowSize, height: this.mRowSize })           }         }         .width(this.focusFrameSize)         .height(this.focusFrameSize)         .position({           x: this.focusPointVal[0] - this.mFocusPoint,           y: this.focusPointVal[1] - this.mFocusPoint         })       }       .zIndex(1)     }   } }  
  • 对焦:对焦功能位于FocusAreaComponent.ets,通过FocusAreaComponent组件的onTouch接口将归一化焦点通过CameraService.setFocusPoint方法调整预览显示的对焦画面。
    以上就是本篇文章所带来的鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!
    下面是鸿蒙的完整学习路线,展示如下:
    1

除此之外,根据这个学习鸿蒙全栈学习路线,也附带一整套完整的学习【文档+视频】,内容包含如下

内容包含了:(ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!

鸿蒙【北向应用开发+南向系统层开发】文档

鸿蒙【基础+实战项目】视频

鸿蒙面经

在这里插入图片描述

为了避免大家在学习过程中产生更多的时间成本,对比我把以上内容全部放在了↓↓↓想要的可以自拿喔!谢谢大家观看!

    广告一刻

    为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!