From 5b104e09c04fea7be7aa2b7d60b170e6a08764b3 Mon Sep 17 00:00:00 2001 From: Charles Ricketts Date: Wed, 6 May 2015 07:33:05 -0500 Subject: [PATCH] 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. --- src/mumble/AudioConfigDialog.cpp | 21 ++++++++++++++++++ src/mumble/AudioConfigDialog.h | 1 + src/mumble/AudioOutput.ui | 38 ++++++++++++++++++++++++++++++++ src/mumble/PulseAudio.cpp | 26 ++++++++++++++++++++++ src/mumble/PulseAudio.h | 2 ++ src/mumble/Settings.cpp | 6 +++++ src/mumble/Settings.h | 2 ++ 7 files changed, 96 insertions(+) diff --git a/src/mumble/AudioConfigDialog.cpp b/src/mumble/AudioConfigDialog.cpp index 06169a33a..e934bead1 100644 --- a/src/mumble/AudioConfigDialog.cpp +++ b/src/mumble/AudioConfigDialog.cpp @@ -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(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(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); } diff --git a/src/mumble/AudioConfigDialog.h b/src/mumble/AudioConfigDialog.h index a01fde395..a41615cd8 100644 --- a/src/mumble/AudioConfigDialog.h +++ b/src/mumble/AudioConfigDialog.h @@ -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 diff --git a/src/mumble/AudioOutput.ui b/src/mumble/AudioOutput.ui index 6cb71f9f1..4491c5023 100644 --- a/src/mumble/AudioOutput.ui +++ b/src/mumble/AudioOutput.ui @@ -333,6 +333,44 @@ + + + + + + + Advanced Attenuation Options + + + + + + If checked, Mumble will only attenuate applications that are using the same output source as Mumble + + + <b>Attenuate only applications using the same output as Mumble</b><br />If checked, applications that use a different output than Mumble will not be attenuated. + + + Only attenuate applications using the same output device + + + + + + + If checked, PulseAudio loopback modules will be attenuated + + + <b>Attenuate PulseAudio loopback modules</b><br />If loopback modules are linked to Mumble's output device/sink, they will also be attenuated. + + + Attenuate PulseAudio loopback modules + + + + + + diff --git a/src/mumble/PulseAudio.cpp b/src/mumble/PulseAudio.cpp index 7eea13483..bb0c8c888 100644 --- a/src/mumble/PulseAudio.cpp +++ b/src/mumble/PulseAudio.cpp @@ -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(userdata); + if (!i || eol) { + return; + } + + pas->iSinkId = i->index; +} + + void PulseAudioSystem::stream_callback(pa_stream *s, void *userdata) { PulseAudioSystem *pas = reinterpret_cast(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(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 diff --git a/src/mumble/PulseAudio.h b/src/mumble/PulseAudio.h index 65823bf18..1652ad500 100644 --- a/src/mumble/PulseAudio.h +++ b/src/mumble/PulseAudio.h @@ -74,6 +74,7 @@ class PulseAudioSystem : public QObject { bool bAttenuating; int iRemainingOperations; + int iSinkId; QHash qhVolumes; QList qlMatchedSinks; QHash 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); diff --git a/src/mumble/Settings.cpp b/src/mumble/Settings.cpp index 0c3a6e7cc..b0a17870d 100644 --- a/src/mumble/Settings.cpp +++ b/src/mumble/Settings.cpp @@ -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"); diff --git a/src/mumble/Settings.h b/src/mumble/Settings.h index 2a7ab5c92..ad9246114 100644 --- a/src/mumble/Settings.h +++ b/src/mumble/Settings.h @@ -204,6 +204,8 @@ struct Settings { bool bAttenuateOthersOnTalk; bool bAttenuateOthers; bool bAttenuateUsersOnPrioritySpeak; + bool bOnlyAttenuateSameOutput; + bool bAttenuateLoopbacks; int iOutputDelay; QString qsALSAInput, qsALSAOutput;