summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPavel Dubsky <pavel.dubsky@qt.io>2023-05-26 12:51:02 +0200
committerPavel Dubsky <pavel.dubsky@qt.io>2023-05-30 20:08:41 +0200
commit5aa899abb67ebdbcc9d0feb994385f1ab12656f7 (patch)
treeb66a5a27b8c38d154edd2abbe06cc0c5e0dfbcbf /src
parentcfc5b97daee9ac38b6fa8ec52be292e0790220ce (diff)
Add audio engine warm-up on Windows
Current implementation has the following audio problem on Windows: if any of the classes that support audio playback are used (QMediaPlayer or QSoundEffect) sound is cutting off (part of the sound is missing usually beginning). If there're some other applications playing sounds in the system while we try to play our sound then everyting works fine. Apparently this happens due to warm up delays for system audio engine and if there're other applications available they're simply keeping it alive and it's already fully initialized and ready to be used by us when we need it. As a workaround to this problem an unused audio device is created that is fully initialized and set to play (nothing) but is actually present for any subsequently created audio devices that are ready from the start without any delays. Pick-to: 6.5 Task-number: QTBUG-112512 Change-Id: I660a48e8ac1a1ebd1cfb6a9ff76605c3b4742e17 Reviewed-by: Artem Dyomin <artem.dyomin@qt.io> Reviewed-by: Lars Knoll <lars@knoll.priv.no> Reviewed-by: Jøger Hansegård <joger.hansegard@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/multimedia/CMakeLists.txt2
-rw-r--r--src/multimedia/audio/qaudiosystem.cpp7
-rw-r--r--src/multimedia/audio/qsoundeffect.cpp3
-rw-r--r--src/multimedia/platform/qplatformmediadevices.cpp1
-rw-r--r--src/multimedia/platform/qplatformmediadevices_p.h2
-rw-r--r--src/multimedia/platform/qplatformmediaplayer.cpp6
-rw-r--r--src/multimedia/platform/qplatformmediaplayer_p.h5
-rw-r--r--src/multimedia/windows/qcomtaskresource_p.h57
-rw-r--r--src/multimedia/windows/qwindowsmediadevices.cpp71
-rw-r--r--src/multimedia/windows/qwindowsmediadevices_p.h7
10 files changed, 157 insertions, 4 deletions
diff --git a/src/multimedia/CMakeLists.txt b/src/multimedia/CMakeLists.txt
index 089d88c8e..1cdbd5927 100644
--- a/src/multimedia/CMakeLists.txt
+++ b/src/multimedia/CMakeLists.txt
@@ -214,6 +214,7 @@ qt_internal_extend_target(Multimedia CONDITION QT_FEATURE_wmf
windows/qwindowsmultimediautils.cpp windows/qwindowsmultimediautils_p.h
windows/qwindowsmfdefs.cpp windows/qwindowsmfdefs_p.h
windows/qcomptr_p.h
+ windows/qcomtaskresource_p.h
INCLUDE_DIRECTORIES
windows
LIBRARIES
@@ -235,6 +236,7 @@ qt_internal_extend_target(Multimedia CONDITION QT_FEATURE_wmf AND MINGW
windows/qwindowsmultimediautils.cpp windows/qwindowsmultimediautils_p.h
windows/qwindowsmfdefs.cpp windows/qwindowsmfdefs_p.h
windows/qcomptr_p.h
+ windows/qcomtaskresource_p.h
)
qt_internal_extend_target(Multimedia CONDITION WASM
diff --git a/src/multimedia/audio/qaudiosystem.cpp b/src/multimedia/audio/qaudiosystem.cpp
index ef7552492..b052b78a6 100644
--- a/src/multimedia/audio/qaudiosystem.cpp
+++ b/src/multimedia/audio/qaudiosystem.cpp
@@ -3,9 +3,14 @@
#include "qaudiosystem_p.h"
+#include <private/qplatformmediadevices_p.h>
+
QT_BEGIN_NAMESPACE
-QPlatformAudioSink::QPlatformAudioSink(QObject *parent) : QObject(parent) { }
+QPlatformAudioSink::QPlatformAudioSink(QObject *parent) : QObject(parent)
+{
+ QPlatformMediaDevices::instance()->prepareAudio();
+}
qreal QPlatformAudioSink::volume() const
{
diff --git a/src/multimedia/audio/qsoundeffect.cpp b/src/multimedia/audio/qsoundeffect.cpp
index b6b6f2d1f..d65df2502 100644
--- a/src/multimedia/audio/qsoundeffect.cpp
+++ b/src/multimedia/audio/qsoundeffect.cpp
@@ -8,6 +8,7 @@
#include "qaudiosink.h"
#include "qmediadevices.h"
#include <QtCore/qloggingcategory.h>
+#include <private/qplatformmediadevices_p.h>
static Q_LOGGING_CATEGORY(qLcSoundEffect, "qt.multimedia.soundeffect")
@@ -72,6 +73,8 @@ QSoundEffectPrivate::QSoundEffectPrivate(QSoundEffect *q, const QAudioDevice &au
, m_audioDevice(audioDevice)
{
open(QIODevice::ReadOnly);
+
+ QPlatformMediaDevices::instance()->prepareAudio();
}
void QSoundEffectPrivate::sampleReady()
diff --git a/src/multimedia/platform/qplatformmediadevices.cpp b/src/multimedia/platform/qplatformmediadevices.cpp
index 03f1af75e..95cf3b7a0 100644
--- a/src/multimedia/platform/qplatformmediadevices.cpp
+++ b/src/multimedia/platform/qplatformmediadevices.cpp
@@ -153,5 +153,6 @@ void QPlatformMediaDevices::videoInputsChanged() const
emit m->videoInputsChanged();
}
+void QPlatformMediaDevices::prepareAudio() { }
QT_END_NAMESPACE
diff --git a/src/multimedia/platform/qplatformmediadevices_p.h b/src/multimedia/platform/qplatformmediadevices_p.h
index 3cde651fa..9b8d0d5e7 100644
--- a/src/multimedia/platform/qplatformmediadevices_p.h
+++ b/src/multimedia/platform/qplatformmediadevices_p.h
@@ -62,6 +62,8 @@ public:
void videoInputsChanged() const;
+ virtual void prepareAudio();
+
protected:
void audioInputsChanged() const;
void audioOutputsChanged() const;
diff --git a/src/multimedia/platform/qplatformmediaplayer.cpp b/src/multimedia/platform/qplatformmediaplayer.cpp
index 76de92cff..310776c8a 100644
--- a/src/multimedia/platform/qplatformmediaplayer.cpp
+++ b/src/multimedia/platform/qplatformmediaplayer.cpp
@@ -4,9 +4,15 @@
#include "qplatformmediaplayer_p.h"
#include <private/qmediaplayer_p.h>
#include "qmediaplayer.h"
+#include "qplatformmediadevices_p.h"
QT_BEGIN_NAMESPACE
+QPlatformMediaPlayer::QPlatformMediaPlayer(QMediaPlayer *parent) : player(parent)
+{
+ QPlatformMediaDevices::instance()->prepareAudio();
+}
+
QPlatformMediaPlayer::~QPlatformMediaPlayer()
{
}
diff --git a/src/multimedia/platform/qplatformmediaplayer_p.h b/src/multimedia/platform/qplatformmediaplayer_p.h
index b2cd5e571..b31cbac89 100644
--- a/src/multimedia/platform/qplatformmediaplayer_p.h
+++ b/src/multimedia/platform/qplatformmediaplayer_p.h
@@ -125,9 +125,8 @@ public:
}
protected:
- explicit QPlatformMediaPlayer(QMediaPlayer *parent = nullptr)
- : player(parent)
- {}
+ explicit QPlatformMediaPlayer(QMediaPlayer *parent = nullptr);
+
private:
QMediaPlayer *player = nullptr;
QMediaPlayer::MediaStatus m_status = QMediaPlayer::NoMedia;
diff --git a/src/multimedia/windows/qcomtaskresource_p.h b/src/multimedia/windows/qcomtaskresource_p.h
new file mode 100644
index 000000000..7d3fd46ba
--- /dev/null
+++ b/src/multimedia/windows/qcomtaskresource_p.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#ifndef QCOMTASKRESOURCE_P_H
+#define QCOMTASKRESOURCE_P_H
+
+#include <QtCore/qassert.h>
+
+#include <objbase.h>
+#include <utility>
+
+template<typename T>
+class QComTaskResource final
+{
+public:
+ QComTaskResource() = default;
+ explicit QComTaskResource(T *resource) : m_resource(resource) { }
+ ~QComTaskResource() { reset(); }
+
+ QComTaskResource(const QComTaskResource<T> &source) = delete;
+ QComTaskResource &operator=(const QComTaskResource<T> &right) = delete;
+
+ explicit operator bool() const { return m_resource != nullptr; }
+ T *operator->() const { return m_resource; }
+
+ T **address()
+ {
+ Q_ASSERT(m_resource == nullptr);
+ return &m_resource;
+ }
+ T *get() const { return m_resource; }
+ T *release() { return std::exchange(m_resource, nullptr); }
+ void reset(T *resource = nullptr)
+ {
+ if (m_resource != resource) {
+ if (m_resource)
+ CoTaskMemFree(m_resource);
+ m_resource = resource;
+ }
+ }
+
+private:
+ T *m_resource = nullptr;
+};
+
+#endif
diff --git a/src/multimedia/windows/qwindowsmediadevices.cpp b/src/multimedia/windows/qwindowsmediadevices.cpp
index 9dd2db383..c6c2b68df 100644
--- a/src/multimedia/windows/qwindowsmediadevices.cpp
+++ b/src/multimedia/windows/qwindowsmediadevices.cpp
@@ -8,6 +8,7 @@
#include "qwindowsaudiosource_p.h"
#include "qwindowsaudiosink_p.h"
#include "qwindowsaudiodevice_p.h"
+#include "qcomtaskresource_p.h"
#include <mmsystem.h>
#include <mmddk.h>
@@ -191,9 +192,16 @@ QWindowsMediaDevices::~QWindowsMediaDevices()
if (m_deviceEnumerator) {
m_deviceEnumerator->UnregisterEndpointNotificationCallback(m_notificationClient.get());
}
+ if (m_warmUpAudioClient) {
+ HRESULT hr = m_warmUpAudioClient->Stop();
+ if (FAILED(hr)) {
+ qWarning() << "Failed to stop audio engine" << hr;
+ }
+ }
m_deviceEnumerator.reset();
m_notificationClient.reset();
+ m_warmUpAudioClient.reset();
CoUninitialize();
}
@@ -285,4 +293,67 @@ QPlatformAudioSink *QWindowsMediaDevices::createAudioSink(const QAudioDevice &de
return new QWindowsAudioSink(devInfo->immDev(), parent);
}
+void QWindowsMediaDevices::prepareAudio()
+{
+ if (m_isAudioClientWarmedUp.exchange(true))
+ return;
+
+ QComPtr<IMMDeviceEnumerator> deviceEnumerator;
+ HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL,
+ __uuidof(IMMDeviceEnumerator),
+ reinterpret_cast<void **>(deviceEnumerator.address()));
+ if (FAILED(hr)) {
+ qWarning() << "Failed to create device enumerator" << hr;
+ return;
+ }
+
+ QComPtr<IMMDevice> device;
+ hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, device.address());
+ if (FAILED(hr)) {
+ qWarning() << "Failed to retrieve default audio endpoint" << hr;
+ return;
+ }
+
+ hr = device->Activate(__uuidof(IAudioClient3), CLSCTX_ALL, nullptr,
+ reinterpret_cast<void **>(m_warmUpAudioClient.address()));
+ if (FAILED(hr)) {
+ qWarning() << "Failed to activate audio engine" << hr;
+ return;
+ }
+
+ QComTaskResource<WAVEFORMATEX> deviceFormat;
+ UINT32 currentPeriodInFrames = 0;
+ hr = m_warmUpAudioClient->GetCurrentSharedModeEnginePeriod(deviceFormat.address(),
+ &currentPeriodInFrames);
+ if (FAILED(hr)) {
+ qWarning() << "Failed to retrieve the current format and periodicity of the audio engine"
+ << hr;
+ return;
+ }
+
+ UINT32 defaultPeriodInFrames = 0;
+ UINT32 fundamentalPeriodInFrames = 0;
+ UINT32 minPeriodInFrames = 0;
+ UINT32 maxPeriodInFrames = 0;
+ hr = m_warmUpAudioClient->GetSharedModeEnginePeriod(deviceFormat.get(), &defaultPeriodInFrames,
+ &fundamentalPeriodInFrames,
+ &minPeriodInFrames, &maxPeriodInFrames);
+ if (FAILED(hr)) {
+ qWarning() << "Failed to retrieve the range of periodicities supported by the audio engine"
+ << hr;
+ return;
+ }
+
+ hr = m_warmUpAudioClient->InitializeSharedAudioStream(
+ AUDCLNT_SHAREMODE_SHARED, minPeriodInFrames, deviceFormat.get(), nullptr);
+ if (FAILED(hr)) {
+ qWarning() << "Failed to initialize audio engine stream" << hr;
+ return;
+ }
+
+ hr = m_warmUpAudioClient->Start();
+ if (FAILED(hr))
+ qWarning() << "Failed to start audio engine" << hr;
+}
+
QT_END_NAMESPACE
diff --git a/src/multimedia/windows/qwindowsmediadevices_p.h b/src/multimedia/windows/qwindowsmediadevices_p.h
index 4aa1e6577..1973d4769 100644
--- a/src/multimedia/windows/qwindowsmediadevices_p.h
+++ b/src/multimedia/windows/qwindowsmediadevices_p.h
@@ -26,6 +26,7 @@ QT_BEGIN_NAMESPACE
class QWindowsEngine;
class CMMNotificationClient;
+struct IAudioClient3;
class QWindowsMediaDevices : public QPlatformMediaDevices
{
@@ -40,11 +41,17 @@ public:
QPlatformAudioSink *createAudioSink(const QAudioDevice &deviceInfo,
QObject *parent) override;
+ void prepareAudio() override;
+
private:
QList<QAudioDevice> availableDevices(QAudioDevice::Mode mode) const;
QComPtr<IMMDeviceEnumerator> m_deviceEnumerator;
QComPtr<CMMNotificationClient> m_notificationClient;
+ // The "warm-up" audio client is required to run in the background in order to keep audio engine
+ // ready for audio output immediately after creating any other subsequent audio client.
+ QComPtr<IAudioClient3> m_warmUpAudioClient;
+ std::atomic_bool m_isAudioClientWarmedUp = false;
friend CMMNotificationClient;
};