欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

本文基于flutter3.27.5开发

在这里插入图片描述

一、camera 库概述

相机功能是移动应用的核心能力之一,广泛应用于拍照、录像、扫码、视频通话等场景。在 Flutter for OpenHarmony 应用开发中,camera 是官方提供的跨平台相机插件,提供了完整的相机控制能力。

camera 库特点

camera 库基于 Flutter 平台接口实现,提供了以下核心特性:

多相机支持:支持前后摄像头切换、多摄像头设备枚举和选择。

实时预览:提供 CameraPreview 组件,支持实时相机画面预览。

拍照录像:支持高质量照片拍摄和视频录制,支持暂停/恢复录制。

图像流处理:支持实时图像帧流输出,可用于扫码、人脸检测等场景。

丰富的控制选项:支持闪光灯、对焦、曝光、缩放等多种相机参数控制。

方向管理:支持设备方向检测和捕获方向锁定。

功能支持对比

功能 Android iOS OpenHarmony
相机枚举
实时预览
拍照
视频录制
闪光灯控制
对焦控制
曝光控制
缩放控制
图像流
前后摄像头切换

使用场景:拍照应用、视频录制、二维码扫描、人脸识别、AR应用、视频通话等。


二、安装与配置

2.1 添加依赖

在项目的 pubspec.yaml 文件中添加 camera 依赖:

dependencies:
  camera:
    git:
      url: https://atomgit.com/openharmony-tpc/flutter_packages.git
      path: packages/camera/camera

然后执行以下命令获取依赖:

flutter pub get

2.2 权限配置

在 OpenHarmony 项目中需要配置相机和麦克风权限。在 ohos/entry/src/main/module.json5 中添加:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.CAMERA",
        "reason": "$string:camera_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.MICROPHONE",
        "reason": "$string:microphone_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.WRITE_MEDIA",
        "reason": "$string:media_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      }
    ]
  }
}

ohos/entry/src/main/resources/base/element/string.json 中添加:

{
  "string": [
    {
      "name": "camera_reason",
      "value": "用于拍照和录像功能"
    },
    {
      "name": "microphone_reason",
      "value": "用于视频录制时的音频采集"
    },
    {
      "name": "media_reason",
      "value": "用于保存照片和视频"
    }
  ]
}

三、核心 API 详解

3.1 CameraDescription 类

CameraDescription 描述相机设备的属性。

class CameraDescription {
  final String name;              // 相机唯一标识符
  final CameraLensDirection lensDirection;  // 镜头方向
  final int sensorOrientation;    // 传感器方向角度
  
  const CameraDescription({
    required this.name,
    required this.lensDirection,
    required this.sensorOrientation,
  });
}

3.2 CameraLensDirection 枚举

CameraLensDirection 定义镜头方向。

enum CameraLensDirection {
  front,    // 前置摄像头
  back,     // 后置摄像头
  external, // 外部摄像头
}

3.3 availableCameras 方法

availableCameras 方法获取设备上可用的相机列表。

Future<List<CameraDescription>> availableCameras()

使用示例

final cameras = await availableCameras();
for (final camera in cameras) {
  print('相机: ${camera.name}, 方向: ${camera.lensDirection}');
}

3.4 CameraController 类

CameraController 是相机控制的核心类,管理相机的所有操作。

class CameraController extends ValueNotifier<CameraValue> {
  CameraController(
    CameraDescription description,
    ResolutionPreset resolutionPreset, {
    bool enableAudio = true,
    ImageFormatGroup? imageFormatGroup,
  });
  
  Future<void> initialize();
  Future<XFile> takePicture();
  Future<void> startVideoRecording();
  Future<XFile> stopVideoRecording();
  Future<void> setFlashMode(FlashMode mode);
  Future<void> setFocusMode(FocusMode mode);
  Future<void> setZoomLevel(double zoom);
  Future<void> dispose();
}

3.5 initialize 方法

initialize 方法初始化相机,必须在其他操作之前调用。

Future<void> initialize()

使用示例

final controller = CameraController(
  cameras.first,
  ResolutionPreset.high,
);
await controller.initialize();

3.6 takePicture 方法

takePicture 方法拍摄照片并返回保存的文件。

Future<XFile> takePicture()

使用示例

if (controller.value.isInitialized) {
  final image = await controller.takePicture();
  print('照片保存路径: ${image.path}');
}

3.7 startVideoRecording 方法

startVideoRecording 方法开始录制视频。

Future<void> startVideoRecording()

使用示例

await controller.startVideoRecording();

3.8 stopVideoRecording 方法

stopVideoRecording 方法停止录制并返回视频文件。

Future<XFile> stopVideoRecording()

