Flutter 3.10.5下CameraImage转JPEG失败,求适配安卓/iOS的方案
Flutter 3.10.5 下 NV21 格式 CameraImage 转 JPEG 方案
针对你遇到的旧转换方案失效问题,以下是两种适配当前版本的可行实现:
方案一:纯 Dart 实现(基于 image 库)
步骤1:添加依赖
在 pubspec.yaml 中引入 image 库:
dependencies: image: ^4.0.17 path_provider: ^2.0.15 # 可选,用于保存文件
步骤2:转换代码实现
import 'dart:typed_data'; import 'dart:io'; import 'package:image/image.dart' as img; import 'package:path_provider/path_provider.dart'; // NV21 转 RGB Image 对象 img.Image convertNV21ToImage(CameraImage image) { final int width = image.width; final int height = image.height; final Uint8List yPlane = image.planes[0].bytes; final Uint8List uvPlane = image.planes[1].bytes; final img.Image rgbImage = img.Image(width, height); int uvIndex = 0; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { final int yValue = yPlane[y * width + x]; // NV21 为 4:2:0 采样,每4个Y共用一组UV if (y % 2 == 0 && x % 2 == 0) { final int uValue = uvPlane[uvIndex]; final int vValue = uvPlane[uvIndex + 1]; uvIndex += 2; // BT.601 标准 YUV 转 RGB 公式 int r = (yValue + 1.370705 * (vValue - 128)).round(); int g = (yValue - 0.698001 * (vValue - 128) - 0.337633 * (uValue - 128)).round(); int b = (yValue + 1.732446 * (uValue - 128)).round(); // 限制 RGB 值在 0-255 区间 r = r.clamp(0, 255); g = g.clamp(0, 255); b = b.clamp(0, 255); rgbImage.setPixelRgba(x, y, r, g, b, 255); } else { // 复用相邻像素的 UV 值 final int neighborX = x % 2 == 0 ? x : x - 1; final int neighborY = y % 2 == 0 ? y : y - 1; final int r = rgbImage.getRed(neighborY * width + neighborX); final int g = rgbImage.getGreen(neighborY * width + neighborX); final int b = rgbImage.getBlue(neighborY * width + neighborX); rgbImage.setPixelRgba(x, y, r, g, b, 255); } } } return rgbImage; } // 处理 CameraImage 并转成 JPEG void processImage(CameraImage image) async { try { final img.Image rgbImage = convertNV21ToImage(image); final Uint8List jpegBytes = img.encodeJpg(rgbImage, quality: 80); // 示例:保存到临时文件 final Directory tempDir = await getTemporaryDirectory(); final File file = File('${tempDir.path}/camera_jpeg.jpg'); await file.writeAsBytes(jpegBytes); print("JPEG 文件已保存:${file.path}"); } catch (e) { print("转换失败:$e"); } }
方案二:安卓原生实现(性能更优)
适合实时高帧率场景,利用安卓原生 API 直接转换 NV21 为 JPEG。
Flutter 端代码
import 'dart:typed_data'; import 'dart:io'; import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; final MethodChannel _imageConverterChannel = MethodChannel('com.example/camera_converter'); // 调用原生转换 Future<Uint8List?> convertNV21ToJpegNative(Uint8List nv21Bytes, int width, int height) async { try { return await _imageConverterChannel.invokeMethod('convertNV21ToJpeg', { 'nv21Bytes': nv21Bytes, 'width': width, 'height': height, 'quality': 80, }); } catch (e) { print("原生转换出错:$e"); return null; } } void processImage(CameraImage image) async { try { // 合并 Y 和 UV 平面为完整 NV21 字节数组 final Uint8List nv21Bytes = Uint8List(image.width * image.height * 3 ~/ 2); nv21Bytes.setRange(0, image.planes[0].bytes.length, image.planes[0].bytes); nv21Bytes.setRange(image.planes[0].bytes.length, nv21Bytes.length, image.planes[1].bytes); final Uint8List? jpegBytes = await convertNV21ToJpegNative(nv21Bytes, image.width, image.height); if (jpegBytes != null) { final Directory tempDir = await getTemporaryDirectory(); final File file = File('${tempDir.path}/native_camera_jpeg.jpg'); await file.writeAsBytes(jpegBytes); print("原生转换 JPEG 文件已保存:${file.path}"); } } catch (e) { print("处理失败:$e"); } }
安卓端代码(MainActivity)
package com.example.your_app_package; import android.graphics.YuvImage; import android.graphics.ImageFormat; import android.graphics.Rect; import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.plugin.common.MethodChannel; import java.io.ByteArrayOutputStream; public class MainActivity extends FlutterActivity { private static final String CHANNEL = "com.example/camera_converter"; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL) .setMethodCallHandler((call, result) -> { if (call.method.equals("convertNV21ToJpeg")) { byte[] nv21Bytes = call.argument("nv21Bytes"); int width = call.argument("width"); int height = call.argument("height"); int quality = call.argument("quality"); if (nv21Bytes == null || width <= 0 || height <= 0) { result.error("INVALID_ARGS", "参数无效", null); return; } try { YuvImage yuvImage = new YuvImage(nv21Bytes, ImageFormat.NV21, width, height, null); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); yuvImage.compressToJpeg(new Rect(0, 0, width, height), quality, outputStream); result.success(outputStream.toByteArray()); } catch (Exception e) { result.error("CONVERT_FAILED", "转换失败:" + e.getMessage(), null); } } else { result.notImplemented(); } }); } }
权限配置
在安卓 AndroidManifest.xml 中添加必要权限:
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" android:minSdkVersion="33" />
内容的提问来源于stack exchange,提问作者kaffeBurk
相关产品推荐
相关产品推荐

