6#include <QtCore/qapplicationstatic.h>
7#include <QtCore/qcoreapplication.h>
8#include <QtCore/qdebug.h>
9#include <QtCore/qspan.h>
10#include <QtCore/qthread.h>
11#include <QtCore/qtimer.h>
12#include <QtCore/quuid.h>
13#include <QtCore/private/qsystemerror_p.h>
14#include <QtMultimedia/private/qcominitializer_p.h>
15#include <QtMultimedia/private/qcomtaskresource_p.h>
16#include <QtMultimedia/private/qwindowsaudioutils_p.h>
18#include <audioclient.h>
20#include <mmdeviceapi.h>
35class QWasapiWarmupClient
38 QWasapiWarmupClient();
39 ~QWasapiWarmupClient();
42 ComPtr<IAudioClient3> m_warmupClient;
45QWasapiWarmupClient::QWasapiWarmupClient()
47 using namespace QWindowsAudioUtils;
48 ensureComInitializedOnThisThread();
50 ComPtr<IMMDeviceEnumerator> deviceEnumerator;
51 HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
nullptr, CLSCTX_ALL,
52 IID_PPV_ARGS(&deviceEnumerator));
54 qWarning() <<
"Failed to create device enumerator" << audioClientErrorString(hr);
58 ComPtr<IMMDevice> device;
59 hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, device.GetAddressOf());
62 qWarning() <<
"Failed to retrieve default audio endpoint" << audioClientErrorString(hr);
66 hr = device->Activate(__uuidof(IAudioClient3), CLSCTX_ALL,
nullptr,
67 reinterpret_cast<
void **>(m_warmupClient.GetAddressOf()));
69 qWarning() <<
"Failed to activate audio engine" << audioClientErrorString(hr);
73 QComTaskResource<WAVEFORMATEX> deviceFormat;
74 UINT32 currentPeriodInFrames = 0;
75 hr = m_warmupClient->GetCurrentSharedModeEnginePeriod(deviceFormat.address(),
76 ¤tPeriodInFrames);
78 qWarning() <<
"Failed to retrieve the current format and periodicity of the audio engine"
79 << audioClientErrorString(hr);
83 UINT32 defaultPeriodInFrames = 0;
84 UINT32 fundamentalPeriodInFrames = 0;
85 UINT32 minPeriodInFrames = 0;
86 UINT32 maxPeriodInFrames = 0;
87 hr = m_warmupClient->GetSharedModeEnginePeriod(deviceFormat.get(), &defaultPeriodInFrames,
88 &fundamentalPeriodInFrames, &minPeriodInFrames,
91 qWarning() <<
"Failed to retrieve the range of periodicities supported by the audio engine"
92 << audioClientErrorString(hr);
96 audioClientSetRole(m_warmupClient, QtMultimediaPrivate::AudioEndpointRole::SoundEffect);
98 hr = m_warmupClient->InitializeSharedAudioStream(
99 AUDCLNT_SHAREMODE_SHARED, defaultPeriodInFrames, deviceFormat.get(),
nullptr);
101 qWarning() <<
"Failed to initialize audio engine stream" << audioClientErrorString(hr);
105 hr = m_warmupClient->Start();
107 qWarning() <<
"Failed to start audio engine" << audioClientErrorString(hr);
110QWasapiWarmupClient::~QWasapiWarmupClient()
112 using namespace QWindowsAudioUtils;
114 if (m_warmupClient) {
115 HRESULT hr = m_warmupClient->Stop();
117 qWarning() <<
"Failed to stop audio engine" << audioClientErrorString(hr);
126DEFINE_GUID(
GUID_SLEEP_TIMEOUT, 0x29f6c1db, 0x86da, 0x48c5, 0x9f, 0xdb, 0xf2, 0xb6, 0x7b, 0x1f,
142 static LRESULT handlePowerSettingsChanged(HWND hwnd, POWERBROADCAST_SETTING *pbs);
144 std::wstring m_windowClass;
145 HWND m_offscreenWindow{};
146 HPOWERNOTIFY m_powerNotifySleepTimeout;
151 m_windowClass = QUuid::createUuid().toString(QUuid::WithoutBraces).toStdWString()
152 + L"PowerNotificationWindowClass";
155 wc.cbSize =
sizeof(WNDCLASSEX);
156 wc.lpfnWndProc = WndProc;
157 wc.hInstance =
nullptr;
158 wc.lpszClassName = m_windowClass.c_str();
159 wc.hCursor =
nullptr;
161 if (!RegisterClassEx(&wc)) {
162 qWarning() <<
"RegisterClassEx failed" << QSystemError::windowsString();
163 m_windowClass.clear();
167 m_offscreenWindow = CreateWindowEx(0, m_windowClass.c_str(), L"Power Notification Window", 0, 0,
168 0, 0, 0, HWND_MESSAGE,
nullptr,
nullptr,
this);
170 if (!m_offscreenWindow) {
171 qWarning() <<
"CreateWindowEx failed" << QSystemError::windowsString();
175 SetWindowLongPtr(m_offscreenWindow, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(
this));
177 m_powerNotifySleepTimeout = RegisterPowerSettingNotification(
178 m_offscreenWindow, &GUID_SLEEP_TIMEOUT, DEVICE_NOTIFY_WINDOW_HANDLE);
179 if (!m_powerNotifySleepTimeout) {
180 qWarning() <<
"RegisterPowerSettingNotification failed" << QSystemError::windowsString();
186 if (m_powerNotifySleepTimeout)
187 UnregisterPowerSettingNotification(m_powerNotifySleepTimeout);
189 if (m_offscreenWindow)
190 DestroyWindow(m_offscreenWindow);
191 if (!m_windowClass.empty())
192 UnregisterClass(m_windowClass.c_str(), GetModuleHandle(
nullptr));
198 case WM_POWERBROADCAST: {
200 case PBT_POWERSETTINGCHANGE: {
201 POWERBROADCAST_SETTING *pbs =
reinterpret_cast<POWERBROADCAST_SETTING *>(lParam);
202 return handlePowerSettingsChanged(hwnd, pbs);
210 return DefWindowProc(hwnd, msg, wParam, lParam);
216LRESULT
SleepTimeoutMonitor::handlePowerSettingsChanged(HWND hwnd, POWERBROADCAST_SETTING *pbs)
218 if (pbs->PowerSetting == GUID_SLEEP_TIMEOUT) {
219 Q_ASSERT(pbs->DataLength == 4);
222 DWORD sleepTimeoutInSeconds;
225 std::copy_n(pbs->Data, pbs->DataLength, helper.data);
226 auto self =
reinterpret_cast<SleepTimeoutMonitor *>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
228 emit self->sleepTimeoutChanged(std::chrono::seconds{ helper.sleepTimeoutInSeconds });
241class QWasapiWarmupClientHelper
244 QWasapiWarmupClientHelper()
246 m_cleanupTimer.setInterval(120s);
247 m_cleanupTimer.setTimerType(Qt::TimerType::VeryCoarseTimer);
248 m_cleanupTimer.setSingleShot(
true);
249 m_cleanupTimer.callOnTimeout(&m_cleanupTimer, [
this] {
253 if (!QThread::isMainThread())
254 m_cleanupTimer.moveToThread(qApp->thread());
256 QObject::connect(qApp, &QCoreApplication::aboutToQuit, qApp, [
this] {
260 QObject::connect(&m_sleepTimeoutMonitor, &SleepTimeoutMonitor::sleepTimeoutChanged,
261 &m_cleanupTimer, [
this](std::chrono::seconds sleepTimeout) {
262 m_cleanupTimer.setInterval(std::min<std::chrono::milliseconds>(sleepTimeout / 2, 5min));
268 Q_ASSERT(QThread::isMainThread());
271 m_warmupClient = std::make_unique<QWasapiWarmupClient>();
273 m_cleanupTimer.start();
277 QTimer m_cleanupTimer;
279 std::unique_ptr<QWasapiWarmupClient> m_warmupClient;
282Q_APPLICATION_STATIC(QWasapiWarmupClientHelper, warmupClient);
290 const static bool useWarmupClient = [] {
291 bool envVarSet =
false;
292 int disableWarmup = qEnvironmentVariableIntValue(
"QT_DISABLE_AUDIO_PREPARE", &envVarSet);
293 return !envVarSet || disableWarmup == 0;
296 if (!useWarmupClient)
299 QMetaObject::invokeMethod(qApp, [] {
300 warmupClient->refresh();
308#include "qwindows_wasapi_warmup_client.moc"