You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Flutter Camera图像流生成视频并上传至服务器?

Flutter 相机图像流合并为MP4并上传服务器的可行方案

方案一:使用flutter_ffmpeg处理图像序列转视频

这是通用性最强的方案,借助FFmpeg的编码能力将采集到的图像序列转为标准MP4格式。

  1. 采集并保存图像帧
    在_processCameraImage中将每一帧相机图像转换为JPEG格式,按顺序保存到临时目录:
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:image/image.dart' as img;

int _frameIndex = 0;

Future<void> _processCameraImage(CameraImage image) async {
  // 将CameraImage转为可编码的RGB图像(适配YUV420格式相机输出)
  final img.Image convertedImg = _convertYUVToRGB(image);
  final tempDir = await getTemporaryDirectory();
  final framePath = '${tempDir.path}/frame_${_frameIndex.toString().padLeft(4, '0')}.jpg';
  
  await File(framePath).writeAsBytes(img.encodeJpg(convertedImg, quality: 80));
  _frameIndex++;
}

// YUV420转RGB的具体实现
img.Image _convertYUVToRGB(CameraImage image) {
  final width = image.width;
  final height = image.height;
  final yPlane = image.planes[0];
  final uPlane = image.planes[1];
  final vPlane = image.planes[2];

  final img.Image result = img.Image(width, height);
  int uvIndex = 0;

  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      final yValue = yPlane.bytes[y * yPlane.bytesPerRow + x];
      final uValue = uPlane.bytes[uvIndex];
      final vValue = vPlane.bytes[uvIndex];

      // YUV转RGB公式计算
      final r = (yValue + 1.370705 * (vValue - 128)).clamp(0, 255).toInt();
      final g = (yValue - 0.698001 * (vValue - 128) - 0.337633 * (uValue - 128)).clamp(0, 255).toInt();
      final b = (yValue + 1.732446 * (uValue - 128)).clamp(0, 255).toInt();

      result.setPixelRgb(x, y, r, g, b);
      if (x % 2 == 1) uvIndex++;
    }
    if (y % 2 == 0) uvIndex += (uPlane.bytesPerRow - (width ~/ 2));
  }
  return result;
}
  1. 执行FFmpeg命令生成MP4
    添加flutter_ffmpeg依赖后,通过命令行将图像序列合并为视频:
import 'package:flutter_ffmpeg/flutter_ffmpeg.dart';

final _ffmpeg = FlutterFFmpeg();

Future<void> generateVideo() async {
  final tempDir = await getTemporaryDirectory();
  final inputPattern = '${tempDir.path}/frame_%04d.jpg';
  final outputPath = '${tempDir.path}/output.mp4';
  
  // 命令参数:帧率30(需与图像采集频率匹配)、H.264编码、兼容全平台的YUV420P像素格式
  final command = '-framerate 30 -i $inputPattern -c:v libx264 -r 30 -pix_fmt yuv420p $outputPath';
  
  final exitCode = await _ffmpeg.execute(command);
  if (exitCode == 0) {
    print('视频生成完成');
    await uploadVideo(outputPath);
    // 清理临时帧文件
    await _cleanTempFrames(tempDir);
  } else {
    print('视频生成失败,错误码:$exitCode');
  }
}

// 清理临时帧文件
Future<void> _cleanTempFrames(Directory tempDir) async {
  final files = tempDir.listSync().where((file) => file.path.contains('frame_')).toList();
  for (var file in files) {
    await file.delete();
  }
}
  1. 上传视频到服务器
    使用dio包实现文件上传:
import 'package:dio/dio.dart';

Future<void> uploadVideo(String videoPath) async {
  final dio = Dio();
  final formData = FormData.fromMap({
    'video': await MultipartFile.fromFile(videoPath, filename: 'camera_recording.mp4'),
  });
  
  try {
    final response = await dio.post('你的服务器上传接口地址', data: formData);
    if (response.statusCode == 200) {
      print('视频上传成功');
      // 上传完成后删除本地MP4文件
      await File(videoPath).delete();
    }
  } catch (e) {
    print('上传失败:$e');
  }
}

方案二:Native端实时编码(高性能场景)

如果需要低延迟、高稳定性的实时处理(避免大量临时文件IO),可通过Flutter平台通道调用原生能力:

  • Android端:使用MediaCodec直接将CameraImage的YUV数据编码为H.264,再封装为MP4
  • iOS端:利用AVAssetWriter将转换后的CMSampleBuffer写入MP4文件
    该方案需编写原生代码,但能大幅降低磁盘和内存开销,适合长时间录制场景。

关键注意事项

  • 帧率匹配:视频帧率必须与图像采集频率一致(如每33ms采集一帧对应30fps),否则播放速度会异常
  • 资源控制:高分辨率采集时,需限制临时文件数量,避免内存溢出或存储耗尽
  • 兼容性:优先使用H.264编码+YUV420P像素格式的MP4,确保在设备和服务器上的兼容性

内容的提问来源于stack exchange,提问作者Kristi Jorgji

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 09:55:11