82 lines
4.2 KiB
Swift
82 lines
4.2 KiB
Swift
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<AudioObjectID>.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<UInt32>.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<CMIOObjectID>.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<UInt32>.size), &valueUsed, &value) == noErr
|
||
&& value != 0
|
||
}
|
||
}
|
||
}
|