From 54ff90604d8b623078823b86f1de0378d87b7f16 Mon Sep 17 00:00:00 2001 From: Daniel Murfin Date: Sun, 6 Mar 2022 10:49:36 +0000 Subject: [PATCH 1/3] Added method to request a specific camera unique identifier. --- Sources/VideoIO/Camera.swift | 59 +++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/Sources/VideoIO/Camera.swift b/Sources/VideoIO/Camera.swift index f33e1fa..22006b7 100644 --- a/Sources/VideoIO/Camera.swift +++ b/Sources/VideoIO/Camera.swift @@ -49,11 +49,13 @@ public class Camera { private let configurator: Configurator + private let defaultCameraUniqueId: String? + private let defaultCameraPosition: AVCaptureDevice.Position private let defaultCameraDeviceTypes: [AVCaptureDevice.DeviceType] - public init(captureSessionPreset: AVCaptureSession.Preset, defaultCameraPosition: AVCaptureDevice.Position = .back, defaultCameraDeviceTypes: [AVCaptureDevice.DeviceType] = [], configurator: Configurator = Configurator()) { + public init(captureSessionPreset: AVCaptureSession.Preset, defaultCameraUniqueId: String? = nil, defaultCameraPosition: AVCaptureDevice.Position = .back, defaultCameraDeviceTypes: [AVCaptureDevice.DeviceType] = [], configurator: Configurator = Configurator()) { let captureSession = AVCaptureSession() assert(captureSession.canSetSessionPreset(captureSessionPreset)) captureSession.beginConfiguration() @@ -68,6 +70,7 @@ public class Camera { captureSession.commitConfiguration() self.captureSession = captureSession self.configurator = configurator + self.defaultCameraUniqueId = defaultCameraUniqueId self.defaultCameraPosition = defaultCameraPosition self.defaultCameraDeviceTypes = defaultCameraDeviceTypes } @@ -100,7 +103,7 @@ public class Camera { return self.audioDeviceInput?.device } - public func switchToVideoCaptureDevice(with position: AVCaptureDevice.Position, preferredDeviceTypes: [AVCaptureDevice.DeviceType] = []) throws { + public func switchToVideoCaptureDevice(with position: AVCaptureDevice.Position, preferredDeviceTypes: [AVCaptureDevice.DeviceType] = [], preferredCameraUniqueId: String? = nil) throws { let deviceTypes: [AVCaptureDevice.DeviceType] if preferredDeviceTypes.count == 0 { if defaultCameraDeviceTypes.count == 0 { @@ -124,31 +127,37 @@ public class Camera { deviceTypes = preferredDeviceTypes } let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: deviceTypes, mediaType: .video, position: position) - if let device = discoverySession.devices.first { - let newVideoDeviceInput = try AVCaptureDeviceInput(device: device) - self.captureSession.beginConfiguration() - if let currentVideoDeviceInput = self.videoDeviceInput { - self.captureSession.removeInput(currentVideoDeviceInput) - } - if self.captureSession.canAddInput(newVideoDeviceInput) { - self.captureSession.addInput(newVideoDeviceInput) - self.videoDeviceInput = newVideoDeviceInput - } else { - self.captureSession.commitConfiguration() - throw Error.cannotAddInput - } - - if let connection = self.videoCaptureConnection { - self.configurator.videoConnectionConfigurator(self, connection) - } - self.captureSession.commitConfiguration() - - try device.lockForConfiguration() - self.configurator.videoDeviceConfigurator(self, device) - device.unlockForConfiguration() + + let device: AVCaptureDevice + if let preferredCamera = discoverySession.devices.first(where: { $0.uniqueID == preferredCameraUniqueId }) { + device = preferredCamera + } else if let discoveredDevice = discoverySession.devices.first { + device = discoveredDevice } else { throw Error.noDeviceFound } + + let newVideoDeviceInput = try AVCaptureDeviceInput(device: device) + self.captureSession.beginConfiguration() + if let currentVideoDeviceInput = self.videoDeviceInput { + self.captureSession.removeInput(currentVideoDeviceInput) + } + if self.captureSession.canAddInput(newVideoDeviceInput) { + self.captureSession.addInput(newVideoDeviceInput) + self.videoDeviceInput = newVideoDeviceInput + } else { + self.captureSession.commitConfiguration() + throw Error.cannotAddInput + } + + if let connection = self.videoCaptureConnection { + self.configurator.videoConnectionConfigurator(self, connection) + } + self.captureSession.commitConfiguration() + + try device.lockForConfiguration() + self.configurator.videoDeviceConfigurator(self, device) + device.unlockForConfiguration() } public var videoCaptureConnection: AVCaptureConnection? { @@ -160,7 +169,7 @@ public class Camera { public func enableVideoDataOutput(on queue: DispatchQueue = .main, delegate: AVCaptureVideoDataOutputSampleBufferDelegate) throws { assert(self.videoDataOutput == nil) if self.videoDevice == nil { - try self.switchToVideoCaptureDevice(with: self.defaultCameraPosition) + try self.switchToVideoCaptureDevice(with: self.defaultCameraPosition, preferredCameraUniqueId: self.defaultCameraUniqueId) } self.captureSession.beginConfiguration() defer { From 9cd6290e58ec548271b15471c0ff6a45ef57cb66 Mon Sep 17 00:00:00 2001 From: Daniel Murfin Date: Sun, 6 Mar 2022 11:39:39 +0000 Subject: [PATCH 2/3] Add methods for specifying a default audio device, and switching audio devices. --- Sources/VideoIO/Camera.swift | 75 +++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/Sources/VideoIO/Camera.swift b/Sources/VideoIO/Camera.swift index 22006b7..d82bbbf 100644 --- a/Sources/VideoIO/Camera.swift +++ b/Sources/VideoIO/Camera.swift @@ -55,7 +55,11 @@ public class Camera { private let defaultCameraDeviceTypes: [AVCaptureDevice.DeviceType] - public init(captureSessionPreset: AVCaptureSession.Preset, defaultCameraUniqueId: String? = nil, defaultCameraPosition: AVCaptureDevice.Position = .back, defaultCameraDeviceTypes: [AVCaptureDevice.DeviceType] = [], configurator: Configurator = Configurator()) { + private let defaultAudioDeviceUniqueId: String? + + private let defaultAudioDeviceTypes: [AVCaptureDevice.DeviceType] + + public init(captureSessionPreset: AVCaptureSession.Preset, defaultCameraUniqueId: String? = nil, defaultCameraPosition: AVCaptureDevice.Position = .back, defaultCameraDeviceTypes: [AVCaptureDevice.DeviceType] = [], defaultAudioDeviceUniqueId: String? = nil, defaultAudioDeviceTypes: [AVCaptureDevice.DeviceType] = [], configurator: Configurator = Configurator()) { let captureSession = AVCaptureSession() assert(captureSession.canSetSessionPreset(captureSessionPreset)) captureSession.beginConfiguration() @@ -73,6 +77,8 @@ public class Camera { self.defaultCameraUniqueId = defaultCameraUniqueId self.defaultCameraPosition = defaultCameraPosition self.defaultCameraDeviceTypes = defaultCameraDeviceTypes + self.defaultAudioDeviceUniqueId = defaultAudioDeviceUniqueId + self.defaultAudioDeviceTypes = defaultAudioDeviceTypes } public var captureSessionIsRunning: Bool { @@ -202,6 +208,51 @@ public class Camera { self.captureSession.commitConfiguration() } + public func switchToAudioCaptureDevice(preferredDeviceTypes: [AVCaptureDevice.DeviceType] = [], preferredAudioDeviceId: String? = nil) throws { + let deviceTypes: [AVCaptureDevice.DeviceType] + if preferredDeviceTypes.count == 0 { + if defaultAudioDeviceTypes.count == 0 { + #if os(macOS) + deviceTypes = [.builtInMicrophone, .externalUnknown] + #elseif os(tvOS) + deviceTypes = [] + #else + deviceTypes = [.builtInMicrophone] + #endif + } else { + deviceTypes = defaultAudioDeviceTypes + } + } else { + deviceTypes = preferredDeviceTypes + } + let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: deviceTypes, mediaType: .audio, position: .unspecified) + + let device: AVCaptureDevice + if let preferredAudioDevice = discoverySession.devices.first(where: { $0.uniqueID == preferredAudioDeviceId }) { + device = preferredAudioDevice + } else if let discoveredDevice = AVCaptureDevice.default(for: .audio) { + device = discoveredDevice + } else { + throw Error.noDeviceFound + } + + let newAudioDeviceInput = try AVCaptureDeviceInput(device: device) + self.captureSession.beginConfiguration() + + if let currentAudioDeviceInput = self.audioDeviceInput { + self.captureSession.removeInput(currentAudioDeviceInput) + } + if self.captureSession.canAddInput(newAudioDeviceInput) { + self.captureSession.addInput(newAudioDeviceInput) + self.audioDeviceInput = newAudioDeviceInput + } else { + self.captureSession.commitConfiguration() + throw Error.cannotAddInput + } + + self.captureSession.commitConfiguration() + } + public var audioCaptureConnection: AVCaptureConnection? { return self.audioDataOutput?.connection(with: .audio) } @@ -209,23 +260,14 @@ public class Camera { public private(set) var audioDataOutput: AVCaptureAudioDataOutput? public func enableAudioDataOutput(on queue: DispatchQueue = .main, delegate: AVCaptureAudioDataOutputSampleBufferDelegate) throws { + assert(self.audioDataOutput == nil) + if self.audioDevice == nil { + try self.switchToAudioCaptureDevice(preferredAudioDeviceId: self.defaultAudioDeviceUniqueId) + } self.captureSession.beginConfiguration() defer { self.captureSession.commitConfiguration() } - if self.audioDeviceInput == nil { - if let device = AVCaptureDevice.default(for: .audio), let audioDeviceInput = try? AVCaptureDeviceInput(device: device) { - if self.captureSession.canAddInput(audioDeviceInput) { - self.captureSession.addInput(audioDeviceInput) - self.audioDeviceInput = audioDeviceInput - } else { - throw Error.cannotAddInput - } - } else { - throw Error.cannotAddInput - } - } - assert(self.audioDataOutput == nil) if let audioOutput = self.audioDataOutput { self.captureSession.removeOutput(audioOutput) } @@ -245,11 +287,6 @@ public class Camera { self.captureSession.removeOutput(output) } self.audioDataOutput = nil - - if let input = self.audioDeviceInput { - self.captureSession.removeInput(input) - } - self.audioDeviceInput = nil self.captureSession.commitConfiguration() } From 82ea86bdb365ef8239cec7a4682be8cda45aa740 Mon Sep 17 00:00:00 2001 From: Daniel Murfin Date: Sun, 6 Mar 2022 11:42:52 +0000 Subject: [PATCH 3/3] Add method to remove audio capture device --- Sources/VideoIO/Camera.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Sources/VideoIO/Camera.swift b/Sources/VideoIO/Camera.swift index d82bbbf..29a0051 100644 --- a/Sources/VideoIO/Camera.swift +++ b/Sources/VideoIO/Camera.swift @@ -253,6 +253,17 @@ public class Camera { self.captureSession.commitConfiguration() } + public func removeAudioCaptureDevice() { + self.captureSession.beginConfiguration() + + if let currentAudioDeviceInput = self.audioDeviceInput { + self.captureSession.removeInput(currentAudioDeviceInput) + self.audioDeviceInput = nil + } + + self.captureSession.commitConfiguration() + } + public var audioCaptureConnection: AVCaptureConnection? { return self.audioDataOutput?.connection(with: .audio) }