使用示例

final video = await controller.stopVideoRecording();
print('视频保存路径: ${video.path}');

3.9 ResolutionPreset 枚举

ResolutionPreset 定义预览分辨率预设。

enum ResolutionPreset {
  low,       // 240p (320x240)
  medium,    // 480p (720x480)
  high,      // 720p (1280x720)
  veryHigh,  // 1080p (1920x1080)
  max,       // 最高分辨率
}

3.10 FlashMode 枚举

FlashMode 定义闪光灯模式。

enum FlashMode {
  off,      // 关闭
  auto,     // 自动
  always,   // 常开
  torch,    // 手电筒模式
}

3.11 FocusMode 枚举

FocusMode 定义对焦模式。

enum FocusMode {
  auto,     // 自动对焦
  locked,   // 锁定对焦
}

3.12 ExposureMode 枚举

ExposureMode 定义曝光模式。

enum ExposureMode {
  auto,     // 自动曝光
  locked,   // 锁定曝光
}

四、Widget 组件详解

4.1 CameraPreview 组件

CameraPreview 是相机预览组件,用于显示实时相机画面。

class CameraPreview extends StatelessWidget {
  const CameraPreview(
    this.controller, {
    super.key,
    this.child,
  });
  
  final CameraController controller;
  final Widget? child;
}

使用示例

if (controller.value.isInitialized) {
  return CameraPreview(controller);
}

4.2 CameraValue 类

CameraValue 包含相机的当前状态信息。

class CameraValue {
  final bool isInitialized;           // 是否已初始化
  final bool isRecordingVideo;        // 是否正在录制视频
  final bool isTakingPicture;         // 是否正在拍照
  final bool isStreamingImages;       // 是否正在流式传输图像
  final Size? previewSize;            // 预览尺寸
  final FlashMode flashMode;          // 闪光灯模式
  final ExposureMode exposureMode;    // 曝光模式
  final FocusMode focusMode;          // 对焦模式
  final DeviceOrientation deviceOrientation;  // 设备方向
}

4.3 CameraImage 类

CameraImage 表示从相机获取的图像帧数据。

class CameraImage {
  final List<Plane> planes;     // 图像平面数据
  final int width;              // 图像宽度
  final int height;             // 图像高度
  final ImageFormat format;     // 图像格式
}

4.4 startImageStream 方法

startImageStream 方法启动图像流,用于实时处理相机帧。

Future<void> startImageStream(onLatestImageAvailable onAvailable)

使用示例

controller.startImageStream((CameraImage image) {
  // 处理每一帧图像
  print('收到图像: ${image.width}x${image.height}');
});

五、OpenHarmony 平台实现原理

5.1 原生 API 映射

camera 在 OpenHarmony 平台上使用 @kit.CameraKit@kit.MediaKit 模块实现:

Flutter API OpenHarmony API
availableCameras cameraManager.getSupportedCameras
initialize cameraInput.open + session.start
takePicture photoOutput.capture
startVideoRecording videoOutput.start + avRecorder
setFlashMode session.setFlashMode
setFocusMode session.setFocusMode
setZoomLevel session.setZoomRatio
setExposureMode session.setExposureMode

5.2 相机枚举实现

OpenHarmony 使用 cameraManager 获取可用相机列表:

getAvailableCameras(context: Context): HashMap<String, Object>[] {
  let cameraManager: camera.CameraManager = this.getCameraManager(context);
  let cameras: Array<camera.CameraDevice> = cameraManager.getSupportedCameras();
  
  let reply: HashMap<String, Object>[] = [];
  for (let cameraDevice of cameras) {
    let map: HashMap<String, Object> = new HashMap();
    map.set('name', cameraDevice.cameraId);
    map.set('lensFacing', this.getLensFacing(cameraDevice));
    map.set('sensorOrientation', this.getSensorOrientation(cameraDevice));
    reply.push(map);
  }
  return reply;
}

5.3 相机初始化实现

相机初始化涉及创建 CameraInput 和 Session:

async open(imageFormatGroup: string | null): Promise<void> {
  this.cameraDevice = CameraUtils.getCameraDevice(
    this.cameraProperties.getCameraName(), 
    this.cameraManager
  );
  
  this.cameraInput = this.cameraManager.createCameraInput(this.cameraDevice);
  await this.cameraInput.open();
  
  this.startPreview();
  this.dartMessenger.sendCameraInitializedEvent(
    resolutionFeature.getPreviewSize().width,
    resolutionFeature.getPreviewSize().height,
    exposureMode,
    focusMode,
    exposurePointSupported,
    focusPointSupported
  );
}

5.4 预览实现

