目录

适配相机旋转角度(ArkTS)

屏幕处于不同的屏幕状态时,原始图像需旋转不同的角度,以确保图像在合适的方向显示,效果如图所示。

本开发指导将指导开发者在预览、拍照、录像等不同场景下,如何适配相机的旋转角度。

在本开发指导中,提供两种方案来获取预览、拍照、录像的旋转角度:

详细的API参考说明,请参考Camera API文档。

创建会话

  1. 导入相机等相关模块。

    import { camera } from '@kit.CameraKit';
    import { BusinessError } from '@kit.BasicServicesKit';
  2. 创建Session会话。

    相机使用预览等功能前,均需创建相机会话,调用CameraManager中的createSession方法创建一个会话,创建会话时需指定创建SceneMode为NORMAL_PHOTO或NORMAL_VIDEO,创建的session处于拍照或者录像模式。

    createPhotoSession(cameraManager: camera.CameraManager): camera.Session | undefined {
      let session: camera.Session | undefined = undefined;
      try {
        session = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
      } catch (error) {
        let err = error as BusinessError;
        console.error(`Failed to create the session instance. error: ${err}`);
      }
      return session;
    }
    
    createVideoSession(cameraManager: camera.CameraManager): camera.Session | undefined {
      let session: camera.Session | undefined = undefined;
      try {
        session = cameraManager.createSession(camera.SceneMode.NORMAL_VIDEO) as camera.VideoSession;
      } catch (error) {
        let err = error as BusinessError;
        console.error(`Failed to create the session instance. error: ${err}`);
      }
      return session;
    }

预览

完成会话创建后,开发者可根据实际需求,配置输出流。

  1. 在会话配置过程中获取并设置预览旋转角度,即:使用commitConfig接口提交相关配置后调用,建议在Start起流前调用。

    接口说明 实现方案
    • 方案一:

      由应用通过display.getDefaultDisplaySync接口获取Display对象并读取其rotation属性值,来计算displayRotation(显示设备的屏幕旋转角度),将对应角度填入getPreviewRotation接口。

      例如,Display.rotation = 1,表示显示设备屏幕顺时针旋转为90°,此处displayRotation填入90。

      import { display } from '@kit.ArkUI';
      import { camera } from '@kit.CameraKit';
      
      GetAndSetPreviewRotation(previewOutput: camera.PreviewOutput) {
        // previewOutput是创建的预览输出
        try {
          let displayRotation = display.getDefaultDisplaySync().rotation;
          let previewRotation = previewOutput.getPreviewRotation(displayRotation * camera.ImageRotation.ROTATION_90);
          let isDisplayLocked: boolean = false; // 建议与setXComponentSurfaceRotation入参的lock属性保持一致
          previewOutput.setPreviewRotation(previewRotation, isDisplayLocked);
        } catch (error) {
          // 失败返回错误码error.code并处理
          let err = error as BusinessError;
          console.error(`GetAndSetPreviewRotation call failed. error code: ${err.code}`);
        }
      }
    • 方案二:

      从API版本23开始,入参displayRotation为可选参数,当不传入参数时,由系统获取displayRotation进行预览旋转角度计算。如果应用涉及使用USB相机或在多屏场景下,建议使用方案二。

      import { camera } from '@kit.CameraKit';
      
      GetAndSetPreviewRotationWithoutDisplayRotation(previewOutput: camera.PreviewOutput) {
        // previewOutput是创建的预览输出
        try {
          let previewRotation = previewOutput.getPreviewRotation();
          let isDisplayLocked: boolean = false; // 建议与setXComponentSurfaceRotation入参的lock属性保持一致
          previewOutput.setPreviewRotation(previewRotation, isDisplayLocked);
        } catch (error) {
          // 失败返回错误码error.code并处理
          let err = error as BusinessError;
          console.error(`GetAndSetPreviewRotationWithoutDisplayRotation call failed. error code: ${err.code}`);
        }
      }
  2. 通过监听Display对象变化,感知窗口当前状态,如当前相机窗口发生旋转时,需对预览流进行角度修正。推荐在会话配置中完成获取并设置预览旋转后,直接创建监听。

    • 方案一:

      display.off('change');
      display.on('change', () => {
        GetAndSetPreviewRotation(previewOutput);
      });
    • 方案二:

      从API版本23开始,入参displayRotation为可选参数,当不传入参数时,由系统获取displayRotation进行预览旋转角度计算。如果应用涉及使用USB相机或在多屏场景下,建议使用方案二。

      display.off('change');
      display.on('change', () => {
        GetAndSetPreviewRotationWithoutDisplayRotation(previewOutput);
      });

