6#include <QtCore/qdebug.h>
7#include <QtCore/qt_windows.h>
8#include <QtCore/qloggingcategory.h>
9#include <QtCore/private/qsystemerror_p.h>
11#include <QtMultimedia/private/qaudioformat_p.h>
12#include <QtMultimedia/private/qcominitializer_p.h>
13#include <QtMultimedia/private/qcomtaskresource_p.h>
14#include <QtMultimedia/private/qwindows_propertystore_p.h>
15#include <QtMultimedia/private/qwindowsaudioutils_p.h>
17#include <audioclient.h>
18#include <mmdeviceapi.h>
19#include <propkeydef.h>
25using QtMultimediaPrivate::PropertyStoreHelper;
29Q_STATIC_LOGGING_CATEGORY(qLcAudioDeviceProbes,
"qt.multimedia.audiodevice.probes");
31std::optional<EndpointFormFactor> inferFormFactor(PropertyStoreHelper &propertyStore)
33 std::optional<uint32_t> val = propertyStore.getUInt32(PKEY_AudioEndpoint_FormFactor);
34 if (val == EndpointFormFactor::UnknownFormFactor)
35 return EndpointFormFactor(*val);
40std::optional<QAudioFormat::ChannelConfig>
41inferChannelConfiguration(PropertyStoreHelper &propertyStore,
int maximumChannelCount)
43 std::optional<uint32_t> val = propertyStore.getUInt32(PKEY_AudioEndpoint_PhysicalSpeakers);
45 return QWindowsAudioUtils::maskToChannelConfig(*val, maximumChannelCount);
50int maxChannelCountForFormFactor(EndpointFormFactor formFactor)
53 case EndpointFormFactor::Headphones:
54 case EndpointFormFactor::Headset:
56 case EndpointFormFactor::SPDIF:
59 case EndpointFormFactor::DigitalAudioDisplayDevice:
62 case EndpointFormFactor::Microphone:
70struct FormatProbeResult
72 void update(
const QAudioFormat &fmt)
74 supportedSampleFormats.insert(fmt.sampleFormat());
75 updateChannelCount(fmt.channelCount());
76 updateSamplingRate(fmt.sampleRate());
79 void updateChannelCount(
int channelCount)
81 if (channelCount < channelCountRange.first)
82 channelCountRange.first = channelCount;
83 if (channelCount > channelCountRange.second)
84 channelCountRange.second = channelCount;
87 void updateSamplingRate(
int samplingRate)
89 if (samplingRate < sampleRateRange.first)
90 sampleRateRange.first = samplingRate;
91 if (samplingRate > sampleRateRange.second)
92 sampleRateRange.second = samplingRate;
95 std::set<QAudioFormat::SampleFormat> supportedSampleFormats;
96 std::pair<
int,
int> channelCountRange{ std::numeric_limits<
int>::max(), 0 };
97 std::pair<
int,
int> sampleRateRange{ std::numeric_limits<
int>::max(), 0 };
100 friend QDebug operator<<(QDebug dbg,
const FormatProbeResult &self)
102 QDebugStateSaver saver(dbg);
105 dbg <<
"FormatProbeResult{supportedSampleFormats: " << self.supportedSampleFormats
106 <<
", channelCountRange: " << self.channelCountRange.first <<
" - " << self.channelCountRange.second
107 <<
", sampleRateRange: " << self.sampleRateRange.first <<
"-" << self.sampleRateRange.second
113std::optional<QAudioFormat> performIsFormatSupportedWithClosestMatch(
const ComPtr<IAudioClient> &audioClient,
114 const QAudioFormat &fmt)
116 using namespace QWindowsAudioUtils;
117 std::optional<WAVEFORMATEXTENSIBLE> formatEx = toWaveFormatExtensible(fmt);
119 qCWarning(qLcAudioDeviceProbes) <<
"toWaveFormatExtensible failed" << fmt;
123 qCDebug(qLcAudioDeviceProbes) <<
"performIsFormatSupportedWithClosestMatch for" << fmt;
124 QComTaskResource<WAVEFORMATEX> closestMatch;
125 HRESULT result = audioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &formatEx->Format,
126 closestMatch.address());
128 if (FAILED(result)) {
129 qCDebug(qLcAudioDeviceProbes) <<
"performIsFormatSupportedWithClosestMatch: error" << QSystemError::windowsComString(result);
134 QAudioFormat closestMatchFormat = waveFormatExToFormat(*closestMatch);
135 qCDebug(qLcAudioDeviceProbes) <<
"performProbe returned closest match" << closestMatchFormat;
136 return closestMatchFormat;
139 qCDebug(qLcAudioDeviceProbes) <<
"performProbe successful";
144std::optional<FormatProbeResult> probeFormats(
const ComPtr<IAudioClient> &audioClient,
145 PropertyStoreHelper &propertyStore,
146 const QAudioFormat &preferredFormat)
148 using namespace QWindowsAudioUtils;
151 std::optional<EndpointFormFactor> formFactor = inferFormFactor(propertyStore);
152 int maxChannelsForFormFactor = formFactor ? maxChannelCountForFormFactor(*formFactor) : 128;
154 qCDebug(qLcAudioDeviceProbes) <<
"probing: maxChannelsForFormFactor" << maxChannelsForFormFactor << formFactor;
156 std::optional<FormatProbeResult> limits;
160 constexpr QAudioFormat::SampleFormat initialSampleFormat = QAudioFormat::SampleFormat::Float;
165 QAudioFormat initialProbeFormat;
166 initialProbeFormat.setSampleFormat(initialSampleFormat);
167 initialProbeFormat.setSampleRate(preferredFormat.sampleRate());
168 initialProbeFormat.setChannelCount(maxChannelsForFormFactor);
170 qCDebug(qLcAudioDeviceProbes) <<
"probeFormats: probing for" << initialProbeFormat;
172 std::optional<QAudioFormat> initialProbeResult =
173 performIsFormatSupportedWithClosestMatch(audioClient, initialProbeFormat);
175 int maxChannelForFormat;
176 if (initialProbeResult) {
177 if (initialProbeResult->sampleRate() != preferredFormat.sampleRate()) {
178 qCDebug(qLcAudioDeviceProbes)
179 <<
"probing: returned a different sample rate as closest match ..."
180 << *initialProbeResult;
184 maxChannelForFormat = initialProbeResult->channelCount();
190 maxChannelForFormat =
std::min(maxChannelsForFormFactor, 2);
195 QAudioFormat::SampleFormat probeSampleFormat =
196 initialProbeResult ? initialProbeResult->sampleFormat() : initialSampleFormat;
198 for (
int channelCount = 1; channelCount != maxChannelForFormat + 1; ++channelCount) {
200 fmt.setSampleFormat(probeSampleFormat);
201 fmt.setSampleRate(preferredFormat.sampleRate());
202 fmt.setChannelCount(channelCount);
204 std::optional<WAVEFORMATEXTENSIBLE> formatEx = toWaveFormatExtensible(fmt);
208 qCDebug(qLcAudioDeviceProbes) <<
"probing" << fmt;
210 QComTaskResource<WAVEFORMATEX> closestMatch;
211 HRESULT result = audioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &formatEx->Format,
212 closestMatch.address());
214 if (FAILED(result)) {
215 qCDebug(qLcAudioDeviceProbes)
216 <<
"probing format failed" << QSystemError::windowsComString(result);
221 qCDebug(qLcAudioDeviceProbes) <<
"probing format reported a closest match"
222 << waveFormatExToFormat(*closestMatch);
227 limits = FormatProbeResult{};
229 qCDebug(qLcAudioDeviceProbes) <<
"probing format successful" << fmt;
233 qCDebug(qLcAudioDeviceProbes) <<
"probing successful" << limits;
238std::optional<QAudioFormat> probePreferredFormat(
const ComPtr<IAudioClient> &audioClient)
240 using namespace QWindowsAudioUtils;
242 static const QAudioFormat preferredFormat = [] {
244 fmt.setSampleRate(44100);
245 fmt.setChannelCount(2);
246 fmt.setSampleFormat(QAudioFormat::Int16);
250 std::optional<WAVEFORMATEXTENSIBLE> formatEx = toWaveFormatExtensible(preferredFormat);
254 QComTaskResource<WAVEFORMATEX> closestMatch;
255 HRESULT result = audioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &formatEx->Format,
256 closestMatch.address());
261 return preferredFormat;
263 QAudioFormat closestMatchFormat = waveFormatExToFormat(*closestMatch);
264 if (closestMatchFormat.isValid())
265 return closestMatchFormat;
271QWindowsAudioDevice::QWindowsAudioDevice(QByteArray id, ComPtr<IMMDevice> immDev, QString desc,
272 QAudioDevice::Mode mode)
273 : QAudioDevicePrivate(std::move(id), mode, std::move(desc))
277 ComPtr<IAudioClient> audioClient;
278 HRESULT hr = immDev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER,
nullptr,
279 reinterpret_cast<
void **>(audioClient.GetAddressOf()));
282 QComTaskResource<WAVEFORMATEX> mixFormat;
283 hr = audioClient->GetMixFormat(mixFormat.address());
285 preferredFormat = QWindowsAudioUtils::waveFormatExToFormat(*mixFormat);
287 qWarning() <<
"QWindowsAudioDeviceInfo: could not activate audio client:" << description
288 << QSystemError::windowsComString(hr);
292 auto propStoreHelper = PropertyStoreHelper::open(immDev);
293 if (!propStoreHelper) {
294 qWarning() <<
"QWindowsAudioDeviceInfo: could not open property store:" << description
295 << propStoreHelper.error();
299 qCDebug(qLcAudioDeviceProbes) <<
"probing formats for" << description;
301 std::optional<FormatProbeResult> probedFormats =
302 probeFormats(audioClient, *propStoreHelper, preferredFormat);
305 supportedSampleFormats = qAllSupportedSampleFormats();
310 minimumSampleRate = QtMultimediaPrivate::allSupportedSampleRates.front();
311 maximumSampleRate = QtMultimediaPrivate::allSupportedSampleRates.back();
313 minimumChannelCount = 1;
315 maximumChannelCount = probedFormats->channelCountRange.second;
317 m_probedChannelCountRange = probedFormats->channelCountRange;
318 m_probedSampleRateRange = probedFormats->sampleRateRange;
321 if (!preferredFormat.isValid()) {
322 std::optional<QAudioFormat> probedFormat = probePreferredFormat(audioClient);
324 preferredFormat = *probedFormat;
327 std::optional<QAudioFormat::ChannelConfig> config =
328 inferChannelConfiguration(*propStoreHelper, maximumChannelCount);
330 channelConfiguration = config
332 : QAudioFormat::defaultChannelConfigForChannelCount(maximumChannelCount);
335ComPtr<IMMDevice> QWindowsAudioDevice::open()
const
337 QComInitializer init;
338 ComPtr<IMMDeviceEnumerator> deviceEnumerator;
339 HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
nullptr, CLSCTX_ALL,
340 IID_PPV_ARGS(&deviceEnumerator));
342 qWarning() <<
"Failed to create device enumerator" << hr;
346 auto deviceId = QString::fromUtf8(id);
348 ComPtr<IMMDevice> device;
350 deviceEnumerator->GetDevice(deviceId.toStdWString().c_str(), device.GetAddressOf());
351 if (FAILED(result)) {
352 qWarning() <<
"IMMDeviceEnumerator::GetDevice failed" << id
353 << QSystemError::windowsComString(result);
359QWindowsAudioDevice::~QWindowsAudioDevice() =
default;