预览使用双路输出实现 Texture 渲染:

private previewOutput: camera.PreviewOutput | null = null;
private previewOutput2: camera.PreviewOutput | null = null;

startPreview(): void {
  this.photoSession = this.cameraManager.createSession('photo');
  this.photoSession.beginConfig();
  this.photoSession.addInput(this.cameraInput);
  this.photoSession.addOutput(this.previewOutput);
  this.photoSession.addOutput(this.previewOutput2);
  this.photoSession.commitConfig();
  this.photoSession.start();
}

5.5 拍照实现

拍照使用 PhotoOutput 捕获图像:

async takePicture(result: MethodResult) {
  this.runPictureAutoFocus();
  
  let photoCaptureSetting: camera.PhotoCaptureSetting = {
    quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,
    rotation: this.photoRotation
  };
  
  this.photoOutPut.capture(photoCaptureSetting, async (err, photo) => {
    if (photo) {
      let file = await this.savePhoto(photo);
      result.success(file);
    }
  });
}

5.6 视频录制实现

视频录制使用 AVRecorder 和 VideoOutput:

async startVideoRecording(result: MethodResult, enableStream: boolean) {
  this.videoSession = this.cameraManager.createSession('video');
  this.videoSession.beginConfig();
  this.videoSession.addInput(this.cameraInput);
  this.videoSession.addOutput(this.videoOutput);
  this.videoSession.commitConfig();
  this.videoSession.start();
  
  this.avRecorder = media.createAVRecorder();
  await this.avRecorder.prepare(this.getAVRecorderConfig());
  await this.avRecorder.start();
  
  this.recordingVideo = true;
  result.success(null);
}

5.7 闪光灯控制实现

setFlashMode(result: MethodResult, newMode: camera.FlashMode) {
  if (!this.getCurSession().hasFlash()) {
    result.error("setFlashModeFailed", "The camera device does not have flash.", null);
    return;
  }
  
  if (!this.getCurSession().isFlashModeSupported(newMode)) {
    result.error("setFlashModeFailed", "The flash mode is unsupported.", null);
    return;
  }
  
  this.getCurSession().setFlashMode(newMode);
  result.success(null);
}

5.8 缩放控制实现

setZoomLevel(result: MethodResult, zoom: number): void {
  let maxZoom = this.cameraFeatures.getZoomLevel().getMaximumZoomLevel(this.getCurSession());
  let minZoom = this.cameraFeatures.getZoomLevel().getMinimumZoomLevel(this.getCurSession());
  
  if (zoom > maxZoom || zoom < minZoom) {
    result.error("ZOOM_ERROR", "Zoom level out of bounds", null);
    return;
  }
  
  this.getCurSession().setZoomRatio(zoom);
  result.success(null);
}

六、MethodChannel 通信协议

6.1 方法列表

方法 参数 返回值 说明
availableCameras - List <Camera> 获取可用相机
create cameraName, resolutionPreset cameraId 创建相机实例
initialize cameraId, imageFormatGroup - 初始化相机
takePicture cameraId XFile 拍照
startVideoRecording cameraId, enableStream - 开始录制
stopVideoRecording cameraId XFile 停止录制
setFlashMode cameraId, mode - 设置闪光灯
setFocusMode cameraId, mode - 设置对焦模式
setFocusPoint cameraId, x, y - 设置对焦点
setExposureMode cameraId, mode - 设置曝光模式
setExposurePoint cameraId, x, y - 设置曝光点
setExposureOffset cameraId, offset - 设置曝光补偿
setZoomLevel cameraId, zoom - 设置缩放级别
getMaxZoomLevel cameraId maxZoom 获取最大缩放
getMinZoomLevel cameraId minZoom 获取最小缩放
pausePreview cameraId - 暂停预览
resumePreview cameraId - 恢复预览
startImageStream cameraId - 开始图像流
stopImageStream cameraId - 停止图像流
dispose cameraId - 释放资源

6.2 相机信息返回格式

{
  "name": "0",
  "lensFacing": "back",
  "sensorOrientation": 90,
}

6.3 事件流

事件 说明
cameraInitialized 相机初始化完成
cameraError 相机发生错误
cameraClosing 相机正在关闭
videoRecorded 视频录制完成
deviceOrientationChanged 设备方向改变

七、实战案例

7.1 基础相机预览

import 'package:camera/camera.dart';
import 'package:flutter/material.dart';

class BasicCameraPage extends StatefulWidget {
  const BasicCameraPage({super.key});

  
  State<BasicCameraPage> createState() => _BasicCameraPageState();
}

