一、背景

iPhone 的大部分机器都提供了后置双摄像头,包含广角和超广角(某些机型上是广角和长焦),Pro 和 Pro Max 机型上提供了后置三摄像头,包含广角、超广角和长焦。

我们目前只用到了广角,了解虚拟摄像头设备挖掘更多的使用场景。

二、简介

1、采集设备类型

iOS 或 macOS 上提供的逻辑摄像头如下:

  • AVCaptureDeviceTypeBuiltInWideAngleCamera:广角摄像头(默认,约 28mm 焦段)。
  • AVCaptureDeviceTypeBuiltInTelephotoCamera:长焦摄像头(2x 或 3x 光学变焦)。
  • AVCaptureDeviceTypeBuiltInUltraWideCamera:超广角摄像头(约 0.5x 视野扩展),iPhone 13 以上机器提供了微距能力
  • AVCaptureDeviceTypeBuiltInDualCamera:双摄像头(广角 + 长焦,如 iPhone X)。
  • AVCaptureDeviceTypeBuiltInDualWideCamera:双摄像头(广角 + 超广角,如 iPhone 15)。
  • AVCaptureDeviceTypeBuiltInTripleCamera:三摄像头(超广角 + 广角 + 长焦,如 iPhone 15 Pro Max)。
  • AVCaptureDeviceTypeBuiltInTrueDepthCamera:前置原深感摄像头(支持 Face ID 的设备)。
  • AVCaptureDeviceTypeBuiltInLiDARDepthCamera:LiDAR 深度摄像头,用于增强现实和测量。
  • AVCaptureDeviceTypeContinuityCamera:连续互通相机,用于跨设备视频功能。
  • AVCaptureDeviceTypeDeskViewCamera:从超广角摄像头上剪下来的经过畸变校正的图像,使其近似于指向桌子的架空摄像机,用于演示和直播(只支持 macOS)。

以上类型只能通过 AVCaptureDeviceDiscoverySession 使用。

2、虚拟摄像头设备

虚拟摄像头设备(Virtual Device)提供了一种抽象层,代表一个包含多个物理摄像头的虚拟摄像头设备,使得开发者可以通过一个单一的 AVCaptureDevice 来访问和控制多个物理摄像头。

当在操作虚拟设备时,iOS 会在 constituentDevices 之间自动切换,以提供一致的用户体验。

iPhone 15 Pro 里,以下设备都属于虚拟设备:

  • 前置原深感摄像头
  • 后置双镜头
  • 后置双广角镜头
  • 后置三镜头
  • 后置激光雷达景深相机

下面主要讨论后置双镜头后置双广角镜头后置三镜头

自动切换需要具备的条件是:变焦系数、光照水平、焦点位置在系统要求的范围内。

变焦系数

系统会根据当前的变焦系数自动选择最适合的摄像头以保持最佳的图像质量。

假如在使用 iPhone 15 Pro(带有广角、超广角和长焦摄像头),从 1x 变焦逐渐增加到 3x 变焦时,系统可能会从广角摄像头切换到长焦摄像头,因为长焦摄像头更适合拍摄远距离对象而不会降低图像质量。

光照水平

系统会根据当前环境光照水平决定使用哪一个摄像头。

在较暗的环境中,系统可能会选择使用广角摄像头而非长焦摄像头,因为广角摄像头通常具有更大的光圈和更好的低光性能。

焦点位置

系统会根据焦点位置判断哪个摄像头能够更好地对焦并提供最佳图像质量。

当焦点位置接近镜头,系统可能会选择使用超广角摄像头以获得更好的微距能力;如果焦点位置远离镜头,系统可能会切换到长焦摄像头以捕捉更远处的细节而不会丢失画面细节。

三、应用场景

1、场景

  1. 提供光学变焦能力;
  2. 提供微距能力;
  3. 提供更好的对焦能力(解决在 iPhone 15 系列 PC 直播扫码不聚焦的问题);

