目录

如何使自定义相机完成对焦后拍照

问题现象

使用自定义相机实现扫码组件,在识别到条形码后触发拍照,相机对焦模式设置为FOCUS_MODE_CONTINUOUS_AUTO(连续自动对焦),有时会出现拍照模糊的情况。如何实现扫码后对焦完成自动拍照,使拍照结果清晰?

背景知识

解决方案

在扫码完成后,将相机对焦模式设置为FOCUS_MODE_AUTO(自动对焦),通过setFocusPoint接口设置对焦点为条形码中心,注册focusStateChange事件,监听焦点状态变化,确保对焦成功后,触发拍照,相关代码如下:

let focusModeStatus: boolean = false;
try {
  // photoSession为相机会话。
  focusModeStatus = resources.photoSession.isFocusModeSupported(camera.FocusMode.FOCUS_MODE_AUTO);
} catch (err) {
  console.error('failed to check whether the focus mode is supported: ' + JSON.stringify(err));
}
if (focusModeStatus) {
  const focusPoint: camera.Point = { x: 0.5, y: 0.5 };
  try {
    // 设置对焦模式为自动对焦。
    resources.photoSession.setFocusMode(camera.FocusMode.FOCUS_MODE_AUTO);
    // 设置对焦点为条形码中心。
    resources.photoSession.setFocusPoint(focusPoint);
    console.info(`succeed in set focus mode`);
  } catch (err) {
    console.error(`failed to set the focus mode: ${JSON.stringify(err)}`);
  }
}

let focusStatePrev = camera.FocusState.FOCUS_STATE_SCAN; // 上一次对焦状态。
// 监听焦点变化事件。
resources.photoSession.on('focusStateChange', async (err: BusinessError, focusState: camera.FocusState) => {
  if (err !== undefined && err.code !== 0) {
    console.error(`focus status change error: ${JSON.stringify(err)}`);
    return;
  }
  // 上一次对焦状态为触发对焦,当前对焦状态为对焦成功,此时完成对焦。
  if (focusStatePrev === camera.FocusState.FOCUS_STATE_SCAN &&
    focusState === camera.FocusState.FOCUS_STATE_FOCUSED) {
    await resources.photoOutput!.capture(); // 触发拍照。
  }
  focusStatePrev = focusState;
});

完整代码如下:

import { camera } from '@kit.CameraKit';
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';

const permissions: Array<Permissions> = ['ohos.permission.CAMERA'];

async function reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext) {
  let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  try {
    await atManager.requestPermissionsFromUser(context, permissions);
  } catch (err) {
    console.error(`request permissions from user failed, ${JSON.stringify(err)}`);
  }
}

interface ShootingResources {
  cameraInput?: camera.CameraInput;
  previewOutput?: camera.PreviewOutput;
  photoOutput?: camera.PhotoOutput;
  photoSession?: camera.PhotoSession;
}

const resources: ShootingResources = {};

function setPhotoOutputCb(photoOutput: camera.PhotoOutput): void {
  if (!photoOutput) {
    console.error('photoOutput is null');
    return;
  }
  // 设置回调之后,调用photoOutput的capture方法,就会将拍照的buffer回传到回调中。
  photoOutput?.on('photoAvailable', (err: BusinessError, photo: camera.Photo): void => {
    console.info('getPhoto start');
    if (err && err.code !== 0) {
      console.error('getPhoto failed');
      return;
    }
    if (!photo || !photo.main) {
      console.error('photo is null');
      return;
    }
    let imageObj = photo.main;
    imageObj.getComponent(image.ComponentType.JPEG,
      async (errCode: BusinessError, component: image.Component) => {
        console.info('getComponent start');
        if (errCode && errCode.code !== 0) {
          console.error('getComponent failed');
          imageObj.release();
          return;
        }
        let buffer: ArrayBuffer;
        if (component && component.byteBuffer) {
          buffer = component.byteBuffer;
        } else {
          console.error('byteBuffer is null');
          imageObj.release();
          return;
        }
        // 如需要在图库中看到所保存的图片、视频资源,请使用用户无感的安全控件创建媒体资源。
        let imageSource = image.createImageSource(buffer);
        let pixelMap = await imageSource.createPixelMap();
        AppStorage.setOrCreate('PixelMap', pixelMap);

        // buffer处理结束后需要释放该资源,如果未正确释放资源会导致后续拍照获取不到buffer。
        imageObj.release();
      });
  });
}

