Camera

使用AVCaptureSession调用系统的摄像头、麦克风来捕获视频、音频,包括二维码解析。

AVCaptureSession

An object that manages capture activity and coordinates the flow of data from input devices to capture outputs.

sessionPreset

A constant value indicating the quality level or bitrate of the output.

Symbol Resolution Comments
AVCaptureSessionPresetHigh High Highest recording quality.
This varies per device.
AVCaptureSessionPresetMedium Medium Suitable for Wi-Fi sharing.
The actual values may change.
AVCaptureSessionPresetLow Low Suitable for 3G sharing.
The actual values may change.
AVCaptureSessionPreset640x480 640x480 VGA.
AVCaptureSessionPreset1280x720 1280x720 720p HD.
AVCaptureSessionPresetPhoto Photo Full photo resolution.
This is not supported for video output.

beginConfiguration && commitConfiguration

在对已经运行的session进行配置修改都需要放在中间,最后会调用commitConfiguration来使配置生效。

1
2
3
4
5
session.beginConfiguration()
// Remove an existing capture device.
// Add a new capture device.
// Reset the preset.
session.commitConfiguration()

如果session还没有运行,可以直接配置。

startRunning && stopRunning

在调用之前我们可以用isRunning来判断session有没有运行。

调用会阻塞当前线程,需要异步到其他线程中执行。

AVCaptureDevice

A device that provides input (such as audio or video) for capture sessions and offers controls for hardware-specific capture features.

这里我觉得写成下面获取视频的Device比较方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/// Camera position.
public enum CameraPosition {

case back, front

/// Is mirror
public var isMirror: Bool {
switch self {
case .front: return true
case .back: return false
}
}

/// Convert to system AVCaptureDevicePosition
public var position: AVCaptureDevicePosition {
switch self {
case .back: return .back
case .front: return .front
}
}

/// Device for current camera position, else return defaultDevice.
public var device: AVCaptureDevice {
let devices = AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo)

for case let device as AVCaptureDevice in devices! {
if (device.position == position) {
return device
}
}

return AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
}

/// Convenience init by AVCaptureDevicePosition
///
/// - Parameter position: AVCaptureDevicePosition
public init(position: AVCaptureDevicePosition) {
switch position {
case .back: self = .back
case .front, .unspecified: self = .front
}
}

/// Convenience rotate camera position like this: `newCameraPosition = !oldCameraPosition`
///
/// - Parameter origin: Origin camera position.
/// - Returns: Rotated camera position.
public static prefix func !(_ origin: CameraPosition) -> CameraPosition {
return origin == .front ? .back : .front
}
}

iPhone 8 Plus输出以下结果。

lockForConfiguration && unlockForConfiguration

Before you attempt to set properties of a capture device (its focus mode, exposure mode, and so on), you must first acquire a lock on the device using the lockForConfiguration() method. You should also query the device’s capabilities to ensure that the new modes you intend to set are valid for that device. You can then set the properties and release the lock using the unlockForConfiguration() method. You may hold the lock if you want all settable device properties to remain unchanged. However, holding the device lock unnecessarily may degrade capture quality in other applications sharing the device and is not recommended.

formats

The capture formats supported by the device.

An AVCaptureDevice.Format object describes in detail the video, image, or audio parameters of a specific mode of capture. If you need access to capture settings not covered by an AVCaptureSession preset, you can set the activeFormat property to any of the formats in this array.

下面是iPhone 8 Plus后置摄像头输出结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
(lldb) po device
<AVCaptureFigVideoDevice: 0x106c05d80 [Back Camera][com.apple.avfoundation.avcapturedevice.built-in_video:0]>

