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;
458
const void *data = NULL;
459
pa_stream_peek(s, &data, &length);
460
if (data == NULL && length > 0) {
461
qWarning("PulseAudio: pa_stream_peek reports no data at current read index.");
462
} else if (data == NULL && length == 0) {
463
qWarning("PulseAudio: pa_stream_peek reports empty memblockq.");
464
} else if (data == NULL || length == 0) {
465
qWarning("PulseAudio: invalid pa_stream_peek state encountered.");
469
AudioInputPtr ai = g.ai;
470
PulseAudioInput *pai = dynamic_cast<PulseAudioInput *>(ai.get());
477
const pa_sample_spec *pss = pa_stream_get_sample_spec(s);
479
if (s == pas->pasInput) {
480
if (!pa_sample_spec_equal(pss, &pai->pssMic)) {
482
pai->iMicFreq = pss->rate;
483
pai->iMicChannels = pss->channels;
484
if (pss->format == PA_SAMPLE_FLOAT32NE)
485
pai->eMicFormat = PulseAudioInput::SampleFloat;
487
pai->eMicFormat = PulseAudioInput::SampleShort;
488
pai->initializeMixer();
491
pai->addMic(data, length / pai->iMicSampleSize);
493
} else if (s == pas->pasSpeaker) {
494
if (!pa_sample_spec_equal(pss, &pai->pssEcho)) {
496
pai->iEchoFreq = pss->rate;
497
pai->iEchoChannels = pss->channels;
498
if (pss->format == PA_SAMPLE_FLOAT32NE)
499
pai->eEchoFormat = PulseAudioInput::SampleFloat;
501
pai->eEchoFormat = PulseAudioInput::SampleShort;
502
pai->initializeMixer();
505
pai->addEcho(data, length / pai->iEchoSampleSize);
512
void PulseAudioSystem::write_callback(pa_stream *s, size_t bytes, void *userdata) {
513
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
514
Q_ASSERT(s == pas->pasOutput);
516
AudioOutputPtr ao = g.ao;
517
PulseAudioOutput *pao = dynamic_cast<PulseAudioOutput *>(ao.get());
519
unsigned char buffer[bytes];
522
// Transitioning, but most likely transitions back, so just zero.
523
memset(buffer, 0, bytes);
524
pa_stream_write(s, buffer, bytes, NULL, 0, PA_SEEK_RELATIVE);
529
const pa_sample_spec *pss = pa_stream_get_sample_spec(s);
530
const pa_channel_map *pcm = pa_stream_get_channel_map(pas->pasOutput);
531
if (!pa_sample_spec_equal(pss, &pao->pss) || !pa_channel_map_equal(pcm, &pao->pcm)) {
534
if (pss->format == PA_SAMPLE_FLOAT32NE)
535
pao->eSampleFormat = PulseAudioOutput::SampleFloat;
537
pao->eSampleFormat = PulseAudioOutput::SampleShort;
538
pao->iMixerFreq = pss->rate;
539
pao->iChannels = pss->channels;
540
unsigned int chanmasks[pss->channels];
541
for (int i=0;i<pss->channels;++i) {
543
switch (pcm->map[i]) {
544
case PA_CHANNEL_POSITION_LEFT:
545
cm = SPEAKER_FRONT_LEFT;
547
case PA_CHANNEL_POSITION_RIGHT:
548
cm = SPEAKER_FRONT_RIGHT;
550
case PA_CHANNEL_POSITION_CENTER:
551
cm = SPEAKER_FRONT_CENTER;
553
case PA_CHANNEL_POSITION_REAR_LEFT:
554
cm = SPEAKER_BACK_LEFT;
556
case PA_CHANNEL_POSITION_REAR_RIGHT:
557
cm = SPEAKER_BACK_RIGHT;
559
case PA_CHANNEL_POSITION_REAR_CENTER:
560
cm = SPEAKER_BACK_CENTER;
562
case PA_CHANNEL_POSITION_LFE:
563
cm = SPEAKER_LOW_FREQUENCY;
565
case PA_CHANNEL_POSITION_SIDE_LEFT:
566
cm = SPEAKER_SIDE_LEFT;
568
case PA_CHANNEL_POSITION_SIDE_RIGHT:
569
cm = SPEAKER_SIDE_RIGHT;
571
case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER:
572
cm = SPEAKER_FRONT_LEFT_OF_CENTER;
574
case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER:
575
cm = SPEAKER_FRONT_RIGHT_OF_CENTER;
583
pao->initializeMixer(chanmasks);
586
const unsigned int iSampleSize = pao->iSampleSize;
587
const unsigned int samples = bytes / iSampleSize;
588
bool oldAttenuation = pas->bAttenuating;
590
// do we have some mixed output?
591
if (pao->mix(buffer, samples)) {
592
// attenuate if instructed to or it's in settings
593
pas->bAttenuating = (g.bAttenuateOthers || g.s.bAttenuateOthers);
596
memset(buffer, 0, bytes);
598
// attenuate if intructed to (self-activated)
599
pas->bAttenuating = g.bAttenuateOthers;
602
// if the attenuation state has changed
603
if (oldAttenuation != pas->bAttenuating) {
607
pa_stream_write(s, buffer, iSampleSize * samples, NULL, 0, PA_SEEK_RELATIVE);
610
void PulseAudioSystem::volume_sink_input_list_callback(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) {
611
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
614
// ensure we're not attenuating ourselves!
615
if (strcmp(i->name, mumble_sink_input) != 0) {
616
// create a new entry
617
PulseAttenuation patt;
618
patt.index = i->index;
619
patt.name = QLatin1String(i->name);
620
patt.stream_restore_id = QLatin1String(pa_proplist_gets(i->proplist, "module-stream-restore.id"));
621
patt.normal_volume = i->volume;
623
// calculate the attenuated volume
624
pa_volume_t adj = static_cast<pa_volume_t>(PA_VOLUME_NORM * g.s.fOtherVolume);
625
pa_sw_cvolume_multiply_scalar(&patt.attenuated_volume, &i->volume, adj);
627
// set it on the sink input
628
pa_operation_unref(pa_context_set_sink_input_volume(c, i->index, &patt.attenuated_volume, NULL, NULL));
631
pas->qhVolumes[i->index] = patt;
634
} else if (eol < 0) {
635
qWarning("PulseAudio: Sink input introspection error.");
639
void PulseAudioSystem::restore_sink_input_list_callback(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) {
640
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
643
// if we were tracking this specific sink previously
644
if (pas->qhVolumes.contains(i->index)) {
645
// and if it has the attenuated volume we applied to it
646
if (pa_cvolume_equal(&i->volume, &pas->qhVolumes[i->index].attenuated_volume) != 0) {
647
// mark it as matched
648
pas->qlMatchedSinks.append(i->index);
650
// reset the volume to normal
651
pas->iRemainingOperations++;
652
pa_operation_unref(pa_context_set_sink_input_volume(c, i->index, &pas->qhVolumes[i->index].normal_volume, restore_volume_success_callback, pas));
655
// otherwise, save for matching at the end of iteration
657
QString restore_id = QLatin1String(pa_proplist_gets(i->proplist, "module-stream-restore.id"));
658
PulseAttenuation patt;
659
patt.index = i->index;
660
patt.normal_volume = i->volume;
661
pas->qhUnmatchedSinks[restore_id] = patt;
664
} else if (eol < 0) {
665
qWarning("PulseAudio: Sink input introspection error.");
668
// build a list of missing streams by iterating our active list
669
QHash<uint32_t, PulseAttenuation>::const_iterator it;
670
for (it = pas->qhVolumes.constBegin(); it != pas->qhVolumes.constEnd(); ++it) {
671
// skip if previously matched
672
if (pas->qlMatchedSinks.contains(it.key())) {
676
// check if the restore id matches. the only case where this would
677
// happen is if the application was reopened during attenuation.
678
if (pas->qhUnmatchedSinks.contains(it.value().stream_restore_id)) {
679
PulseAttenuation active_sink = pas->qhUnmatchedSinks[it.value().stream_restore_id];
680
// if the volume wasn't changed from our attenuation
681
if (pa_cvolume_equal(&active_sink.normal_volume, &it.value().attenuated_volume) != 0) {
682
// reset the volume to normal
683
pas->iRemainingOperations++;
684
pa_operation_unref(pa_context_set_sink_input_volume(c, active_sink.index, &it.value().normal_volume, restore_volume_success_callback, pas));
689
// at this point, we don't know what happened to the sink. add
690
// it to a list to check the stream restore database for.
691
pas->qhMissingSinks[it.value().stream_restore_id] = it.value();
695
pas->qlMatchedSinks.clear();
696
pas->qhUnmatchedSinks.clear();
697
pas->qhVolumes.clear();
699
// if we had missing sinks, check the stream restore database
700
// to see if we can find and update them.
701
if (pas->qhMissingSinks.count() > 0) {
702
pas->iRemainingOperations++;
703
pa_operation_unref(pa_ext_stream_restore_read(c, stream_restore_read_callback, pas));
706
// trigger the volume completion callback;
707
// necessary so that shutdown actions are called
708
restore_volume_success_callback(c, 1, pas);
712
void PulseAudioSystem::stream_restore_read_callback(pa_context *c, const pa_ext_stream_restore_info *i, int eol, void *userdata) {
713
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
716
QString name = QLatin1String(i->name);
718
// were we looking for this restoration?
719
if (pas->qhMissingSinks.contains(name)) {
720
// make sure it still has the volume we gave it
721
if (pa_cvolume_equal(&pas->qhMissingSinks[name].attenuated_volume, &i->volume) != 0) {
722
// update the stream restore record
723
pa_ext_stream_restore_info restore = *i;
724
restore.volume = pas->qhMissingSinks[name].normal_volume;
725
pas->iRemainingOperations++;
726
pa_operation_unref(pa_ext_stream_restore_write(c, PA_UPDATE_REPLACE, &restore, 1, 1, restore_volume_success_callback, pas));
729
pas->qhMissingSinks.remove(name);
732
} else if (eol < 0) {
733
qWarning("PulseAudio: Couldn't read stream restore database.");
734
pas->qhMissingSinks.clear();
737
// verify missing list is empty
738
if (pas->qhMissingSinks.count() > 0) {
739
qWarning("PulseAudio: Failed to match %d stream(s).", pas->qhMissingSinks.count());
740
pas->qhMissingSinks.clear();
743
// trigger the volume completion callback;
744
// necessary so that shutdown actions are called
745
restore_volume_success_callback(c, 1, pas);
749
void PulseAudioSystem::restore_volume_success_callback(pa_context *c, int success, void *userdata) {
750
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
752
pas->iRemainingOperations--;
754
// if there are no more pending volume adjustments and we're shutting down,
755
// let the main thread know
756
if (! pas->bRunning && pas->iRemainingOperations == 0) {
757
pas->qwcWait.wakeAll();
761
void PulseAudioSystem::query() {
762
bSourceDone=bSinkDone=bServerDone = false;
768
qhInput.insert(QString(), tr("Default Input"));
769
qhOutput.insert(QString(), tr("Default Output"));
770
pa_operation_unref(pa_context_get_server_info(pacContext, server_callback, this));
771
pa_operation_unref(pa_context_get_sink_info_list(pacContext, sink_callback, this));
772
pa_operation_unref(pa_context_get_source_info_list(pacContext, source_callback, this));
776
void PulseAudioSystem::setVolumes() {
777
// set attenuation state and volumes
779
// ensure the volume map is empty, otherwise it may be dangerous to change
780
if (qhVolumes.empty()) {
781
// set the new per-application volumes and store the old ones
782
pa_operation_unref(pa_context_get_sink_input_info_list(pacContext, volume_sink_input_list_callback, this));
784
// clear attenuation state and restore normal volumes
786
iRemainingOperations++;
787
pa_operation_unref(pa_context_get_sink_input_info_list(pacContext, restore_sink_input_list_callback, this));
791
void PulseAudioSystem::contextCallback(pa_context *c) {
792
Q_ASSERT(c == pacContext);
793
switch (pa_context_get_state(c)) {
794
case PA_CONTEXT_READY:
796
pa_operation_unref(pa_context_subscribe(pacContext, PA_SUBSCRIPTION_MASK_SOURCE, NULL, this));
797
pa_operation_unref(pa_context_subscribe(pacContext, PA_SUBSCRIPTION_MASK_SINK, NULL, this));
800
case PA_CONTEXT_TERMINATED:
801
qWarning("PulseAudio: Forcibly disconnected from PulseAudio");
803
case PA_CONTEXT_FAILED:
804
qWarning("PulseAudio: Connection failure: %s", pa_strerror(pa_context_errno(c)));
814
PulseAudioInputRegistrar::PulseAudioInputRegistrar() : AudioInputRegistrar(QLatin1String("PulseAudio"), 10) {
817
AudioInput *PulseAudioInputRegistrar::create() {
818
return new PulseAudioInput();
821
const QList<audioDevice> PulseAudioInputRegistrar::getDeviceChoices() {
822
QList<audioDevice> qlReturn;
824
QStringList qlInputDevs = pasys->qhInput.keys();
827
if (qlInputDevs.contains(g.s.qsPulseAudioInput)) {
828
qlInputDevs.removeAll(g.s.qsPulseAudioInput);
829
qlInputDevs.prepend(g.s.qsPulseAudioInput);
832
foreach(const QString &dev, qlInputDevs) {
833
qlReturn << audioDevice(pasys->qhInput.value(dev), dev);
839
void PulseAudioInputRegistrar::setDeviceChoice(const QVariant &choice, Settings &s) {
840
s.qsPulseAudioInput = choice.toString();
843
bool PulseAudioInputRegistrar::canEcho(const QString &osys) const {
844
return (osys == name);
847
PulseAudioOutputRegistrar::PulseAudioOutputRegistrar() : AudioOutputRegistrar(QLatin1String("PulseAudio"), 10) {
850
AudioOutput *PulseAudioOutputRegistrar::create() {
851
return new PulseAudioOutput();
854
const QList<audioDevice> PulseAudioOutputRegistrar::getDeviceChoices() {
855
QList<audioDevice> qlReturn;
857
QStringList qlOutputDevs = pasys->qhOutput.keys();
860
if (qlOutputDevs.contains(g.s.qsPulseAudioOutput)) {
861
qlOutputDevs.removeAll(g.s.qsPulseAudioOutput);
862
qlOutputDevs.prepend(g.s.qsPulseAudioOutput);
865
foreach(const QString &dev, qlOutputDevs) {
866
qlReturn << audioDevice(pasys->qhOutput.value(dev), dev);
872
void PulseAudioOutputRegistrar::setDeviceChoice(const QVariant &choice, Settings &s) {
873
s.qsPulseAudioOutput = choice.toString();
876
bool PulseAudioOutputRegistrar::canMuteOthers() const {
880
PulseAudioInput::PulseAudioInput() {
881
memset(&pssMic, 0, sizeof(pssMic));
882
memset(&pssEcho, 0, sizeof(pssEcho));
885
pasys->wakeup_lock();
888
PulseAudioInput::~PulseAudioInput() {
895
pasys->wakeup_lock();
898
void PulseAudioInput::run() {
901
qwcWait.wait(&qmMutex);
905
PulseAudioOutput::PulseAudioOutput() {
906
memset(&pss, 0, sizeof(pss));
907
memset(&pcm, 0, sizeof(pcm));
910
pasys->wakeup_lock();
913
PulseAudioOutput::~PulseAudioOutput() {
920
pasys->wakeup_lock();
923
void PulseAudioOutput::run() {
926
qwcWait.wait(&qmMutex);