拍照

完成会话创建后,开发者可根据实际需求,配置输出流。

  1. 调用PhotoOutput中的getPhotoRotation可以获取到拍照旋转角度。该接口需要在session调用commitConfig完成配流后调用。

    • 方案一:

      deviceDegree:设备旋转角度。拍照的旋转角度与重力方向(即设备旋转角度)相关,获取方式请见计算设备旋转角度。

      getPhotoRotation(photoOutput: camera.PhotoOutput, deviceDegree: number): camera.ImageRotation {
        let photoRotation: camera.ImageRotation = camera.ImageRotation.ROTATION_0;
        try {
          photoRotation = photoOutput.getPhotoRotation(deviceDegree);
          console.info(`Photo rotation is: ${photoRotation}`);
        } catch (error) {
          // 失败返回错误码error.code并处理
          let err = error as BusinessError;
          console.error(`The photoOutput.getPhotoRotation call failed. error code: ${err.code}`);
        }
        return photoRotation;
      }
    • 方案二:

      从API版本23开始,入参deviceDegree为可选参数,当不传入参数时,由系统获取deviceDegree进行拍照旋转角度计算。当重力传感器数据无效,无法计算deviceDegree时,系统将使用最后一次有效的deviceDegree。如果应用涉及使用USB相机或在多屏场景下,建议使用方案二。

      getPhotoRotationWithoutDeviceDegree(photoOutput: camera.PhotoOutput): camera.ImageRotation {
        let photoRotation: camera.ImageRotation = camera.ImageRotation.ROTATION_0;
        try {
          photoRotation = photoOutput.getPhotoRotation();
          console.info(`Photo rotation is: ${photoRotation}`);
        } catch (error) {
          // 失败返回错误码error.code并处理
          let err = error as BusinessError;
          console.error(`The photoOutput.getPhotoRotationWithoutDeviceDegree call failed. error code: ${err.code}`);
        }
        return photoRotation;
      }
  2. 应用将拍照角度写入PhotoCaptureSetting.rotation。

  3. 其余参数的配置及拍照,可参考拍照开发指导。

录像

完成会话创建后,开发者可根据实际需求,配置输出流。

  1. 调用VideoOutput中的getVideoRotation可以获取到录像的旋转角度。该接口需要在session调用commitConfig完成配流后调用。

    • 方案一:

      deviceDegree:设备旋转角度。录像的旋转角度与重力方向(即设备旋转角度)相关,获取方式请见计算设备旋转角度。

      getVideoRotation(videoOutput: camera.VideoOutput, deviceDegree: number): camera.ImageRotation {
        let videoRotation: camera.ImageRotation = camera.ImageRotation.ROTATION_0;
        try {
          videoRotation = videoOutput.getVideoRotation(deviceDegree);
          console.info(`Video rotation is: ${videoRotation}`);
        } catch (error) {
          // 失败返回错误码error.code并处理
          let err = error as BusinessError;
          console.error(`The videoOutput.getVideoRotation call failed. error code: ${err.code}`);
        }
        return videoRotation;
      }
    • 方案二:

      从API版本23开始,入参deviceDegree为可选参数,当不传入参数时,由系统获取deviceDegree进行录像旋转角度计算。当重力传感器数据无效,无法计算deviceDegree时,系统将使用最后一次有效的deviceDegree。如果应用涉及使用USB相机或在多屏场景下,建议使用方案二。

      getVideoRotationWithoutDeviceDegree(videoOutput: camera.VideoOutput): camera.ImageRotation {
        let videoRotation: camera.ImageRotation = camera.ImageRotation.ROTATION_0;
        try {
          videoRotation = videoOutput.getVideoRotation();
          console.info(`Video rotation is: ${videoRotation}`);
        } catch (error) {
          // 失败返回错误码error.code并处理
          let err = error as BusinessError;
          console.error(`The videoOutput.getVideoRotationWithoutDeviceDegree call failed. error code: ${err.code}`);
        }
        return videoRotation;
      }
  2. 在AVRecorder.prepare后使用updateRotation设置录像角度。

  3. 其余参数的配置及启动录像,可参考录像开发指导。