(lldb) po device.formats
▿ 40 elements
- 0 : <AVCaptureDeviceFormat: 0x1d000ea30 'vide'/'420v' 192x 144, { 3- 60 fps}, HRSI:4032x3024, fov:63.301, max zoom:189.00 (upscales @21.00), AF System:2, ISO:22.0-1760.0, SS:0.000020-0.333333>
- 1 : <AVCaptureDeviceFormat: 0x1d000ea20 'vide'/'420f' 192x 144, { 3- 60 fps}, HRSI:4032x3024, fov:63.301, max zoom:189.00 (upscales @21.00), AF System:2, ISO:22.0-1760.0, SS:0.000020-0.333333, supports wide color>
- 2 : <AVCaptureDeviceFormat: 0x1d000ea10 'vide'/'420v' 352x 288, { 3- 60 fps}, HRSI:3696x3024, fov:58.026, max zoom:189.00 (upscales @10.50), AF System:2, ISO:22.0-1760.0, SS:0.000020-0.333333>
- 3 : <AVCaptureDeviceFormat: 0x1d000ea00 'vide'/'420f' 352x 288, { 3- 60 fps}, HRSI:3696x3024, fov:58.026, max zoom:189.00 (upscales @10.50), AF System:2, ISO:22.0-1760.0, SS:0.000020-0.333333, supports wide color>
- 4 : <AVCaptureDeviceFormat: 0x1d000e9f0 'vide'/'420v' 480x 360, { 3- 60 fps}, HRSI:4032x3024, fov:63.301, max zoom:189.00 (upscales @8.40), AF System:2, ISO:22.0-1760.0, SS:0.000020-0.333333>
- 5 : <AVCaptureDeviceFormat: 0x1d000e9e0 'vide'/'420f' 480x 360, { 3- 60 fps}, HRSI:4032x3024, fov:63.301, max zoom:189.00 (upscales @8.40), AF System:2, ISO:22.0-1760.0, SS:0.000020-0.333333, supports wide color>
- 6 : <AVCaptureDeviceFormat: 0x1d000e9d0 'vide'/'420v' 640x 480, { 3- 60 fps}, HRSI:4032x3024, fov:63.301, max zoom:189.00 (upscales @6.30), AF System:2, ISO:22.0-1760.0, SS:0.000020-0.333333>
- 7 : <AVCaptureDeviceFormat: 0x1d000e9c0 'vide'/'420f' 640x 480, { 3- 60 fps}, HRSI:4032x3024, fov:63.301, max zoom:189.00 (upscales @6.30), AF System:2, ISO:22.0-1760.0, SS:0.000020-0.333333, supports wide color>
- 8 : <AVCaptureDeviceFormat: 0x1d000e9b0 'vide'/'420v' 640x 480, { 6- 60 fps}, HRSI:2016x1512, fov:60.400, binned, max zoom:94.50 (upscales @3.15), AF System:1, ISO:22.0-1760.0, SS:0.000011-0.166666>
- 9 : <AVCaptureDeviceFormat: 0x1d000e9a0 'vide'/'420f' 640x 480, { 6- 60 fps}, HRSI:2016x1512, fov:60.400, binned, max zoom:94.50 (upscales @3.15), AF System:1, ISO:22.0-1760.0, SS:0.000011-0.166666, supports wide color>
- 10 : <AVCaptureDeviceFormat: 0x1d000e990 'vide'/'420v' 960x 540, { 3- 60 fps}, HRSI:4224x2376, fov:65.707, supports vis, max zoom:135.00 (upscales @4.00), AF System:2, ISO:22.0-880.0, SS:0.000020-0.333333>
- 11 : <AVCaptureDeviceFormat: 0x1d000e980 'vide'/'420f' 960x 540, { 3- 60 fps}, HRSI:4224x2376, fov:65.707, supports vis, max zoom:135.00 (upscales @4.00), AF System:2, ISO:22.0-880.0, SS:0.000020-0.333333, supports wide color>
- 12 : <AVCaptureDeviceFormat: 0x1d000e970 'vide'/'420v' 1280x 720, { 3- 30 fps}, HRSI:4224x2376, fov:65.707, supports vis, max zoom:24.00 (upscales @3.00), AF System:2, ISO:22.0-880.0, SS:0.000020-0.333333>
- 13 : <AVCaptureDeviceFormat: 0x1d000e960 'vide'/'420f' 1280x 720, { 3- 30 fps}, HRSI:4224x2376, fov:65.707, supports vis, max zoom:24.00 (upscales @3.00), AF System:2, ISO:22.0-880.0, SS:0.000020-0.333333, supports wide color>
- 14 : <AVCaptureDeviceFormat: 0x1d000e950 'vide'/'420v' 1280x 720, { 3- 60 fps}, HRSI:4224x2376, fov:65.707, supports vis, max zoom:24.00 (upscales @3.00), AF System:2, ISO:22.0-880.0, SS:0.000020-0.333333>
- 15 : <AVCaptureDeviceFormat: 0x1d000e940 'vide'/'420f' 1280x 720, { 3- 60 fps}, HRSI:4224x2376, fov:65.707, supports vis, max zoom:24.00 (upscales @3.00), AF System:2, ISO:22.0-880.0, SS:0.000020-0.333333, supports wide color>
- 16 : <AVCaptureDeviceFormat: 0x1d000e930 'vide'/'420v' 1280x 720, { 6- 60 fps}, fov:65.707, binned, supports vis, max zoom:61.88 (upscales @1.50), AF System:1, ISO:22.0-880.0, SS:0.000011-0.166666>
- 17 : <AVCaptureDeviceFormat: 0x1d000e920 'vide'/'420f' 1280x 720, { 6- 60 fps}, fov:65.707, binned, supports vis, max zoom:61.88 (upscales @1.50), AF System:1, ISO:22.0-880.0, SS:0.000011-0.166666, supports wide color>
- 18 : <AVCaptureDeviceFormat: 0x1d000e910 'vide'/'420v' 1280x 720, { 6-240 fps}, fov:65.707, binned, supports vis, max zoom:67.50 (upscales @1.50), AF System:1, ISO:22.0-880.0, SS:0.000011-0.166666>
- 19 : <AVCaptureDeviceFormat: 0x1d000e900 'vide'/'420f' 1280x 720, { 6-240 fps}, fov:65.707, binned, supports vis, max zoom:67.50 (upscales @1.50), AF System:1, ISO:22.0-880.0, SS:0.000011-0.166666, supports wide color>
- 20 : <AVCaptureDeviceFormat: 0x1d000e8f0 'vide'/'420v' 1440x1080, { 6- 60 fps}, HRSI:2016x1512, fov:60.400, binned, max zoom:94.50 (upscales @1.40), AF System:1, ISO:22.0-1760.0, SS:0.000011-0.166666>
- 21 : <AVCaptureDeviceFormat: 0x1d000e8e0 'vide'/'420f' 1440x1080, { 6- 60 fps}, HRSI:2016x1512, fov:60.400, binned, max zoom:94.50 (upscales @1.40), AF System:1, ISO:22.0-1760.0, SS:0.000011-0.166666, supports wide color>
- 22 : <AVCaptureDeviceFormat: 0x1d000e8d0 'vide'/'420v' 1920x1080, { 3- 30 fps}, HRSI:4224x2376, fov:65.707, supports vis, max zoom:16.00 (upscales @2.00), AF System:2, ISO:22.0-880.0, SS:0.000020-0.333333>
- 23 : <AVCaptureDeviceFormat: 0x1d000e8c0 'vide'/'420f' 1920x1080, { 3- 30 fps}, HRSI:4224x2376, fov:65.707, supports vis, max zoom:16.00 (upscales @2.00), AF System:2, ISO:22.0-880.0, SS:0.000020-0.333333, supports wide color>
- 24 : <AVCaptureDeviceFormat: 0x1d000e8b0 'vide'/'420v' 1920x1080, { 3- 60 fps}, HRSI:4224x2376, fov:65.707, supports vis, max zoom:16.00 (upscales @2.00), AF System:2, ISO:22.0-880.0, SS:0.000020-0.333333>
- 25 : <AVCaptureDeviceFormat: 0x1d000e8a0 'vide'/'420f' 1920x1080, { 3- 60 fps}, HRSI:4224x2376, fov:65.707, supports vis, max zoom:16.00 (upscales @2.00), AF System:2, ISO:22.0-880.0, SS:0.000020-0.333333, supports wide color>
- 26 : <AVCaptureDeviceFormat: 0x1d000e890 'vide'/'420v' 1920x1080, { 5-120 fps}, fov:65.707, supports vis, max zoom:135.00 (upscales @2.00), AF System:2, ISO:22.0-880.0, SS:0.000012-0.200000>
- 27 : <AVCaptureDeviceFormat: 0x1d000e880 'vide'/'420f' 1920x1080, { 5-120 fps}, fov:65.707, supports vis, max zoom:135.00 (upscales @2.00), AF System:2, ISO:22.0-880.0, SS:0.000012-0.200000, supports wide color>
- 28 : <AVCaptureDeviceFormat: 0x1d000e870 'vide'/'420v' 1920x1080, { 6-240 fps}, fov:65.707, binned, supports vis, max zoom:67.50 (upscales @1.00), AF System:1, ISO:22.0-880.0, SS:0.000011-0.166666>
- 29 : <AVCaptureDeviceFormat: 0x1d000e860 'vide'/'420f' 1920x1080, { 6-240 fps}, fov:65.707, binned, supports vis, max zoom:67.50 (upscales @1.00), AF System:1, ISO:22.0-880.0, SS:0.000011-0.166666, supports wide color>
- 30 : <AVCaptureDeviceFormat: 0x1d000e850 'vide'/'420v' 2592x1936, { 3- 30 fps}, HRSI:4032x3024, fov:63.301, max zoom:189.00 (upscales @1.56), AF System:2, ISO:22.0-2112.0, SS:0.000020-0.333333>
- 31 : <AVCaptureDeviceFormat: 0x1d000e840 'vide'/'420f' 2592x1936, { 3- 30 fps}, HRSI:4032x3024, fov:63.301, max zoom:189.00 (upscales @1.56), AF System:2, ISO:22.0-2112.0, SS:0.000020-0.333333, supports wide color>
- 32 : <AVCaptureDeviceFormat: 0x1d000e830 'vide'/'420v' 3264x2448, { 3- 30 fps}, HRSI:4032x3024, fov:63.301, max zoom:189.00 (upscales @1.24), AF System:2, ISO:22.0-2112.0, SS:0.000020-0.333333>
- 33 : <AVCaptureDeviceFormat: 0x1d000ead0 'vide'/'420f' 3264x2448, { 3- 30 fps}, HRSI:4032x3024, fov:63.301, max zoom:189.00 (upscales @1.24), AF System:2, ISO:22.0-2112.0, SS:0.000020-0.333333, supports wide color>
- 34 : <AVCaptureDeviceFormat: 0x1d000eae0 'vide'/'420v' 3840x2160, { 3- 30 fps}, fov:65.707, supports vis, max zoom:9.00 (upscales @1.00), AF System:2, ISO:22.0-880.0, SS:0.000020-0.333333>
- 35 : <AVCaptureDeviceFormat: 0x1d000eaf0 'vide'/'420f' 3840x2160, { 3- 30 fps}, fov:65.707, supports vis, max zoom:9.00 (upscales @1.00), AF System:2, ISO:22.0-880.0, SS:0.000020-0.333333, supports wide color>
- 36 : <AVCaptureDeviceFormat: 0x1d000eb00 'vide'/'420v' 3840x2160, { 3- 60 fps}, fov:65.707, supports vis, max zoom:135.00 (upscales @1.00), AF System:2, ISO:22.0-880.0, SS:0.000020-0.333333>
- 37 : <AVCaptureDeviceFormat: 0x1d000eb10 'vide'/'420f' 3840x2160, { 3- 60 fps}, fov:65.707, supports vis, max zoom:135.00 (upscales @1.00), AF System:2, ISO:22.0-880.0, SS:0.000020-0.333333, supports wide color>
- 38 : <AVCaptureDeviceFormat: 0x1d000eb20 'vide'/'420v' 4032x3024, { 3- 30 fps}, HRSI:4032x3024, fov:63.301, max zoom:189.00 (upscales @1.00), AF System:2, ISO:22.0-2112.0, SS:0.000020-0.333333>
- 39 : <AVCaptureDeviceFormat: 0x1d000eb30 'vide'/'420f' 4032x3024, { 3- 30 fps}, HRSI:4032x3024, fov:63.301, max zoom:189.00 (upscales @1.00), AF System:2, ISO:22.0-2112.0, SS:0.000020-0.333333, supports wide color>