async function cameraShootingCase(context: Context, surfaceId: string): Promise<void> {
  try {
    // 创建CameraManager对象。
    let cameraManager: camera.CameraManager = camera.getCameraManager(context);
    if (!cameraManager) {
      console.error('camera.getCameraManager error');
      return;
    }

    // 监听相机状态变化。
    cameraManager.on('cameraStatus', (err: BusinessError, cameraStatusInfo: camera.CameraStatusInfo) => {
      if (err !== undefined && err.code !== 0) {
        console.error('cameraStatus with errorCode = ' + err.code);
        return;
      }
      console.info(`camera : ${cameraStatusInfo.camera.cameraId}`);
      console.info(`status: ${cameraStatusInfo.status}`);
    });

    // 获取相机列表。
    let cameraArray: Array<camera.CameraDevice> = cameraManager.getSupportedCameras();
    if (!cameraArray || cameraArray.length <= 0) {
      console.error('cameraManager.getSupportedCameras error');
      return;
    }

    for (let index = 0; index < cameraArray.length; index++) {
      console.info('cameraId : ' + cameraArray[index].cameraId); // 获取相机ID。
      console.info('cameraPosition : ' + cameraArray[index].cameraPosition); // 获取相机位置。
      console.info('cameraType : ' + cameraArray[index].cameraType); // 获取相机类型。
      console.info('connectionType : ' + cameraArray[index].connectionType); // 获取相机连接类型。
    }

    // 创建相机输入流。
    resources.cameraInput = cameraManager.createCameraInput(cameraArray[0]);
    if (!resources.cameraInput) {
      console.error('cameraInput is null');
      return;
    }

    // 监听cameraInput错误信息。
    let cameraDevice: camera.CameraDevice = cameraArray[0];
    resources.cameraInput.on('error', cameraDevice, (error: BusinessError) => {
      console.error(`Camera input error code: ${error.code}`);
    });

    // 打开相机。
    await resources.cameraInput.open();

    // 获取支持的模式类型。
    let sceneModes: Array<camera.SceneMode> = cameraManager.getSupportedSceneModes(cameraArray[0]);
    let isSupportPhotoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0;
    if (!isSupportPhotoMode) {
      console.error('photo mode not support');
      releaseResources();
      return;
    }

    // 获取相机设备支持的输出流能力。
    let cameraOutputCap: camera.CameraOutputCapability =
      cameraManager.getSupportedOutputCapability(cameraArray[0], camera.SceneMode.NORMAL_PHOTO);
    if (!cameraOutputCap) {
      console.error('cameraManager.getSupportedOutputCapability error');
      return;
    }
    console.info('outputCapability: ' + JSON.stringify(cameraOutputCap));

    let previewProfilesArray: Array<camera.Profile> = cameraOutputCap.previewProfiles;
    if (!previewProfilesArray || previewProfilesArray.length <= 0) {
      console.error('previewProfilesArray is null or []');
      releaseResources();
      return;
    }

    let photoProfilesArray: Array<camera.Profile> = cameraOutputCap.photoProfiles;
    if (!photoProfilesArray || photoProfilesArray.length <= 0) {
      console.error('photoProfilesArray is null or []');
      releaseResources();
      return;
    }

    // 创建预览输出流,其中参数surfaceId参考上文XComponent组件,预览流为XComponent组件提供的surface。
    resources.previewOutput = cameraManager.createPreviewOutput(previewProfilesArray[0], surfaceId);
    if (!resources.previewOutput) {
      console.error('previewOutput is null');
      releaseResources();
      return;
    }
    try {
      // 监听预览输出错误信息。
      resources.previewOutput.on('error', (error: BusinessError) => {
        console.error(`Preview output error code: ${error.code}`);
      });
    } catch (e) {
      console.error(`previewOutput.on call failed, error: ${JSON.stringify(e)}`);
    }

    // 创建拍照输出流。
    resources.photoOutput = cameraManager.createPhotoOutput(photoProfilesArray[0]);
    if (!resources.photoOutput) {
      console.error('photoOutput is null');
      releaseResources();
      return;
    }

    // 调用上面的回调函数来展示图片。
    setPhotoOutputCb(resources.photoOutput);

    // 创建会话。
    let photoSession = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO);
    if (!photoSession) {
      console.error('photoSession is null');
      releaseResources();
      return;
    }
    resources.photoSession = photoSession as camera.PhotoSession;
    try {
      // 监听session错误信息。
      resources.photoSession.on('error', (error: BusinessError) => {
        console.error(`Capture session error code: ${error.code}`);
      });
    } catch (e) {
      console.error(`photoSession.on call failed, error: ${JSON.stringify(e)}`);
    }

    // 开始配置会话。
    resources.photoSession.beginConfig();

    // 向会话中添加相机输入流。
    resources.photoSession.addInput(resources.cameraInput);

    // 向会话中添加预览输出流。
    resources.photoSession.addOutput(resources.previewOutput);

    // 向会话中添加拍照输出流。
    resources.photoSession.addOutput(resources.photoOutput);

    // 提交会话配置。
    await resources.photoSession.commitConfig();

    // 启动会话。
    await resources.photoSession.start();
    // 判断设备是否支持闪光灯。
    let flashStatus: boolean = false;
    flashStatus = resources.photoSession.hasFlash();
    console.info('Returned with the flash light support status:' + flashStatus);

    if (flashStatus) {
      // 判断是否支持自动闪光灯模式。
      let flashModeStatus: boolean = resources.photoSession.isFlashModeSupported(camera.FlashMode.FLASH_MODE_AUTO);
      if (flashModeStatus) {
        // 设置自动闪光灯模式。
        resources.photoSession.setFlashMode(camera.FlashMode.FLASH_MODE_AUTO);
      }
    }
    let focusModeStatus: boolean = false;
    try {
      // photoSession为相机会话。
      focusModeStatus = resources.photoSession.isFocusModeSupported(camera.FocusMode.FOCUS_MODE_AUTO);
    } catch (err) {
      console.error('failed to check whether the focus mode is supported: ' + JSON.stringify(err));
    }
    if (focusModeStatus) {
      const focusPoint: camera.Point = { x: 0.5, y: 0.5 };
      try {
        // 设置对焦模式为自动对焦。
        resources.photoSession.setFocusMode(camera.FocusMode.FOCUS_MODE_AUTO);
        // 设置对焦点为条形码中心。
        resources.photoSession.setFocusPoint(focusPoint);
        console.info(`succeed in set focus mode`);
      } catch (err) {
        console.error(`failed to set the focus mode: ${JSON.stringify(err)}`);
      }
    }

    let focusStatePrev = camera.FocusState.FOCUS_STATE_SCAN; // 上一次对焦状态。
    // 监听焦点变化事件。
    resources.photoSession.on('focusStateChange', async (err: BusinessError, focusState: camera.FocusState) => {
      if (err !== undefined && err.code !== 0) {
        console.error(`focus status change error: ${JSON.stringify(err)}`);
        return;
      }
      // 上一次对焦状态为触发对焦,当前对焦状态为对焦成功,此时完成对焦。
      if (focusStatePrev === camera.FocusState.FOCUS_STATE_SCAN &&
        focusState === camera.FocusState.FOCUS_STATE_FOCUSED) {
        await resources.photoOutput!.capture(); // 触发拍照。
      }
      focusStatePrev = focusState;
    });

    // 会话置空。
    resources.photoSession = undefined;
  } catch (error) {
    console.error(`cameraShootingCase call failed, error: ${JSON.stringify(error)}`);
    releaseResources();
  }
}

