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