ohmu
·
2026-10-15
1extern "C" {
2#include "sound.h"
3#include "common/debug.h"
4#include "common/windows/com.h"
5}
6#include "common/windows/unicode.hpp"
7#include "common/windows/variant.hpp"
8
9#include <initguid.h>
10#include <mmdeviceapi.h>
11#include <endpointvolume.h>
12#include <functiondiscoverykeys_devpkey.h>
13
14static void ffCoTaskMemFreeWrapper(void* pptr) {
15 assert(pptr != nullptr);
16 void* ptr = *(void**) pptr;
17 if (ptr) {
18 CoTaskMemFree(ptr);
19 }
20}
21#define FF_COTASK_AUTO_FREE [[gnu::cleanup(ffCoTaskMemFreeWrapper)]]
22
23static const char* detectSoundDevice(FFlist* devices /* List of FFSoundDevice */, IMMDevice* immDevice, LPWSTR mainDeviceId) {
24 FF_COTASK_AUTO_FREE LPWSTR immDeviceId = nullptr;
25 HRESULT hr = immDevice->GetId(&immDeviceId);
26 if (FAILED(hr)) {
27 FF_DEBUG("immDevice->GetId() failed: %s", ffDebugHResult(hr));
28 return "immDevice->GetId() failed";
29 }
30
31 FF_AUTO_RELEASE_COM_OBJECT IPropertyStore* immPropStore = nullptr;
32 hr = immDevice->OpenPropertyStore(STGM_READ, &immPropStore);
33 if (FAILED(hr)) {
34 FF_DEBUG("immDevice->OpenPropertyStore() failed: %s", ffDebugHResult(hr));
35 return "immDevice->OpenPropertyStore() failed";
36 }
37
38 DWORD immState;
39 hr = immDevice->GetState(&immState);
40 if (FAILED(hr)) {
41 FF_DEBUG("immDevice->GetState() failed: %s", ffDebugHResult(hr));
42 return "immDevice->GetState() failed";
43 }
44
45 FFSoundDevice* device = FF_LIST_ADD(FFSoundDevice, *devices);
46 device->type = (FFSoundType) ((!mainDeviceId || wcscmp(immDeviceId, mainDeviceId) == 0 ? FF_SOUND_TYPE_MAIN : FF_SOUND_TYPE_NONE) |
47 ((immState & DEVICE_STATE_ACTIVE) ? FF_SOUND_TYPE_ACTIVE : FF_SOUND_TYPE_NONE));
48 device->volume = FF_SOUND_VOLUME_UNKNOWN;
49 ffStrbufInitWS(&device->identifier, immDeviceId);
50 ffStrbufInit(&device->name);
51 ffStrbufInitStatic(&device->platformApi, "Core Audio APIs");
52
53 {
54 FFPropVariant friendlyName;
55 if (SUCCEEDED(immPropStore->GetValue(PKEY_Device_FriendlyName, &friendlyName))) {
56 ffStrbufSetWSV(&device->name, friendlyName.get<std::wstring_view>());
57 } else if (SUCCEEDED(immPropStore->GetValue(PKEY_Device_DeviceDesc, &friendlyName))) {
58 ffStrbufSetWSV(&device->name, friendlyName.get<std::wstring_view>());
59 } else {
60 ffStrbufSetStatic(&device->name, "Unknown Device");
61 }
62 }
63
64 FF_AUTO_RELEASE_COM_OBJECT IAudioEndpointVolume* immEndpointVolume = nullptr;
65 if (SUCCEEDED(immDevice->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, nullptr, (void**) &immEndpointVolume))) {
66 BOOL muted;
67 if (FAILED(immEndpointVolume->GetMute(&muted)) || !muted) {
68 FLOAT volume;
69 if (SUCCEEDED(immEndpointVolume->GetMasterVolumeLevelScalar(&volume))) {
70 device->volume = (uint8_t) (volume * 100 + 0.5);
71 }
72 }
73 }
74
75 return nullptr;
76}
77
78const char* ffDetectSound(FFSoundOptions* options, FFlist* devices /* List of FFSoundDevice */) {
79 const char* error = ffInitCom();
80 if (error) {
81 return error;
82 }
83
84 FF_AUTO_RELEASE_COM_OBJECT IMMDeviceEnumerator* pEnum = nullptr;
85
86 HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pEnum));
87 if (FAILED(hr)) {
88 FF_DEBUG("CoCreateInstance(CLSID_MMDeviceEnumerator) failed: %s", ffDebugHResult(hr));
89 return "CoCreateInstance(CLSID_MMDeviceEnumerator) failed";
90 }
91
92 FF_COTASK_AUTO_FREE LPWSTR mainDeviceId = nullptr;
93
94 {
95 FF_AUTO_RELEASE_COM_OBJECT IMMDevice* pDefaultDevice = nullptr;
96
97 HRESULT hrDefault = pEnum->GetDefaultAudioEndpoint(eRender, eMultimedia, &pDefaultDevice);
98 if (FAILED(hrDefault)) {
99 FF_DEBUG("GetDefaultAudioEndpoint() failed: %s", ffDebugHResult(hrDefault));
100 return "GetDefaultAudioEndpoint() failed";
101 }
102
103 if (options->soundType & FF_SOUND_TYPE_MAIN) {
104 return detectSoundDevice(devices, pDefaultDevice, nullptr);
105 }
106
107 hrDefault = pDefaultDevice->GetId(&mainDeviceId);
108 if (FAILED(hrDefault)) {
109 FF_DEBUG("pDefaultDevice->GetId() failed: %s", ffDebugHResult(hrDefault));
110 return "pDefaultDevice->GetId() failed";
111 }
112 }
113
114 FF_AUTO_RELEASE_COM_OBJECT IMMDeviceCollection* pDevices = nullptr;
115
116 hr = pEnum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE | (options->soundType & FF_SOUND_TYPE_ACTIVE ? 0 : DEVICE_STATE_DISABLED), &pDevices);
117 if (FAILED(hr)) {
118 FF_DEBUG("EnumAudioEndpoints() failed: %s", ffDebugHResult(hr));
119 return "EnumAudioEndpoints() failed";
120 }
121
122 uint32_t deviceCount;
123 hr = pDevices->GetCount(&deviceCount);
124 if (FAILED(hr)) {
125 FF_DEBUG("pDevices->GetCount() failed: %s", ffDebugHResult(hr));
126 return "pDevices->GetCount() failed";
127 }
128
129 for (uint32_t deviceIdx = 0; deviceIdx < deviceCount; ++deviceIdx) {
130 FF_AUTO_RELEASE_COM_OBJECT IMMDevice* immDevice = nullptr;
131 if (FAILED(pDevices->Item(deviceIdx, &immDevice))) {
132 continue;
133 }
134
135 detectSoundDevice(devices, immDevice, mainDeviceId);
136 }
137
138 return nullptr;
139}