6#include <QtCore/qdebug.h>
7#include <QtCore/qloggingcategory.h>
8#include <QtCore/qthreadpool.h>
9#include <QtCore/qt_windows.h>
10#include <QtCore/private/qsystemerror_p.h>
11#include <QtCore/qapplicationstatic.h>
13#include <QtMultimedia/private/qaudioformat_p.h>
14#include <QtMultimedia/private/qcominitializer_p.h>
15#include <QtMultimedia/private/qcomtaskresource_p.h>
16#include <QtMultimedia/private/qwindows_propertystore_p.h>
17#include <QtMultimedia/private/qwindowsaudioutils_p.h>
19#include <audioclient.h>
20#include <mmdeviceapi.h>
21#include <propkeydef.h>
28using QtMultimediaPrivate::PropertyStoreHelper;
29using namespace Qt::Literals;
33Q_STATIC_LOGGING_CATEGORY(qLcAudioDeviceProbes,
"qt.multimedia.audiodevice.probes")
35std::optional<EndpointFormFactor> inferFormFactor(PropertyStoreHelper &propertyStore)
37 std::optional<uint32_t> val = propertyStore.getUInt32(PKEY_AudioEndpoint_FormFactor);
38 if (val == EndpointFormFactor::UnknownFormFactor)
39 return EndpointFormFactor(*val);
44std::optional<QAudioFormat::ChannelConfig>
45inferChannelConfiguration(PropertyStoreHelper &propertyStore,
int maximumChannelCount)
47 std::optional<uint32_t> val = propertyStore.getUInt32(PKEY_AudioEndpoint_PhysicalSpeakers);
49 return QWindowsAudioUtils::maskToChannelConfig(*val, maximumChannelCount);
54int maxChannelCountForFormFactor(EndpointFormFactor formFactor)
57 case EndpointFormFactor::Headphones:
58 case EndpointFormFactor::Headset:
60 case EndpointFormFactor::SPDIF:
63 case EndpointFormFactor::DigitalAudioDisplayDevice:
66 case EndpointFormFactor::Microphone:
74struct FormatProbeResult
76 void update(
const QAudioFormat &fmt)
78 supportedSampleFormats.insert(fmt.sampleFormat());
79 updateChannelCount(fmt.channelCount());
80 updateSamplingRate(fmt.sampleRate());
83 void updateChannelCount(
int channelCount)
85 if (channelCount < channelCountRange.first)
86 channelCountRange.first = channelCount;
87 if (channelCount > channelCountRange.second)
88 channelCountRange.second = channelCount;
91 void updateSamplingRate(
int samplingRate)
93 if (samplingRate < sampleRateRange.first)
94 sampleRateRange.first = samplingRate;
95 if (samplingRate > sampleRateRange.second)
96 sampleRateRange.second = samplingRate;
99 std::set<QAudioFormat::SampleFormat> supportedSampleFormats;
100 std::pair<
int,
int> channelCountRange{ std::numeric_limits<
int>::max(), 0 };
101 std::pair<
int,
int> sampleRateRange{ std::numeric_limits<
int>::max(), 0 };
104 friend QDebug operator<<(QDebug dbg,
const FormatProbeResult &self)
106 QDebugStateSaver saver(dbg);
109 dbg <<
"FormatProbeResult{supportedSampleFormats: " << self.supportedSampleFormats
110 <<
", channelCountRange: " << self.channelCountRange.first <<
" - " << self.channelCountRange.second
111 <<
", sampleRateRange: " << self.sampleRateRange.first <<
"-" << self.sampleRateRange.second
117std::optional<QAudioFormat> performIsFormatSupportedWithClosestMatch(
const ComPtr<IAudioClient> &audioClient,
118 const QAudioFormat &fmt)
120 using namespace QWindowsAudioUtils;
121 std::optional<WAVEFORMATEXTENSIBLE> formatEx = toWaveFormatExtensible(fmt);
123 qCWarning(qLcAudioDeviceProbes) <<
"toWaveFormatExtensible failed" << fmt;
127 qCDebug(qLcAudioDeviceProbes) <<
"performIsFormatSupportedWithClosestMatch for" << fmt;
128 QComTaskResource<WAVEFORMATEX> closestMatch;
129 HRESULT result = audioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &formatEx->Format,
130 closestMatch.address());
132 if (FAILED(result)) {
133 qCDebug(qLcAudioDeviceProbes) <<
"performIsFormatSupportedWithClosestMatch: error" << QSystemError::windowsComString(result);
138 QAudioFormat closestMatchFormat = waveFormatExToFormat(*closestMatch);
139 qCDebug(qLcAudioDeviceProbes) <<
"performProbe returned closest match" << closestMatchFormat;
140 return closestMatchFormat;
143 qCDebug(qLcAudioDeviceProbes) <<
"performProbe successful";
148std::optional<FormatProbeResult> probeFormats(
const ComPtr<IAudioClient> &audioClient,
149 PropertyStoreHelper &propertyStore,
150 const QAudioFormat &preferredFormat)
152 using namespace QWindowsAudioUtils;
155 std::optional<EndpointFormFactor> formFactor = inferFormFactor(propertyStore);
156 int maxChannelsForFormFactor = formFactor ? maxChannelCountForFormFactor(*formFactor) : 128;
158 qCDebug(qLcAudioDeviceProbes) <<
"probing: maxChannelsForFormFactor" << maxChannelsForFormFactor << formFactor;
160 std::optional<FormatProbeResult> limits;
164 constexpr QAudioFormat::SampleFormat initialSampleFormat = QAudioFormat::SampleFormat::Float;
169 QAudioFormat initialProbeFormat;
170 initialProbeFormat.setSampleFormat(initialSampleFormat);
171 initialProbeFormat.setSampleRate(preferredFormat.sampleRate());
172 initialProbeFormat.setChannelCount(maxChannelsForFormFactor);
174 qCDebug(qLcAudioDeviceProbes) <<
"probeFormats: probing for" << initialProbeFormat;
176 std::optional<QAudioFormat> initialProbeResult =
177 performIsFormatSupportedWithClosestMatch(audioClient, initialProbeFormat);
179 int maxChannelForFormat;
180 if (initialProbeResult) {
181 if (initialProbeResult->sampleRate() != preferredFormat.sampleRate()) {
182 qCDebug(qLcAudioDeviceProbes)
183 <<
"probing: returned a different sample rate as closest match ..."
184 << *initialProbeResult;
188 maxChannelForFormat = initialProbeResult->channelCount();
194 maxChannelForFormat =
std::min(maxChannelsForFormFactor, 2);
199 QAudioFormat::SampleFormat probeSampleFormat =
200 initialProbeResult ? initialProbeResult->sampleFormat() : initialSampleFormat;
202 for (
int channelCount = 1; channelCount != maxChannelForFormat + 1; ++channelCount) {
204 fmt.setSampleFormat(probeSampleFormat);
205 fmt.setSampleRate(preferredFormat.sampleRate());
206 fmt.setChannelCount(channelCount);
208 std::optional<WAVEFORMATEXTENSIBLE> formatEx = toWaveFormatExtensible(fmt);
212 qCDebug(qLcAudioDeviceProbes) <<
"probing" << fmt;
214 QComTaskResource<WAVEFORMATEX> closestMatch;
215 HRESULT result = audioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &formatEx->Format,
216 closestMatch.address());
218 if (FAILED(result)) {
219 qCDebug(qLcAudioDeviceProbes)
220 <<
"probing format failed" << QSystemError::windowsComString(result);
225 qCDebug(qLcAudioDeviceProbes) <<
"probing format reported a closest match"
226 << waveFormatExToFormat(*closestMatch);
231 limits = FormatProbeResult{};
233 qCDebug(qLcAudioDeviceProbes) <<
"probing format successful" << fmt;
237 qCDebug(qLcAudioDeviceProbes) <<
"probing successful" << limits;
242std::optional<QAudioFormat> probePreferredFormat(
const ComPtr<IAudioClient> &audioClient)
244 using namespace QWindowsAudioUtils;
246 static const QAudioFormat preferredFormat = [] {
248 fmt.setSampleRate(44100);
249 fmt.setChannelCount(2);
250 fmt.setSampleFormat(QAudioFormat::Int16);
254 std::optional<WAVEFORMATEXTENSIBLE> formatEx = toWaveFormatExtensible(preferredFormat);
258 QComTaskResource<WAVEFORMATEX> closestMatch;
259 HRESULT result = audioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &formatEx->Format,
260 closestMatch.address());
265 return preferredFormat;
267 QAudioFormat closestMatchFormat = waveFormatExToFormat(*closestMatch);
268 if (closestMatchFormat.isValid())
269 return closestMatchFormat;
273struct WindowsFormatResult
275 QAudioDevicePrivate::AudioDeviceFormat format;
279WindowsFormatResult performFormatProbe(ComPtr<IMMDevice> immDev)
281 QAudioDevicePrivate::AudioDeviceFormat format;
284 { QtMultimediaPrivate::allSupportedSampleRates.front(),
285 QtMultimediaPrivate::allSupportedSampleRates.back() },
288 ComPtr<IAudioClient> audioClient;
289 HRESULT hr = immDev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER,
nullptr,
290 reinterpret_cast<
void **>(audioClient.GetAddressOf()));
293 QComTaskResource<WAVEFORMATEX> mixFormat;
294 hr = audioClient->GetMixFormat(mixFormat.address());
296 format.preferredFormat = QWindowsAudioUtils::waveFormatExToFormat(*mixFormat);
298 qWarning() <<
"QWindowsAudioDeviceInfo: could not activate audio client:"
299 << QSystemError::windowsComString(hr);
300 return {format, probeData};
303 auto propStoreHelper = PropertyStoreHelper::open(immDev);
304 if (!propStoreHelper) {
305 qWarning() <<
"QWindowsAudioDeviceInfo: could not open property store:"
306 << propStoreHelper.error();
307 return {format, probeData};
310 qCDebug(qLcAudioDeviceProbes) <<
"probing formats";
312 std::optional<FormatProbeResult> probedFormats =
313 probeFormats(audioClient, *propStoreHelper, format.preferredFormat);
316 format.supportedSampleFormats = qAllSupportedSampleFormats();
321 format.minimumSampleRate = QtMultimediaPrivate::allSupportedSampleRates.front();
322 format.maximumSampleRate = QtMultimediaPrivate::allSupportedSampleRates.back();
324 format.minimumChannelCount = 1;
326 format.maximumChannelCount = probedFormats->channelCountRange.second;
328 probeData.channelCountRange = probedFormats->channelCountRange;
329 probeData.sampleRateRange = probedFormats->sampleRateRange;
332 if (!format.preferredFormat.isValid()) {
333 std::optional<QAudioFormat> probedFormat = probePreferredFormat(audioClient);
335 format.preferredFormat = *probedFormat;
338 std::optional<QAudioFormat::ChannelConfig> config =
339 inferChannelConfiguration(*propStoreHelper, format.maximumChannelCount);
341 format.channelConfiguration = config
343 : QAudioFormat::defaultChannelConfigForChannelCount(format.maximumChannelCount);
345 return {format, probeData};
348struct WasapiProbeThreadpool
final :
public QThreadPool
350 WasapiProbeThreadpool()
352 setObjectName(u"WasapiProbeThreadpool"_s);
353 setMaxThreadCount(2);
354 setThreadPriority(QThread::LowPriority);
355 setServiceLevel(QThread::QualityOfService::Eco);
356 setExpiryTimeout(500 );
359 ~WasapiProbeThreadpool()
366Q_APPLICATION_STATIC(WasapiProbeThreadpool, wasapiProbeThreadpool)
370 std::promise<QAudioDevicePrivate::AudioDeviceFormat> formatPromise;
374 formatPromise.get_future(),
375 probePromise.get_future(),
378 wasapiProbeThreadpool->start([immDev = std::move(immDev),
379 formatPromise = std::move(formatPromise),
380 probePromise = std::move(probePromise)]()
mutable {
381 auto result = performFormatProbe(immDev);
382 formatPromise.set_value(result.format);
383 probePromise.set_value(result.probeData);
392 QUuid containerId, EndpointFormFactor formFactor,
393 QAudioDevice::Mode mode)
400 QUuid containerId, EndpointFormFactor formFactor,
401 QAudioDevice::Mode mode,
419 QComInitializer init;
420 ComPtr<IMMDeviceEnumerator> deviceEnumerator;
421 HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
nullptr, CLSCTX_ALL,
422 IID_PPV_ARGS(&deviceEnumerator));
424 qWarning() <<
"Failed to create device enumerator" << hr;
428 auto deviceId = QString::fromUtf8(id);
430 ComPtr<IMMDevice> device;
432 deviceEnumerator->GetDevice(deviceId.toStdWString().c_str(), device.GetAddressOf());
433 if (FAILED(result)) {
434 qWarning() <<
"IMMDeviceEnumerator::GetDevice failed" << id
435 << QSystemError::windowsComString(result);
445 return std::unique_ptr<QAudioDevicePrivate>(
new QWindowsAudioDevice{ *
this });
std::unique_ptr< QAudioDevicePrivate > clone() const
QWindowsAudioDevice(QByteArray deviceId, QString description, QUuid containerId, EndpointFormFactor, QAudioDevice::Mode, QtWASAPI::WindowsFormatResultFutures)
QWindowsAudioDevice(QByteArray deviceId, ComPtr< IMMDevice >, QString description, QUuid containerId, EndpointFormFactor, QAudioDevice::Mode)
ComPtr< IMMDevice > open() const