23
#include <QtCore/QAbstractEventDispatcher>
24
26
#include "mixer_pulse.h"
27
static pa_context *context = NULL;
28
static pa_glib_mainloop *mainloop = NULL;
29
#include <pulse/glib-mainloop.h>
30
#include <pulse/ext-stream-restore.h>
33
#define KMIXPA_PLAYBACK 0
34
#define KMIXPA_CAPTURE 1
35
#define KMIXPA_APP_PLAYBACK 2
36
#define KMIXPA_APP_CAPTURE 3
37
#define KMIXPA_WIDGET_MAX KMIXPA_APP_CAPTURE
39
static unsigned int refcount = 0;
40
static pa_glib_mainloop *s_mainloop = NULL;
41
static pa_context *s_context = NULL;
42
static enum { UNKNOWN, ACTIVE, INACTIVE } s_pulseActive = UNKNOWN;
43
static int s_outstandingRequests = 0;
45
QMap<int,Mixer_PULSE*> s_mixers;
47
typedef QMap<int,devinfo> devmap;
48
static devmap outputDevices;
49
static devmap captureDevices;
50
static QMap<int,QString> clients;
51
static devmap outputStreams;
52
static devmap captureStreams;
53
static devmap outputRoles;
56
pa_channel_map channel_map;
61
static QMap<QString,restoreRule> s_RestoreRules;
63
static void dec_outstanding(pa_context *c) {
64
if (s_outstandingRequests <= 0)
67
if (--s_outstandingRequests == 0)
69
s_pulseActive = ACTIVE;
71
// If this is our probe phase, exit our context immediately
73
pa_context_disconnect(c);
75
kDebug(67100) << "Reconnected to PulseAudio";
79
static void translateMasksAndMaps(devinfo& dev)
81
dev.chanMask = Volume::MNONE;
84
if (dev.channel_map.channels != dev.volume.channels) {
85
kError() << "Hiddeous Channel mixup map says " << dev.channel_map.channels << ", volume says: " << dev.volume.channels;
88
if (1 == dev.channel_map.channels && PA_CHANNEL_POSITION_MONO == dev.channel_map.map[0]) {
89
// We just use the left channel to represent this.
90
dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MLEFT);
91
dev.chanIDs[0] = Volume::LEFT;
93
for (uint8_t i = 0; i < dev.channel_map.channels; ++i) {
94
switch (dev.channel_map.map[i]) {
95
case PA_CHANNEL_POSITION_MONO:
96
kWarning(67100) << "Channel Map contains a MONO element but has >1 channel - we can't handle this.";
99
case PA_CHANNEL_POSITION_FRONT_LEFT:
100
dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MLEFT);
101
dev.chanIDs[i] = Volume::LEFT;
103
case PA_CHANNEL_POSITION_FRONT_RIGHT:
104
dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MRIGHT);
105
dev.chanIDs[i] = Volume::RIGHT;
107
case PA_CHANNEL_POSITION_FRONT_CENTER:
108
dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MCENTER);
109
dev.chanIDs[i] = Volume::CENTER;
111
case PA_CHANNEL_POSITION_REAR_CENTER:
112
dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MREARCENTER);
113
dev.chanIDs[i] = Volume::REARCENTER;
115
case PA_CHANNEL_POSITION_REAR_LEFT:
116
dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MSURROUNDLEFT);
117
dev.chanIDs[i] = Volume::SURROUNDLEFT;
119
case PA_CHANNEL_POSITION_REAR_RIGHT:
120
dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MSURROUNDRIGHT);
121
dev.chanIDs[i] = Volume::SURROUNDRIGHT;
123
case PA_CHANNEL_POSITION_LFE:
124
dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MWOOFER);
125
dev.chanIDs[i] = Volume::WOOFER;
127
case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER:
128
dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MREARSIDELEFT);
129
dev.chanIDs[i] = Volume::REARSIDELEFT;
131
case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER:
132
dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MREARSIDERIGHT);
133
dev.chanIDs[i] = Volume::REARSIDERIGHT;
136
kWarning(67100) << "Channel Map contains a pa_channel_position we cannot handle " << dev.channel_map.map[i];
143
static QString getIconNameFromProplist(pa_proplist *l) {
146
if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ICON_NAME)))
149
if ((t = pa_proplist_gets(l, PA_PROP_WINDOW_ICON_NAME)))
152
if ((t = pa_proplist_gets(l, PA_PROP_APPLICATION_ICON_NAME)))
155
if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ROLE))) {
157
if (strcmp(t, "video") == 0 || strcmp(t, "phone") == 0)
160
if (strcmp(t, "music") == 0)
163
if (strcmp(t, "game") == 0)
164
return "applications-games";
166
if (strcmp(t, "event") == 0)
167
return "dialog-information";
173
static void sink_cb(pa_context *c, const pa_sink_info *i, int eol, void *) {
176
if (pa_context_errno(c) == PA_ERR_NOENTITY)
179
kWarning(67100) << "Sink callback failure";
185
if (s_mixers.contains(KMIXPA_PLAYBACK))
186
s_mixers[KMIXPA_PLAYBACK]->triggerUpdate();
191
s.index = s.device_index = i->index;
192
s.name = QString(i->name).replace(' ', '_');
193
s.description = i->description;
194
s.icon_name = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_ICON_NAME);
195
s.volume = i->volume;
196
s.channel_map = i->channel_map;
198
s.stream_restore_rule = "";
200
translateMasksAndMaps(s);
202
bool is_new = !outputDevices.contains(s.index);
203
outputDevices[s.index] = s;
204
kDebug(67100) << "Got some info about sink: " << s.description;
206
if (s_mixers.contains(KMIXPA_PLAYBACK)) {
208
s_mixers[KMIXPA_PLAYBACK]->addWidget(s.index);
210
int mid = s_mixers[KMIXPA_PLAYBACK]->id2num(s.name);
212
MixSet *ms = s_mixers[KMIXPA_PLAYBACK]->getMixSet();
213
(*ms)[mid]->setReadableName(s.description);
219
static void source_cb(pa_context *c, const pa_source_info *i, int eol, void *) {
222
if (pa_context_errno(c) == PA_ERR_NOENTITY)
225
kWarning(67100) << "Source callback failure";
231
if (s_mixers.contains(KMIXPA_CAPTURE))
232
s_mixers[KMIXPA_CAPTURE]->triggerUpdate();
237
if (PA_INVALID_INDEX != i->monitor_of_sink)
239
kDebug(67100) << "Ignoring Monitor Source: " << i->description;
244
s.index = s.device_index = i->index;
245
s.name = QString(i->name).replace(' ', '_');
246
s.description = i->description;
247
s.icon_name = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_ICON_NAME);
248
s.volume = i->volume;
249
s.channel_map = i->channel_map;
251
s.stream_restore_rule = "";
253
translateMasksAndMaps(s);
255
bool is_new = !captureDevices.contains(s.index);
256
captureDevices[s.index] = s;
257
kDebug(67100) << "Got some info about source: " << s.description;
259
if (s_mixers.contains(KMIXPA_CAPTURE)) {
261
s_mixers[KMIXPA_CAPTURE]->addWidget(s.index);
263
int mid = s_mixers[KMIXPA_CAPTURE]->id2num(s.name);
265
MixSet *ms = s_mixers[KMIXPA_CAPTURE]->getMixSet();
266
(*ms)[mid]->setReadableName(s.description);
272
static void client_cb(pa_context *c, const pa_client_info *i, int eol, void *) {
275
if (pa_context_errno(c) == PA_ERR_NOENTITY)
278
kWarning(67100) << "Client callback failure";
287
clients[i->index] = i->name;
288
kDebug(67100) << "Got some info about client: " << i->name;
291
static void sink_input_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *) {
294
if (pa_context_errno(c) == PA_ERR_NOENTITY)
297
kWarning(67100) << "Sink Input callback failure";
303
if (s_mixers.contains(KMIXPA_APP_PLAYBACK))
304
s_mixers[KMIXPA_APP_PLAYBACK]->triggerUpdate();
309
if ((t = pa_proplist_gets(i->proplist, "module-stream-restore.id"))) {
310
if (strcmp(t, "sink-input-by-media-role:event") == 0) {
311
kWarning(67100) << "Ignoring sink-input due to it being designated as an event and thus handled by the Event slider";
316
QString prefix = QString("%1: ").arg(i18n("Unknown Application"));
317
if (clients.contains(i->client))
318
prefix = QString("%1: ").arg(clients[i->client]);
322
s.device_index = i->sink;
323
s.description = prefix + i->name;
324
s.name = QString("stream:") + i->index;
325
s.icon_name = getIconNameFromProplist(i->proplist);
326
s.volume = i->volume;
327
s.channel_map = i->channel_map;
329
s.stream_restore_rule = t;
331
translateMasksAndMaps(s);
333
bool is_new = !outputStreams.contains(s.index);
334
outputStreams[s.index] = s;
335
kDebug(67100) << "Got some info about sink input (playback stream): " << s.description;
337
if (s_mixers.contains(KMIXPA_APP_PLAYBACK)) {
339
s_mixers[KMIXPA_APP_PLAYBACK]->addWidget(s.index);
341
int mid = s_mixers[KMIXPA_APP_PLAYBACK]->id2num(s.name);
343
MixSet *ms = s_mixers[KMIXPA_APP_PLAYBACK]->getMixSet();
344
(*ms)[mid]->setReadableName(s.description);
350
static void source_output_cb(pa_context *c, const pa_source_output_info *i, int eol, void *) {
353
if (pa_context_errno(c) == PA_ERR_NOENTITY)
356
kWarning(67100) << "Source Output callback failure";
362
if (s_mixers.contains(KMIXPA_APP_CAPTURE))
363
s_mixers[KMIXPA_APP_CAPTURE]->triggerUpdate();
367
/* NB Until Source Outputs support volumes, we just use the volume of the source itself */
368
if (!captureDevices.contains(i->source)) {
369
kWarning(67100) << "Source Output refers to a Source we don't have any info for :s";
373
QString prefix = QString("%1: ").arg(i18n("Unknown Application"));
374
if (clients.contains(i->client))
375
prefix = QString("%1: ").arg(clients[i->client]);
379
s.device_index = i->source;
380
s.description = prefix + i->name;
381
s.name = QString("stream:") + i->index;
382
s.icon_name = getIconNameFromProplist(i->proplist);
383
//s.volume = i->volume;
384
s.volume = captureDevices[i->source].volume;
385
s.channel_map = i->channel_map;
386
//s.mute = !!i->mute;
387
s.mute = captureDevices[i->source].mute;
388
s.stream_restore_rule = pa_proplist_gets(i->proplist, "module-stream-restore.id");
390
translateMasksAndMaps(s);
392
bool is_new = !captureStreams.contains(s.index);
393
captureStreams[s.index] = s;
394
kDebug(67100) << "Got some info about source output (capture stream): " << s.description;
396
if (s_mixers.contains(KMIXPA_APP_CAPTURE)) {
398
s_mixers[KMIXPA_APP_CAPTURE]->addWidget(s.index);
400
int mid = s_mixers[KMIXPA_APP_CAPTURE]->id2num(s.name);
402
MixSet *ms = s_mixers[KMIXPA_APP_CAPTURE]->getMixSet();
403
(*ms)[mid]->setReadableName(s.description);
410
static devinfo create_role_devinfo(const char* name) {
412
Q_ASSERT(s_RestoreRules.contains(name));
415
s.index = s.device_index = PA_INVALID_INDEX;
416
s.description = i18n("Event Sounds");
417
s.name = QString("restore:") + name;
418
s.icon_name = "dialog-information";
419
s.channel_map = s_RestoreRules[name].channel_map;
420
s.volume = s_RestoreRules[name].volume;
421
s.mute = s_RestoreRules[name].mute;
422
s.stream_restore_rule = name;
424
translateMasksAndMaps(s);
429
void ext_stream_restore_read_cb(pa_context *c, const pa_ext_stream_restore_info *i, int eol, void *) {
433
kWarning(67100) << "Failed to initialize stream_restore extension: " << pa_strerror(pa_context_errno(s_context));
439
// Special case: ensure that our media events exists.
440
// On first login by a new users, this wont be in our database so we should create it.
441
if (!outputRoles.contains(PA_INVALID_INDEX)) {
442
// Create a fake rule
444
rule.channel_map.channels = 1;
445
rule.channel_map.map[0] = PA_CHANNEL_POSITION_MONO;
446
rule.volume.channels = 1;
447
rule.volume.values[0] = PA_VOLUME_NORM;
450
s_RestoreRules["sink-input-by-media-role:event"] = rule;
452
devinfo s = create_role_devinfo("sink-input-by-media-role:event");
453
outputRoles[s.index] = s;
454
kDebug(67100) << "Initialising restore rule for new user: " << s.description;
456
if (s_mixers.contains(KMIXPA_APP_PLAYBACK))
457
s_mixers[KMIXPA_APP_PLAYBACK]->addWidget(s.index);
460
if (s_mixers.contains(KMIXPA_APP_PLAYBACK))
461
s_mixers[KMIXPA_APP_PLAYBACK]->triggerUpdate();
465
kDebug(67100) << "Got some info about restore rule: " << i->name << i->device;
467
rule.channel_map = i->channel_map;
468
rule.volume = i->volume;
469
rule.mute = !!i->mute;
470
rule.device = i->device;
471
s_RestoreRules[i->name] = rule;
473
// We only want to know about Sound Events for now...
474
if (strcmp(i->name, "sink-input-by-media-role:event") == 0) {
475
devinfo s = create_role_devinfo(i->name);
476
bool is_new = !outputRoles.contains(s.index);
477
outputRoles[s.index] = s;
479
if (is_new && s_mixers.contains(KMIXPA_APP_PLAYBACK))
480
s_mixers[KMIXPA_APP_PLAYBACK]->addWidget(s.index);
484
static void ext_stream_restore_subscribe_cb(pa_context *c, void *) {
486
Q_ASSERT(c == s_context);
489
if (!(o = pa_ext_stream_restore_read(c, ext_stream_restore_read_cb, NULL))) {
490
kWarning(67100) << "pa_ext_stream_restore_read() failed";
494
pa_operation_unref(o);
498
static void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *) {
500
Q_ASSERT(c == s_context);
502
switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
503
case PA_SUBSCRIPTION_EVENT_SINK:
504
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
505
if (s_mixers.contains(KMIXPA_PLAYBACK))
506
s_mixers[KMIXPA_PLAYBACK]->removeWidget(index);
509
if (!(o = pa_context_get_sink_info_by_index(c, index, sink_cb, NULL))) {
510
kWarning(67100) << "pa_context_get_sink_info_by_index() failed";
513
pa_operation_unref(o);
517
case PA_SUBSCRIPTION_EVENT_SOURCE:
518
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
519
if (s_mixers.contains(KMIXPA_CAPTURE))
520
s_mixers[KMIXPA_CAPTURE]->removeWidget(index);
523
if (!(o = pa_context_get_source_info_by_index(c, index, source_cb, NULL))) {
524
kWarning(67100) << "pa_context_get_source_info_by_index() failed";
527
pa_operation_unref(o);
531
case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
532
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
533
if (s_mixers.contains(KMIXPA_APP_PLAYBACK))
534
s_mixers[KMIXPA_APP_PLAYBACK]->removeWidget(index);
537
if (!(o = pa_context_get_sink_input_info(c, index, sink_input_cb, NULL))) {
538
kWarning(67100) << "pa_context_get_sink_input_info() failed";
541
pa_operation_unref(o);
545
case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
546
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
547
if (s_mixers.contains(KMIXPA_APP_CAPTURE))
548
s_mixers[KMIXPA_APP_CAPTURE]->removeWidget(index);
551
if (!(o = pa_context_get_source_output_info(c, index, source_output_cb, NULL))) {
552
kWarning(67100) << "pa_context_get_sink_input_info() failed";
555
pa_operation_unref(o);
559
case PA_SUBSCRIPTION_EVENT_CLIENT:
560
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
561
clients.remove(index);
564
if (!(o = pa_context_get_client_info(c, index, client_cb, NULL))) {
565
kWarning(67100) << "pa_context_get_client_info() failed";
568
pa_operation_unref(o);
576
static void context_state_callback(pa_context *c, void *)
578
pa_context_state_t state = pa_context_get_state(c);
579
if (state == PA_CONTEXT_READY) {
580
// Attempt to load things up
583
// 1. Register for the stream changes (except during probe)
584
if (s_context == c) {
585
pa_context_set_subscribe_callback(c, subscribe_cb, NULL);
587
if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t)
588
(PA_SUBSCRIPTION_MASK_SINK|
589
PA_SUBSCRIPTION_MASK_SOURCE|
590
PA_SUBSCRIPTION_MASK_CLIENT|
591
PA_SUBSCRIPTION_MASK_SINK_INPUT|
592
PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT), NULL, NULL))) {
593
kWarning(67100) << "pa_context_subscribe() failed";
596
pa_operation_unref(o);
599
if (!(o = pa_context_get_sink_info_list(c, sink_cb, NULL))) {
600
kWarning(67100) << "pa_context_get_sink_info_list() failed";
603
pa_operation_unref(o);
604
s_outstandingRequests++;
606
if (!(o = pa_context_get_source_info_list(c, source_cb, NULL))) {
607
kWarning(67100) << "pa_context_get_source_info_list() failed";
610
pa_operation_unref(o);
611
s_outstandingRequests++;
614
if (!(o = pa_context_get_client_info_list(c, client_cb, NULL))) {
615
kWarning(67100) << "pa_context_client_info_list() failed";
618
pa_operation_unref(o);
619
s_outstandingRequests++;
621
if (!(o = pa_context_get_sink_input_info_list(c, sink_input_cb, NULL))) {
622
kWarning(67100) << "pa_context_get_sink_input_info_list() failed";
625
pa_operation_unref(o);
626
s_outstandingRequests++;
628
if (!(o = pa_context_get_source_output_info_list(c, source_output_cb, NULL))) {
629
kWarning(67100) << "pa_context_get_source_output_info_list() failed";
632
pa_operation_unref(o);
633
s_outstandingRequests++;
635
/* These calls are not always supported */
636
if ((o = pa_ext_stream_restore_read(c, ext_stream_restore_read_cb, NULL))) {
637
pa_operation_unref(o);
638
s_outstandingRequests++;
640
pa_ext_stream_restore_set_subscribe_cb(c, ext_stream_restore_subscribe_cb, NULL);
642
if ((o = pa_ext_stream_restore_subscribe(c, 1, NULL, NULL)))
643
pa_operation_unref(o);
645
kWarning(67100) << "Failed to initialize stream_restore extension: " << pa_strerror(pa_context_errno(s_context));
647
} else if (!PA_CONTEXT_IS_GOOD(state)) {
648
// If this is our probe phase, exit our context immediately
649
if (s_context != c) {
650
pa_context_disconnect(c);
652
// If we're not probing, it means we've been disconnected from our
654
pa_context_unref(s_context);
657
// Remove all GUI elements
658
QMap<int,Mixer_PULSE*>::iterator it;
659
for (it = s_mixers.begin(); it != s_mixers.end(); ++it) {
660
(*it)->removeAllWidgets();
662
// This one is not handled above.
665
if (s_mixers.contains(KMIXPA_PLAYBACK)) {
666
kWarning(67100) << "Connection to PulseAudio daemon closed. Attempting reconnection.";
667
s_pulseActive = UNKNOWN;
668
QTimer::singleShot(50, s_mixers[KMIXPA_PLAYBACK], SLOT(reinit()));
674
static void setVolumeFromPulse(Volume& volume, const devinfo& dev)
676
chanIDMap::const_iterator iter;
677
for (iter = dev.chanIDs.begin(); iter != dev.chanIDs.end(); ++iter)
679
//kDebug(67100) << "Setting volume for channel " << iter.value() << " to " << (long)dev.volume.values[iter.key()] << " (" << ((100*(long)dev.volume.values[iter.key()]) / PA_VOLUME_NORM) << "%)";
680
volume.setVolume(iter.value(), (long)dev.volume.values[iter.key()]);
684
static pa_cvolume genVolumeForPulse(const devinfo& dev, Volume& volume)
686
pa_cvolume cvol = dev.volume;
688
chanIDMap::const_iterator iter;
689
for (iter = dev.chanIDs.begin(); iter != dev.chanIDs.end(); ++iter)
691
cvol.values[iter.key()] = (uint32_t)volume.getVolume(iter.value());
692
//kDebug(67100) << "Setting volume for channel " << iter.value() << " to " << cvol.values[iter.key()] << " (" << ((100*cvol.values[iter.key()]) / PA_VOLUME_NORM) << "%)";
697
static devmap* get_widget_map(int type, QString id = "")
699
Q_ASSERT(type >= 0 && type <= KMIXPA_WIDGET_MAX);
701
if (KMIXPA_PLAYBACK == type)
702
return &outputDevices;
703
else if (KMIXPA_CAPTURE == type)
704
return &captureDevices;
705
else if (KMIXPA_APP_PLAYBACK == type) {
706
if (id.startsWith("restore:"))
708
return &outputStreams;
709
} else if (KMIXPA_APP_CAPTURE == type)
710
return &captureStreams;
715
static devmap* get_widget_map(int type, int index)
717
if (PA_INVALID_INDEX == (uint32_t)index)
718
return get_widget_map(type, "restore:");
719
return get_widget_map(type);
722
void Mixer_PULSE::addWidget(int index)
724
devmap* map = get_widget_map(m_devnum, index);
726
if (!map->contains(index)) {
727
kWarning(67100) << "New " << m_devnum << " widget notified for index " << index << " but I cannot find it in my list :s";
730
addDevice((*map)[index]);
731
emit controlsReconfigured(_mixer->id());
734
void Mixer_PULSE::removeWidget(int index)
736
devmap* map = get_widget_map(m_devnum);
738
if (!map->contains(index)) {
739
//kWarning(67100) << "Removing " << m_devnum << " widget notified for index " << index << " but I cannot find it in my list :s";
740
// Sometimes we ignore things (e.g. event sounds) so don't be too noisy here.
744
QString id = (*map)[index].name;
747
// We need to find the MixDevice that goes with this widget and remove it.
748
MixSet::iterator iter;
749
for (iter = m_mixDevices.begin(); iter != m_mixDevices.end(); ++iter)
751
if ((*iter)->id() == id)
754
m_mixDevices.erase(iter);
755
emit controlsReconfigured(_mixer->id());
761
void Mixer_PULSE::removeAllWidgets()
763
devmap* map = get_widget_map(m_devnum);
767
if (KMIXPA_APP_PLAYBACK == m_devnum)
770
MixSet::iterator iter;
771
for (iter = m_mixDevices.begin(); iter != m_mixDevices.end(); ++iter)
774
m_mixDevices.erase(iter);
776
emit controlsReconfigured(_mixer->id());
779
void Mixer_PULSE::addDevice(devinfo& dev)
781
if (dev.chanMask != Volume::MNONE) {
783
if (m_devnum == KMIXPA_APP_PLAYBACK && s_mixers.contains(KMIXPA_PLAYBACK))
784
ms = s_mixers[KMIXPA_PLAYBACK]->getMixSet();
785
else if (m_devnum == KMIXPA_APP_CAPTURE && s_mixers.contains(KMIXPA_CAPTURE))
786
ms = s_mixers[KMIXPA_CAPTURE]->getMixSet();
788
Volume v(dev.chanMask, PA_VOLUME_NORM, PA_VOLUME_MUTED, true, false);
789
setVolumeFromPulse(v, dev);
790
MixDevice* md = new MixDevice( _mixer, dev.name, dev.description, dev.icon_name, true, ms);
791
md->addPlaybackVolume(v);
792
md->setMuted(dev.mute);
793
m_mixDevices.append(md);
30
797
Mixer_Backend* PULSE_getMixer( Mixer *mixer, int devnum )
804
bool Mixer_PULSE::connectToDaemon()
806
Q_ASSERT(NULL == s_context);
808
kDebug(67100) << "Attempting connection to PulseAudio sound daemon";
809
pa_mainloop_api *api = pa_glib_mainloop_get_api(s_mainloop);
812
s_context = pa_context_new(api, "KMix KDE 4");
815
if (pa_context_connect(s_context, NULL, PA_CONTEXT_NOFAIL, 0) < 0) {
816
pa_context_unref(s_context);
820
pa_context_set_state_callback(s_context, &context_state_callback, NULL);
38
825
Mixer_PULSE::Mixer_PULSE(Mixer *mixer, int devnum) : Mixer_Backend(mixer, devnum)
830
QString pulseenv = qgetenv("KMIX_PULSEAUDIO_DISABLE");
831
if (pulseenv.toInt())
832
s_pulseActive = INACTIVE;
834
// We require a glib event loop
835
if (QLatin1String(QAbstractEventDispatcher::instance()->metaObject()->className())
836
!= "QGuiEventDispatcherGlib") {
837
kDebug(67100) << "Disabling PulseAudio integration for lack of GLib event loop.";
838
s_pulseActive = INACTIVE;
843
if (INACTIVE != s_pulseActive && 1 == refcount)
845
// First of all conenct to PA via simple/blocking means and if that succeeds,
846
// use a fully async integrated mainloop method to connect and get proper support.
847
pa_mainloop *p_test_mainloop;
848
if (!(p_test_mainloop = pa_mainloop_new())) {
849
kDebug(67100) << "PulseAudio support disabled: Unable to create mainloop";
850
s_pulseActive = INACTIVE;
854
pa_context *p_test_context;
855
if (!(p_test_context = pa_context_new(pa_mainloop_get_api(p_test_mainloop), "kmix-probe"))) {
856
kDebug(67100) << "PulseAudio support disabled: Unable to create context";
857
pa_mainloop_free(p_test_mainloop);
858
s_pulseActive = INACTIVE;
862
kDebug(67100) << "Probing for PulseAudio...";
863
// (cg) Convert to PA_CONTEXT_NOFLAGS when PulseAudio 0.9.19 is required
864
if (pa_context_connect(p_test_context, NULL, static_cast<pa_context_flags_t>(0), NULL) < 0) {
865
kDebug(67100) << QString("PulseAudio support disabled: %1").arg(pa_strerror(pa_context_errno(p_test_context)));
866
pa_context_disconnect(p_test_context);
867
pa_context_unref(p_test_context);
868
pa_mainloop_free(p_test_mainloop);
869
s_pulseActive = INACTIVE;
873
// Assume we are inactive, it will be set to active if appropriate
874
s_pulseActive = INACTIVE;
875
pa_context_set_state_callback(p_test_context, &context_state_callback, NULL);
877
pa_mainloop_iterate(p_test_mainloop, 1, NULL);
879
if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(p_test_context))) {
880
kDebug(67100) << "PulseAudio probe complete.";
884
pa_context_disconnect(p_test_context);
885
pa_context_unref(p_test_context);
886
pa_mainloop_free(p_test_mainloop);
889
if (INACTIVE != s_pulseActive)
891
// Reconnect via integrated mainloop
892
s_mainloop = pa_glib_mainloop_new(NULL);
893
Q_ASSERT(s_mainloop);
898
kDebug(67100) << "PulseAudio status: " << (s_pulseActive==UNKNOWN ? "Unknown (bug)" : (s_pulseActive==ACTIVE ? "Active" : "Inactive"));
902
s_mixers[m_devnum] = this;
44
905
Mixer_PULSE::~Mixer_PULSE()
907
s_mixers.remove(m_devnum);
915
pa_context_unref(s_context);
920
pa_glib_mainloop_free(s_mainloop);
49
927
int Mixer_PULSE::open()
51
kDebug(67100) << "Trying Pulse sink";
52
mainloop = pa_glib_mainloop_new(g_main_context_default());
54
pa_mainloop_api *api = pa_glib_mainloop_get_api(mainloop);
57
context = pa_context_new(api, "KMix KDE 4");
59
//return Mixer::ERR_OPEN;
929
//kDebug(67100) << "Trying Pulse sink";
931
if (ACTIVE == s_pulseActive && m_devnum <= KMIXPA_APP_CAPTURE)
933
// Make sure the GUI layers know we are dynamic so as to always paint us
934
_mixer->setDynamic();
936
devmap::iterator iter;
937
if (KMIXPA_PLAYBACK == m_devnum)
939
m_mixerName = i18n("Playback Devices");
940
for (iter = outputDevices.begin(); iter != outputDevices.end(); ++iter)
943
else if (KMIXPA_CAPTURE == m_devnum)
945
m_mixerName = i18n("Capture Devices");
946
for (iter = captureDevices.begin(); iter != captureDevices.end(); ++iter)
949
else if (KMIXPA_APP_PLAYBACK == m_devnum)
951
m_mixerName = i18n("Playback Streams");
952
for (iter = outputRoles.begin(); iter != outputRoles.end(); ++iter)
954
for (iter = outputStreams.begin(); iter != outputStreams.end(); ++iter)
957
else if (KMIXPA_APP_CAPTURE == m_devnum)
959
m_mixerName = i18n("Capture Streams");
960
for (iter = captureStreams.begin(); iter != captureStreams.end(); ++iter)
964
kDebug(67100) << "Using PulseAudio for mixer: " << m_mixerName;
63
// Mixer is open. Now define all of the mix devices.
66
for ( int idx = 0; idx < numDevs; idx++ )
68
Volume vol( 2, AUDIO_MAX_GAIN );
71
MixDevice* md = new MixDevice( _mixer, id,
72
QString(MixerDevNames[idx]), MixerChannelTypes[idx]);
73
md->addPlaybackVolume(vol);
74
md->setRecSource( isRecsrcHW( idx ) );
75
m_mixDevices.append( md );
79
m_mixerName = "PULSE Audio Mixer";
86
971
int Mixer_PULSE::close()
90
pa_context_unref(context);
95
pa_glib_mainloop_free(mainloop);
976
int Mixer_PULSE::id2num(const QString& id) {
977
//kDebug(67100) << "id2num() id=" << id;
979
// todo: Store this in a hash or similar
981
for (i = 0; i < m_mixDevices.size(); ++i) {
982
if (m_mixDevices[i]->id() == id) {
987
//kDebug(67100) << "id2num() num=" << num;
101
991
int Mixer_PULSE::readVolumeFromHW( const QString& id, MixDevice *md )
103
/* audio_info_t audioinfo;
104
uint_t devMask = MixerSunPortMasks[devnum];
106
Volume& volume = md->playbackVolume();
107
int devnum = id2num(id);
109
// Read the current audio information from the driver
111
if ( ioctl( fd, AUDIO_GETINFO, &audioinfo ) < 0 )
113
return( Mixer::ERR_READ );
118
// Extract the appropriate fields based on the requested device
122
case MIXERDEV_MASTER_VOLUME :
123
volume.setSwitchActivated( audioinfo.output_muted );
124
GainBalanceToVolume( audioinfo.play.gain,
125
audioinfo.play.balance,
129
case MIXERDEV_RECORD_MONITOR :
131
volume.setAllVolumes( audioinfo.monitor_gain );
134
case MIXERDEV_INTERNAL_SPEAKER :
135
case MIXERDEV_HEADPHONE :
136
case MIXERDEV_LINE_OUT :
137
md->setMuted( (audioinfo.play.port & devMask) ? false : true );
138
GainBalanceToVolume( audioinfo.play.gain,
139
audioinfo.play.balance,
143
case MIXERDEV_MICROPHONE :
144
case MIXERDEV_LINE_IN :
146
md->setMuted( (audioinfo.record.port & devMask) ? false : true );
147
GainBalanceToVolume( audioinfo.record.gain,
148
audioinfo.record.balance,
153
return Mixer::ERR_READ;
993
devmap *map = get_widget_map(m_devnum, id);
995
devmap::iterator iter;
996
for (iter = map->begin(); iter != map->end(); ++iter)
998
if (iter->name == id)
1000
setVolumeFromPulse(md->playbackVolume(), *iter);
1001
md->setMuted(iter->mute);
160
1009
int Mixer_PULSE::writeVolumeToHW( const QString& id, MixDevice *md )
166
Volume& volume = md->playbackVolume();
167
int devnum = id2num(id);
169
// Convert the Volume(left vol, right vol) to the Gain/Balance Sun uses
171
VolumeToGainBalance( volume, gain, balance );
172
mute = md->isMuted() ? 1 : 0;
175
// Read the current audio settings from the hardware
177
audio_info_t audioinfo;
178
if ( ioctl( fd, AUDIO_GETINFO, &audioinfo ) < 0 )
180
return( Mixer::ERR_READ );
184
// Now, based on the devnum that we are writing to, update the appropriate
185
// volume field and twiddle the appropriate bitmask to enable/mute the
186
// device as necessary.
190
case MIXERDEV_MASTER_VOLUME :
191
audioinfo.play.gain = gain;
192
audioinfo.play.balance = balance;
193
audioinfo.output_muted = mute;
196
case MIXERDEV_RECORD_MONITOR :
197
audioinfo.monitor_gain = gain;
198
// no mute or balance for record monitor
201
case MIXERDEV_INTERNAL_SPEAKER :
202
case MIXERDEV_HEADPHONE :
203
case MIXERDEV_LINE_OUT :
204
audioinfo.play.gain = gain;
205
audioinfo.play.balance = balance;
207
audioinfo.play.port &= ~MixerSunPortMasks[devnum];
209
audioinfo.play.port |= MixerSunPortMasks[devnum];
212
case MIXERDEV_MICROPHONE :
213
case MIXERDEV_LINE_IN :
215
audioinfo.record.gain = gain;
216
audioinfo.record.balance = balance;
218
audioinfo.record.port &= ~MixerSunPortMasks[devnum];
220
audioinfo.record.port |= MixerSunPortMasks[devnum];
224
return Mixer::ERR_READ;
228
// Now that we've updated the audioinfo struct, write it back to the hardware
230
if ( ioctl( fd, AUDIO_SETINFO, &audioinfo ) < 0 )
232
return( Mixer::ERR_WRITE );
1011
devmap::iterator iter;
1012
if (KMIXPA_PLAYBACK == m_devnum)
1014
for (iter = outputDevices.begin(); iter != outputDevices.end(); ++iter)
1016
if (iter->name == id)
1020
pa_cvolume volume = genVolumeForPulse(*iter, md->playbackVolume());
1021
if (!(o = pa_context_set_sink_volume_by_index(s_context, iter->index, &volume, NULL, NULL))) {
1022
kWarning(67100) << "pa_context_set_sink_volume_by_index() failed";
1023
return Mixer::ERR_READ;
1025
pa_operation_unref(o);
1027
if (!(o = pa_context_set_sink_mute_by_index(s_context, iter->index, (md->isMuted() ? 1 : 0), NULL, NULL))) {
1028
kWarning(67100) << "pa_context_set_sink_mute_by_index() failed";
1029
return Mixer::ERR_READ;
1031
pa_operation_unref(o);
1037
else if (KMIXPA_CAPTURE == m_devnum)
1039
for (iter = captureDevices.begin(); iter != captureDevices.end(); ++iter)
1041
if (iter->name == id)
1045
pa_cvolume volume = genVolumeForPulse(*iter, md->playbackVolume());
1046
if (!(o = pa_context_set_source_volume_by_index(s_context, iter->index, &volume, NULL, NULL))) {
1047
kWarning(67100) << "pa_context_set_source_volume_by_index() failed";
1048
return Mixer::ERR_READ;
1050
pa_operation_unref(o);
1052
if (!(o = pa_context_set_source_mute_by_index(s_context, iter->index, (md->isMuted() ? 1 : 0), NULL, NULL))) {
1053
kWarning(67100) << "pa_context_set_source_mute_by_index() failed";
1054
return Mixer::ERR_READ;
1056
pa_operation_unref(o);
1062
else if (KMIXPA_APP_PLAYBACK == m_devnum)
1064
if (id.startsWith("stream:"))
1066
for (iter = outputStreams.begin(); iter != outputStreams.end(); ++iter)
1068
if (iter->name == id)
1072
pa_cvolume volume = genVolumeForPulse(*iter, md->playbackVolume());
1073
if (!(o = pa_context_set_sink_input_volume(s_context, iter->index, &volume, NULL, NULL))) {
1074
kWarning(67100) << "pa_context_set_sink_input_volume() failed";
1075
return Mixer::ERR_READ;
1077
pa_operation_unref(o);
1079
if (!(o = pa_context_set_sink_input_mute(s_context, iter->index, (md->isMuted() ? 1 : 0), NULL, NULL))) {
1080
kWarning(67100) << "pa_context_set_sink_input_mute() failed";
1081
return Mixer::ERR_READ;
1083
pa_operation_unref(o);
1089
else if (id.startsWith("restore:"))
1091
for (iter = outputRoles.begin(); iter != outputRoles.end(); ++iter)
1093
if (iter->name == id)
1095
restoreRule &rule = s_RestoreRules[iter->stream_restore_rule];
1096
pa_ext_stream_restore_info info;
1097
info.name = iter->stream_restore_rule.toAscii().constData();
1098
info.channel_map = rule.channel_map;
1099
info.volume = genVolumeForPulse(*iter, md->playbackVolume());
1100
info.device = rule.device.isEmpty() ? NULL : rule.device.toAscii().constData();
1101
info.mute = (md->isMuted() ? 1 : 0);
1104
if (!(o = pa_ext_stream_restore_write(s_context, PA_UPDATE_REPLACE, &info, 1, TRUE, NULL, NULL))) {
1105
kWarning(67100) << "pa_ext_stream_restore_write() failed" << info.channel_map.channels << info.volume.channels << info.name;
1106
return Mixer::ERR_READ;
1108
pa_operation_unref(o);
1115
else if (KMIXPA_APP_CAPTURE == m_devnum)
1117
for (iter = captureStreams.begin(); iter != captureStreams.end(); ++iter)
1119
if (iter->name == id)
1123
// NB Note that this is different from APP_PLAYBACK in that we set the volume on the source itself.
1124
pa_cvolume volume = genVolumeForPulse(*iter, md->playbackVolume());
1125
if (!(o = pa_context_set_source_volume_by_index(s_context, iter->device_index, &volume, NULL, NULL))) {
1126
kWarning(67100) << "pa_context_set_source_volume_by_index() failed";
1127
return Mixer::ERR_READ;
1129
pa_operation_unref(o);
1131
if (!(o = pa_context_set_source_mute_by_index(s_context, iter->device_index, (md->isMuted() ? 1 : 0), NULL, NULL))) {
1132
kWarning(67100) << "pa_context_set_source_mute_by_index() failed";
1133
return Mixer::ERR_READ;
1135
pa_operation_unref(o);
1146
* Move the stream to a new destination
1148
bool Mixer_PULSE::moveStream( const QString& id, const QString& destId ) {
1149
Q_ASSERT(KMIXPA_APP_PLAYBACK == m_devnum || KMIXPA_APP_CAPTURE == m_devnum);
1151
kDebug(67100) << "Mixer_PULSE::moveStream(): Move Stream Requested - Stream: " << id << ", Destination: " << destId;
1153
// Lookup the stream index.
1154
uint32_t stream_index = PA_INVALID_INDEX;
1155
const char* stream_restore_rule = NULL;
1156
devmap::iterator iter;
1157
devmap *map = get_widget_map(m_devnum);
1158
for (iter = map->begin(); iter != map->end(); ++iter) {
1159
if (iter->name == id) {
1160
stream_index = iter->index;
1161
stream_restore_rule = iter->stream_restore_rule.isEmpty() ? NULL : iter->stream_restore_rule.toAscii().constData();
1166
if (PA_INVALID_INDEX == stream_index) {
1167
kError(67100) << "Mixer_PULSE::moveStream(): Cannot find stream index";
1171
if (destId.isEmpty()) {
1172
// We want to remove any specific device in the stream restore rule.
1173
if (!stream_restore_rule || !s_RestoreRules.contains(stream_restore_rule)) {
1174
kWarning(67100) << "Mixer_PULSE::moveStream(): Trying to set Automatic on a stream with no rule";
1176
restoreRule &rule = s_RestoreRules[stream_restore_rule];
1177
pa_ext_stream_restore_info info;
1178
info.name = stream_restore_rule;
1179
info.channel_map = rule.channel_map;
1180
info.volume = rule.volume;
1182
info.mute = rule.mute ? 1 : 0;
1185
if (!(o = pa_ext_stream_restore_write(s_context, PA_UPDATE_REPLACE, &info, 1, TRUE, NULL, NULL))) {
1186
kWarning(67100) << "pa_ext_stream_restore_write() failed" << info.channel_map.channels << info.volume.channels << info.name;
1187
return Mixer::ERR_READ;
1189
pa_operation_unref(o);
1193
if (KMIXPA_APP_PLAYBACK == m_devnum) {
1194
if (!(o = pa_context_move_sink_input_by_name(s_context, stream_index, destId.toAscii().constData(), NULL, NULL))) {
1195
kWarning(67100) << "pa_context_move_sink_input_by_name() failed";
1199
if (!(o = pa_context_move_source_output_by_name(s_context, stream_index, destId.toAscii().constData(), NULL, NULL))) {
1200
kWarning(67100) << "pa_context_move_source_output_by_name() failed";
1204
pa_operation_unref(o);
1210
void Mixer_PULSE::reinit()
1212
// We only support reinit on our primary mixer.
1213
Q_ASSERT(KMIXPA_PLAYBACK == m_devnum);
1217
void Mixer_PULSE::triggerUpdate()
1219
readSetFromHWforceUpdate();
241
1223
void Mixer_PULSE::setRecsrcHW( const QString& /*id*/, bool /* on */ )