下面是iPhone 8 Plus前置摄像头输出结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(lldb) po device
<AVCaptureFigVideoDevice: 0x103e11f30 [Front Camera][com.apple.avfoundation.avcapturedevice.built-in_video:1]>

(lldb) po device.formats
▿ 22 elements
- 0 : <AVCaptureDeviceFormat: 0x1d000ec30 'vide'/'420v' 192x 144, { 2- 30 fps}, HRSI:3088x2320, fov:56.559, max zoom:145.00 (upscales @16.08), ISO:19.0-1824.0, SS:0.000013-0.500000>
- 1 : <AVCaptureDeviceFormat: 0x1d000ec20 'vide'/'420f' 192x 144, { 2- 30 fps}, HRSI:3088x2320, fov:56.559, max zoom:145.00 (upscales @16.08), ISO:19.0-1824.0, SS:0.000013-0.500000, supports wide color>
- 2 : <AVCaptureDeviceFormat: 0x1d000ec10 'vide'/'420v' 352x 288, { 2- 30 fps}, HRSI:2836x2320, fov:51.943, max zoom:145.00 (upscales @8.06), ISO:19.0-1824.0, SS:0.000013-0.500000>
- 3 : <AVCaptureDeviceFormat: 0x1d000ec00 'vide'/'420f' 352x 288, { 2- 30 fps}, HRSI:2836x2320, fov:51.943, max zoom:145.00 (upscales @8.06), ISO:19.0-1824.0, SS:0.000013-0.500000, supports wide color>
- 4 : <AVCaptureDeviceFormat: 0x1d000ebf0 'vide'/'420v' 480x 360, { 2- 30 fps}, HRSI:3088x2320, fov:56.559, max zoom:145.00 (upscales @6.43), ISO:19.0-1824.0, SS:0.000013-0.500000>
- 5 : <AVCaptureDeviceFormat: 0x1d000ebe0 'vide'/'420f' 480x 360, { 2- 30 fps}, HRSI:3088x2320, fov:56.559, max zoom:145.00 (upscales @6.43), ISO:19.0-1824.0, SS:0.000013-0.500000, supports wide color>
- 6 : <AVCaptureDeviceFormat: 0x1d000ebd0 'vide'/'420v' 640x 480, { 2- 30 fps}, HRSI:3088x2320, fov:56.559, max zoom:145.00 (upscales @4.82), ISO:19.0-1824.0, SS:0.000013-0.500000>
- 7 : <AVCaptureDeviceFormat: 0x1d000ebc0 'vide'/'420f' 640x 480, { 2- 30 fps}, HRSI:3088x2320, fov:56.559, max zoom:145.00 (upscales @4.82), ISO:19.0-1824.0, SS:0.000013-0.500000, supports wide color>
- 8 : <AVCaptureDeviceFormat: 0x1d000ebb0 'vide'/'420v' 640x 480, { 2- 60 fps}, HRSI:1440x1080, fov:42.612, binned, max zoom:67.50 (upscales @2.25), ISO:19.0-1824.0, SS:0.000013-0.500000>
- 9 : <AVCaptureDeviceFormat: 0x1d000eba0 'vide'/'420f' 640x 480, { 2- 60 fps}, HRSI:1440x1080, fov:42.612, binned, max zoom:67.50 (upscales @2.25), ISO:19.0-1824.0, SS:0.000013-0.500000, supports wide color>
- 10 : <AVCaptureDeviceFormat: 0x1d000eca0 'vide'/'420v' 960x 540, { 2- 30 fps}, HRSI:3840x2160, fov:67.564, max zoom:135.00 (upscales @4.00), ISO:19.0-1824.0, SS:0.000013-0.500000>
- 11 : <AVCaptureDeviceFormat: 0x1d000ecb0 'vide'/'420f' 960x 540, { 2- 30 fps}, HRSI:3840x2160, fov:67.564, max zoom:135.00 (upscales @4.00), ISO:19.0-1824.0, SS:0.000013-0.500000, supports wide color>
- 12 : <AVCaptureDeviceFormat: 0x1d000ecc0 'vide'/'420v' 1280x 720, { 2- 30 fps}, HRSI:3840x2160, fov:67.564, max zoom:135.00 (upscales @3.00), ISO:19.0-1824.0, SS:0.000013-0.500000>
- 13 : <AVCaptureDeviceFormat: 0x1d000ecd0 'vide'/'420f' 1280x 720, { 2- 30 fps}, HRSI:3840x2160, fov:67.564, max zoom:135.00 (upscales @3.00), ISO:19.0-1824.0, SS:0.000013-0.500000, supports wide color>
- 14 : <AVCaptureDeviceFormat: 0x1d000ece0 'vide'/'420v' 1280x 720, { 2- 60 fps}, HRSI:1920x1080, fov:67.284, binned, max zoom:67.50 (upscales @1.50), ISO:19.0-1824.0, SS:0.000013-0.500000>
- 15 : <AVCaptureDeviceFormat: 0x1d000ecf0 'vide'/'420f' 1280x 720, { 2- 60 fps}, HRSI:1920x1080, fov:67.284, binned, max zoom:67.50 (upscales @1.50), ISO:19.0-1824.0, SS:0.000013-0.500000, supports wide color>
- 16 : <AVCaptureDeviceFormat: 0x1d000ed00 'vide'/'420v' 1440x1080, { 2- 60 fps}, HRSI:1440x1080, fov:42.612, binned, max zoom:67.50 (upscales @1.00), ISO:19.0-1824.0, SS:0.000013-0.500000>
- 17 : <AVCaptureDeviceFormat: 0x1d000ed10 'vide'/'420f' 1440x1080, { 2- 60 fps}, HRSI:1440x1080, fov:42.612, binned, max zoom:67.50 (upscales @1.00), ISO:19.0-1824.0, SS:0.000013-0.500000, supports wide color>
- 18 : <AVCaptureDeviceFormat: 0x1d000ed20 'vide'/'420v' 1920x1080, { 2- 30 fps}, HRSI:3840x2160, fov:67.564, max zoom:135.00 (upscales @2.00), ISO:19.0-1824.0, SS:0.000013-0.500000>
- 19 : <AVCaptureDeviceFormat: 0x1d000ed30 'vide'/'420f' 1920x1080, { 2- 30 fps}, HRSI:3840x2160, fov:67.564, max zoom:135.00 (upscales @2.00), ISO:19.0-1824.0, SS:0.000013-0.500000, supports wide color>
- 20 : <AVCaptureDeviceFormat: 0x1d000ed40 'vide'/'420v' 3088x2320, { 2- 30 fps}, HRSI:3088x2320, fov:56.559, max zoom:145.00 (upscales @1.00), ISO:19.0-1824.0, SS:0.000013-0.500000>
- 21 : <AVCaptureDeviceFormat: 0x1d000ed50 'vide'/'420f' 3088x2320, { 2- 30 fps}, HRSI:3088x2320, fov:56.559, max zoom:145.00 (upscales @1.00), ISO:19.0-1824.0, SS:0.000013-0.500000, supports wide color>

