自己开发的HarmonyOS App拍照会是怎样

举报
Jack20 发表于 2026/06/20 22:19:36 2026/06/20
【摘要】 一、小知识你有没有遇到过这样的尴尬——用自己开发的 App 拍照,结果照片糊得像打了马赛克?或者晚上拍照一片漆黑,闪光灯死活不亮?又或者想拍一组连拍,结果只能一张一张按?拍照,看似只是"按下快门"这一个动作,但背后涉及的东西远比你想象的多。PhotoSession 是拍照的核心会话,它管理着从预览到成片的整条数据流。闪光灯的控制不是简单的开关,而是要根据环境光智能判断。定时拍照需要精确的倒...

一、小知识

你有没有遇到过这样的尴尬——用自己开发的 App 拍照,结果照片糊得像打了马赛克?或者晚上拍照一片漆黑,闪光灯死活不亮?又或者想拍一组连拍,结果只能一张一张按?

拍照,看似只是"按下快门"这一个动作,但背后涉及的东西远比你想象的多。PhotoSession 是拍照的核心会话,它管理着从预览到成片的整条数据流。闪光灯的控制不是简单的开关,而是要根据环境光智能判断。定时拍照需要精确的倒计时管理和 UI 反馈。连拍模式则要处理高速图像采集和存储的性能问题。

这篇文章,我们就把拍照这件事掰开揉碎了讲清楚。


二、核心原理

2.1 PhotoSession 数据流架构

PhotoSession 是拍照场景的会话管理器,它将相机输入、预览输出和拍照输出串联起来,形成一条完整的数据流:

flowchart LR
    classDef primary fill:#4A90D9,stroke:#2C5F8A,color:#fff,font-weight:bold
    classDef warning fill:#E8A838,stroke:#B87A1A,color:#fff,font-weight:bold
    classDef error fill:#D94A4A,stroke:#8A2C2C,color:#fff,font-weight:bold
    classDef info fill:#50B5A9,stroke:#2C7A6F,color:#fff,font-weight:bold
    classDef purple fill:#9B59B6,stroke:#6C3483,color:#fff,font-weight:bold

    A[CameraInput<br/>相机输入]:::primary --> B[PhotoSession<br/>拍照会话]:::error
    B --> C[PreviewOutput<br/>预览输出]:::info
    B --> D[PhotoOutput<br/>拍照输出]:::warning
    D --> E[相册/文件系统]:::purple

    F[闪光灯控制]:::warning -.-> B
    G[对焦模式]:::info -.-> B
    H[曝光补偿]:::purple -.-> B

    style B stroke-width:4px

关键流程

  1. 创建会话cameraManager.createPhotoSession(cameraInput, previewOutput, photoOutput)
  2. 配置参数:设置闪光灯、对焦、曝光等
  3. 启动会话session.start() 开始预览
  4. 触发拍照photoOutput.capture() 捕获图像
  5. 接收结果:通过回调获取拍照完成的通知

2.2 拍照完整流程

flowchart TD
    classDef primary fill:#4A90D9,stroke:#2C5F8A,color:#fff,font-weight:bold
    classDef warning fill:#E8A838,stroke:#B87A1A,color:#fff,font-weight:bold
    classDef error fill:#D94A4A,stroke:#8A2C2C,color:#fff,font-weight:bold
    classDef info fill:#50B5A9,stroke:#2C7A6F,color:#fff,font-weight:bold
    classDef purple fill:#9B59B6,stroke:#6C3483,color:#fff,font-weight:bold

    A[创建PhotoOutput]:::primary --> B[创建PhotoSession]:::primary
    B --> C[配置会话参数]:::info
    C --> D[session.start]:::warning
    D --> E{用户操作}:::purple
    E -->|单拍| F[photoOutput.capture]:::error
    E -->|连拍| G[循环调用capture]:::error
    E -->|定时拍| H[倒计时后capture]:::error
    F --> I[拍照回调]:::info
    G --> I
    H --> I
    I --> J[保存图片]:::purple

    style E stroke-width:3px
    style F stroke-width:3px

2.3 闪光灯模式

