screencapturekit 9.0.1

Safe Rust bindings for Apple's ScreenCaptureKit framework - screen and audio capture on macOS
Documentation
// Audio device enumeration using AVFoundation

import AVFoundation
import Foundation

/// Represents an audio input device (microphone)
public struct AudioInputDevice {
    public let id: String
    public let name: String
    public let isDefault: Bool
}

/// An immutable list of input devices captured in one pass.
///
/// Enumerating device-by-device against a live `AVCaptureDevice.DiscoverySession`
/// re-runs the query for every accessor, so a microphone plugged in or removed
/// mid-enumeration produces a torn list: a count from one moment, a name from
/// another, and an index that may no longer exist. Freezing one pass into a
/// snapshot makes the whole read consistent.
final class AudioInputDeviceSnapshot {
    let devices: [AudioInputDevice]
    /// Index of the system default input in `devices`, or -1 when there is
    /// none (or it is not part of the discovery session's results).
    let defaultIndex: Int

    init() {
        let discovered = AudioInputDeviceSnapshot.discover()
        let defaultUniqueID = AVCaptureDevice.default(for: .audio)?.uniqueID
        devices = discovered.map {
            AudioInputDevice(
                id: $0.uniqueID,
                name: $0.localizedName,
                isDefault: $0.uniqueID == defaultUniqueID
            )
        }
        defaultIndex = devices.firstIndex { $0.isDefault } ?? -1
    }

    private static func discover() -> [AVCaptureDevice] {
        AVCaptureDevice.DiscoverySession(
            deviceTypes: [.builtInMicrophone, .externalUnknown],
            mediaType: .audio,
            position: .unspecified
        ).devices
    }
}

// MARK: - Snapshot FFI

/// Capture the current input-device list. Never returns nil; release with
/// `sc_audio_input_devices_snapshot_release`.
@_cdecl("sc_audio_input_devices_snapshot_create")
public func createAudioInputDevicesSnapshot() -> OpaquePointer {
    retain(AudioInputDeviceSnapshot())
}

@_cdecl("sc_audio_input_devices_snapshot_release")
public func releaseAudioInputDevicesSnapshot(_ snapshot: OpaquePointer) {
    release(snapshot)
}

@_cdecl("sc_audio_input_devices_snapshot_count")
public func audioInputDevicesSnapshotCount(_ snapshot: OpaquePointer) -> Int {
    let s: AudioInputDeviceSnapshot = unretained(snapshot)
    return s.devices.count
}

/// Index of the default input device, or -1 when there is none.
@_cdecl("sc_audio_input_devices_snapshot_default_index")
public func audioInputDevicesSnapshotDefaultIndex(_ snapshot: OpaquePointer) -> Int {
    let s: AudioInputDeviceSnapshot = unretained(snapshot)
    return s.defaultIndex
}

/// Device unique ID as an owned string (caller frees with `sc_free_string`).
@_cdecl("sc_audio_input_devices_snapshot_id_owned")
public func audioInputDevicesSnapshotIDOwned(
    _ snapshot: OpaquePointer,
    _ index: Int
) -> UnsafeMutablePointer<CChar>? {
    let s: AudioInputDeviceSnapshot = unretained(snapshot)
    guard index >= 0, index < s.devices.count else { return nil }
    return strdup(s.devices[index].id)
}

/// Device display name as an owned string (caller frees with `sc_free_string`).
@_cdecl("sc_audio_input_devices_snapshot_name_owned")
public func audioInputDevicesSnapshotNameOwned(
    _ snapshot: OpaquePointer,
    _ index: Int
) -> UnsafeMutablePointer<CChar>? {
    let s: AudioInputDeviceSnapshot = unretained(snapshot)
    guard index >= 0, index < s.devices.count else { return nil }
    return strdup(s.devices[index].name)
}

@_cdecl("sc_audio_input_devices_snapshot_is_default")
public func audioInputDevicesSnapshotIsDefault(_ snapshot: OpaquePointer, _ index: Int) -> Bool {
    let s: AudioInputDeviceSnapshot = unretained(snapshot)
    guard index >= 0, index < s.devices.count else { return false }
    return s.devices[index].isDefault
}

// MARK: - Legacy per-index FFI
//
// Each of these re-runs the discovery query, so a list assembled from them can
// be torn across a device hot-plug. They remain for ABI compatibility; the
// snapshot API above is what the Rust side uses.

/// Get the count of available audio input devices
@_cdecl("sc_audio_get_input_device_count")
public func getInputDeviceCount() -> Int {
    AudioInputDeviceSnapshot().devices.count
}

/// Get audio input device ID at index into a buffer
@_cdecl("sc_audio_get_input_device_id")
public func getInputDeviceId(index: Int, buffer: UnsafeMutablePointer<CChar>?, bufferSize: Int) -> Bool {
    let devices = AudioInputDeviceSnapshot().devices
    guard index >= 0, index < devices.count else { return false }
    return writeCString(devices[index].id, into: buffer, bufferSize: bufferSize)
}

/// Get audio input device name at index into a buffer
@_cdecl("sc_audio_get_input_device_name")
public func getInputDeviceName(index: Int, buffer: UnsafeMutablePointer<CChar>?, bufferSize: Int) -> Bool {
    let devices = AudioInputDeviceSnapshot().devices
    guard index >= 0, index < devices.count else { return false }
    return writeCString(devices[index].name, into: buffer, bufferSize: bufferSize)
}

/// Check if the device at index is the default audio input device
@_cdecl("sc_audio_is_default_input_device")
public func isDefaultInputDevice(index: Int) -> Bool {
    let devices = AudioInputDeviceSnapshot().devices
    guard index >= 0, index < devices.count else { return false }
    return devices[index].isDefault
}

/// Get the default audio input device ID into a buffer
@_cdecl("sc_audio_get_default_input_device_id")
public func getDefaultInputDeviceId(buffer: UnsafeMutablePointer<CChar>?, bufferSize: Int) -> Bool {
    guard let device = AVCaptureDevice.default(for: .audio) else { return false }
    return writeCString(device.uniqueID, into: buffer, bufferSize: bufferSize)
}

/// Get the default audio input device name into a buffer
@_cdecl("sc_audio_get_default_input_device_name")
public func getDefaultInputDeviceName(buffer: UnsafeMutablePointer<CChar>?, bufferSize: Int) -> Bool {
    guard let device = AVCaptureDevice.default(for: .audio) else { return false }
    return writeCString(device.localizedName, into: buffer, bufferSize: bufferSize)
}