class _BasicCameraPageState extends State<BasicCameraPage> {
  CameraController? _controller;
  List<CameraDescription>? _cameras;

  
  void initState() {
    super.initState();
    _initCamera();
  }

  Future<void> _initCamera() async {
    _cameras = await availableCameras();
    if (_cameras!.isNotEmpty) {
      _controller = CameraController(
        _cameras!.first,
        ResolutionPreset.high,
      );
      await _controller!.initialize();
      setState(() {});
    }
  }

  
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    if (_controller == null || !_controller!.value.isInitialized) {
      return const Scaffold(
        body: Center(child: CircularProgressIndicator()),
      );
    }
    return Scaffold(
      appBar: AppBar(title: const Text('相机预览')),
      body: CameraPreview(_controller!),
    );
  }
}

7.2 拍照功能

class TakePicturePage extends StatefulWidget {
  const TakePicturePage({super.key});

  
  State<TakePicturePage> createState() => _TakePicturePageState();
}

class _TakePicturePageState extends State<TakePicturePage> {
  CameraController? _controller;
  bool _isTakingPicture = false;

  
  void initState() {
    super.initState();
    _initCamera();
  }

  Future<void> _initCamera() async {
    final cameras = await availableCameras();
    if (cameras.isNotEmpty) {
      _controller = CameraController(
        cameras.first,
        ResolutionPreset.veryHigh,
      );
      await _controller!.initialize();
      setState(() {});
    }
  }

  Future<void> _takePicture() async {
    if (_controller == null || !_controller!.value.isInitialized) return;
    if (_isTakingPicture) return;

    setState(() => _isTakingPicture = true);
    try {
      final image = await _controller!.takePicture();
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('照片已保存: ${image.path}')),
      );
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('拍照失败: $e')),
      );
    } finally {
      setState(() => _isTakingPicture = false);
    }
  }

  
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    if (_controller == null || !_controller!.value.isInitialized) {
      return const Scaffold(
        body: Center(child: CircularProgressIndicator()),
      );
    }
    return Scaffold(
      body: CameraPreview(_controller!),
      floatingActionButton: FloatingActionButton(
        onPressed: _isTakingPicture ? null : _takePicture,
        child: const Icon(Icons.camera_alt),
      ),
    );
  }
}

7.3 视频录制

class VideoRecordingPage extends StatefulWidget {
  const VideoRecordingPage({super.key});

  
  State<VideoRecordingPage> createState() => _VideoRecordingPageState();
}

class _VideoRecordingPageState extends State<VideoRecordingPage> {
  CameraController? _controller;

  
  void initState() {
    super.initState();
    _initCamera();
  }

  Future<void> _initCamera() async {
    final cameras = await availableCameras();
    if (cameras.isNotEmpty) {
      _controller = CameraController(
        cameras.first,
        ResolutionPreset.high,
        enableAudio: true,
      );
      await _controller!.initialize();
      setState(() {});
    }
  }

  Future<void> _toggleRecording() async {
    if (_controller == null) return;

    if (_controller!.value.isRecordingVideo) {
      final video = await _controller!.stopVideoRecording();
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('视频已保存: ${video.path}')),
      );
    } else {
      await _controller!.startVideoRecording();
    }
    setState(() {});
  }

  
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    if (_controller == null || !_controller!.value.isInitialized) {
      return const Scaffold(
        body: Center(child: CircularProgressIndicator()),
      );
    }
    return Scaffold(
      body: CameraPreview(_controller!),
      floatingActionButton: FloatingActionButton(
        onPressed: _toggleRecording,
        backgroundColor: _controller!.value.isRecordingVideo 
            ? Colors.red 
            : Colors.blue,
        child: Icon(
          _controller!.value.isRecordingVideo 
              ? Icons.stop 
              : Icons.videocam,
        ),
      ),
    );
  }
}

7.4 前后摄像头切换

class CameraSwitchPage extends StatefulWidget {
  const CameraSwitchPage({super.key});

  
  State<CameraSwitchPage> createState() => _CameraSwitchPageState();
}

class _CameraSwitchPageState extends State<CameraSwitchPage> {
  List<CameraDescription>? _cameras;
  CameraController? _controller;
  int _cameraIndex = 0;

  
  void initState() {
    super.initState();
    _initCamera();
  }

  Future<void> _initCamera() async {
    _cameras = await availableCameras();
    if (_cameras!.isNotEmpty) {
      await _setupCamera(_cameras![_cameraIndex]);
    }
  }

  Future<void> _setupCamera(CameraDescription description) async {
    _controller?.dispose();
    _controller = CameraController(
      description,
      ResolutionPreset.high,
    );
    await _controller!.initialize();
    setState(() {});
  }

