You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Flutter使用camera package时,如何在录制视频时抓拍照片?

嘿,这个需求我之前帮不少开发者解决过!Flutter官方的camera包确实没有直接提供“录视频时实时抓拍”的API,但我们可以通过捕获预览帧或者事后从视频提取帧的方式来实现,下面给你两种实用的方案:

方案一:实时抓拍(从预览帧生成图片)

这个方案是在录视频的同时,监听相机的预览流,当用户触发抓拍时,把当前的预览帧转换成图片保存,完全实时。

实现步骤:

  1. 先在pubspec.yaml中添加image依赖(用于转换相机帧格式):
dependencies:
  flutter:
    sdk: flutter
  camera: ^0.10.0+1
  image: ^4.0.17
  path_provider: ^2.0.15
  1. 核心代码实现:
import 'package:camera/camera.dart';
import 'package:image/image.dart' as img;
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'dart:typed_data';

class CameraScreen extends StatefulWidget {
  @override
  _CameraScreenState createState() => _CameraScreenState();
}

class _CameraScreenState extends State<CameraScreen> {
  CameraController? _controller;
  bool _isRecording = false;
  bool _shouldCapture = false;
  XFile? _capturedImage;

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

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

  // 开始录制视频
  Future<void> startRecording() async {
    final controller = _controller!;
    if (!controller.value.isInitialized || controller.value.isRecordingVideo) return;

    try {
      setState(() => _isRecording = true);
      await controller.startVideoRecording();
      // 启动预览帧流,用于抓拍
      controller.startImageStream((CameraImage image) async {
        if (_shouldCapture) {
          _capturedImage = await _convertFrameToImage(image);
          setState(() => _shouldCapture = false);
          // 这里可以添加保存到相册或UI更新的逻辑
        }
      });
    } on CameraException catch (e) {
      print('录制出错: ${e.description}');
      setState(() => _isRecording = false);
    }
  }

  // 结束录制视频
  Future<void> stopRecording() async {
    final controller = _controller!;
    if (!controller.value.isRecordingVideo) return;

    try {
      await controller.stopVideoRecording();
      setState(() => _isRecording = false);
      controller.stopImageStream(); // 停止帧流监听
    } on CameraException catch (e) {
      print('停止录制出错: ${e.description}');
    }
  }

  // 抓拍按钮触发方法
  void captureDuringRecording() {
    if (_isRecording) setState(() => _shouldCapture = true);
  }

  // 将CameraImage转换为可保存的XFile
  Future<XFile> _convertFrameToImage(CameraImage image) async {
    img.Image convertedImage;
    // 处理不同设备的图像格式,YUV420是通用格式
    if (image.format.group == ImageFormatGroup.yuv420) {
      convertedImage = img.convertYUV420(image);
    } else if (image.format.group == ImageFormatGroup.bgra8888) {
      convertedImage = img.convertBGRA8888(image);
    } else {
      throw Exception('不支持的图像格式');
    }

    final jpegBytes = img.encodeJpg(convertedImage);
    final tempDir = await getTemporaryDirectory();
    final file = File('${tempDir.path}/抓拍照片_${DateTime.now().millisecondsSinceEpoch}.jpg');
    await file.writeAsBytes(jpegBytes);
    return XFile(file.path);
  }

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

  // 省略UI部分代码,你可以根据自己的布局添加录制、抓拍按钮
}

方案二:事后从视频截取帧(非实时)

如果你的场景对实时性要求不高,也可以在视频录制完成后,从已保存的视频文件里提取指定帧作为抓拍的照片,这种方式更省资源。

实现步骤:

  1. 添加video_player依赖到pubspec.yaml
dependencies:
  video_player: ^2.7.0
  1. 核心代码:
import 'package:video_player/video_player.dart';

// 从视频文件中截取第一帧作为抓拍照片
Future<XFile> captureFrameFromVideo(XFile videoFile) async {
  final controller = VideoPlayerController.file(File(videoFile.path));
  await controller.initialize();
  await controller.pause(); // 停在第一帧
  final capturedImage = await controller.takePicture();
  await controller.dispose();
  return capturedImage;
}

// 使用示例:录制完成后调用
Future<void> onRecordingStopped(XFile videoFile) async {
  final capturedPhoto = await captureFrameFromVideo(videoFile);
  // 保存capturedPhoto到相册或其他位置
}

注意事项

  • 务必确保已经申请了相机、存储、麦克风权限(录视频必须要有麦克风权限)
  • 高分辨率下开启预览帧流会增加CPU负载,可以根据需求调整ResolutionPreset为中等或低分辨率
  • 测试不同设备时,优先适配YUV420格式(大部分安卓和iOS设备都支持)

内容的提问来源于stack exchange,提问作者dinesh.rajbhar

火山引擎 最新活动