1
/* Copyright (C) 2005-2011, Thorvald Natvig <thorvald@natvig.com>
5
Redistribution and use in source and binary forms, with or without
6
modification, are permitted provided that the following conditions
9
- Redistributions of source code must retain the above copyright notice,
10
this list of conditions and the following disclaimer.
11
- Redistributions in binary form must reproduce the above copyright notice,
12
this list of conditions and the following disclaimer in the documentation
13
and/or other materials provided with the distribution.
14
- Neither the name of the Mumble Developers nor the names of its
15
contributors may be used to endorse or promote products derived from this
16
software without specific prior written permission.
18
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
22
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
#include "mumble_pch.hpp"
33
#include "PulseAudio.h"
35
#include <sys/soundcard.h>
38
#include <sys/ioctl.h>
41
#include "MainWindow.h"
46
static const char *mumble_sink_input = "Mumble Speakers";
47
static const char *mumble_echo = "Mumble Speakers (Echo)";
49
static PulseAudioSystem *pasys = NULL;
53
class PulseAudioInputRegistrar : public AudioInputRegistrar {
55
PulseAudioInputRegistrar();
56
virtual AudioInput *create();
57
virtual const QList<audioDevice> getDeviceChoices();
58
virtual void setDeviceChoice(const QVariant &, Settings &);
59
virtual bool canEcho(const QString &) const;
63
class PulseAudioOutputRegistrar : public AudioOutputRegistrar {
65
PulseAudioOutputRegistrar();
66
virtual AudioOutput *create();
67
virtual const QList<audioDevice> getDeviceChoices();
68
virtual void setDeviceChoice(const QVariant &, Settings &);
69
bool canMuteOthers() const;
72
class PulseAudioInit : public DeferInit {
74
PulseAudioInputRegistrar *airPulseAudio;
75
PulseAudioOutputRegistrar *aorPulseAudio;
77
pasys = new PulseAudioSystem();
79
pasys->qwcWait.wait(&pasys->qmWait, 1000);
80
pasys->qmWait.unlock();
81
if (pasys->bPulseIsGood) {
82
airPulseAudio = new PulseAudioInputRegistrar();
83
aorPulseAudio = new PulseAudioOutputRegistrar();
99
static PulseAudioInit pulseinit;
101
PulseAudioSystem::PulseAudioSystem() {
102
pasInput = pasOutput = pasSpeaker = NULL;
103
bSourceDone=bSinkDone=bServerDone = false;
105
bPositionalCache = false;
106
bAttenuating = false;
107
iRemainingOperations = 0;
108
bPulseIsGood = false;
110
pam = pa_threaded_mainloop_new();
111
pa_mainloop_api *api = pa_threaded_mainloop_get_api(pam);
113
pa_proplist *proplist;
115
proplist = pa_proplist_new();
116
pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "Mumble");
117
pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "net.sourceforge.mumble.mumble");
118
pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "mumble");
119
pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE, "game");
121
pacContext = pa_context_new_with_proplist(api, NULL, proplist);
122
pa_proplist_free(proplist);
124
pa_context_set_subscribe_callback(pacContext, subscribe_callback, this);
126
pa_context_set_state_callback(pacContext, context_state_callback, this);
127
pa_context_connect(pacContext, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
129
pade = api->defer_new(api, defer_event_callback, this);
130
api->defer_enable(pade, false);
132
pa_threaded_mainloop_start(pam);
137
PulseAudioSystem::~PulseAudioSystem() {
141
bAttenuating = false;
143
bool success = qwcWait.wait(&qmWait, 1000);
145
qWarning("PulseAudio: Shutdown timeout when attempting to restore volumes.");
149
pa_threaded_mainloop_stop(pam);
150
pa_context_disconnect(pacContext);
151
pa_context_unref(pacContext);
152
pa_threaded_mainloop_free(pam);
155
void PulseAudioSystem::wakeup() {
156
pa_mainloop_api *api = pa_threaded_mainloop_get_api(pam);
157
api->defer_enable(pade, true);
160
void PulseAudioSystem::wakeup_lock() {
161
pa_threaded_mainloop_lock(pam);
162
pa_mainloop_api *api = pa_threaded_mainloop_get_api(pam);
163
api->defer_enable(pade, true);
164
pa_threaded_mainloop_unlock(pam);
167
void PulseAudioSystem::defer_event_callback(pa_mainloop_api *a, pa_defer_event *e, void *userdata) {
168
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
169
pas->eventCallback(a, e);
172
void PulseAudioSystem::eventCallback(pa_mainloop_api *api, pa_defer_event *) {
173
api->defer_enable(pade, false);
175
if (! bSourceDone || ! bSinkDone || ! bServerDone)
178
AudioInputPtr ai = g.ai;
179
AudioOutputPtr ao = g.ao;
180
AudioInput *raw_ai = ai.get();
181
AudioOutput *raw_ao = ao.get();
182
PulseAudioInput *pai = dynamic_cast<PulseAudioInput *>(raw_ai);
183
PulseAudioOutput *pao = dynamic_cast<PulseAudioOutput *>(raw_ao);
186
QString odev = g.s.qsPulseAudioOutput.isEmpty() ? qsDefaultOutput : g.s.qsPulseAudioOutput;
187
pa_stream_state ost = pasOutput ? pa_stream_get_state(pasOutput) : PA_STREAM_TERMINATED;
188
bool do_stop = false;
189
bool do_start = false;
191
if (! pao && (ost == PA_STREAM_READY)) {
195
case PA_STREAM_TERMINATED: {
197
pa_stream_unref(pasOutput);
199
pa_sample_spec pss = qhSpecMap.value(odev);
200
pa_channel_map pcm = qhChanMap.value(odev);
201
if ((pss.format != PA_SAMPLE_FLOAT32NE) && (pss.format != PA_SAMPLE_S16NE))
202
pss.format = PA_SAMPLE_FLOAT32NE;
204
pss.rate = SAMPLE_RATE;
205
if ((pss.channels == 0) || (! g.s.doPositionalAudio()))
208
pasOutput = pa_stream_new(pacContext, mumble_sink_input, &pss, (pss.channels == 1) ? NULL : &pcm);
209
pa_stream_set_state_callback(pasOutput, stream_callback, this);
210
pa_stream_set_write_callback(pasOutput, write_callback, this);
212
case PA_STREAM_UNCONNECTED:
215
case PA_STREAM_READY: {
216
if (g.s.iOutputDelay != iDelayCache) {
218
} else if (g.s.doPositionalAudio() != bPositionalCache) {
220
} else if (odev != qsOutputCache) {
230
qWarning("PulseAudio: Stopping output");
231
pa_stream_disconnect(pasOutput);
232
} else if (do_start) {
233
qWarning("PulseAudio: Starting output: %s", qPrintable(odev));
235
const pa_sample_spec *pss = pa_stream_get_sample_spec(pasOutput);
236
const unsigned int iBlockLen = ((pao->iFrameSize * pss->rate) / SAMPLE_RATE) * pss->channels * ((pss->format == PA_SAMPLE_FLOAT32NE) ? sizeof(float) : sizeof(short));
237
buff.tlength = iBlockLen * (g.s.iOutputDelay+1);
238
buff.minreq = iBlockLen;
241
buff.fragsize = iBlockLen;
243
iDelayCache = g.s.iOutputDelay;
244
bPositionalCache = g.s.doPositionalAudio();
245
qsOutputCache = odev;
247
pa_stream_connect_playback(pasOutput, qPrintable(odev), &buff, PA_STREAM_ADJUST_LATENCY, NULL, NULL);
252
QString idev = g.s.qsPulseAudioInput.isEmpty() ? qsDefaultInput : g.s.qsPulseAudioInput;
253
pa_stream_state ist = pasInput ? pa_stream_get_state(pasInput) : PA_STREAM_TERMINATED;
254
bool do_stop = false;
255
bool do_start = false;
257
if (! pai && (ist == PA_STREAM_READY)) {
261
case PA_STREAM_TERMINATED: {
263
pa_stream_unref(pasInput);
265
pa_sample_spec pss = qhSpecMap.value(idev);
266
if ((pss.format != PA_SAMPLE_FLOAT32NE) && (pss.format != PA_SAMPLE_S16NE))
267
pss.format = PA_SAMPLE_FLOAT32NE;
269
pss.rate = SAMPLE_RATE;
272
pasInput = pa_stream_new(pacContext, "Microphone", &pss, NULL);
273
pa_stream_set_state_callback(pasInput, stream_callback, this);
274
pa_stream_set_read_callback(pasInput, read_callback, this);
276
case PA_STREAM_UNCONNECTED:
279
case PA_STREAM_READY: {
280
if (idev != qsInputCache) {
290
qWarning("PulseAudio: Stopping input");
291
pa_stream_disconnect(pasInput);
292
} else if (do_start) {
293
qWarning("PulseAudio: Starting input %s",qPrintable(idev));
295
const pa_sample_spec *pss = pa_stream_get_sample_spec(pasInput);
296
const unsigned int iBlockLen = ((pai->iFrameSize * pss->rate) / SAMPLE_RATE) * pss->channels * ((pss->format == PA_SAMPLE_FLOAT32NE) ? sizeof(float) : sizeof(short));
297
buff.tlength = iBlockLen;
298
buff.minreq = iBlockLen;
301
buff.fragsize = iBlockLen;
305
pa_stream_connect_record(pasInput, qPrintable(idev), &buff, PA_STREAM_ADJUST_LATENCY);
310
QString odev = g.s.qsPulseAudioOutput.isEmpty() ? qsDefaultOutput : g.s.qsPulseAudioOutput;
311
QString edev = qhEchoMap.value(odev);
312
pa_stream_state est = pasSpeaker ? pa_stream_get_state(pasSpeaker) : PA_STREAM_TERMINATED;
313
bool do_stop = false;
314
bool do_start = false;
316
if ((! pai || ! g.s.doEcho()) && (est == PA_STREAM_READY)) {
318
} else if (pai && g.s.doEcho()) {
320
case PA_STREAM_TERMINATED: {
322
pa_stream_unref(pasSpeaker);
324
pa_sample_spec pss = qhSpecMap.value(edev);
325
pa_channel_map pcm = qhChanMap.value(edev);
326
if ((pss.format != PA_SAMPLE_FLOAT32NE) && (pss.format != PA_SAMPLE_S16NE))
327
pss.format = PA_SAMPLE_FLOAT32NE;
329
pss.rate = SAMPLE_RATE;
330
if ((pss.channels == 0) || (! g.s.bEchoMulti))
333
pasSpeaker = pa_stream_new(pacContext, mumble_echo, &pss, (pss.channels == 1) ? NULL : &pcm);
334
pa_stream_set_state_callback(pasSpeaker, stream_callback, this);
335
pa_stream_set_read_callback(pasSpeaker, read_callback, this);
337
case PA_STREAM_UNCONNECTED:
340
case PA_STREAM_READY: {
341
if (g.s.bEchoMulti != bEchoMultiCache) {
343
} else if (edev != qsEchoCache) {
353
qWarning("PulseAudio: Stopping echo");
354
pa_stream_disconnect(pasSpeaker);
355
} else if (do_start) {
356
qWarning("PulseAudio: Starting echo: %s", qPrintable(edev));
358
const pa_sample_spec *pss = pa_stream_get_sample_spec(pasSpeaker);
359
const unsigned int iBlockLen = ((pai->iFrameSize * pss->rate) / SAMPLE_RATE) * pss->channels * ((pss->format == PA_SAMPLE_FLOAT32NE) ? sizeof(float) : sizeof(short));
360
buff.tlength = iBlockLen;
361
buff.minreq = iBlockLen;
364
buff.fragsize = iBlockLen;
366
bEchoMultiCache = g.s.bEchoMulti;
369
pa_stream_connect_record(pasSpeaker, qPrintable(edev), &buff, PA_STREAM_ADJUST_LATENCY);
374
void PulseAudioSystem::context_state_callback(pa_context *c, void *userdata) {
375
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
376
pas->contextCallback(c);
379
void PulseAudioSystem::subscribe_callback(pa_context *, pa_subscription_event_type evt, unsigned int, void *userdata) {
380
switch (evt & PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
381
case PA_SUBSCRIPTION_EVENT_NEW:
382
case PA_SUBSCRIPTION_EVENT_REMOVE:
387
switch (evt & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
388
case PA_SUBSCRIPTION_EVENT_SINK:
389
case PA_SUBSCRIPTION_EVENT_SOURCE:
394
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
395
qWarning("PulseAudio: Sinks or inputs changed (inserted or removed sound card)");
399
void PulseAudioSystem::sink_callback(pa_context *, const pa_sink_info *i, int eol, void *userdata) {
400
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
402
pas->bSinkDone = true;
407
const QString name = QLatin1String(i->name);
409
pas->qhSpecMap.insert(name, i->sample_spec);
410
pas->qhChanMap.insert(name, i->channel_map);
411
pas->qhOutput.insert(name, QLatin1String(i->description));
412
pas->qhEchoMap.insert(name, QLatin1String(i->monitor_source_name));
415
void PulseAudioSystem::source_callback(pa_context *, const pa_source_info *i, int eol, void *userdata) {
416
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
418
pas->bSourceDone = true;
423
const QString name = QLatin1String(i->name);
425
pas->qhSpecMap.insert(name, i->sample_spec);
426
pas->qhChanMap.insert(name, i->channel_map);
428
if (i->monitor_of_sink == PA_INVALID_INDEX)
429
pas->qhInput.insert(QLatin1String(i->name), QLatin1String(i->description));
432
void PulseAudioSystem::server_callback(pa_context *, const pa_server_info *i, void *userdata) {
433
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
435
pas->qsDefaultInput = QLatin1String(i->default_source_name);
436
pas->qsDefaultOutput = QLatin1String(i->default_sink_name);
438
pas->bServerDone = true;
442
void PulseAudioSystem::stream_callback(pa_stream *s, void *userdata) {
443
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
444
switch (pa_stream_get_state(s)) {
445
case PA_STREAM_FAILED:
446
qWarning("PulseAudio: Stream error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
454
void PulseAudioSystem::read_callback(pa_stream *s, size_t bytes, void *userdata) {
455
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
457
size_t length = bytes;
459
pa_stream_peek(s, &data, &length);
461
AudioInputPtr ai = g.ai;
462
PulseAudioInput *pai = dynamic_cast<PulseAudioInput *>(ai.get());
469
const pa_sample_spec *pss = pa_stream_get_sample_spec(s);
471
if (s == pas->pasInput) {
472
if (!pa_sample_spec_equal(pss, &pai->pssMic)) {
474
pai->iMicFreq = pss->rate;
475
pai->iMicChannels = pss->channels;
476
if (pss->format == PA_SAMPLE_FLOAT32NE)
477
pai->eMicFormat = PulseAudioInput::SampleFloat;
479
pai->eMicFormat = PulseAudioInput::SampleShort;
480
pai->initializeMixer();
482
pai->addMic(data, length / pai->iMicSampleSize);
483
} else if (s == pas->pasSpeaker) {
484
if (!pa_sample_spec_equal(pss, &pai->pssEcho)) {
486
pai->iEchoFreq = pss->rate;
487
pai->iEchoChannels = pss->channels;
488
if (pss->format == PA_SAMPLE_FLOAT32NE)
489
pai->eEchoFormat = PulseAudioInput::SampleFloat;
491
pai->eEchoFormat = PulseAudioInput::SampleShort;
492
pai->initializeMixer();
494
pai->addEcho(data, length / pai->iEchoSampleSize);
500
void PulseAudioSystem::write_callback(pa_stream *s, size_t bytes, void *userdata) {
501
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
502
Q_ASSERT(s == pas->pasOutput);
504
AudioOutputPtr ao = g.ao;
505
PulseAudioOutput *pao = dynamic_cast<PulseAudioOutput *>(ao.get());
507
unsigned char buffer[bytes];
510
// Transitioning, but most likely transitions back, so just zero.
511
memset(buffer, 0, bytes);
512
pa_stream_write(s, buffer, bytes, NULL, 0, PA_SEEK_RELATIVE);
517
const pa_sample_spec *pss = pa_stream_get_sample_spec(s);
518
const pa_channel_map *pcm = pa_stream_get_channel_map(pas->pasOutput);
519
if (!pa_sample_spec_equal(pss, &pao->pss) || !pa_channel_map_equal(pcm, &pao->pcm)) {
522
if (pss->format == PA_SAMPLE_FLOAT32NE)
523
pao->eSampleFormat = PulseAudioOutput::SampleFloat;
525
pao->eSampleFormat = PulseAudioOutput::SampleShort;
526
pao->iMixerFreq = pss->rate;
527
pao->iChannels = pss->channels;
528
unsigned int chanmasks[pss->channels];
529
for (int i=0;i<pss->channels;++i) {
531
switch (pcm->map[i]) {
532
case PA_CHANNEL_POSITION_LEFT:
533
cm = SPEAKER_FRONT_LEFT;
535
case PA_CHANNEL_POSITION_RIGHT:
536
cm = SPEAKER_FRONT_RIGHT;
538
case PA_CHANNEL_POSITION_CENTER:
539
cm = SPEAKER_FRONT_CENTER;
541
case PA_CHANNEL_POSITION_REAR_LEFT:
542
cm = SPEAKER_BACK_LEFT;
544
case PA_CHANNEL_POSITION_REAR_RIGHT:
545
cm = SPEAKER_BACK_RIGHT;
547
case PA_CHANNEL_POSITION_REAR_CENTER:
548
cm = SPEAKER_BACK_CENTER;
550
case PA_CHANNEL_POSITION_LFE:
551
cm = SPEAKER_LOW_FREQUENCY;
553
case PA_CHANNEL_POSITION_SIDE_LEFT:
554
cm = SPEAKER_SIDE_LEFT;
556
case PA_CHANNEL_POSITION_SIDE_RIGHT:
557
cm = SPEAKER_SIDE_RIGHT;
559
case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER:
560
cm = SPEAKER_FRONT_LEFT_OF_CENTER;
562
case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER:
563
cm = SPEAKER_FRONT_RIGHT_OF_CENTER;
571
pao->initializeMixer(chanmasks);
574
const unsigned int iSampleSize = pao->iSampleSize;
575
const unsigned int samples = bytes / iSampleSize;
576
bool oldAttenuation = pas->bAttenuating;
578
// do we have some mixed output?
579
if (pao->mix(buffer, samples)) {
580
// attenuate if instructed to or it's in settings
581
pas->bAttenuating = (g.bAttenuateOthers || g.s.bAttenuateOthers);
584
memset(buffer, 0, bytes);
586
// attenuate if intructed to (self-activated)
587
pas->bAttenuating = g.bAttenuateOthers;
590
// if the attenuation state has changed
591
if (oldAttenuation != pas->bAttenuating) {
595
pa_stream_write(s, buffer, iSampleSize * samples, NULL, 0, PA_SEEK_RELATIVE);
598
void PulseAudioSystem::volume_sink_input_list_callback(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) {
599
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
602
// ensure we're not attenuating ourselves!
603
if (strcmp(i->name, mumble_sink_input) != 0) {
604
// create a new entry
605
PulseAttenuation patt;
606
patt.index = i->index;
607
patt.name = QLatin1String(i->name);
608
patt.stream_restore_id = QLatin1String(pa_proplist_gets(i->proplist, "module-stream-restore.id"));
609
patt.normal_volume = i->volume;
611
// calculate the attenuated volume
612
pa_volume_t adj = static_cast<pa_volume_t>(PA_VOLUME_NORM * g.s.fOtherVolume);
613
pa_sw_cvolume_multiply_scalar(&patt.attenuated_volume, &i->volume, adj);
615
// set it on the sink input
616
pa_operation_unref(pa_context_set_sink_input_volume(c, i->index, &patt.attenuated_volume, NULL, NULL));
619
pas->qhVolumes[i->index] = patt;
622
} else if (eol < 0) {
623
qWarning("PulseAudio: Sink input introspection error.");
627
void PulseAudioSystem::restore_sink_input_list_callback(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) {
628
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
631
// if we were tracking this specific sink previously
632
if (pas->qhVolumes.contains(i->index)) {
633
// and if it has the attenuated volume we applied to it
634
if (pa_cvolume_equal(&i->volume, &pas->qhVolumes[i->index].attenuated_volume) != 0) {
635
// mark it as matched
636
pas->qlMatchedSinks.append(i->index);
638
// reset the volume to normal
639
pas->iRemainingOperations++;
640
pa_operation_unref(pa_context_set_sink_input_volume(c, i->index, &pas->qhVolumes[i->index].normal_volume, restore_volume_success_callback, pas));
643
// otherwise, save for matching at the end of iteration
645
QString restore_id = QLatin1String(pa_proplist_gets(i->proplist, "module-stream-restore.id"));
646
PulseAttenuation patt;
647
patt.index = i->index;
648
patt.normal_volume = i->volume;
649
pas->qhUnmatchedSinks[restore_id] = patt;
652
} else if (eol < 0) {
653
qWarning("PulseAudio: Sink input introspection error.");
656
// build a list of missing streams by iterating our active list
657
QHash<uint32_t, PulseAttenuation>::const_iterator it;
658
for (it = pas->qhVolumes.constBegin(); it != pas->qhVolumes.constEnd(); ++it) {
659
// skip if previously matched
660
if (pas->qlMatchedSinks.contains(it.key())) {
664
// check if the restore id matches. the only case where this would
665
// happen is if the application was reopened during attenuation.
666
if (pas->qhUnmatchedSinks.contains(it.value().stream_restore_id)) {
667
PulseAttenuation active_sink = pas->qhUnmatchedSinks[it.value().stream_restore_id];
668
// if the volume wasn't changed from our attenuation
669
if (pa_cvolume_equal(&active_sink.normal_volume, &it.value().attenuated_volume) != 0) {
670
// reset the volume to normal
671
pas->iRemainingOperations++;
672
pa_operation_unref(pa_context_set_sink_input_volume(c, active_sink.index, &it.value().normal_volume, restore_volume_success_callback, pas));
677
// at this point, we don't know what happened to the sink. add
678
// it to a list to check the stream restore database for.
679
pas->qhMissingSinks[it.value().stream_restore_id] = it.value();
683
pas->qlMatchedSinks.clear();
684
pas->qhUnmatchedSinks.clear();
685
pas->qhVolumes.clear();
687
// if we had missing sinks, check the stream restore database
688
// to see if we can find and update them.
689
if (pas->qhMissingSinks.count() > 0) {
690
pas->iRemainingOperations++;
691
pa_operation_unref(pa_ext_stream_restore_read(c, stream_restore_read_callback, pas));
694
// trigger the volume completion callback;
695
// necessary so that shutdown actions are called
696
restore_volume_success_callback(c, 1, pas);
700
void PulseAudioSystem::stream_restore_read_callback(pa_context *c, const pa_ext_stream_restore_info *i, int eol, void *userdata) {
701
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
704
QString name = QLatin1String(i->name);
706
// were we looking for this restoration?
707
if (pas->qhMissingSinks.contains(name)) {
708
// make sure it still has the volume we gave it
709
if (pa_cvolume_equal(&pas->qhMissingSinks[name].attenuated_volume, &i->volume) != 0) {
710
// update the stream restore record
711
pa_ext_stream_restore_info restore = *i;
712
restore.volume = pas->qhMissingSinks[name].normal_volume;
713
pas->iRemainingOperations++;
714
pa_operation_unref(pa_ext_stream_restore_write(c, PA_UPDATE_REPLACE, &restore, 1, 1, restore_volume_success_callback, pas));
717
pas->qhMissingSinks.remove(name);
720
} else if (eol < 0) {
721
qWarning("PulseAudio: Couldn't read stream restore database.");
722
pas->qhMissingSinks.clear();
725
// verify missing list is empty
726
if (pas->qhMissingSinks.count() > 0) {
727
qWarning("PulseAudio: Failed to match %d stream(s).", pas->qhMissingSinks.count());
728
pas->qhMissingSinks.clear();
731
// trigger the volume completion callback;
732
// necessary so that shutdown actions are called
733
restore_volume_success_callback(c, 1, pas);
737
void PulseAudioSystem::restore_volume_success_callback(pa_context *c, int success, void *userdata) {
738
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
740
pas->iRemainingOperations--;
742
// if there are no more pending volume adjustments and we're shutting down,
743
// let the main thread know
744
if (! pas->bRunning && pas->iRemainingOperations == 0) {
745
pas->qwcWait.wakeAll();
749
void PulseAudioSystem::query() {
750
bSourceDone=bSinkDone=bServerDone = false;
756
qhInput.insert(QString(), tr("Default Input"));
757
qhOutput.insert(QString(), tr("Default Output"));
758
pa_operation_unref(pa_context_get_server_info(pacContext, server_callback, this));
759
pa_operation_unref(pa_context_get_sink_info_list(pacContext, sink_callback, this));
760
pa_operation_unref(pa_context_get_source_info_list(pacContext, source_callback, this));
764
void PulseAudioSystem::setVolumes() {
765
// set attenuation state and volumes
767
// ensure the volume map is empty, otherwise it may be dangerous to change
768
if (qhVolumes.empty()) {
769
// set the new per-application volumes and store the old ones
770
pa_operation_unref(pa_context_get_sink_input_info_list(pacContext, volume_sink_input_list_callback, this));
772
// clear attenuation state and restore normal volumes
774
iRemainingOperations++;
775
pa_operation_unref(pa_context_get_sink_input_info_list(pacContext, restore_sink_input_list_callback, this));
779
void PulseAudioSystem::contextCallback(pa_context *c) {
780
Q_ASSERT(c == pacContext);
781
switch (pa_context_get_state(c)) {
782
case PA_CONTEXT_READY:
784
pa_operation_unref(pa_context_subscribe(pacContext, PA_SUBSCRIPTION_MASK_SOURCE, NULL, this));
785
pa_operation_unref(pa_context_subscribe(pacContext, PA_SUBSCRIPTION_MASK_SINK, NULL, this));
788
case PA_CONTEXT_TERMINATED:
789
qWarning("PulseAudio: Forcibly disconnected from PulseAudio");
791
case PA_CONTEXT_FAILED:
792
qWarning("PulseAudio: Connection failure: %s", pa_strerror(pa_context_errno(c)));
802
PulseAudioInputRegistrar::PulseAudioInputRegistrar() : AudioInputRegistrar(QLatin1String("PulseAudio"), 10) {
805
AudioInput *PulseAudioInputRegistrar::create() {
806
return new PulseAudioInput();
809
const QList<audioDevice> PulseAudioInputRegistrar::getDeviceChoices() {
810
QList<audioDevice> qlReturn;
812
QStringList qlInputDevs = pasys->qhInput.keys();
815
if (qlInputDevs.contains(g.s.qsPulseAudioInput)) {
816
qlInputDevs.removeAll(g.s.qsPulseAudioInput);
817
qlInputDevs.prepend(g.s.qsPulseAudioInput);
820
foreach(const QString &dev, qlInputDevs) {
821
qlReturn << audioDevice(pasys->qhInput.value(dev), dev);
827
void PulseAudioInputRegistrar::setDeviceChoice(const QVariant &choice, Settings &s) {
828
s.qsPulseAudioInput = choice.toString();
831
bool PulseAudioInputRegistrar::canEcho(const QString &osys) const {
832
return (osys == name);
835
PulseAudioOutputRegistrar::PulseAudioOutputRegistrar() : AudioOutputRegistrar(QLatin1String("PulseAudio"), 10) {
838
AudioOutput *PulseAudioOutputRegistrar::create() {
839
return new PulseAudioOutput();
842
const QList<audioDevice> PulseAudioOutputRegistrar::getDeviceChoices() {
843
QList<audioDevice> qlReturn;
845
QStringList qlOutputDevs = pasys->qhOutput.keys();
848
if (qlOutputDevs.contains(g.s.qsPulseAudioOutput)) {
849
qlOutputDevs.removeAll(g.s.qsPulseAudioOutput);
850
qlOutputDevs.prepend(g.s.qsPulseAudioOutput);
853
foreach(const QString &dev, qlOutputDevs) {
854
qlReturn << audioDevice(pasys->qhOutput.value(dev), dev);
860
void PulseAudioOutputRegistrar::setDeviceChoice(const QVariant &choice, Settings &s) {
861
s.qsPulseAudioOutput = choice.toString();
864
bool PulseAudioOutputRegistrar::canMuteOthers() const {
868
PulseAudioInput::PulseAudioInput() {
869
memset(&pssMic, 0, sizeof(pssMic));
870
memset(&pssEcho, 0, sizeof(pssEcho));
873
pasys->wakeup_lock();
876
PulseAudioInput::~PulseAudioInput() {
883
pasys->wakeup_lock();
886
void PulseAudioInput::run() {
889
qwcWait.wait(&qmMutex);
893
PulseAudioOutput::PulseAudioOutput() {
894
memset(&pss, 0, sizeof(pss));
895
memset(&pcm, 0, sizeof(pcm));
898
pasys->wakeup_lock();
901
PulseAudioOutput::~PulseAudioOutput() {
908
pasys->wakeup_lock();
911
void PulseAudioOutput::run() {
914
qwcWait.wait(&qmMutex);