计算设备旋转角度

当前可通过调用once(type: SensorId.GRAVITY, callback: Callback<GravityResponse>)获取一次重力传感器在x、y、z三个方向上的数据,计算得出设备旋转角度deviceDegree,示例如下所示。

如果无法获得重力传感器数据,需要申请重力传感器权限ohos.permission.ACCELEROMETER。权限申请请参考声明权限,如何获取传感器数据请参考传感器开发指导。

getRealData(data: sensor.GravityResponse): number {
  let getDeviceDegree: number = 0;
  let x = data.x;
  let y = data.y;
  let z = data.z;
  if ((x * x + y * y) * 3 < z * z) {
    return getDeviceDegree;
  } else {
    try {
      let sd: Decimal = Decimal.atan2(y, -x);
      let sc: Decimal = Decimal.round(Number(sd) / 3.141592653589 * 180);
      getDeviceDegree = 90 - Number(sc);
      getDeviceDegree = getDeviceDegree >= 0 ? getDeviceDegree % 360 : getDeviceDegree % 360 + 360;
    } catch (error) {
      let err = error as BusinessError;
      console.error(`decimal failed, error: ${err.code}`);
    }
  }
  return getDeviceDegree;
}

async getGravity() : Promise<number> {
  let data: sensor.Sensor[];
  let isSupported: boolean = false;
  try {
    data = await sensor.getSensorList();
  } catch (error) {
    let err = error as BusinessError;
    console.error(`getSensorList failed, error: ${err.code}`);
    return -1; // 异常场景下返回默认值
  }

  for (let i = 0; i < data.length; i++) {
    if (data[i].sensorId === sensor.SensorId.GRAVITY) {
      isSupported = true;
      break;
    }
  }
  try {
    if (isSupported === true) {
      const promise: Promise<number> = new Promise((resolve) => {
        sensor.once(sensor.SensorId.GRAVITY, (data: sensor.GravityResponse) => {
          resolve(this.getRealData(data));
        });
      })
      return promise;
    } else {
      const promise: Promise<number> = new Promise((resolve) => {
        sensor.once(sensor.SensorId.ACCELEROMETER, (data: sensor.AccelerometerResponse) => {
          resolve(this.getRealData(data as sensor.GravityResponse));
        });
      })
      return promise;
    }
  } catch (error) {
    let err = error as BusinessError;
    console.error(`gePromise failed, error: ${err.code}`);
    return -1; // 异常场景下返回默认值
  }
}

// 获取当前设备旋转角度
async getCurrentDeviceDegree() : Promise<number> {
  getDeviceDegree = await getGravity(); // 调用使用await
  return getDeviceDegree;
}

视频通话送远端场景

两个设备之间进行视频通话,存在设备间持握方向不一致问题,建议在本端将画面转正,再通过网络发送到对端,画面转正参考自绘制场景预览角度的归一化处理。

实现相机无损出图

在部分折叠屏设备上,不同折叠状态下的设备自然方向会发生改变,导致不同折叠状态下真实的相机镜头安装角度不同。为了屏蔽不同设备间的差异,使得不同折叠状态下的相机镜头安装角度一致,系统会自动调整部分折叠状态下的相机采集图像方向(通过旋转裁切的方式)和相机镜头安装角度,因此会存在视场角(Field of View, FOV)损失,可能会导致相机预览、拍照、录像可见范围降低,因此如果需要实现相机无损出图,可以通过usePhysicalCameraOrientation接口来实现相机无损出图。具体方式如下:

