1 | /* This file is part of the KDE project |
2 | Copyright (C) 2005-2006 Matthias Kretz <kretz@kde.org> |
3 | |
4 | This library is free software; you can redistribute it and/or |
5 | modify it under the terms of the GNU Lesser General Public |
6 | License as published by the Free Software Foundation; either |
7 | version 2.1 of the License, or (at your option) version 3, or any |
8 | later version accepted by the membership of KDE e.V. (or its |
9 | successor approved by the membership of KDE e.V.), Nokia Corporation |
10 | (or its successors, if any) and the KDE Free Qt Foundation, which shall |
11 | act as a proxy defined in Section 6 of version 3 of the license. |
12 | |
13 | This library is distributed in the hope that it will be useful, |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | Lesser General Public License for more details. |
17 | |
18 | You should have received a copy of the GNU Lesser General Public |
19 | License along with this library. If not, see <http://www.gnu.org/licenses/>. |
20 | */ |
21 | |
22 | #include "audiooutput.h" |
23 | #include "audiooutput_p.h" |
24 | |
25 | #include "audiooutputinterface.h" |
26 | #include "factory_p.h" |
27 | #include "globalconfig.h" |
28 | #include "objectdescription.h" |
29 | #include "phononconfig_p.h" |
30 | #include "phononnamespace_p.h" |
31 | #include "platform_p.h" |
32 | #include "pulsesupport.h" |
33 | #ifdef HAVE_PULSEAUDIO |
34 | # include "pulsestream_p.h" |
35 | #endif |
36 | |
37 | #include <QUuid> |
38 | #include <qmath.h> |
39 | |
40 | #define PHONON_CLASSNAME AudioOutput |
41 | #define IFACES10 AudioOutputInterface410 |
42 | #define IFACES9 AudioOutputInterface49 |
43 | #define IFECES7 AudioOutputInterface47 |
44 | #define IFACES2 AudioOutputInterface42 |
45 | #define IFACES1 IFACES2 |
46 | #define IFACES0 AudioOutputInterface40, IFACES1, IFECES7, IFACES9, IFACES10 |
47 | #define PHONON_INTERFACENAME IFACES0 |
48 | |
49 | namespace Phonon |
50 | { |
51 | |
52 | static inline bool callSetOutputDevice(AudioOutputPrivate *const d, const AudioOutputDevice &dev) |
53 | { |
54 | PulseSupport *pulse = PulseSupport::getInstance(); |
55 | if (pulse->isActive()) |
56 | return pulse->setOutputDevice(streamUuid: d->getStreamUuid(), device: dev.index()); |
57 | |
58 | if (!d->backendObject()) |
59 | return false; |
60 | |
61 | Iface<IFACES2> iface(d); |
62 | if (iface) { |
63 | return iface->setOutputDevice(dev); |
64 | } |
65 | return Iface<IFACES0>::cast(d)->setOutputDevice(dev.index()); |
66 | } |
67 | |
68 | AudioOutput::AudioOutput(Phonon::Category category, QObject *parent) |
69 | : AbstractAudioOutput(*new AudioOutputPrivate, parent) |
70 | { |
71 | P_D(AudioOutput); |
72 | d->init(c: category); |
73 | } |
74 | |
75 | AudioOutput::AudioOutput(QObject *parent) |
76 | : AbstractAudioOutput(*new AudioOutputPrivate, parent) |
77 | { |
78 | P_D(AudioOutput); |
79 | d->init(c: NoCategory); |
80 | } |
81 | |
82 | void AudioOutputPrivate::init(Phonon::Category c) |
83 | { |
84 | P_Q(AudioOutput); |
85 | |
86 | category = c; |
87 | #ifndef QT_NO_QUUID_STRING |
88 | streamUuid = QUuid::createUuid().toString(); |
89 | #endif |
90 | |
91 | createBackendObject(); |
92 | |
93 | #ifdef HAVE_PULSEAUDIO |
94 | PulseSupport *pulse = PulseSupport::getInstance(); |
95 | if (pulse->isActive()) { |
96 | PulseStream *stream = pulse->registerOutputStream(streamUuid, category); |
97 | if (stream) { |
98 | q->connect(asender: stream, SIGNAL(usingDevice(int)), SLOT(_k_deviceChanged(int))); |
99 | q->connect(asender: stream, SIGNAL(volumeChanged(qreal)), SLOT(_k_volumeChanged(qreal))); |
100 | q->connect(asender: stream, SIGNAL(muteChanged(bool)), SLOT(_k_mutedChanged(bool))); |
101 | |
102 | AudioOutputInterface47 *iface = Iface<AudioOutputInterface47>::cast(d: this); |
103 | if (iface) |
104 | iface->setStreamUuid(streamUuid); |
105 | else |
106 | pulse->setupStreamEnvironment(streamUuid); |
107 | } |
108 | } |
109 | #endif |
110 | |
111 | q->connect(asender: Factory::sender(), SIGNAL(availableAudioOutputDevicesChanged()), SLOT(_k_deviceListChanged())); |
112 | } |
113 | |
114 | QString AudioOutputPrivate::getStreamUuid() |
115 | { |
116 | return streamUuid; |
117 | } |
118 | |
119 | void AudioOutputPrivate::createBackendObject() |
120 | { |
121 | if (m_backendObject) |
122 | return; |
123 | P_Q(AudioOutput); |
124 | m_backendObject = Factory::createAudioOutput(parent: q); |
125 | // (cg) Is it possible that PulseAudio initialisation means that the device here is not valid? |
126 | // User reports seem to suggest this possibility but I can't see how :s. |
127 | // See other comment and check for isValid() in handleAutomaticDeviceChange() |
128 | device = AudioOutputDevice::fromIndex(index: GlobalConfig().audioOutputDeviceFor(category, override: GlobalConfig::AdvancedDevicesFromSettings | GlobalConfig::HideUnavailableDevices)); |
129 | if (m_backendObject) { |
130 | setupBackendObject(); |
131 | } |
132 | } |
133 | |
134 | QString AudioOutput::name() const |
135 | { |
136 | P_D(const AudioOutput); |
137 | return d->name; |
138 | } |
139 | |
140 | void AudioOutput::setName(const QString &newName) |
141 | { |
142 | P_D(AudioOutput); |
143 | if (d->name == newName) { |
144 | return; |
145 | } |
146 | d->name = newName; |
147 | PulseSupport *pulse = PulseSupport::getInstance(); |
148 | if (pulse->isActive()) |
149 | pulse->setOutputName(streamUuid: d->getStreamUuid(), name: newName); |
150 | else |
151 | setVolume(Platform::loadVolume(outputName: newName)); |
152 | } |
153 | |
154 | static const qreal LOUDNESS_TO_VOLTAGE_EXPONENT = qreal(0.67); |
155 | static const qreal VOLTAGE_TO_LOUDNESS_EXPONENT = qreal(1.0/LOUDNESS_TO_VOLTAGE_EXPONENT); |
156 | |
157 | void AudioOutput::setVolume(qreal volume) |
158 | { |
159 | P_D(AudioOutput); |
160 | d->volume = volume; |
161 | PulseSupport *pulse = PulseSupport::getInstance(); |
162 | if (k_ptr->backendObject()) { |
163 | if (pulse->isActive()) { |
164 | pulse->setOutputVolume(streamUuid: d->getStreamUuid(), volume); |
165 | } else if (!d->muted) { |
166 | // using Stevens' power law loudness is proportional to (sound pressure)^0.67 |
167 | // sound pressure is proportional to voltage: |
168 | // p² \prop P \prop V² |
169 | // => if a factor for loudness of x is requested |
170 | INTERFACE_CALL(setVolume(pow(volume, VOLTAGE_TO_LOUDNESS_EXPONENT))); |
171 | } else { |
172 | emit volumeChanged(newVolume: volume); |
173 | } |
174 | } else { |
175 | emit volumeChanged(newVolume: volume); |
176 | } |
177 | if (!pulse->isActive()) |
178 | Platform::saveVolume(outputName: d->name, volume); |
179 | } |
180 | |
181 | qreal AudioOutput::volume() const |
182 | { |
183 | P_D(const AudioOutput); |
184 | if (d->muted || !d->m_backendObject || PulseSupport::getInstance()->isActive()) |
185 | return d->volume; |
186 | return pow(INTERFACE_CALL(volume()), y: LOUDNESS_TO_VOLTAGE_EXPONENT); |
187 | } |
188 | |
189 | #ifndef PHONON_LOG10OVER20 |
190 | #define PHONON_LOG10OVER20 |
191 | static const qreal log10over20 = qreal(0.1151292546497022842); // ln(10) / 20 |
192 | #endif // PHONON_LOG10OVER20 |
193 | |
194 | qreal AudioOutput::volumeDecibel() const |
195 | { |
196 | P_D(const AudioOutput); |
197 | if (d->muted || !d->m_backendObject || PulseSupport::getInstance()->isActive()) |
198 | return log(x: d->volume) / log10over20; |
199 | return 0.67 * log(INTERFACE_CALL(volume())) / log10over20; |
200 | } |
201 | |
202 | void AudioOutput::setVolumeDecibel(qreal newVolumeDecibel) |
203 | { |
204 | setVolume(exp(x: newVolumeDecibel * log10over20)); |
205 | } |
206 | |
207 | bool AudioOutput::isMuted() const |
208 | { |
209 | P_D(const AudioOutput); |
210 | return d->muted; |
211 | } |
212 | |
213 | void AudioOutput::setMuted(bool mute) |
214 | { |
215 | P_D(AudioOutput); |
216 | |
217 | if (d->muted == mute) { |
218 | return; |
219 | } |
220 | d->muted = mute; |
221 | |
222 | if (!k_ptr->backendObject()) { |
223 | return; |
224 | } |
225 | |
226 | PulseSupport *pulse = PulseSupport::getInstance(); |
227 | if (pulse->isActive()) { |
228 | pulse->setOutputMute(streamUuid: d->getStreamUuid(), mute); |
229 | } else { |
230 | // When interface 9 is implemented we always default to it. |
231 | Iface<IFACES9> iface9(d); |
232 | if (iface9) { |
233 | iface9->setMuted(mute); |
234 | // iface9 is fully async, we let the backend emit the state change. |
235 | return; |
236 | } |
237 | |
238 | if (mute) { |
239 | INTERFACE_CALL(setVolume(0.0)); |
240 | } else { |
241 | INTERFACE_CALL(setVolume(pow(d->volume, VOLTAGE_TO_LOUDNESS_EXPONENT))); |
242 | } |
243 | } |
244 | emit mutedChanged(mute); |
245 | } |
246 | |
247 | Category AudioOutput::category() const |
248 | { |
249 | P_D(const AudioOutput); |
250 | return d->category; |
251 | } |
252 | |
253 | AudioOutputDevice AudioOutput::outputDevice() const |
254 | { |
255 | P_D(const AudioOutput); |
256 | return d->device; |
257 | } |
258 | |
259 | bool AudioOutput::setOutputDevice(const AudioOutputDevice &newAudioOutputDevice) |
260 | { |
261 | P_D(AudioOutput); |
262 | if (!newAudioOutputDevice.isValid()) { |
263 | d->outputDeviceOverridden = d->forceMove = false; |
264 | const int newIndex = GlobalConfig().audioOutputDeviceFor(category: d->category); |
265 | if (newIndex == d->device.index()) { |
266 | return true; |
267 | } |
268 | d->device = AudioOutputDevice::fromIndex(index: newIndex); |
269 | } else { |
270 | d->outputDeviceOverridden = d->forceMove = true; |
271 | if (d->device == newAudioOutputDevice) { |
272 | return true; |
273 | } |
274 | d->device = newAudioOutputDevice; |
275 | } |
276 | if (k_ptr->backendObject()) { |
277 | return callSetOutputDevice(d, dev: d->device); |
278 | } |
279 | return true; |
280 | } |
281 | |
282 | bool AudioOutputPrivate::aboutToDeleteBackendObject() |
283 | { |
284 | if (m_backendObject) { |
285 | volume = pINTERFACE_CALL(volume()); |
286 | } |
287 | return AbstractAudioOutputPrivate::aboutToDeleteBackendObject(); |
288 | } |
289 | |
290 | void AudioOutputPrivate::setupBackendObject() |
291 | { |
292 | P_Q(AudioOutput); |
293 | Q_ASSERT(m_backendObject); |
294 | AbstractAudioOutputPrivate::setupBackendObject(); |
295 | |
296 | QObject::connect(sender: m_backendObject, SIGNAL(volumeChanged(qreal)), receiver: q, SLOT(_k_volumeChanged(qreal))); |
297 | QObject::connect(sender: m_backendObject, SIGNAL(audioDeviceFailed()), receiver: q, SLOT(_k_audioDeviceFailed())); |
298 | if (Iface<IFACES9>(this)) { |
299 | QObject::connect(sender: m_backendObject, SIGNAL(mutedChanged(bool)), |
300 | receiver: q, SLOT(_k_mutedChanged(bool))); |
301 | } |
302 | |
303 | Iface<IFACES10> iface10(this); |
304 | if (iface10) { |
305 | iface10->setCategory(category); |
306 | } |
307 | |
308 | if (!PulseSupport::getInstance()->isActive()) { |
309 | // set up attributes |
310 | pINTERFACE_CALL(setVolume(pow(volume, VOLTAGE_TO_LOUDNESS_EXPONENT))); |
311 | |
312 | #ifndef QT_NO_PHONON_SETTINGSGROUP |
313 | // if the output device is not available and the device was not explicitly set |
314 | // There is no need to set the output device initially if PA is used as |
315 | // we know it will not work (stream doesn't exist yet) and that this will be |
316 | // handled by _k_deviceChanged() |
317 | if (!callSetOutputDevice(d: this, dev: device) && !outputDeviceOverridden) { |
318 | // fall back in the preference list of output devices |
319 | QList<int> deviceList = GlobalConfig().audioOutputDeviceListFor(category, override: GlobalConfig::AdvancedDevicesFromSettings | GlobalConfig::HideUnavailableDevices); |
320 | if (deviceList.isEmpty()) { |
321 | return; |
322 | } |
323 | for (int i = 0; i < deviceList.count(); ++i) { |
324 | const AudioOutputDevice &dev = AudioOutputDevice::fromIndex(index: deviceList.at(i)); |
325 | if (callSetOutputDevice(d: this, dev)) { |
326 | handleAutomaticDeviceChange(newDev: dev, type: AudioOutputPrivate::FallbackChange); |
327 | return; // found one that works |
328 | } |
329 | } |
330 | // if we get here there is no working output device. Tell the backend. |
331 | const AudioOutputDevice none; |
332 | callSetOutputDevice(d: this, dev: none); |
333 | handleAutomaticDeviceChange(newDev: none, type: FallbackChange); |
334 | } |
335 | #endif //QT_NO_PHONON_SETTINGSGROUP |
336 | } |
337 | } |
338 | |
339 | void AudioOutputPrivate::_k_volumeChanged(qreal newVolume) |
340 | { |
341 | volume = pow(x: newVolume, y: LOUDNESS_TO_VOLTAGE_EXPONENT); |
342 | if (!muted) { |
343 | P_Q(AudioOutput); |
344 | emit q->volumeChanged(newVolume: volume); |
345 | } |
346 | } |
347 | |
348 | void AudioOutputPrivate::_k_mutedChanged(bool newMuted) |
349 | { |
350 | muted = newMuted; |
351 | P_Q(AudioOutput); |
352 | emit q->mutedChanged(newMuted); |
353 | } |
354 | |
355 | void AudioOutputPrivate::_k_revertFallback() |
356 | { |
357 | if (deviceBeforeFallback == -1) { |
358 | return; |
359 | } |
360 | device = AudioOutputDevice::fromIndex(index: deviceBeforeFallback); |
361 | callSetOutputDevice(d: this, dev: device); |
362 | P_Q(AudioOutput); |
363 | emit q->outputDeviceChanged(newAudioOutputDevice: device); |
364 | } |
365 | |
366 | void AudioOutputPrivate::_k_audioDeviceFailed() |
367 | { |
368 | if (PulseSupport::getInstance()->isActive()) |
369 | return; |
370 | |
371 | #ifndef QT_NO_PHONON_SETTINGSGROUP |
372 | |
373 | pDebug() << Q_FUNC_INFO; |
374 | // outputDeviceIndex identifies a failing device |
375 | // fall back in the preference list of output devices |
376 | const QList<int> deviceList = GlobalConfig().audioOutputDeviceListFor(category, override: GlobalConfig::AdvancedDevicesFromSettings | GlobalConfig::HideUnavailableDevices); |
377 | for (int i = 0; i < deviceList.count(); ++i) { |
378 | const int devIndex = deviceList.at(i); |
379 | // if it's the same device as the one that failed, ignore it |
380 | if (device.index() != devIndex) { |
381 | const AudioOutputDevice &info = AudioOutputDevice::fromIndex(index: devIndex); |
382 | if (callSetOutputDevice(d: this, dev: info)) { |
383 | handleAutomaticDeviceChange(newDev: info, type: FallbackChange); |
384 | return; // found one that works |
385 | } |
386 | } |
387 | } |
388 | #endif //QT_NO_PHONON_SETTINGSGROUP |
389 | // if we get here there is no working output device. Tell the backend. |
390 | const AudioOutputDevice none; |
391 | callSetOutputDevice(d: this, dev: none); |
392 | handleAutomaticDeviceChange(newDev: none, type: FallbackChange); |
393 | } |
394 | |
395 | void AudioOutputPrivate::_k_deviceListChanged() |
396 | { |
397 | if (PulseSupport::getInstance()->isActive()) |
398 | return; |
399 | |
400 | #ifndef QT_NO_PHONON_SETTINGSGROUP |
401 | pDebug() << Q_FUNC_INFO; |
402 | // Check to see if we have an override and do not change to a higher priority device if the overridden device is still present. |
403 | if (outputDeviceOverridden && device.property(name: "available" ).toBool()) { |
404 | return; |
405 | } |
406 | // let's see if there's a usable device higher in the preference list |
407 | const QList<int> deviceList = GlobalConfig().audioOutputDeviceListFor(category, override: GlobalConfig::AdvancedDevicesFromSettings); |
408 | DeviceChangeType changeType = HigherPreferenceChange; |
409 | for (int i = 0; i < deviceList.count(); ++i) { |
410 | const int devIndex = deviceList.at(i); |
411 | const AudioOutputDevice &info = AudioOutputDevice::fromIndex(index: devIndex); |
412 | if (!info.property(name: "available" ).toBool()) { |
413 | if (device.index() == devIndex) { |
414 | // we've reached the currently used device and it's not available anymore, so we |
415 | // fallback to the next available device |
416 | changeType = FallbackChange; |
417 | } |
418 | pDebug() << devIndex << "is not available" ; |
419 | continue; |
420 | } |
421 | pDebug() << devIndex << "is available" ; |
422 | if (device.index() == devIndex) { |
423 | // we've reached the currently used device, nothing to change |
424 | break; |
425 | } |
426 | if (callSetOutputDevice(d: this, dev: info)) { |
427 | handleAutomaticDeviceChange(newDev: info, type: changeType); |
428 | break; // found one with higher preference that works |
429 | } |
430 | } |
431 | #endif //QT_NO_PHONON_SETTINGSGROUP |
432 | } |
433 | |
434 | void AudioOutputPrivate::_k_deviceChanged(int deviceIndex) |
435 | { |
436 | // NB that this method is only used by PulseAudio at present. |
437 | |
438 | // 1. Check to see if we are overridden. If we are, and devices do not match, |
439 | // then try and apply our own device as the output device. |
440 | // We only do this the first time |
441 | if (outputDeviceOverridden && forceMove) { |
442 | forceMove = false; |
443 | const AudioOutputDevice ¤tDevice = AudioOutputDevice::fromIndex(index: deviceIndex); |
444 | if (currentDevice != device) { |
445 | if (!callSetOutputDevice(d: this, dev: device)) { |
446 | // What to do if we are overridden and cannot change to our preferred device? |
447 | } |
448 | } |
449 | } |
450 | // 2. If we are not overridden, then we need to update our perception of what |
451 | // device we are using. If the devices do not match, something lower in the |
452 | // stack is overriding our preferences (e.g. a per-application stream preference, |
453 | // specific application move, priority list changed etc. etc.) |
454 | else if (!outputDeviceOverridden) { |
455 | const AudioOutputDevice ¤tDevice = AudioOutputDevice::fromIndex(index: deviceIndex); |
456 | if (currentDevice != device) { |
457 | // The device is not what we think it is, so lets say what is happening. |
458 | handleAutomaticDeviceChange(newDev: currentDevice, type: SoundSystemChange); |
459 | } |
460 | } |
461 | } |
462 | |
463 | static struct |
464 | { |
465 | int first; |
466 | int second; |
467 | } g_lastFallback = { .first: 0, .second: 0 }; |
468 | |
469 | void AudioOutputPrivate::handleAutomaticDeviceChange(const AudioOutputDevice &device2, DeviceChangeType type) |
470 | { |
471 | P_Q(AudioOutput); |
472 | deviceBeforeFallback = device.index(); |
473 | device = device2; |
474 | emit q->outputDeviceChanged(newAudioOutputDevice: device2); |
475 | const AudioOutputDevice &device1 = AudioOutputDevice::fromIndex(index: deviceBeforeFallback); |
476 | switch (type) { |
477 | case FallbackChange: |
478 | if (g_lastFallback.first != device1.index() || g_lastFallback.second != device2.index()) { |
479 | #ifndef QT_NO_PHONON_PLATFORMPLUGIN |
480 | const QString &text = //device2.isValid() ? |
481 | AudioOutput::tr(s: "<html>The audio playback device <b>%1</b> does not work.<br/>" |
482 | "Falling back to <b>%2</b>.</html>" ).arg(a: device1.name()).arg(a: device2.name()) /*: |
483 | AudioOutput::tr("<html>The audio playback device <b>%1</b> does not work.<br/>" |
484 | "No other device available.</html>").arg(device1.name())*/; |
485 | Platform::notification(notificationName: "AudioDeviceFallback" , text); |
486 | #endif //QT_NO_PHONON_PLATFORMPLUGIN |
487 | g_lastFallback.first = device1.index(); |
488 | g_lastFallback.second = device2.index(); |
489 | } |
490 | break; |
491 | case HigherPreferenceChange: |
492 | { |
493 | #ifndef QT_NO_PHONON_PLATFORMPLUGIN |
494 | const QString text = AudioOutput::tr(s: "<html>Switching to the audio playback device <b>%1</b><br/>" |
495 | "which just became available and has higher preference.</html>" ).arg(a: device2.name()); |
496 | Platform::notification(notificationName: "AudioDeviceFallback" , text, |
497 | actions: QStringList(AudioOutput::tr(s: "Revert back to device '%1'" ).arg(a: device1.name())), |
498 | receiver: q, SLOT(_k_revertFallback())); |
499 | #endif //QT_NO_PHONON_PLATFORMPLUGIN |
500 | g_lastFallback.first = 0; |
501 | g_lastFallback.second = 0; |
502 | } |
503 | break; |
504 | case SoundSystemChange: |
505 | { |
506 | #ifndef QT_NO_PHONON_PLATFORMPLUGIN |
507 | // If device1 is not "valid" this indicates that the preferences used to select |
508 | // a device was perhaps not available when this object was created (although |
509 | // I can't quite work out how that would be....) |
510 | if (device1.isValid()) { |
511 | if (device1.property(name: "available" ).toBool()) { |
512 | const QString text = AudioOutput::tr(s: "<html>Switching to the audio playback device <b>%1</b><br/>" |
513 | "which has higher preference or is specifically configured for this stream.</html>" ).arg(a: device2.name()); |
514 | Platform::notification(notificationName: "AudioDeviceFallback" , text, |
515 | actions: QStringList(AudioOutput::tr(s: "Revert back to device '%1'" ).arg(a: device1.name())), |
516 | receiver: q, SLOT(_k_revertFallback())); |
517 | } else { |
518 | const QString &text = |
519 | AudioOutput::tr(s: "<html>The audio playback device <b>%1</b> does not work.<br/>" |
520 | "Falling back to <b>%2</b>.</html>" ).arg(a: device1.name()).arg(a: device2.name()); |
521 | Platform::notification(notificationName: "AudioDeviceFallback" , text); |
522 | } |
523 | } |
524 | #endif //QT_NO_PHONON_PLATFORMPLUGIN |
525 | //outputDeviceOverridden = true; |
526 | g_lastFallback.first = 0; |
527 | g_lastFallback.second = 0; |
528 | } |
529 | break; |
530 | } |
531 | } |
532 | |
533 | AudioOutputPrivate::~AudioOutputPrivate() |
534 | { |
535 | PulseSupport *pulse = PulseSupport::getInstanceOrNull(allowNull: true); |
536 | if (pulse) { |
537 | pulse->clearStreamCache(streamUuid); |
538 | } |
539 | } |
540 | |
541 | } //namespace Phonon |
542 | |
543 | #include "moc_audiooutput.cpp" |
544 | |
545 | #undef PHONON_CLASSNAME |
546 | #undef PHONON_INTERFACENAME |
547 | #undef IFECES7 |
548 | #undef IFACES2 |
549 | #undef IFACES1 |
550 | #undef IFACES0 |
551 | |