闪光灯有三种模式,理解它们的区别很重要:

模式 说明 适用场景
FLASH_MODE_CLOSE 始终关闭 拍风景、拍文档
FLASH_MODE_OPEN 始终开启 暗光环境补光
FLASH_MODE_AUTO 自动判断 日常拍摄(推荐默认值)
FLASH_MODE_ALWAYS_ON 常亮(手电筒) 录像补光

2.4 连拍模式原理

连拍的核心是快速连续调用 capture() 方法,系统会在底层做帧缓冲,确保高速采集不会丢帧。但要注意:

  • 连拍速度受硬件限制,通常 3-10 张/秒
  • 连拍时内存占用会快速上升,需要及时处理回调释放缓冲
  • 连拍数量要有上限,否则可能 OOM

三、代码实战

3.1 PhotoSession 基础拍照

这是最基础的拍照实现——创建会话、启动预览、按下快门、保存照片。

import { camera } from '@kit.CameraKit';
import { image } from '@kit.ImageKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { BusinessError } from '@kit.BasicServicesKit';

/**
 * 基础拍照控制器
 * 实现完整的拍照流程:创建会话 → 预览 → 拍照 → 保存
 */
export class PhotoCaptureController {
  private cameraManager: camera.CameraManager | null = null;
  private cameraInput: camera.CameraInput | null = null;
  private photoSession: camera.PhotoSession | null = null;
  private previewOutput: camera.PreviewOutput | null = null;
  private photoOutput: camera.PhotoOutput | null = null;

  // 拍照结果回调
  private onPhotoTaken?: (uri: string) => void;
  private onError?: (error: BusinessError) => void;

  /**
   * 初始化拍照会话
   * @param context 应用上下文
   * @param surfaceId 预览的 Surface ID
   */
  async init(context: Context, surfaceId: string): Promise<boolean> {
    try {
      // 1. 创建 CameraManager
      this.cameraManager = camera.getCameraManager(context);

      // 2. 获取后置摄像头
      const devices = this.cameraManager.getSupportedCameras();
      const backCamera = devices.find(
        d => d.cameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK
      ) || devices[0];

      // 3. 创建 CameraInput 并打开
      this.cameraInput = this.cameraManager.createCameraInput(backCamera);
      await this.cameraInput.open();

      // 4. 获取输出能力
      const capability = this.cameraManager.getSupportedOutputCapability(backCamera);

      // 5. 创建预览输出
      const previewProfile = capability.previewProfiles[0];
      this.previewOutput = this.cameraManager.createPreviewOutput(previewProfile, surfaceId);

      // 6. 创建拍照输出
      const photoProfile = capability.photoProfiles[0];
      this.photoOutput = this.cameraManager.createPhotoOutput(photoProfile);

      // 7. 创建 PhotoSession
      this.photoSession = this.cameraManager.createPhotoSession(
        this.cameraInput,
        this.previewOutput,
        this.photoOutput
      );

      // 8. 注册拍照回调
      this.setupPhotoCallback();

      // 9. 启动会话
      await this.photoSession.start();

      console.info('[拍照] 初始化完成,会话已启动');
      return true;

    } catch (error) {
      const err = error as BusinessError;
      console.error(`[拍照初始化失败] code: ${err.code}, msg: ${err.message}`);
      this.onError?.(err);
      return false;
    }
  }

  /**
   * 注册拍照输出回调
   * 这是接收拍照结果的核心机制
   */
  private setupPhotoCallback(): void {
    if (!this.photoOutput) return;

    // 拍照完成回调 - 照片可用时触发
    this.photoOutput.on('photoAvailable', (photo: camera.Photo): void => {
      console.info('[拍照] 照片已捕获,开始处理');

      // 获取照片的主图像
      const mainImage = photo.main;
      if (mainImage) {
        this.savePhoto(mainImage);
      }
    });

    // 拍照开始回调 - 快门按下时触发
    this.photoOutput.on('captureStart', (captureInfo: camera.CaptureStartInfo): void => {
      console.info(`[拍照] 快门已按下, captureId: ${captureInfo.captureId}`);
    });

    // 拍照结束回调 - 图像处理完成时触发
    this.photoOutput.on('captureEnd', (endInfo: camera.CaptureEndInfo): void => {
      console.info(`[拍照] 图像处理完成, captureId: ${endInfo.captureId}, frameCount: ${endInfo.frameCount}`);
    });

    // 拍照错误回调
    this.photoOutput.on('error', (errorInfo: BusinessError): void => {
      console.error(`[拍照错误] code: ${errorInfo.code}, msg: ${errorInfo.message}`);
    });
  }

