import CoreAudio import CoreMediaIO import Foundation /// 摄像头与麦克风是否正被某个进程使用。只读设备的“正在运行”属性,不打开设备,也不需要隐私授权 public struct MediaUsage: Sendable, Equatable { public var camera = false public var microphone = false public init(camera: Bool = false, microphone: Bool = false) { self.camera = camera self.microphone = microphone } } public enum MediaUsageSampler { public static func sample() -> MediaUsage { MediaUsage(camera: cameraInUse(), microphone: microphoneInUse()) } // MARK: 麦克风(Core Audio) private static func microphoneInUse() -> Bool { audioDevices().contains { device in hasInputStreams(device) && audioFlag(device, kAudioDevicePropertyDeviceIsRunningSomewhere) } } private static func audioDevices() -> [AudioObjectID] { var address = AudioObjectPropertyAddress(mSelector: kAudioHardwarePropertyDevices, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMain) var size: UInt32 = 0 let system = AudioObjectID(kAudioObjectSystemObject) guard AudioObjectGetPropertyDataSize(system, &address, 0, nil, &size) == noErr, size > 0 else { return [] } var devices = [AudioObjectID](repeating: 0, count: Int(size) / MemoryLayout.size) guard AudioObjectGetPropertyData(system, &address, 0, nil, &size, &devices) == noErr else { return [] } return devices } private static func hasInputStreams(_ device: AudioObjectID) -> Bool { var address = AudioObjectPropertyAddress(mSelector: kAudioDevicePropertyStreams, mScope: kAudioObjectPropertyScopeInput, mElement: kAudioObjectPropertyElementMain) var size: UInt32 = 0 return AudioObjectGetPropertyDataSize(device, &address, 0, nil, &size) == noErr && size > 0 } private static func audioFlag(_ device: AudioObjectID, _ selector: AudioObjectPropertySelector) -> Bool { var address = AudioObjectPropertyAddress(mSelector: selector, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMain) var value: UInt32 = 0 var size = UInt32(MemoryLayout.size) return AudioObjectGetPropertyData(device, &address, 0, nil, &size, &value) == noErr && value != 0 } // MARK: 摄像头(Core Media IO) private static func cameraInUse() -> Bool { var address = CMIOObjectPropertyAddress(mSelector: CMIOObjectPropertySelector(kCMIOHardwarePropertyDevices), mScope: CMIOObjectPropertyScope(kCMIOObjectPropertyScopeGlobal), mElement: CMIOObjectPropertyElement(kCMIOObjectPropertyElementMain)) let system = CMIOObjectID(kCMIOObjectSystemObject) var size: UInt32 = 0 guard CMIOObjectGetPropertyDataSize(system, &address, 0, nil, &size) == noErr, size > 0 else { return false } var devices = [CMIOObjectID](repeating: 0, count: Int(size) / MemoryLayout.size) var used: UInt32 = 0 guard CMIOObjectGetPropertyData(system, &address, 0, nil, size, &used, &devices) == noErr else { return false } return devices.contains { device in var running = CMIOObjectPropertyAddress(mSelector: CMIOObjectPropertySelector(kCMIODevicePropertyDeviceIsRunningSomewhere), mScope: CMIOObjectPropertyScope(kCMIOObjectPropertyScopeWildcard), mElement: CMIOObjectPropertyElement(kCMIOObjectPropertyElementWildcard)) var value: UInt32 = 0 var valueUsed: UInt32 = 0 return CMIOObjectGetPropertyData(device, &running, 0, nil, UInt32(MemoryLayout.size), &valueUsed, &value) == noErr && value != 0 } } }