  void _switchCamera() {
    if (_cameras == null || _cameras!.length < 2) return;
    _cameraIndex = (_cameraIndex + 1) % _cameras!.length;
    _setupCamera(_cameras![_cameraIndex]);
  }

  
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    if (_controller == null || !_controller!.value.isInitialized) {
      return const Scaffold(
        body: Center(child: CircularProgressIndicator()),
      );
    }
    return Scaffold(
      body: CameraPreview(_controller!),
      floatingActionButton: FloatingActionButton(
        onPressed: _switchCamera,
        child: const Icon(Icons.flip_camera_ios),
      ),
    );
  }
}

7.5 闪光灯控制

class FlashControlPage extends StatefulWidget {
  const FlashControlPage({super.key});

  
  State<FlashControlPage> createState() => _FlashControlPageState();
}

class _FlashControlPageState extends State<FlashControlPage> {
  CameraController? _controller;
  FlashMode _flashMode = FlashMode.auto;

  
  void initState() {
    super.initState();
    _initCamera();
  }

  Future<void> _initCamera() async {
    final cameras = await availableCameras();
    if (cameras.isNotEmpty) {
      _controller = CameraController(
        cameras.first,
        ResolutionPreset.high,
      );
      await _controller!.initialize();
      setState(() {});
    }
  }

  Future<void> _cycleFlashMode() async {
    if (_controller == null) return;

    final modes = [FlashMode.off, FlashMode.auto, FlashMode.always, FlashMode.torch];
    final currentIndex = modes.indexOf(_flashMode);
    final nextIndex = (currentIndex + 1) % modes.length;
    _flashMode = modes[nextIndex];

    await _controller!.setFlashMode(_flashMode);
    setState(() {});
  }

  IconData _getFlashIcon() {
    switch (_flashMode) {
      case FlashMode.off:
        return Icons.flash_off;
      case FlashMode.auto:
        return Icons.flash_auto;
      case FlashMode.always:
        return Icons.flash_on;
      case FlashMode.torch:
        return Icons.flashlight_on;
    }
  }

  
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    if (_controller == null || !_controller!.value.isInitialized) {
      return const Scaffold(
        body: Center(child: CircularProgressIndicator()),
      );
    }
    return Scaffold(
      body: Stack(
        children: [
          CameraPreview(_controller!),
          Positioned(
            top: 40,
            right: 20,
            child: FloatingActionButton(
              mini: true,
              onPressed: _cycleFlashMode,
              child: Icon(_getFlashIcon()),
            ),
          ),
        ],
      ),
    );
  }
}

7.6 缩放控制

class ZoomControlPage extends StatefulWidget {
  const ZoomControlPage({super.key});

  
  State<ZoomControlPage> createState() => _ZoomControlPageState();
}

class _ZoomControlPageState extends State<ZoomControlPage> {
  CameraController? _controller;
  double _minZoom = 1.0;
  double _maxZoom = 1.0;
  double _currentZoom = 1.0;

  
  void initState() {
    super.initState();
    _initCamera();
  }

  Future<void> _initCamera() async {
    final cameras = await availableCameras();
    if (cameras.isNotEmpty) {
      _controller = CameraController(
        cameras.first,
        ResolutionPreset.high,
      );
      await _controller!.initialize();
      _minZoom = await _controller!.getMinZoomLevel();
      _maxZoom = await _controller!.getMaxZoomLevel();
      setState(() {});
    }
  }