  /**
   * 保存照片到本地
   * @param photo 图片数据
   */
  private async savePhoto(photo: image.Image): Promise<void> {
    try {
      // 创建图片打包器
      const imagePackerApi = image.createImagePacker();

      // 打包参数
      const packOpts: image.PackingOption = {
        format: 'image/jpeg',
        quality: 95
      };

      // 将图片打包为 ArrayBuffer
      const data = await imagePackerApi.packing(photo, packOpts);

      // 生成文件路径
      const fileName = `IMG_${Date.now()}.jpg`;
      const filePath = `${getContext().filesDir}/${fileName}`;

      // 写入文件
      const file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
      fs.writeSync(file.fd, data);
      fs.closeSync(file);

      console.info(`[拍照] 照片已保存: ${filePath}`);
      this.onPhotoTaken?.(filePath);

      // 释放资源
      imagePackerApi.release();
      photo.release();

    } catch (error) {
      console.error(`[照片保存失败] ${error}`);
    }
  }

  /**
   * 执行拍照
   * 这是对外暴露的核心方法
   */
  async capture(): Promise<void> {
    if (!this.photoOutput) {
      console.error('[拍照] PhotoOutput 未初始化');
      return;
    }

    try {
      // 拍照配置
      const captureSetting: camera.PhotoCaptureSetting = {
        quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,  // 高质量
        rotation: camera.ImageRotation.ROTATION_0,         // 不旋转
        mirror: false,                                      // 不镜像
      };

      // 触发拍照
      await this.photoOutput.capture(captureSetting);
      console.info('[拍照] capture 已触发');

    } catch (error) {
      const err = error as BusinessError;
      console.error(`[拍照失败] code: ${err.code}, msg: ${err.message}`);
    }
  }

  /**
   * 释放所有资源
   */
  async release(): Promise<void> {
    try {
      if (this.photoSession) {
        await this.photoSession.stop();
        await this.photoSession.release();
      }
      if (this.previewOutput) {
        await this.previewOutput.release();
      }
      if (this.photoOutput) {
        await this.photoOutput.release();
      }
      if (this.cameraInput) {
        await this.cameraInput.close();
        await this.cameraInput.release();
      }
      console.info('[拍照] 资源已全部释放');
    } catch (error) {
      console.error(`[资源释放失败] ${error}`);
    }
  }
}

3.2 闪光灯控制

闪光灯不是简单的开关,它有自动模式,需要根据当前环境光条件智能判断。这个示例实现了完整的闪光灯控制逻辑。

import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';

/**
 * 闪光灯控制器
 * 支持关闭/开启/自动/常亮四种模式
 */
export class FlashController {
  private photoSession: camera.PhotoSession | null = null;

  // 当前闪光灯模式
  @State currentMode: camera.FlashMode = camera.FlashMode.FLASH_MODE_CLOSE;

  /**
   * 绑定 PhotoSession
   * 闪光灯控制依赖于会话对象
   */
  bindSession(session: camera.PhotoSession): void {
    this.photoSession = session;
    // 初始化为自动模式
    this.setFlashMode(camera.FlashMode.FLASH_MODE_AUTO);
  }

