Skip to content
Permalink
Browse files

PulseAudio: Add options to disable attenuating applications on other …

…audio outputs and to disable attenuation of loopback modules.

Before this commit, Mumble indiscriminately attenuates applications.
A simple example: it would attenuate applications that were set to output
to HDMI -- something that wasn't always desirable. My HDMI wouldn't need
to be attenuated because I am able to physically distinguish between sound
coming from either my HDMI or my laptop and "tune in" accordingly.

A more advanced use case is my PulseAudio streaming setup. I create two
additional sinks: "stream" and "stream_spkr." The "stream" sink is used
when I want to send audio from an application to my stream but not to
my speakers/headset (i.e., background music or something). In this case,
before this commit Mumble would attenuate my program playing the
background music. If I am running Mumble off-stream (which I often am),
this leads the background music volume fluctuating for seemingly no reason.

The second sink, "stream_spkr" routes both to "stream" and my
speakers/headset via two module-loopbacks. In this way, anything I attach
to stream_spkr can be heard both by my viewers and myself.

The option to include attenuation on loopback modules is for advanced
configurations. Loopback modules are used to route audio, usually between
a sink monitor and a sink. Sometimes it might be beneficial to attenuate a
loopback module that goes into Mumble's sink. Sometimes, however it's
inconvenient such as in the case where a user moves an application to
another sink that uses a loopback to Mumble's sink in order to specifically
have Mumble ignore attenuation on that application. But, if the loopback
that carries the non-Mumble sink's audio back to Mumble's sink is attenuated
then there would still be indirect attenuation of that application. In my
configuration, an example is the loopback from stream_spkr.monitor to the
physical speakers.

Only PulseAusio is supported in this patch, but this functionality could
potentially be extended to other audio systems.
  • Loading branch information...