设备是否支持无损出图,首先需要确认设备的相机镜头安装角度是否可变,可以通过isPhysicalCameraOrientationVariable接口查询。

  1. 当相机镜头安装角度不可变时,不同折叠状态下的相机出图均为无损出图。

  2. 当相机镜头安装角度可变时:

    如应用需要实现相机无损出图,由于相机镜头安装角度与相机旋转相关,需要应用完成相机旋转的适配后,通过getPhysicalCameraOrientation接口获取设备当前折叠状态下真实的相机镜头安装角度,并通过usePhysicalCameraOrientation接口实现相机无损出图(相机镜头安装角度不可变时使用usePhysicalCameraOrientation将会返回7400102错误码,未适配相机旋转时使用相机无损出图会导致预览、拍照、录像旋转异常),推荐在createCameraInput后直接使用usePhysicalCameraOrientation接口实现相机无损出图。

示例代码如下:

enablePhysicalCameraOrientation(cameraInput: camera.CameraInput) {
  // 查询设备的相机镜头安装角度是否可变
  let isVariable: boolean = cameraInput.isPhysicalCameraOrientationVariable();

  if (isVariable) {
    // 获取设备当前折叠状态下真实的相机镜头安装角度
    let physicalOrientation: number = cameraInput.getPhysicalCameraOrientation();
    console.info(`physical Orientation is ${physicalOrientation}`);

    // 选择是否使用真实的相机镜头安装角度, 以实现无损出图
    let isUsed: boolean = true;
    cameraInput.usePhysicalCameraOrientation(isUsed);
  }
}

常见问题

[h2]指定XComponent的大小,防止旋转后图像拉伸变形

图像显示出现拉伸或压缩等变形,是因为图像分辨率与XComponent的宽高比不匹配。以应用层下发的1920*1080(16:9)竖屏和横屏为例,器件出图均是按照4:3比例出一张RAW图,在此基础上,根据应用层下发的16:9比例进行裁切,提供数据给应用层。因此,无论手机持握方向如何变化,应用层接收的数据始终是16:9比例的图片。具体图示如下:

设备和镜头方向 处理过程示意图 XComponent布局

设备条件:

手机竖屏、充电口向下。

使用后置相机拍摄。

可得:

- 后置相机镜头角度 = 90°

- 屏幕旋转角度 = 0°,Display.rotation = 0

- 图像预览旋转角度 = 0°+90° = 90°

出图与最终成像有90度夹角,布局宽高与图像宽高交换。

设备条件:

手机横屏、充电口向右。

使用后置相机拍摄。

可得:

- 后置相机镜头角度 = 90°

- 屏幕旋转角度 = 270°,Display.rotation = 3

- 图像预览旋转角度 = 270°+90° = 360° = 0°

出图与最终成像有0度夹角,布局与图像宽高比一致。

从上图可以看出,当手机从竖屏转换为横屏时,图像始终保持16:9的输出比例,但镜头与屏幕显示方向之间的夹角从90度变为0度。如果布局保持9:16不变,那么16:9的图像数据放置在9:16的空间内显示,会导致图像形变。因此,为确保图像显示正常,横屏时需要将布局的宽高比调整为16:9。

首先,将XComponent的宽度和高度作为状态变量进行监听,通过Window.on('windowSizeChange')监听窗口的变化,根据屏幕旋转角度(Display.rotation )与相机镜头角度(CameraDevice.cameraOrientation)之间的角度来确定布局的宽高比,以确保布局能跟随窗口实时调整。

具体的实现方法如下,在需要进行横竖屏切换的页面中,通常建议在aboutToAppear中执行窗口变化的监听。

import { bundleManager } from '@kit.AbilityKit';
import { display } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
import { BusinessError, deviceInfo  } from '@kit.BasicServicesKit';

// ....
let previewOutput : camera.PreviewOutput; // 根据具体使用场景创建的预览输出流
let cameraDevice : camera.CameraDevice; // 根据使用诉求选择符合的相机设备
@Entry
@Component
struct Index {
  @State mXComponentWidth: number = 1280;
  @State mXComponentHeight: number = 720;
  @State mRotate: number = 0;
  @State mConfigRatio: number = 16 / 9;
  private targetVersion: number = 0;
  private mWindowHeight = 0;
  private mWindowWidth = 0;

