File indexing completed on 2025-02-09 05:31:53
0001 /* 0002 Copyright (C) 2010 Colin Guthrie <cguthrie@mandriva.org> 0003 Copyright (C) 2013 Harald Sitter <sitter@kde.org> 0004 0005 This library is free software; you can redistribute it and/or 0006 modify it under the terms of the GNU Lesser General Public 0007 License as published by the Free Software Foundation; either 0008 version 2.1 of the License, or (at your option) version 3, or any 0009 later version accepted by the membership of KDE e.V. (or its 0010 successor approved by the membership of KDE e.V.), Nokia Corporation 0011 (or its successors, if any) and the KDE Free Qt Foundation, which shall 0012 act as a proxy defined in Section 6 of version 3 of the license. 0013 0014 This library is distributed in the hope that it will be useful, 0015 but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0017 Lesser General Public License for more details. 0018 0019 You should have received a copy of the GNU Lesser General Public 0020 License along with this library. If not, see <http://www.gnu.org/licenses/>. 0021 */ 0022 0023 #include "pulsesupport.h" 0024 0025 #include <QAbstractEventDispatcher> 0026 #include <QApplication> 0027 #include <QDebug> 0028 #include <QIcon> 0029 #include <QMutex> 0030 #include <QStringList> 0031 #include <QTimer> 0032 0033 #ifdef HAVE_PULSEAUDIO 0034 #include "pulsestream_p.h" 0035 #include <pulse/pulseaudio.h> 0036 #include <pulse/xmalloc.h> 0037 #include <pulse/glib-mainloop.h> 0038 0039 #define HAVE_PULSEAUDIO_DEVICE_MANAGER PA_CHECK_VERSION(0,9,21) 0040 #if HAVE_PULSEAUDIO_DEVICE_MANAGER 0041 # include <pulse/ext-device-manager.h> 0042 #endif 0043 #endif // HAVE_PULSEAUDIO 0044 0045 #include "phononnamespace_p.h" 0046 #include "platform_p.h" 0047 0048 #define PA_PROP_PHONON_STREAMID "phonon.streamid" 0049 0050 namespace Phonon 0051 { 0052 0053 QMutex probeMutex; 0054 static PulseSupport *s_instance = nullptr; 0055 static bool s_wasShutDown = false; 0056 static bool s_pulseActive = false; 0057 0058 #ifdef HAVE_PULSEAUDIO 0059 /*** 0060 * Prints a conditional debug message based on the current debug level 0061 * If obj is provided, classname and objectname will be printed as well 0062 * 0063 * see debugLevel() 0064 */ 0065 0066 static int debugLevel() { 0067 static int level = -1; 0068 if (level < 1) { 0069 level = 0; 0070 QByteArray pulseenv = qgetenv("PHONON_PULSEAUDIO_DEBUG"); 0071 int l = pulseenv.toInt(); 0072 if (l > 0) 0073 level = (l > 2 ? 2 : l); 0074 } 0075 return level; 0076 } 0077 0078 static void logMessage(const QString &message, int priority = 2, QObject *obj=nullptr); 0079 static void logMessage(const QString &message, int priority, QObject *obj) 0080 { 0081 if (debugLevel() > 0) { 0082 QString output; 0083 if (obj) { 0084 // Strip away namespace from className 0085 QByteArray className(obj->metaObject()->className()); 0086 int nameLength = className.length() - className.lastIndexOf(':') - 1; 0087 className = className.right(nameLength); 0088 output.asprintf("%s %s (%s %p)", message.toLatin1().constData(), 0089 obj->objectName().toLatin1().constData(), 0090 className.constData(), obj); 0091 } 0092 else { 0093 output = message; 0094 } 0095 if (priority <= debugLevel()) { 0096 qDebug() << QString::fromLatin1("PulseSupport(%1): %2").arg(priority).arg(output); 0097 } 0098 } 0099 } 0100 0101 0102 class AudioDevice 0103 { 0104 public: 0105 inline 0106 AudioDevice(QString name, QString desc, QString icon, uint32_t index) 0107 : pulseName(name), pulseIndex(index) 0108 { 0109 properties["name"] = desc; 0110 properties["description"] = QLatin1String(""); // We don't have descriptions (well we do, but we use them as the name!) 0111 properties["icon"] = icon; 0112 properties["available"] = (index != PA_INVALID_INDEX); 0113 properties["isAdvanced"] = false; // Nothing is advanced! 0114 0115 DeviceAccessList dal; 0116 dal.append(DeviceAccess("pulse", desc)); 0117 properties["deviceAccessList"] = QVariant::fromValue<DeviceAccessList>(dal); 0118 } 0119 0120 // Needed for QMap 0121 inline AudioDevice() {} 0122 0123 QString pulseName; 0124 uint32_t pulseIndex; 0125 QHash<QByteArray, QVariant> properties; 0126 }; 0127 bool operator!=(const AudioDevice &a, const AudioDevice &b) 0128 { 0129 return !(a.pulseName == b.pulseName && a.properties == b.properties); 0130 } 0131 0132 class PulseUserData 0133 { 0134 public: 0135 inline 0136 PulseUserData() 0137 { 0138 } 0139 0140 QMap<QString, AudioDevice> newOutputDevices; 0141 QMap<Phonon::Category, QMap<int, int> > newOutputDevicePriorities; // prio, device 0142 0143 QMap<QString, AudioDevice> newCaptureDevices; 0144 QMap<Phonon::CaptureCategory, QMap<int, int> > newCaptureDevicePriorities; // prio, device 0145 }; 0146 0147 static pa_glib_mainloop *s_mainloop = nullptr; 0148 static pa_context *s_context = nullptr; 0149 0150 0151 0152 static int s_deviceIndexCounter = 0; 0153 0154 static QMap<QString, int> s_outputDeviceIndexes; 0155 static QMap<int, AudioDevice> s_outputDevices; 0156 static QMap<Phonon::Category, QMap<int, int> > s_outputDevicePriorities; // prio, device 0157 static QMap<QString, PulseStream*> s_outputStreams; 0158 0159 static const Phonon::CaptureCategory s_audioCapCategories[] = { 0160 Phonon::NoCaptureCategory, 0161 Phonon::CommunicationCaptureCategory, 0162 Phonon::RecordingCaptureCategory, 0163 Phonon::ControlCaptureCategory 0164 }; 0165 0166 static const int s_audioCapCategoriesCount = sizeof(s_audioCapCategories) / sizeof(Phonon::CaptureCategory); 0167 0168 static QMap<QString, int> s_captureDeviceIndexes; 0169 static QMap<int, AudioDevice> s_captureDevices; 0170 static QMap<Phonon::CaptureCategory, QMap<int, int> > s_captureDevicePriorities; // prio, device 0171 static QMap<QString, PulseStream*> s_captureStreams; 0172 0173 static PulseStream* findStreamByPulseIndex(QMap<QString, PulseStream*> map, uint32_t index) 0174 { 0175 QMap<QString, PulseStream*>::iterator it; 0176 for (it = map.begin(); it != map.end(); ++it) 0177 if ((*it)->index() == index) 0178 return *it; 0179 return nullptr; 0180 } 0181 0182 static Phonon::Category pulseRoleToPhononCategory(const char *role, bool *success) 0183 { 0184 Q_ASSERT(role); 0185 Q_ASSERT(success); 0186 *success = true; 0187 QByteArray r(role); 0188 if (r == "none") 0189 return Phonon::NoCategory; 0190 if (r == "video") 0191 return Phonon::VideoCategory; 0192 if (r == "music") 0193 return Phonon::MusicCategory; 0194 if (r == "game") 0195 return Phonon::GameCategory; 0196 if (r == "event") 0197 return Phonon::NotificationCategory; 0198 if (r == "phone") 0199 return Phonon::CommunicationCategory; 0200 if (r == "a11y") 0201 return Phonon::AccessibilityCategory; 0202 0203 // ^^ "animation" and "production" have no mapping 0204 0205 *success = false; 0206 return Phonon::NoCategory; 0207 } 0208 0209 static Phonon::CaptureCategory pulseRoleToPhononCaptureCategory(const char *role, bool *success) 0210 { 0211 Q_ASSERT(role); 0212 Q_ASSERT(success); 0213 *success = true; 0214 QByteArray r(role); 0215 if (r == "none") 0216 return Phonon::NoCaptureCategory; 0217 if (r == "phone") 0218 return Phonon::CommunicationCaptureCategory; 0219 if (r == "production") 0220 return Phonon::RecordingCaptureCategory; 0221 if (r == "a11y") 0222 return Phonon::ControlCaptureCategory; 0223 0224 *success = false; 0225 return Phonon::NoCaptureCategory; 0226 } 0227 0228 static const QByteArray phononCategoryToPulseRole(Phonon::Category category) 0229 { 0230 switch (category) { 0231 case Phonon::NoCategory: 0232 return QByteArray("none"); 0233 case Phonon::VideoCategory: 0234 return QByteArray("video"); 0235 case Phonon::MusicCategory: 0236 return QByteArray("music"); 0237 case Phonon::GameCategory: 0238 return QByteArray("game"); 0239 case Phonon::NotificationCategory: 0240 return QByteArray("event"); 0241 case Phonon::CommunicationCategory: 0242 return QByteArray("phone"); 0243 case Phonon::AccessibilityCategory: 0244 return QByteArray("a11y"); 0245 default: 0246 return QByteArray(); 0247 } 0248 } 0249 0250 static const QByteArray phononCaptureCategoryToPulseRole(Phonon::CaptureCategory category) 0251 { 0252 switch (category) { 0253 case Phonon::NoCaptureCategory: 0254 return QByteArray("none"); 0255 case Phonon::CommunicationCaptureCategory: 0256 return QByteArray("phone"); 0257 case Phonon::RecordingCaptureCategory: 0258 return QByteArray("production"); 0259 case Phonon::ControlCaptureCategory: 0260 return QByteArray("a11y"); 0261 default: 0262 return QByteArray(); 0263 } 0264 } 0265 0266 static void createGenericDevices() 0267 { 0268 // OK so we don't have the device manager extension, but we can show a single device and fake it. 0269 int index; 0270 s_outputDeviceIndexes.clear(); 0271 s_outputDevices.clear(); 0272 s_outputDevicePriorities.clear(); 0273 index = s_deviceIndexCounter++; 0274 s_outputDeviceIndexes.insert(QLatin1String("sink:default"), index); 0275 s_outputDevices.insert(index, AudioDevice(QLatin1String("sink:default"), QObject::tr("PulseAudio Sound Server"), QLatin1String("audio-backend-pulseaudio"), 0)); 0276 for (int i = Phonon::NoCategory; i <= Phonon::LastCategory; ++i) { 0277 Phonon::Category cat = static_cast<Phonon::Category>(i); 0278 s_outputDevicePriorities[cat].insert(0, index); 0279 } 0280 0281 s_captureDeviceIndexes.clear(); 0282 s_captureDevices.clear(); 0283 s_captureDevicePriorities.clear(); 0284 index = s_deviceIndexCounter++; 0285 s_captureDeviceIndexes.insert(QLatin1String("source:default"), index); 0286 s_captureDevices.insert(index, AudioDevice(QLatin1String("source:default"), QObject::tr("PulseAudio Sound Server"), QLatin1String("audio-backend-pulseaudio"), 0)); 0287 for (int i = 0; i < s_audioCapCategoriesCount; ++i) { 0288 Phonon::CaptureCategory cat = s_audioCapCategories[i]; 0289 s_captureDevicePriorities[cat].insert(0, index); 0290 } 0291 } 0292 0293 #if HAVE_PULSEAUDIO_DEVICE_MANAGER 0294 static void ext_device_manager_read_cb(pa_context *c, const pa_ext_device_manager_info *info, int eol, void *userdata) { 0295 Q_ASSERT(c); 0296 Q_ASSERT(userdata); 0297 0298 PulseUserData *u = reinterpret_cast<PulseUserData*>(userdata); 0299 0300 if (eol < 0) { 0301 logMessage(QString::fromLatin1("Failed to initialize device manager extension: %1").arg(pa_strerror(pa_context_errno(c)))); 0302 if (s_context != c) { 0303 logMessage(QLatin1String("Falling back to single device mode")); 0304 // Only create our generic devices during the probe phase. 0305 createGenericDevices(); 0306 // As this is our probe phase, exit immediately 0307 pa_context_disconnect(c); 0308 } 0309 delete u; 0310 0311 return; 0312 } 0313 0314 if (eol) { 0315 // We're done reading the data, so order it by priority and copy it into the 0316 // static variables where it can then be accessed by those classes that need it. 0317 0318 QMap<QString, AudioDevice>::iterator newdev_it; 0319 0320 // Check for new output devices or things changing about known output devices. 0321 bool output_changed = false; 0322 for (newdev_it = u->newOutputDevices.begin(); newdev_it != u->newOutputDevices.end(); ++newdev_it) { 0323 QString name = newdev_it.key(); 0324 0325 // The name + index map is always written when a new device is added. 0326 Q_ASSERT(s_outputDeviceIndexes.contains(name)); 0327 0328 int index = s_outputDeviceIndexes[name]; 0329 if (!s_outputDevices.contains(index)) { 0330 // This is a totally new device 0331 output_changed = true; 0332 logMessage(QString("Brand New Output Device Found.")); 0333 s_outputDevices.insert(index, *newdev_it); 0334 } else if (s_outputDevices[index] != *newdev_it) { 0335 // We have this device already, but is it different? 0336 output_changed = true; 0337 logMessage(QString("Change to Existing Output Device (may be Added/Removed or something else)")); 0338 s_outputDevices.remove(index); 0339 s_outputDevices.insert(index, *newdev_it); 0340 } 0341 } 0342 // Go through the output devices we know about and see if any are no longer mentioned in the list. 0343 QMutableMapIterator<QString, int> output_existing_it(s_outputDeviceIndexes); 0344 while (output_existing_it.hasNext()) { 0345 output_existing_it.next(); 0346 if (!u->newOutputDevices.contains(output_existing_it.key())) { 0347 output_changed = true; 0348 logMessage(QString("Output Device Completely Removed")); 0349 s_outputDevices.remove(output_existing_it.value()); 0350 output_existing_it.remove(); 0351 } 0352 } 0353 0354 // Check for new capture devices or things changing about known capture devices. 0355 bool capture_changed = false; 0356 for (newdev_it = u->newCaptureDevices.begin(); newdev_it != u->newCaptureDevices.end(); ++newdev_it) { 0357 QString name = newdev_it.key(); 0358 0359 // The name + index map is always written when a new device is added. 0360 Q_ASSERT(s_captureDeviceIndexes.contains(name)); 0361 0362 int index = s_captureDeviceIndexes[name]; 0363 if (!s_captureDevices.contains(index)) { 0364 // This is a totally new device 0365 capture_changed = true; 0366 logMessage(QString("Brand New Capture Device Found.")); 0367 s_captureDevices.insert(index, *newdev_it); 0368 } else if (s_captureDevices[index] != *newdev_it) { 0369 // We have this device already, but is it different? 0370 capture_changed = true; 0371 logMessage(QString("Change to Existing Capture Device (may be Added/Removed or something else)")); 0372 s_captureDevices.remove(index); 0373 s_captureDevices.insert(index, *newdev_it); 0374 } 0375 } 0376 // Go through the capture devices we know about and see if any are no longer mentioned in the list. 0377 QMutableMapIterator<QString, int> capture_existing_it(s_captureDeviceIndexes); 0378 while (capture_existing_it.hasNext()) { 0379 capture_existing_it.next(); 0380 if (!u->newCaptureDevices.contains(capture_existing_it.key())) { 0381 capture_changed = true; 0382 logMessage(QString("Capture Device Completely Removed")); 0383 s_captureDevices.remove(capture_existing_it.value()); 0384 capture_existing_it.remove(); 0385 } 0386 } 0387 0388 // Just copy across the new priority lists as we know they are valid 0389 if (s_outputDevicePriorities != u->newOutputDevicePriorities) { 0390 output_changed = true; 0391 s_outputDevicePriorities = u->newOutputDevicePriorities; 0392 } 0393 if (s_captureDevicePriorities != u->newCaptureDevicePriorities) { 0394 capture_changed = true; 0395 s_captureDevicePriorities = u->newCaptureDevicePriorities; 0396 } 0397 0398 if (s_instance) { 0399 // This won't be emitted during the connection probe phase 0400 // which is intentional 0401 if (output_changed) 0402 s_instance->emitObjectDescriptionChanged(AudioOutputDeviceType); 0403 if (capture_changed) 0404 s_instance->emitObjectDescriptionChanged(AudioCaptureDeviceType); 0405 } 0406 0407 // We can free the user data as we will not be called again. 0408 delete u; 0409 0410 // Some debug 0411 logMessage(QString("Output Device Priority List:")); 0412 for (int i = Phonon::NoCategory; i <= Phonon::LastCategory; ++i) { 0413 Phonon::Category cat = static_cast<Phonon::Category>(i); 0414 if (s_outputDevicePriorities.contains(cat)) { 0415 logMessage(QString(" Phonon Category %1").arg(cat)); 0416 int count = 0; 0417 foreach (int j, s_outputDevicePriorities[cat]) { 0418 QHash<QByteArray, QVariant> &props = s_outputDevices[j].properties; 0419 logMessage(QString(" %1. %2 (Available: %3)").arg(++count).arg(props["name"].toString()).arg(props["available"].toBool())); 0420 } 0421 } 0422 } 0423 logMessage(QString("Capture Device Priority List:")); 0424 for (int i = 0; i < s_audioCapCategoriesCount; ++i) { 0425 Phonon::CaptureCategory cat = s_audioCapCategories[i]; 0426 if (s_captureDevicePriorities.contains(cat)) { 0427 logMessage(QString(" Phonon Category %1").arg(cat)); 0428 int count = 0; 0429 foreach (int j, s_captureDevicePriorities[cat]) { 0430 QHash<QByteArray, QVariant> &props = s_captureDevices[j].properties; 0431 logMessage(QString(" %1. %2 (Available: %3)").arg(++count).arg(props["name"].toString()).arg(props["available"].toBool())); 0432 } 0433 } 0434 } 0435 0436 // If this is our probe phase, exit now as we're finished reading 0437 // our device info and can exit and reconnect 0438 if (s_context != c) 0439 pa_context_disconnect(c); 0440 0441 return; // eol 0442 } 0443 0444 // If we aren't at eol we expect info to be valid! 0445 Q_ASSERT(info); 0446 Q_ASSERT(info->name); 0447 Q_ASSERT(info->description); 0448 Q_ASSERT(info->icon); 0449 0450 // QString wrapper 0451 QString name(info->name); 0452 int index; 0453 QMap<Phonon::Category, QMap<int, int> > *new_prio_map_cats = nullptr; // prio, device 0454 QMap<Phonon::CaptureCategory, QMap<int, int> > *new_prio_map_capcats = nullptr; // prio, device 0455 QMap<QString, AudioDevice> *new_devices = nullptr; 0456 0457 bool isSink = false; 0458 bool isSource = false; 0459 0460 if (name.startsWith(QLatin1String("sink:"))) { 0461 isSink = true; 0462 0463 new_devices = &u->newOutputDevices; 0464 new_prio_map_cats = &u->newOutputDevicePriorities; 0465 0466 if (s_outputDeviceIndexes.contains(name)) 0467 index = s_outputDeviceIndexes[name]; 0468 else 0469 index = s_outputDeviceIndexes[name] = s_deviceIndexCounter++; 0470 } else if (name.startsWith(QLatin1String("source:"))) { 0471 isSource = true; 0472 0473 new_devices = &u->newCaptureDevices; 0474 new_prio_map_capcats = &u->newCaptureDevicePriorities; 0475 0476 if (s_captureDeviceIndexes.contains(name)) 0477 index = s_captureDeviceIndexes[name]; 0478 else 0479 index = s_captureDeviceIndexes[name] = s_deviceIndexCounter++; 0480 } else { 0481 // This indicates a bug in pulseaudio. 0482 return; 0483 } 0484 0485 Q_ASSERT(new_devices); 0486 Q_ASSERT(!isSink || new_prio_map_cats); 0487 Q_ASSERT(!isSource || new_prio_map_capcats); 0488 0489 // Add the new device itself. 0490 new_devices->insert(name, AudioDevice(name, QString::fromUtf8(info->description), QString::fromUtf8(info->icon), info->index)); 0491 0492 // For each role in the priority, map it to a phonon category and store the order. 0493 for (uint32_t i = 0; i < info->n_role_priorities; ++i) { 0494 pa_ext_device_manager_role_priority_info* role_prio = &info->role_priorities[i]; 0495 Q_ASSERT(role_prio->role); 0496 0497 bool conversionSuccess; 0498 0499 if (isSink) { 0500 Phonon::Category cat = pulseRoleToPhononCategory(role_prio->role, &conversionSuccess); 0501 if (conversionSuccess) { 0502 (*new_prio_map_cats)[cat].insert(role_prio->priority, index); 0503 } 0504 } 0505 0506 if (isSource) { 0507 Phonon::CaptureCategory capcat = pulseRoleToPhononCaptureCategory(role_prio->role, &conversionSuccess); 0508 if (conversionSuccess) { 0509 (*new_prio_map_capcats)[capcat].insert(role_prio->priority, index); 0510 } 0511 } 0512 } 0513 } 0514 0515 static void ext_device_manager_subscribe_cb(pa_context *c, void *) { 0516 Q_ASSERT(c); 0517 0518 pa_operation *o; 0519 PulseUserData *u = new PulseUserData; 0520 if (!(o = pa_ext_device_manager_read(c, ext_device_manager_read_cb, u))) { 0521 logMessage(QString::fromLatin1("pa_ext_device_manager_read() failed.")); 0522 delete u; 0523 return; 0524 } 0525 pa_operation_unref(o); 0526 } 0527 #endif 0528 0529 static void sink_input_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { 0530 Q_UNUSED(userdata); 0531 Q_ASSERT(c); 0532 0533 if (eol < 0) { 0534 if (pa_context_errno(c) == PA_ERR_NOENTITY) 0535 return; 0536 0537 logMessage(QLatin1String("Sink input callback failure")); 0538 return; 0539 } 0540 0541 if (eol > 0) 0542 return; 0543 0544 Q_ASSERT(i); 0545 0546 // loop through (*i) and extract phonon->streamindex... 0547 const char *t; 0548 if ((t = pa_proplist_gets(i->proplist, PA_PROP_PHONON_STREAMID))) { 0549 logMessage(QString::fromLatin1("Found PulseAudio stream index %1 for Phonon Output Stream %2").arg(i->index).arg(QLatin1String(t))); 0550 0551 // We only care about our own streams (other phonon processes are irrelevant) 0552 if (s_outputStreams.contains(QLatin1String(t))) { 0553 PulseStream *stream = s_outputStreams[QString(t)]; 0554 stream->setIndex(i->index); 0555 stream->setVolume(&i->volume); 0556 stream->setMute(!!i->mute); 0557 0558 // Find the sink's phonon index and notify whoever cares... 0559 if (PA_INVALID_INDEX != i->sink) { 0560 QMap<int, AudioDevice>::iterator it; 0561 for (it = s_outputDevices.begin(); it != s_outputDevices.end(); ++it) { 0562 if ((*it).pulseIndex == i->sink) { 0563 stream->setDevice(it.key()); 0564 break; 0565 } 0566 } 0567 } 0568 } 0569 } 0570 } 0571 0572 static void source_output_cb(pa_context *c, const pa_source_output_info *i, int eol, void *userdata) { 0573 Q_UNUSED(userdata); 0574 Q_ASSERT(c); 0575 0576 if (eol < 0) { 0577 if (pa_context_errno(c) == PA_ERR_NOENTITY) 0578 return; 0579 0580 logMessage(QLatin1String("Source output callback failure")); 0581 return; 0582 } 0583 0584 if (eol > 0) 0585 return; 0586 0587 Q_ASSERT(i); 0588 0589 // loop through (*i) and extract phonon->streamindex... 0590 const char *t; 0591 if ((t = pa_proplist_gets(i->proplist, PA_PROP_PHONON_STREAMID))) { 0592 logMessage(QString::fromLatin1("Found PulseAudio stream index %1 for Phonon Capture Stream %2").arg(i->index).arg(QLatin1String(t))); 0593 0594 // We only care about our own streams (other phonon processes are irrelevant) 0595 if (s_captureStreams.contains(QLatin1String(t))) { 0596 PulseStream *stream = s_captureStreams[QString(t)]; 0597 stream->setIndex(i->index); 0598 //stream->setVolume(&i->volume); 0599 //stream->setMute(!!i->mute); 0600 0601 // Find the source's phonon index and notify whoever cares... 0602 if (PA_INVALID_INDEX != i->source) { 0603 QMap<int, AudioDevice>::iterator it; 0604 for (it = s_captureDevices.begin(); it != s_captureDevices.end(); ++it) { 0605 if ((*it).pulseIndex == i->source) { 0606 stream->setDevice(it.key()); 0607 break; 0608 } 0609 } 0610 } 0611 } 0612 } 0613 } 0614 0615 static void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata) { 0616 Q_UNUSED(userdata); 0617 0618 switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { 0619 case PA_SUBSCRIPTION_EVENT_SINK_INPUT: 0620 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { 0621 PulseStream *stream = findStreamByPulseIndex(s_outputStreams, index); 0622 if (stream) { 0623 logMessage(QString::fromLatin1("Phonon Output Stream %1 is gone at the PA end. Marking it as invalid in our cache as we may reuse it.").arg(stream->uuid())); 0624 stream->setIndex(PA_INVALID_INDEX); 0625 } 0626 } else { 0627 pa_operation *o; 0628 if (!(o = pa_context_get_sink_input_info(c, index, sink_input_cb, nullptr))) { 0629 logMessage(QString::fromLatin1("pa_context_get_sink_input_info() failed")); 0630 return; 0631 } 0632 pa_operation_unref(o); 0633 } 0634 break; 0635 0636 case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: 0637 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { 0638 PulseStream *stream = findStreamByPulseIndex(s_captureStreams, index); 0639 if (stream) { 0640 logMessage(QString::fromLatin1("Phonon Capture Stream %1 is gone at the PA end. Marking it as invalid in our cache as we may reuse it.").arg(stream->uuid())); 0641 stream->setIndex(PA_INVALID_INDEX); 0642 } 0643 } else { 0644 pa_operation *o; 0645 if (!(o = pa_context_get_source_output_info(c, index, source_output_cb, nullptr))) { 0646 logMessage(QString::fromLatin1("pa_context_get_sink_input_info() failed")); 0647 return; 0648 } 0649 pa_operation_unref(o); 0650 } 0651 break; 0652 } 0653 } 0654 0655 0656 static QString statename(pa_context_state_t state) 0657 { 0658 switch (state) 0659 { 0660 case PA_CONTEXT_UNCONNECTED: return QLatin1String("Unconnected"); 0661 case PA_CONTEXT_CONNECTING: return QLatin1String("Connecting"); 0662 case PA_CONTEXT_AUTHORIZING: return QLatin1String("Authorizing"); 0663 case PA_CONTEXT_SETTING_NAME: return QLatin1String("Setting Name"); 0664 case PA_CONTEXT_READY: return QLatin1String("Ready"); 0665 case PA_CONTEXT_FAILED: return QLatin1String("Failed"); 0666 case PA_CONTEXT_TERMINATED: return QLatin1String("Terminated"); 0667 } 0668 0669 return QString::fromLatin1("Unknown state: %0").arg(state); 0670 } 0671 0672 static void context_state_callback(pa_context *c, void *) 0673 { 0674 Q_ASSERT(c); 0675 0676 logMessage(QString::fromLatin1("context_state_callback %1").arg(statename(pa_context_get_state(c)))); 0677 pa_context_state_t state = pa_context_get_state(c); 0678 if (state == PA_CONTEXT_READY) { 0679 // We've connected to PA, so it is active 0680 s_pulseActive = true; 0681 0682 // Attempt to load things up 0683 pa_operation *o; 0684 0685 // 1. Register for the stream changes (except during probe) 0686 if (s_context == c) { 0687 pa_context_set_subscribe_callback(c, subscribe_cb, nullptr); 0688 0689 if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t) 0690 (PA_SUBSCRIPTION_MASK_SINK_INPUT| 0691 PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT), nullptr, nullptr))) { 0692 logMessage(QLatin1String("pa_context_subscribe() failed")); 0693 return; 0694 } 0695 pa_operation_unref(o); 0696 0697 // In the case of reconnection or simply lagging behind the stream object creation 0698 // on startup (due to the probe+reconnect system), we invalidate all loaded streams 0699 // and then load up info about all streams. 0700 for (QMap<QString, PulseStream*>::iterator it = s_outputStreams.begin(); it != s_outputStreams.end(); ++it) { 0701 PulseStream *stream = *it; 0702 logMessage(QString::fromLatin1("Phonon Output Stream %1 is gone at the PA end. Marking it as invalid in our cache as we may reuse it.").arg(stream->uuid())); 0703 stream->setIndex(PA_INVALID_INDEX); 0704 } 0705 if (!(o = pa_context_get_sink_input_info_list(c, sink_input_cb, nullptr))) { 0706 logMessage(QString::fromLatin1("pa_context_get_sink_input_info_list() failed")); 0707 return; 0708 } 0709 pa_operation_unref(o); 0710 0711 for (QMap<QString, PulseStream*>::iterator it = s_captureStreams.begin(); it != s_captureStreams.end(); ++it) { 0712 PulseStream *stream = *it; 0713 logMessage(QString::fromLatin1("Phonon Capture Stream %1 is gone at the PA end. Marking it as invalid in our cache as we may reuse it.").arg(stream->uuid())); 0714 stream->setIndex(PA_INVALID_INDEX); 0715 } 0716 if (!(o = pa_context_get_source_output_info_list(c, source_output_cb, nullptr))) { 0717 logMessage(QString::fromLatin1("pa_context_get_source_output_info_list() failed")); 0718 return; 0719 } 0720 pa_operation_unref(o); 0721 } 0722 0723 #if HAVE_PULSEAUDIO_DEVICE_MANAGER 0724 // 2a. Attempt to initialise Device Manager info (except during probe) 0725 if (s_context == c) { 0726 pa_ext_device_manager_set_subscribe_cb(c, ext_device_manager_subscribe_cb, nullptr); 0727 if (!(o = pa_ext_device_manager_subscribe(c, 1, nullptr, nullptr))) { 0728 logMessage(QString::fromLatin1("pa_ext_device_manager_subscribe() failed")); 0729 return; 0730 } 0731 pa_operation_unref(o); 0732 } 0733 0734 // 3. Attempt to read info from Device Manager 0735 PulseUserData *u = new PulseUserData; 0736 if (!(o = pa_ext_device_manager_read(c, ext_device_manager_read_cb, u))) { 0737 if (s_context != c) { 0738 logMessage(QString::fromLatin1("pa_ext_device_manager_read() failed. Attempting to continue without device manager support")); 0739 // Only create our generic devices during the probe phase. 0740 createGenericDevices(); 0741 // As this is our probe phase, exit immediately 0742 pa_context_disconnect(c); 0743 } 0744 delete u; 0745 0746 return; 0747 } 0748 pa_operation_unref(o); 0749 0750 #else 0751 // If we know do not have Device Manager support, we just create our dummy devices now 0752 if (s_context != c) { 0753 // Only create our generic devices during the probe phase. 0754 createGenericDevices(); 0755 // As this is our probe phase, exit immediately 0756 pa_context_disconnect(c); 0757 } 0758 0759 #endif 0760 } else if (!PA_CONTEXT_IS_GOOD(state)) { 0761 /// @todo Deal with reconnection... 0762 //logMessage(QString("Connection to PulseAudio lost: %1").arg(pa_strerror(pa_context_errno(c)))); 0763 0764 // If this is our probe phase, exit our context immediately 0765 if (s_context != c) 0766 pa_context_disconnect(c); 0767 else { 0768 pa_context_unref(s_context); 0769 s_context = nullptr; 0770 QTimer::singleShot(50, PulseSupport::getInstance(), SLOT(connectToDaemon())); 0771 } 0772 } 0773 } 0774 #endif // HAVE_PULSEAUDIO 0775 0776 PulseSupport *PulseSupport::getInstanceOrNull(bool allowNull) 0777 { 0778 if (s_wasShutDown && allowNull) { 0779 return nullptr; 0780 } 0781 0782 if (nullptr == s_instance) { 0783 /* 0784 * In order to prevent the instance being used from multiple threads 0785 * prior to it being constructed fully, we need to ensure we obtain a 0786 * lock prior to creating it. After we acquire the lock, check to see 0787 * if the object is created again before proceeding. 0788 */ 0789 probeMutex.lock(); 0790 if (nullptr == s_instance) 0791 s_instance = new PulseSupport(); 0792 probeMutex.unlock(); 0793 } 0794 return s_instance; 0795 } 0796 0797 PulseSupport *PulseSupport::getInstance() 0798 { 0799 return getInstanceOrNull(false); 0800 } 0801 0802 void PulseSupport::shutdown() 0803 { 0804 if (nullptr != s_instance) { 0805 delete s_instance; 0806 s_instance = nullptr; 0807 s_wasShutDown = true; 0808 } 0809 } 0810 0811 void PulseSupport::debug() 0812 { 0813 #ifdef HAVE_PULSEAUDIO 0814 logMessage(QString::fromLatin1("Have we been initialised yet? %1").arg(s_instance ? "Yes" : "No")); 0815 if (s_instance) { 0816 logMessage(QString::fromLatin1("Connected to PulseAudio? %1").arg(s_pulseActive ? "Yes" : "No")); 0817 logMessage(QString::fromLatin1("PulseAudio support 'Active'? %1").arg(s_instance->isActive() ? "Yes" : "No")); 0818 } 0819 #endif 0820 } 0821 0822 PulseSupport::PulseSupport() 0823 : QObject() 0824 , mEnabled(false) 0825 , m_requested(false) 0826 { 0827 #ifdef HAVE_PULSEAUDIO 0828 0829 // To allow for easy debugging, give an easy way to disable this pulseaudio check 0830 QByteArray pulseenv = qgetenv("PHONON_PULSEAUDIO_DISABLE"); 0831 if (pulseenv.toInt()) { 0832 logMessage(QLatin1String("PulseAudio support disabled: PHONON_PULSEAUDIO_DISABLE is set")); 0833 return; 0834 } 0835 0836 if (!QAbstractEventDispatcher::instance() || !QAbstractEventDispatcher::instance()->metaObject()) { 0837 qWarning("WARNING: Cannot construct PulseSupport because there is no Eventloop." 0838 " May be because of application shutdown."); 0839 return; 0840 } 0841 0842 // We require a glib event loop 0843 if (!QByteArray(QAbstractEventDispatcher::instance()->metaObject()->className()).contains("EventDispatcherGlib") && 0844 !QByteArray(QAbstractEventDispatcher::instance()->metaObject()->className()).contains("GlibEventDispatcher")) { 0845 qWarning("WARNING: Disabling PulseAudio integration for lack of GLib event loop."); 0846 return; 0847 } 0848 0849 // First of all connect to PA via simple/blocking means and if that succeeds, 0850 // use a fully async integrated mainloop method to connect and get proper support. 0851 pa_mainloop *p_test_mainloop; 0852 if (!(p_test_mainloop = pa_mainloop_new())) { 0853 logMessage(QLatin1String("PulseAudio support disabled: Unable to create mainloop")); 0854 return; 0855 } 0856 0857 pa_context *p_test_context; 0858 if (!(p_test_context = pa_context_new(pa_mainloop_get_api(p_test_mainloop), "libphonon-probe"))) { 0859 logMessage(QLatin1String("PulseAudio support disabled: Unable to create context")); 0860 pa_mainloop_free(p_test_mainloop); 0861 return; 0862 } 0863 0864 logMessage(QLatin1String("Probing for PulseAudio...")); 0865 // (cg) Convert to PA_CONTEXT_NOFLAGS when PulseAudio 0.9.19 is required 0866 if (pa_context_connect(p_test_context, nullptr, static_cast<pa_context_flags_t>(0), nullptr) < 0) { 0867 logMessage(QString::fromLatin1("PulseAudio support disabled: %1").arg(QString::fromLocal8Bit(pa_strerror(pa_context_errno(p_test_context))))); 0868 pa_context_disconnect(p_test_context); 0869 pa_context_unref(p_test_context); 0870 pa_mainloop_free(p_test_mainloop); 0871 return; 0872 } 0873 0874 pa_context_set_state_callback(p_test_context, &context_state_callback, nullptr); 0875 for (;;) { 0876 pa_mainloop_iterate(p_test_mainloop, 1, nullptr); 0877 0878 if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(p_test_context))) { 0879 logMessage(QLatin1String("PulseAudio probe complete.")); 0880 break; 0881 } 0882 } 0883 pa_context_disconnect(p_test_context); 0884 pa_context_unref(p_test_context); 0885 pa_mainloop_free(p_test_mainloop); 0886 0887 if (!s_pulseActive) { 0888 logMessage(QLatin1String("PulseAudio support is not available.")); 0889 return; 0890 } 0891 0892 // If we're still here, PA is available. 0893 logMessage(QLatin1String("PulseAudio support enabled")); 0894 0895 // Now we connect for real using a proper main loop that we can forget 0896 // all about processing. 0897 s_mainloop = pa_glib_mainloop_new(nullptr); 0898 Q_ASSERT(s_mainloop); 0899 0900 connectToDaemon(); 0901 #endif 0902 } 0903 0904 PulseSupport::~PulseSupport() 0905 { 0906 #ifdef HAVE_PULSEAUDIO 0907 if (s_context) { 0908 pa_context_disconnect(s_context); 0909 s_context = nullptr; 0910 } 0911 0912 if (s_mainloop) { 0913 pa_glib_mainloop_free(s_mainloop); 0914 s_mainloop = nullptr; 0915 } 0916 #endif 0917 } 0918 0919 0920 void PulseSupport::connectToDaemon() 0921 { 0922 #ifdef HAVE_PULSEAUDIO 0923 pa_mainloop_api *api = pa_glib_mainloop_get_api(s_mainloop); 0924 0925 s_context = pa_context_new(api, "libphonon"); 0926 if (pa_context_connect(s_context, nullptr, PA_CONTEXT_NOFAIL, nullptr) >= 0) 0927 pa_context_set_state_callback(s_context, &context_state_callback, nullptr); 0928 #endif 0929 } 0930 0931 bool PulseSupport::isActive() 0932 { 0933 #ifdef HAVE_PULSEAUDIO 0934 return mEnabled && isUsed(); 0935 #else 0936 return false; 0937 #endif 0938 } 0939 0940 bool PulseSupport::isUsed() 0941 { 0942 return isRequested() && isUsable(); 0943 } 0944 0945 bool PulseSupport::isUsable() const 0946 { 0947 return s_pulseActive; 0948 } 0949 0950 bool PulseSupport::isRequested() const 0951 { 0952 return m_requested; 0953 } 0954 0955 void PulseSupport::request(bool requested) 0956 { 0957 m_requested = requested; 0958 } 0959 0960 void PulseSupport::enable(bool enabled) 0961 { 0962 mEnabled = enabled; 0963 request(enabled); // compat, enable needs to imply request. 0964 #ifdef HAVE_PULSEAUDIO 0965 logMessage(QString::fromLocal8Bit("Enabled Breakdown: mEnabled: %1, s_pulseActive %2").arg(mEnabled ? "Yes" : "No" ).arg(s_pulseActive ? "Yes" : "No")); 0966 #endif 0967 } 0968 0969 QList<int> PulseSupport::objectDescriptionIndexes(ObjectDescriptionType type) const 0970 { 0971 QList<int> list; 0972 0973 if (type != AudioOutputDeviceType && type != AudioCaptureDeviceType) 0974 return list; 0975 0976 #ifdef HAVE_PULSEAUDIO 0977 if (s_pulseActive) { 0978 switch (type) { 0979 0980 case AudioOutputDeviceType: { 0981 QMap<QString, int>::iterator it; 0982 for (it = s_outputDeviceIndexes.begin(); it != s_outputDeviceIndexes.end(); ++it) { 0983 list.append(*it); 0984 } 0985 break; 0986 } 0987 case AudioCaptureDeviceType: { 0988 QMap<QString, int>::iterator it; 0989 for (it = s_captureDeviceIndexes.begin(); it != s_captureDeviceIndexes.end(); ++it) { 0990 list.append(*it); 0991 } 0992 break; 0993 } 0994 default: 0995 break; 0996 } 0997 } 0998 #endif 0999 1000 return list; 1001 } 1002 1003 QHash<QByteArray, QVariant> PulseSupport::objectDescriptionProperties(ObjectDescriptionType type, int index) const 1004 { 1005 QHash<QByteArray, QVariant> ret; 1006 1007 if (type != AudioOutputDeviceType && type != AudioCaptureDeviceType) 1008 return ret; 1009 1010 #ifndef HAVE_PULSEAUDIO 1011 Q_UNUSED(index); 1012 #else 1013 if (s_pulseActive) { 1014 switch (type) { 1015 1016 case AudioOutputDeviceType: 1017 Q_ASSERT(s_outputDevices.contains(index)); 1018 ret = s_outputDevices[index].properties; 1019 break; 1020 1021 case AudioCaptureDeviceType: 1022 Q_ASSERT(s_captureDevices.contains(index)); 1023 ret = s_captureDevices[index].properties; 1024 break; 1025 1026 default: 1027 break; 1028 } 1029 } 1030 #endif 1031 1032 return ret; 1033 } 1034 1035 QList<int> PulseSupport::objectIndexesByCategory(ObjectDescriptionType type, Category category) const 1036 { 1037 QList<int> ret; 1038 1039 if (type != AudioOutputDeviceType) 1040 return ret; 1041 1042 #ifndef HAVE_PULSEAUDIO 1043 Q_UNUSED(category); 1044 #else 1045 if (s_pulseActive) { 1046 if (s_outputDevicePriorities.contains(category)) 1047 ret = s_outputDevicePriorities[category].values(); 1048 } 1049 #endif 1050 1051 return ret; 1052 } 1053 1054 QList<int> PulseSupport::objectIndexesByCategory(ObjectDescriptionType type, CaptureCategory category) const 1055 { 1056 QList<int> ret; 1057 1058 if (type != AudioCaptureDeviceType) 1059 return ret; 1060 1061 #ifndef HAVE_PULSEAUDIO 1062 Q_UNUSED(category); 1063 #else 1064 if (s_pulseActive) { 1065 if (s_captureDevicePriorities.contains(category)) 1066 ret = s_captureDevicePriorities[category].values(); 1067 } 1068 #endif 1069 1070 return ret; 1071 } 1072 1073 #ifdef HAVE_PULSEAUDIO 1074 static void setDevicePriority(QString role, QStringList list) 1075 { 1076 logMessage(QString::fromLatin1("Reindexing %1: %2").arg(role).arg(list.join(QLatin1String(", ")))); 1077 1078 char **devices; 1079 devices = pa_xnew(char *, list.size()+1); 1080 int i = 0; 1081 foreach (const QString &str, list) { 1082 devices[i++] = pa_xstrdup(str.toUtf8().constData()); 1083 } 1084 devices[list.size()] = nullptr; 1085 1086 #if HAVE_PULSEAUDIO_DEVICE_MANAGER 1087 pa_operation *o; 1088 if (!(o = pa_ext_device_manager_reorder_devices_for_role(s_context, role.toUtf8().constData(), (const char**)devices, nullptr, nullptr))) 1089 logMessage(QString::fromLatin1("pa_ext_device_manager_reorder_devices_for_role() failed")); 1090 else 1091 pa_operation_unref(o); 1092 #endif 1093 1094 for (i = 0; i < list.size(); ++i) 1095 pa_xfree(devices[i]); 1096 pa_xfree(devices); 1097 } 1098 1099 static void setDevicePriority(Category category, QStringList list) 1100 { 1101 QString role = phononCategoryToPulseRole(category); 1102 if (role.isEmpty()) 1103 return; 1104 1105 setDevicePriority(role, list); 1106 } 1107 1108 static void setDevicePriority(CaptureCategory category, QStringList list) 1109 { 1110 QString role = phononCaptureCategoryToPulseRole(category); 1111 if (role.isEmpty()) 1112 return; 1113 1114 setDevicePriority(role, list); 1115 } 1116 #endif // HAVE_PULSEAUDIO 1117 1118 void PulseSupport::setOutputDevicePriorityForCategory(Category category, QList<int> order) 1119 { 1120 #ifndef HAVE_PULSEAUDIO 1121 Q_UNUSED(category); 1122 Q_UNUSED(order); 1123 #else 1124 QStringList list; 1125 QList<int>::iterator it; 1126 1127 for (it = order.begin(); it != order.end(); ++it) { 1128 if (s_outputDevices.contains(*it)) { 1129 list << s_outputDeviceIndexes.key(*it); 1130 } 1131 } 1132 setDevicePriority(category, list); 1133 #endif 1134 } 1135 1136 void PulseSupport::setCaptureDevicePriorityForCategory(CaptureCategory category, QList<int> order) 1137 { 1138 #ifndef HAVE_PULSEAUDIO 1139 Q_UNUSED(category); 1140 Q_UNUSED(order); 1141 #else 1142 QStringList list; 1143 QList<int>::iterator it; 1144 1145 for (it = order.begin(); it != order.end(); ++it) { 1146 if (s_captureDevices.contains(*it)) { 1147 list << s_captureDeviceIndexes.key(*it); 1148 } 1149 } 1150 setDevicePriority(category, list); 1151 #endif 1152 } 1153 1154 void PulseSupport::setCaptureDevicePriorityForCategory(Category category, QList<int> order) 1155 { 1156 CaptureCategory cat = categoryToCaptureCategory(category); 1157 setCaptureDevicePriorityForCategory(cat, order); 1158 } 1159 1160 #ifdef HAVE_PULSEAUDIO 1161 static PulseStream* register_stream(QMap<QString,PulseStream*> &map, QString streamUuid, QString role) 1162 { 1163 logMessage(QString::fromLatin1("Initialising streamindex %1").arg(streamUuid)); 1164 1165 PulseStream *stream = new PulseStream(streamUuid, role); 1166 map[streamUuid] = stream; 1167 1168 // Setup environment... 1169 // These values are considered static, so we force property overrides for them. 1170 if (!Platform::applicationName().isEmpty()) 1171 qputenv(QString("PULSE_PROP_OVERRIDE_%1").arg(PA_PROP_APPLICATION_NAME).toUtf8(), 1172 Platform::applicationName().toUtf8()); 1173 if (!qApp->applicationVersion().isEmpty()) 1174 qputenv(QString("PULSE_PROP_OVERRIDE_%1").arg(PA_PROP_APPLICATION_VERSION).toUtf8(), 1175 qApp->applicationVersion().toUtf8()); 1176 if (!qApp->applicationName().isEmpty()) { 1177 QString icon; 1178 if (!qApp->windowIcon().isNull()){ 1179 // Try to get the fromTheme() name of the QIcon. 1180 icon = qApp->windowIcon().name(); 1181 } 1182 if (icon.isEmpty()) { 1183 // If we failed to get a proper icon name, use the appname instead. 1184 icon = qApp->applicationName().toLower(); 1185 } 1186 qputenv(QString("PULSE_PROP_OVERRIDE_%1").arg(PA_PROP_APPLICATION_ICON_NAME).toUtf8(), 1187 icon.toUtf8()); 1188 } 1189 1190 return stream; 1191 } 1192 1193 static PulseStream* register_stream(QMap<QString,PulseStream*> &map, QString streamUuid, Category category) 1194 { 1195 QString role = phononCategoryToPulseRole(category); 1196 return register_stream(map, streamUuid, role); 1197 } 1198 1199 static PulseStream* register_stream(QMap<QString,PulseStream*> &map, QString streamUuid, CaptureCategory category) 1200 { 1201 QString role = phononCaptureCategoryToPulseRole(category); 1202 return register_stream(map, streamUuid, role); 1203 } 1204 1205 #endif 1206 1207 PulseStream *PulseSupport::registerOutputStream(QString streamUuid, Category category) 1208 { 1209 #ifndef HAVE_PULSEAUDIO 1210 Q_UNUSED(streamUuid); 1211 Q_UNUSED(category); 1212 return NULL; 1213 #else 1214 return register_stream(s_outputStreams, streamUuid, category); 1215 #endif 1216 } 1217 1218 PulseStream *PulseSupport::registerCaptureStream(QString streamUuid, CaptureCategory category) 1219 { 1220 #ifndef HAVE_PULSEAUDIO 1221 Q_UNUSED(streamUuid); 1222 Q_UNUSED(category); 1223 return NULL; 1224 #else 1225 return register_stream(s_captureStreams, streamUuid, category); 1226 #endif 1227 } 1228 1229 PulseStream *PulseSupport::registerCaptureStream(QString streamUuid, Category category) 1230 { 1231 #ifndef HAVE_PULSEAUDIO 1232 Q_UNUSED(streamUuid); 1233 Q_UNUSED(category); 1234 return NULL; 1235 #else 1236 return register_stream(s_captureStreams, streamUuid, category); 1237 #endif 1238 } 1239 1240 QHash<QString, QString> PulseSupport::streamProperties(QString streamUuid) const 1241 { 1242 QHash<QString, QString> properties; 1243 1244 #ifdef HAVE_PULSEAUDIO 1245 PulseStream *stream = nullptr; 1246 1247 // Try to find the stream among the known output streams. 1248 if (!stream) 1249 stream = s_outputStreams.value(streamUuid); 1250 1251 // Not an output stream, try capture streams. 1252 if (!stream) 1253 stream = s_captureStreams.value(streamUuid); 1254 1255 // Also no capture stream, start crying and return an empty hash. 1256 if (!stream) { 1257 qWarning() << Q_FUNC_INFO << "Requested UUID Could not be found. Returning with empty properties."; 1258 return properties; 1259 } 1260 1261 properties[QLatin1String(PA_PROP_PHONON_STREAMID)] = stream->uuid(); 1262 properties[QLatin1String(PA_PROP_MEDIA_ROLE)] = stream->role(); 1263 1264 // Tear down environment before returning. This is to prevent backends from 1265 // being overridden by the environment if present. 1266 QHashIterator<QString, QString> it(properties); 1267 while (it.hasNext()) { 1268 it.next(); 1269 unsetenv(QString("PULSE_PROP_OVERRIDE_%1").arg(it.key()).toUtf8()); 1270 } 1271 #endif // HAVE_PULSEAUDIO 1272 1273 return properties; 1274 } 1275 1276 void PulseSupport::setupStreamEnvironment(QString streamUuid) 1277 { 1278 pDebug() << "Please note that your current Phonon backend is trying to force" 1279 " stream dependent PulseAudio properties through environment variables." 1280 " Slightly imprecise timing in doing so will cause the first" 1281 " of two subsequently started AudioOutputs to have disfunct volume" 1282 " control. Also see https://bugs.kde.org/show_bug.cgi?id=321288"; 1283 1284 const QHash<QString, QString> properties = streamProperties(streamUuid); 1285 1286 QHashIterator<QString, QString> it(properties); 1287 while (it.hasNext()) { 1288 it.next(); 1289 pDebug() << "PULSE_PROP_OVERRIDE_" << it.key() << " = " << it.value(); 1290 qputenv(QString("PULSE_PROP_OVERRIDE_%1").arg(it.key()).toUtf8(), it.value().toUtf8()); 1291 } 1292 } 1293 1294 void PulseSupport::emitObjectDescriptionChanged(ObjectDescriptionType type) 1295 { 1296 if (isUsed()) 1297 emit objectDescriptionChanged(type); 1298 } 1299 1300 bool PulseSupport::setOutputName(QString streamUuid, QString name) { 1301 #ifndef HAVE_PULSEAUDIO 1302 Q_UNUSED(streamUuid); 1303 Q_UNUSED(name); 1304 return false; 1305 #else 1306 logMessage(QString::fromLatin1("Unimplemented: Need to find a way to set either application.name or media.name in SI proplist")); 1307 Q_UNUSED(streamUuid); 1308 Q_UNUSED(name); 1309 return true; 1310 #endif 1311 } 1312 1313 bool PulseSupport::setOutputDevice(QString streamUuid, int device) { 1314 #ifndef HAVE_PULSEAUDIO 1315 Q_UNUSED(streamUuid); 1316 Q_UNUSED(device); 1317 return false; 1318 #else 1319 if (s_outputDevices.size() < 2) 1320 return true; 1321 1322 if (!s_outputDevices.contains(device)) { 1323 logMessage(QString::fromLatin1("Attempting to set Output Device for invalid device id %1.").arg(device)); 1324 return false; 1325 } 1326 const QVariant var = s_outputDevices[device].properties["name"]; 1327 logMessage(QString::fromLatin1("Attempting to set Output Device to '%1' for Output Stream %2").arg(var.toString()).arg(streamUuid)); 1328 1329 // Attempt to look up the pulse stream index. 1330 if (s_outputStreams.contains(streamUuid) && s_outputStreams[streamUuid]->index() != PA_INVALID_INDEX) { 1331 logMessage(QString::fromLatin1("... Found in map. Moving now")); 1332 1333 uint32_t pulse_device_index = s_outputDevices[device].pulseIndex; 1334 uint32_t pulse_stream_index = s_outputStreams[streamUuid]->index(); 1335 1336 logMessage(QString::fromLatin1("Moving Pulse Sink Input %1 to '%2' (Pulse Sink %3)").arg(pulse_stream_index).arg(var.toString()).arg(pulse_device_index)); 1337 1338 /// @todo Find a way to move the stream without saving it... We don't want to pollute the stream restore db. 1339 pa_operation* o; 1340 if (!(o = pa_context_move_sink_input_by_index(s_context, pulse_stream_index, pulse_device_index, nullptr, nullptr))) { 1341 logMessage(QString::fromLatin1("pa_context_move_sink_input_by_index() failed")); 1342 return false; 1343 } 1344 pa_operation_unref(o); 1345 } else { 1346 logMessage(QString::fromLatin1("... Not found in map. We will be notified of the device when the stream appears and we can process any moves needed then")); 1347 } 1348 return true; 1349 #endif 1350 } 1351 1352 bool PulseSupport::setOutputVolume(QString streamUuid, qreal volume) { 1353 #ifndef HAVE_PULSEAUDIO 1354 Q_UNUSED(streamUuid); 1355 Q_UNUSED(volume); 1356 return false; 1357 #else 1358 logMessage(QString::fromLatin1("Attempting to set volume to %1 for Output Stream %2").arg(volume).arg(streamUuid)); 1359 1360 // Attempt to look up the pulse stream index. 1361 if (s_outputStreams.contains(streamUuid) && s_outputStreams[streamUuid]->index() != PA_INVALID_INDEX) { 1362 PulseStream *stream = s_outputStreams[streamUuid]; 1363 1364 uint8_t channels = stream->channels(); 1365 if (channels < 1) { 1366 logMessage(QString::fromLatin1("Channel count is less than 1. Cannot set volume.")); 1367 return false; 1368 } 1369 1370 pa_cvolume vol; 1371 pa_cvolume_set(&vol, channels, (volume * PA_VOLUME_NORM)); 1372 1373 logMessage(QString::fromLatin1("Found PA index %1. Calling pa_context_set_sink_input_volume()").arg(stream->index())); 1374 pa_operation* o; 1375 if (!(o = pa_context_set_sink_input_volume(s_context, stream->index(), &vol, nullptr, nullptr))) { 1376 logMessage(QString::fromLatin1("pa_context_set_sink_input_volume() failed")); 1377 return false; 1378 } 1379 pa_operation_unref(o); 1380 } else if (s_outputStreams.contains(streamUuid) && s_outputStreams[streamUuid]->index() == PA_INVALID_INDEX) { 1381 logMessage(QString::fromLatin1("Setting volume on an invalid stream ..... this better be intended")); 1382 PulseStream *stream = s_outputStreams[streamUuid]; 1383 stream->setCachedVolume(volume); 1384 } 1385 return true; 1386 #endif 1387 } 1388 1389 bool PulseSupport::setOutputMute(QString streamUuid, bool mute) { 1390 #ifndef HAVE_PULSEAUDIO 1391 Q_UNUSED(streamUuid); 1392 Q_UNUSED(mute); 1393 return false; 1394 #else 1395 logMessage(QString::fromLatin1("Attempting to %1 mute for Output Stream %2").arg(mute ? "set" : "unset").arg(streamUuid)); 1396 1397 // Attempt to look up the pulse stream index. 1398 if (s_outputStreams.contains(streamUuid) && s_outputStreams[streamUuid]->index() != PA_INVALID_INDEX) { 1399 PulseStream *stream = s_outputStreams[streamUuid]; 1400 1401 logMessage(QString::fromLatin1("Found PA index %1. Calling pa_context_set_sink_input_mute()").arg(stream->index())); 1402 pa_operation* o; 1403 if (!(o = pa_context_set_sink_input_mute(s_context, stream->index(), (mute ? 1 : 0), nullptr, nullptr))) { 1404 logMessage(QString::fromLatin1("pa_context_set_sink_input_mute() failed")); 1405 return false; 1406 } 1407 pa_operation_unref(o); 1408 } 1409 return true; 1410 #endif 1411 } 1412 1413 bool PulseSupport::setCaptureDevice(QString streamUuid, int device) { 1414 #ifndef HAVE_PULSEAUDIO 1415 Q_UNUSED(streamUuid); 1416 Q_UNUSED(device); 1417 return false; 1418 #else 1419 if (s_captureDevices.size() < 2) 1420 return true; 1421 1422 if (!s_captureDevices.contains(device)) { 1423 logMessage(QString::fromLatin1("Attempting to set Capture Device for invalid device id %1.").arg(device)); 1424 return false; 1425 } 1426 const QVariant var = s_captureDevices[device].properties["name"]; 1427 logMessage(QString::fromLatin1("Attempting to set Capture Device to '%1' for Capture Stream %2").arg(var.toString()).arg(streamUuid)); 1428 1429 // Attempt to look up the pulse stream index. 1430 if (s_captureStreams.contains(streamUuid) && s_captureStreams[streamUuid]->index() == PA_INVALID_INDEX) { 1431 logMessage(QString::fromLatin1("... Found in map. Moving now")); 1432 1433 uint32_t pulse_device_index = s_captureDevices[device].pulseIndex; 1434 uint32_t pulse_stream_index = s_captureStreams[streamUuid]->index(); 1435 1436 logMessage(QString::fromLatin1("Moving Pulse Source Output %1 to '%2' (Pulse Sink %3)").arg(pulse_stream_index).arg(var.toString()).arg(pulse_device_index)); 1437 1438 /// @todo Find a way to move the stream without saving it... We don't want to pollute the stream restore db. 1439 pa_operation* o; 1440 if (!(o = pa_context_move_source_output_by_index(s_context, pulse_stream_index, pulse_device_index, nullptr, nullptr))) { 1441 logMessage(QString::fromLatin1("pa_context_move_source_output_by_index() failed")); 1442 return false; 1443 } 1444 pa_operation_unref(o); 1445 } else { 1446 logMessage(QString::fromLatin1("... Not found in map. We will be notified of the device when the stream appears and we can process any moves needed then")); 1447 } 1448 return true; 1449 #endif 1450 } 1451 1452 void PulseSupport::clearStreamCache(QString streamUuid) { 1453 #ifndef HAVE_PULSEAUDIO 1454 Q_UNUSED(streamUuid); 1455 return; 1456 #else 1457 logMessage(QString::fromLatin1("Clearing stream cache for stream %1").arg(streamUuid)); 1458 if (s_outputStreams.contains(streamUuid)) { 1459 PulseStream *stream = s_outputStreams[streamUuid]; 1460 s_outputStreams.remove(streamUuid); 1461 delete stream; 1462 } else if (s_captureStreams.contains(streamUuid)) { 1463 PulseStream *stream = s_captureStreams[streamUuid]; 1464 s_captureStreams.remove(streamUuid); 1465 delete stream; 1466 } 1467 #endif 1468 } 1469 1470 } // namespace Phonon 1471 1472 #include "moc_pulsesupport.cpp"