如何通过Flutter Camera图像流生成视频并上传至服务器?
Flutter 相机图像流合并为MP4并上传服务器的可行方案
方案一:使用flutter_ffmpeg处理图像序列转视频
这是通用性最强的方案,借助FFmpeg的编码能力将采集到的图像序列转为标准MP4格式。
- 采集并保存图像帧
在_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; }
- 执行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(); } }
- 上传视频到服务器
使用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
相关产品推荐
相关产品推荐

