HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
audio_system_win32.hpp
1// Copyright Take Vos 2020-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "audio_system.hpp"
8#include "audio_device_win32.hpp"
9#include "../container/container.hpp"
10#include "../memory/memory.hpp"
11#include "../macros.hpp"
12#include "../win32_headers.hpp"
13#include <memory>
14#include <coroutine>
15
16hi_export_module(hikogui.audio.audio_system_win32);
17
18hi_export namespace hi { inline namespace v1 {
19class audio_system_win32;
20
21hi_export struct audio_system_win32_event {
22 virtual void handle_event(audio_system_win32 *self) noexcept = 0;
23};
24
25hi_export class audio_system_win32 : public audio_system {
26public:
27 using super = audio_system;
28
29 audio_system_win32() : super(), _notification_client(std::make_unique<audio_system_win32_notification_client>(this))
30 {
31 hi_hresult_check(CoInitializeEx(NULL, COINIT_MULTITHREADED));
32
33 hi_hresult_check(CoCreateInstance(
34 __uuidof(MMDeviceEnumerator),
35 NULL,
36 CLSCTX_ALL,
37 __uuidof(IMMDeviceEnumerator),
38 reinterpret_cast<LPVOID *>(&_device_enumerator)));
39 hi_assert(_device_enumerator);
40
41 _device_enumerator->RegisterEndpointNotificationCallback(_notification_client.get());
42
43 // Start with enumerating the devices.
44 update_device_list();
45 }
46
47 virtual ~audio_system_win32()
48 {
49 if (_device_enumerator) {
50 _device_enumerator->UnregisterEndpointNotificationCallback(_notification_client.get());
51 _device_enumerator->Release();
52 }
53 }
54
55 [[nodiscard]] generator<audio_device&> devices() noexcept override
56 {
57 for (auto const& device : _devices) {
58 co_yield *device;
59 }
60 }
61
62private:
63 class audio_system_win32_notification_client : public IMMNotificationClient {
64 public:
65 virtual ~audio_system_win32_notification_client() = default;
66
67 audio_system_win32_notification_client(audio_system_win32 *system) : IMMNotificationClient(), _system(system) {}
68
69 STDMETHOD(OnDefaultDeviceChanged)(EDataFlow flow, ERole role, LPCWSTR device_id) override
70 {
71 loop::main().wfree_post_function([this]() {
72 _system->update_device_list();
73 _system->_notifier();
74 });
75 return S_OK;
76 }
77
78 STDMETHOD(OnDeviceAdded)(LPCWSTR device_id) override
79 {
80 loop::main().wfree_post_function([this]() {
81 _system->update_device_list();
82 _system->_notifier();
83 });
84 return S_OK;
85 }
86
87 STDMETHOD(OnDeviceRemoved)(LPCWSTR device_id) override
88 {
89 // OnDeviceRemoved can not be implemented according to the win32 specification due
90 // to conflicting requirements.
91 // 1. This function may not block.
92 // 2. The string pointed by device_id will not exist after this function.
93 // 2. We need to copy device_id which has an unbounded size.
94 // 3. Allocating a string blocks.
95
96 hi_assert_not_null(device_id);
97 loop::main().wfree_post_function([this]() {
98 _system->update_device_list();
99 _system->_notifier();
100 });
101 return S_OK;
102 }
103
104 STDMETHOD(OnDeviceStateChanged)(LPCWSTR device_id, DWORD state) override
105 {
106 loop::main().wfree_post_function([this]() {
107 _system->update_device_list();
108 _system->_notifier();
109 });
110 return S_OK;
111 }
112
113 STDMETHOD(OnPropertyValueChanged)(LPCWSTR device_id, PROPERTYKEY const key) override
114 {
115 loop::main().wfree_post_function([this]() {
116 _system->update_device_list();
117 _system->_notifier();
118 });
119 return S_OK;
120 }
121
122 STDMETHOD(QueryInterface)(REFIID iid, void **object) override
123 {
124 hi_no_default();
125 }
126
127 STDMETHOD_(ULONG, AddRef)() override
128 {
129 hi_no_default();
130 }
131
132 STDMETHOD_(ULONG, Release)() override
133 {
134 hi_no_default();
135 }
136
137 private:
138 audio_system_win32 *_system;
139 };
140
151
152 IMMDeviceEnumerator *_device_enumerator;
154
157 void update_device_list() noexcept
158 {
159 hi_axiom(loop::main().on_thread());
160 hi_log_info("Updating audio device list:");
161
162 IMMDeviceCollection *device_collection;
163 if (FAILED(_device_enumerator->EnumAudioEndpoints(
164 eAll, DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED | DEVICE_STATE_UNPLUGGED, &device_collection))) {
165 hi_log_error("EnumAudioEndpoints() failed: {}", get_last_error_message());
166 return;
167 }
168 hi_assert(device_collection);
169
170 UINT number_of_devices;
171 if (FAILED(device_collection->GetCount(&number_of_devices))) {
172 hi_log_error("EnumAudioEndpoints()->GetCount() failed: {}", get_last_error_message());
173 device_collection->Release();
174 return;
175 }
176
177 auto old_devices = _devices;
178 _devices.clear();
179 for (UINT i = 0; i < number_of_devices; i++) {
180 IMMDevice *win32_device;
181 if (FAILED(device_collection->Item(i, &win32_device))) {
182 hi_log_error("EnumAudioEndpoints()->Item({}) failed: {}", i, get_last_error_message());
183 device_collection->Release();
184 return;
185 }
186 hi_assert(win32_device);
187
188 auto win32_device_id = std::string{};
189 try {
190 win32_device_id = std::string{"win32:"} + audio_device_win32::get_device_id(win32_device);
191 } catch (std::exception const& e) {
192 hi_log_error("EnumAudioEndpoints()->Item({})->get_device_id failed: {}", i, e.what());
193 device_collection->Release();
194 win32_device->Release();
195 return;
196 }
197
198 auto it = std::find_if(old_devices.begin(), old_devices.end(), [&win32_device_id](auto& item) {
199 return item->id() == win32_device_id;
200 });
201
202 if (it != old_devices.end()) {
203 // This device was already instantiated.
204 win32_device->Release();
205 _devices.push_back(std::move(*it));
206 old_devices.erase(it);
207
208 // Let the audio device them self see if anything has changed in their own state.
209 _devices.back()->update_state();
210
211 } else {
212 auto device =
213 std::allocate_shared<audio_device_win32>(locked_memory_allocator<audio_device_win32>{}, win32_device);
214 // hi_log_info(
215 // "Found audio device \"{}\", state={}, channels={}, speakers={}",
216 // device->name(),
217 // device->state(),
218 // device->full_num_channels(),
219 // device->full_channel_mapping());
220 _devices.push_back(std::move(device));
221 }
222 }
223
224 device_collection->Release();
225 }
226
227 friend class audio_system_win32_notification_client;
228};
229
230hi_export [[nodiscard]] inline audio_system &audio_system::global() noexcept
231{
232 if (not detail::audio_system_global) {
233 auto tmp = std::make_unique<audio_system_aggregate>();
234 tmp->add_child(std::make_unique<audio_system_win32>());
235 // Possibly add asio here as well.
236
237 detail::audio_system_global = std::move(tmp);
238 }
239 return *detail::audio_system_global;
240}
241
242}} // namespace hi::inline v1
Rules for working with win32 headers.
The HikoGUI namespace.
Definition array_generic.hpp:20
hi_export std::string get_last_error_message()
Get the OS error message from the last error received on this thread.
Definition exception_win32_impl.hpp:30
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
static std::string get_device_id(IMMDevice *device)
Get the device id for the given win32 audio end-point.
Definition audio_device_win32.hpp:73
Definition audio_system.hpp:22
static audio_system & global() noexcept
Create an audio system object specific for the current operating system.
Definition audio_system_win32.hpp:230
Definition audio_system_win32.hpp:21
Definition audio_system_win32.hpp:25
generator< audio_device & > devices() noexcept override
The devices that are part of the audio system.
Definition audio_system_win32.hpp:55
T back(T... args)
T clear(T... args)
T find_if(T... args)
T get(T... args)
T move(T... args)
T push_back(T... args)
T what(T... args)