async function releaseResources(): Promise<void> {
  // 停止当前会话。
  await resources.photoSession?.stop().catch((e: BusinessError) => {
    console.error('photoSession stop failed: ', e);
  });

  // 释放相机输入流。
  await resources.cameraInput?.close().catch((e: BusinessError) => {
    console.error('cameraInput close failed: ', e);
  });

  // 释放预览输出流。
  await resources.previewOutput?.release().catch((e: BusinessError) => {
    console.error('previewOutput release failed: ', e);
  });

  // 释放拍照输出流。
  await resources.photoOutput?.release().catch((e: BusinessError) => {
    console.error('photoOutput release failed: ', e);
  });

  // 释放会话。
  await resources.photoSession?.release().catch((e: BusinessError) => {
    console.error('photoSession release failed: ', e);
  });
}

@Entry
@Component
struct Index {
  @StorageLink('PixelMap') pixelMap: image.PixelMap | undefined = undefined;
  private mXComponentCtrl: XComponentController = new XComponentController();

  aboutToAppear(): void {
    let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
    reqPermissionsFromUser(permissions, context);
  }

  build() {
    Column({ space: 40 }) {
      Image(this.pixelMap)
        .width('50%')
        .aspectRatio(1)
        .backgroundColor(Color.Black)

      XComponent({
        type: XComponentType.SURFACE,
        controller: this.mXComponentCtrl,
      })
        .width('50%')
        .aspectRatio(1)
        .renderFit(RenderFit.RESIZE_CONTAIN)

      Button() {
        Text('拍照')
          .padding(10)
          .fontSize(30);
      }
      .onClick(() => {
        let surfaceId = this.mXComponentCtrl.getXComponentSurfaceId();
        let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
        cameraShootingCase(context, surfaceId);
      });
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}