22#include "audiooutput.h"
23#include "audiooutput_p.h"
25#include "audiooutputinterface.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"
34# include "pulsestream_p.h"
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
52static inline bool callSetOutputDevice(AudioOutputPrivate *
const d,
const AudioOutputDevice &dev)
54 PulseSupport *pulse = PulseSupport::getInstance();
55 if (pulse->isActive())
56 return pulse->setOutputDevice(d->getStreamUuid(), dev.index());
58 if (!d->backendObject())
61 Iface<IFACES2> iface(d);
63 return iface->setOutputDevice(dev);
65 return Iface<IFACES0>::cast(d)->setOutputDevice(dev.index());
82void AudioOutputPrivate::init(Phonon::Category c)
87#ifndef QT_NO_QUUID_STRING
91 createBackendObject();
94 PulseSupport *pulse = PulseSupport::getInstance();
95 if (pulse->isActive()) {
96 PulseStream *stream = pulse->registerOutputStream(streamUuid, category);
98 q->connect(stream, SIGNAL(usingDevice(
int)), SLOT(_k_deviceChanged(
int)));
99 q->connect(stream, SIGNAL(volumeChanged(qreal)), SLOT(_k_volumeChanged(qreal)));
100 q->connect(stream, SIGNAL(muteChanged(
bool)), SLOT(_k_mutedChanged(
bool)));
102 AudioOutputInterface47 *iface = Iface<AudioOutputInterface47>::cast(
this);
104 iface->setStreamUuid(streamUuid);
106 pulse->setupStreamEnvironment(streamUuid);
111 q->connect(Factory::sender(), SIGNAL(availableAudioOutputDevicesChanged()), SLOT(_k_deviceListChanged()));
114QString AudioOutputPrivate::getStreamUuid()
119void AudioOutputPrivate::createBackendObject()
124 m_backendObject = Factory::createAudioOutput(q);
128 device =
AudioOutputDevice::fromIndex(GlobalConfig().audioOutputDeviceFor(category, GlobalConfig::AdvancedDevicesFromSettings | GlobalConfig::HideUnavailableDevices));
129 if (m_backendObject) {
130 setupBackendObject();
140void AudioOutput::setName(
const QString &newName)
143 if (d->name == newName) {
147 PulseSupport *pulse = PulseSupport::getInstance();
148 if (pulse->isActive())
149 pulse->setOutputName(d->getStreamUuid(), newName);
151 setVolume(Platform::loadVolume(newName));
154static const qreal LOUDNESS_TO_VOLTAGE_EXPONENT = qreal(0.67);
155static const qreal VOLTAGE_TO_LOUDNESS_EXPONENT = qreal(1.0/LOUDNESS_TO_VOLTAGE_EXPONENT);
157void AudioOutput::setVolume(qreal volume)
161 PulseSupport *pulse = PulseSupport::getInstance();
162 if (k_ptr->backendObject()) {
163 if (pulse->isActive()) {
164 pulse->setOutputVolume(d->getStreamUuid(),
volume);
165 }
else if (!d->muted) {
170 INTERFACE_CALL(setVolume(pow(
volume, VOLTAGE_TO_LOUDNESS_EXPONENT)));
177 if (!pulse->isActive())
178 Platform::saveVolume(d->name,
volume);
184 if (d->muted || !d->m_backendObject || PulseSupport::getInstance()->isActive())
186 return pow(INTERFACE_CALL(
volume()), LOUDNESS_TO_VOLTAGE_EXPONENT);
189#ifndef PHONON_LOG10OVER20
190#define PHONON_LOG10OVER20
191static const qreal log10over20 = qreal(0.1151292546497022842);
197 if (d->muted || !d->m_backendObject || PulseSupport::getInstance()->isActive())
198 return log(d->volume) / log10over20;
199 return 0.67 * log(INTERFACE_CALL(
volume())) / log10over20;
202void AudioOutput::setVolumeDecibel(qreal newVolumeDecibel)
204 setVolume(exp(newVolumeDecibel * log10over20));
207bool AudioOutput::isMuted()
const
213void AudioOutput::setMuted(
bool mute)
217 if (d->muted == mute) {
222 if (!k_ptr->backendObject()) {
226 PulseSupport *pulse = PulseSupport::getInstance();
227 if (pulse->isActive()) {
228 pulse->setOutputMute(d->getStreamUuid(), mute);
231 Iface<IFACES9> iface9(d);
233 iface9->setMuted(mute);
239 INTERFACE_CALL(setVolume(0.0));
241 INTERFACE_CALL(setVolume(pow(d->volume, VOLTAGE_TO_LOUDNESS_EXPONENT)));
259bool AudioOutput::setOutputDevice(
const AudioOutputDevice &newAudioOutputDevice)
262 if (!newAudioOutputDevice.isValid()) {
263 d->outputDeviceOverridden = d->forceMove =
false;
264 const int newIndex = GlobalConfig().audioOutputDeviceFor(d->category);
265 if (newIndex == d->device.index()) {
270 d->outputDeviceOverridden = d->forceMove =
true;
271 if (d->device == newAudioOutputDevice) {
274 d->device = newAudioOutputDevice;
276 if (k_ptr->backendObject()) {
277 return callSetOutputDevice(d, d->device);
282bool AudioOutputPrivate::aboutToDeleteBackendObject()
284 if (m_backendObject) {
287 return AbstractAudioOutputPrivate::aboutToDeleteBackendObject();
290void AudioOutputPrivate::setupBackendObject()
293 Q_ASSERT(m_backendObject);
294 AbstractAudioOutputPrivate::setupBackendObject();
296 QObject::connect(m_backendObject, SIGNAL(volumeChanged(qreal)), q, SLOT(_k_volumeChanged(qreal)));
297 QObject::connect(m_backendObject, SIGNAL(audioDeviceFailed()), q, SLOT(_k_audioDeviceFailed()));
298 if (Iface<IFACES9>(
this)) {
300 q, SLOT(_k_mutedChanged(
bool)));
303 Iface<IFACES10> iface10(
this);
305 iface10->setCategory(category);
308 if (!PulseSupport::getInstance()->isActive()) {
310 pINTERFACE_CALL(
setVolume(pow(volume, VOLTAGE_TO_LOUDNESS_EXPONENT)));
312#ifndef QT_NO_PHONON_SETTINGSGROUP
317 if (!callSetOutputDevice(
this, device) && !outputDeviceOverridden) {
319 QList<int> deviceList = GlobalConfig().audioOutputDeviceListFor(category, GlobalConfig::AdvancedDevicesFromSettings | GlobalConfig::HideUnavailableDevices);
323 for (
int i = 0; i < deviceList.
count(); ++i) {
325 if (callSetOutputDevice(
this, dev)) {
326 handleAutomaticDeviceChange(dev, AudioOutputPrivate::FallbackChange);
331 const AudioOutputDevice none;
332 callSetOutputDevice(
this, none);
333 handleAutomaticDeviceChange(none, FallbackChange);
339void AudioOutputPrivate::_k_volumeChanged(qreal newVolume)
341 volume = pow(newVolume, LOUDNESS_TO_VOLTAGE_EXPONENT);
344 emit q->volumeChanged(volume);
348void AudioOutputPrivate::_k_mutedChanged(
bool newMuted)
352 emit q->mutedChanged(newMuted);
355void AudioOutputPrivate::_k_revertFallback()
357 if (deviceBeforeFallback == -1) {
361 callSetOutputDevice(
this, device);
363 emit q->outputDeviceChanged(device);
366void AudioOutputPrivate::_k_audioDeviceFailed()
368 if (PulseSupport::getInstance()->isActive())
371#ifndef QT_NO_PHONON_SETTINGSGROUP
373 pDebug() << Q_FUNC_INFO;
376 const QList<int> deviceList = GlobalConfig().audioOutputDeviceListFor(category, GlobalConfig::AdvancedDevicesFromSettings | GlobalConfig::HideUnavailableDevices);
377 for (
int i = 0; i < deviceList.
count(); ++i) {
378 const int devIndex = deviceList.
at(i);
380 if (device.index() != devIndex) {
382 if (callSetOutputDevice(
this, info)) {
383 handleAutomaticDeviceChange(info, FallbackChange);
390 const AudioOutputDevice none;
391 callSetOutputDevice(
this, none);
392 handleAutomaticDeviceChange(none, FallbackChange);
395void AudioOutputPrivate::_k_deviceListChanged()
397 if (PulseSupport::getInstance()->isActive())
400#ifndef QT_NO_PHONON_SETTINGSGROUP
401 pDebug() << Q_FUNC_INFO;
403 if (outputDeviceOverridden && device.property(
"available").toBool()) {
407 const QList<int> deviceList = GlobalConfig().audioOutputDeviceListFor(category, GlobalConfig::AdvancedDevicesFromSettings);
408 DeviceChangeType changeType = HigherPreferenceChange;
409 for (
int i = 0; i < deviceList.
count(); ++i) {
410 const int devIndex = deviceList.
at(i);
412 if (!info.property(
"available").toBool()) {
413 if (device.index() == devIndex) {
416 changeType = FallbackChange;
418 pDebug() << devIndex <<
"is not available";
421 pDebug() << devIndex <<
"is available";
422 if (device.index() == devIndex) {
426 if (callSetOutputDevice(
this, info)) {
427 handleAutomaticDeviceChange(info, changeType);
434void AudioOutputPrivate::_k_deviceChanged(
int deviceIndex)
441 if (outputDeviceOverridden && forceMove) {
444 if (currentDevice != device) {
445 if (!callSetOutputDevice(
this, device)) {
454 else if (!outputDeviceOverridden) {
456 if (currentDevice != device) {
458 handleAutomaticDeviceChange(currentDevice, SoundSystemChange);
467} g_lastFallback = { 0, 0 };
469void AudioOutputPrivate::handleAutomaticDeviceChange(
const AudioOutputDevice &device2, DeviceChangeType type)
472 deviceBeforeFallback = device.index();
474 emit q->outputDeviceChanged(device2);
478 if (g_lastFallback.first != device1.index() || g_lastFallback.second != device2.index()) {
479#ifndef QT_NO_PHONON_PLATFORMPLUGIN
481 AudioOutput::tr(
"<html>The audio playback device <b>%1</b> does not work.<br/>"
482 "Falling back to <b>%2</b>.</html>").
arg(device1.name()).
arg(device2.name())
485 Platform::notification(
"AudioDeviceFallback", text);
487 g_lastFallback.first = device1.index();
488 g_lastFallback.second = device2.index();
491 case HigherPreferenceChange:
493#ifndef QT_NO_PHONON_PLATFORMPLUGIN
495 "which just became available and has higher preference.</html>").
arg(device2.name());
496 Platform::notification(
"AudioDeviceFallback", text,
498 q, SLOT(_k_revertFallback()));
500 g_lastFallback.first = 0;
501 g_lastFallback.second = 0;
504 case SoundSystemChange:
506#ifndef QT_NO_PHONON_PLATFORMPLUGIN
510 if (device1.isValid()) {
511 if (device1.property(
"available").toBool()) {
513 "which has higher preference or is specifically configured for this stream.</html>").
arg(device2.name());
514 Platform::notification(
"AudioDeviceFallback", text,
516 q, SLOT(_k_revertFallback()));
519 AudioOutput::tr(
"<html>The audio playback device <b>%1</b> does not work.<br/>"
520 "Falling back to <b>%2</b>.</html>").
arg(device1.name()).
arg(device2.name());
521 Platform::notification(
"AudioDeviceFallback", text);
526 g_lastFallback.first = 0;
527 g_lastFallback.second = 0;
533AudioOutputPrivate::~AudioOutputPrivate()
535 PulseSupport *pulse = PulseSupport::getInstanceOrNull(
true);
537 pulse->clearStreamCache(streamUuid);
543#include "moc_audiooutput.cpp"
545#undef PHONON_CLASSNAME
546#undef PHONON_INTERFACENAME
Common base class for all audio outputs.
Class for audio output to the soundcard.
void mutedChanged(bool)
This signal is emitted when the muted property has changed.
QString name
This is the name that appears in Mixer applications that control the volume of this output.
AudioOutput(Phonon::Category category, QObject *parent=nullptr)
Creates a new AudioOutput that defines output to a physical device.
void volumeChanged(qreal newVolume)
This signal is emitted whenever the volume has changed.
qreal volume
This is the current loudness of the output (it is using Stevens' law to calculate the change in volta...
Phonon::Category category() const
Returns the category of this output.
AudioOutputDevice outputDevice
This property holds the (hardware) destination for the output.
qreal volumeDecibel
This is the current volume of the output in decibel.
Provides a tuple of enduser visible name and description.
static ObjectDescription< T > fromIndex(int index)
Returns a new description object that describes the device/effect/codec/... with the given index.
void setVolume(qreal volume)
const_reference at(qsizetype i) const const
qsizetype count() const const
bool isEmpty() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString tr(const char *sourceText, const char *disambiguation, int n)
QString arg(Args &&... args) const const
QString toString(StringFormat mode) const const