activeFormat

The currently active media data format of the capture device.

一般我们使用AVCaptureSessionsessionPreset来配置捕获配置。使用AVAudioSession来配置音频。当我们设置之后会自动配置activeFormat这个参数。

但是可能我们需要特殊需求,比如高帧率视频,不能从sessionPreset配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// Apple 提供设置高帧率视频捕获例子。
- (void)configureCameraForHighestFrameRate:(AVCaptureDevice *)device
{
AVCaptureDeviceFormat *bestFormat = nil;
AVFrameRateRange *bestFrameRateRange = nil;
for ( AVCaptureDeviceFormat *format in [device formats] ) {
for ( AVFrameRateRange *range in format.videoSupportedFrameRateRanges ) {
if ( range.maxFrameRate > bestFrameRateRange.maxFrameRate ) {
bestFormat = format;
bestFrameRateRange = range;
}
}
}
if ( bestFormat ) {
if ( [device lockForConfiguration:NULL] == YES ) {
device.activeFormat = bestFormat;
device.activeVideoMinFrameDuration = bestFrameRateRange.minFrameDuration;
device.activeVideoMaxFrameDuration = bestFrameRateRange.minFrameDuration;
[device unlockForConfiguration];
}
}
}

activeDepthDataFormat

The currently active depth data format of the capture device.

