大家好,我是[晚风依旧似温柔],新人一枚,欢迎大家关注~

一、CameraX API 思路:别怕,结构其实就三块

虽然大纲写的是 “CameraX API”,但在鸿蒙里你更多是这样几个角色:

  • CameraManager:管理摄像头列表、创建 Session
  • CameraInput:真正的“相机输入”,比如后置 / 前置
  • PreviewOutput:预览流(给 UI 展示)
  • PhotoOutput:拍照输出(JPEG 等)
  • VideoOutput / Recorder:录像输出(MP4 等)

整体流程基本是:

  1. 获取 CameraManager
  2. 选择一个 CameraInput(后置 / 前置)
  3. 创建 PreviewOutputPhotoOutput / VideoOutput
  4. 将这些绑定到一个 CameraSession
  5. 把 Preview 的 Surface 绑定到 ArkUI 的相机预览组件
  6. 调用 capture() / start() / stop() 实现拍照与录像

1.1 初始化 CameraManager

import camera from '@ohos.multimedia.camera';

let cameraMgr: camera.CameraManager | null = null;

async function getCameraManager(): Promise<camera.CameraManager> {
  if (cameraMgr) return cameraMgr;

  return new Promise((resolve, reject) => {
    camera.getCameraManager((err, mgr) => {
      if (err || !mgr) {
        console.error('获取 CameraManager 失败: ' + JSON.stringify(err));
        reject(err);
        return;
      }
      cameraMgr = mgr;
      resolve(mgr);
    });
  });
}

1.2 选择摄像头(前/后置)

async function selectBackCamera(): Promise<camera.CameraDevice> {
  const mgr = await getCameraManager();
  const devices = mgr.getSupportedCameras();

  // 简单选一个后置摄像头
  const back = devices.find(d => d.cameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK);
  if (!back) {
    throw new Error('未找到后置摄像头');
  }
  return back;
}

1.3 创建相机输入 & 输出

interface CameraContext {
  input: camera.CameraInput;
  session: camera.CameraSession;
  previewOutput: camera.PreviewOutput;
  photoOutput: camera.PhotoOutput;
  videoOutput?: camera.VideoOutput;
}

async function createCameraContext(surfaceIdForPreview: string): Promise<CameraContext> {
  const mgr = await getCameraManager();
  const device = await selectBackCamera();

  // 1. 创建 CameraInput
  const input = mgr.createCameraInput(device);

  // 2. 预览输出(绑定 Surface ID)
  const previewOutput = mgr.createPreviewOutput({
    surfaceId: surfaceIdForPreview
  });

  // 3. 拍照输出
  const photoOutput = mgr.createPhotoOutput();

  // 4. 会话
  const session = mgr.createSession();

  // 5. 绑定
  await session.beginConfig();
  await session.addInput(input);
  await session.addOutput(previewOutput);
  await session.addOutput(photoOutput);
  await session.commitConfig();

  // 启动预览
  await session.start();

  return {
    input,
    session,
    previewOutput,
    photoOutput
  };
}

⚠️ 提醒:不同 API 版本里方法名可能是 createCaptureSession / addInput / addOutput 等,按你的 SDK 为准,但总体是“输入+输出+会话”的套路。


二、拍照 & 录像能力:按钮背后到底干了啥?

2.1 拍照流程

UI 点“拍照” → 调用 capture() → 回调里拿到 JPEG buffer → 保存为文件 → 写入相册

async function takePhoto(ctx: CameraContext, savePath: string): Promise<void> {
  return new Promise((resolve, reject) => {
    ctx.photoOutput.capture({
      quality: camera.QualityLevel.QUALITY_LEVEL_HIGH // 示例
    }, async (err, photoData) => {
      if (err) {
        console.error('拍照失败:' + JSON.stringify(err));
        reject(err);
        return;
      }

      // photoData 里通常是图像二进制数据
      // 这里你可以写入文件系统
      try {
        await saveBytesToFile(savePath, photoData.buffer);
        resolve();
      } catch (e) {
        reject(e);
      }
    });
  });
}

saveBytesToFile 这里可以用 fileio / fs 写二进制文件,后面在“相册写入”里统一说明路径怎么选。


2.2 录像流程

录像一般会多一个 Recorder / VideoOutput,典型步骤是:

  1. 创建 VideoOutput,指定编码格式、分辨率、比特率等
  2. VideoOutput 加入 CameraSession
  3. 调用 start() 开始录制
  4. 调用 stop() 结束录制并生成文件

简化示例:

import media from '@ohos.multimedia.media';

interface CameraContextWithVideo extends CameraContext {
  recorder: media.AVRecorder;
}