2、竞品的情况

image

在 iPhone 11 上通过 hook 抖音,可以看到切换到后置相机时,使用的是 AVCaptureDeviceTypeBuiltInDualWideCamera。

缩放系数设置 1.0 后马上设置为 2.0(这里用的是超广角和广角组合的双摄像头设备,所以相当于是设置了 0.5x 的缩放系数,然后再设置 1x 的缩放系数)。

四、系统接口

// 指示设备是否为由多个物理设备组成的虚拟设备。
@property(nonatomic, readonly, getter=isVirtualDevice) BOOL virtualDevice;

// 构成虚拟设备的物理摄像头数组。如果设备不是虚拟设备,则返回空数组。
@property(nonatomic, readonly) NSArray<AVCaptureDevice *> *constituentDevices;

// 虚拟设备在变焦到某一倍数或更高时可能切换到下一个物理设备的变焦系数数组。非虚拟设备返回空数组。
@property(nonatomic, readonly) NSArray<NSNumber *> *virtualDeviceSwitchOverVideoZoomFactors;

// 设置摄像头切换行为和限制条件。
- (void)setPrimaryConstituentDeviceSwitchingBehavior:(AVCapturePrimaryConstituentDeviceSwitchingBehavior)switchingBehavior restrictedSwitchingBehaviorConditions:(AVCapturePrimaryConstituentDeviceRestrictedSwitchingBehaviorConditions)restrictedSwitchingBehaviorConditions;

// 设备的当前摄像头切换行为。支持键值观察。
@property(nonatomic, readonly) AVCapturePrimaryConstituentDeviceSwitchingBehavior primaryConstituentDeviceSwitchingBehavior;

// 当前摄像头切换行为的限制条件。默认设置为无条件限制,支持键值观察。
@property(nonatomic, readonly) AVCapturePrimaryConstituentDeviceRestrictedSwitchingBehaviorConditions primaryConstituentDeviceRestrictedSwitchingBehaviorConditions;

// 当前激活的摄像头切换行为。对于非虚拟设备,返回“不支持”。支持键值观察。
@property(nonatomic, readonly) AVCapturePrimaryConstituentDeviceSwitchingBehavior activePrimaryConstituentDeviceSwitchingBehavior;

// 当前激活的摄像头切换限制条件。对于非虚拟设备,返回“无条件限制”。支持键值观察。
@property(nonatomic, readonly) AVCapturePrimaryConstituentDeviceRestrictedSwitchingBehaviorConditions activePrimaryConstituentDeviceRestrictedSwitchingBehaviorConditions;

// 当前激活的主要摄像头设备。非虚拟设备或未运行的虚拟设备返回 nil。支持键值观察。
@property(nonatomic, readonly, nullable) AVCaptureDevice *activePrimaryConstituentDevice;

// 可作为主要摄像头设备的备用摄像头数组。
@property(nonatomic, readonly) NSArray<AVCaptureDevice *> *supportedFallbackPrimaryConstituentDevices;

// 当主要摄像头设备受限时,可用作备用的摄像头设备数组。
@property(nonatomic) NSArray<AVCaptureDevice *> *fallbackPrimaryConstituentDevices;

五、代码修改

1、设备选择策略

现有的逻辑会把几乎所有类型的设备都获取出来,但是真正打开相机时只会选择第一个设备,即只会使用广角镜头。而在双摄场景中,则只会获取广角镜头。

精简获取设备的类型,优化设备获取及选择策略。

选择策略:优先选择使用虚拟摄像头设备,没有虚拟设备时使用广角摄像头作为兜底设备。 虚拟摄像头设备优先级:TripleCamera > DualWideCamera > DualCamera, 拥有后置三摄的设备可以获取到 TripleCamera,拥有后置双摄的设备只可能获取 DualWideCamera 或 DualCamera 其中的一个。

2、Zoom 相关改动

a. 缩放系数

image