Githlar authored and mkrautz committed May 6, 2015
1 parent dc9ea2c commit 5b104e09c04fea7be7aa2b7d60b170e6a08764b3
@@ -454,6 +454,13 @@ void AudioOutputDialog::load(const Settings &r) {
loadCheckBox(qcbAttenuateOthersOnTalk, r.bAttenuateOthersOnTalk);
loadCheckBox(qcbAttenuateOthers, r.bAttenuateOthers);
loadCheckBox(qcbAttenuateUsersOnPrioritySpeak, r.bAttenuateUsersOnPrioritySpeak);
loadCheckBox(qcbOnlyAttenuateSameOutput, r.bOnlyAttenuateSameOutput);
loadCheckBox(qcbAttenuateLoopbacks, r.bAttenuateLoopbacks);
if (AudioOutputRegistrar::current == QLatin1String("PulseAudio")) {
qgbAdvancedAttenuation->setVisible(true);
} else {
qgbAdvancedAttenuation->setVisible(false);
}
loadSlider(qsJitter, r.iJitterBufferSize);
loadComboBox(qcbLoopback, r.lmLoopMode);
loadSlider(qsPacketDelay, static_cast<int>(r.dMaxPacketDelay));
@@ -467,6 +474,7 @@ void AudioOutputDialog::load(const Settings &r) {

qsOtherVolume->setEnabled(r.bAttenuateOthersOnTalk || r.bAttenuateOthers);
qlOtherVolume->setEnabled(r.bAttenuateOthersOnTalk || r.bAttenuateOthers);
qcbAttenuateLoopbacks->setEnabled(r.bOnlyAttenuateSameOutput);
}

void AudioOutputDialog::save() const {
@@ -475,6 +483,8 @@ void AudioOutputDialog::save() const {
s.fOtherVolume = 1.0f - (static_cast<float>(qsOtherVolume->value()) / 100.0f);
s.bAttenuateOthersOnTalk = qcbAttenuateOthersOnTalk->isChecked();
s.bAttenuateOthers = qcbAttenuateOthers->isChecked();
s.bOnlyAttenuateSameOutput = qcbOnlyAttenuateSameOutput->isChecked();
s.bAttenuateLoopbacks = qcbAttenuateLoopbacks->isChecked();
s.bAttenuateUsersOnPrioritySpeak = qcbAttenuateUsersOnPrioritySpeak->isChecked();
s.iJitterBufferSize = qsJitter->value();
s.qsAudioOutput = qcbSystem->currentText();
@@ -522,6 +532,11 @@ void AudioOutputDialog::on_qcbSystem_currentIndexChanged(int) {
bool canmute = aor->canMuteOthers();
qsOtherVolume->setEnabled(canmute);
qcbAttenuateOthersOnTalk->setEnabled(canmute);
if (aor->name == QLatin1String("PulseAudio")) {
qgbAdvancedAttenuation->setVisible(true);
} else {
qgbAdvancedAttenuation->setVisible(false);
}
qcbAttenuateOthers->setEnabled(canmute);
qlOtherVolume->setEnabled(canmute);

@@ -605,10 +620,16 @@ void AudioOutputDialog::on_qcbAttenuateOthersOnTalk_clicked(bool checked) {
bool b = qcbAttenuateOthers->isChecked() || checked;
qsOtherVolume->setEnabled(b);
qlOtherVolume->setEnabled(b);
qgbAdvancedAttenuation->setEnabled(b);
}

void AudioOutputDialog::on_qcbAttenuateOthers_clicked(bool checked) {
bool b = qcbAttenuateOthersOnTalk->isChecked() || checked;
qsOtherVolume->setEnabled(b);
qlOtherVolume->setEnabled(b);
qgbAdvancedAttenuation->setEnabled(b);
}

void AudioOutputDialog::on_qcbOnlyAttenuateSameOutput_clicked(bool checked) {
qcbAttenuateLoopbacks->setEnabled(checked);
}
@@ -103,6 +103,7 @@ class AudioOutputDialog : public ConfigWidget, public Ui::AudioOutput {
void on_qcbPositional_stateChanged(int);
void on_qcbAttenuateOthersOnTalk_clicked(bool checked);
void on_qcbAttenuateOthers_clicked(bool checked);
void on_qcbOnlyAttenuateSameOutput_clicked(bool checked);
};

#endif
@@ -333,6 +333,44 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="qgbAdvancedAttenuation">
<property name="toolTip">
<string/>
</property>
<property name="title">
<string>Advanced Attenuation Options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QCheckBox" name="qcbOnlyAttenuateSameOutput">
<property name="toolTip">
<string>If checked, Mumble will only attenuate applications that are using the same output source as Mumble</string>
</property>
<property name="whatsThis">
<string>&lt;b&gt;Attenuate only applications using the same output as Mumble&lt;/b&gt;&lt;br /&gt;If checked, applications that use a different output than Mumble will not be attenuated.</string>
</property>
<property name="text">
<string>Only attenuate applications using the same output device</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="qcbAttenuateLoopbacks">
<property name="toolTip">
<string>If checked, PulseAudio loopback modules will be attenuated</string>
</property>
<property name="whatsThis">
<string>&lt;b&gt;Attenuate PulseAudio loopback modules&lt;/b&gt;&lt;br /&gt;If loopback modules are linked to Mumble's output device/sink, they will also be attenuated.</string>
</property>
<property name="text">
<string>Attenuate PulseAudio loopback modules</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="qgbPrioritySpeaker">
<property name="title">
@@ -106,6 +106,7 @@ PulseAudioSystem::PulseAudioSystem() {
bAttenuating = false;
iRemainingOperations = 0;
bPulseIsGood = false;
iSinkId = -1;

pam = pa_threaded_mainloop_new();
pa_mainloop_api *api = pa_threaded_mainloop_get_api(pam);
@@ -229,6 +230,7 @@ void PulseAudioSystem::eventCallback(pa_mainloop_api *api, pa_defer_event *) {
if (do_stop) {
qWarning("PulseAudio: Stopping output");
pa_stream_disconnect(pasOutput);
iSinkId = -1;
} else if (do_start) {
qWarning("PulseAudio: Starting output: %s", qPrintable(odev));
pa_buffer_attr buff;
@@ -245,6 +247,7 @@ void PulseAudioSystem::eventCallback(pa_mainloop_api *api, pa_defer_event *) {
qsOutputCache = odev;

pa_stream_connect_playback(pasOutput, qPrintable(odev), &buff, PA_STREAM_ADJUST_LATENCY, NULL, NULL);
pa_context_get_sink_info_by_name(pacContext, qPrintable(odev), sink_info_callback, this);
}
}

@@ -438,6 +441,16 @@ void PulseAudioSystem::server_callback(pa_context *, const pa_server_info *i, vo
pas->wakeup();
}

void PulseAudioSystem::sink_info_callback(pa_context *, const pa_sink_info *i, int eol, void *userdata) {
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
if (!i || eol) {
return;
}

pas->iSinkId = i->index;
}


void PulseAudioSystem::stream_callback(pa_stream *s, void *userdata) {
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
switch (pa_stream_get_state(s)) {
@@ -614,6 +627,19 @@ void PulseAudioSystem::volume_sink_input_list_callback(pa_context *c, const pa_s
PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);

if (eol == 0) {
// If we're using the default of "enable attenuation on all ouputs" and output from an application is loopbacked,
// both the loopback and the application will be attenuated leading to double attenuation.
if (!g.s.bOnlyAttenuateSameOutput && pas->iSinkId > -1 && !strcmp(i->driver, "module-loopback.c")) {
return;
}
// If we're not attenuating different sinks and the input is not on this sink, don't attenuate. Or,
// if the input is a loopback module and connected to Mumble's sink, also ignore it (loopbacks are used to connect
// sinks). An attenuated loopback means an indirect application attenuation.
if (g.s.bOnlyAttenuateSameOutput && pas->iSinkId > -1) {
if (int(i->sink) != pas->iSinkId || (int(i->sink) == pas->iSinkId && !strcmp(i->driver, "module-loopback.c") && !g.s.bAttenuateLoopbacks)) {
return;
}
}
// ensure we're not attenuating ourselves!
if (strcmp(i->name, mumble_sink_input) != 0) {
// create a new entry
@@ -74,6 +74,7 @@ class PulseAudioSystem : public QObject {

bool bAttenuating;
int iRemainingOperations;
int iSinkId;
QHash<uint32_t, PulseAttenuation> qhVolumes;
QList<uint32_t> qlMatchedSinks;
QHash<QString, PulseAttenuation> qhUnmatchedSinks;
@@ -85,6 +86,7 @@ class PulseAudioSystem : public QObject {
static void sink_callback(pa_context *c, const pa_sink_info *i, int eol, void *userdata);
static void source_callback(pa_context *c, const pa_source_info *i, int eol, void *userdata);
static void server_callback(pa_context *c, const pa_server_info *i, void *userdata);
static void sink_info_callback(pa_context *c, const pa_sink_info *i, int eol, void *userdata);
static void stream_callback(pa_stream *s, void *userdata);
static void read_callback(pa_stream *s, size_t bytes, void *userdata);
static void write_callback(pa_stream *s, size_t bytes, void *userdata);
@@ -256,6 +256,8 @@ Settings::Settings() {
bAttenuateOthersOnTalk = false;
bAttenuateOthers = true;
bAttenuateUsersOnPrioritySpeak = false;
bOnlyAttenuateSameOutput = false;
bAttenuateLoopbacks = false;
iMinLoudness = 1000;
iVoiceHold = 50;
iJitterBufferSize = 1;
@@ -570,6 +572,8 @@ void Settings::load(QSettings* settings_ptr) {
SAVELOAD(bAttenuateOthers, "audio/attenuateothers");
SAVELOAD(bAttenuateOthersOnTalk, "audio/attenuateothersontalk");
SAVELOAD(bAttenuateUsersOnPrioritySpeak, "audio/attenuateusersonpriorityspeak");
SAVELOAD(bOnlyAttenuateSameOutput, "audio/onlyattenuatesameoutput");
SAVELOAD(bAttenuateLoopbacks, "audio/attenuateloopbacks");
LOADENUM(vsVAD, "audio/vadsource");
SAVELOAD(fVADmin, "audio/vadmin");
SAVELOAD(fVADmax, "audio/vadmax");
@@ -870,6 +874,8 @@ void Settings::save() {
SAVELOAD(bAttenuateOthers, "audio/attenuateothers");
SAVELOAD(bAttenuateOthersOnTalk, "audio/attenuateothersontalk");
SAVELOAD(bAttenuateUsersOnPrioritySpeak, "audio/attenuateusersonpriorityspeak");
SAVELOAD(bOnlyAttenuateSameOutput, "audio/onlyattenuatesameoutput");
SAVELOAD(bAttenuateLoopbacks, "audio/attenuateloopbacks");
SAVELOAD(vsVAD, "audio/vadsource");
SAVELOAD(fVADmin, "audio/vadmin");
SAVELOAD(fVADmax, "audio/vadmax");
@@ -204,6 +204,8 @@ struct Settings {
bool bAttenuateOthersOnTalk;
bool bAttenuateOthers;
bool bAttenuateUsersOnPrioritySpeak;
bool bOnlyAttenuateSameOutput;
bool bAttenuateLoopbacks;
int iOutputDelay;

QString qsALSAInput, qsALSAOutput;

0 comments on commit 5b104e0

Please sign in to comment.
You can’t perform that action at this time.