HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
audio_device_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_device.hpp"
8#include "audio_stream_format.hpp"
9#include "audio_stream_format_win32.hpp"
10#include "win32_device_interface.hpp"
11#include "win32_wave_device.hpp"
12#include "audio_format_range.hpp"
13#include "../algorithm/algorithm.hpp"
14#include "../macros.hpp"
15#include "../win32_headers.hpp"
16#include <algorithm>
17#include <vector>
18#include <coroutine>
19
20hi_export_module(hikogui.audio.audio_device_win32);
21
22hi_export namespace hi { inline namespace v1 {
23
26hi_export class audio_device_win32 : public audio_device {
27public:
28 audio_device_win32(IMMDevice *device) :
29 audio_device(), _previous_state(audio_device_state::uninitialized), _device(device), _audio_client(nullptr)
30 {
31 hi_assert(_device != nullptr);
32 _end_point_id = get_device_id(_device);
33 _id = std::string{"win32:"} + _end_point_id;
34 hi_hresult_check(_device->QueryInterface(&_end_point));
35 hi_hresult_check(_device->OpenPropertyStore(STGM_READ, &_property_store));
36
37 EDataFlow data_flow;
38 hi_hresult_check(_end_point->GetDataFlow(&data_flow));
39 switch (data_flow) {
40 case eRender:
41 _direction = audio_direction::output;
42 break;
43 case eCapture:
44 _direction = audio_direction::input;
45 break;
46 case eAll:
47 _direction = audio_direction::bidirectional;
48 break;
49 default:
50 hi_no_default();
51 }
52
53 update_state();
54 }
55
57 {
58 _property_store->Release();
59 _end_point->Release();
60 _device->Release();
61
62 if (_audio_client != nullptr) {
63 _audio_client->Release();
64 }
65 }
66
73 [[nodiscard]] static std::string get_device_id(IMMDevice *device)
74 {
75 hi_assert(device);
76
77 // Get the cross-reboot-unique-id-string of the device.
78 LPWSTR device_id;
79 hi_hresult_check(device->GetId(&device_id));
80 hi_assert(device_id);
81
82 auto device_id_ = hi::to_string(device_id);
83
84 CoTaskMemFree(device_id);
85 return device_id_;
86 }
87
89 void update_state() noexcept override
90 {
91 hi_axiom(loop::main().on_thread());
92
93 _name = end_point_name();
94
95 auto new_state = state();
96
97 // Log the correct message.
98 if (_previous_state == audio_device_state::uninitialized) {
99 hi_log_info(" * Found new audio device '{}' {} ({})", name(), id(), state());
100
101 } else if (_previous_state != new_state) {
102 hi_log_info(" * Audio device changed state '{}' {} ({})", name(), id(), state());
103 }
104
105 // Start and stop the audio device depending if it was enabled/disabled for some reason.
106 if (_previous_state == audio_device_state::active and new_state != audio_device_state::active) {
107 _audio_client->Release();
108 _audio_client = nullptr;
109
110 } else if (_previous_state != audio_device_state::active and new_state == audio_device_state::active) {
111 if (FAILED(_device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, reinterpret_cast<void **>(&_audio_client)))) {
112 hi_log_error("Audio device {} does not have IAudioClient interface", name());
113 _audio_client = nullptr;
114 }
115
116 update_supported_formats();
117
118 // By setting exclusivity to false at the start the audio stream format is initialized properly.
119 set_exclusive(false);
120 }
121 _previous_state = new_state;
122 }
123
124 [[nodiscard]] hi::label label() const noexcept override
125 {
126 return {elusive_icon::Speaker, txt("{}", name())};
127 }
128
129 [[nodiscard]] audio_device_state state() const noexcept override
130 {
131 DWORD state;
132 hi_hresult_check(_device->GetState(&state));
133
134 switch (state) {
135 case DEVICE_STATE_ACTIVE:
136 return audio_device_state::active;
137 case DEVICE_STATE_DISABLED:
138 return audio_device_state::disabled;
139 case DEVICE_STATE_NOTPRESENT:
140 return audio_device_state::not_present;
141 case DEVICE_STATE_UNPLUGGED:
142 return audio_device_state::unplugged;
143 default:
144 hi_no_default();
145 }
146 }
147
148 [[nodiscard]] audio_direction direction() const noexcept override
149 {
150 return _direction;
151 }
152
153 [[nodiscard]] bool exclusive() const noexcept override
154 {
155 return _exclusive;
156 }
157
158 void set_exclusive(bool exclusive) noexcept override
159 {
160 if (exclusive) {
161 _current_stream_format = find_exclusive_stream_format(_sample_rate, _speaker_mapping);
162 } else {
163 _current_stream_format = shared_stream_format();
164 }
165
166 _exclusive = exclusive;
167 }
168
169 [[nodiscard]] double sample_rate() const noexcept override
170 {
171 return _sample_rate;
172 }
173
174 void set_sample_rate(double sample_rate) noexcept override
175 {
176 _sample_rate = sample_rate;
177 }
178
179 [[nodiscard]] hi::speaker_mapping input_speaker_mapping() const noexcept override
180 {
181 switch (direction()) {
182 case audio_direction::input:
183 [[fallthrough]];
184 case audio_direction::bidirectional:
185 return _speaker_mapping;
186 case audio_direction::output:
187 return hi::speaker_mapping::none;
188 default:
189 hi_no_default();
190 }
191 }
192
193 void set_input_speaker_mapping(hi::speaker_mapping speaker_mapping) noexcept override
194 {
195 switch (direction()) {
196 case audio_direction::input:
197 [[fallthrough]];
198 case audio_direction::bidirectional:
199 _speaker_mapping = speaker_mapping;
200 break;
201 case audio_direction::output:
202 break;
203 default:
204 hi_no_default();
205 }
206 }
207
208 [[nodiscard]] std::vector<hi::speaker_mapping> available_input_speaker_mappings() const noexcept override
209 {
210 return {};
211 }
212
213 [[nodiscard]] hi::speaker_mapping output_speaker_mapping() const noexcept override
214 {
215 switch (direction()) {
216 case audio_direction::output:
217 [[fallthrough]];
218 case audio_direction::bidirectional:
219 return _speaker_mapping;
220 case audio_direction::input:
221 return hi::speaker_mapping::none;
222 default:
223 hi_no_default();
224 }
225 }
226
227 void set_output_speaker_mapping(hi::speaker_mapping speaker_mapping) noexcept override
228 {
229 switch (direction()) {
230 case audio_direction::output:
231 [[fallthrough]];
232 case audio_direction::bidirectional:
233 _speaker_mapping = speaker_mapping;
234 break;
235 case audio_direction::input:
236 break;
237 default:
238 hi_no_default();
239 }
240 }
241
242 [[nodiscard]] std::vector<hi::speaker_mapping> available_output_speaker_mappings() const noexcept override
243 {
244 return {};
245 }
246
247 [[nodiscard]] bool supports_format(audio_stream_format const& format) const noexcept
248 {
249 if (not win32_use_extensible(format)) {
250 // First try the simple format.
251 auto format_ = audio_stream_format_to_win32(format, false);
252 switch (_audio_client->IsFormatSupported(
253 AUDCLNT_SHAREMODE_EXCLUSIVE, reinterpret_cast<WAVEFORMATEX const *>(&format_), NULL)) {
254 case S_OK:
255 return true;
256 case S_FALSE:
257 case AUDCLNT_E_UNSUPPORTED_FORMAT:
258 break;
259 default:
260 hi_log_error("Failed to check format. {}", get_last_error_message());
261 }
262 }
263
264 // Always check the extensible format as fallback.
265 {
266 auto format_ = audio_stream_format_to_win32(format, true);
267 switch (_audio_client->IsFormatSupported(
268 AUDCLNT_SHAREMODE_EXCLUSIVE, reinterpret_cast<WAVEFORMATEX const *>(&format_), NULL)) {
269 case S_OK:
270 return true;
271 case S_FALSE:
272 case AUDCLNT_E_UNSUPPORTED_FORMAT:
273 break;
274 default:
275 hi_log_error("Failed to check format. {}", get_last_error_message());
276 }
277 }
278
279 return false;
280 }
282private:
283 std::string _end_point_id;
284 audio_device_state _previous_state;
285 audio_direction _direction;
286 bool _exclusive = false;
287 double _sample_rate = 0.0;
288 hi::speaker_mapping _speaker_mapping = hi::speaker_mapping::none;
289 audio_stream_format _current_stream_format;
290
291 IMMDevice *_device = nullptr;
292 IMMEndpoint *_end_point = nullptr;
293 IPropertyStore *_property_store = nullptr;
294 IAudioClient *_audio_client = nullptr;
295
296 template<typename T>
297 [[nodiscard]] static T get_property(IPropertyStore *property_store, REFPROPERTYKEY key)
298 {
299 hi_not_implemented();
300 }
301
302 template<>
303 [[nodiscard]] std::string get_property(IPropertyStore *property_store, REFPROPERTYKEY key)
304 {
305 hi_assert(property_store != nullptr);
306
307 PROPVARIANT property_value;
308 PropVariantInit(&property_value);
309
310 hi_hresult_check(property_store->GetValue(key, &property_value));
311 if (property_value.vt == VT_LPWSTR) {
312 auto value_wstring = std::wstring_view(property_value.pwszVal);
313 auto value_string = to_string(value_wstring);
314 PropVariantClear(&property_value);
315 return value_string;
316
317 } else {
318 PropVariantClear(&property_value);
319 throw io_error(std::format("Unexpected property value type {}.", static_cast<int>(property_value.vt)));
320 }
321 }
322
323 template<>
324 [[nodiscard]] uint32_t get_property(IPropertyStore *property_store, REFPROPERTYKEY key)
325 {
326 hi_assert(property_store != nullptr);
327
328 PROPVARIANT property_value;
329 PropVariantInit(&property_value);
330
331 hi_hresult_check(property_store->GetValue(key, &property_value));
332 if (property_value.vt == VT_UI4) {
333 auto value = narrow_cast<uint32_t>(property_value.ulVal);
334 PropVariantClear(&property_value);
335 return value;
336
337 } else {
338 PropVariantClear(&property_value);
339 throw io_error(std::format("Unexpected property value type {}.", static_cast<int>(property_value.vt)));
340 }
341 }
342
343 template<>
344 [[nodiscard]] GUID get_property(IPropertyStore *property_store, REFPROPERTYKEY key)
345 {
346 hi_assert(property_store != nullptr);
347
348 PROPVARIANT property_value;
349 PropVariantInit(&property_value);
350
351 hi_hresult_check(property_store->GetValue(key, &property_value));
352 if (property_value.vt == VT_CLSID) {
353 auto value = static_cast<GUID>(*property_value.puuid);
354 PropVariantClear(&property_value);
355 return value;
356
357 } else {
358 PropVariantClear(&property_value);
359 throw io_error(std::format("Unexpected property value type {}.", static_cast<int>(property_value.vt)));
360 }
361 }
362
363 [[nodiscard]] std::string end_point_name() const noexcept
364 {
365 try {
366 return get_property<std::string>(_property_store, PKEY_Device_FriendlyName);
367 } catch (io_error const&) {
368 return "<unknown name>";
369 }
370 }
371
376 [[nodiscard]] std::string device_name() const noexcept
377 {
378 try {
379 return get_property<std::string>(_property_store, PKEY_DeviceInterface_FriendlyName);
380 } catch (io_error const&) {
381 return "<unknown device name>";
382 }
383 }
384
388 [[nodiscard]] std::string end_point_description() const noexcept
389 {
390 try {
391 return get_property<std::string>(_property_store, PKEY_Device_DeviceDesc);
392 } catch (io_error const&) {
393 return "<unknown end point name>";
394 }
395 }
396
397 GUID pin_category() const noexcept
398 {
399 try {
400 return get_property<GUID>(_property_store, PKEY_AudioEndpoint_Association);
401 } catch (io_error const& e) {
402 hi_log_error("Could not get pin-category for audio end-point {}: {}", name(), e.what());
403 return {};
404 }
405 }
406
412 //unsigned int wave_id() const;
413
414 /*[[nodiscard]] std::optional<audio_stream_format>
415 best_format(double sample_rate, hi::speaker_mapping speaker_mapping) const noexcept
416 {
417 constexpr auto sample_formats = std::array{
418 audio_sample_format::float32(),
419 audio_sample_format::fix8_23(),
420 audio_sample_format::int24(),
421 audio_sample_format::int20(),
422 audio_sample_format::int16()};
423
424 for (auto const &sample_format : sample_formats) {
425 auto const format = audio_stream_format{sample_format, sample_rate, speaker_mapping};
426 if (supports_format(format)) {
427 return format;
428 }
429 }
430 return {};
431 }
432
433 [[nodiscard]] std::optional<audio_stream_format> best_format(double sample_rate, int num_channels) const
434 noexcept
435 {
436 constexpr auto sample_formats = std::array{
437 audio_sample_format::float32(),
438 audio_sample_format::fix8_23(),
439 audio_sample_format::int24(),
440 audio_sample_format::int20(),
441 audio_sample_format::int16()};
442
443 for (auto const &sample_format : sample_formats) {
444 auto const format = audio_stream_format{sample_format, sample_rate, num_channels};
445 if (supports_format(format)) {
446 return format;
447 }
448 }
449 return {};
450 }
451
452 [[nodiscard]] std::vector<audio_stream_format> enumerate_formats(double sample_rate) const noexcept
453 {
454 constexpr max_num_channels = 128;
455
456 auto r = std::vector<audio_stream_format>{};
457 r.reserve(size(speaker_mappings) + (max_num_channels / 2) + 2);
458
459 for (auto const &info: speaker_mappings) {
460 if (auto f = best_format(sample_rate, info.mapping)) {
461 r.push_back(*std::move(f));
462 }
463 }
464 for (auto num_channels = 1; num_channels != 4; ++num_channels) {
465 if (auto f = best_format(sample_rate, num_channels)) {
466 r.push_back(*std::move(f));
467 }
468 }
469 for (auto num_channels = 4; num_channels <= max_num_channels; num_channels += 2) {
470 if (auto f = best_format(sample_rate, num_channels)) {
471 r.push_back(*std::move(f));
472 }
473 }
474
475 return r;
476 }*/
477
480 void update_supported_formats() noexcept
481 {
482 // https://stackoverflow.com/questions/50396224/how-to-get-audio-formats-supported-by-physical-device-winapi-windows
483 // https://github.com/EddieRingle/portaudio/blob/master/src/os/win/pa_win_wdmks_utils.c
484 // https://docs.microsoft.com/en-us/previous-versions/ff561658(v=vs.85)
485
486 for (auto const& format_range : get_format_ranges()) {
487 hi_log_info(" * {}", format_range);
488 }
489 }
490
502 [[nodiscard]] audio_stream_format
503 find_exclusive_stream_format(double sample_rate, hi::speaker_mapping speaker_mapping) noexcept
504 {
505 return {};
506 }
507
510 [[nodiscard]] audio_stream_format shared_stream_format() const
511 {
512 if (not _audio_client) {
513 return {};
514 }
515
516 WAVEFORMATEX *ex;
517 hi_hresult_check(_audio_client->GetMixFormat(&ex));
518 hi_assert_not_null(ex);
519 auto const r = audio_stream_format_from_win32(*ex);
520 CoTaskMemFree(ex);
521 return r;
522 }
523
524 [[nodiscard]] generator<audio_format_range> get_format_ranges() const noexcept
525 {
526 // try {
527 auto wave_device = win32_wave_device::find_matching_end_point(direction(), _end_point_id);
528 auto device_interface = wave_device.open_device_interface();
529 auto format_ranges = make_vector(device_interface.get_format_ranges(direction()));
531
532 auto first = format_ranges.begin();
533 auto last = format_ranges.end();
534
535 // Split the ranged sample rates into individual sample rates.
536 auto it = first;
537 while (it != last) {
538 auto const format = audio_stream_format{it->format, it->min_sample_rate, it->num_channels};
539
540 // Eliminate bit-depths that are not supported.
541 if (not supports_format(format)) {
542 last = unordered_remove(first, last, it);
543 continue;
544 }
545
546 // Check the speaker mapping capability at this bit-depth and sample rate.
547 it->surround_mode_mask = surround_mode::none;
548 for (auto const mode : enumerate_surround_modes()) {
549 auto surround_format = format;
550 surround_format.speaker_mapping = to_speaker_mapping(mode);
551 surround_format.num_channels = narrow_cast<uint16_t>(popcount(surround_format.speaker_mapping));
552
553 if (surround_format.num_channels <= it->num_channels and supports_format(surround_format)) {
554 it->surround_mode_mask |= mode;
555 }
556 }
557
558 auto odd_rate_format = format;
559 ++odd_rate_format.sample_rate;
560 if (not supports_format(odd_rate_format)) {
561 // The device was lying that it could handle the full range of sample rates.
562 // Look for specific sample rates and create separate format ranges.
563 for (auto sample_rate : common_sample_rates) {
564 auto common_rate_format = format;
565 common_rate_format.sample_rate = sample_rate;
566
567 if (it->min_sample_rate <= sample_rate and sample_rate <= it->max_sample_rate and
568 supports_format(common_rate_format)) {
569 tmp.emplace_back(it->format, it->num_channels, sample_rate, sample_rate, it->surround_mode_mask);
570 }
571 }
572
573 last = unordered_remove(first, last, it);
574 continue;
575 }
576
577 ++it;
578 }
579 format_ranges.erase(last, format_ranges.end());
580 format_ranges.insert(format_ranges.cend(), tmp.cbegin(), tmp.cend());
581
582 std::sort(format_ranges.begin(), format_ranges.end(), std::greater{});
583 last = std::unique(format_ranges.begin(), format_ranges.end(), [](auto const& lhs, auto const& rhs) {
584 return equal_except_bit_depth(lhs, rhs);
585 });
586 format_ranges.erase(last, format_ranges.end());
587
588 for (auto& format_range : format_ranges) {
589 co_yield format_range;
590 }
591
592 //} catch (std::exception const& e) {
593 // hi_log_error("get_format_ranges() on audio device {}: {}", name(), e.what());
594 //}
595 }
596};
597
598}} // 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
constexpr std::vector< Value > make_vector(Range &&range)
Make a vector from a view.
Definition ranges.hpp:47
constexpr It unordered_remove(It first, It last, It element)
Remove element from a container.
Definition algorithm_misc.hpp:63
A set of audio channels which can be rendered and/or captures at the same time.
Definition audio_device.hpp:34
std::string name() const noexcept
Get a user friendly name of the audio device.
Definition audio_device.hpp:50
Definition audio_device_win32.hpp:26
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
T begin(T... args)
T sort(T... args)
T to_string(T... args)
T unique(T... args)