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
42
QT_BEGIN_NAMESPACE
43
44
namespace Phonon
45
{
46
47
static 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
60
static 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
73
AudioOutput::AudioOutput(Phonon::Category category, QObject *parent)
74
    : AbstractAudioOutput(*new AudioOutputPrivate, parent)
75
{
76
    K_D(AudioOutput);
77
    d->init(category);
78
}
79
80
AudioOutput::AudioOutput(QObject *parent) 
81
    : AbstractAudioOutput(*new AudioOutputPrivate, parent)
82
{
83
    K_D(AudioOutput);
84
    d->init(NoCategory);
85
}
86
87
void 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
112
QString AudioOutputPrivate::getStreamUuid()
113
{
114
    return streamUuid;
115
}
116
117
void 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
129
QString AudioOutput::name() const
130
{
131
    K_D(const AudioOutput);
132
    return d->name;
133
}
134
135
void 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
150
static const qreal LOUDNESS_TO_VOLTAGE_EXPONENT = qreal(0.67);
151
static const qreal VOLTAGE_TO_LOUDNESS_EXPONENT = qreal(1.0/LOUDNESS_TO_VOLTAGE_EXPONENT);
152
153
void 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
169
qreal 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
180
static const qreal log10over20 = qreal(0.1151292546497022842); // ln(10) / 20
181
#endif // PHONON_LOG10OVER20
182
183
qreal 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
192
void AudioOutput::setVolumeDecibel(qreal newVolumeDecibel)
193
{
194
    setVolume(exp(newVolumeDecibel * log10over20));
195
}
196
197
bool AudioOutput::isMuted() const
198
{
199
    K_D(const AudioOutput);
200
    return d->muted;
201
}
202
203
void 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
222
Category AudioOutput::category() const
223
{
224
    K_D(const AudioOutput);
225
    return d->category;
226
}
227
228
AudioOutputDevice AudioOutput::outputDevice() const
229
{
230
    K_D(const AudioOutput);
231
    return d->device;
232
}
233
234
bool 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
257
bool AudioOutputPrivate::aboutToDeleteBackendObject()
258
{
259
    if (m_backendObject) {
260
        volume = pINTERFACE_CALL(volume());
261
    }
262
    return AbstractAudioOutputPrivate::aboutToDeleteBackendObject();
263
}
264
265
void 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
303
void 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
311
void 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
325
void 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
354
void 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
393
void 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
423
static struct
424
{
425
    int first;
426
    int second;
427
} g_lastFallback = { 0, 0 };
428
429
void 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
491
AudioOutputPrivate::~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
503
QT_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