Files
observer/flutter_app/lib/camera/frame_analyzer.dart
T
2026-08-25 16:01:07 +08:00

132 lines
3.3 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:typed_data';
import 'package:camera/camera.dart';
import '../detection/detection_result.dart';
import '../detection/detector_worker.dart';
import 'camera_view_model.dart';
/// 抽帧节流 + 后台推理(对应 Kotlin FrameAnalyzer)。
/// 推理在后台 isolateDetectorWorker)执行,主 isolate 只投递帧与收结果。
class FrameAnalyzer {
/// 连续检测:100ms 一帧
int intervalMs = 100;
/// null = 模型加载失败,仅预览不分析
final DetectorWorker? worker;
final CameraViewModel viewModel;
int _lastDetectMs = 0;
/// 诊断计数:推理调用/异常次数
int detectCalls = 0;
int detectErrors = 0;
String? lastError;
/// 最近一次完整处理(预处理+推理+运动检测)耗时 ms(worker 侧)
int lastProcessMs = 0;
/// 图像流回调是否到达(诊断用)
int framesReceived = 0;
FrameAnalyzer({required this.worker, required this.viewModel}) {
worker?.onResult = _onResult;
worker?.onError = _onError;
}
void recordStreamError(String msg) {
lastError = msg;
detectErrors++;
}
void _onResult(
List<DetectionResult> results,
List<MotionRegion> motion,
List<MotionRegion> novelty,
int rotation,
int width,
int height,
int processMs,
String yuvDiag) {
detectCalls++;
lastProcessMs = processMs;
viewModel.onFramesAnalyzed(
results,
rotation,
width,
height,
motion,
novelty,
detectCalls: detectCalls,
detectErrors: detectErrors,
lastError: lastError,
framesReceived: framesReceived,
lastProcessMs: lastProcessMs,
yuvDiag: yuvDiag,
);
}
void _onError(String msg) {
detectErrors++;
lastError = msg;
viewModel.onFramesAnalyzed(
const [],
90,
0,
0,
const [],
const [],
detectCalls: detectCalls,
detectErrors: detectErrors,
lastError: lastError,
framesReceived: framesReceived,
lastProcessMs: lastProcessMs,
);
}
/// 节流与 busy 丢帧判定(两入口共用);通过后才允许投递
bool _canSend() {
framesReceived++;
final w = worker;
if (w == null) return false;
final now = DateTime.now().millisecondsSinceEpoch;
if (now - _lastDetectMs < intervalMs) return false;
_lastDetectMs = now;
if (w.busy) return false; // 上一帧未返回则丢帧,避免在途积压
return true;
}
void analyze(CameraImage image, int rotationDegrees,
{bool rgbaOrder = false}) {
if (!_canSend()) return;
worker!.analyze(image, rotationDegrees, rgbaOrder: rgbaOrder);
}
/// 原生相机通道帧(Android):字节已在 Kotlin 侧旋转成竖屏,rotation=0。
/// isBgra=true + rgbaOrder 与插件路径同语义:false=BGRA(rOff=2)/true=RGBA(rOff=0)
void analyzeRaw({
required List<Uint8List> planes,
required List<int> strides,
required int width,
required int height,
required bool isBgra,
required bool rgbaOrder,
int rotationDegrees = 0,
}) {
if (!_canSend()) return;
worker!.analyzeRaw(
planes: planes,
strides: strides,
width: width,
height: height,
isBgra: isBgra,
rgbaOrder: rgbaOrder,
rotationDegrees: rotationDegrees,
);
}
void reset() => _lastDetectMs = 0;
void dispose() => worker?.dispose();
}