  Future<void> _setZoom(double zoom) async {
    if (_controller == null) return;
    await _controller!.setZoomLevel(zoom);
    setState(() => _currentZoom = zoom);
  }

  
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    if (_controller == null || !_controller!.value.isInitialized) {
      return const Scaffold(
        body: Center(child: CircularProgressIndicator()),
      );
    }
    return Scaffold(
      body: Stack(
        children: [
          CameraPreview(_controller!),
          Positioned(
            bottom: 40,
            left: 20,
            right: 20,
            child: Column(
              children: [
                Text(
                  '缩放: ${_currentZoom.toStringAsFixed(1)}x',
                  style: const TextStyle(
                    color: Colors.white,
                    fontSize: 16,
                    shadows: [Shadow(blurRadius: 4)],
                  ),
                ),
                Slider(
                  value: _currentZoom,
                  min: _minZoom,
                  max: _maxZoom,
                  onChanged: _setZoom,
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

7.7 图像流处理

class ImageStreamPage extends StatefulWidget {
  const ImageStreamPage({super.key});

  
  State<ImageStreamPage> createState() => _ImageStreamPageState();
}

class _ImageStreamPageState extends State<ImageStreamPage> {
  CameraController? _controller;
  bool _isStreaming = false;
  int _frameCount = 0;

  
  void initState() {
    super.initState();
    _initCamera();
  }

  Future<void> _initCamera() async {
    final cameras = await availableCameras();
    if (cameras.isNotEmpty) {
      _controller = CameraController(
        cameras.first,
        ResolutionPreset.medium,
        imageFormatGroup: ImageFormatGroup.yuv420,
      );
      await _controller!.initialize();
      setState(() {});
    }
  }

  void _toggleImageStream() {
    if (_controller == null) return;

    if (_isStreaming) {
      _controller!.stopImageStream();
      setState(() => _isStreaming = false);
    } else {
      _controller!.startImageStream((CameraImage image) {
        setState(() => _frameCount++);
        // 这里可以进行图像处理,如扫码、人脸检测等
      });
      setState(() {
        _isStreaming = true;
        _frameCount = 0;
      });
    }
  }

  
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    if (_controller == null || !_controller!.value.isInitialized) {
      return const Scaffold(
        body: Center(child: CircularProgressIndicator()),
      );
    }
    return Scaffold(
      body: Stack(
        children: [
          CameraPreview(_controller!),
          Positioned(
            bottom: 40,
            left: 0,
            right: 0,
            child: Column(
              children: [
                Text(
                  '帧数: $_frameCount',
                  style: const TextStyle(
                    color: Colors.white,
                    fontSize: 20,
                    shadows: [Shadow(blurRadius: 4)],
                  ),
                ),
                const SizedBox(height: 20),
                ElevatedButton(
                  onPressed: _toggleImageStream,
                  child: Text(_isStreaming ? '停止图像流' : '开始图像流'),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

八、最佳实践

8.1 相机生命周期管理

正确管理相机生命周期,避免内存泄漏:

class CameraLifecyclePage extends StatefulWidget {
  const CameraLifecyclePage({super.key});

  
  State<CameraLifecyclePage> createState() => _CameraLifecyclePageState();
}

class _CameraLifecyclePageState extends State<CameraLifecyclePage> 
    with WidgetsBindingObserver {
  CameraController? _controller;

  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _initCamera();
  }

  
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (_controller == null || !_controller!.value.isInitialized) return;

    if (state == AppLifecycleState.inactive) {
      _controller?.dispose();
      _controller = null;
    } else if (state == AppLifecycleState.resumed) {
      _initCamera();
    }
  }

  Future<void> _initCamera() async {
    final cameras = await availableCameras();
    if (cameras.isNotEmpty) {
      _controller = CameraController(cameras.first, ResolutionPreset.high);
      await _controller!.initialize();
      setState(() {});
    }
  }

  
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    _controller?.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: _controller?.value.isInitialized == true
          ? CameraPreview(_controller!)
          : const Center(child: CircularProgressIndicator()),
    );
  }
}

8.2 错误处理

完善的错误处理机制:

Future<void> _initCamera() async {
  try {
    final cameras = await availableCameras();
    if (cameras.isEmpty) {
      _showError('未找到可用相机');
      return;
    }
  
    _controller = CameraController(
      cameras.first,
      ResolutionPreset.high,
    );
  
    await _controller!.initialize();
  
    if (mounted) {
      setState(() {});
    }
  } on CameraException catch (e) {
    _showError('相机错误: ${e.description}');
  } catch (e) {
    _showError('初始化失败: $e');
  }
}

void _showError(String message) {
  if (mounted) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.red,
      ),
    );
  }
}

8.3 权限处理

在初始化相机前检查权限:

Future<bool> _checkPermissions() async {
  final cameraStatus = await Permission.camera.status;
  final microphoneStatus = await Permission.microphone.status;

  if (!cameraStatus.isGranted) {
    final result = await Permission.camera.request();
    if (!result.isGranted) {
      _showError('需要相机权限');
      return false;
    }
  }

  if (!microphoneStatus.isGranted) {
    final result = await Permission.microphone.request();
    if (!result.isGranted) {
      _showError('需要麦克风权限');
      return false;
    }
  }

  return true;
}

8.4 性能优化

使用合适的分辨率预设:

// 根据使用场景选择分辨率
ResolutionPreset _getResolutionForUseCase(UseCase useCase) {
  switch (useCase) {
    case UseCase.preview:
      return ResolutionPreset.medium;  // 预览使用中等分辨率
    case UseCase.photo:
      return ResolutionPreset.veryHigh; // 拍照使用高分辨率
    case UseCase.video:
      return ResolutionPreset.high;     // 视频使用高清分辨率
    case UseCase.stream:
      return ResolutionPreset.low;      // 图像流使用低分辨率
  }
}

8.5 资源释放

确保正确释放资源:


void dispose() {
  // 停止所有操作
  if (_controller?.value.isRecordingVideo == true) {
    _controller?.stopVideoRecording();
  }
  if (_controller?.value.isStreamingImages == true) {
    _controller?.stopImageStream();
  }
  
  // 释放控制器
  _controller?.dispose();
  _controller = null;
  
  super.dispose();
}

九、常见问题

Q1: 相机初始化失败怎么办?

A: 检查以下几点:

  1. 确保已在 module.json5 中配置相机权限
  2. 确保应用已获得相机权限授权
  3. 检查是否有其他应用正在占用相机
  4. 尝试重新启动应用
try {
  await controller.initialize();
} on CameraException catch (e) {
  if (e.code == 'CameraAccessDenied') {
    // 引导用户去设置中开启权限
  } else if (e.code == 'CameraDeviceInUse') {
    // 相机被占用,稍后重试
  }
}

Q2: 预览画面方向不正确?

A: 使用 sensorOrientation 调整预览方向:

final camera = cameras.first;
final orientation = camera.sensorOrientation;

// 根据传感器方向设置预览旋转
if (Platform.isAndroid || Platform.isOHOS) {
  // OpenHarmony 和 Android 需要考虑传感器方向
}

Q3: 拍照后照片方向错误?

A: 使用 EXIF 信息或锁定捕获方向:

// 锁定捕获方向
await controller.lockCaptureOrientation(DeviceOrientation.portraitUp);

// 拍照
final image = await controller.takePicture();

// 解锁捕获方向
await controller.unlockCaptureOrientation();

Q4: 视频录制没有声音?

A: 确保初始化时启用了音频:

final controller = CameraController(
  camera,
  ResolutionPreset.high,
  enableAudio: true,  // 启用音频
);

Q5: 图像流帧率过低?

A: 降低分辨率预设:

// 使用较低分辨率提高帧率
final controller = CameraController(
  camera,
  ResolutionPreset.low,  // 或 ResolutionPreset.medium
  imageFormatGroup: ImageFormatGroup.yuv420,
);

Q6: 如何实现连续对焦?

A: 设置自动对焦模式:

await controller.setFocusMode(FocusMode.auto);
await controller.setFocusPoint(null);  // 重置对焦点,启用连续对焦

十、总结

camera 库为 Flutter for OpenHarmony 提供了完整的相机功能支持,包括:

核心能力

  • 多相机设备枚举和选择
  • 实时相机预览
  • 高质量照片拍摄
  • 视频录制(支持暂停/恢复)
  • 图像流实时处理

控制功能

  • 闪光灯模式控制
  • 对焦模式和区域控制
  • 曝光模式和补偿控制
  • 缩放级别控制
  • 捕获方向锁定

OpenHarmony 特性

  • 基于 @kit.CameraKit 原生实现
  • 支持 Texture 高效渲染
  • 完整的权限管理
  • 双路预览输出

通过 camera 库,开发者可以快速构建功能丰富的相机应用,满足拍照、录像、扫码等多种场景需求。


十一、完整代码示例

在这里插入图片描述

import 'package:camera/camera.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const CameraApp());
}

class CameraApp extends StatelessWidget {
  const CameraApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Camera Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const CameraHomePage(),
    );
  }
}

class CameraHomePage extends StatefulWidget {
  const CameraHomePage({super.key});

  
  State<CameraHomePage> createState() => _CameraHomePageState();
}

class _CameraHomePageState extends State<CameraHomePage> 
    with WidgetsBindingObserver {
  CameraController? _controller;
  List<CameraDescription>? _cameras;
  int _cameraIndex = 0;
  FlashMode _flashMode = FlashMode.auto;
  double _currentZoom = 1.0;
  double _minZoom = 1.0;
  double _maxZoom = 1.0;
  String _statusMessage = '';
  bool _isInitialized = false;

  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _initCamera();
  }

  
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (_controller == null || !_controller!.value.isInitialized) return;

    if (state == AppLifecycleState.inactive) {
      _controller?.dispose();
      setState(() {
        _controller = null;
        _isInitialized = false;
      });
    } else if (state == AppLifecycleState.resumed) {
      _initCamera();
    }
  }

  Future<void> _initCamera() async {
    try {
      _cameras = await availableCameras();
      if (_cameras!.isEmpty) {
        setState(() => _statusMessage = '未找到可用相机');
        return;
      }

      await _setupCamera(_cameras![_cameraIndex]);
    } catch (e) {
      setState(() => _statusMessage = '初始化失败: $e');
    }
  }

  Future<void> _setupCamera(CameraDescription description) async {
    _controller?.dispose();

    _controller = CameraController(
      description,
      ResolutionPreset.high,
      enableAudio: true,
    );

    await _controller!.initialize();

    final minZoom = await _controller!.getMinZoomLevel();
    final maxZoom = await _controller!.getMaxZoomLevel();

    setState(() {
      _minZoom = minZoom <= 0 ? 1.0 : minZoom;
      _maxZoom = maxZoom <= _minZoom ? _minZoom + 3.0 : maxZoom;
      _currentZoom = _minZoom;
      _isInitialized = true;
    });
  }

  void _switchCamera() {
    if (_cameras == null || _cameras!.length < 2) return;
    _cameraIndex = (_cameraIndex + 1) % _cameras!.length;
    _setupCamera(_cameras![_cameraIndex]);
  }

  Future<void> _takePicture() async {
    if (_controller == null || !_controller!.value.isInitialized) return;

    try {
      final image = await _controller!.takePicture();
      _showMessage('照片已保存: ${image.path}');
    } catch (e) {
      _showMessage('拍照失败: $e');
    }
  }

  Future<void> _toggleRecording() async {
    if (_controller == null) return;

    try {
      if (_controller!.value.isRecordingVideo) {
        final video = await _controller!.stopVideoRecording();
        _showMessage('视频已保存: ${video.path}');
      } else {
        await _controller!.startVideoRecording();
        _showMessage('开始录制');
      }
      setState(() {});
    } catch (e) {
      _showMessage('录制失败: $e');
    }
  }

  Future<void> _cycleFlashMode() async {
    if (_controller == null) return;

    final modes = [FlashMode.off, FlashMode.auto, FlashMode.always, FlashMode.torch];
    final currentIndex = modes.indexOf(_flashMode);
    _flashMode = modes[(currentIndex + 1) % modes.length];

    await _controller!.setFlashMode(_flashMode);
    setState(() {});
  }

  Future<void> _setZoom(double zoom) async {
    if (_controller == null) return;
    await _controller!.setZoomLevel(zoom);
    setState(() => _currentZoom = zoom);
  }

  void _showMessage(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  IconData _getFlashIcon() {
    switch (_flashMode) {
      case FlashMode.off:
        return Icons.flash_off;
      case FlashMode.auto:
        return Icons.flash_auto;
      case FlashMode.always:
        return Icons.flash_on;
      case FlashMode.torch:
        return Icons.flashlight_on;
    }
  }

  
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    _controller?.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      appBar: AppBar(
        title: const Text('相机'),
        actions: [
          if (_cameras != null && _cameras!.length > 1)
            IconButton(
              icon: const Icon(Icons.flip_camera_ios),
              onPressed: _switchCamera,
            ),
        ],
      ),
      body: Stack(
        children: [
          if (_isInitialized && _controller != null)
            Center(child: CameraPreview(_controller!))
          else
            Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const CircularProgressIndicator(),
                  const SizedBox(height: 16),
                  Text(
                    _statusMessage.isEmpty ? '正在初始化...' : _statusMessage,
                    style: const TextStyle(color: Colors.white),
                  ),
                ],
              ),
            ),
          if (_isInitialized) ...[
            Positioned(
              top: 20,
              right: 20,
              child: FloatingActionButton(
                mini: true,
                heroTag: 'flash',
                onPressed: _cycleFlashMode,
                child: Icon(_getFlashIcon()),
              ),
            ),
            Positioned(
              bottom: 100,
              left: 20,
              right: 20,
              child: Column(
                children: [
                  Text(
                    '缩放: ${_currentZoom.toStringAsFixed(1)}x',
                    style: const TextStyle(
                      color: Colors.white,
                      fontSize: 16,
                      shadows: [Shadow(blurRadius: 4)],
                    ),
                  ),
                  Slider(
                    value: _currentZoom,
                    min: _minZoom,
                    max: _maxZoom,
                    onChanged: _setZoom,
                  ),
                ],
              ),
            ),
          ],
        ],
      ),
      floatingActionButton: _isInitialized
          ? Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                FloatingActionButton(
                  heroTag: 'picture',
                  onPressed: _takePicture,
                  child: const Icon(Icons.camera_alt),
                ),
                const SizedBox(width: 20),
                FloatingActionButton(
                  heroTag: 'video',
                  onPressed: _toggleRecording,
                  backgroundColor: _controller?.value.isRecordingVideo == true
                      ? Colors.red
                      : null,
                  child: Icon(
                    _controller?.value.isRecordingVideo == true
                        ? Icons.stop
                        : Icons.videocam,
                  ),
                ),
              ],
            )
          : null,
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
    );
  }
}
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