aboutsummaryrefslogtreecommitdiffstats
path: root/alc
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2021-06-18 20:55:00 -0700
committerChris Robinson <[email protected]>2021-06-18 20:55:00 -0700
commita7d2e2974f3d15d416754ad0b7dcb5598811ac07 (patch)
tree1b74f15c4ce3e8c93512b7ec96f3693d390f17a0 /alc
parent302b6f3cede2ef60c7df424bef64a7be2c6f2afe (diff)
Initial attempt at CoreAudio playback enumeration
Diffstat (limited to 'alc')
-rw-r--r--alc/backends/coreaudio.cpp267
1 files changed, 253 insertions, 14 deletions
diff --git a/alc/backends/coreaudio.cpp b/alc/backends/coreaudio.cpp
index 006c3a76..01a81f06 100644
--- a/alc/backends/coreaudio.cpp
+++ b/alc/backends/coreaudio.cpp
@@ -45,6 +45,199 @@ namespace {
static const char ca_device[] = "CoreAudio Default";
+struct DeviceEntry {
+ AudioDeviceID mId;
+ std::string mName;
+};
+
+std::vector<DeviceEntry> PlaybackList;
+
+
+OSStatus GetHwProperty(AudioHardwarePropertyID propId, UInt32 dataSize, void *propData)
+{
+ const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ return AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, nullptr, &dataSize,
+ propData);
+}
+
+OSStatus GetHwPropertySize(AudioHardwarePropertyID propId, UInt32 *outSize)
+{
+ const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ return AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, 0, nullptr, outSize);
+}
+
+OSStatus GetDevProperty(AudioDeviceID devId, AudioDevicePropertyID propId, bool isCapture,
+ UInt32 elem, UInt32 dataSize, void *propData)
+{
+ static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
+ kAudioDevicePropertyScopeInput};
+ const AudioObjectPropertyAddress addr{propId, scopes[isCapture], elem};
+ return AudioObjectGetPropertyData(devId, &addr, 0, nullptr, &dataSize, propData);
+}
+
+OSStatus GetDevPropertySize(AudioDeviceID devId, AudioDevicePropertyID inPropertyID,
+ bool isCapture, UInt32 elem, UInt32 *outSize)
+{
+ static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
+ kAudioDevicePropertyScopeInput};
+ const AudioObjectPropertyAddress addr{inPropertyID, scopes[isCapture], elem};
+ return AudioObjectGetPropertyDataSize(devId, &addr, 0, nullptr, outSize);
+}
+
+
+std::string GetDeviceName(AudioDeviceID devId)
+{
+ std::string devname;
+ CFStringRef nameRef;
+
+ /* Try to get the device name as a CFString, for Unicode name support. */
+ OSStatus err{GetDevProperty(devId, kAudioDevicePropertyDeviceNameCFString, false, 0,
+ sizeof(nameRef), &nameRef)};
+ if(err == noErr)
+ {
+ UInt32 propSize{CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef),
+ kCFStringEncodingUTF8)};
+ devname.resize(propSize+1, '\0');
+
+ CFStringGetCString(nameRef, &devname[0], propSize+1, kCFStringEncodingUTF8);
+ CFRelease(nameRef);
+ }
+ else
+ {
+ /* If that failed, just get the C string. Hopefully there's nothing bad
+ * with this.
+ */
+ UInt32 propSize{};
+ if(GetDevPropertySize(devId, kAudioDevicePropertyDeviceName, false, 0, &propSize))
+ return devname;
+
+ devname.resize(propSize+1, '\0');
+ if(GetDevProperty(devId, kAudioDevicePropertyDeviceName, false, 0, propSize, &devname[0]))
+ {
+ devname.clear();
+ return devname;
+ }
+ }
+
+ /* Clear extraneous nul chars that may have been written with the name
+ * string, and return it.
+ */
+ while(!devname.back())
+ devname.pop_back();
+ return devname;
+}
+
+UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
+{
+ UInt32 propSize{};
+ auto err = GetDevPropertySize(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0,
+ &propSize);
+ if(err)
+ {
+ ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err);
+ return 0;
+ }
+
+ auto buffer_data = std::vector<char>(propSize);
+ AudioBufferList *buflist{::new(buffer_data.data()) AudioBufferList{}};
+
+ err = GetDevProperty(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0, propSize,
+ buflist);
+ if(err)
+ {
+ ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err);
+ al::destroy_at(buflist);
+ return 0;
+ }
+
+ UInt32 numChannels{0};
+ for(size_t i{0};i < buflist->mNumberBuffers;++i)
+ numChannels += buflist->mBuffers[i].mNumberChannels;
+
+ al::destroy_at(buflist);
+ return numChannels;
+}
+
+
+void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
+{
+ UInt32 propSize{};
+ if(auto err = GetHwPropertySize(kAudioHardwarePropertyDevices, &propSize))
+ {
+ ERR("Failed to get device list size: %u\n", err);
+ return;
+ }
+
+ auto devIds = std::vector<AudioDeviceID>(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown);
+ if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data()))
+ {
+ ERR("Failed to get device list: %u\n", err);
+ return;
+ }
+
+ std::vector<DeviceEntry> newdevs;
+ newdevs.reserve(devIds.size());
+
+ AudioDeviceID defaultId{kAudioDeviceUnknown};
+ GetHwProperty(isCapture ? kAudioHardwarePropertyDefaultInputDevice :
+ kAudioHardwarePropertyDefaultOutputDevice, sizeof(defaultId), &defaultId);
+
+ if(defaultId != kAudioDeviceUnknown)
+ {
+ newdevs.emplace_back(DeviceEntry{defaultId, GetDeviceName(defaultId)});
+ const auto &entry = newdevs.back();
+ TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
+ }
+ for(const AudioDeviceID devId : devIds)
+ {
+ if(devId == kAudioDeviceUnknown)
+ continue;
+
+ auto match_devid = [devId](const DeviceEntry &entry) noexcept -> bool
+ { return entry.mId == devId; };
+ auto match = std::find_if(newdevs.cbegin(), newdevs.cend(), match_devid);
+ if(match != newdevs.cend()) continue;
+
+ auto numChannels = GetDeviceChannelCount(devId, isCapture);
+ if(numChannels > 0)
+ {
+ newdevs.emplace_back(DeviceEntry{devId, GetDeviceName(devId)});
+ const auto &entry = newdevs.back();
+ TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
+ }
+ }
+
+ if(newdevs.size() > 1)
+ {
+ /* Rename entries that have matching names, by appending '#2', '#3',
+ * etc, as needed.
+ */
+ for(auto curitem = newdevs.begin()+1;curitem != newdevs.end();++curitem)
+ {
+ auto check_match = [curitem](const DeviceEntry &entry) -> bool
+ { return entry.mName == curitem->mName; };
+ if(std::find_if(newdevs.begin(), curitem, check_match) != curitem)
+ {
+ std::string name{curitem->mName};
+ size_t count{1};
+ auto check_name = [&name](const DeviceEntry &entry) -> bool
+ { return entry.mName == name; };
+ do {
+ name = curitem->mName;
+ name += " #";
+ name += std::to_string(++count);
+ } while(std::find_if(newdevs.begin(), curitem, check_name) != curitem);
+ curitem->mName = std::move(name);
+ }
+ }
+ }
+
+ newdevs.shrink_to_fit();
+ newdevs.swap(list);
+}
+
struct CoreAudioPlayback final : public BackendBase {
CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
@@ -96,11 +289,32 @@ OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTi
void CoreAudioPlayback::open(const char *name)
{
+#if TARGET_OS_IOS || TARGET_OS_TV
if(!name)
name = ca_device;
else if(strcmp(name, ca_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
+#else
+ AudioDeviceID audioDevice{kAudioDeviceUnknown};
+ if(!name)
+ GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice),
+ &audioDevice);
+ else
+ {
+ if(PlaybackList.empty())
+ EnumerateDevices(PlaybackList, false);
+
+ auto find_name = [name](const DeviceEntry &entry) -> bool
+ { return entry.mName == name; };
+ auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name);
+ if(devmatch == PlaybackList.cend())
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Device name \"%s\" not found", name};
+
+ audioDevice = devmatch->mId;
+ }
+#endif
/* open the default output unit */
AudioComponentDescription desc{};
@@ -108,7 +322,8 @@ void CoreAudioPlayback::open(const char *name)
#if TARGET_OS_IOS || TARGET_OS_TV
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#else
- desc.componentSubType = kAudioUnitSubType_DefaultOutput;
+ desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
+ kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
#endif
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
@@ -124,6 +339,12 @@ void CoreAudioPlayback::open(const char *name)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not create component instance: %u", err};
+#if !TARGET_OS_IOS && !TARGET_OS_TV
+ if(audioDevice != kAudioDeviceUnknown)
+ AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global, 0, &audioDevice, sizeof(AudioDeviceID));
+#endif
+
err = AudioUnitInitialize(audioUnit);
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
@@ -139,7 +360,19 @@ void CoreAudioPlayback::open(const char *name)
}
mAudioUnit = audioUnit;
- mDevice->DeviceName = name;
+ if(name)
+ mDevice->DeviceName = name;
+ else
+ {
+ UInt32 propSize{sizeof(audioDevice)};
+ audioDevice = kAudioDeviceUnknown;
+ AudioUnitGetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global, 0, &audioDevice, &propSize);
+
+ std::string devname{GetDeviceName(audioDevice)};
+ if(!devname.empty()) mDevice->DeviceName = std::move(devname);
+ else mDevice->DeviceName = "Unknown Device Name";
+ }
}
bool CoreAudioPlayback::reset()
@@ -449,25 +682,18 @@ void CoreAudioCapture::open(const char *name)
#if !TARGET_OS_IOS && !TARGET_OS_TV
{
// Get the default input device
- AudioDeviceID inputDevice = kAudioDeviceUnknown;
-
- propertySize = sizeof(AudioDeviceID);
- AudioObjectPropertyAddress propertyAddress{};
- propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
- propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
- propertyAddress.mElement = kAudioObjectPropertyElementMaster;
-
- err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, nullptr,
- &propertySize, &inputDevice);
+ AudioDeviceID defaultId{kAudioDeviceUnknown};
+ err = GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(defaultId),
+ &defaultId);
if(err != noErr)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not get input device: %u", err};
- if(inputDevice == kAudioDeviceUnknown)
+ if(defaultId == kAudioDeviceUnknown)
throw al::backend_exception{al::backend_error::NoDevice, "Unknown input device"};
// Track the input device
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
- kAudioUnitScope_Global, 0, &inputDevice, sizeof(AudioDeviceID));
+ kAudioUnitScope_Global, 0, &defaultId, sizeof(AudioDeviceID));
if(err != noErr)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not set input device: %u", err};
@@ -675,9 +901,22 @@ bool CoreAudioBackendFactory::querySupport(BackendType type)
std::string CoreAudioBackendFactory::probe(BackendType type)
{
std::string outnames;
+ auto append_name = [&outnames](const DeviceEntry &entry) -> void
+ {
+ /* Includes null char. */
+ outnames.append(entry.mName.c_str(), entry.mName.length()+1);
+ };
+
switch(type)
{
case BackendType::Playback:
+#if !TARGET_OS_IOS && !TARGET_OS_TV
+ EnumerateDevices(PlaybackList, false);
+ std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
+ break;
+#else
+ /*fall-through*/
+#endif
case BackendType::Capture:
/* Includes null char. */
outnames.append(ca_device, sizeof(ca_device));