diff --git a/app/xcode/Package.swift b/app/xcode/Package.swift index e631697..2075c37 100644 --- a/app/xcode/Package.swift +++ b/app/xcode/Package.swift @@ -25,7 +25,7 @@ let package = Package( .target(name: "VCamUIFoundation"), .target(name: "VCamData", dependencies: ["VCamEntity"]), .target(name: "VCamEntity", dependencies: ["VCamDefaults", "VCamLocalization"], swiftSettings: [ -// .define("ENABLE_MOCOPI") + .define("ENABLE_MOCOPI") ]), .target(name: "VCamLocalization", resources: [.process("VCamResources")]), .target(name: "VCamMedia", dependencies: ["VCamEntity", "VCamAppExtension"]), diff --git a/app/xcode/Sources/VCamEntity/AudioDevice.swift b/app/xcode/Sources/VCamEntity/AudioDevice.swift new file mode 100644 index 0000000..f068f5f --- /dev/null +++ b/app/xcode/Sources/VCamEntity/AudioDevice.swift @@ -0,0 +1,19 @@ +// +// AudioDevice.swift +// +// +// Created by Tatsuya Tanaka on 2022/04/24. +// + +import Foundation +import struct AVFAudio.AudioDeviceID + +public struct AudioDevice: Hashable, Identifiable { + public init(id: AudioDeviceID, uid: String) { + self.id = id + self.uid = uid + } + + public let id: AudioDeviceID + public let uid: String +} diff --git a/app/xcode/Sources/VCamMedia/AudioDevice+.swift b/app/xcode/Sources/VCamMedia/AudioDevice+.swift new file mode 100644 index 0000000..f43dfcf --- /dev/null +++ b/app/xcode/Sources/VCamMedia/AudioDevice+.swift @@ -0,0 +1,147 @@ +// +// AudioDevice+.swift +// +// +// Created by Tatsuya Tanaka on 2022/04/24. +// + +import Foundation +import CoreAudio +import VCamEntity + +extension AudioDevice { + public init(id: AudioDeviceID) { + self.init(id: id, uid: Self.getUid(id: id)) + } + + private static func getUid(id: AudioDeviceID) -> String { + var address = AudioObjectPropertyAddress( + mSelector: AudioObjectPropertySelector(kAudioDevicePropertyDeviceUID), + mScope: AudioObjectPropertyScope(kAudioObjectPropertyScopeGlobal), + mElement: AudioObjectPropertyElement(kAudioObjectPropertyElementMain) + ) + + var name: CFString? + var propsize: UInt32 = UInt32(MemoryLayout.size) + let result: OSStatus = AudioObjectGetPropertyData(id, &address, 0, nil, &propsize, &name) + if result != 0 { + return "" + } + + if let str = name { + return str as String + } + + return "" + } +} + +public extension AudioDevice { + static func defaultDevice() -> AudioDevice? { + var outputID: AudioDeviceID = 0 + var propsize = UInt32(MemoryLayout.size) + var address = AudioObjectPropertyAddress( + mSelector: kAudioHardwarePropertyDefaultInputDevice, + mScope: kAudioObjectPropertyScopeGlobal, + mElement: kAudioObjectPropertyElementMain + ) + let error = AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject), + &address, + 0, + nil, + &propsize, + &outputID) + if error != noErr { + print("defaultDevice error: \(error)") + return nil + } + return AudioDevice(id: outputID) + } + + func name() -> String { + var address = AudioObjectPropertyAddress( + mSelector: AudioObjectPropertySelector(kAudioDevicePropertyDeviceNameCFString), + mScope: AudioObjectPropertyScope(kAudioObjectPropertyScopeGlobal), + mElement: AudioObjectPropertyElement(kAudioObjectPropertyElementMain) + ) + + var name: CFString? + var propsize: UInt32 = UInt32(MemoryLayout.size) + let result: OSStatus = AudioObjectGetPropertyData(id, &address, 0, nil, &propsize, &name) + if result != 0 { + return "" + } + + let value = name as? String ?? "" + return value +// return value.hasPrefix("CADefaultDevice") ? L10n.default.text : value + } + + func bufferSize() -> UInt32 { + var address = AudioObjectPropertyAddress(mSelector: kAudioDevicePropertyAvailableNominalSampleRates, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMain) + var buf: UInt32 = 0 + var bufSize = UInt32(MemoryLayout.size) + + AudioObjectGetPropertyData(id, &address, 0, nil, &bufSize, &buf) + return buf + } + + func sampleRate() -> Float64 { + var address = AudioObjectPropertyAddress(mSelector: kAudioDevicePropertyActualSampleRate, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMain) + var buf: Float64 = 0 + var bufSize = UInt32(MemoryLayout.size) + + AudioObjectGetPropertyData(id, &address, 0, nil, &bufSize, &buf) + return buf + } + + func latencyTimeInterval() -> TimeInterval { + let sampleRate = sampleRate() + guard sampleRate > 0 else { + return 0 + } + + return TimeInterval( + safetyOffset(scope: kAudioObjectPropertyScopeInput) + + bufferFrameSize(scope: kAudioObjectPropertyScopeInput) + + safetyOffset(scope: kAudioObjectPropertyScopeOutput) + + bufferFrameSize(scope: kAudioObjectPropertyScopeOutput) + ) / sampleRate + } + + private func deviceLatency(scope: AudioObjectPropertyScope) -> UInt32 { + var address = AudioObjectPropertyAddress(mSelector: kAudioDevicePropertyLatency, mScope: scope, mElement: kAudioObjectPropertyElementMain) + var buf: UInt32 = 0 + var bufSize = UInt32(MemoryLayout.size) + + _ = AudioObjectGetPropertyData(id, &address, 0, nil, &bufSize, &buf) + return buf + } + + private func streamLatency(scope: AudioObjectPropertyScope) -> UInt32 { + var address = AudioObjectPropertyAddress(mSelector: kAudioStreamPropertyLatency, mScope: scope, mElement: kAudioObjectPropertyElementMain) + var buf: UInt32 = 0 + var bufSize = UInt32(MemoryLayout.size) + + _ = AudioObjectGetPropertyData(id, &address, 0, nil, &bufSize, &buf) + return buf + } + + private func safetyOffset(scope: AudioObjectPropertyScope) -> UInt32 { + var address = AudioObjectPropertyAddress(mSelector: kAudioDevicePropertySafetyOffset, mScope: scope, mElement: kAudioObjectPropertyElementMain) + var buf: UInt32 = 0 + var bufSize = UInt32(MemoryLayout.size) + + _ = AudioObjectGetPropertyData(id, &address, 0, nil, &bufSize, &buf) + return buf + } + + private func bufferFrameSize(scope: AudioObjectPropertyScope) -> UInt32 { + var address = AudioObjectPropertyAddress(mSelector: kAudioDevicePropertyBufferFrameSize, mScope: scope, mElement: kAudioObjectPropertyElementMain) + var buf: UInt32 = 0 + var bufSize = UInt32(MemoryLayout.size) + + _ = AudioObjectGetPropertyData(id, &address, 0, nil, &bufSize, &buf) + return buf + } +}