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#include "factory_p.h"
25#include "objectdescription.h"
26#include "audiooutputadaptor_p.h"
27#include "globalconfig.h"
28#include "audiooutputinterface.h"
29#include "phononnamespace_p.h"
30#include "platform_p.h"
31#include "pulsesupport.h"
32
33#include <QtCore/qmath.h>
34#include <QtCore/quuid.h>
35
36#define PHONON_CLASSNAME AudioOutput
37#define IFACES2 AudioOutputInterface42
38#define IFACES1 IFACES2
39#define IFACES0 AudioOutputInterface40, IFACES1
40#define PHONON_INTERFACENAME IFACES0
41
42QT_BEGIN_NAMESPACE
43
44namespace Phonon
45{
46
47static inline bool callSetOutputDevice(AudioOutputPrivate *const d, int index)
48{
49 PulseSupport *pulse = PulseSupport::getInstance();
50 if (pulse->isActive())
51 return pulse->setOutputDevice(d->getStreamUuid(), index);
52
53 Iface<IFACES2> iface(d);
54 if (iface) {
55 return iface->setOutputDevice(AudioOutputDevice::fromIndex(index));
56 }
57 return Iface<IFACES0>::cast(d)->setOutputDevice(index);
58}
59
60static inline bool callSetOutputDevice(AudioOutputPrivate *const d, const AudioOutputDevice &dev)
61{
62 PulseSupport *pulse = PulseSupport::getInstance();
63 if (pulse->isActive())
64 return pulse->setOutputDevice(d->getStreamUuid(), dev.index());
65
66 Iface<IFACES2> iface(d);
67 if (iface) {
68 return iface->setOutputDevice(dev);
69 }
70 return Iface<IFACES0>::cast(d)->setOutputDevice(dev.index());
71}
72
73AudioOutput::AudioOutput(Phonon::Category category, QObject *parent)
74 : AbstractAudioOutput(*new AudioOutputPrivate, parent)
75{
76 K_D(AudioOutput);
77 d->init(category);
78}
79
80AudioOutput::AudioOutput(QObject *parent)
81 : AbstractAudioOutput(*new AudioOutputPrivate, parent)
82{
83 K_D(AudioOutput);
84 d->init(NoCategory);
85}
86
87void AudioOutputPrivate::init(Phonon::Category c)
88{
89 Q_Q(AudioOutput);
90#ifndef QT_NO_DBUS
91 adaptor = new AudioOutputAdaptor(q);
92 static unsigned int number = 0;
93 const QString &path = QLatin1String("/AudioOutputs/") + QString::number(number++);
94 QDBusConnection con = QDBusConnection::sessionBus();
95 con.registerObject(path, q);
96 emit adaptor->newOutputAvailable(con.baseService(), path);
97 q->connect(q, SIGNAL(volumeChanged(qreal)), adaptor, SIGNAL(volumeChanged(qreal)));
98 q->connect(q, SIGNAL(mutedChanged(bool)), adaptor, SIGNAL(mutedChanged(bool)));
99#endif
100
101 category = c;
102 streamUuid = QUuid::createUuid().toString();
103 PulseSupport *pulse = PulseSupport::getInstance();
104 pulse->setStreamPropList(category, streamUuid);
105 q->connect(pulse, SIGNAL(usingDevice(QString,int)), SLOT(_k_deviceChanged(QString,int)));
106
107 createBackendObject();
108
109 q->connect(Factory::sender(), SIGNAL(availableAudioOutputDevicesChanged()), SLOT(_k_deviceListChanged()));
110}
111
112QString AudioOutputPrivate::getStreamUuid()
113{
114 return streamUuid;
115}
116
117void AudioOutputPrivate::createBackendObject()
118{
119 if (m_backendObject)
120 return;
121 Q_Q(AudioOutput);
122 m_backendObject = Factory::createAudioOutput(q);
123 device = AudioOutputDevice::fromIndex(GlobalConfig().audioOutputDeviceFor(category, GlobalConfig::AdvancedDevicesFromSettings | GlobalConfig::HideUnavailableDevices));
124 if (m_backendObject) {
125 setupBackendObject();
126 }
127}
128
129QString AudioOutput::name() const
130{
131 K_D(const AudioOutput);
132 return d->name;
133}
134
135void AudioOutput::setName(const QString &newName)
136{
137 K_D(AudioOutput);
138 if (d->name == newName) {
139 return;
140 }
141 d->name = newName;
142 setVolume(Platform::loadVolume(newName));
143#ifndef QT_NO_DBUS
144 if (d->adaptor) {
145 emit d->adaptor->nameChanged(newName);
146 }
147#endif
148}
149
150static const qreal LOUDNESS_TO_VOLTAGE_EXPONENT = qreal(0.67);
151static const qreal VOLTAGE_TO_LOUDNESS_EXPONENT = qreal(1.0/LOUDNESS_TO_VOLTAGE_EXPONENT);
152
153void AudioOutput::setVolume(qreal volume)
154{
155 K_D(AudioOutput);
156 d->volume = volume;
157 if (k_ptr->backendObject() && !d->muted) {
158 // using Stevens' power law loudness is proportional to (sound pressure)^0.67
159 // sound pressure is proportional to voltage:
160 // p² \prop P \prop V²
161 // => if a factor for loudness of x is requested
162 INTERFACE_CALL(setVolume(pow(volume, VOLTAGE_TO_LOUDNESS_EXPONENT)));
163 } else {
164 emit volumeChanged(volume);
165 }
166 Platform::saveVolume(d->name, volume);
167}
168
169qreal AudioOutput::volume() const
170{
171 K_D(const AudioOutput);
172 if (d->muted || !d->m_backendObject) {
173 return d->volume;
174 }
175 return pow(INTERFACE_CALL(volume()), LOUDNESS_TO_VOLTAGE_EXPONENT);
176}
177
178#ifndef PHONON_LOG10OVER20
179#define PHONON_LOG10OVER20
180static const qreal log10over20 = qreal(0.1151292546497022842); // ln(10) / 20
181#endif // PHONON_LOG10OVER20
182
183qreal AudioOutput::volumeDecibel() const
184{
185 K_D(const AudioOutput);
186 if (d->muted || !d->m_backendObject) {
187 return log(d->volume) / log10over20;
188 }
189 return 0.67 * log(INTERFACE_CALL(volume())) / log10over20;
190}
191
192void AudioOutput::setVolumeDecibel(qreal newVolumeDecibel)
193{
194 setVolume(exp(newVolumeDecibel * log10over20));
195}
196
197bool AudioOutput::isMuted() const
198{
199 K_D(const AudioOutput);
200 return d->muted;
201}
202
203void AudioOutput::setMuted(bool mute)
204{
205 K_D(AudioOutput);
206 if (d->muted != mute) {
207 if (mute) {
208 d->muted = mute;
209 if (k_ptr->backendObject()) {
210 INTERFACE_CALL(setVolume(0.0));
211 }
212 } else {
213 if (k_ptr->backendObject()) {
214 INTERFACE_CALL(setVolume(pow(d->volume, VOLTAGE_TO_LOUDNESS_EXPONENT)));
215 }
216 d->muted = mute;
217 }
218 emit mutedChanged(mute);
219 }
220}
221
222Category AudioOutput::category() const
223{
224 K_D(const AudioOutput);
225 return d->category;
226}
227
228AudioOutputDevice AudioOutput::outputDevice() const
229{
230 K_D(const AudioOutput);
231 return d->device;
232}
233
234bool AudioOutput::setOutputDevice(const AudioOutputDevice &newAudioOutputDevice)
235{
236 K_D(AudioOutput);
237 if (!newAudioOutputDevice.isValid()) {
238 d->outputDeviceOverridden = d->forceMove = false;
239 const int newIndex = GlobalConfig().audioOutputDeviceFor(d->category);
240 if (newIndex == d->device.index()) {
241 return true;
242 }
243 d->device = AudioOutputDevice::fromIndex(newIndex);
244 } else {
245 d->outputDeviceOverridden = d->forceMove = true;
246 if (d->device == newAudioOutputDevice) {
247 return true;
248 }
249 d->device = newAudioOutputDevice;
250 }
251 if (k_ptr->backendObject()) {
252 return callSetOutputDevice(d, d->device.index());
253 }
254 return true;
255}
256
257bool AudioOutputPrivate::aboutToDeleteBackendObject()
258{
259 if (m_backendObject) {
260 volume = pINTERFACE_CALL(volume());
261 }
262 return AbstractAudioOutputPrivate::aboutToDeleteBackendObject();
263}
264
265void AudioOutputPrivate::setupBackendObject()
266{
267 Q_Q(AudioOutput);
268 Q_ASSERT(m_backendObject);
269 AbstractAudioOutputPrivate::setupBackendObject();
270
271 QObject::connect(m_backendObject, SIGNAL(volumeChanged(qreal)), q, SLOT(_k_volumeChanged(qreal)));
272 QObject::connect(m_backendObject, SIGNAL(audioDeviceFailed()), q, SLOT(_k_audioDeviceFailed()));
273
274 // set up attributes
275 pINTERFACE_CALL(setVolume(pow(volume, VOLTAGE_TO_LOUDNESS_EXPONENT)));
276
277#ifndef QT_NO_PHONON_SETTINGSGROUP
278 // if the output device is not available and the device was not explicitly set
279 // There is no need to set the output device initially if PA is used as
280 // we know it will not work (stream doesn't exist yet) and that this will be
281 // handled by _k_deviceChanged()
282 if (!PulseSupport::getInstance()->isActive() && !callSetOutputDevice(this, device) && !outputDeviceOverridden) {
283 // fall back in the preference list of output devices
284 QList<int> deviceList = GlobalConfig().audioOutputDeviceListFor(category, GlobalConfig::AdvancedDevicesFromSettings | GlobalConfig::HideUnavailableDevices);
285 if (deviceList.isEmpty()) {
286 return;
287 }
288 for (int i = 0; i < deviceList.count(); ++i) {
289 const AudioOutputDevice &dev = AudioOutputDevice::fromIndex(deviceList.at(i));
290 if (callSetOutputDevice(this, dev)) {
291 handleAutomaticDeviceChange(dev, AudioOutputPrivate::FallbackChange);
292 return; // found one that works
293 }
294 }
295 // if we get here there is no working output device. Tell the backend.
296 const AudioOutputDevice none;
297 callSetOutputDevice(this, none);
298 handleAutomaticDeviceChange(none, FallbackChange);
299 }
300#endif //QT_NO_PHONON_SETTINGSGROUP
301}
302
303void AudioOutputPrivate::_k_volumeChanged(qreal newVolume)
304{
305 if (!muted) {
306 Q_Q(AudioOutput);
307 emit q->volumeChanged(pow(newVolume, qreal(0.67)));
308 }
309}
310
311void AudioOutputPrivate::_k_revertFallback()
312{
313 if (deviceBeforeFallback == -1) {
314 return;
315 }
316 device = AudioOutputDevice::fromIndex(deviceBeforeFallback);
317 callSetOutputDevice(this, device);
318 Q_Q(AudioOutput);
319 emit q->outputDeviceChanged(device);
320#ifndef QT_NO_DBUS
321 emit adaptor->outputDeviceIndexChanged(device.index());
322#endif
323}
324
325void AudioOutputPrivate::_k_audioDeviceFailed()
326{
327 if (PulseSupport::getInstance()->isActive())
328 return;
329
330#ifndef QT_NO_PHONON_SETTINGSGROUP
331
332 pDebug() << Q_FUNC_INFO;
333 // outputDeviceIndex identifies a failing device
334 // fall back in the preference list of output devices
335 const QList<int> deviceList = GlobalConfig().audioOutputDeviceListFor(category, GlobalConfig::AdvancedDevicesFromSettings | GlobalConfig::HideUnavailableDevices);
336 for (int i = 0; i < deviceList.count(); ++i) {
337 const int devIndex = deviceList.at(i);
338 // if it's the same device as the one that failed, ignore it
339 if (device.index() != devIndex) {
340 const AudioOutputDevice &info = AudioOutputDevice::fromIndex(devIndex);
341 if (callSetOutputDevice(this, info)) {
342 handleAutomaticDeviceChange(info, FallbackChange);
343 return; // found one that works
344 }
345 }
346 }
347#endif //QT_NO_PHONON_SETTINGSGROUP
348 // if we get here there is no working output device. Tell the backend.
349 const AudioOutputDevice none;
350 callSetOutputDevice(this, none);
351 handleAutomaticDeviceChange(none, FallbackChange);
352}
353
354void AudioOutputPrivate::_k_deviceListChanged()
355{
356 if (PulseSupport::getInstance()->isActive())
357 return;
358
359#ifndef QT_NO_PHONON_SETTINGSGROUP
360 pDebug() << Q_FUNC_INFO;
361 // Check to see if we have an override and do not change to a higher priority device if the overridden device is still present.
362 if (outputDeviceOverridden && device.property("available").toBool()) {
363 return;
364 }
365 // let's see if there's a usable device higher in the preference list
366 const QList<int> deviceList = GlobalConfig().audioOutputDeviceListFor(category, GlobalConfig::AdvancedDevicesFromSettings);
367 DeviceChangeType changeType = HigherPreferenceChange;
368 for (int i = 0; i < deviceList.count(); ++i) {
369 const int devIndex = deviceList.at(i);
370 const AudioOutputDevice &info = AudioOutputDevice::fromIndex(devIndex);
371 if (!info.property("available").toBool()) {
372 if (device.index() == devIndex) {
373 // we've reached the currently used device and it's not available anymore, so we
374 // fallback to the next available device
375 changeType = FallbackChange;
376 }
377 pDebug() << devIndex << "is not available";
378 continue;
379 }
380 pDebug() << devIndex << "is available";
381 if (device.index() == devIndex) {
382 // we've reached the currently used device, nothing to change
383 break;
384 }
385 if (callSetOutputDevice(this, info)) {
386 handleAutomaticDeviceChange(info, changeType);
387 break; // found one with higher preference that works
388 }
389 }
390#endif //QT_NO_PHONON_SETTINGSGROUP
391}
392
393void AudioOutputPrivate::_k_deviceChanged(QString inStreamUuid, int deviceIndex)
394{
395 // Note that this method is only used by PulseAudio at present.
396 if (inStreamUuid == streamUuid) {
397 // 1. Check to see if we are overridden. If we are, and devices do not match,
398 // then try and apply our own device as the output device.
399 // We only do this the first time
400 if (outputDeviceOverridden && forceMove) {
401 forceMove = false;
402 const AudioOutputDevice &currentDevice = AudioOutputDevice::fromIndex(deviceIndex);
403 if (currentDevice != device) {
404 if (!callSetOutputDevice(this, device)) {
405 // What to do if we are overridden and cannot change to our preferred device?
406 }
407 }
408 }
409 // 2. If we are not overridden, then we need to update our perception of what
410 // device we are using. If the devices do not match, something lower in the
411 // stack is overriding our preferences (e.g. a per-application stream preference,
412 // specific application move, priority list changed etc. etc.)
413 else if (!outputDeviceOverridden) {
414 const AudioOutputDevice &currentDevice = AudioOutputDevice::fromIndex(deviceIndex);
415 if (currentDevice != device) {
416 // The device is not what we think it is, so lets say what is happening.
417 handleAutomaticDeviceChange(currentDevice, SoundSystemChange);
418 }
419 }
420 }
421}
422
423static struct
424{
425 int first;
426 int second;
427} g_lastFallback = { 0, 0 };
428
429void AudioOutputPrivate::handleAutomaticDeviceChange(const AudioOutputDevice &device2, DeviceChangeType type)
430{
431 Q_Q(AudioOutput);
432 deviceBeforeFallback = device.index();
433 device = device2;
434 emit q->outputDeviceChanged(device2);
435#ifndef QT_NO_DBUS
436 emit adaptor->outputDeviceIndexChanged(device.index());
437#endif
438 const AudioOutputDevice &device1 = AudioOutputDevice::fromIndex(deviceBeforeFallback);
439 switch (type) {
440 case FallbackChange:
441 if (g_lastFallback.first != device1.index() || g_lastFallback.second != device2.index()) {
442#ifndef QT_NO_PHONON_PLATFORMPLUGIN
443 const QString &text = //device2.isValid() ?
444 AudioOutput::tr("<html>The audio playback device <b>%1</b> does not work.<br/>"
445 "Falling back to <b>%2</b>.</html>").arg(device1.name()).arg(device2.name()) /*:
446 AudioOutput::tr("<html>The audio playback device <b>%1</b> does not work.<br/>"
447 "No other device available.</html>").arg(device1.name())*/;
448 Platform::notification("AudioDeviceFallback", text);
449#endif //QT_NO_PHONON_PLATFORMPLUGIN
450 g_lastFallback.first = device1.index();
451 g_lastFallback.second = device2.index();
452 }
453 break;
454 case HigherPreferenceChange:
455 {
456#ifndef QT_NO_PHONON_PLATFORMPLUGIN
457 const QString text = AudioOutput::tr("<html>Switching to the audio playback device <b>%1</b><br/>"
458 "which just became available and has higher preference.</html>").arg(device2.name());
459 Platform::notification("AudioDeviceFallback", text,
460 QStringList(AudioOutput::tr("Revert back to device '%1'").arg(device1.name())),
461 q, SLOT(_k_revertFallback()));
462#endif //QT_NO_PHONON_PLATFORMPLUGIN
463 g_lastFallback.first = 0;
464 g_lastFallback.second = 0;
465 }
466 break;
467 case SoundSystemChange:
468 {
469#ifndef QT_NO_PHONON_PLATFORMPLUGIN
470 if (device1.property("available").toBool()) {
471 const QString text = AudioOutput::tr("<html>Switching to the audio playback device <b>%1</b><br/>"
472 "which has higher preference or is specifically configured for this stream.</html>").arg(device2.name());
473 Platform::notification("AudioDeviceFallback", text,
474 QStringList(AudioOutput::tr("Revert back to device '%1'").arg(device1.name())),
475 q, SLOT(_k_revertFallback()));
476 } else {
477 const QString &text =
478 AudioOutput::tr("<html>The audio playback device <b>%1</b> does not work.<br/>"
479 "Falling back to <b>%2</b>.</html>").arg(device1.name()).arg(device2.name());
480 Platform::notification("AudioDeviceFallback", text);
481 }
482#endif //QT_NO_PHONON_PLATFORMPLUGIN
483 //outputDeviceOverridden = true;
484 g_lastFallback.first = 0;
485 g_lastFallback.second = 0;
486 }
487 break;
488 }
489}
490
491AudioOutputPrivate::~AudioOutputPrivate()
492{
493 PulseSupport::getInstance()->clearStreamCache(streamUuid);
494#ifndef QT_NO_DBUS
495 if (adaptor) {
496 emit adaptor->outputDestroyed();
497 }
498#endif
499}
500
501} //namespace Phonon
502
503QT_END_NAMESPACE
504
505#include "moc_audiooutput.cpp"
506
507#undef PHONON_CLASSNAME
508#undef PHONON_INTERFACENAME
509#undef IFACES2
510#undef IFACES1
511#undef IFACES0
512