  /**
   * 设置闪光灯模式
   * @param mode 目标模式
   */
  setFlashMode(mode: camera.FlashMode): boolean {
    if (!this.photoSession) {
      console.error('[闪光灯] 会话未绑定');
      return false;
    }

    try {
      // 先检查设备是否支持该模式
      const supportedModes = this.photoSession.getSupportedFlashModes();
      if (!supportedModes.includes(mode)) {
        console.warn(`[闪光灯] 不支持模式: ${mode}`);
        return false;
      }

      // 设置闪光灯模式
      this.photoSession.setFlashMode(mode);
      this.currentMode = mode;

      const modeText = this.getFlashModeText(mode);
      console.info(`[闪光灯] 已切换到: ${modeText}`);
      return true;

    } catch (error) {
      const err = error as BusinessError;
      console.error(`[闪光灯设置失败] code: ${err.code}, msg: ${err.message}`);
      return false;
    }
  }

  /**
   * 切换到下一个闪光灯模式
   * 依次循环:关闭 → 自动 → 开启 → 常亮 → 关闭
   */
  cycleFlashMode(): camera.FlashMode {
    if (!this.photoSession) return this.currentMode;

    const supportedModes = this.photoSession.getSupportedFlashModes();
    const currentIndex = supportedModes.indexOf(this.currentMode);
    const nextIndex = (currentIndex + 1) % supportedModes.length;
    const nextMode = supportedModes[nextIndex];

    this.setFlashMode(nextMode);
    return nextMode;
  }

  /**
   * 判断当前是否支持闪光灯
   */
  hasFlash(): boolean {
    if (!this.photoSession) return false;
    const modes = this.photoSession.getSupportedFlashModes();
    // 如果只有关闭模式,说明设备没有闪光灯
    return modes.length > 1;
  }

  /**
   * 获取当前闪光灯模式文本
   */
  getFlashModeText(mode: camera.FlashMode): string {
    const map: Record<number, string> = {
      [camera.FlashMode.FLASH_MODE_CLOSE]: '关闭',
      [camera.FlashMode.FLASH_MODE_OPEN]: '开启',
      [camera.FlashMode.FLASH_MODE_AUTO]: '自动',
      [camera.FlashMode.FLASH_MODE_ALWAYS_ON]: '常亮',
    };
    return map[mode] || '未知';
  }

  /**
   * 获取闪光灯图标标识
   * 用于UI展示
   */
  getFlashIcon(mode: camera.FlashMode): string {
    const map: Record<number, string> = {
      [camera.FlashMode.FLASH_MODE_CLOSE]: '⚡⊘',      // 关闭
      [camera.FlashMode.FLASH_MODE_OPEN]: '⚡',         // 开启
      [camera.FlashMode.FLASH_MODE_AUTO]: '⚡A',        // 自动
      [camera.FlashMode.FLASH_MODE_ALWAYS_ON]: '⚡🔒',  // 常亮
    };
    return map[mode] || '⚡⊘';
  }
}

3.3 定时拍照与连拍模式

这个示例实现了两种高级拍照模式:定时拍照(3秒/5秒/10秒倒计时)和连拍模式(可配置连拍张数和间隔)。

import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';

/**
 * 高级拍照控制器
 * 支持定时拍照和连拍模式
 */
export class AdvancedPhotoController {
  private photoOutput: camera.PhotoOutput | null = null;
  private photoSession: camera.PhotoSession | null = null;

  // 定时拍照状态
  private timerCountdown: number = 0;
  private timerHandle: number = -1;
  private onCountdownTick?: (remaining: number) => void;
  private onCountdownFinish?: () => void;

  // 连拍状态
  private isBursting: boolean = false;
  private burstCount: number = 0;
  private burstMaxCount: number = 10;
  private burstInterval: number = 200; // 毫秒
  private onBurstProgress?: (current: number, total: number) => void;

  /**
   * 绑定拍照输出和会话
   */
  bind(photoOutput: camera.PhotoOutput, session: camera.PhotoSession): void {
    this.photoOutput = photoOutput;
    this.photoSession = session;
  }