  private windowClass = (this.getUIContext().getHostContext() as common.UIAbilityContext).windowStage.getMainWindowSync();
  getBundleInfoForSelf() { // 获取应用的编译版本
    let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION | bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_METADATA;
    try {
      bundleManager.getBundleInfoForSelf(bundleFlags).then((data) => {
        console.info(`getBundleInfoForSelf successfully. Data: ${data.targetVersion}`);
        this.targetVersion = data.targetVersion;
      }).catch((err: BusinessError) => {
        console.error(`getBundleInfoForSelf failed ${err}`);
      });
    } catch (err) {
      let message = (err as BusinessError).message;
      console.error(`getBundleInfoForSelf failed ${message}`);
    }
  }

  isIsolateForSpecialType(): boolean { // 兼容平板API14之前的设备
    return deviceInfo.deviceType == "tablet" && this.targetVersion <= 50000013;
  }

  aboutToAppear(): void {
    this.updateXComponentSize();
    this.getBundleInfoForSelf();
    this.windowClass.on('windowSizeChange', (size) => {
      this.mWindowWidth = size.width;
      this.mWindowHeight = size.height;
      this.updateXComponentSize();
    });
    let rotation : number = 0;
    try {
      rotation = display.getDefaultDisplaySync().rotation;
      this.mRotate = rotation * camera.ImageRotation.ROTATION_90;
    } catch (error) {
      const err = error as BusinessError;
      console.error(`Failed to get display rotation: ${err.code}, ${err.message}`);
      this.mRotate = 0;
    }
    display.on('change', () => {
      if (this.mRotate!= rotation * camera.ImageRotation.ROTATION_90) {
        this.mRotate= rotation * camera.ImageRotation.ROTATION_90; // 获取屏幕旋转角度
        this.updateXComponentSize();
        let imageRotation = this.getImageRotation();
        if (!imageRotation) {
          console.error(`current get image rotation is undefined`);
          return;
        }
        let previewRotation = previewOutput.getPreviewRotation(imageRotation); // previewOutput是创建的预览流
        previewOutput.setPreviewRotation(previewRotation, false);
      }
    });
  }
  getImageRotation() : camera.ImageRotation | undefined {
    let displayRotation : number= 0;
    try {
      displayRotation = display.getDefaultDisplaySync().rotation
    } catch (error) {
      const err = error as BusinessError;
      console.error(`Failed to get display rotation: ${err.code}, ${err.message}`);
      return undefined;
    }
    let imageRotation = displayRotation * camera.ImageRotation.ROTATION_90;
    return imageRotation;
  }
  updateXComponentSize(): void {
    let angleDiff = (this.mRotate+ cameraDevice?.cameraOrientation) % 360;
    if (this.isIsolateForSpecialType()) { // 如果设备为平板设备,且使用的API版本<14,应进入此逻辑。
      if (angleDiff === 90 || angleDiff=== 270) {
      this.mXComponentWidth = this.mConfigRatio * this.mWindowHeight;
      this.mXComponentHeight = this.mWindowHeight;
    } else {
      this.mXComponentWidth = this.mWindowWidth;
      this.mXComponentHeight = this.mConfigRatio * this.mWindowWidth; // 1920 *1080
    }
    } else { // 如果使用API版本≥14,或是API14以下的非平板设备,应进入此逻辑。
      if (angleDiff === 90 || angleDiff=== 270) {
        this.mXComponentWidth = this.mWindowWidth;
        this.mXComponentHeight = this.mConfigRatio * this.mWindowWidth; // 1920 *1080
      } else {
        this.mXComponentWidth = this.mConfigRatio * this.mWindowHeight;
        this.mXComponentHeight = this.mWindowHeight;
      }
    }
  }

  async aboutToDisAppear(): Promise<void> {
    display.off('change');
    this.windowClass.off('windowSizeChange');
    // 解注册
  }
  build() {
    // 根据使用诉求补充界面处理逻辑。
  }
}

