ohmu
·
2026-10-15
1#include "sound.h"
2#include "common/apple/cf_helpers.h"
3#include "common/debug.h"
4
5#include <CoreAudio/CoreAudio.h>
6#include <AvailabilityMacros.h>
7
8#ifndef MAC_OS_VERSION_12_0
9 #define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster
10#endif
11
12const char* ffDetectSound(FFSoundOptions* options, FFlist* devices /* List of FFSoundDevice */) {
13 AudioDeviceID mainDeviceId;
14 UInt32 dataSize = sizeof(mainDeviceId);
15 OSStatus defaultOutputStatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, &(AudioObjectPropertyAddress) { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, &mainDeviceId);
16 if (defaultOutputStatus != kAudioHardwareNoError) {
17 FF_DEBUG("AudioObjectGetPropertyData(kAudioHardwarePropertyDefaultOutputDevice) failed: OSStatus %d", (int) defaultOutputStatus);
18 return "AudioObjectGetPropertyData(kAudioHardwarePropertyDefaultOutputDevice) failed";
19 }
20
21 AudioObjectID deviceIds[32] = {};
22 if (options->soundType & FF_SOUND_TYPE_MAIN) {
23 deviceIds[0] = mainDeviceId;
24 dataSize = sizeof(mainDeviceId);
25 } else {
26 dataSize = sizeof(deviceIds);
27 OSStatus deviceListStatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, &(AudioObjectPropertyAddress) { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, &deviceIds);
28 if (deviceListStatus != kAudioHardwareNoError) {
29 FF_DEBUG("AudioObjectGetPropertyData(kAudioHardwarePropertyDevices) failed: OSStatus %d", (int) deviceListStatus);
30 return "AudioObjectGetPropertyData(kAudioHardwarePropertyDevices) failed";
31 }
32 }
33
34 for (uint32_t index = 0, length = dataSize / sizeof(*deviceIds); index < length; ++index) {
35 AudioDeviceID deviceId = deviceIds[index];
36
37 // Ignore input devices
38 if (AudioObjectGetPropertyDataSize(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyStreams, kAudioObjectPropertyScopeInput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize) == kAudioHardwareNoError && dataSize > 0) {
39 continue;
40 }
41
42 uint32_t dataSource;
43 dataSize = sizeof(dataSource);
44 if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyDataSource, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, &dataSource) == kAudioHardwareNoError && dataSource == 'hdpn') {
45 uint32_t connected;
46 dataSize = sizeof(connected);
47 if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyJackIsConnected, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, &connected) == kAudioHardwareNoError) {
48 if (!connected) {
49 continue;
50 }
51 }
52 }
53
54 uint32_t active = true;
55 dataSize = sizeof(active);
56 if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, &active) == kAudioHardwareNoError) {
57 if ((options->soundType & FF_SOUND_TYPE_ACTIVE) && !active) {
58 continue;
59 }
60 }
61
62 FFSoundDevice* device = FF_LIST_ADD(FFSoundDevice, *devices);
63 device->type = (deviceId == mainDeviceId ? FF_SOUND_TYPE_MAIN : FF_SOUND_TYPE_NONE) |
64 (active ? FF_SOUND_TYPE_ACTIVE : FF_SOUND_TYPE_NONE);
65 device->volume = FF_SOUND_VOLUME_UNKNOWN;
66 ffStrbufInit(&device->identifier);
67 ffStrbufInit(&device->name);
68 ffStrbufInitStatic(&device->platformApi, "Core Audio");
69
70 FF_CFTYPE_AUTO_RELEASE CFStringRef uid = nullptr;
71 dataSize = sizeof(uid);
72 if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyDeviceUID, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, &uid) == kAudioHardwareNoError) {
73 ffCfStrGetString(uid, &device->identifier);
74 } else {
75 ffStrbufAppendF(&device->identifier, "ID-%u", (unsigned) deviceId);
76 }
77
78 FF_CFTYPE_AUTO_RELEASE CFStringRef name = nullptr;
79 dataSize = sizeof(name);
80 if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioObjectPropertyName, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, &name) == kAudioHardwareNoError) {
81 ffCfStrGetString(name, &device->name);
82 } else {
83 ffStrbufSet(&device->name, &device->identifier);
84 }
85
86 uint32_t muted;
87 dataSize = sizeof(muted);
88 if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyMute, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, &muted) != kAudioHardwareNoError) {
89 muted = false; // Device may not support volume control
90 }
91
92 if (muted) {
93 device->volume = 0;
94 } else {
95 float volume;
96 dataSize = sizeof(volume);
97 if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyVolumeScalar, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, &volume) == kAudioHardwareNoError) {
98 device->volume = (uint8_t) (volume * 100 + 0.5);
99 } else {
100 // Try detecting volume from channels
101 uint32_t channels[2];
102 dataSize = sizeof(channels);
103 if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyPreferredChannelsForStereo, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, nullptr, &dataSize, channels) == kAudioHardwareNoError) {
104 dataSize = sizeof(volume);
105 if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyVolumeScalar, kAudioObjectPropertyScopeOutput, channels[0] }, 0, nullptr, &dataSize, &volume) == kAudioHardwareNoError) {
106 float temp;
107 if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyVolumeScalar, kAudioObjectPropertyScopeOutput, channels[1] }, 0, nullptr, &dataSize, &temp) == kAudioHardwareNoError) {
108 device->volume = (uint8_t) ((volume + temp) / 2 * 100 + 0.5);
109 }
110 }
111 }
112 }
113 }
114 }
115
116 return nullptr;
117}