focusMode

The device’s focus mode.

focusPointOfInterest

The point of interest for focusing.

This property’s CGPoint value uses a coordinate system where {0,0} is the top left of the picture area and {1,1} is the bottom right. This coordinate system is always relative to a landscape device orientation with the home button on the right, regardless of the actual device orientation.

1
2
3
4
5
if ([currentDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
CGPoint autofocusPoint = CGPointMake(0.5f, 0.5f);
[currentDevice setFocusPointOfInterest:autofocusPoint];
[currentDevice setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
}

AVCaptureDevice.Format

A set of media format and capture settings (such as video resolution and frame rate) that can be used to configure a capture device.

AVCaptureDevice.FocusMode

AVCaptureInput

The abstract superclass for objects that provide input data to a capture session.

AVCaptureDeviceInput

A capture input that provides media from a capture device to a capture session.

AVCaptureOutput

The abstract superclass for objects that output the media recorded in a capture session.

AVCaptureVideoDataOutput

A capture output that records video and provides access to video frames for processing.

videoSettings

Format YUV Discussion
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange Bi-Planar Component Y’CbCr 8-bit 4:2:0
video-range (luma=[16,235] chroma=[16,240]).
baseAddr points to a big-endian CVPlanarPixelBufferInfo_YCbCrBiPlanar struct.
kCVPixelFormatType_420YpCbCr8BiPlanarFullRange Bi-Planar Component Y’CbCr 8-bit 4:2:0
full-range (luma=[0,255] chroma=[1,255]).
baseAddr points to a big-endian CVPlanarPixelBufferInfo_YCbCrBiPlanar struct.
kCVPixelFormatType_32BGRA 32 bit BGRA.
1
videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as AnyHashable: kCVPixelFormatType_32BGRA]

摄像头输出的视频流原始格式是YUV的格式,如果手动配置为BGRA的格式,系统会在内部进行YUV转BGRA的格式,GPUImage采用的是输出YUV,然后用OpenGL来转换。具体的转换矩阵可以之前的文章:GPUImage-Mini灬哆啦

alwaysDiscardsLateVideoFrames

Indicates whether video frames are dropped if they arrive late.

参数默认是true,如果不想丢帧,设置为false

AVCaptureVideoDataOutputSampleBufferDelegate

1
2
3
optional public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection)
@available(iOS 6.0, *)
optional public func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection)