async function initVideo(ctx: CameraContext, videoPath: string): Promise<CameraContextWithVideo> {
  const avProfile: media.AVRecorderConfig = {
    audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
    videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,
    profile: {
      fileFormat: media.ContainerFormatType.CONTAINER_FORMAT_MPEG_4,
      videoCodec: media.CodecMimeType.VIDEO_AVC,
      audioCodec: media.CodecMimeType.AUDIO_AAC,
      width: 1280,
      height: 720,
      videoBitrate: 2_000_000,
      videoFrameRate: 30
    },
    url: videoPath,
    rotation: 0
  };

  const recorder = await media.createAVRecorder(avProfile);

  const videoOutput = (await getCameraManager()).createVideoOutput({
    surfaceId: recorder.getInputSurface()
  });

  await ctx.session.beginConfig();
  await ctx.session.addOutput(videoOutput);
  await ctx.session.commitConfig();

  return {
    ...ctx,
    recorder,
    videoOutput
  };
}

async function startRecording(ctx: CameraContextWithVideo) {
  await ctx.recorder.start();
}

async function stopRecording(ctx: CameraContextWithVideo) {
  await ctx.recorder.stop();
}

实际项目里你会多一层状态管理(录制中 / 空闲),并在 UI 上做按钮禁用等状态反馈。


三、相册写入:让系统相册也能看到你的照片/视频

拍照 / 录像后只是生成了文件,想让系统相册里出现,需要通过 MediaLibrary(媒体库) 注册。

3.1 常用媒体库能力

主要是 @ohos.multimedia.mediaLibrary

  • 创建媒体文件(图片 / 视频)
  • 获取媒体文件的公有保存路径
  • 写入二进制数据
  • 通知系统刷新媒体库

3.2 保存照片到相册

import mediaLibrary from '@ohos.multimedia.mediaLibrary';
import fileio from '@ohos.fileio';

async function savePhotoToGallery(context, buffer: ArrayBuffer): Promise<string> {
  const ml = mediaLibrary.getMediaLibrary(context);

  // 1. 创建媒体资源
  const photoAsset = await ml.createAsset(mediaLibrary.MediaType.IMAGE, 'IMG_' + Date.now() + '.jpg', 'Pictures');

  // 2. 打开文件句柄
  const fd = await ml.openAsset(photoAsset.id);

  // 3. 写入
  const writeBuffer = new Uint8Array(buffer);
  await fileio.write(fd, writeBuffer);

  // 4. 关闭
  await fileio.close(fd);
  return photoAsset.uri;
}

拍照回调里直接调用:

ctx.photoOutput.capture({}, async (err, photoData) => {
  if (err) return;
  await savePhotoToGallery(getContext(this), photoData.buffer);
  // 可以 Toast 提示:保存成功
});

3.3 保存视频到相册

录像时你一般已经指定了 url(视频文件保存路径),结束录制之后,往媒体库注册即可:

async function saveVideoToGallery(context, filePath: string): Promise<string> {
  const ml = mediaLibrary.getMediaLibrary(context);

  const videoAsset = await ml.createAsset(
    mediaLibrary.MediaType.VIDEO,
    'VID_' + Date.now() + '.mp4',
    'Videos'
  );

  const fd = await ml.openAsset(videoAsset.id);

  // 把 filePath 对应的文件内容拷贝进来
  const srcFd = await fileio.open(filePath, fileio.O_RDONLY);
  const buffer = new ArrayBuffer(1024 * 1024);
  let bytesRead = 0;
  while ((bytesRead = await fileio.read(srcFd, buffer)) > 0) {
    await fileio.write(fd, new Uint8Array(buffer, 0, bytesRead));
  }
  await fileio.close(srcFd);
  await fileio.close(fd);

  return videoAsset.uri;
}

简化起见实际可用更高效的文件拷贝方式,但思路一样:先有文件 → 媒体库建 Asset → 构建媒体文件 → 拷数据进去


四、相机权限:不配好,前面所有代码都是摆设

相机 / 录像类应用必备的权限:

  • ohos.permission.CAMERA —— 使用摄像头
  • ohos.permission.MICROPHONE —— 录像时录音
  • ohos.permission.READ_MEDIA / ohos.permission.WRITE_MEDIA 或相关存储权限 —— 写相册

4.1 module.json5 静态声明

{
  "module": {
    // ...
    "requestPermissions": [
      { "name": "ohos.permission.CAMERA" },
      { "name": "ohos.permission.MICROPHONE" },
      { "name": "ohos.permission.READ_MEDIA" },
      { "name": "ohos.permission.WRITE_MEDIA" }
    ]
  }
}

4.2 动态申请(和 BLE 那一篇套路一样)

import abilityAccessCtrl from '@ohos.abilityAccessCtrl';

