import 'dart:async'; import 'dart:math' as math; import 'package:sensors_plus/sensors_plus.dart'; /// 陀螺仪全局运动追踪(主 isolate):对角速度按 dt 积分得到两次运动分析帧 /// 之间的累计转角,折算为缩略图像素位移,供 MotionDetector 做全局运动补偿—— /// 补偿后差分 = 目标独立运动(手持/走动的旋转分量被抵消)。 /// /// 符号约定(背部相机、竖屏、假设无镜像):绕设备竖轴(y)偏航 → 画面水平位移; /// 绕设备横轴(x)俯仰 → 画面垂直位移。kPanSign/kTiltSign 待真机校准: /// 若补偿后误报反而变多(差分被放大),把对应符号取反即可。 /// 位移超限(缩略图短边 1/4)时由消费方丢弃该帧——运动过快时像素级补偿不可靠。 class GyroTracker { static const double assumedVfovDeg = 52; // 与测距口径一致 static const double kPanSign = 1; static const double kTiltSign = 1; StreamSubscription? _gyroSub; StreamSubscription? _accSub; double _panRad = 0; // 两次 takeShift 之间的累计偏航 double _tiltRad = 0; // 累计俯仰 DateTime? _lastGyroMs; double _accLowZ = 9.8; // 加速度计低通(重力在设备 z 轴分量) double _accLowY = 0; // 低通 y 轴分量(算俯仰用) bool _hasAcc = false; /// 当前分析窗口内的角速度峰值(|x|+|y|+|z|,rad/s):走动/车载时显著抬升。 /// takeShift 消费时清零(按分析窗口计量),供曝光联动等判定 double _recentOmega = 0; bool _running = false; bool get hasData => _hasGyro; bool _hasGyro = false; /// 角速度峰值超阈值(rad/s)→ 机位正在明显运动(手持快走/车载) bool get recentlyMoving => _recentOmega > 0.5; void start() { if (_running) return; _running = true; _gyroSub = gyroscopeEventStream().listen((e) { final now = DateTime.now(); final last = _lastGyroMs; if (last != null) { final dt = (now.millisecondsSinceEpoch - last.millisecondsSinceEpoch) / 1000.0; if (dt > 0 && dt < 0.5) { _panRad += e.y * dt; _tiltRad += e.x * dt; _hasGyro = true; final mag = e.x.abs() + e.y.abs() + e.z.abs(); if (mag > _recentOmega) _recentOmega = mag; } } _lastGyroMs = now; }); _accSub = accelerometerEventStream().listen((e) { // 低通估重力方向 → 俯仰角(镜头朝上为正) const a = 0.1; _accLowZ = _accLowZ * (1 - a) + e.z * a; _accLowY = _accLowY * (1 - a) + e.y * a; _hasAcc = true; }); } void stop() { _running = false; _gyroSub?.cancel(); _accSub?.cancel(); _gyroSub = null; _accSub = null; } /// 消费累计转角 → 缩略图像素位移 (dx, dy)。 /// dx>0 表示画面内容向右移动(前帧采样点左移补偿)。 (int, int) takeShift(double thumbW, double thumbH) { final pan = _panRad; final tilt = _tiltRad; _panRad = 0; _tiltRad = 0; _recentOmega = 0; if (!_hasGyro || (pan == 0 && tilt == 0)) return (0, 0); final focalPx = (thumbH / 2) / math.tan(assumedVfovDeg * math.pi / 180 / 2); final limit = math.max(thumbW, thumbH) / 4; final dx = (kPanSign * pan * focalPx).clamp(-limit, limit).round(); final dy = (kTiltSign * tilt * focalPx).clamp(-limit, limit).round(); return (dx, dy); } /// 俯仰角(度):0=平举,正=镜头朝上。无加速度计数据返回 0。 double get pitchDeg { if (!_hasAcc) return 0; return math.atan2(_accLowY, _accLowZ) * 180 / math.pi; } /// 角速度是否持续偏大(走动/车载判定,供曝光联动等使用) bool get moving => recentlyMoving; }