将回调帧放到指定的队列中执行,为了保证帧的顺序,队列一定要是串行队列。

AVCaptureMetadataOutput

A capture output for processing timed metadata produced by a capture session.

metadataObjectTypes

An array of strings identifying the types of metadata objects to process.

最常用的元数据类型:二维码AVMetadataObjectTypeQRCode和脸部AVMetadataObjectTypeFace

AVCaptureMetadataOutputObjectsDelegate

1
optional public func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection)

Capture

使用AVCaptureSession捕获视频数据。

首先我们先定义一个基类,主要负责启动和停止AVCaptureSession

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/// Simple system camera to capture video.
/// Only one camera input.
open class SystemCamera: NSObject {

public var currentCameraPosition: CoreCameraPosition
public final let cameraQueue = DispatchQueue(label: "com.xwjack.Camera.Serial")

fileprivate var videoDeviceInput: AVCaptureDeviceInput? = nil

/// Session for capture.
final let session = AVCaptureSession()

public required init(cameraPosition: CoreCameraPosition = .front) {
currentCameraPosition = cameraPosition
super.init()
needToResetInput()
}

deinit {
session.stopRunning()
}

/// Start Capture
///
/// - Parameter completion: Call back when it completed.
public func startCapture(completion: (() -> ())? = nil) {
guard !session.isRunning else { return }
cameraQueue.async { [weak self] in
self?.session.startRunning()
}
}

/// Stop Capture
///
/// - Parameter completion: Call back when it completed.
public func stopCapture(completion: (() -> ())? = nil) {
guard session.isRunning else { return }
cameraQueue.async { [weak self] in
self?.session.stopRunning()
}
}

/// Switch camera.
public func switchCamera(completion: (() -> ())? = nil) {
cameraQueue.async { [weak self] in
guard let `self` = self else { return }
self.currentCameraPosition = !self.currentCameraPosition
self.needToResetInput()
}
}

open func configuration(videoDeviceInput: AVCaptureDeviceInput) {
session.beginConfiguration()
defer {
session.commitConfiguration()
}
/// Config new preset.
session.sessionPreset = AVCaptureSessionPreset640x480
if session.canAddInput(videoDeviceInput) {
session.addInput(videoDeviceInput)
}
}

private func needToResetInput() {
do {
videoDeviceInput = try AVCaptureDeviceInput(device: currentCameraPosition.device)
configuration(videoDeviceInput: videoDeviceInput!)
} catch {
assertionFailure(error.localizedDescription)
}
}
}