除了指定XComponent的宽高外,还可以通过设置XComponent的renderFit来实现图片的自适应大小显示、居中裁剪显示等效果。具体详情请参考RenderFit介绍。

[h2]自绘制场景预览角度的归一化处理

在自绘制场景中,对于后置摄像头,可以通过调用getPreviewRotation获取旋转角度,将图像转正;对于前置摄像头,由于存在水平镜像和垂直镜像的差异,为了简化操作,需先对前置摄像头的图像角度进行归一化处理后,再将图像转正,并根据业务需求决定是否进行镜像处理。

pixelMap处理方式:

import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { image } from '@kit.ImageKit';
import { display } from '@kit.ArkUI';

let previewOutputReceiver: camera.PreviewOutput | undefined = undefined; // 回调流定义
let curCameraPosition = camera.CameraPosition.CAMERA_POSITION_FRONT; // 相机设备的位置定义,此处以前置为例。
// .....
function  onImageArrival(receiver: image.ImageReceiver): void {
  receiver.on('imageArrival', () => { // imageRecevier回调
    // 获取图像
    receiver.readNextImage((err: BusinessError, nextImage: image.Image) => {
      if (err || nextImage === undefined) {
        console.error('readNextImage failed');
        return;
      }
      // 解析图像内容
      nextImage.getComponent(image.ComponentType.JPEG, async (err: BusinessError, imgComponent: image.Component) => {
        if (err || imgComponent === undefined) {
          console.error('getComponent failed');
        }

        if (imgComponent.byteBuffer) {
          let width = nextImage.size.width; // 获取图片的宽
          let height = nextImage.size.height; // 获取图片的高
          let stride = imgComponent.rowStride; // 获取图片的stride
          // stride与width一致
          if (stride == width) {
            let pixelMap = await image.createPixelMap(imgComponent.byteBuffer, {
              size: { height: height, width: width },
              srcPixelFormat: image.PixelMapFormat.NV21, // 此处以NV21为例
            })
            updatePixelMap(pixelMap);
          } else {
            // stride与width不一致
            const dstBufferSize = width * height * 1.5 // 以NV21为例(YUV_420_SP格式的图片)YUV_420_SP内存计算公式:长x宽+(长x宽)/2
            const dstArr = new Uint8Array(dstBufferSize)
            for (let j = 0; j < height * 1.5; j++) {
              const srcBuf = new Uint8Array(imgComponent.byteBuffer, j * stride, width)
              dstArr.set(srcBuf, j * width)
            }
            let pixelMap = await image.createPixelMap(dstArr.buffer, {
              size: { height: height, width: width },
              srcPixelFormat: image.PixelMapFormat.NV21, // 此处以NV21为例
            });
            updatePixelMap(pixelMap);
          }
        } else {
          console.error('byteBuffer is null');
        }
        // 确保当前buffer没有在使用的情况下,可进行资源释放。
        // 如果对buffer进行异步操作,需要在异步操作结束后再释放该资源(nextImage.release())。
        nextImage.release();
        console.info('image process done');
      })
    })
  })
}
async function updatePixelMap(pixelMap: image.PixelMap): Promise<void> {
  let rotation : number = 0;
  try {
    rotation = display.getDefaultDisplaySync().rotation * camera.ImageRotation.ROTATION_90;
  } catch (error) {
    const err = error as BusinessError;
    console.error(`Failed to get display rotation: ${err.code}, ${err.message}`);
    return;
  }
  let angle = previewOutputReceiver?.getPreviewRotation(rotation);
  if (angle === undefined) {
    return;
  }
  // 在display.on中对该previewOutput设置过setPreviewRotation,此处可以不执行。
  previewOutputReceiver?.setPreviewRotation(angle);
  if (curCameraPosition === camera.CameraPosition.CAMERA_POSITION_FRONT) {
    if (rotation ===90 || rotation === 270) {
      angle = (angle + 180 ) % 360;
    }
    await pixelMap.rotate(angle);
    await pixelMap.flip(true, false);
  } else {
    await pixelMap.rotate(angle);
  }
}

[h2]适配一多设备

