29 audio_device(), _previous_state(audio_device_state::uninitialized), _device(device), _audio_client(
nullptr)
31 hi_assert(_device !=
nullptr);
34 hi_hresult_check(_device->QueryInterface(&_end_point));
35 hi_hresult_check(_device->OpenPropertyStore(STGM_READ, &_property_store));
38 hi_hresult_check(_end_point->GetDataFlow(&data_flow));
41 _direction = audio_direction::output;
44 _direction = audio_direction::input;
47 _direction = audio_direction::bidirectional;
58 _property_store->Release();
59 _end_point->Release();
62 if (_audio_client !=
nullptr) {
63 _audio_client->Release();
79 hi_hresult_check(device->GetId(&device_id));
82 auto device_id_ = hi::to_string(device_id);
84 CoTaskMemFree(device_id);
89 void update_state() noexcept
override
91 hi_axiom(loop::main().on_thread());
93 _name = end_point_name();
95 auto new_state = state();
98 if (_previous_state == audio_device_state::uninitialized) {
99 hi_log_info(
" * Found new audio device '{}' {} ({})",
name(),
id(), state());
101 }
else if (_previous_state != new_state) {
102 hi_log_info(
" * Audio device changed state '{}' {} ({})",
name(),
id(), state());
106 if (_previous_state == audio_device_state::active and new_state != audio_device_state::active) {
107 _audio_client->Release();
108 _audio_client =
nullptr;
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;
116 update_supported_formats();
119 set_exclusive(
false);
121 _previous_state = new_state;
124 [[nodiscard]] hi::label label() const noexcept
override
126 return {elusive_icon::Speaker, txt(
"{}",
name())};
129 [[nodiscard]] audio_device_state state() const noexcept
override
132 hi_hresult_check(_device->GetState(&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;
148 [[nodiscard]] audio_direction direction() const noexcept
override
153 [[nodiscard]]
bool exclusive() const noexcept
override
158 void set_exclusive(
bool exclusive)
noexcept override
161 _current_stream_format = find_exclusive_stream_format(_sample_rate, _speaker_mapping);
163 _current_stream_format = shared_stream_format();
166 _exclusive = exclusive;
169 [[nodiscard]]
double sample_rate() const noexcept
override
174 void set_sample_rate(
double sample_rate)
noexcept override
176 _sample_rate = sample_rate;
179 [[nodiscard]] hi::speaker_mapping input_speaker_mapping() const noexcept
override
181 switch (direction()) {
182 case audio_direction::input:
184 case audio_direction::bidirectional:
185 return _speaker_mapping;
186 case audio_direction::output:
187 return hi::speaker_mapping::none;
193 void set_input_speaker_mapping(hi::speaker_mapping speaker_mapping)
noexcept override
195 switch (direction()) {
196 case audio_direction::input:
198 case audio_direction::bidirectional:
199 _speaker_mapping = speaker_mapping;
201 case audio_direction::output:
213 [[nodiscard]] hi::speaker_mapping output_speaker_mapping() const noexcept
override
215 switch (direction()) {
216 case audio_direction::output:
218 case audio_direction::bidirectional:
219 return _speaker_mapping;
220 case audio_direction::input:
221 return hi::speaker_mapping::none;
227 void set_output_speaker_mapping(hi::speaker_mapping speaker_mapping)
noexcept override
229 switch (direction()) {
230 case audio_direction::output:
232 case audio_direction::bidirectional:
233 _speaker_mapping = speaker_mapping;
235 case audio_direction::input:
247 [[nodiscard]]
bool supports_format(audio_stream_format
const& format)
const noexcept
249 if (not win32_use_extensible(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)) {
257 case AUDCLNT_E_UNSUPPORTED_FORMAT:
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)) {
272 case AUDCLNT_E_UNSUPPORTED_FORMAT:
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;
291 IMMDevice *_device =
nullptr;
292 IMMEndpoint *_end_point =
nullptr;
293 IPropertyStore *_property_store =
nullptr;
294 IAudioClient *_audio_client =
nullptr;
297 [[nodiscard]]
static T get_property(IPropertyStore *property_store, REFPROPERTYKEY key)
299 hi_not_implemented();
303 [[nodiscard]]
std::string get_property(IPropertyStore *property_store, REFPROPERTYKEY key)
305 hi_assert(property_store !=
nullptr);
307 PROPVARIANT property_value;
308 PropVariantInit(&property_value);
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);
318 PropVariantClear(&property_value);
319 throw io_error(std::format(
"Unexpected property value type {}.",
static_cast<int>(property_value.vt)));
324 [[nodiscard]] uint32_t get_property(IPropertyStore *property_store, REFPROPERTYKEY key)
326 hi_assert(property_store !=
nullptr);
328 PROPVARIANT property_value;
329 PropVariantInit(&property_value);
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);
338 PropVariantClear(&property_value);
339 throw io_error(std::format(
"Unexpected property value type {}.",
static_cast<int>(property_value.vt)));
344 [[nodiscard]] GUID get_property(IPropertyStore *property_store, REFPROPERTYKEY key)
346 hi_assert(property_store !=
nullptr);
348 PROPVARIANT property_value;
349 PropVariantInit(&property_value);
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);
358 PropVariantClear(&property_value);
359 throw io_error(std::format(
"Unexpected property value type {}.",
static_cast<int>(property_value.vt)));
363 [[nodiscard]]
std::string end_point_name() const noexcept
366 return get_property<std::string>(_property_store, PKEY_Device_FriendlyName);
367 }
catch (io_error
const&) {
368 return "<unknown name>";
376 [[nodiscard]]
std::string device_name() const noexcept
379 return get_property<std::string>(_property_store, PKEY_DeviceInterface_FriendlyName);
380 }
catch (io_error
const&) {
381 return "<unknown device name>";
388 [[nodiscard]]
std::string end_point_description() const noexcept
391 return get_property<std::string>(_property_store, PKEY_Device_DeviceDesc);
392 }
catch (io_error
const&) {
393 return "<unknown end point name>";
397 GUID pin_category() const noexcept
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());
480 void update_supported_formats() noexcept
486 for (
auto const& format_range : get_format_ranges()) {
487 hi_log_info(
" * {}", format_range);
502 [[nodiscard]] audio_stream_format
503 find_exclusive_stream_format(
double sample_rate, hi::speaker_mapping speaker_mapping)
noexcept
510 [[nodiscard]] audio_stream_format shared_stream_format()
const
512 if (not _audio_client) {
517 hi_hresult_check(_audio_client->GetMixFormat(&ex));
518 hi_assert_not_null(ex);
519 auto const r = audio_stream_format_from_win32(*ex);
524 [[nodiscard]] generator<audio_format_range> get_format_ranges() const noexcept
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()));
532 auto first = format_ranges.
begin();
533 auto last = format_ranges.end();
538 auto const format = audio_stream_format{it->format, it->min_sample_rate, it->num_channels};
541 if (not supports_format(format)) {
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));
553 if (surround_format.num_channels <= it->num_channels and supports_format(surround_format)) {
554 it->surround_mode_mask |= mode;
558 auto odd_rate_format = format;
559 ++odd_rate_format.sample_rate;
560 if (not supports_format(odd_rate_format)) {
563 for (
auto sample_rate : common_sample_rates) {
564 auto common_rate_format = format;
565 common_rate_format.sample_rate = sample_rate;
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);
579 format_ranges.erase(last, format_ranges.end());
580 format_ranges.insert(format_ranges.cend(), tmp.cbegin(), tmp.cend());
583 last =
std::unique(format_ranges.begin(), format_ranges.end(), [](
auto const& lhs,
auto const& rhs) {
584 return equal_except_bit_depth(lhs, rhs);
586 format_ranges.erase(last, format_ranges.end());
588 for (
auto& format_range : format_ranges) {
589 co_yield format_range;