Files
aisee/aisee_flutter/lib/services/camera_service.dart
2026-03-02 22:57:35 +08:00

159 lines
4.0 KiB
Dart
Raw Permalink 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:async';
import 'dart:typed_data';
import 'package:camera/camera.dart';
import 'package:flutter/foundation.dart';
import 'package:image/image.dart' as img;
import '../utils/app_config.dart';
class CameraService extends ChangeNotifier {
CameraController? _controller;
List<CameraDescription> _cameras = [];
bool _isInitialized = false;
bool _isStreaming = false;
Timer? _captureTimer;
CameraController? get controller => _controller;
bool get isInitialized => _isInitialized;
bool get isStreaming => _isStreaming;
/// 初始化相机
Future<void> initialize() async {
try {
_cameras = await availableCameras();
if (_cameras.isEmpty) {
print('没有可用的相机');
return;
}
// 使用后置摄像头
final camera = _cameras.firstWhere(
(camera) => camera.lensDirection == CameraLensDirection.back,
orElse: () => _cameras.first,
);
_controller = CameraController(
camera,
ResolutionPreset.medium, // 中等分辨率,平衡质量和性能
enableAudio: false,
imageFormatGroup: ImageFormatGroup.jpeg,
);
await _controller!.initialize();
_isInitialized = true;
notifyListeners();
print('相机初始化成功');
} catch (e) {
print('相机初始化失败: $e');
_isInitialized = false;
}
}
/// 拍摄单张照片
Future<Uint8List?> takePicture() async {
if (!_isInitialized || _controller == null) return null;
try {
final XFile image = await _controller!.takePicture();
final bytes = await image.readAsBytes();
// 压缩图像
final compressedBytes = await _compressImage(bytes);
return compressedBytes;
} catch (e) {
print('拍照失败: $e');
return null;
}
}
/// 开始实时捕获(定时拍照)
void startStreaming(Function(Uint8List) onImageCaptured) {
if (_isStreaming) return;
_isStreaming = true;
notifyListeners();
_captureTimer = Timer.periodic(AppConfig.captureInterval, (timer) async {
if (!_isStreaming) {
timer.cancel();
return;
}
final imageBytes = await takePicture();
if (imageBytes != null) {
onImageCaptured(imageBytes);
}
});
print('开始实时捕获,间隔: ${AppConfig.captureInterval.inMilliseconds}ms');
}
/// 停止实时捕获
void stopStreaming() {
_isStreaming = false;
_captureTimer?.cancel();
_captureTimer = null;
notifyListeners();
print('停止实时捕获');
}
/// 压缩图像
Future<Uint8List> _compressImage(Uint8List bytes) async {
return await compute(_compressImageIsolate, bytes);
}
/// 在独立 Isolate 中压缩图像(避免阻塞 UI
static Uint8List _compressImageIsolate(Uint8List bytes) {
// 解码图像
img.Image? image = img.decodeImage(bytes);
if (image == null) return bytes;
// 调整大小
if (image.width > AppConfig.imageMaxWidth ||
image.height > AppConfig.imageMaxHeight) {
image = img.copyResize(
image,
width: AppConfig.imageMaxWidth,
height: AppConfig.imageMaxHeight,
);
}
// 压缩为 JPEG
final compressed = img.encodeJpg(image, quality: AppConfig.imageQuality);
print('图像压缩: ${bytes.length} bytes -> ${compressed.length} bytes');
return Uint8List.fromList(compressed);
}
/// 切换摄像头
Future<void> switchCamera() async {
if (_cameras.length < 2) return;
final currentLens = _controller?.description.lensDirection;
final newCamera = _cameras.firstWhere(
(camera) => camera.lensDirection != currentLens,
orElse: () => _cameras.first,
);
await _controller?.dispose();
_controller = CameraController(
newCamera,
ResolutionPreset.medium,
enableAudio: false,
imageFormatGroup: ImageFormatGroup.jpeg,
);
await _controller!.initialize();
notifyListeners();
}
/// 释放资源
@override
void dispose() {
stopStreaming();
_controller?.dispose();
super.dispose();
}
}