为了适配一多设备,主要分为以下几步:

  1. 根据屏幕比例选择合适的预览分辨率。

  2. 根据确定的预览分辨率,通过宽/高得到新的比例。

  3. 根据上一步的比例计算XComponent宽高,可参考指定XComponent的大小,防止旋转后图像拉伸变形,将mConfigRatio应用于布局宽高的计算。

    在适配折叠屏设备时,每次折叠屏镜头变化都需要重新获取屏幕比例。

let mConfigRatio: number = 16 / 9; // 设置分辨率比例初始值,此处以16:9宽高比为例。
let reConfigType : number = 720;
let previewProfileObj: camera.Profile = {
  format: 1003,
  size: {
    width: 1280,
    height: 720
  }
};
// 根据屏幕初步计算比例,长边/短边
function getConfigRation(cameraDevice: camera.CameraDevice, cameraManager: camera.CameraManager) : number {
  let previewProfile = getSurfaceSize(cameraDevice, mConfigRatio, cameraManager); // 获取最接近的分辨率
  if (previewProfile === undefined || previewProfile.size === undefined) {
    return 0;
  }
  mConfigRatio = previewProfile.size.width / previewProfile.size.height; // 以新的比例重新计算显示宽高
  return mConfigRatio;
}

// 获取最接近屏幕的分辨率
function getSurfaceSize(cameraDevice: camera.CameraDevice, configRatio: number, cameraManager: camera.CameraManager): camera.Profile | undefined {
  console.info(`previewProfiles is ${configRatio}`);
  let cameraOutputCapability =
    cameraManager.getSupportedOutputCapability(cameraDevice, camera.SceneMode.NORMAL_PHOTO); // 此处以NORMAL_PHOTO为例
  return getPreviewProfile(cameraOutputCapability, configRatio);
}
function getPreviewProfile(cameraOutputCapability: camera.CameraOutputCapability, configRatio: number): camera.Profile | undefined {
  let previewProfiles = cameraOutputCapability.previewProfiles;
  if (previewProfiles.length < 1) {
    return undefined;
  }
  console.info(`previewProfiles this.foramt: ${previewProfileObj.format} configRatio = ${configRatio}`);
  let optimalSize: camera.Profile|undefined;
  let minDiff = Number.MAX_VALUE;
  // 计算屏幕的宽高比
  for (let i = 0; i < previewProfiles.length; i++) {
    if (previewProfiles[i].format !== previewProfileObj.format) {
      continue;
    }
    let ratio = previewProfiles[i].size.width / previewProfiles[i].size.height; // 1088*1080
    // 检查宽高比是否匹配
    if (Math.abs(ratio - configRatio) > 0.2) continue; // 0.2的误差可自行调整
    // 选择最接近的分辨率
    if (Math.abs(previewProfiles[i].size.height - reConfigType) < minDiff) {
      optimalSize = previewProfiles[i];
      minDiff = Math.abs(previewProfiles[i].size.height - reConfigType);
    }
  }

  // 如果没有找到合适的分辨率,选择第一个
  if (optimalSize === undefined) {
    minDiff = Number.MAX_VALUE;
    for (let i = 0; i < previewProfiles.length; i++) {
      if (previewProfiles[i].format !== previewProfileObj.format) {
        continue;
      }
      if (Math.abs(previewProfiles[i].size.height - reConfigType) < minDiff) { // 720
        optimalSize = previewProfiles[i];
        minDiff = Math.abs(previewProfiles[i].size.height - reConfigType);
      }
    }
  }
  return optimalSize;
}

[h2]拍照无法镜像

通过设置PhotoCaptureSetting中的mirror属性改变拍照镜像。

// this.photoOutput是拍照输出output, this.getDeviceDegree是重力角度
let photoSettings: camera.PhotoCaptureSetting = {
  quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,
  mirror: this.photoOutput?.isMirrorSupported() // 设置拍照镜像,true表示镜像,false表示非镜像
};
// ... 省略获取代码
this.photoRotation = getPhotoRotation(this.photoOutput!!,this.getDeviceDegree)
photoSettings.rotation = this.photoRotation // 指定拍照旋转角度