  /**
   * 定时拍照
   * @param delaySeconds 延迟秒数(3/5/10)
   * @param onTick 每秒倒计时回调
   * @param onFinish 倒计时结束回调
   */
  async startTimerCapture(
    delaySeconds: number,
    onTick?: (remaining: number) => void,
    onFinish?: () => void
  ): Promise<void> {
    if (!this.photoOutput) {
      console.error('[定时拍照] PhotoOutput 未初始化');
      return;
    }

    this.onCountdownTick = onTick;
    this.onCountdownFinish = onFinish;
    this.timerCountdown = delaySeconds;

    console.info(`[定时拍照] 开始倒计时: ${delaySeconds}`);

    // 启动倒计时
    this.timerHandle = setInterval(() => {
      this.timerCountdown--;
      this.onCountdownTick?.(this.timerCountdown);

      if (this.timerCountdown <= 0) {
        // 倒计时结束,清除定时器
        clearInterval(this.timerHandle);
        this.timerHandle = -1;

        // 触发拍照
        this.executeCapture();
        this.onCountdownFinish?.();
      }
    }, 1000) as unknown as number;

    // 立即触发第一次回调
    this.onCountdownTick?.(this.timerCountdown);
  }

  /**
   * 取消定时拍照
   */
  cancelTimerCapture(): void {
    if (this.timerHandle !== -1) {
      clearInterval(this.timerHandle);
      this.timerHandle = -1;
      this.timerCountdown = 0;
      console.info('[定时拍照] 已取消');
    }
  }

  /**
   * 连拍模式
   * @param count 连拍张数
   * @param interval 每张间隔(毫秒)
   * @param onProgress 进度回调
   */
  async startBurstCapture(
    count: number = 10,
    interval: number = 200,
    onProgress?: (current: number, total: number) => void
  ): Promise<void> {
    if (!this.photoOutput) {
      console.error('[连拍] PhotoOutput 未初始化');
      return;
    }

    if (this.isBursting) {
      console.warn('[连拍] 正在连拍中,请勿重复触发');
      return;
    }

    this.isBursting = true;
    this.burstCount = 0;
    this.burstMaxCount = count;
    this.burstInterval = interval;
    this.onBurstProgress = onProgress;

    console.info(`[连拍] 开始: 共${count}张, 间隔${interval}ms`);

    // 连拍循环
    await this.burstLoop();
  }

  /**
   * 连拍循环
   * 递归调用实现定时连拍
   */
  private async burstLoop(): Promise<void> {
    if (!this.isBursting || this.burstCount >= this.burstMaxCount) {
      // 连拍完成
      this.isBursting = false;
      console.info(`[连拍] 完成,共拍摄 ${this.burstCount}`);
      return;
    }

    try {
      // 执行单次拍照
      await this.executeCapture();
      this.burstCount++;

      // 通知进度
      this.onBurstProgress?.(this.burstCount, this.burstMaxCount);

      // 延迟后继续下一张
      setTimeout(() => {
        this.burstLoop();
      }, this.burstInterval);

    } catch (error) {
      console.error(`[连拍] 第${this.burstCount + 1}张拍摄失败: ${error}`);
      this.isBursting = false;
    }
  }

  /**
   * 停止连拍
   */
  stopBurstCapture(): void {
    this.isBursting = false;
    console.info(`[连拍] 已停止,已拍摄 ${this.burstCount}`);
  }

  /**
   * 执行单次拍照
   */
  private async executeCapture(): Promise<void> {
    if (!this.photoOutput) return;

    try {
      const captureSetting: camera.PhotoCaptureSetting = {
        quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,
        rotation: camera.ImageRotation.ROTATION_0,
        mirror: false,
      };
      await this.photoOutput.capture(captureSetting);
    } catch (error) {
      const err = error as BusinessError;
      console.error(`[拍照执行失败] code: ${err.code}, msg: ${err.message}`);
      throw error;
    }
  }

  /**
   * 获取连拍状态
   */
  getBurstingState(): { isBursting: boolean; count: number; maxCount: number } {
    return {
      isBursting: this.isBursting,
      count: this.burstCount,
      maxCount: this.burstMaxCount
    };
  }
}

/**
 * UI 组件 - 拍照页面
 * 集成定时拍照和连拍功能
 */
@Entry
@Component
struct PhotoPage {
  private photoController: AdvancedPhotoController = new AdvancedPhotoController();
  private flashController: FlashController = new FlashController();