iPhone 15 Pro 中能获得最大 0.5x 的视野扩展,但是在设置虚拟设备的 videoZoomFactor 时不能直接设置为 0.5,因为即使是包含超广角摄像头的虚拟设备中,最小的缩放系数依然为 1.0。

这里我们需要通过 virtualDeviceSwitchOverVideoZoomFactors 接口获取物理设备的变焦系数数组,对实际设置的缩放系数做一个转换:

if (device.constituentDevices.count > 0) {
	NSArray<NSNumber *> *zoomFactorsNS = device.virtualDeviceSwitchOverVideoZoomFactors;
	NSMutableArray<NSNumber *> *zoomFactors = [NSMutableArray arrayWithObject:@(1)];
	for (NSNumber *zoomFactor in zoomFactorsNS) {
		[zoomFactors addObject:@(zoomFactor.floatValue)];
	}

	NSUInteger mainIndex = [device.constituentDevices indexOfObjectPassingTest:^BOOL(AVCaptureDevice * _Nonnull device, NSUInteger idx, BOOL * _Nonnull stop) {
		return [device.deviceType isEqualToString:AVCaptureDeviceTypeBuiltInWideAngleCamera];
	}];

	float mainZoomFactor = zoomFactors[mainIndex].floatValue;
	NSMutableArray<NSNumber *> *factors = [NSMutableArray arrayWithCapacity:zoomFactors.count];
	for (NSNumber *zoomFactor in zoomFactors) {
		[factors addObject:@(zoomFactor.floatValue / mainZoomFactor)];
	}

	targetFactor = (CGFloat)pam->value * mainZoomFactor;
}

b. 缩放动画

原来只会使用 setVideoZoomFactor: 来设置缩放系数,客户端需要提供按钮直接设置缩放系数,点击按钮时画面缩放需要有动画。

新增系统接口调用:

// 设置缩放动画的速度
- (void)rampToVideoZoomFactor:(CGFloat)factor withRate:(float)rate;
 
// 是否在缩放动画中
@property(nonatomic, readonly, getter=isRampingVideoZoom) BOOL rampingVideoZoom;

3、客户端 Zoom 相关修改

image

客户端目前的后摄的缩放系数写死了范围为 [1, 4],可跟竞品对齐修改为 [0.5, 10],并可提供与竞品类似的按钮直接设置缩放系数,点击按钮时画面缩放需要有动画。

为此,SDK 提供获取物理设备的变焦系数数组

六、其它

1、注意点

虚拟摄像头设备不支持的功能:

  • 自定义曝光模式:该设备不支持 AVCaptureExposureModeCustom 模式,也不支持手动曝光包围功能。这意味着你无法手动控制曝光参数来进行精确的曝光调整。
  • 锁定非当前镜头位置的焦点:设备不支持在镜头位置不为当前状态时锁定焦点。这意味着你无法强制设备在非当前镜头位置时保持对焦锁定。
  • 锁定非当前状态的自动白平衡:设备不支持在白平衡增益不为当前状态时锁定自动白平衡。这限制了在复杂光照条件下进行精确白平衡调整的能力。

以上功能我们都没有使用到,所以不会有影响。

另外,即使在锁定状态下,当设备从一台相机切换到另一台相机时,曝光时间、ISO、光圈、白平衡增益或镜头位置也可能发生变化。

但是系统会尽量保证整体曝光、白平衡和焦点位置是一致的。

2、设置相机参数的影响

帧率

无影响

分辨率

设置采集分辨率后缩放系数会重置为 1.0,这里已处理为设置采集分辨率前的缩放系数

Zoom

已经设置了 Zoom 时,系统自动切换相机后,会维持该缩放系数

EV

EV 设置太高时,系统自动切换相机会变得不灵敏。

这里需要监听场景变化,场景变化后需要重置对焦点(但不重置 EV)

3、性能

在本地测试中,使用虚拟摄像头设备后 CPU、GPU、功耗等指标均无劣化。