Video

仅捕获视频。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
open class VideoCamera: SystemCamera, AVCaptureVideoDataOutputSampleBufferDelegate {

/// Video data output.
private let videoDataOutput = AVCaptureVideoDataOutput()

/// Video process queue.
private let videoProcessQueue = DispatchQueue(label: "com.xwjack.Camera.Video.Serial")

/// Cache for last frame.
public final var bufferCache: CoreVideoFrame?

/// Call back with video frame.
public final var videoCallback: ((CoreVideoFrame) -> ())?

public required init(cameraPosition: CoreCameraPosition = .front) {
super.init(cameraPosition: cameraPosition)
configuration(videoDataOutput: videoDataOutput)
}

deinit {
videoDataOutput.setSampleBufferDelegate(nil, queue: nil)
}

open func configuration(videoDataOutput: AVCaptureVideoDataOutput) {
session.beginConfiguration()
defer {
session.commitConfiguration()
}
//kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
//kCVPixelFormatType_32BGRA
videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as AnyHashable: kCVPixelFormatType_32BGRA]
if session.canAddOutput(videoDataOutput) {
session.addOutput(videoDataOutput)
}
}

public override func startCapture(completion: (() -> ())? = nil) {
videoDataOutput.setSampleBufferDelegate(self, queue: videoProcessQueue)
super.startCapture(completion: completion)
}

public override func stopCapture(completion: (() -> ())? = nil) {
videoDataOutput.setSampleBufferDelegate(nil, queue: nil)
super.stopCapture(completion: completion)
}

public func captureOutput(_ output: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) {
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
let time = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
if bufferCache == nil {
bufferCache = CoreVideoFrame(buffer: imageBuffer, time: time, isMirror: currentCameraPosition.isMirror)
} else {
bufferCache?.buffer = imageBuffer
bufferCache?.time = time
bufferCache?.isMirror = currentCameraPosition.isMirror
}
videoCallback?(bufferCache!)
}
}

在这里videoSettings设置为kCVPixelFormatType_32BGRA,这样就不用自己将YUV的数据转换成BGRA的格式了。

Media

捕获音频和视频。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
open class MediaCamera: VideoCamera, AVCaptureAudioDataOutputSampleBufferDelegate {

/// Call back for audio frame.
public var audioCallback: ((CMSampleBuffer) -> ())? = nil

/// Audio device input.
private var audioDeviceInput: AVCaptureDeviceInput? = nil

/// Audio data output.
private let audioDataOutput = AVCaptureAudioDataOutput()

/// Serial queue for audio process.
private let audioProcessQueue = DispatchQueue(label: "com.xwjack.Camera.Audio.Serial")

public required init(cameraPosition: CoreCameraPosition = .front) {
super.init(cameraPosition: cameraPosition)
do {
audioDeviceInput = try AVCaptureDeviceInput(device: AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeAudio))
configuration(audioDeviceInput: audioDeviceInput!)
} catch {
assertionFailure(error.localizedDescription)
}

}

public override func startCapture(completion: (() -> ())? = nil) {
audioDataOutput.setSampleBufferDelegate(self, queue: audioProcessQueue)
super.startCapture(completion: completion)
}

public override func stopCapture(completion: (() -> ())? = nil) {
audioDataOutput.setSampleBufferDelegate(nil, queue: nil)
super.stopCapture(completion: completion)
}

open func configuration(audioDeviceInput: AVCaptureDeviceInput) {
session.beginConfiguration()
defer {
session.commitConfiguration()
}
if session.canAddInput(audioDeviceInput) {
session.addInput(audioDeviceInput)
}
}

open func configuration(audioDataOutput: AVCaptureAudioDataOutput) {
session.beginConfiguration()
defer {
session.commitConfiguration()
}
if session.canAddOutput(audioDataOutput) {
session.addOutput(audioDataOutput)
}
}

public override func captureOutput(_ output: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) {
guard output != audioDataOutput else {
audioCallback?(sampleBuffer)
return
}
super.captureOutput(output, didOutputSampleBuffer: sampleBuffer, from: connection)
}
}

QR Decode

