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

相机数据采集保存

介绍

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

效果预览

使用说明

  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方法调整预览显示的对焦画面。
    以上就是本篇文章所带来的鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!
    下面是鸿蒙的完整学习路线 ,展示如下:

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

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

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

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

鸿蒙面经

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

相关推荐
Random_index3 小时前
#Uniapp篇:支持纯血鸿蒙&发布&适配&UIUI
uni-app·harmonyos
鸿蒙自习室6 小时前
鸿蒙多线程开发——线程间数据通信对象02
ui·harmonyos·鸿蒙
SuperHeroWu79 小时前
【HarmonyOS】鸿蒙应用接入微博分享
华为·harmonyos·鸿蒙·微博·微博分享·微博sdk集成·sdk集成
zhangjr057511 小时前
【HarmonyOS Next】鸿蒙实用装饰器一览(一)
前端·harmonyos·arkts
Industio_触觉智能12 小时前
如何在开源鸿蒙OpenHarmony开启SELinux模式?RK3566鸿蒙开发板演示
openharmony·selinux·开源鸿蒙·鸿蒙开发板·rk3566开发板
九州ip动态13 小时前
模拟器多开限制ip,如何设置单窗口单ip,每个窗口ip不同
tcp/ip·游戏·媒体
那就举个栗子!14 小时前
多传感器融合slam过程解析【大白话版】
数码相机
xy1899016 小时前
相机触发模式
数码相机
诗歌难吟46418 小时前
初识ArkUI
harmonyos
SameX18 小时前
HarmonyOS Next 设备安全特性深度剖析学习
harmonyos