fix(ios): 修复iOS摄像头对焦异常,优化多摄选择逻辑

1. 重构iOS原生对焦插件,支持按摄像头名称精准匹配设备
2. 优化摄像头选择逻辑,优先选用后置主摄并排除长焦/超广角镜头
3. 修复连续自动对焦失效问题,增加延迟初始化和定期校验
4. 添加调试日志便于排查摄像头相关问题
This commit is contained in:
“虞渠成” 2026-07-23 14:56:53 +08:00
parent 2acd61f832
commit 5c21080203
2 changed files with 96 additions and 34 deletions

View File

@ -1,23 +1,64 @@
#import <AVFoundation/AVFoundation.h> #import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
/// 通过名称查找摄像头设备
static AVCaptureDevice *FindCameraByName(const char *cameraName)
{
if (cameraName == NULL || strlen(cameraName) == 0) {
return [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
}
NSString *name = [NSString stringWithUTF8String:cameraName];
// iOS 10+ DiscoverySession
if (@available(iOS 10.0, *)) {
AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession
discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera,
AVCaptureDeviceTypeBuiltInTelephotoCamera,
AVCaptureDeviceTypeBuiltInUltraWideCamera]
mediaType:AVMediaTypeVideo
position:AVCaptureDevicePositionUnspecified];
for (AVCaptureDevice *device in session.devices) {
if ([device.localizedName containsString:name] ||
[device.modelID containsString:name]) {
return device;
}
}
// 没找到精确匹配,返回第一个活跃的
for (AVCaptureDevice *device in session.devices) {
if (device.isConnected && !device.isSuspended) {
return device;
}
}
}
return [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
}
extern "C" { extern "C" {
/// 启用连续自动对焦和自动曝光 /// 启用连续自动对焦和自动曝光(传入 C# 选中的摄像头名称)
void _EnableContinuousAutoFocus() void _EnableContinuousAutoFocus(const char *cameraName)
{ {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; AVCaptureDevice *device = FindCameraByName(cameraName);
if (!device) return; if (!device) {
NSLog(@"[CameraFocusHelper] 未找到摄像头设备");
return;
}
NSLog(@"[CameraFocusHelper] 配置设备: %@ (name=%@, model=%@)",
device.localizedName, device.localizedName, device.modelID);
NSError *error = nil; NSError *error = nil;
// 锁定设备开始配置
if ([device lockForConfiguration:&error]) { if ([device lockForConfiguration:&error]) {
// 连续自动对焦模式(持续调整,适合扫描二维码) // 连续自动对焦
if ([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) { if ([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
device.focusMode = AVCaptureFocusModeContinuousAutoFocus; device.focusMode = AVCaptureFocusModeContinuousAutoFocus;
NSLog(@"[CameraFocusHelper] 已设为连续自动对焦");
} else {
NSLog(@"[CameraFocusHelper] 设备不支持连续自动对焦");
} }
// 连续自动曝光 // 连续自动曝光
@ -25,36 +66,34 @@ extern "C" {
device.exposureMode = AVCaptureExposureModeContinuousAutoExposure; device.exposureMode = AVCaptureExposureModeContinuousAutoExposure;
} }
// 对焦范围限制为近距(适合二维码) // 对焦范围限制为近距
if ([device respondsToSelector:@selector(setAutoFocusRangeRestriction:)]) { if ([device respondsToSelector:@selector(isAutoFocusRangeRestrictionSupported)] &&
if ([device isAutoFocusRangeRestrictionSupported]) { [device isAutoFocusRangeRestrictionSupported]) {
device.autoFocusRangeRestriction = AVCaptureAutoFocusRangeRestrictionNear; device.autoFocusRangeRestriction = AVCaptureAutoFocusRangeRestrictionNear;
} NSLog(@"[CameraFocusHelper] 已设对焦范围为近距");
} }
[device unlockForConfiguration]; [device unlockForConfiguration];
NSLog(@"[CameraFocusHelper] 摄像头配置完成");
NSLog(@"[CameraFocusHelper] 已启用连续自动对焦");
} else { } else {
NSLog(@"[CameraFocusHelper] 锁定设备失败: %@", error.localizedDescription); NSLog(@"[CameraFocusHelper] lockForConfiguration 失败: %@", error.localizedDescription);
} }
}); });
} }
/// 触发一次自动对焦(用于手动点按时调用 /// 确保连续自动对焦仍在生效(传入 C# 选中的摄像头名称
void _TriggerAutoFocus() void _EnsureContinuousAutoFocus(const char *cameraName)
{ {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; AVCaptureDevice *device = FindCameraByName(cameraName);
if (!device) return; if (!device) return;
NSError *error = nil; NSError *error = nil;
if ([device lockForConfiguration:&error]) { if ([device lockForConfiguration:&error]) {
if ([device isFocusModeSupported:AVCaptureFocusModeAutoFocus]) { if (device.focusMode != AVCaptureFocusModeContinuousAutoFocus &&
device.focusMode = AVCaptureFocusModeAutoFocus; [device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
} device.focusMode = AVCaptureFocusModeContinuousAutoFocus;
if ([device isExposureModeSupported:AVCaptureExposureModeAutoExpose]) { NSLog(@"[CameraFocusHelper] 重新设回连续自动对焦");
device.exposureMode = AVCaptureExposureModeAutoExpose;
} }
[device unlockForConfiguration]; [device unlockForConfiguration];
} }

View File

@ -45,10 +45,10 @@ namespace Kill.UI.Pages
#if UNITY_IOS && !UNITY_EDITOR #if UNITY_IOS && !UNITY_EDITOR
[DllImport("__Internal")] [DllImport("__Internal")]
private static extern void _EnableContinuousAutoFocus(); private static extern void _EnableContinuousAutoFocus(string cameraName);
[DllImport("__Internal")] [DllImport("__Internal")]
private static extern void _TriggerAutoFocus(); private static extern void _EnsureContinuousAutoFocus(string cameraName);
#endif #endif
// 摄像头相关 // 摄像头相关
private WebCamTexture webCamTexture; private WebCamTexture webCamTexture;
@ -59,6 +59,9 @@ namespace Kill.UI.Pages
// 扫描结果回调 // 扫描结果回调
public event Action<string> OnQRCodeScanned; public event Action<string> OnQRCodeScanned;
// iOS 摄像头名称(供原生对焦插件使用)
private string selectedCameraName;
void OnEnable() void OnEnable()
{ {
StartScan(); StartScan();
@ -148,17 +151,32 @@ namespace Kill.UI.Pages
yield break; yield break;
} }
// 优先使用后置摄像头,并选择支持自动对焦的摄像头 // 优先使用后置主摄广角iOS 多摄设备排除超广角/长焦
string deviceName = devices[0].name; string deviceName = devices[0].name;
currentDevice = devices[0];
for (int i = 0; i < devices.Length; i++) for (int i = 0; i < devices.Length; i++)
{ {
if (!devices[i].isFrontFacing) if (!devices[i].isFrontFacing)
{ {
deviceName = devices[i].name; string lowerName = devices[i].name.ToLower();
currentDevice = devices[i]; // iOS 多摄命名: "Back Camera", "Telephoto Camera", "Ultra Wide Camera"
break; // 优先选不带 telephoto/ultra wide 的后置摄像头(即主摄)
if (!lowerName.Contains("telephoto") && !lowerName.Contains("ultra wide"))
{
deviceName = devices[i].name;
currentDevice = devices[i];
break;
}
// 兜底:第一个后置
if (deviceName == devices[0].name)
{
deviceName = devices[i].name;
currentDevice = devices[i];
}
} }
} }
Debug.Log($"[ScanQRcode] 选中摄像头: {deviceName}");
selectedCameraName = deviceName;
RectTransform cameraPreviewRect=cameraPreview.GetComponent<RectTransform>(); RectTransform cameraPreviewRect=cameraPreview.GetComponent<RectTransform>();
// 使用较低的扫描分辨率以提高性能 // 使用较低的扫描分辨率以提高性能
int targetWidth = 2160; int targetWidth = 2160;
@ -172,11 +190,13 @@ namespace Kill.UI.Pages
// 等待摄像头启动 // 等待摄像头启动
yield return new WaitUntil(() => webCamTexture.width > 100); yield return new WaitUntil(() => webCamTexture.width > 100);
// iOS: 调用原生插件强制开启连续自动对焦 // iOS: 延迟后调用原生插件强制开启连续自动对焦
// (等待 Unity 内部 AVCaptureSession 完全就绪)
#if UNITY_IOS && !UNITY_EDITOR #if UNITY_IOS && !UNITY_EDITOR
if (enableAutoFocus) if (enableAutoFocus)
{ {
_EnableContinuousAutoFocus(); yield return new WaitForSeconds(1.5f);
_EnableContinuousAutoFocus(deviceName);
} }
#endif #endif
// 调整预览画面比例 // 调整预览画面比例
@ -287,7 +307,7 @@ namespace Kill.UI.Pages
/// <summary> /// <summary>
/// 自动对焦维护协程 /// 自动对焦维护协程
/// iOS: 走原生插件(连续自动对焦已启用,只需定期触发即可) /// iOS: 走原生插件周期确保连续对焦模式不被重置
/// 其他平台: 使用 WebCamTexture.autoFocusPoint 定期设置对焦点 /// 其他平台: 使用 WebCamTexture.autoFocusPoint 定期设置对焦点
/// </summary> /// </summary>
private IEnumerator AutoFocusCoroutine() private IEnumerator AutoFocusCoroutine()
@ -295,8 +315,11 @@ namespace Kill.UI.Pages
while (isScanning) while (isScanning)
{ {
#if UNITY_IOS && !UNITY_EDITOR #if UNITY_IOS && !UNITY_EDITOR
// iOS 通过原生插件触发一次自动对焦 // iOS 通过原生插件确保连续自动对焦仍在生效
_TriggerAutoFocus(); if (!string.IsNullOrEmpty(selectedCameraName))
{
_EnsureContinuousAutoFocus(selectedCameraName);
}
#else #else
// Android/其他: 定期重置对焦点到画面中心 // Android/其他: 定期重置对焦点到画面中心
if (webCamTexture != null && webCamTexture.isPlaying) if (webCamTexture != null && webCamTexture.isPlaying)