Skip to content

Commit 8511563

Browse files
committed
fix: allow device closing to be async
1 parent 21a5d20 commit 8511563

2 files changed

Lines changed: 37 additions & 7 deletions

File tree

src/soundio/soundmanager.cpp

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,27 +145,48 @@ QList<QString> SoundManager::getHostAPIList() const {
145145
return apiList;
146146
}
147147

148-
void SoundManager::closeDevices(bool sleepAfterClosing) {
149-
//qDebug() << "SoundManager::closeDevices()";
148+
void SoundManager::closeDevices(
149+
[[maybe_unused]] bool sleepAfterClosing, [[maybe_unused]] bool async) {
150+
// sleepAfterClosing and async maybe unused depending on platform support
151+
// qDebug() << "SoundManager::closeDevices()";
150152

153+
#ifdef __LINUX__
151154
bool closed = false;
155+
#endif
152156
for (const auto& pDevice : std::as_const(m_devices)) {
153157
if (pDevice->isOpen()) {
154158
// NOTE(rryan): As of 2009 (?) it has been safe to close() a SoundDevice
155159
// while callbacks are active.
156160
pDevice->close();
161+
#ifdef __LINUX__
157162
closed = true;
163+
#endif
158164
}
159165
}
160166

161-
if (closed && sleepAfterClosing) {
162167
#ifdef __LINUX__
168+
if (closed && sleepAfterClosing) {
163169
// Sleep for 5 sec to allow asynchronously sound APIs like "pulse" to free
164170
// its resources as well
171+
if (async) {
172+
// Async mode - the caller will wait for `devicesClosed` before
173+
// trying to reconfigure or reopen audio devices
174+
QTimer::singleShot(
175+
std::chrono::seconds(kSleepSecondsAfterClosingDevice),
176+
this,
177+
&SoundManager::completeDevicesClosing);
178+
return;
179+
}
180+
// Sync mode, legacy - we sleep the current thread for 5 seconds
165181
QThread::sleep(kSleepSecondsAfterClosingDevice);
182+
} else if (!closed)
166183
#endif
184+
{
185+
completeDevicesClosing();
167186
}
187+
}
168188

189+
void SoundManager::completeDevicesClosing() {
169190
// TODO(rryan): Should we do this before SoundDevice::close()? No! Because
170191
// then the callback may be running when we call
171192
// onInputDisconnected/onOutputDisconnected.
@@ -199,6 +220,7 @@ void SoundManager::closeDevices(bool sleepAfterClosing) {
199220

200221
// Indicate to the rest of Mixxx that sound is disconnected.
201222
m_pControlObjectSoundStatusCO->set(SOUNDMANAGER_DISCONNECTED);
223+
emit devicesClosed();
202224
}
203225

204226
void SoundManager::clearDeviceList(bool sleepAfterClosing) {
@@ -553,12 +575,12 @@ SoundManagerConfig SoundManager::getConfig() const {
553575
return m_config;
554576
}
555577

556-
void SoundManager::closeActiveConfig() {
578+
void SoundManager::closeActiveConfig(bool async) {
557579
// Close open devices. After this call we will not get any more
558580
// onDeviceOutputCallback() or pushBuffer() calls because all the
559581
// SoundDevices are closed. closeDevices() blocks and can take a while.
560582
const bool sleepAfterClosing = true;
561-
closeDevices(sleepAfterClosing);
583+
closeDevices(sleepAfterClosing, async);
562584
}
563585

564586
SoundDeviceStatus SoundManager::setConfig(const SoundManagerConfig& config) {

src/soundio/soundmanager.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,12 @@ class SoundManager : public QObject {
7474
QList<QString> getHostAPIList() const;
7575
SoundManagerConfig getConfig() const;
7676
SoundDeviceStatus setConfig(const SoundManagerConfig& config);
77-
void closeActiveConfig();
77+
// Due to a bug in in PulseAudio, we must give at least 5 seconds of cool
78+
// down before performing further audio related operation. This sleep
79+
// happens during the function call by default (synchronous blocking), but
80+
// the caller may decide to use the async version, and must not performs any
81+
// audio operation till it received the `devicesClosed` signal
82+
void closeActiveConfig(bool async = false);
7883
void checkConfig();
7984

8085
void onDeviceOutputCallback(const SINT iFramesPerBuffer);
@@ -118,6 +123,9 @@ class SoundManager : public QObject {
118123
void outputRegistered(const AudioOutput& output, AudioSource* src);
119124
void inputRegistered(const AudioInput& input, AudioDestination* dest);
120125

126+
private slots:
127+
void completeDevicesClosing();
128+
121129
private:
122130
// Closes all the devices and empties the list of devices we have.
123131
void clearDeviceList(bool sleepAfterClosing);
@@ -126,7 +134,7 @@ class SoundManager : public QObject {
126134
// open, this method simply runs through the list of all known soundcards
127135
// (from PortAudio) and attempts to close them all. Closing a soundcard that
128136
// isn't open is safe.
129-
void closeDevices(bool sleepAfterClosing);
137+
void closeDevices(bool sleepAfterClosing, bool async = false);
130138

131139
void setJACKName() const;
132140
bool jackApiUsed() const {

0 commit comments

Comments
 (0)