killapp/Assets/Plugins/iOS/CameraFocusHelper.mm
“虞渠成” 668dad0e67 feat(iOS): 实现原生二维码扫描功能
重构iOS平台摄像头扫描逻辑,使用AVCaptureSession原生实现二维码扫描,替换原有的WebCamTexture方案:
1. 新增NativeCameraController封装摄像头会话、预览层和二维码检测逻辑
2. 实现摄像头权限申请、自动对焦配置和原生QR码识别
3. 适配C#层调用,通过UnitySendMessage回调扫描结果
4. 移除原iOS平台下冗余的自动对焦维护逻辑
2026-07-23 15:04:51 +08:00

192 lines
6.9 KiB
Plaintext
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 <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
@interface NativeCameraController : NSObject <AVCaptureMetadataOutputObjectsDelegate>
@property (nonatomic, strong) AVCaptureSession *session;
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer;
@property (nonatomic, copy) NSString *gameObjectName;
@property (nonatomic, assign) BOOL hasScanned;
@end
@implementation NativeCameraController
- (void)startWithName:(NSString *)goName
{
self.gameObjectName = goName;
self.hasScanned = NO;
// 1. 创建 session
self.session = [[AVCaptureSession alloc] init];
self.session.sessionPreset = AVCaptureSessionPresetHigh;
// 2. 获取后置摄像头
AVCaptureDevice *device = [self findRearCamera];
if (!device) {
[self sendToUnity:@"ERROR:NO_CAMERA"];
return;
}
NSLog(@"[NativeCamera] 使用摄像头: %@", device.localizedName);
// 3. 配置连续自动对焦
NSError *error = nil;
if ([device lockForConfiguration:&error]) {
if ([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
device.focusMode = AVCaptureFocusModeContinuousAutoFocus;
}
if ([device isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
device.exposureMode = AVCaptureExposureModeContinuousAutoExposure;
}
if ([device respondsToSelector:@selector(isAutoFocusRangeRestrictionSupported)] &&
[device isAutoFocusRangeRestrictionSupported]) {
device.autoFocusRangeRestriction = AVCaptureAutoFocusRangeRestrictionNear;
}
[device unlockForConfiguration];
NSLog(@"[NativeCamera] 已启用连续自动对焦");
} else {
NSLog(@"[NativeCamera] 配置摄像头失败: %@", error.localizedDescription);
}
// 4. 输入
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
[self sendToUnity:[NSString stringWithFormat:@"ERROR:INPUT:%@", error.localizedDescription]];
return;
}
[self.session addInput:input];
// 5. 元数据输出(二维码检测)
AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];
[self.session addOutput:metadataOutput];
[metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
metadataOutput.metadataObjectTypes = @[AVMetadataObjectTypeQRCode];
// 6. 预览层 — 插入到 Unity 视图底层
UIViewController *rootVC = UnityGetGLViewController();
self.previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.session];
self.previewLayer.frame = rootVC.view.bounds;
self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
// 插入到最底层Unity UI 正常渲染在上层
[rootVC.view.layer insertSublayer:self.previewLayer atIndex:0];
// 7. 启动 session
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.session startRunning];
dispatch_async(dispatch_get_main_queue(), ^{
[self sendToUnity:@"CAMERA_READY"];
});
});
}
/// 选择后置主摄(排除超广角/长焦)
- (AVCaptureDevice *)findRearCamera
{
if (@available(iOS 10.0, *)) {
AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession
discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera,
AVCaptureDeviceTypeBuiltInTelephotoCamera,
AVCaptureDeviceTypeBuiltInUltraWideCamera]
mediaType:AVMediaTypeVideo
position:AVCaptureDevicePositionBack];
// 优先宽角(主摄)
for (AVCaptureDevice *d in session.devices) {
if (d.deviceType == AVCaptureDeviceTypeBuiltInWideAngleCamera) {
return d;
}
}
// 兜底:返回第一个
return session.devices.firstObject;
}
return [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
}
- (void)stop
{
self.hasScanned = YES;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.session stopRunning];
});
[self.previewLayer removeFromSuperlayer];
self.previewLayer = nil;
self.session = nil;
}
- (void)sendToUnity:(NSString *)msg
{
if (self.gameObjectName) {
UnitySendMessage([self.gameObjectName UTF8String], "OnNativeScanResult", [msg UTF8String]);
}
}
#pragma mark - AVCaptureMetadataOutputObjectsDelegate
- (void)captureOutput:(AVCaptureOutput *)output
didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects
fromConnection:(AVCaptureConnection *)connection
{
if (self.hasScanned) return;
if (metadataObjects.count == 0) return;
AVMetadataMachineReadableCodeObject *code = metadataObjects.firstObject;
if (![code isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) return;
if (!code.stringValue) return;
self.hasScanned = YES;
NSLog(@"[NativeCamera] 扫描到二维码: %@", code.stringValue);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.session stopRunning];
});
[self sendToUnity:code.stringValue];
}
@end
// ──── 单例管理 ────
static NativeCameraController *g_cameraController = nil;
extern "C" {
void _StartNativeCamera(const char *gameObjectName)
{
if (g_cameraController) {
[g_cameraController stop];
g_cameraController = nil;
}
// 权限请求
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if (status == AVAuthorizationStatusNotDetermined) {
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
if (granted) {
g_cameraController = [[NativeCameraController alloc] init];
NSString *goName = [NSString stringWithUTF8String:gameObjectName];
[g_cameraController startWithName:goName];
} else {
UnitySendMessage((char *)gameObjectName, "OnNativeScanResult", "ERROR:PERMISSION_DENIED");
}
}];
return;
}
if (status != AVAuthorizationStatusAuthorized) {
UnitySendMessage((char *)gameObjectName, "OnNativeScanResult", "ERROR:PERMISSION_DENIED");
return;
}
g_cameraController = [[NativeCameraController alloc] init];
NSString *goName = [NSString stringWithUTF8String:gameObjectName];
[g_cameraController startWithName:goName];
}
void _StopNativeCamera()
{
if (g_cameraController) {
[g_cameraController stop];
g_cameraController = nil;
}
}
}