  @State countdown: number = 0;          // 倒计时剩余秒数
  @State isBursting: boolean = false;    // 是否正在连拍
  @State burstProgress: string = '';     // 连拍进度文本
  @State flashModeText: string = '自动'; // 闪光灯模式文本
  @State timerDelay: number = 3;         // 定时拍照延迟秒数

  build() {
    Column() {
      // 顶部工具栏
      Row() {
        // 闪光灯按钮
        Button(this.flashModeText)
          .fontSize(14)
          .onClick(() => {
            const newMode = this.flashController.cycleFlashMode();
            this.flashModeText = this.flashController.getFlashModeText(newMode);
          })

        Blank()

        // 定时拍照按钮
        Button(`定时: ${this.timerDelay}s`)
          .fontSize(14)
          .onClick(() => {
            // 循环切换延迟: 3 → 5 → 10 → 关闭 → 3
            if (this.timerDelay === 3) this.timerDelay = 5;
            else if (this.timerDelay === 5) this.timerDelay = 10;
            else if (this.timerDelay === 10) this.timerDelay = 0;
            else this.timerDelay = 3;
          })
      }
      .width('100%')
      .padding({ left: 20, right: 20 })
      .justifyContent(FlexAlign.SpaceBetween)

      // 倒计时显示
      if (this.countdown > 0) {
        Text(`${this.countdown}`)
          .fontSize(80)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FF6B6B')
          .margin({ top: 50 })
      }

      // 连拍进度
      if (this.isBursting) {
        Text(this.burstProgress)
          .fontSize(16)
          .fontColor('#4A90D9')
          .margin({ top: 20 })
      }

      Blank()

      // 底部操作区
      Row() {
        // 定时拍照按钮
        Button('定时拍')
          .enabled(this.timerDelay > 0 && !this.isBursting && this.countdown === 0)
          .onClick(() => this.startTimerCapture())

        // 普通拍照按钮
        Button('拍照')
          .width(70)
          .height(70)
          .borderRadius(35)
          .backgroundColor('#FF6B6B')
          .enabled(!this.isBursting && this.countdown === 0)
          .onClick(() => this.photoController.startBurstCapture(1, 0))

        // 连拍按钮
        Button(this.isBursting ? '停止' : '连拍')
          .backgroundColor(this.isBursting ? '#E8A838' : '#4A90D9')
          .onClick(() => {
            if (this.isBursting) {
              this.photoController.stopBurstCapture();
              this.isBursting = false;
            } else {
              this.startBurstCapture();
            }
          })
      }
      .width('100%')
      .padding({ left: 30, right: 30, bottom: 30 })
      .justifyContent(FlexAlign.SpaceAround)
    }
    .width('100%')
    .height('100%')
  }

  /**
   * 启动定时拍照
   */
  private startTimerCapture(): void {
    this.countdown = this.timerDelay;
    this.photoController.startTimerCapture(
      this.timerDelay,
      (remaining) => {
        this.countdown = remaining;
      },
      () => {
        this.countdown = 0;
      }
    );
  }

  /**
   * 启动连拍
   */
  private startBurstCapture(): void {
    this.isBursting = true;
    this.photoController.startBurstCapture(
      10,
      200,
      (current, total) => {
        this.burstProgress = `连拍中: ${current}/${total}`;
        if (current >= total) {
          this.isBursting = false;
          this.burstProgress = '';
        }
      }
    );
  }
}

四、踩坑与注意事项

4.1 PhotoOutput 回调注册时机

:先调了 capture(),再注册 photoAvailable 回调,结果照片丢了。

:必须在 capture() 之前注册好所有回调,否则可能丢失拍照结果。

// ❌ 错误:先拍照后注册回调
await photoOutput.capture(setting);
photoOutput.on('photoAvailable', callback); // 太晚了!

// ✅ 正确:先注册回调再拍照
photoOutput.on('photoAvailable', callback);
await photoOutput.capture(setting);

4.2 连拍时内存暴涨

:连拍 20 张后 App 因内存不足被系统杀掉。

:在 photoAvailable 回调中及时处理图片数据并释放资源。每张照片处理完后立即调用 photo.release() 释放图像内存。

