视频通话时，画面比例显示异常
==============

#### 问题现象

视频通话时，画面比例显示异常，画面明显被拉长了。

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/2f/v3/0QiPAnVzQaCnKaUs3FtPIA/zh-cn_image_0000002658911779.png?HW-CC-KV=V1&HW-CC-Date=20260709T134231Z&HW-CC-Expire=86400&HW-CC-Sign=E2F02CCAAE90DF736C984A88CA244242D15EBC3BE46F7F0C9DC540DCC449DE53 "点击放大")

#### 背景知识

*   [申请相机开发的权限](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/camera-preparation)：在开发相机应用时，需要先申请相机相关权限，确保应用拥有访问相机硬件及其他功能的权限。
*   [XComponent](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-xcomponent)：提供用于图形绘制和媒体数据写入的Surface，XComponent负责将其嵌入到视图中，支持应用自定义Surface位置和大小。其可通过指定type字段来实现不同的渲染方式，分别为XComponentType.SURFACE和XComponentType.TEXTURE。
*   [XComponentType](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-appendix-enums#xcomponenttype10)：枚举值，用于指定XComponent组件类型（渲染方式）。
*   [CameraManager](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-camera-cameramanager)：相机输出能力。

#### 问题定位

*   排查视频预览流分辨率（previewProfiles）与XComponent容器的宽高比是否一致。
    
    根据关键词CreatePreviewOutputInstance查找日志，确认采用的previewProfiles的宽高是480\*480，宽高比是1:1，日志如下：
    
    C02B01/应用包名/CAMERA                  应用包名  I     {CreatePreviewOutputInstance():869} CameraManagerNapi::CreatePreviewOutputInstance ParseProfile size.width = 480, size.height = 480, format = 1003, surfaceId = 6811818131538
    
    检查XComponent组件的宽高，是100vp\*300vp，宽高比是1:3，与视频预览流分辨率的宽高比不一致：
    
    import { camera } from '@kit.CameraKit';
    
    @Entry
    @Component
    struct CameraPreviewPage {
      private xComponentCtl: XComponentController = new XComponentController();
      private xComponentSurfaceId: string = '';
      private cameraManager: camera.CameraManager | undefined = undefined;
      private cameras: Array<camera.CameraDevice> = \[\];
      private cameraInput: camera.CameraInput | undefined = undefined;
      private previewOutput: camera.PreviewOutput | undefined = undefined;
      private session: camera.VideoSession | undefined = undefined;
      private uiContext: UIContext = this.getUIContext();
      private context: Context | undefined = this.uiContext.getHostContext();
    
      build() {
        Column() {
          XComponent({
            id: 'componentId',
            type: XComponentType.SURFACE,
            controller: this.xComponentCtl
          })
         _   // XComponent组件的宽高是100vp\*200vp，宽高比是1:2_
            .width(100)
            .height(300)
        }
        .justifyContent(FlexAlign.Center)
        .height('100%')
        .width('100%')
      }
    
      async initCamera() {
        try {
        _  // 获取相机管理器实例。_
          this.cameraManager = camera.getCameraManager(this.context);
       _   // 获取当前设备支持的相机device列表。_
          this.cameras = this.cameraManager.getSupportedCameras();
       _   // 选择一个相机device，创建cameraInput输出对象。_
          this.cameraInput = this.cameraManager.createCameraInput(this.cameras\[0\]);
         _ // 打开相机。_
          await this.cameraInput.open()
        _  // 获取相机device支持的profile。_
          let capability: camera.CameraOutputCapability =
            this.cameraManager.getSupportedOutputCapability(this.cameras\[0\], camera.SceneMode.NORMAL\_VIDEO);
       _   // 根据需求选择一个支持的预览流previewProfile。_
          let previewProfile: camera.Profile = capability.previewProfiles\[0\];
          console.info(\`\[PreviewProfile\], width: ${previewProfile.size.width}px, height:${previewProfile.size.height}px\`);
    
         _ // 使用xComponentSurfaceId创建预览。_
          this.previewOutput = this.cameraManager.createPreviewOutput(previewProfile, this.xComponentSurfaceId);
        _  // 创建录像模式相机会话。_
          this.session = this.cameraManager.createSession(camera.SceneMode.NORMAL\_VIDEO) as camera.VideoSession;
         _ // 开始配置会话。_
          this.session.beginConfig();
       _   // 添加相机设备输入。_
          this.session.addInput(this.cameraInput);
       _   // 添加第二路预览流输出。_
          this.session.addOutput(this.previewOutput);
          _// 提交会话配置。_
          await this.session.commitConfig();
        _  // 开始启动已配置的输入输出流。_
          await this.session.start();
        } catch (error) {
       _   // ..._
        }
      }
    }
    
*   排查应用是否有使用cameraOutputCap.previewProfiles的相关能力，根据设备的宽高设置合适的预览流分辨率。

#### 分析结论

*   视频预览分辨率（previewProfiles）与XComponent容器的宽高比不一致，从而导致画面比例显示异常。
*   应用没有根据设备的宽高设置合适的预览流分辨率，而是强制设置自定义值，导致画面比例显示异常。

#### 修改建议

保持预览分辨率与XComponent容器比例一致。

*   方案一：根据使用相机Profile的调整XComponent组件的宽高。
    
    import { camera } from '@kit.CameraKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit';
    
    @Entry
    @Component
    struct AdaptProfile {
      private xComponentCtl: XComponentController = new XComponentController();
      private xComponentSurfaceId: string = '';
      private cameraManager: camera.CameraManager | undefined = undefined;
      private cameras: Array<camera.CameraDevice> = \[\];
      private cameraInput: camera.CameraInput | undefined = undefined;
      private previewOutput: camera.PreviewOutput | undefined = undefined;
      private session: camera.VideoSession | undefined = undefined;
      private uiContext: UIContext = this.getUIContext();
      private context: Context | undefined = this.uiContext.getHostContext();
      private cameraPermission: Permissions = 'ohos.permission.CAMERA'; _// 申请权限相关问题可参考本篇开头的申请相关权限文档_
      @State isShow: boolean = false;
      @State profileRatio: number = 1 / 1; _// Profile的宽高比_
    
      async requestPermissionsFn(): Promise<void> {
        let atManager = abilityAccessCtrl.createAtManager();
        if (this.context) {
          let res = await atManager.requestPermissionsFromUser(this.context, \[this.cameraPermission\]);
          for (let i = 0; i < res.permissions.length; i++) {
            if (this.cameraPermission.toString() === res.permissions\[i\] && res.authResults\[i\] === 0) {
              this.isShow = true;
            }
          }
        }
      }
    
      aboutToAppear(): void {
        this.requestPermissionsFn();
      }
    
      onPageShow(): void {
        console.info('onPageShow');
        if (this.xComponentSurfaceId !== '') {
          this.initCamera();
        }
      }
    
      onPageHide(): void {
        console.info('onPageHide');
        this.releaseCamera();
      }
    
      build() {
        Column() {
          if (this.isShow) {
            XComponent({
              id: 'componentId',
              type: XComponentType.SURFACE,
              controller: this.xComponentCtl
            })
              .onLoad(async () => {
                this.xComponentSurfaceId = this.xComponentCtl.getXComponentSurfaceId(); _// 获取组件surfaceId。_
              _  // 初始化相机，组件实时渲染每帧预览流数据。_
                _// 该方法中会根据使用的Profile更新XComponent组件的宽高比_
                this.initCamera();
              })
              .width('100%')
              .aspectRatio(this.profileRatio)
          }
        }
        .justifyContent(FlexAlign.Center)
        .height('100%')
        .width('100%')
      }
    
     _ // 初始化相机。_
      async initCamera(): Promise<void> {
        console.info(\`initCamera xComponentSurfaceId:${this.xComponentSurfaceId}\`);
        try {
        _  // 获取相机管理器实例。_
          this.cameraManager = camera.getCameraManager(this.context);
          if (!this.cameraManager) {
            console.error('initCamera getCameraManager');
          }
         _ // 获取当前设备支持的相机device列表。_
          this.cameras = this.cameraManager.getSupportedCameras();
          if (!this.cameras) {
            console.error('initCamera getSupportedCameras');
          }
         _ // 选择一个相机device，创建cameraInput输出对象。_
          this.cameraInput = this.cameraManager.createCameraInput(this.cameras\[0\]);
          if (!this.cameraInput) {
            console.error('initCamera createCameraInput');
          }
        _  // 打开相机。_
          await this.cameraInput.open().catch((err: BusinessError) => {
            console.error(\`initCamera open fail: ${err}\`);
          });
       _   // 获取相机device支持的profile。_
          let capability: camera.CameraOutputCapability =
            this.cameraManager.getSupportedOutputCapability(this.cameras\[0\], camera.SceneMode.NORMAL\_VIDEO);
          if (!capability) {
            console.error('initCamera getSupportedOutputCapability');
          }
          let previewProfile: camera.Profile = capability.previewProfiles\[0\];
       _   // 此处选择第一个预览流previewProfile。_
          _// 更新Profile的宽高比_
          this.profileRatio = previewProfile.size.width / previewProfile.size.height;
       _   // 使用xComponentSurfaceId创建预览。_
          this.previewOutput = this.cameraManager.createPreviewOutput(previewProfile, this.xComponentSurfaceId);
          if (!this.previewOutput) {
            console.error('initCamera createpreviewOutput');
          }
        _  // 创建录像模式相机会话。_
          this.session = this.cameraManager.createSession(camera.SceneMode.NORMAL\_VIDEO) as camera.VideoSession;
          if (!this.session) {
            console.error('initCamera createSession');
          }
        _  // 开始配置会话。_
          this.session.beginConfig();
         _ // 添加相机设备输入。_
          this.session.addInput(this.cameraInput);
         _ // 添加第二路预览流输出。_
          this.session.addOutput(this.previewOutput);
         _ // 提交会话配置。_
          await this.session.commitConfig();
         _ // 开始启动已配置的输入输出流。_
          await this.session.start();
        } catch (error) {
          console.error(\`initCamera fail: ${error}\`);
        }
      }
    
     _ // 释放相机。_
      async releaseCamera(): Promise<void> {
        console.info('releaseCamera E');
        try {
        _  // 停止当前会话。_
          await this.session?.stop();
        _  // 释放相机输入流。_
          await this.cameraInput?.close();
        _  // 释放拍照输出流。_
          await this.previewOutput?.release();
        _  // 释放会话。_
          await this.session?.release();
        } catch (error) {
          console.error(\`initCamera fail: ${error}\`);
        }
      }
    }
    
    效果如下：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/63/v3/MTtuwfVcTx-s5E--t_RLQQ/zh-cn_image_0000002628392564.png?HW-CC-KV=V1&HW-CC-Date=20260709T134231Z&HW-CC-Expire=86400&HW-CC-Sign=FD4766EC44BFF4869ED4D2DAB519FEE4215E4A1A7D6024A2738F043E99D572D5 "点击放大")
    
*   方案二：根据XComponent的宽高比调整使用的Profile。
    
    import { camera } from '@kit.CameraKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit';
    
    @Entry
    @Component
    struct AdaptXComponent {
      private xComponentCtl: XComponentController = new XComponentController();
      private xComponentSurfaceId: string = '';
      @State xComponentWidth: number = 1920;
      @State xComponentHeight: number = 1080;
      private cameraManager: camera.CameraManager | undefined = undefined;
      private cameras: Array<camera.CameraDevice> = \[\];
      private cameraInput: camera.CameraInput | undefined = undefined;
      private previewOutput: camera.PreviewOutput | undefined = undefined;
      private session: camera.VideoSession | undefined = undefined;
      private uiContext: UIContext = this.getUIContext();
      private context: Context | undefined = this.uiContext.getHostContext();
      private cameraPermission: Permissions = 'ohos.permission.CAMERA'; _// 申请权限相关问题可参考本篇开头的申请相关权限文档_
      @State isShow: boolean = false;
    
      async requestPermissionsFn(): Promise<void> {
        let atManager = abilityAccessCtrl.createAtManager();
        if (this.context) {
          let res = await atManager.requestPermissionsFromUser(this.context, \[this.cameraPermission\]);
          for (let i = 0; i < res.permissions.length; i++) {
            if (this.cameraPermission.toString() === res.permissions\[i\] && res.authResults\[i\] === 0) {
              this.isShow = true;
            }
          }
        }
      }
    
      aboutToAppear(): void {
        this.requestPermissionsFn();
      }
    
      onPageShow(): void {
        console.info('onPageShow');
        if (this.xComponentSurfaceId !== '') {
          this.initCameraWithSize();
        }
      }
    
      onPageHide(): void {
        console.info('onPageHide');
        this.releaseCamera();
      }
    
      build() {
        Column() {
          if (this.isShow) {
            XComponent({
              id: 'componentId',
              type: XComponentType.SURFACE,
              controller: this.xComponentCtl
            })
              .onLoad(async () => {
                this.xComponentSurfaceId = this.xComponentCtl.getXComponentSurfaceId(); _// 获取组件surfaceId。_
             _   // 初始化相机，组件实时渲染每帧预览流数据。_
                _// 该方法中会根据XComponent组件的宽高比筛选合适的Profile，并更新XComponent组件的宽高_
                this.initCameraWithSize();
              })
              .width(this.uiContext.px2vp(this.xComponentHeight))
              .height(this.uiContext.px2vp(this.xComponentWidth))
          }
        }
        .justifyContent(FlexAlign.Center)
        .height('100%')
        .width('100%')
      }
    
      _// 释放相机。_
      async releaseCamera(): Promise<void> {
        console.info('releaseCamera E');
        try {
        _  // 停止当前会话。_
          await this.session?.stop();
        _  // 释放相机输入流。_
          await this.cameraInput?.close();
        _  // 释放拍照输出流。_
          await this.previewOutput?.release();
        _  // 释放会话。_
          await this.session?.release();
        } catch (error) {
          console.error(\`initCamera fail: ${error}\`);
        }
      }
    
     _ // 根据XComponent的宽高选择合适的预览Profile，并调整XComponent的宽高_
      async initCameraWithSize() {
        console.info(\`initCamera xComponentSurfaceId:${this.xComponentSurfaceId}\`);
        try {
         _ // 获取相机管理器实例。_
          this.cameraManager = camera.getCameraManager(this.context);
          if (!this.cameraManager) {
            console.error('initCamera getCameraManager');
          }
        _  // 获取当前设备支持的相机device列表。_
          this.cameras = this.cameraManager.getSupportedCameras();
          if (!this.cameras) {
            console.error('initCamera getSupportedCameras');
          }
         _ // 选择一个相机device，创建cameraInput输出对象。_
          this.cameraInput = this.cameraManager.createCameraInput(this.cameras\[0\]);
          if (!this.cameraInput) {
            console.error('initCamera createCameraInput');
          }
        _  // 打开相机。_
          await this.cameraInput.open().catch((err: BusinessError) => {
            console.error(\`initCamera open fail: ${err}\`);
          });
         _ // 获取相机device支持的profile。_
          let capability: camera.CameraOutputCapability =
            this.cameraManager.getSupportedOutputCapability(this.cameras\[0\], camera.SceneMode.NORMAL\_VIDEO);
          if (!capability) {
            console.error('initCamera getSupportedOutputCapability');
          }
        _  // 此处仅为示例，可以根据实际需要确定筛选方案_
          let maxRatioDiff: number = 0.1; _// 宽高比最大差距_
          let surfaceRatio: number = this.xComponentWidth / this.xComponentHeight; _// 目标宽高比，16:9。_
          let previewProfile: camera.Profile = capability.previewProfiles\[0\];
       _   // 应用开发者根据实际业务需求选择一个支持的预览流previewProfile。_
          _// 此处以选择CAMERA\_FORMAT\_YUV\_420\_SP（NV21）格式、满足限定条件分辨率的预览流previewProfile为例。_
          for (let index = 0; index < capability.previewProfiles.length; index++) {
            const tempProfile = capability.previewProfiles\[index\];
           _ // 计算Profile宽高比_
            let tempRatio = tempProfile.size.width >= tempProfile.size.height ?
              tempProfile.size.width / tempProfile.size.height : tempProfile.size.height / tempProfile.size.width;
            let currentRatio = Math.abs(tempRatio - surfaceRatio);
         _   // 采用Profile数组中第一个符合要求的Profile_
            if (currentRatio <= maxRatioDiff && tempProfile.format == camera.CameraFormat.CAMERA\_FORMAT\_YUV\_420\_SP) {
              previewProfile = tempProfile;
              break;
            }
          }
          this.xComponentWidth = previewProfile.size.width; _// 更新xComponent组件的宽。_
          this.xComponentHeight = previewProfile.size.height; _// 更新xComponent组件的高。_
          console.info(\`initCamera xComponentWidth:${this.xComponentWidth}px xComponentHeight:${this.xComponentHeight}px\`);
    
         _ // 使用xComponentSurfaceId创建预览。_
          this.previewOutput = this.cameraManager.createPreviewOutput(previewProfile, this.xComponentSurfaceId);
          if (!this.previewOutput) {
            console.error('initCamera createpreviewOutput');
          }
         _ // 创建录像模式相机会话。_
          this.session = this.cameraManager.createSession(camera.SceneMode.NORMAL\_VIDEO) as camera.VideoSession;
          if (!this.session) {
            console.error('initCamera createSession');
          }
        _  // 开始配置会话。_
          this.session.beginConfig();
        _  // 添加相机设备输入。_
          this.session.addInput(this.cameraInput);
        _  // 添加第二路预览流输出。_
          this.session.addOutput(this.previewOutput);
       _   // 提交会话配置。_
          await this.session.commitConfig();
         _ // 开始启动已配置的输入输出流。_
          await this.session.start();
        } catch (error) {
          console.error(\`initCamera fail: ${error}\`);
        }
      }
    }
    
    效果如下：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/19/v3/M1UxqUIvThSv46I-5mJ-Nw/zh-cn_image_0000002658791841.png?HW-CC-KV=V1&HW-CC-Date=20260709T134231Z&HW-CC-Expire=86400&HW-CC-Sign=ACAF907D4F33A5E2C3F9B745D5F292EABE5AC408DBE601645918946F169BA32E "点击放大")