const PERMISSIONS = [
  'ohos.permission.CAMERA',
  'ohos.permission.MICROPHONE',
  'ohos.permission.READ_MEDIA',
  'ohos.permission.WRITE_MEDIA'
];

export async function ensureCameraPermissions(context): Promise<boolean> {
  const atManager = abilityAccessCtrl.createAtManager();

  for (const p of PERMISSIONS) {
    const grantStatus = await atManager.checkAccessToken(context.tokenId, p);
    if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) continue;

    const res = await atManager.requestPermissionsFromUser(context, [p]);
    if (res.authResults[0] !== abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
      console.error(`权限被拒绝: ${p}`);
      return false;
    }
  }
  return true;
}

建议在用户进入“相机页面”前调用:

const ctx = getContext(this);
const ok = await ensureCameraPermissions(ctx);
if (!ok) {
  // 给出弹窗提示:不开权限没法用相机
  return;
}

五、自定义相机 UI 实战:一屏搞定预览 + 拍照 + 录像

下面给你一个 简化版自定义相机页面 思路,包含:

  • 上半屏预览(绑定 SurfaceId)
  • 下半屏拍照按钮 + 录像按钮(带状态)

注意:真实运行需要与你当前 SDK 下的“相机预览组件”/Surface 绑定方式对齐,这里重点是UI & 状态组织方式

5.1 ArkUI 结构示意

@Entry
@Component
struct CustomCameraPage {
  private cameraCtx?: CameraContextWithVideo;
  @State surfaceId: string = ''; // 预览用 Surface ID
  @State isRecording: boolean = false;

  async aboutToAppear() {
    const ctx = getContext(this);
    const ok = await ensureCameraPermissions(ctx);
    if (!ok) return;

    // 创建预览 Surface
    // 实际可以用 SurfaceContainer / Texture / 相机专用组件获取 surfaceId
    this.surfaceId = await createPreviewSurfaceId(); // 伪代码

    const baseCtx = await createCameraContext(this.surfaceId);
    this.cameraCtx = await initVideo(baseCtx, getTempVideoPath());
  }

  aboutToDisappear() {
    // 清理:停止预览、释放会话等
  }

  async onTakePhoto() {
    if (!this.cameraCtx) return;
    // 采集一张照片,将 buffer 写入相册
    this.cameraCtx.photoOutput.capture({}, async (err, photoData) => {
      if (err) return;
      await savePhotoToGallery(getContext(this), photoData.buffer);
      // Toast:保存成功
    });
  }

  async onRecordToggle() {
    if (!this.cameraCtx) return;
    if (!this.isRecording) {
      await startRecording(this.cameraCtx);
      this.isRecording = true;
    } else {
      await stopRecording(this.cameraCtx);
      this.isRecording = false;
      // 将临时文件写入相册
      await saveVideoToGallery(getContext(this), getTempVideoPath());
    }
  }

  build() {
    Column() {
      // 预览区域
      // 这里放相机预览组件(示意)
      CameraPreview({ surfaceId: this.surfaceId }) // 自封装组件

      // 底部控制栏
      Row() {
        // 拍照按钮
        Button('拍照')
          .width(80).height(80)
          .borderRadius(40)
          .onClick(() => this.onTakePhoto())

        // 录像按钮
        Button(this.isRecording ? '停止' : '录像')
          .width(80).height(80)
          .borderRadius(40)
          .margin({ left: 40 })
          .backgroundColor(this.isRecording ? '#FF4444' : '#FFFFFF')
          .onClick(() => this.onRecordToggle())
      }
      .justifyContent(FlexAlign.Center)
      .margin({ top: 16, bottom: 32 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#000000')
  }
}

UI 可以随你发挥:圆环式快门、长按拍视频、加网格线、加缩略图、加切换镜头按钮等等,本质上都是在上面的逻辑基础上做交互层优化


小结:把相机这条线过一遍,你就离“相机类 App”不远了

这一篇按你给的大纲,已经把主干打通了:

  1. CameraX API / Camera 架构

    • CameraManager → CameraInput → Output → Session → 预览 Surface
  2. 拍照 / 录像能力

    • PhotoOutput.capture() → 获取 JPEG 数据
    • AVRecorder + VideoOutput → 控制开始 / 停止录制
  3. 相册写入

    • MediaLibrary 创建图片 / 视频 Asset
    • 写入二进制数据 → 系统相册可见
  4. 相机权限

    • CAMERA / MICROPHONE / READ_MEDIA / WRITE_MEDIA
    • 静态 + 动态权限申请
  5. 自定义相机 UI 实战

    • 自己写预览 + 操作按钮
    • 利用状态管理(isRecording 等)做 UI 反馈

如果觉得有帮助,别忘了点个赞+关注支持一下~
喜欢记得关注,别让好内容被埋没~

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