photoOutput.on('photoAvailable', async (photo: camera.Photo) => {
  const mainImage = photo.main;
  // 处理图片...
  await this.savePhoto(mainImage);
  // 关键:释放图像资源!
  mainImage.release();
  photo.release();
});

4.3 闪光灯设置不生效

:调了 setFlashMode() 但拍照时闪光灯没亮。

:闪光灯模式必须在会话 start() 之后设置才生效。另外,前置摄像头通常不支持闪光灯,调用前要先检查。

// ❌ 错误:会话启动前设置闪光灯
session.setFlashMode(camera.FlashMode.FLASH_MODE_OPEN);
await session.start(); // 闪光灯设置可能被重置

// ✅ 正确:会话启动后设置闪光灯
await session.start();
session.setFlashMode(camera.FlashMode.FLASH_MODE_OPEN);

4.4 定时拍照取消后状态残留

:用户取消了定时拍照,但倒计时 UI 还在显示。

:取消定时拍照时,必须同时重置所有 UI 状态。

4.5 capture() 是异步的

:连续快速调用 capture(),第二次调用时第一次还没完成,导致报错。

:使用标志位或队列确保同一时间只有一个 capture 操作在进行。

private isCapturing: boolean = false;

async capture(): Promise<void> {
  if (this.isCapturing) {
    console.warn('[拍照] 正在拍摄中,请稍候');
    return;
  }
  this.isCapturing = true;
  try {
    await this.photoOutput.capture();
  } finally {
    this.isCapturing = false;
  }
}

4.6 图片旋转问题

:竖屏拍照保存后,图片变成了横屏。

:在 PhotoCaptureSetting 中设置正确的 rotation,或者根据设备方向传感器动态设置。


五、HarmonyOS 6 适配

5.1 API 变更

变更项 HarmonyOS 5.0 HarmonyOS 6.0
PhotoSession 创建 createPhotoSession(input, preview, photo) 保持不变
拍照质量 QUALITY_LEVEL_HIGH/MEDIUM/LOW 新增 QUALITY_LEVEL_ULTRA
连拍 API 手动循环 capture() 新增 burstCapture(count, interval)
照片格式 JPEG 新增 HEIF 格式支持
闪光灯 三种模式 新增柔光灯模式

5.2 迁移要点

  1. HEIF 格式支持:HarmonyOS 6.0 新增了 HEIF 格式,同等画质下体积约为 JPEG 的 50%,建议优先使用:
// HarmonyOS 6.0 使用 HEIF 格式
const captureSetting: camera.PhotoCaptureSetting = {
  quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,
  format: camera.PhotoFormat.PHOTO_FORMAT_HEIF, // 新增
};
  1. 连拍 API 升级:6.0 新增了原生连拍方法,不再需要手动循环:
// HarmonyOS 6.0 原生连拍
await photoOutput.burstCapture({
  count: 10,
  interval: 200
});
  1. 拍照回调增强:6.0 中 photoAvailable 回调新增了 EXIF 信息,可以直接获取拍摄参数。

六、总结

mindmap
  root((拍照开发))
    PhotoSession
      创建三要素 Input + Preview + PhotoOutput
      start 后才能拍照
      参数配置在 start 后生效
    拍照流程
      注册回调 → capture → photoAvailable → 保存
      capture 是异步操作
      需要防止并发调用
    闪光灯
      四种模式 关闭/开启/自动/常亮
      start 后设置才生效
      前置摄像头通常不支持
    定时拍照
      setInterval 实现倒计时
      取消时重置所有状态
      倒计时结束触发 capture
    连拍模式
      循环调用 capture
      及时释放图像内存
      设置合理上限防 OOM
    踩坑要点
      回调注册在 capture 之前
      图片旋转方向处理
      连拍内存管理

核心记忆口诀

回调先注册,capture 后触发;闪光灯 start 后设,连拍记得释内存。

下篇文章我们将深入「录像」功能,讲解 VideoSession 的使用、录像参数配置、防抖、暂停恢复等实战内容。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。