直接捕获元数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
open class QRCamera: VideoCamera, AVCaptureMetadataOutputObjectsDelegate {

/// Is scanning
public private(set) var isScanning: Bool = false

/// Metdata output.
private let metdataOutput = AVCaptureMetadataOutput()

/// Metadata callback.
public final var metadataCallback: ((String) -> ())?

private let qrProcessQueue = DispatchQueue(label: "com.xwjack.Camera.QR.Serial")

/// Start QR scan
public func startScanner(completion: (() -> ())? = nil) {
isScanning = true
metdataOutput.setMetadataObjectsDelegate(self, queue: qrProcessQueue)
cameraQueue.async { [weak self] in
guard let `self` = self else { return }
if self.session.canAddOutput(self.metdataOutput) {
self.session.beginConfiguration()
self.session.addOutput(self.metdataOutput)
self.session.commitConfiguration()
}
/// Need to set metadataObjectTypes after addOutput
self.metdataOutput.metadataObjectTypes = [AVMetadataObjectTypeQRCode]
completion?()
}
}

/// Stop QR scan.
public func stopScanner(completion: (() -> ())? = nil) {
isScanning = false
metdataOutput.setMetadataObjectsDelegate(nil, queue: nil)
cameraQueue.async {
self.session.beginConfiguration()
self.session.removeOutput(self.metdataOutput)
self.session.commitConfiguration()
self.isScanning = false
completion?()
}
}

public func captureOutput(_ output: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {

guard let metadataObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
metadataObject.type == AVMetadataObjectTypeQRCode else { return }
metadataCallback?(metadataObject.stringValue)
}
}

Display

摄像头采集的视频并不是可以直接显示在视图中,而是由于摄像头设备的朝向,所以有如下的转换。

AVCaptureDevicePosition Mirror Rotation
front 90
back 90

首先我们先定义一个VideoFrame来承接即将到来的视频帧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/// Core video frame.
open class CoreVideoFrame {

/// CoreVideo frame Orientation
public enum Orientation {
case portrait
case portraitUpsideDown
case landscapeLeft
case landscapeRight

/// Angle for orientation.
public var rotationAngle: Double {
switch self {
case .portrait: return 0
case .portraitUpsideDown: return Double.pi
case .landscapeLeft: return Double.pi / 2
case .landscapeRight: return -Double.pi / 2
}
}
}

/// Buffer base CPU.
open fileprivate(set) var buffer: CVPixelBuffer

/// Is mirror
open fileprivate(set) var isMirror: Bool

/// Time stemp for texture.
open fileprivate(set) var time: CMTime

/// Default all captured video frame orientation is `landscapeLeft`
open let orientaion: Orientation = .landscapeLeft

init(buffer: CVPixelBuffer, time: CMTime, isMirror: Bool) {
self.buffer = buffer
self.time = time
self.isMirror = isMirror
}

/// Width for texture.
var width: Int { return CVPixelBufferGetWidth(buffer) }
/// Height for texture.
var height: Int { return CVPixelBufferGetHeight(buffer) }
/// Type for texture, default is `kCVPixelFormatType_32BGRA`
var bufferType: OSType { return CVPixelBufferGetPixelFormatType(buffer) }
}

使用GLKView来显示视频帧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/// Renderer video frame by GLKView, default is AspectRatio.
public final class GLKPreviewView: GLKView {

public var isEnable: Bool = true

private let ciContext: CIContext

public override init(frame: CGRect, context: EAGLContext) {
ciContext = CIContext(eaglContext: context)
super.init(frame: frame, context: context)
enableSetNeedsDisplay = false
}

/// Initlization by given default EAGLContext with OpenGL ES 2
///
/// - Parameter frame: CGRect
public override convenience init(frame: CGRect) {
self.init(frame: frame, context: EAGLContext(api: .openGLES2))
}

public func process(_ value: VideoFrame) {

if isEnable {
/// ⚠️ Sync run in main queue.
DispatchQueue.main.sync {

let image = CIImage(cvPixelBuffer: value.texture)
let drawableSize = CGSize(width: drawableWidth, height: drawableHeight)
/// Create affine transform by orientation.
let transform = CGAffineTransform(rotationAngle: -CGFloat(value.orientaion.rotationAngle))

/// Set context.
if context != EAGLContext.current() {
EAGLContext.setCurrent(context)
}

let transformImage = image.applying(value.isMirror ? transform.scaledBy(x: 1, y: -1) : transform)
/// Dicide CGRect to render video frame.
let drawRect = AVMakeRect(aspectRatio: drawableSize, insideRect: transformImage.extent)

/// Begin draw.
bindDrawable()
ciContext.draw(transformImage, in: CGRect(origin: .zero, size: drawableSize), from: drawRect)
display()
}
}
}

required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

视频帧是采用推流的方式获取,所以在设置GLKView的时候,我们需要设置enableSetNeedsDisplay = false表示我们采用推流的方式绘制视频,在调用display()表示需要绘制视频帧。

由于前置摄像头捕获的数据是一个镜像数据,所以需要进行一次transform,最后使用CIContext绘制到GLKView