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


    相机数据采集保存

    介绍

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

    效果预览

    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 | Array = [];
      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 {
        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 {
        let zoomRatioRange: Array = [];
        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 {
        Logger.info(TAG, 'takePicture start');
        let cameraDeviceIndex = GlobalContext.get().getT('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 {
        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 {
        let supportedCameras: Array = [];
        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 {
        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 {
        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', {
            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 {
        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('filePath', filePath);
        let file: fileIo.File = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
        return file.fd;
      }
    
      async prepareAVRecorder(): Promise {
        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 {
        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 {
        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 | 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 = [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 {
        Logger.info(TAG, 'aboutToDisAppear');
        this.flashMode = camera.FlashMode.FLASH_MODE_CLOSE;
        await CameraService.releaseCamera();
      }
    
      async onPageShow(): Promise {
        Logger.info(TAG, 'onPageShow');
        if (this.surfaceId !== '' && !this.isOpenEditPage) {
          await CameraService.initCamera(this.surfaceId, GlobalContext.get().getT('cameraDeviceIndex'));
        }
        this.isOpenEditPage = false;
      }
    
      async onPageHide(): Promise {
        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('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('cameraDeviceIndex');
              let surfaceId = GlobalContext.get().getT('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('cameraDeviceIndex');
              let surfaceId = GlobalContext.get().getT('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('cameraDeviceIndex');
                  let surfaceId = GlobalContext.get().getT('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;
      @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;
      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内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!

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

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

    鸿蒙面经

    在这里插入图片描述

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

  • 相关阅读:
    与所有 ARM 工具、软件兼容?韦斯佰瑞这款MCU内核值得关注!
    1.摄像机几何
    Vue2高级-scoped样式、组件自定义事件
    嵌套快元素垂直外边距的塌陷以及清除网页元素的内外边距
    Map接口和常用方法
    MongoDB文档(二)
    C++:new与delete
    基于Spring Boot和Spring Cloud实现微服务架构学习(四)-Spring Cloud总结
    Pytorch中关于forward函数的理解与用法
    readline
  • 原文地址:https://blog.csdn.net/2301_76813281/article/details/140895554