From 37392be4171303db08a4842b7882b4cb758a8f8d Mon Sep 17 00:00:00 2001 From: Nicky Dasmijn Date: Tue, 9 Apr 2024 20:26:06 +0200 Subject: Update Linux media handling (#1146) * Enable CEF browser for Linux * Disable the update for Linux, we don't have that one right now * Update build_linux.yaml We need libpulse-dev for volume_catcher Linux * Add linux_volum_catcher* files * Enable OpenAL for Linux-ReleaseOS * Linux: Update OpenAL * Update SDL2 * Add libsndio-dev to the dependencies. * Update CEF to an official LL version * Remove dupe of emoji_shortcodes * Reording autobuild does because it can and wants to * Linux: Disable NDOF for the time being. After updating the ndof 3P needs to be rebuilt and we do not have a fresh one from LL yet. Forcefully undefine LIB_NDOF, it gets defined in the build variables no matter if it is safe to define. * Remove wrestling with mutliarch and LIBGL_DRIVERS_PATH * Remove tcmalloc snippet, tcmalloc is a very faint bad dream of the past * Putting out a warning this viewer ran on a x64 arch and then suggesting to install 32 bit compat packages makes no sense at all * CEF resources need to be in lib * It;'s okay to warn about missing plugins * Linux: CEF keyboard handling * Remove old gstreamer 0.10 implementation * Linux DSO loading always had been very peculiar due to macro magic. At least now it is peculiar shared magic with only one implementation. * Remove -fPIC. We get that one from LL_BUILD * /proc/cpuinfo is not reliable to detrmine the max CPU clock. Try to determine this by reading "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq". Only if this fails go back to /proc/cpuinfo * Cleanup * Cleanup common linker and compiler flags, make it more obvious which flags are for which OS/compiler * Switch to correct plugin file * Install libpulse-dev for volume catcher. * And the runner needs libsndio-dev as well. * check for runner.os=='linux'. matrix.os is the full name of the image (limux-large). --- indra/media_plugins/cef/CMakeLists.txt | 28 +- indra/media_plugins/cef/linux_volume_catcher.cpp | 424 +++++++++++++++++++++ .../cef/linux_volume_catcher_pa_syms.inc | 21 + .../cef/linux_volume_catcher_paglib_syms.inc | 6 + indra/media_plugins/cef/media_plugin_cef.cpp | 34 +- 5 files changed, 501 insertions(+), 12 deletions(-) create mode 100755 indra/media_plugins/cef/linux_volume_catcher.cpp create mode 100755 indra/media_plugins/cef/linux_volume_catcher_pa_syms.inc create mode 100755 indra/media_plugins/cef/linux_volume_catcher_paglib_syms.inc (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/CMakeLists.txt b/indra/media_plugins/cef/CMakeLists.txt index 410778114d..bbd2eb222a 100644 --- a/indra/media_plugins/cef/CMakeLists.txt +++ b/indra/media_plugins/cef/CMakeLists.txt @@ -10,18 +10,10 @@ include(Linking) include(PluginAPI) include(CEFPlugin) - +include(GLIB) ### media_plugin_cef -if(NOT ADDRESS_SIZE EQUAL 32) - if(WINDOWS) - ##add_definitions(/FIXED:NO) - else(WINDOWS) # not windows therefore gcc LINUX and DARWIN - add_definitions(-fPIC) - endif(WINDOWS) -endif(NOT ADDRESS_SIZE EQUAL 32) - set(media_plugin_cef_SOURCE_FILES media_plugin_cef.cpp ) @@ -32,8 +24,21 @@ set(media_plugin_cef_HEADER_FILES # Select which VolumeCatcher implementation to use if (LINUX) - message(FATAL_ERROR "CEF plugin has been enabled for a Linux compile.\n" - " Please create a volume_catcher implementation for this platform.") + foreach( PULSE_FILE pulse/introspect.h pulse/context.h pulse/subscribe.h pulse/glib-mainloop.h ) + find_path( PULSE_FILE_${PULSE_FILE}_FOUND ${PULSE_FILE} NO_CACHE) + if( NOT PULSE_FILE_${PULSE_FILE}_FOUND ) + message( "Looking for ${PULSE_FILE} ... not found") + message( FATAL_ERROR "Pulse header not found" ) + else() + message( "Looking for ${PULSE_FILE} ... found") + endif() + endforeach() + message( "Building with linux volume catcher" ) + set(LINUX_VOLUME_CATCHER linux_volume_catcher.cpp) + + list(APPEND media_plugin_cef_SOURCE_FILES ${LINUX_VOLUME_CATCHER}) + set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--build-id -Wl,-rpath,'$ORIGIN:$ORIGIN/../../lib'") + list(APPEND media_plugin_cef_LINK_LIBRARIES llwindow ) elseif (DARWIN) list(APPEND media_plugin_cef_SOURCE_FILES mac_volume_catcher_null.cpp) find_library(CORESERVICES_LIBRARY CoreServices) @@ -60,6 +65,7 @@ add_library(media_plugin_cef target_link_libraries(media_plugin_cef media_plugin_base ll::cef + ll::glib_headers ) if (WINDOWS) diff --git a/indra/media_plugins/cef/linux_volume_catcher.cpp b/indra/media_plugins/cef/linux_volume_catcher.cpp new file mode 100755 index 0000000000..9e6fe461a6 --- /dev/null +++ b/indra/media_plugins/cef/linux_volume_catcher.cpp @@ -0,0 +1,424 @@ +/** + * @file linux_volume_catcher.cpp + * @brief A Linux-specific, PulseAudio-specific hack to detect and volume-adjust new audio sources + * + * @cond + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + * @endcond + */ + +/* + The high-level design is as follows: + 1) Connect to the PulseAudio daemon + 2) Watch for the creation of new audio players connecting to the daemon (this includes ALSA clients running on the PulseAudio emulation layer, such as Flash plugins) + 3) Examine any new audio player's PID to see if it belongs to our own process + 4) If so, tell PA to adjust the volume of that audio player ('sink input' in PA parlance) + 5) Keep a list of all living audio players that we care about, adjust the volumes of all of them when we get a new setVolume() call + */ + +#include "linden_common.h" + +#include "volume_catcher.h" +#include +#include +#include +extern "C" { +#include +#include + +#include +#include +#include +#include // There's no special reason why we want the *glib* PA mainloop, but the generic polling implementation seems broken. + +#include "apr_pools.h" +#include "apr_dso.h" +} + +#include "media_plugin_base.h" + +SymbolGrabber gSymbolGrabber; + +#include "linux_volume_catcher_pa_syms.inc" +#include "linux_volume_catcher_paglib_syms.inc" +//////////////////////////////////////////////////// + +// PulseAudio requires a chain of callbacks with C linkage +extern "C" { + void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *i, int eol, void *userdata); + void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata); + void callback_context_state(pa_context *context, void *userdata); +} + +class VolumeCatcherImpl +{ +public: + VolumeCatcherImpl(); + ~VolumeCatcherImpl(); + + void setVolume(F32 volume); + void pump(void); + + // for internal use - can't be private because used from our C callbacks + + bool loadsyms(std::string pulse_dso_name); + void init(); + void cleanup(); + + void update_all_volumes(F32 volume); + void update_index_volume(U32 index, F32 volume); + void connected_okay(); + + std::set mSinkInputIndices; + std::map mSinkInputNumChannels; + F32 mDesiredVolume; + pa_glib_mainloop *mMainloop; + pa_context *mPAContext; + bool mConnected; + bool mGotSyms; +}; + +VolumeCatcherImpl::VolumeCatcherImpl() + : mDesiredVolume(0.0f), + mMainloop(nullptr), + mPAContext(nullptr), + mConnected(false), + mGotSyms(false) +{ + init(); +} + +VolumeCatcherImpl::~VolumeCatcherImpl() +{ + cleanup(); +} + +bool VolumeCatcherImpl::loadsyms(std::string pulse_dso_name) +{ + //return grab_pa_syms({pulse_dso_name}); + return gSymbolGrabber.grabSymbols( { pulse_dso_name }) ; +} + +void VolumeCatcherImpl::init() +{ + // try to be as defensive as possible because PA's interface is a + // bit fragile and (for our purposes) we'd rather simply not function + // than crash + + // we cheat and rely upon libpulse-mainloop-glib.so.0 to pull-in + // libpulse.so.0 - this isn't a great assumption, and the two DSOs should + // probably be loaded separately. Our Linux DSO framework needs refactoring, + // we do this sort of thing a lot with practically identical logic... + mGotSyms = loadsyms("libpulse-mainloop-glib.so.0"); + if (!mGotSyms) return; + + mMainloop = llpa_glib_mainloop_new(g_main_context_default()); + + if (mMainloop) + { + pa_mainloop_api *api = llpa_glib_mainloop_get_api(mMainloop); + + if (api) + { + pa_proplist *proplist = llpa_proplist_new(); + + if (proplist) + { + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "multimedia-player"); + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "com.secondlife.viewer.mediaplugvoladjust"); + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "SL Plugin Volume Adjuster"); + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, "1"); + + // plain old pa_context_new() is broken! + mPAContext = llpa_context_new_with_proplist(api, nullptr, proplist); + + llpa_proplist_free(proplist); + } + } + } + + // Now we've set up a PA context and mainloop, try connecting the + // PA context to a PA daemon. + if (mPAContext) + { + llpa_context_set_state_callback(mPAContext, callback_context_state, this); + pa_context_flags_t cflags = (pa_context_flags)0; // maybe add PA_CONTEXT_NOAUTOSPAWN? + if (llpa_context_connect(mPAContext, nullptr, cflags, nullptr) >= 0) + { + // Okay! We haven't definitely connected, but we + // haven't definitely failed yet. + } + else + { + // Failed to connect to PA manager... we'll leave + // things like that. Perhaps we should try again later. + } + } +} + +void VolumeCatcherImpl::cleanup() +{ + mConnected = false; + + if (mGotSyms && mPAContext) + { + llpa_context_disconnect(mPAContext); + llpa_context_unref(mPAContext); + } + + mPAContext = nullptr; + + if (mGotSyms && mMainloop) + llpa_glib_mainloop_free(mMainloop); + + mMainloop = nullptr; +} + +void VolumeCatcherImpl::setVolume(F32 volume) +{ + mDesiredVolume = volume; + + if (!mGotSyms) return; + + if (mConnected && mPAContext) + { + update_all_volumes(mDesiredVolume); + } + + pump(); +} + +void VolumeCatcherImpl::pump() +{ + gboolean may_block = FALSE; + g_main_context_iteration(g_main_context_default(), may_block); +} + +void VolumeCatcherImpl::connected_okay() +{ + pa_operation *op; + + // fetch global list of existing sinkinputs + if ((op = llpa_context_get_sink_input_info_list(mPAContext, + callback_discovered_sinkinput, + this))) + { + llpa_operation_unref(op); + } + + // subscribe to future global sinkinput changes + llpa_context_set_subscribe_callback(mPAContext, + callback_subscription_alert, + this); + if ((op = llpa_context_subscribe(mPAContext, (pa_subscription_mask_t) + (PA_SUBSCRIPTION_MASK_SINK_INPUT), + nullptr, nullptr))) + { + llpa_operation_unref(op); + } +} + +void VolumeCatcherImpl::update_all_volumes(F32 volume) +{ + for (std::set::iterator it = mSinkInputIndices.begin(); + it != mSinkInputIndices.end(); ++it) + { + update_index_volume(*it, volume); + } +} + +void VolumeCatcherImpl::update_index_volume(U32 index, F32 volume) +{ + static pa_cvolume cvol; + llpa_cvolume_set(&cvol, mSinkInputNumChannels[index], + llpa_sw_volume_from_linear(volume)); + + pa_context *c = mPAContext; + uint32_t idx = index; + const pa_cvolume *cvolumep = &cvol; + pa_context_success_cb_t cb = nullptr; // okay as null + void *userdata = nullptr; // okay as null + + pa_operation *op; + if ((op = llpa_context_set_sink_input_volume(c, idx, cvolumep, cb, userdata))) + llpa_operation_unref(op); +} + +pid_t getParentPid( pid_t aPid ) +{ + std::stringstream strm; + strm << "/proc/" << aPid << "/status"; + std::ifstream in{ strm.str() }; + + if( !in.is_open() ) + return 0; + + pid_t res {0}; + while( !in.eof() && res == 0 ) + { + std::string line; + line.resize( 1024, 0 ); + in.getline( &line[0], line.length() ); + + auto i = line.find( "PPid:" ); + + if( i == std::string::npos ) + continue; + + char const *pIn = line.c_str() + 5; // Skip over pid; + while( *pIn != 0 && isspace( *pIn ) ) + ++pIn; + + if( *pIn ) + res = atoll( pIn ); + } + return res; +} + + +bool isPluginPid( pid_t aPid ) +{ + auto myPid = getpid(); + + do + { + if( aPid == myPid ) + return true; + aPid = getParentPid( aPid ); + } while( aPid > 1 ); + + return false; +} + +void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *sii, int eol, void *userdata) +{ + VolumeCatcherImpl *impl = dynamic_cast((VolumeCatcherImpl*)userdata); + llassert(impl); + + if (0 == eol) + { + pa_proplist *proplist = sii->proplist; + pid_t sinkpid = atoll(llpa_proplist_gets(proplist, PA_PROP_APPLICATION_PROCESS_ID)); + + if (isPluginPid( sinkpid )) // does the discovered sinkinput belong to this process? + { + bool is_new = (impl->mSinkInputIndices.find(sii->index) == impl->mSinkInputIndices.end()); + + impl->mSinkInputIndices.insert(sii->index); + impl->mSinkInputNumChannels[sii->index] = sii->channel_map.channels; + + if (is_new) + { + // new! + impl->update_index_volume(sii->index, impl->mDesiredVolume); + } + else + { + // seen it already, do nothing. + } + } + } +} + +void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata) +{ + VolumeCatcherImpl *impl = dynamic_cast((VolumeCatcherImpl*)userdata); + llassert(impl); + + switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) + { + case PA_SUBSCRIPTION_EVENT_SINK_INPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) + { + // forget this sinkinput, if we were caring about it + impl->mSinkInputIndices.erase(index); + impl->mSinkInputNumChannels.erase(index); + } + else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) + { + // ask for more info about this new sinkinput + pa_operation *op; + if ((op = llpa_context_get_sink_input_info(impl->mPAContext, index, callback_discovered_sinkinput, impl))) + { + llpa_operation_unref(op); + } + } + else + { + // property change on this sinkinput - we don't care. + } + break; + + default:; + } +} + +void callback_context_state(pa_context *context, void *userdata) +{ + VolumeCatcherImpl *impl = dynamic_cast((VolumeCatcherImpl*)userdata); + llassert(impl); + + switch (llpa_context_get_state(context)) + { + case PA_CONTEXT_READY: + impl->mConnected = true; + impl->connected_okay(); + break; + case PA_CONTEXT_TERMINATED: + impl->mConnected = false; + break; + case PA_CONTEXT_FAILED: + impl->mConnected = false; + break; + default:; + } +} + +///////////////////////////////////////////////////// + +VolumeCatcher::VolumeCatcher() +{ + pimpl = new VolumeCatcherImpl(); +} + +VolumeCatcher::~VolumeCatcher() +{ + delete pimpl; + pimpl = nullptr; +} + +void VolumeCatcher::setVolume(F32 volume) +{ + llassert(pimpl); + pimpl->setVolume(volume); +} + +void VolumeCatcher::setPan(F32 pan) +{ + // TODO: implement this (if possible) +} + +void VolumeCatcher::pump() +{ + llassert(pimpl); + pimpl->pump(); +} diff --git a/indra/media_plugins/cef/linux_volume_catcher_pa_syms.inc b/indra/media_plugins/cef/linux_volume_catcher_pa_syms.inc new file mode 100755 index 0000000000..4533362e61 --- /dev/null +++ b/indra/media_plugins/cef/linux_volume_catcher_pa_syms.inc @@ -0,0 +1,21 @@ +// required symbols to grab +LL_GRAB_SYM(true, pa_context_connect, int, pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api) +LL_GRAB_SYM(true, pa_context_disconnect, void, pa_context *c) +LL_GRAB_SYM(true, pa_context_get_sink_input_info, pa_operation*, pa_context *c, uint32_t idx, pa_sink_input_info_cb_t cb, void *userdata) +LL_GRAB_SYM(true, pa_context_get_sink_input_info_list, pa_operation*, pa_context *c, pa_sink_input_info_cb_t cb, void *userdata) +LL_GRAB_SYM(true, pa_context_get_state, pa_context_state_t, pa_context *c) +LL_GRAB_SYM(true, pa_context_new_with_proplist, pa_context*, pa_mainloop_api *mainloop, const char *name, pa_proplist *proplist) +LL_GRAB_SYM(true, pa_context_set_sink_input_volume, pa_operation*, pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) +LL_GRAB_SYM(true, pa_context_set_state_callback, void, pa_context *c, pa_context_notify_cb_t cb, void *userdata) +LL_GRAB_SYM(true, pa_context_set_subscribe_callback, void, pa_context *c, pa_context_subscribe_cb_t cb, void *userdata) +LL_GRAB_SYM(true, pa_context_subscribe, pa_operation*, pa_context *c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void *userdata) +LL_GRAB_SYM(true, pa_context_unref, void, pa_context *c) +LL_GRAB_SYM(true, pa_cvolume_set, pa_cvolume*, pa_cvolume *a, unsigned channels, pa_volume_t v) +LL_GRAB_SYM(true, pa_operation_unref, void, pa_operation *o) +LL_GRAB_SYM(true, pa_proplist_free, void, pa_proplist* p) +LL_GRAB_SYM(true, pa_proplist_gets, const char*, pa_proplist *p, const char *key) +LL_GRAB_SYM(true, pa_proplist_new, pa_proplist*, void) +LL_GRAB_SYM(true, pa_proplist_sets, int, pa_proplist *p, const char *key, const char *value) +LL_GRAB_SYM(true, pa_sw_volume_from_linear, pa_volume_t, double v) + +// optional symbols to grab diff --git a/indra/media_plugins/cef/linux_volume_catcher_paglib_syms.inc b/indra/media_plugins/cef/linux_volume_catcher_paglib_syms.inc new file mode 100755 index 0000000000..5fba60c188 --- /dev/null +++ b/indra/media_plugins/cef/linux_volume_catcher_paglib_syms.inc @@ -0,0 +1,6 @@ +// required symbols to grab +LL_GRAB_SYM(true, pa_glib_mainloop_free, void, pa_glib_mainloop* g) +LL_GRAB_SYM(true, pa_glib_mainloop_get_api, pa_mainloop_api*, pa_glib_mainloop* g) +LL_GRAB_SYM(true, pa_glib_mainloop_new, pa_glib_mainloop *, GMainContext *c) + +// optional symbols to grab diff --git a/indra/media_plugins/cef/media_plugin_cef.cpp b/indra/media_plugins/cef/media_plugin_cef.cpp index 43d3a32e64..60d91753d0 100644 --- a/indra/media_plugins/cef/media_plugin_cef.cpp +++ b/indra/media_plugins/cef/media_plugin_cef.cpp @@ -886,7 +886,7 @@ void MediaPluginCEF::receiveMessage(const char* message_string) keyEvent(key_event, native_key_data); -#elif LL_WINDOWS +#else std::string event = message_in.getValue("event"); LLSD native_key_data = message_in.getValueLLSD("native_key_data"); @@ -1050,6 +1050,28 @@ void MediaPluginCEF::keyEvent(dullahan::EKeyEvent key_event, LLSD native_key_dat mCEFLib->nativeKeyboardEventWin(msg, wparam, lparam); #endif + +#if LL_LINUX + + uint32_t native_virtual_key = (uint32_t)(native_key_data["virtual_key"].asInteger()); // this is actually the SDL event.key.keysym.sym; + uint32_t native_virtual_key_win = (uint32_t)(native_key_data["virtual_key_win"].asInteger()); + uint32_t native_modifiers = (uint32_t)(native_key_data["modifiers"].asInteger()); + + // only for non-printable keysyms, the actual text input is done in unicodeInput() below + if (native_virtual_key <= 0x1b || native_virtual_key >= 0x7f) + { + // set keypad flag, not sure if this even does anything + bool keypad = false; + if (native_virtual_key_win >= 0x60 && native_virtual_key_win <= 0x6f) + { + keypad = true; + } + + // yes, we send native_virtual_key_win twice because native_virtual_key breaks it + mCEFLib->nativeKeyboardEventSDL2(key_event, native_virtual_key, native_modifiers, keypad); + } + +#endif // LL_LINUX }; void MediaPluginCEF::unicodeInput(std::string event, LLSD native_key_data = LLSD::emptyMap()) @@ -1080,6 +1102,16 @@ void MediaPluginCEF::unicodeInput(std::string event, LLSD native_key_data = LLSD U64 lparam = ll_U32_from_sd(native_key_data["l_param"]); mCEFLib->nativeKeyboardEventWin(msg, wparam, lparam); #endif + +#if LL_LINUX + + uint32_t native_scan_code = (uint32_t)(native_key_data["sdl_sym"].asInteger()); + uint32_t native_virtual_key = (uint32_t)(native_key_data["virtual_key"].asInteger()); + uint32_t native_modifiers = (uint32_t)(native_key_data["modifiers"].asInteger()); + + mCEFLib->nativeKeyboardEvent(dullahan::KE_KEY_DOWN, native_scan_code, native_virtual_key, native_modifiers); + +#endif // LL_LINUX }; //////////////////////////////////////////////////////////////////////////////// -- cgit v1.2.3 From 1e5b77547fd4a70888ab24834bfdb634206cafc3 Mon Sep 17 00:00:00 2001 From: Maki Date: Wed, 17 Apr 2024 23:06:29 -0400 Subject: Use PipeWire for Linux volume catcher --- indra/media_plugins/cef/CMakeLists.txt | 26 +- indra/media_plugins/cef/linux_volume_catcher.cpp | 424 ------------------- .../media_plugins/cef/linux_volume_catcher_pa.cpp | 424 +++++++++++++++++++ .../media_plugins/cef/linux_volume_catcher_pw.cpp | 451 +++++++++++++++++++++ .../cef/linux_volume_catcher_pw_syms.inc | 22 + 5 files changed, 912 insertions(+), 435 deletions(-) delete mode 100755 indra/media_plugins/cef/linux_volume_catcher.cpp create mode 100755 indra/media_plugins/cef/linux_volume_catcher_pa.cpp create mode 100755 indra/media_plugins/cef/linux_volume_catcher_pw.cpp create mode 100644 indra/media_plugins/cef/linux_volume_catcher_pw_syms.inc (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/CMakeLists.txt b/indra/media_plugins/cef/CMakeLists.txt index bbd2eb222a..9e129f2185 100644 --- a/indra/media_plugins/cef/CMakeLists.txt +++ b/indra/media_plugins/cef/CMakeLists.txt @@ -24,17 +24,21 @@ set(media_plugin_cef_HEADER_FILES # Select which VolumeCatcher implementation to use if (LINUX) - foreach( PULSE_FILE pulse/introspect.h pulse/context.h pulse/subscribe.h pulse/glib-mainloop.h ) - find_path( PULSE_FILE_${PULSE_FILE}_FOUND ${PULSE_FILE} NO_CACHE) - if( NOT PULSE_FILE_${PULSE_FILE}_FOUND ) - message( "Looking for ${PULSE_FILE} ... not found") - message( FATAL_ERROR "Pulse header not found" ) - else() - message( "Looking for ${PULSE_FILE} ... found") - endif() - endforeach() - message( "Building with linux volume catcher" ) - set(LINUX_VOLUME_CATCHER linux_volume_catcher.cpp) + # foreach( PULSE_FILE pulse/introspect.h pulse/context.h pulse/subscribe.h pulse/glib-mainloop.h ) + # find_path( PULSE_FILE_${PULSE_FILE}_FOUND ${PULSE_FILE} NO_CACHE) + # if( NOT PULSE_FILE_${PULSE_FILE}_FOUND ) + # message( "Looking for ${PULSE_FILE} ... not found") + # message( FATAL_ERROR "Pulse header not found" ) + # else() + # message( "Looking for ${PULSE_FILE} ... found") + # endif() + # endforeach() + + include(FindPipeWire) + include_directories(SYSTEM ${PIPEWIRE_INCLUDE_DIRS} ${SPA_INCLUDE_DIRS}) + + message( "Building with Linux volume catcher using PipeWire" ) + set(LINUX_VOLUME_CATCHER linux_volume_catcher_pw.cpp) list(APPEND media_plugin_cef_SOURCE_FILES ${LINUX_VOLUME_CATCHER}) set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--build-id -Wl,-rpath,'$ORIGIN:$ORIGIN/../../lib'") diff --git a/indra/media_plugins/cef/linux_volume_catcher.cpp b/indra/media_plugins/cef/linux_volume_catcher.cpp deleted file mode 100755 index 9e6fe461a6..0000000000 --- a/indra/media_plugins/cef/linux_volume_catcher.cpp +++ /dev/null @@ -1,424 +0,0 @@ -/** - * @file linux_volume_catcher.cpp - * @brief A Linux-specific, PulseAudio-specific hack to detect and volume-adjust new audio sources - * - * @cond - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - * @endcond - */ - -/* - The high-level design is as follows: - 1) Connect to the PulseAudio daemon - 2) Watch for the creation of new audio players connecting to the daemon (this includes ALSA clients running on the PulseAudio emulation layer, such as Flash plugins) - 3) Examine any new audio player's PID to see if it belongs to our own process - 4) If so, tell PA to adjust the volume of that audio player ('sink input' in PA parlance) - 5) Keep a list of all living audio players that we care about, adjust the volumes of all of them when we get a new setVolume() call - */ - -#include "linden_common.h" - -#include "volume_catcher.h" -#include -#include -#include -extern "C" { -#include -#include - -#include -#include -#include -#include // There's no special reason why we want the *glib* PA mainloop, but the generic polling implementation seems broken. - -#include "apr_pools.h" -#include "apr_dso.h" -} - -#include "media_plugin_base.h" - -SymbolGrabber gSymbolGrabber; - -#include "linux_volume_catcher_pa_syms.inc" -#include "linux_volume_catcher_paglib_syms.inc" -//////////////////////////////////////////////////// - -// PulseAudio requires a chain of callbacks with C linkage -extern "C" { - void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *i, int eol, void *userdata); - void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata); - void callback_context_state(pa_context *context, void *userdata); -} - -class VolumeCatcherImpl -{ -public: - VolumeCatcherImpl(); - ~VolumeCatcherImpl(); - - void setVolume(F32 volume); - void pump(void); - - // for internal use - can't be private because used from our C callbacks - - bool loadsyms(std::string pulse_dso_name); - void init(); - void cleanup(); - - void update_all_volumes(F32 volume); - void update_index_volume(U32 index, F32 volume); - void connected_okay(); - - std::set mSinkInputIndices; - std::map mSinkInputNumChannels; - F32 mDesiredVolume; - pa_glib_mainloop *mMainloop; - pa_context *mPAContext; - bool mConnected; - bool mGotSyms; -}; - -VolumeCatcherImpl::VolumeCatcherImpl() - : mDesiredVolume(0.0f), - mMainloop(nullptr), - mPAContext(nullptr), - mConnected(false), - mGotSyms(false) -{ - init(); -} - -VolumeCatcherImpl::~VolumeCatcherImpl() -{ - cleanup(); -} - -bool VolumeCatcherImpl::loadsyms(std::string pulse_dso_name) -{ - //return grab_pa_syms({pulse_dso_name}); - return gSymbolGrabber.grabSymbols( { pulse_dso_name }) ; -} - -void VolumeCatcherImpl::init() -{ - // try to be as defensive as possible because PA's interface is a - // bit fragile and (for our purposes) we'd rather simply not function - // than crash - - // we cheat and rely upon libpulse-mainloop-glib.so.0 to pull-in - // libpulse.so.0 - this isn't a great assumption, and the two DSOs should - // probably be loaded separately. Our Linux DSO framework needs refactoring, - // we do this sort of thing a lot with practically identical logic... - mGotSyms = loadsyms("libpulse-mainloop-glib.so.0"); - if (!mGotSyms) return; - - mMainloop = llpa_glib_mainloop_new(g_main_context_default()); - - if (mMainloop) - { - pa_mainloop_api *api = llpa_glib_mainloop_get_api(mMainloop); - - if (api) - { - pa_proplist *proplist = llpa_proplist_new(); - - if (proplist) - { - llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "multimedia-player"); - llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "com.secondlife.viewer.mediaplugvoladjust"); - llpa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "SL Plugin Volume Adjuster"); - llpa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, "1"); - - // plain old pa_context_new() is broken! - mPAContext = llpa_context_new_with_proplist(api, nullptr, proplist); - - llpa_proplist_free(proplist); - } - } - } - - // Now we've set up a PA context and mainloop, try connecting the - // PA context to a PA daemon. - if (mPAContext) - { - llpa_context_set_state_callback(mPAContext, callback_context_state, this); - pa_context_flags_t cflags = (pa_context_flags)0; // maybe add PA_CONTEXT_NOAUTOSPAWN? - if (llpa_context_connect(mPAContext, nullptr, cflags, nullptr) >= 0) - { - // Okay! We haven't definitely connected, but we - // haven't definitely failed yet. - } - else - { - // Failed to connect to PA manager... we'll leave - // things like that. Perhaps we should try again later. - } - } -} - -void VolumeCatcherImpl::cleanup() -{ - mConnected = false; - - if (mGotSyms && mPAContext) - { - llpa_context_disconnect(mPAContext); - llpa_context_unref(mPAContext); - } - - mPAContext = nullptr; - - if (mGotSyms && mMainloop) - llpa_glib_mainloop_free(mMainloop); - - mMainloop = nullptr; -} - -void VolumeCatcherImpl::setVolume(F32 volume) -{ - mDesiredVolume = volume; - - if (!mGotSyms) return; - - if (mConnected && mPAContext) - { - update_all_volumes(mDesiredVolume); - } - - pump(); -} - -void VolumeCatcherImpl::pump() -{ - gboolean may_block = FALSE; - g_main_context_iteration(g_main_context_default(), may_block); -} - -void VolumeCatcherImpl::connected_okay() -{ - pa_operation *op; - - // fetch global list of existing sinkinputs - if ((op = llpa_context_get_sink_input_info_list(mPAContext, - callback_discovered_sinkinput, - this))) - { - llpa_operation_unref(op); - } - - // subscribe to future global sinkinput changes - llpa_context_set_subscribe_callback(mPAContext, - callback_subscription_alert, - this); - if ((op = llpa_context_subscribe(mPAContext, (pa_subscription_mask_t) - (PA_SUBSCRIPTION_MASK_SINK_INPUT), - nullptr, nullptr))) - { - llpa_operation_unref(op); - } -} - -void VolumeCatcherImpl::update_all_volumes(F32 volume) -{ - for (std::set::iterator it = mSinkInputIndices.begin(); - it != mSinkInputIndices.end(); ++it) - { - update_index_volume(*it, volume); - } -} - -void VolumeCatcherImpl::update_index_volume(U32 index, F32 volume) -{ - static pa_cvolume cvol; - llpa_cvolume_set(&cvol, mSinkInputNumChannels[index], - llpa_sw_volume_from_linear(volume)); - - pa_context *c = mPAContext; - uint32_t idx = index; - const pa_cvolume *cvolumep = &cvol; - pa_context_success_cb_t cb = nullptr; // okay as null - void *userdata = nullptr; // okay as null - - pa_operation *op; - if ((op = llpa_context_set_sink_input_volume(c, idx, cvolumep, cb, userdata))) - llpa_operation_unref(op); -} - -pid_t getParentPid( pid_t aPid ) -{ - std::stringstream strm; - strm << "/proc/" << aPid << "/status"; - std::ifstream in{ strm.str() }; - - if( !in.is_open() ) - return 0; - - pid_t res {0}; - while( !in.eof() && res == 0 ) - { - std::string line; - line.resize( 1024, 0 ); - in.getline( &line[0], line.length() ); - - auto i = line.find( "PPid:" ); - - if( i == std::string::npos ) - continue; - - char const *pIn = line.c_str() + 5; // Skip over pid; - while( *pIn != 0 && isspace( *pIn ) ) - ++pIn; - - if( *pIn ) - res = atoll( pIn ); - } - return res; -} - - -bool isPluginPid( pid_t aPid ) -{ - auto myPid = getpid(); - - do - { - if( aPid == myPid ) - return true; - aPid = getParentPid( aPid ); - } while( aPid > 1 ); - - return false; -} - -void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *sii, int eol, void *userdata) -{ - VolumeCatcherImpl *impl = dynamic_cast((VolumeCatcherImpl*)userdata); - llassert(impl); - - if (0 == eol) - { - pa_proplist *proplist = sii->proplist; - pid_t sinkpid = atoll(llpa_proplist_gets(proplist, PA_PROP_APPLICATION_PROCESS_ID)); - - if (isPluginPid( sinkpid )) // does the discovered sinkinput belong to this process? - { - bool is_new = (impl->mSinkInputIndices.find(sii->index) == impl->mSinkInputIndices.end()); - - impl->mSinkInputIndices.insert(sii->index); - impl->mSinkInputNumChannels[sii->index] = sii->channel_map.channels; - - if (is_new) - { - // new! - impl->update_index_volume(sii->index, impl->mDesiredVolume); - } - else - { - // seen it already, do nothing. - } - } - } -} - -void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata) -{ - VolumeCatcherImpl *impl = dynamic_cast((VolumeCatcherImpl*)userdata); - llassert(impl); - - switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) - { - case PA_SUBSCRIPTION_EVENT_SINK_INPUT: - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) - { - // forget this sinkinput, if we were caring about it - impl->mSinkInputIndices.erase(index); - impl->mSinkInputNumChannels.erase(index); - } - else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) - { - // ask for more info about this new sinkinput - pa_operation *op; - if ((op = llpa_context_get_sink_input_info(impl->mPAContext, index, callback_discovered_sinkinput, impl))) - { - llpa_operation_unref(op); - } - } - else - { - // property change on this sinkinput - we don't care. - } - break; - - default:; - } -} - -void callback_context_state(pa_context *context, void *userdata) -{ - VolumeCatcherImpl *impl = dynamic_cast((VolumeCatcherImpl*)userdata); - llassert(impl); - - switch (llpa_context_get_state(context)) - { - case PA_CONTEXT_READY: - impl->mConnected = true; - impl->connected_okay(); - break; - case PA_CONTEXT_TERMINATED: - impl->mConnected = false; - break; - case PA_CONTEXT_FAILED: - impl->mConnected = false; - break; - default:; - } -} - -///////////////////////////////////////////////////// - -VolumeCatcher::VolumeCatcher() -{ - pimpl = new VolumeCatcherImpl(); -} - -VolumeCatcher::~VolumeCatcher() -{ - delete pimpl; - pimpl = nullptr; -} - -void VolumeCatcher::setVolume(F32 volume) -{ - llassert(pimpl); - pimpl->setVolume(volume); -} - -void VolumeCatcher::setPan(F32 pan) -{ - // TODO: implement this (if possible) -} - -void VolumeCatcher::pump() -{ - llassert(pimpl); - pimpl->pump(); -} diff --git a/indra/media_plugins/cef/linux_volume_catcher_pa.cpp b/indra/media_plugins/cef/linux_volume_catcher_pa.cpp new file mode 100755 index 0000000000..80f58954d4 --- /dev/null +++ b/indra/media_plugins/cef/linux_volume_catcher_pa.cpp @@ -0,0 +1,424 @@ +/** + * @file linux_volume_catcher_pa.cpp + * @brief A Linux-specific, PulseAudio-specific hack to detect and volume-adjust new audio sources + * + * @cond + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + * @endcond + */ + +/* + The high-level design is as follows: + 1) Connect to the PulseAudio daemon + 2) Watch for the creation of new audio players connecting to the daemon (this includes ALSA clients running on the PulseAudio emulation layer, such as Flash plugins) + 3) Examine any new audio player's PID to see if it belongs to our own process + 4) If so, tell PA to adjust the volume of that audio player ('sink input' in PA parlance) + 5) Keep a list of all living audio players that we care about, adjust the volumes of all of them when we get a new setVolume() call + */ + +#include "linden_common.h" + +#include "volume_catcher.h" +#include +#include +#include +extern "C" { +#include +#include + +#include +#include +#include +#include // There's no special reason why we want the *glib* PA mainloop, but the generic polling implementation seems broken. + +#include "apr_pools.h" +#include "apr_dso.h" +} + +#include "media_plugin_base.h" + +SymbolGrabber gSymbolGrabber; + +#include "linux_volume_catcher_pa_syms.inc" +#include "linux_volume_catcher_paglib_syms.inc" +//////////////////////////////////////////////////// + +// PulseAudio requires a chain of callbacks with C linkage +extern "C" { + void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *i, int eol, void *userdata); + void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata); + void callback_context_state(pa_context *context, void *userdata); +} + +class VolumeCatcherImpl +{ +public: + VolumeCatcherImpl(); + ~VolumeCatcherImpl(); + + void setVolume(F32 volume); + void pump(void); + + // for internal use - can't be private because used from our C callbacks + + bool loadsyms(std::string pulse_dso_name); + void init(); + void cleanup(); + + void update_all_volumes(F32 volume); + void update_index_volume(U32 index, F32 volume); + void connected_okay(); + + std::set mSinkInputIndices; + std::map mSinkInputNumChannels; + F32 mDesiredVolume; + pa_glib_mainloop *mMainloop; + pa_context *mPAContext; + bool mConnected; + bool mGotSyms; +}; + +VolumeCatcherImpl::VolumeCatcherImpl() + : mDesiredVolume(0.0f), + mMainloop(nullptr), + mPAContext(nullptr), + mConnected(false), + mGotSyms(false) +{ + init(); +} + +VolumeCatcherImpl::~VolumeCatcherImpl() +{ + cleanup(); +} + +bool VolumeCatcherImpl::loadsyms(std::string pulse_dso_name) +{ + //return grab_pa_syms({pulse_dso_name}); + return gSymbolGrabber.grabSymbols( { pulse_dso_name }) ; +} + +void VolumeCatcherImpl::init() +{ + // try to be as defensive as possible because PA's interface is a + // bit fragile and (for our purposes) we'd rather simply not function + // than crash + + // we cheat and rely upon libpulse-mainloop-glib.so.0 to pull-in + // libpulse.so.0 - this isn't a great assumption, and the two DSOs should + // probably be loaded separately. Our Linux DSO framework needs refactoring, + // we do this sort of thing a lot with practically identical logic... + mGotSyms = loadsyms("libpulse-mainloop-glib.so.0"); + if (!mGotSyms) return; + + mMainloop = llpa_glib_mainloop_new(g_main_context_default()); + + if (mMainloop) + { + pa_mainloop_api *api = llpa_glib_mainloop_get_api(mMainloop); + + if (api) + { + pa_proplist *proplist = llpa_proplist_new(); + + if (proplist) + { + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "multimedia-player"); + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "com.secondlife.viewer.mediaplugvoladjust"); + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "SL Plugin Volume Adjuster"); + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, "1"); + + // plain old pa_context_new() is broken! + mPAContext = llpa_context_new_with_proplist(api, nullptr, proplist); + + llpa_proplist_free(proplist); + } + } + } + + // Now we've set up a PA context and mainloop, try connecting the + // PA context to a PA daemon. + if (mPAContext) + { + llpa_context_set_state_callback(mPAContext, callback_context_state, this); + pa_context_flags_t cflags = (pa_context_flags)0; // maybe add PA_CONTEXT_NOAUTOSPAWN? + if (llpa_context_connect(mPAContext, nullptr, cflags, nullptr) >= 0) + { + // Okay! We haven't definitely connected, but we + // haven't definitely failed yet. + } + else + { + // Failed to connect to PA manager... we'll leave + // things like that. Perhaps we should try again later. + } + } +} + +void VolumeCatcherImpl::cleanup() +{ + mConnected = false; + + if (mGotSyms && mPAContext) + { + llpa_context_disconnect(mPAContext); + llpa_context_unref(mPAContext); + } + + mPAContext = nullptr; + + if (mGotSyms && mMainloop) + llpa_glib_mainloop_free(mMainloop); + + mMainloop = nullptr; +} + +void VolumeCatcherImpl::setVolume(F32 volume) +{ + mDesiredVolume = volume; + + if (!mGotSyms) return; + + if (mConnected && mPAContext) + { + update_all_volumes(mDesiredVolume); + } + + pump(); +} + +void VolumeCatcherImpl::pump() +{ + gboolean may_block = FALSE; + g_main_context_iteration(g_main_context_default(), may_block); +} + +void VolumeCatcherImpl::connected_okay() +{ + pa_operation *op; + + // fetch global list of existing sinkinputs + if ((op = llpa_context_get_sink_input_info_list(mPAContext, + callback_discovered_sinkinput, + this))) + { + llpa_operation_unref(op); + } + + // subscribe to future global sinkinput changes + llpa_context_set_subscribe_callback(mPAContext, + callback_subscription_alert, + this); + if ((op = llpa_context_subscribe(mPAContext, (pa_subscription_mask_t) + (PA_SUBSCRIPTION_MASK_SINK_INPUT), + nullptr, nullptr))) + { + llpa_operation_unref(op); + } +} + +void VolumeCatcherImpl::update_all_volumes(F32 volume) +{ + for (std::set::iterator it = mSinkInputIndices.begin(); + it != mSinkInputIndices.end(); ++it) + { + update_index_volume(*it, volume); + } +} + +void VolumeCatcherImpl::update_index_volume(U32 index, F32 volume) +{ + static pa_cvolume cvol; + llpa_cvolume_set(&cvol, mSinkInputNumChannels[index], + llpa_sw_volume_from_linear(volume)); + + pa_context *c = mPAContext; + uint32_t idx = index; + const pa_cvolume *cvolumep = &cvol; + pa_context_success_cb_t cb = nullptr; // okay as null + void *userdata = nullptr; // okay as null + + pa_operation *op; + if ((op = llpa_context_set_sink_input_volume(c, idx, cvolumep, cb, userdata))) + llpa_operation_unref(op); +} + +pid_t getParentPid( pid_t aPid ) +{ + std::stringstream strm; + strm << "/proc/" << aPid << "/status"; + std::ifstream in{ strm.str() }; + + if( !in.is_open() ) + return 0; + + pid_t res {0}; + while( !in.eof() && res == 0 ) + { + std::string line; + line.resize( 1024, 0 ); + in.getline( &line[0], line.length() ); + + auto i = line.find( "PPid:" ); + + if( i == std::string::npos ) + continue; + + char const *pIn = line.c_str() + 5; // Skip over pid; + while( *pIn != 0 && isspace( *pIn ) ) + ++pIn; + + if( *pIn ) + res = atoll( pIn ); + } + return res; +} + + +bool isPluginPid( pid_t aPid ) +{ + auto myPid = getpid(); + + do + { + if( aPid == myPid ) + return true; + aPid = getParentPid( aPid ); + } while( aPid > 1 ); + + return false; +} + +void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *sii, int eol, void *userdata) +{ + VolumeCatcherImpl *impl = dynamic_cast((VolumeCatcherImpl*)userdata); + llassert(impl); + + if (0 == eol) + { + pa_proplist *proplist = sii->proplist; + pid_t sinkpid = atoll(llpa_proplist_gets(proplist, PA_PROP_APPLICATION_PROCESS_ID)); + + if (isPluginPid( sinkpid )) // does the discovered sinkinput belong to this process? + { + bool is_new = (impl->mSinkInputIndices.find(sii->index) == impl->mSinkInputIndices.end()); + + impl->mSinkInputIndices.insert(sii->index); + impl->mSinkInputNumChannels[sii->index] = sii->channel_map.channels; + + if (is_new) + { + // new! + impl->update_index_volume(sii->index, impl->mDesiredVolume); + } + else + { + // seen it already, do nothing. + } + } + } +} + +void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata) +{ + VolumeCatcherImpl *impl = dynamic_cast((VolumeCatcherImpl*)userdata); + llassert(impl); + + switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) + { + case PA_SUBSCRIPTION_EVENT_SINK_INPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) + { + // forget this sinkinput, if we were caring about it + impl->mSinkInputIndices.erase(index); + impl->mSinkInputNumChannels.erase(index); + } + else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) + { + // ask for more info about this new sinkinput + pa_operation *op; + if ((op = llpa_context_get_sink_input_info(impl->mPAContext, index, callback_discovered_sinkinput, impl))) + { + llpa_operation_unref(op); + } + } + else + { + // property change on this sinkinput - we don't care. + } + break; + + default:; + } +} + +void callback_context_state(pa_context *context, void *userdata) +{ + VolumeCatcherImpl *impl = dynamic_cast((VolumeCatcherImpl*)userdata); + llassert(impl); + + switch (llpa_context_get_state(context)) + { + case PA_CONTEXT_READY: + impl->mConnected = true; + impl->connected_okay(); + break; + case PA_CONTEXT_TERMINATED: + impl->mConnected = false; + break; + case PA_CONTEXT_FAILED: + impl->mConnected = false; + break; + default:; + } +} + +///////////////////////////////////////////////////// + +VolumeCatcher::VolumeCatcher() +{ + pimpl = new VolumeCatcherImpl(); +} + +VolumeCatcher::~VolumeCatcher() +{ + delete pimpl; + pimpl = nullptr; +} + +void VolumeCatcher::setVolume(F32 volume) +{ + llassert(pimpl); + pimpl->setVolume(volume); +} + +void VolumeCatcher::setPan(F32 pan) +{ + // TODO: implement this (if possible) +} + +void VolumeCatcher::pump() +{ + llassert(pimpl); + pimpl->pump(); +} diff --git a/indra/media_plugins/cef/linux_volume_catcher_pw.cpp b/indra/media_plugins/cef/linux_volume_catcher_pw.cpp new file mode 100755 index 0000000000..b7171d10f9 --- /dev/null +++ b/indra/media_plugins/cef/linux_volume_catcher_pw.cpp @@ -0,0 +1,451 @@ +/** + * @file linux_volume_catcher_pw.cpp + * @brief A Linux-specific, PipeWire-specific hack to detect and volume-adjust new audio sources + * + * @cond + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + * @endcond + */ + +/* + The high-level design is as follows: + 1) Connect to the PipeWire daemon + 2) Find all existing and new audio nodes + 3) Examine PID and parent PID's to see if it belongs to our process + 4) If so, tell PipeWire to adjust the volume of that node + 5) Keep a list of all audio nodes and adjust when we setVolume() + */ + +#include "linden_common.h" + +#include "volume_catcher.h" + +#include +#include + +extern "C" { +#include +#include +#include + +#include "apr_pools.h" +#include "apr_dso.h" +} + +#include "media_plugin_base.h" + +SymbolGrabber gSymbolGrabber; + +#include "linux_volume_catcher_pw_syms.inc" + +//////////////////////////////////////////////////// + +class VolumeCatcherImpl +{ +public: + VolumeCatcherImpl(); + ~VolumeCatcherImpl(); + + bool loadsyms(std::string pw_dso_name); + void init(); + void cleanup(); + + void pwLock(); + void pwUnlock(); + + void setVolume(F32 volume); + // void setPan(F32 pan); + + void handleRegistryEventGlobal( + uint32_t id, uint32_t permissions, const char* type, + uint32_t version, const struct spa_dict* props + ); + + class ChildNode + { + public: + bool mActive = false; + + pw_proxy* mProxy = nullptr; + spa_hook mNodeListener {}; + spa_hook mProxyListener {}; + VolumeCatcherImpl* mImpl = nullptr; + + void updateVolume(); + void destroy(); + }; + + bool mGotSyms = false; + + F32 mVolume = 1.0f; // max by default + // F32 mPan = 0.0f; // center + + pw_thread_loop* mThreadLoop; + pw_context* mContext; + pw_core* mCore; + pw_registry* mRegistry; + spa_hook mRegistryListener; + + std::unordered_set mChildNodes; + std::mutex mChildNodesMutex; +}; + +VolumeCatcherImpl::VolumeCatcherImpl() +{ + init(); +} + +VolumeCatcherImpl::~VolumeCatcherImpl() +{ + cleanup(); +} + +// static void debugClear() +// { +// auto file = fopen("/home/maki/git/firestorm-viewer/log.txt", "w"); +// fprintf(file, "\n"); +// fclose(file); +// } + +// static void debugPrint(const char* format, ...) +// { +// va_list args; +// va_start(args, format); +// auto file = fopen("/home/maki/git/firestorm-viewer/log.txt", "a"); +// vfprintf(file, format, args); +// fclose(file); +// va_end(args); +// } + +static void registryEventGlobal( + void *data, uint32_t id, uint32_t permissions, const char *type, + uint32_t version, const struct spa_dict *props +) +{ + static_cast(data)->handleRegistryEventGlobal( + id, permissions, type, version, props + ); +} + +static const struct pw_registry_events REGISTRY_EVENTS = { + .version = PW_VERSION_REGISTRY_EVENTS, + .global = registryEventGlobal, +}; + +bool VolumeCatcherImpl::loadsyms(std::string pw_dso_name) +{ + return gSymbolGrabber.grabSymbols({ pw_dso_name }); +} + +void VolumeCatcherImpl::init() +{ + // debugClear(); + // debugPrint("init\n"); + + mGotSyms = loadsyms("libpipewire-0.3.so.0"); + if (!mGotSyms) return; + + llpw_init(NULL, NULL); + + mThreadLoop = llpw_thread_loop_new("SL Plugin Volume Adjuster", NULL); + if (!mThreadLoop) return; + + pwLock(); + + mContext = llpw_context_new( + llpw_thread_loop_get_loop(mThreadLoop), NULL, 0 + ); + if (!mContext) return pwUnlock(); + + mCore = llpw_context_connect(mContext, NULL, 0); + if (!mCore) return pwUnlock(); + + mRegistry = pw_core_get_registry(mCore, PW_VERSION_REGISTRY, 0); + + // debugPrint("got registry\n"); + + spa_zero(mRegistryListener); + + pw_registry_add_listener( + mRegistry, &mRegistryListener, ®ISTRY_EVENTS, this + ); + + llpw_thread_loop_start(mThreadLoop); + + pwUnlock(); + + // debugPrint("started thread loop\n"); +} + +void VolumeCatcherImpl::cleanup() +{ + mChildNodesMutex.lock(); + for (auto* childNode : mChildNodes) { + childNode->destroy(); + } + mChildNodes.clear(); + mChildNodesMutex.unlock(); + + pwLock(); + if (mRegistry) llpw_proxy_destroy((struct pw_proxy*)mRegistry); + spa_zero(mRegistryListener); + if (mCore) llpw_core_disconnect(mCore); + if (mContext) llpw_context_destroy(mContext); + pwUnlock(); + + if (!mThreadLoop) return; + + llpw_thread_loop_stop(mThreadLoop); + llpw_thread_loop_destroy(mThreadLoop); + + // debugPrint("cleanup done\n"); +} + +void VolumeCatcherImpl::pwLock() { + if (!mThreadLoop) return; + llpw_thread_loop_lock(mThreadLoop); +} + +void VolumeCatcherImpl::pwUnlock() { + if (!mThreadLoop) return; + llpw_thread_loop_unlock(mThreadLoop); +} + +// #define INV_LERP(a, b, v) (v - a) / (b - a) + +// #include + +void VolumeCatcherImpl::ChildNode::updateVolume() +{ + if (!mActive) return; + + F32 volume = std::clamp(mImpl->mVolume, 0.0f, 1.0f); + // F32 pan = std::clamp(mImpl->mPan, -1.0f, 1.0f); + + // debugClear(); + // struct timeval time; + // gettimeofday(&time, NULL); + // double t = (double)time.tv_sec + (double)(time.tv_usec / 1000) / 1000; + // debugPrint("time is %f\n", t); + // F32 pan = std::sin(t * 2.0d); + // debugPrint("pan is %f\n", pan); + + // uint32_t channels = 2; + // float volumes[channels]; + // volumes[1] = INV_LERP(-1.0f, 0.0f, pan) * volume; // left + // volumes[0] = INV_LERP(1.0f, 0.0f, pan) * volume; // right + + uint32_t channels = 1; + float volumes[channels]; + volumes[0] = volume; + + uint8_t buffer[512]; + + spa_pod_builder builder; + spa_pod_builder_init(&builder, buffer, sizeof(buffer)); + + spa_pod_frame frame; + spa_pod_builder_push_object(&builder, &frame, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); + spa_pod_builder_prop(&builder, SPA_PROP_channelVolumes, 0); + spa_pod_builder_array(&builder, sizeof(float), SPA_TYPE_Float, channels, volumes); + spa_pod* pod = static_cast(spa_pod_builder_pop(&builder, &frame)); + + mImpl->pwLock(); + pw_node_set_param(mProxy, SPA_PARAM_Props, 0, pod); + mImpl->pwUnlock(); +} + +void VolumeCatcherImpl::ChildNode::destroy() +{ + if (!mActive) return; + mActive = false; + + mImpl->mChildNodesMutex.lock(); + mImpl->mChildNodes.erase(this); + mImpl->mChildNodesMutex.unlock(); + + spa_hook_remove(&mNodeListener); + spa_hook_remove(&mProxyListener); + + mImpl->pwLock(); + llpw_proxy_destroy(mProxy); + mImpl->pwUnlock(); +} + +static pid_t getParentPid(pid_t aPid) +{ + std::stringstream strm; + strm << "/proc/" << aPid << "/status"; + std::ifstream in{ strm.str() }; + + if (!in.is_open()) return 0; + + pid_t res {0}; + while (!in.eof() && res == 0) + { + std::string line; + line.resize( 1024, 0 ); + in.getline( &line[0], line.length() ); + + auto i = line.find("PPid:"); + + if (i == std::string::npos) continue; + + char const *pIn = line.c_str() + 5; // Skip over pid; + while (*pIn != 0 && isspace( *pIn )) ++pIn; + + if (*pIn) res = atoll(pIn); + } + return res; +} + +static bool isPluginPid(pid_t aPid) +{ + auto myPid = getpid(); + + do + { + if(aPid == myPid) return true; + aPid = getParentPid( aPid ); + } while(aPid > 1); + + return false; +} + +static void nodeEventInfo(void* data, const struct pw_node_info* info) +{ + const char* processId = spa_dict_lookup(info->props, PW_KEY_APP_PROCESS_ID); + if (processId == nullptr) return; + + pid_t pid = atoll(processId); + if (!isPluginPid(pid)) return; + + // const char* appName = spa_dict_lookup(info->props, PW_KEY_APP_NAME); + // debugPrint("got app: %s\n", appName); + + auto* const childNode = static_cast(data); + // debugPrint("init volume to: %f\n", childNode->mImpl->mDesiredVolume); + + childNode->updateVolume(); + + childNode->mImpl->mChildNodesMutex.lock(); + childNode->mImpl->mChildNodes.insert(childNode); + childNode->mImpl->mChildNodesMutex.unlock(); +} + +static const struct pw_node_events NODE_EVENTS = { + .version = PW_VERSION_CLIENT_EVENTS, + .info = nodeEventInfo, +}; + +static void proxyEventDestroy(void* data) +{ + auto* const childNode = static_cast(data); + childNode->destroy(); +} + +static void proxyEventRemoved(void* data) +{ + auto* const childNode = static_cast(data); + childNode->destroy(); +} + +static const struct pw_proxy_events PROXY_EVENTS = { + .version = PW_VERSION_PROXY_EVENTS, + .destroy = proxyEventDestroy, + .removed = proxyEventRemoved, +}; + +void VolumeCatcherImpl::handleRegistryEventGlobal( + uint32_t id, uint32_t permissions, const char *type, uint32_t version, + const struct spa_dict *props +) { + if (props == nullptr || strcmp(type, PW_TYPE_INTERFACE_Node) != 0) return; + + const char* mediaClass = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); + if (mediaClass == nullptr || strcmp(mediaClass, "Stream/Output/Audio") != 0) return; + + pw_proxy* proxy = static_cast( + pw_registry_bind(mRegistry, id, type, PW_VERSION_CLIENT, sizeof(ChildNode)) + ); + + auto* const childNode = static_cast(llpw_proxy_get_user_data(proxy)); + + childNode->mActive = true; + childNode->mProxy = proxy; + childNode->mImpl = this; + + pw_node_add_listener(proxy, &childNode->mNodeListener, &NODE_EVENTS, childNode); + llpw_proxy_add_listener(proxy, &childNode->mProxyListener, &PROXY_EVENTS, childNode); +} + +void VolumeCatcherImpl::setVolume(F32 volume) +{ + // debugPrint("setting all volume to: %f\n", volume); + + mVolume = volume; + + mChildNodesMutex.lock(); + std::unordered_set copyOfChildNodes(mChildNodes); + mChildNodesMutex.unlock(); + + // debugPrint("for %d nodes\n", copyOfChildNodes.size()); + + for (auto* childNode : copyOfChildNodes) { + childNode->updateVolume(); + } +} + +// void VolumeCatcherImpl::setPan(F32 pan) +// { +// mPan = pan; +// setVolume(mVolume); +// } + +///////////////////////////////////////////////////// + +VolumeCatcher::VolumeCatcher() +{ + pimpl = new VolumeCatcherImpl(); +} + +VolumeCatcher::~VolumeCatcher() +{ + delete pimpl; + pimpl = nullptr; +} + +void VolumeCatcher::setVolume(F32 volume) +{ + llassert(pimpl); + pimpl->setVolume(volume); +} + +void VolumeCatcher::setPan(F32 pan) +{ + // llassert(pimpl); + // pimpl->setPan(pan); +} + +void VolumeCatcher::pump() +{ +} diff --git a/indra/media_plugins/cef/linux_volume_catcher_pw_syms.inc b/indra/media_plugins/cef/linux_volume_catcher_pw_syms.inc new file mode 100644 index 0000000000..14e4a42f1d --- /dev/null +++ b/indra/media_plugins/cef/linux_volume_catcher_pw_syms.inc @@ -0,0 +1,22 @@ +// required symbols to grab +LL_GRAB_SYM(true, pw_init, void, int *argc, char **argv[]); +// LL_GRAB_SYM(true, pw_main_loop_new, struct pw_main_loop *, const struct spa_dict *props); +// LL_GRAB_SYM(true, pw_main_loop_get_loop, struct pw_loop *, struct pw_main_loop *loop); +// LL_GRAB_SYM(true, pw_main_loop_destroy, void, struct pw_main_loop *loop); +// LL_GRAB_SYM(true, pw_main_loop_run, void, struct pw_main_loop *loop); +LL_GRAB_SYM(true, pw_context_new, struct pw_context *, struct pw_loop *main_loop, struct pw_properties *props, size_t user_data_size); +LL_GRAB_SYM(true, pw_context_destroy, void, struct pw_context *context); +LL_GRAB_SYM(true, pw_context_connect, struct pw_core *, struct pw_context *context, struct pw_properties *properties, size_t user_data_size); +LL_GRAB_SYM(true, pw_thread_loop_new, struct pw_thread_loop *, const char *name, const struct spa_dict *props); +LL_GRAB_SYM(true, pw_thread_loop_destroy, void, struct pw_thread_loop *loop); +LL_GRAB_SYM(true, pw_thread_loop_get_loop, struct pw_loop *, struct pw_thread_loop *loop); +LL_GRAB_SYM(true, pw_thread_loop_start, int, struct pw_thread_loop *loop); +LL_GRAB_SYM(true, pw_thread_loop_stop, void, struct pw_thread_loop *loop); +LL_GRAB_SYM(true, pw_thread_loop_lock, void, struct pw_thread_loop *loop); +LL_GRAB_SYM(true, pw_thread_loop_unlock, void, struct pw_thread_loop *loop); +LL_GRAB_SYM(true, pw_proxy_add_listener, void, struct pw_proxy *proxy, struct spa_hook *listener, const struct pw_proxy_events *events, void *data); +LL_GRAB_SYM(true, pw_proxy_destroy, void, struct pw_proxy *proxy); +LL_GRAB_SYM(true, pw_proxy_get_user_data, void *, struct pw_proxy *proxy); +LL_GRAB_SYM(true, pw_core_disconnect, int, struct pw_core *core); + +// optional symbols to grab -- cgit v1.2.3 From 4f8f04497c57d2ce65a1f4dffe6856ac18b59fb6 Mon Sep 17 00:00:00 2001 From: Maki Date: Thu, 18 Apr 2024 02:57:46 -0400 Subject: Try getting other symbols for Pulse if it fails --- indra/media_plugins/cef/linux_volume_catcher_pa.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/linux_volume_catcher_pa.cpp b/indra/media_plugins/cef/linux_volume_catcher_pa.cpp index 80f58954d4..daad443e44 100755 --- a/indra/media_plugins/cef/linux_volume_catcher_pa.cpp +++ b/indra/media_plugins/cef/linux_volume_catcher_pa.cpp @@ -129,6 +129,7 @@ void VolumeCatcherImpl::init() // probably be loaded separately. Our Linux DSO framework needs refactoring, // we do this sort of thing a lot with practically identical logic... mGotSyms = loadsyms("libpulse-mainloop-glib.so.0"); + if (!mGotSyms) mGotSyms = loadsyms("libpulse.so.0"); if (!mGotSyms) return; mMainloop = llpa_glib_mainloop_new(g_main_context_default()); -- cgit v1.2.3 From 09b0244f0e623dad43f579c9c7ba42ad0d53ed25 Mon Sep 17 00:00:00 2001 From: Maki Date: Thu, 18 Apr 2024 02:58:08 -0400 Subject: Use Pulse for volume catching and add CMake variable for PipeWire --- indra/media_plugins/cef/CMakeLists.txt | 36 ++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 15 deletions(-) (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/CMakeLists.txt b/indra/media_plugins/cef/CMakeLists.txt index 9e129f2185..4ef2b0957e 100644 --- a/indra/media_plugins/cef/CMakeLists.txt +++ b/indra/media_plugins/cef/CMakeLists.txt @@ -24,21 +24,27 @@ set(media_plugin_cef_HEADER_FILES # Select which VolumeCatcher implementation to use if (LINUX) - # foreach( PULSE_FILE pulse/introspect.h pulse/context.h pulse/subscribe.h pulse/glib-mainloop.h ) - # find_path( PULSE_FILE_${PULSE_FILE}_FOUND ${PULSE_FILE} NO_CACHE) - # if( NOT PULSE_FILE_${PULSE_FILE}_FOUND ) - # message( "Looking for ${PULSE_FILE} ... not found") - # message( FATAL_ERROR "Pulse header not found" ) - # else() - # message( "Looking for ${PULSE_FILE} ... found") - # endif() - # endforeach() - - include(FindPipeWire) - include_directories(SYSTEM ${PIPEWIRE_INCLUDE_DIRS} ${SPA_INCLUDE_DIRS}) - - message( "Building with Linux volume catcher using PipeWire" ) - set(LINUX_VOLUME_CATCHER linux_volume_catcher_pw.cpp) + # off by default. we should keep using pulse for now + set(USE_VOLUME_CATCHER_PW OFF CACHE BOOL "Use PipeWire for CEF volume catching") + + if (USE_VOLUME_CATCHER_PW) + include(FindPipeWire) + include_directories(SYSTEM ${PIPEWIRE_INCLUDE_DIRS} ${SPA_INCLUDE_DIRS}) + message( "Building with Linux volume catcher using PipeWire" ) + set(LINUX_VOLUME_CATCHER linux_volume_catcher_pw.cpp) + else (USE_VOLUME_CATCHER_PW) + foreach( PULSE_FILE pulse/introspect.h pulse/context.h pulse/subscribe.h pulse/glib-mainloop.h ) + find_path( PULSE_FILE_${PULSE_FILE}_FOUND ${PULSE_FILE} NO_CACHE) + if( NOT PULSE_FILE_${PULSE_FILE}_FOUND ) + message( "Looking for ${PULSE_FILE} ... not found") + message( FATAL_ERROR "Pulse header not found" ) + else() + message( "Looking for ${PULSE_FILE} ... found") + endif() + endforeach() + message( "Building with Linux volume catcher using PulseAudio" ) + set(LINUX_VOLUME_CATCHER linux_volume_catcher_pa.cpp) + endif (USE_VOLUME_CATCHER_PW) list(APPEND media_plugin_cef_SOURCE_FILES ${LINUX_VOLUME_CATCHER}) set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--build-id -Wl,-rpath,'$ORIGIN:$ORIGIN/../../lib'") -- cgit v1.2.3 From 477b45be1be256b7496e1d45b41754c6e40ef58a Mon Sep 17 00:00:00 2001 From: Maki Date: Fri, 19 Apr 2024 02:32:29 -0400 Subject: Add toggle for PipeWire volume catcher, and refactoring --- indra/media_plugins/cef/CMakeLists.txt | 45 +- .../cef/linux/volume_catcher_linux.cpp | 103 +++++ .../media_plugins/cef/linux/volume_catcher_linux.h | 151 +++++++ .../cef/linux/volume_catcher_pipewire.cpp | 336 +++++++++++++++ .../cef/linux/volume_catcher_pipewire_syms.inc | 22 + .../cef/linux/volume_catcher_pulseaudio.cpp | 322 +++++++++++++++ .../linux/volume_catcher_pulseaudio_glib_syms.inc | 6 + .../cef/linux/volume_catcher_pulseaudio_syms.inc | 25 ++ .../media_plugins/cef/linux_volume_catcher_pa.cpp | 425 ------------------- .../cef/linux_volume_catcher_pa_syms.inc | 21 - .../cef/linux_volume_catcher_paglib_syms.inc | 6 - .../media_plugins/cef/linux_volume_catcher_pw.cpp | 451 --------------------- .../cef/linux_volume_catcher_pw_syms.inc | 22 - indra/media_plugins/cef/media_plugin_cef.cpp | 7 + indra/media_plugins/cef/volume_catcher.h | 32 +- 15 files changed, 1017 insertions(+), 957 deletions(-) create mode 100644 indra/media_plugins/cef/linux/volume_catcher_linux.cpp create mode 100644 indra/media_plugins/cef/linux/volume_catcher_linux.h create mode 100755 indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp create mode 100644 indra/media_plugins/cef/linux/volume_catcher_pipewire_syms.inc create mode 100755 indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp create mode 100755 indra/media_plugins/cef/linux/volume_catcher_pulseaudio_glib_syms.inc create mode 100755 indra/media_plugins/cef/linux/volume_catcher_pulseaudio_syms.inc delete mode 100755 indra/media_plugins/cef/linux_volume_catcher_pa.cpp delete mode 100755 indra/media_plugins/cef/linux_volume_catcher_pa_syms.inc delete mode 100755 indra/media_plugins/cef/linux_volume_catcher_paglib_syms.inc delete mode 100755 indra/media_plugins/cef/linux_volume_catcher_pw.cpp delete mode 100644 indra/media_plugins/cef/linux_volume_catcher_pw_syms.inc (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/CMakeLists.txt b/indra/media_plugins/cef/CMakeLists.txt index 4ef2b0957e..065aa4605a 100644 --- a/indra/media_plugins/cef/CMakeLists.txt +++ b/indra/media_plugins/cef/CMakeLists.txt @@ -24,27 +24,30 @@ set(media_plugin_cef_HEADER_FILES # Select which VolumeCatcher implementation to use if (LINUX) - # off by default. we should keep using pulse for now - set(USE_VOLUME_CATCHER_PW OFF CACHE BOOL "Use PipeWire for CEF volume catching") - - if (USE_VOLUME_CATCHER_PW) - include(FindPipeWire) - include_directories(SYSTEM ${PIPEWIRE_INCLUDE_DIRS} ${SPA_INCLUDE_DIRS}) - message( "Building with Linux volume catcher using PipeWire" ) - set(LINUX_VOLUME_CATCHER linux_volume_catcher_pw.cpp) - else (USE_VOLUME_CATCHER_PW) - foreach( PULSE_FILE pulse/introspect.h pulse/context.h pulse/subscribe.h pulse/glib-mainloop.h ) - find_path( PULSE_FILE_${PULSE_FILE}_FOUND ${PULSE_FILE} NO_CACHE) - if( NOT PULSE_FILE_${PULSE_FILE}_FOUND ) - message( "Looking for ${PULSE_FILE} ... not found") - message( FATAL_ERROR "Pulse header not found" ) - else() - message( "Looking for ${PULSE_FILE} ... found") - endif() - endforeach() - message( "Building with Linux volume catcher using PulseAudio" ) - set(LINUX_VOLUME_CATCHER linux_volume_catcher_pa.cpp) - endif (USE_VOLUME_CATCHER_PW) + foreach( PULSE_FILE pulse/introspect.h pulse/context.h pulse/subscribe.h pulse/glib-mainloop.h ) + find_path( PULSE_FILE_${PULSE_FILE}_FOUND ${PULSE_FILE} NO_CACHE) + if( NOT PULSE_FILE_${PULSE_FILE}_FOUND ) + message( "Looking for ${PULSE_FILE} ... not found") + message( FATAL_ERROR "Pulse header not found" ) + else() + message( "Looking for ${PULSE_FILE} ... found") + endif() + endforeach() + + include(FindPipeWire) + include_directories(SYSTEM ${PIPEWIRE_INCLUDE_DIRS} ${SPA_INCLUDE_DIRS}) + + message( "Building with Linux volume catcher for PipeWire and PulseAudio" ) + + list(APPEND media_plugin_cef_HEADER_FILES + linux/volume_catcher_linux.h + ) + + set(LINUX_VOLUME_CATCHER + linux/volume_catcher_linux.cpp + linux/volume_catcher_pulseaudio.cpp + linux/volume_catcher_pipewire.cpp + ) list(APPEND media_plugin_cef_SOURCE_FILES ${LINUX_VOLUME_CATCHER}) set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--build-id -Wl,-rpath,'$ORIGIN:$ORIGIN/../../lib'") diff --git a/indra/media_plugins/cef/linux/volume_catcher_linux.cpp b/indra/media_plugins/cef/linux/volume_catcher_linux.cpp new file mode 100644 index 0000000000..86cc329e66 --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_linux.cpp @@ -0,0 +1,103 @@ +/** + * @file volume_catcher.cpp + * @brief Linux volume catcher which will pick an implementation to use + * + * @cond + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + * @endcond + */ + +#include "volume_catcher_linux.h" + +#define PULSEAUDIO pimpl +#define PIPEWIRE pimpl2 + +//////////////////////////////////////////////////// + +VolumeCatcher::VolumeCatcher() +{ + // only init once we receive which implementation to use + + // debugClear(); + // debugPrint("init volume catcher\n"); +} + +void VolumeCatcher::onEnablePipeWireVolumeCatcher(bool enable) { + if (enable) { + // load pipewire + + if (PULSEAUDIO != nullptr) { + delete PULSEAUDIO; + PULSEAUDIO = nullptr; + } + + if (PIPEWIRE == nullptr) { + // debugPrint("volume catcher using pipewire\n"); + PIPEWIRE = new VolumeCatcherPipeWire(); + } + } else { + // load pulseaudio + + if (PIPEWIRE != nullptr) { + delete PIPEWIRE; + PIPEWIRE = nullptr; + } + + if (PULSEAUDIO == nullptr) { + // debugPrint("volume catcher using pulseaudio\n"); + PULSEAUDIO = new VolumeCatcherPulseAudio(); + } + } +} + +VolumeCatcher::~VolumeCatcher() +{ + if (PULSEAUDIO != nullptr) { + delete PULSEAUDIO; + PULSEAUDIO = nullptr; + } + + if (PIPEWIRE != nullptr) { + delete PIPEWIRE; + PIPEWIRE = nullptr; + } +} + +void VolumeCatcher::setVolume(F32 volume) +{ + if (PULSEAUDIO != nullptr) PULSEAUDIO->setVolume(volume); + if (PIPEWIRE != nullptr) PIPEWIRE->setVolume(volume); +} + +void VolumeCatcher::setPan(F32 pan) +{ + // not implemented for both + // if (PULSEAUDIO) PULSEAUDIO->setPan(pan); + // if (PIPEWIRE) PIPEWIRE->setPan(pan); +} + +void VolumeCatcher::pump() +{ + if (PULSEAUDIO != nullptr) PULSEAUDIO->pump(); + if (PIPEWIRE != nullptr) PIPEWIRE->pump(); // doesn't need it +} + diff --git a/indra/media_plugins/cef/linux/volume_catcher_linux.h b/indra/media_plugins/cef/linux/volume_catcher_linux.h new file mode 100644 index 0000000000..5149dd62e0 --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_linux.h @@ -0,0 +1,151 @@ +/** + * @file volume_catcher_impl.h + * @brief + * + * @cond + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + * @endcond + */ + +#ifndef VOLUME_CATCHER_LINUX_H +#define VOLUME_CATCHER_LINUX_H + +#include "linden_common.h" + +#include "../volume_catcher.h" + +#include +#include + +extern "C" { +// There's no special reason why we want the *glib* PA mainloop, but the generic polling implementation seems broken. +#include +#include + +#include + +#include "apr_pools.h" +#include "apr_dso.h" +} + +#include "media_plugin_base.h" + +class VolumeCatcherPulseAudio : public virtual VolumeCatcherImpl +{ +public: + VolumeCatcherPulseAudio(); + ~VolumeCatcherPulseAudio(); + + void setVolume(F32 volume); + void setPan(F32 pan); + void pump(); + + // for internal use - can't be private because used from our C callbacks + + bool loadsyms(std::string pa_dso_name); + void init(); + void cleanup(); + + void update_all_volumes(F32 volume); + void update_index_volume(U32 index, F32 volume); + void connected_okay(); + + std::set mSinkInputIndices; + std::map mSinkInputNumChannels; + F32 mDesiredVolume; + pa_glib_mainloop *mMainloop; + pa_context *mPAContext; + bool mConnected; + bool mGotSyms; +}; + +class VolumeCatcherPipeWire : public virtual VolumeCatcherImpl +{ +public: + VolumeCatcherPipeWire(); + ~VolumeCatcherPipeWire(); + + bool loadsyms(std::string pw_dso_name); + void init(); + void cleanup(); + + // some of these should be private + + void pwLock(); + void pwUnlock(); + + void setVolume(F32 volume); + void setPan(F32 pan); + void pump(); + + void handleRegistryEventGlobal( + uint32_t id, uint32_t permissions, const char* type, + uint32_t version, const struct spa_dict* props + ); + + class ChildNode + { + public: + bool mActive = false; + + pw_proxy* mProxy = nullptr; + spa_hook mNodeListener {}; + spa_hook mProxyListener {}; + VolumeCatcherPipeWire* mImpl = nullptr; + + void updateVolume(); + void destroy(); + }; + + bool mGotSyms = false; + + F32 mVolume = 1.0f; // max by default + // F32 mPan = 0.0f; // center + + pw_thread_loop* mThreadLoop; + pw_context* mContext; + pw_core* mCore; + pw_registry* mRegistry; + spa_hook mRegistryListener; + + std::unordered_set mChildNodes; + std::mutex mChildNodesMutex; +}; + +// static void debugClear() +// { +// auto file = fopen("volume-catcher-log.txt", "w"); +// fprintf(file, "\n"); +// fclose(file); +// } + +// static void debugPrint(const char* format, ...) +// { +// va_list args; +// va_start(args, format); +// auto file = fopen("volume-catcher-log.txt", "a"); +// vfprintf(file, format, args); +// fclose(file); +// va_end(args); +// } + +#endif // VOLUME_CATCHER_LINUX_H diff --git a/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp new file mode 100755 index 0000000000..a779cf868e --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp @@ -0,0 +1,336 @@ +/** + * @file volume_catcher_pipewire.cpp + * @brief A Linux-specific, PipeWire-specific hack to detect and volume-adjust new audio sources + * + * @cond + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + * @endcond + */ + +/* + The high-level design is as follows: + 1) Connect to the PipeWire daemon + 2) Find all existing and new audio nodes + 3) Examine PID and parent PID's to see if it belongs to our process + 4) If so, tell PipeWire to adjust the volume of that node + 5) Keep a list of all audio nodes and adjust when we setVolume() + */ + +#include "linden_common.h" + +#include "volume_catcher_linux.h" + +extern "C" { +#include +#include +} + +SymbolGrabber pwSymbolGrabber; +#undef LL_SYMBOL_GRABBER +#define LL_SYMBOL_GRABBER pwSymbolGrabber + +#include "volume_catcher_pipewire_syms.inc" + +//////////////////////////////////////////////////// + +VolumeCatcherPipeWire::VolumeCatcherPipeWire() +{ + init(); +} + +VolumeCatcherPipeWire::~VolumeCatcherPipeWire() +{ + cleanup(); +} + +static void registryEventGlobal( + void *data, uint32_t id, uint32_t permissions, const char *type, + uint32_t version, const struct spa_dict *props +) +{ + static_cast(data)->handleRegistryEventGlobal( + id, permissions, type, version, props + ); +} + +static const struct pw_registry_events REGISTRY_EVENTS = { + .version = PW_VERSION_REGISTRY_EVENTS, + .global = registryEventGlobal, +}; + +bool VolumeCatcherPipeWire::loadsyms(std::string pw_dso_name) +{ + return pwSymbolGrabber.grabSymbols({ pw_dso_name }); +} + +void VolumeCatcherPipeWire::init() +{ + // debugPrint("init\n"); + + mGotSyms = loadsyms("libpipewire-0.3.so.0"); + + if (!mGotSyms) + return; + + // debugPrint("got syms\n"); + + llpw_init(NULL, NULL); + + mThreadLoop = llpw_thread_loop_new("SL Plugin Volume Adjuster", NULL); + + if (!mThreadLoop) + return; + + pwLock(); + + mContext = llpw_context_new( + llpw_thread_loop_get_loop(mThreadLoop), NULL, 0 + ); + + if (!mContext) + return pwUnlock(); + + mCore = llpw_context_connect(mContext, NULL, 0); + + if (!mCore) + return pwUnlock(); + + mRegistry = pw_core_get_registry(mCore, PW_VERSION_REGISTRY, 0); + + // debugPrint("got registry\n"); + + spa_zero(mRegistryListener); + + pw_registry_add_listener( + mRegistry, &mRegistryListener, ®ISTRY_EVENTS, this + ); + + llpw_thread_loop_start(mThreadLoop); + + pwUnlock(); + + // debugPrint("started thread loop\n"); +} + +void VolumeCatcherPipeWire::cleanup() +{ + mChildNodesMutex.lock(); + for (auto* childNode : mChildNodes) { + childNode->destroy(); + } + mChildNodes.clear(); + mChildNodesMutex.unlock(); + + pwLock(); + if (mRegistry) llpw_proxy_destroy((struct pw_proxy*)mRegistry); + spa_zero(mRegistryListener); + if (mCore) llpw_core_disconnect(mCore); + if (mContext) llpw_context_destroy(mContext); + pwUnlock(); + + if (!mThreadLoop) + return; + + llpw_thread_loop_stop(mThreadLoop); + llpw_thread_loop_destroy(mThreadLoop); + + // debugPrint("cleanup done\n"); +} + +void VolumeCatcherPipeWire::pwLock() { + if (!mThreadLoop) + return; + + llpw_thread_loop_lock(mThreadLoop); +} + +void VolumeCatcherPipeWire::pwUnlock() { + if (!mThreadLoop) + return; + + llpw_thread_loop_unlock(mThreadLoop); +} + +// #define INV_LERP(a, b, v) (v - a) / (b - a) + +// #include + +void VolumeCatcherPipeWire::ChildNode::updateVolume() +{ + if (!mActive) + return; + + F32 volume = std::clamp(mImpl->mVolume, 0.0f, 1.0f); + // F32 pan = std::clamp(mImpl->mPan, -1.0f, 1.0f); + + // debugClear(); + // struct timeval time; + // gettimeofday(&time, NULL); + // double t = (double)time.tv_sec + (double)(time.tv_usec / 1000) / 1000; + // debugPrint("time is %f\n", t); + // F32 pan = std::sin(t * 2.0d); + // debugPrint("pan is %f\n", pan); + + // uint32_t channels = 2; + // float volumes[channels]; + // volumes[1] = INV_LERP(-1.0f, 0.0f, pan) * volume; // left + // volumes[0] = INV_LERP(1.0f, 0.0f, pan) * volume; // right + + uint32_t channels = 1; + float volumes[channels]; + volumes[0] = volume; + + uint8_t buffer[512]; + + spa_pod_builder builder; + spa_pod_builder_init(&builder, buffer, sizeof(buffer)); + + spa_pod_frame frame; + spa_pod_builder_push_object(&builder, &frame, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); + spa_pod_builder_prop(&builder, SPA_PROP_channelVolumes, 0); + spa_pod_builder_array(&builder, sizeof(float), SPA_TYPE_Float, channels, volumes); + spa_pod* pod = static_cast(spa_pod_builder_pop(&builder, &frame)); + + mImpl->pwLock(); + pw_node_set_param(mProxy, SPA_PARAM_Props, 0, pod); + mImpl->pwUnlock(); +} + +void VolumeCatcherPipeWire::ChildNode::destroy() +{ + if (!mActive) + return; + + mActive = false; + + mImpl->mChildNodesMutex.lock(); + mImpl->mChildNodes.erase(this); + mImpl->mChildNodesMutex.unlock(); + + spa_hook_remove(&mNodeListener); + spa_hook_remove(&mProxyListener); + + mImpl->pwLock(); + llpw_proxy_destroy(mProxy); + mImpl->pwUnlock(); +} + +static void nodeEventInfo(void* data, const struct pw_node_info* info) +{ + const char* processId = spa_dict_lookup(info->props, PW_KEY_APP_PROCESS_ID); + + if (processId == nullptr) + return; + + pid_t pid = atoll(processId); + + if (!isPluginPid(pid)) + return; + + // const char* appName = spa_dict_lookup(info->props, PW_KEY_APP_NAME); + // debugPrint("got app: %s\n", appName); + + auto* const childNode = static_cast(data); + // debugPrint("init volume to: %f\n", childNode->mImpl->mVolume); + + childNode->updateVolume(); + + childNode->mImpl->mChildNodesMutex.lock(); + childNode->mImpl->mChildNodes.insert(childNode); + childNode->mImpl->mChildNodesMutex.unlock(); +} + +static const struct pw_node_events NODE_EVENTS = { + .version = PW_VERSION_CLIENT_EVENTS, + .info = nodeEventInfo, +}; + +static void proxyEventDestroy(void* data) +{ + auto* const childNode = static_cast(data); + childNode->destroy(); +} + +static void proxyEventRemoved(void* data) +{ + auto* const childNode = static_cast(data); + childNode->destroy(); +} + +static const struct pw_proxy_events PROXY_EVENTS = { + .version = PW_VERSION_PROXY_EVENTS, + .destroy = proxyEventDestroy, + .removed = proxyEventRemoved, +}; + +void VolumeCatcherPipeWire::handleRegistryEventGlobal( + uint32_t id, uint32_t permissions, const char *type, uint32_t version, + const struct spa_dict *props +) { + if (props == nullptr || strcmp(type, PW_TYPE_INTERFACE_Node) != 0) + return; + + const char* mediaClass = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); + + if (mediaClass == nullptr || strcmp(mediaClass, "Stream/Output/Audio") != 0) + return; + + pw_proxy* proxy = static_cast( + pw_registry_bind(mRegistry, id, type, PW_VERSION_CLIENT, sizeof(ChildNode)) + ); + + auto* const childNode = static_cast(llpw_proxy_get_user_data(proxy)); + + childNode->mActive = true; + childNode->mProxy = proxy; + childNode->mImpl = this; + + pw_node_add_listener(proxy, &childNode->mNodeListener, &NODE_EVENTS, childNode); + llpw_proxy_add_listener(proxy, &childNode->mProxyListener, &PROXY_EVENTS, childNode); +} + +void VolumeCatcherPipeWire::setVolume(F32 volume) +{ + // debugPrint("setting all volume to: %f\n", volume); + + mVolume = volume; + + mChildNodesMutex.lock(); + std::unordered_set copyOfChildNodes(mChildNodes); + mChildNodesMutex.unlock(); + + // debugPrint("for %d nodes\n", copyOfChildNodes.size()); + + for (auto* childNode : copyOfChildNodes) { + childNode->updateVolume(); + } +} + +void VolumeCatcherPipeWire::setPan(F32 pan) +{ + // mPan = pan; + // setVolume(mVolume); +} + +void VolumeCatcherPipeWire::pump() +{ +} \ No newline at end of file diff --git a/indra/media_plugins/cef/linux/volume_catcher_pipewire_syms.inc b/indra/media_plugins/cef/linux/volume_catcher_pipewire_syms.inc new file mode 100644 index 0000000000..14e4a42f1d --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_pipewire_syms.inc @@ -0,0 +1,22 @@ +// required symbols to grab +LL_GRAB_SYM(true, pw_init, void, int *argc, char **argv[]); +// LL_GRAB_SYM(true, pw_main_loop_new, struct pw_main_loop *, const struct spa_dict *props); +// LL_GRAB_SYM(true, pw_main_loop_get_loop, struct pw_loop *, struct pw_main_loop *loop); +// LL_GRAB_SYM(true, pw_main_loop_destroy, void, struct pw_main_loop *loop); +// LL_GRAB_SYM(true, pw_main_loop_run, void, struct pw_main_loop *loop); +LL_GRAB_SYM(true, pw_context_new, struct pw_context *, struct pw_loop *main_loop, struct pw_properties *props, size_t user_data_size); +LL_GRAB_SYM(true, pw_context_destroy, void, struct pw_context *context); +LL_GRAB_SYM(true, pw_context_connect, struct pw_core *, struct pw_context *context, struct pw_properties *properties, size_t user_data_size); +LL_GRAB_SYM(true, pw_thread_loop_new, struct pw_thread_loop *, const char *name, const struct spa_dict *props); +LL_GRAB_SYM(true, pw_thread_loop_destroy, void, struct pw_thread_loop *loop); +LL_GRAB_SYM(true, pw_thread_loop_get_loop, struct pw_loop *, struct pw_thread_loop *loop); +LL_GRAB_SYM(true, pw_thread_loop_start, int, struct pw_thread_loop *loop); +LL_GRAB_SYM(true, pw_thread_loop_stop, void, struct pw_thread_loop *loop); +LL_GRAB_SYM(true, pw_thread_loop_lock, void, struct pw_thread_loop *loop); +LL_GRAB_SYM(true, pw_thread_loop_unlock, void, struct pw_thread_loop *loop); +LL_GRAB_SYM(true, pw_proxy_add_listener, void, struct pw_proxy *proxy, struct spa_hook *listener, const struct pw_proxy_events *events, void *data); +LL_GRAB_SYM(true, pw_proxy_destroy, void, struct pw_proxy *proxy); +LL_GRAB_SYM(true, pw_proxy_get_user_data, void *, struct pw_proxy *proxy); +LL_GRAB_SYM(true, pw_core_disconnect, int, struct pw_core *core); + +// optional symbols to grab diff --git a/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp new file mode 100755 index 0000000000..20a6458ea3 --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp @@ -0,0 +1,322 @@ +/** + * @file volume_catcher_pulseaudio.cpp + * @brief A Linux-specific, PulseAudio-specific hack to detect and volume-adjust new audio sources + * + * @cond + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + * @endcond + */ + +/* + The high-level design is as follows: + 1) Connect to the PulseAudio daemon + 2) Watch for the creation of new audio players connecting to the daemon (this includes ALSA clients running on the PulseAudio emulation layer, such as Flash plugins) + 3) Examine any new audio player's PID to see if it belongs to our own process + 4) If so, tell PA to adjust the volume of that audio player ('sink input' in PA parlance) + 5) Keep a list of all living audio players that we care about, adjust the volumes of all of them when we get a new setVolume() call + */ + +#include "linden_common.h" + +#include "volume_catcher_linux.h" + +extern "C" { +#include +#include + +#include + +#include +} + +SymbolGrabber paSymbolGrabber; +#undef LL_SYMBOL_GRABBER +#define LL_SYMBOL_GRABBER paSymbolGrabber + +#include "volume_catcher_pulseaudio_syms.inc" +#include "volume_catcher_pulseaudio_glib_syms.inc" + +//////////////////////////////////////////////////// + +// PulseAudio requires a chain of callbacks with C linkage +extern "C" { + void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *i, int eol, void *userdata); + void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata); + void callback_context_state(pa_context *context, void *userdata); +} + +VolumeCatcherPulseAudio::VolumeCatcherPulseAudio() + : mDesiredVolume(0.0f), + mMainloop(nullptr), + mPAContext(nullptr), + mConnected(false), + mGotSyms(false) +{ + init(); +} + +VolumeCatcherPulseAudio::~VolumeCatcherPulseAudio() +{ + cleanup(); +} + +bool VolumeCatcherPulseAudio::loadsyms(std::string pulse_dso_name) +{ + return paSymbolGrabber.grabSymbols({ pulse_dso_name }); +} + +void VolumeCatcherPulseAudio::init() +{ + // try to be as defensive as possible because PA's interface is a + // bit fragile and (for our purposes) we'd rather simply not function + // than crash + + // we cheat and rely upon libpulse-mainloop-glib.so.0 to pull-in + // libpulse.so.0 - this isn't a great assumption, and the two DSOs should + // probably be loaded separately. Our Linux DSO framework needs refactoring, + // we do this sort of thing a lot with practically identical logic... + mGotSyms = loadsyms("libpulse-mainloop-glib.so.0"); + if (!mGotSyms) mGotSyms = loadsyms("libpulse.so.0"); + + if (!mGotSyms) + return; + + mMainloop = llpa_glib_mainloop_new(g_main_context_default()); + + if (mMainloop) + { + pa_mainloop_api *api = llpa_glib_mainloop_get_api(mMainloop); + + if (api) + { + pa_proplist *proplist = llpa_proplist_new(); + + if (proplist) + { + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "multimedia-player"); + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "com.secondlife.viewer.mediaplugvoladjust"); + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "SL Plugin Volume Adjuster"); + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, "1"); + + // plain old pa_context_new() is broken! + mPAContext = llpa_context_new_with_proplist(api, nullptr, proplist); + + llpa_proplist_free(proplist); + } + } + } + + // Now we've set up a PA context and mainloop, try connecting the + // PA context to a PA daemon. + if (mPAContext) + { + llpa_context_set_state_callback(mPAContext, callback_context_state, this); + pa_context_flags_t cflags = (pa_context_flags)0; // maybe add PA_CONTEXT_NOAUTOSPAWN? + if (llpa_context_connect(mPAContext, nullptr, cflags, nullptr) >= 0) + { + // Okay! We haven't definitely connected, but we + // haven't definitely failed yet. + } + else + { + // Failed to connect to PA manager... we'll leave + // things like that. Perhaps we should try again later. + } + } +} + +void VolumeCatcherPulseAudio::cleanup() +{ + mConnected = false; + + if (mGotSyms && mPAContext) + { + llpa_context_disconnect(mPAContext); + llpa_context_unref(mPAContext); + } + + mPAContext = nullptr; + + if (mGotSyms && mMainloop) + llpa_glib_mainloop_free(mMainloop); + + mMainloop = nullptr; +} + +void VolumeCatcherPulseAudio::setVolume(F32 volume) +{ + mDesiredVolume = volume; + + if (!mGotSyms) + return; + + if (mConnected && mPAContext) + { + update_all_volumes(mDesiredVolume); + } + + pump(); +} + +void VolumeCatcherPulseAudio::setPan(F32 pan) +{ +} + +void VolumeCatcherPulseAudio::pump() +{ + gboolean may_block = FALSE; + g_main_context_iteration(g_main_context_default(), may_block); +} + +void VolumeCatcherPulseAudio::connected_okay() +{ + pa_operation *op; + + // fetch global list of existing sinkinputs + if ((op = llpa_context_get_sink_input_info_list(mPAContext, + callback_discovered_sinkinput, + this))) + { + llpa_operation_unref(op); + } + + // subscribe to future global sinkinput changes + llpa_context_set_subscribe_callback(mPAContext, + callback_subscription_alert, + this); + if ((op = llpa_context_subscribe(mPAContext, (pa_subscription_mask_t) + (PA_SUBSCRIPTION_MASK_SINK_INPUT), + nullptr, nullptr))) + { + llpa_operation_unref(op); + } +} + +void VolumeCatcherPulseAudio::update_all_volumes(F32 volume) +{ + for (std::set::iterator it = mSinkInputIndices.begin(); + it != mSinkInputIndices.end(); ++it) + { + update_index_volume(*it, volume); + } +} + +void VolumeCatcherPulseAudio::update_index_volume(U32 index, F32 volume) +{ + static pa_cvolume cvol; + llpa_cvolume_set(&cvol, mSinkInputNumChannels[index], + llpa_sw_volume_from_linear(volume)); + + pa_context *c = mPAContext; + uint32_t idx = index; + const pa_cvolume *cvolumep = &cvol; + pa_context_success_cb_t cb = nullptr; // okay as null + void *userdata = nullptr; // okay as null + + pa_operation *op; + if ((op = llpa_context_set_sink_input_volume(c, idx, cvolumep, cb, userdata))) + llpa_operation_unref(op); +} + +void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *sii, int eol, void *userdata) +{ + VolumeCatcherPulseAudio *impl = dynamic_cast((VolumeCatcherPulseAudio*)userdata); + llassert(impl); + + if (0 == eol) + { + pa_proplist *proplist = sii->proplist; + pid_t sinkpid = atoll(llpa_proplist_gets(proplist, PA_PROP_APPLICATION_PROCESS_ID)); + + if (isPluginPid( sinkpid )) // does the discovered sinkinput belong to this process? + { + bool is_new = (impl->mSinkInputIndices.find(sii->index) == impl->mSinkInputIndices.end()); + + impl->mSinkInputIndices.insert(sii->index); + impl->mSinkInputNumChannels[sii->index] = sii->channel_map.channels; + + if (is_new) + { + // new! + impl->update_index_volume(sii->index, impl->mDesiredVolume); + } + else + { + // seen it already, do nothing. + } + } + } +} + +void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata) +{ + VolumeCatcherPulseAudio *impl = dynamic_cast((VolumeCatcherPulseAudio*)userdata); + llassert(impl); + + switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) + { + case PA_SUBSCRIPTION_EVENT_SINK_INPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) + { + // forget this sinkinput, if we were caring about it + impl->mSinkInputIndices.erase(index); + impl->mSinkInputNumChannels.erase(index); + } + else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) + { + // ask for more info about this new sinkinput + pa_operation *op; + if ((op = llpa_context_get_sink_input_info(impl->mPAContext, index, callback_discovered_sinkinput, impl))) + { + llpa_operation_unref(op); + } + } + else + { + // property change on this sinkinput - we don't care. + } + break; + + default:; + } +} + +void callback_context_state(pa_context *context, void *userdata) +{ + VolumeCatcherPulseAudio *impl = dynamic_cast((VolumeCatcherPulseAudio*)userdata); + llassert(impl); + + switch (llpa_context_get_state(context)) + { + case PA_CONTEXT_READY: + impl->mConnected = true; + impl->connected_okay(); + break; + case PA_CONTEXT_TERMINATED: + impl->mConnected = false; + break; + case PA_CONTEXT_FAILED: + impl->mConnected = false; + break; + default:; + } +} diff --git a/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_glib_syms.inc b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_glib_syms.inc new file mode 100755 index 0000000000..5fba60c188 --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_glib_syms.inc @@ -0,0 +1,6 @@ +// required symbols to grab +LL_GRAB_SYM(true, pa_glib_mainloop_free, void, pa_glib_mainloop* g) +LL_GRAB_SYM(true, pa_glib_mainloop_get_api, pa_mainloop_api*, pa_glib_mainloop* g) +LL_GRAB_SYM(true, pa_glib_mainloop_new, pa_glib_mainloop *, GMainContext *c) + +// optional symbols to grab diff --git a/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_syms.inc b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_syms.inc new file mode 100755 index 0000000000..fe4ad084b6 --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_syms.inc @@ -0,0 +1,25 @@ +// required symbols to grab +LL_GRAB_SYM(true, pa_context_connect, int, pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api) +LL_GRAB_SYM(true, pa_context_disconnect, void, pa_context *c) +LL_GRAB_SYM(true, pa_context_get_sink_input_info, pa_operation*, pa_context *c, uint32_t idx, pa_sink_input_info_cb_t cb, void *userdata) +LL_GRAB_SYM(true, pa_context_get_sink_input_info_list, pa_operation*, pa_context *c, pa_sink_input_info_cb_t cb, void *userdata) +LL_GRAB_SYM(true, pa_context_get_state, pa_context_state_t, pa_context *c) +LL_GRAB_SYM(true, pa_context_new_with_proplist, pa_context*, pa_mainloop_api *mainloop, const char *name, pa_proplist *proplist) +LL_GRAB_SYM(true, pa_context_set_sink_input_volume, pa_operation*, pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) +LL_GRAB_SYM(true, pa_context_set_state_callback, void, pa_context *c, pa_context_notify_cb_t cb, void *userdata) +LL_GRAB_SYM(true, pa_context_set_subscribe_callback, void, pa_context *c, pa_context_subscribe_cb_t cb, void *userdata) +LL_GRAB_SYM(true, pa_context_subscribe, pa_operation*, pa_context *c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void *userdata) +LL_GRAB_SYM(true, pa_context_unref, void, pa_context *c) +LL_GRAB_SYM(true, pa_cvolume_set, pa_cvolume*, pa_cvolume *a, unsigned channels, pa_volume_t v) +LL_GRAB_SYM(true, pa_operation_unref, void, pa_operation *o) +LL_GRAB_SYM(true, pa_proplist_free, void, pa_proplist* p) +LL_GRAB_SYM(true, pa_proplist_gets, const char*, pa_proplist *p, const char *key) +LL_GRAB_SYM(true, pa_proplist_new, pa_proplist*, void) +LL_GRAB_SYM(true, pa_proplist_sets, int, pa_proplist *p, const char *key, const char *value) +LL_GRAB_SYM(true, pa_sw_volume_from_linear, pa_volume_t, double v) +// LL_GRAB_SYM(true, pa_mainloop_free, void, pa_mainloop *m) +// LL_GRAB_SYM(true, pa_mainloop_get_api, pa_mainloop_api *, pa_mainloop *m) +// LL_GRAB_SYM(true, pa_mainloop_iterate, int, pa_mainloop *m, int block, int *retval) +// LL_GRAB_SYM(true, pa_mainloop_new, pa_mainloop *, void) + +// optional symbols to grab diff --git a/indra/media_plugins/cef/linux_volume_catcher_pa.cpp b/indra/media_plugins/cef/linux_volume_catcher_pa.cpp deleted file mode 100755 index daad443e44..0000000000 --- a/indra/media_plugins/cef/linux_volume_catcher_pa.cpp +++ /dev/null @@ -1,425 +0,0 @@ -/** - * @file linux_volume_catcher_pa.cpp - * @brief A Linux-specific, PulseAudio-specific hack to detect and volume-adjust new audio sources - * - * @cond - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - * @endcond - */ - -/* - The high-level design is as follows: - 1) Connect to the PulseAudio daemon - 2) Watch for the creation of new audio players connecting to the daemon (this includes ALSA clients running on the PulseAudio emulation layer, such as Flash plugins) - 3) Examine any new audio player's PID to see if it belongs to our own process - 4) If so, tell PA to adjust the volume of that audio player ('sink input' in PA parlance) - 5) Keep a list of all living audio players that we care about, adjust the volumes of all of them when we get a new setVolume() call - */ - -#include "linden_common.h" - -#include "volume_catcher.h" -#include -#include -#include -extern "C" { -#include -#include - -#include -#include -#include -#include // There's no special reason why we want the *glib* PA mainloop, but the generic polling implementation seems broken. - -#include "apr_pools.h" -#include "apr_dso.h" -} - -#include "media_plugin_base.h" - -SymbolGrabber gSymbolGrabber; - -#include "linux_volume_catcher_pa_syms.inc" -#include "linux_volume_catcher_paglib_syms.inc" -//////////////////////////////////////////////////// - -// PulseAudio requires a chain of callbacks with C linkage -extern "C" { - void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *i, int eol, void *userdata); - void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata); - void callback_context_state(pa_context *context, void *userdata); -} - -class VolumeCatcherImpl -{ -public: - VolumeCatcherImpl(); - ~VolumeCatcherImpl(); - - void setVolume(F32 volume); - void pump(void); - - // for internal use - can't be private because used from our C callbacks - - bool loadsyms(std::string pulse_dso_name); - void init(); - void cleanup(); - - void update_all_volumes(F32 volume); - void update_index_volume(U32 index, F32 volume); - void connected_okay(); - - std::set mSinkInputIndices; - std::map mSinkInputNumChannels; - F32 mDesiredVolume; - pa_glib_mainloop *mMainloop; - pa_context *mPAContext; - bool mConnected; - bool mGotSyms; -}; - -VolumeCatcherImpl::VolumeCatcherImpl() - : mDesiredVolume(0.0f), - mMainloop(nullptr), - mPAContext(nullptr), - mConnected(false), - mGotSyms(false) -{ - init(); -} - -VolumeCatcherImpl::~VolumeCatcherImpl() -{ - cleanup(); -} - -bool VolumeCatcherImpl::loadsyms(std::string pulse_dso_name) -{ - //return grab_pa_syms({pulse_dso_name}); - return gSymbolGrabber.grabSymbols( { pulse_dso_name }) ; -} - -void VolumeCatcherImpl::init() -{ - // try to be as defensive as possible because PA's interface is a - // bit fragile and (for our purposes) we'd rather simply not function - // than crash - - // we cheat and rely upon libpulse-mainloop-glib.so.0 to pull-in - // libpulse.so.0 - this isn't a great assumption, and the two DSOs should - // probably be loaded separately. Our Linux DSO framework needs refactoring, - // we do this sort of thing a lot with practically identical logic... - mGotSyms = loadsyms("libpulse-mainloop-glib.so.0"); - if (!mGotSyms) mGotSyms = loadsyms("libpulse.so.0"); - if (!mGotSyms) return; - - mMainloop = llpa_glib_mainloop_new(g_main_context_default()); - - if (mMainloop) - { - pa_mainloop_api *api = llpa_glib_mainloop_get_api(mMainloop); - - if (api) - { - pa_proplist *proplist = llpa_proplist_new(); - - if (proplist) - { - llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "multimedia-player"); - llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "com.secondlife.viewer.mediaplugvoladjust"); - llpa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "SL Plugin Volume Adjuster"); - llpa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, "1"); - - // plain old pa_context_new() is broken! - mPAContext = llpa_context_new_with_proplist(api, nullptr, proplist); - - llpa_proplist_free(proplist); - } - } - } - - // Now we've set up a PA context and mainloop, try connecting the - // PA context to a PA daemon. - if (mPAContext) - { - llpa_context_set_state_callback(mPAContext, callback_context_state, this); - pa_context_flags_t cflags = (pa_context_flags)0; // maybe add PA_CONTEXT_NOAUTOSPAWN? - if (llpa_context_connect(mPAContext, nullptr, cflags, nullptr) >= 0) - { - // Okay! We haven't definitely connected, but we - // haven't definitely failed yet. - } - else - { - // Failed to connect to PA manager... we'll leave - // things like that. Perhaps we should try again later. - } - } -} - -void VolumeCatcherImpl::cleanup() -{ - mConnected = false; - - if (mGotSyms && mPAContext) - { - llpa_context_disconnect(mPAContext); - llpa_context_unref(mPAContext); - } - - mPAContext = nullptr; - - if (mGotSyms && mMainloop) - llpa_glib_mainloop_free(mMainloop); - - mMainloop = nullptr; -} - -void VolumeCatcherImpl::setVolume(F32 volume) -{ - mDesiredVolume = volume; - - if (!mGotSyms) return; - - if (mConnected && mPAContext) - { - update_all_volumes(mDesiredVolume); - } - - pump(); -} - -void VolumeCatcherImpl::pump() -{ - gboolean may_block = FALSE; - g_main_context_iteration(g_main_context_default(), may_block); -} - -void VolumeCatcherImpl::connected_okay() -{ - pa_operation *op; - - // fetch global list of existing sinkinputs - if ((op = llpa_context_get_sink_input_info_list(mPAContext, - callback_discovered_sinkinput, - this))) - { - llpa_operation_unref(op); - } - - // subscribe to future global sinkinput changes - llpa_context_set_subscribe_callback(mPAContext, - callback_subscription_alert, - this); - if ((op = llpa_context_subscribe(mPAContext, (pa_subscription_mask_t) - (PA_SUBSCRIPTION_MASK_SINK_INPUT), - nullptr, nullptr))) - { - llpa_operation_unref(op); - } -} - -void VolumeCatcherImpl::update_all_volumes(F32 volume) -{ - for (std::set::iterator it = mSinkInputIndices.begin(); - it != mSinkInputIndices.end(); ++it) - { - update_index_volume(*it, volume); - } -} - -void VolumeCatcherImpl::update_index_volume(U32 index, F32 volume) -{ - static pa_cvolume cvol; - llpa_cvolume_set(&cvol, mSinkInputNumChannels[index], - llpa_sw_volume_from_linear(volume)); - - pa_context *c = mPAContext; - uint32_t idx = index; - const pa_cvolume *cvolumep = &cvol; - pa_context_success_cb_t cb = nullptr; // okay as null - void *userdata = nullptr; // okay as null - - pa_operation *op; - if ((op = llpa_context_set_sink_input_volume(c, idx, cvolumep, cb, userdata))) - llpa_operation_unref(op); -} - -pid_t getParentPid( pid_t aPid ) -{ - std::stringstream strm; - strm << "/proc/" << aPid << "/status"; - std::ifstream in{ strm.str() }; - - if( !in.is_open() ) - return 0; - - pid_t res {0}; - while( !in.eof() && res == 0 ) - { - std::string line; - line.resize( 1024, 0 ); - in.getline( &line[0], line.length() ); - - auto i = line.find( "PPid:" ); - - if( i == std::string::npos ) - continue; - - char const *pIn = line.c_str() + 5; // Skip over pid; - while( *pIn != 0 && isspace( *pIn ) ) - ++pIn; - - if( *pIn ) - res = atoll( pIn ); - } - return res; -} - - -bool isPluginPid( pid_t aPid ) -{ - auto myPid = getpid(); - - do - { - if( aPid == myPid ) - return true; - aPid = getParentPid( aPid ); - } while( aPid > 1 ); - - return false; -} - -void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *sii, int eol, void *userdata) -{ - VolumeCatcherImpl *impl = dynamic_cast((VolumeCatcherImpl*)userdata); - llassert(impl); - - if (0 == eol) - { - pa_proplist *proplist = sii->proplist; - pid_t sinkpid = atoll(llpa_proplist_gets(proplist, PA_PROP_APPLICATION_PROCESS_ID)); - - if (isPluginPid( sinkpid )) // does the discovered sinkinput belong to this process? - { - bool is_new = (impl->mSinkInputIndices.find(sii->index) == impl->mSinkInputIndices.end()); - - impl->mSinkInputIndices.insert(sii->index); - impl->mSinkInputNumChannels[sii->index] = sii->channel_map.channels; - - if (is_new) - { - // new! - impl->update_index_volume(sii->index, impl->mDesiredVolume); - } - else - { - // seen it already, do nothing. - } - } - } -} - -void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata) -{ - VolumeCatcherImpl *impl = dynamic_cast((VolumeCatcherImpl*)userdata); - llassert(impl); - - switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) - { - case PA_SUBSCRIPTION_EVENT_SINK_INPUT: - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) - { - // forget this sinkinput, if we were caring about it - impl->mSinkInputIndices.erase(index); - impl->mSinkInputNumChannels.erase(index); - } - else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) - { - // ask for more info about this new sinkinput - pa_operation *op; - if ((op = llpa_context_get_sink_input_info(impl->mPAContext, index, callback_discovered_sinkinput, impl))) - { - llpa_operation_unref(op); - } - } - else - { - // property change on this sinkinput - we don't care. - } - break; - - default:; - } -} - -void callback_context_state(pa_context *context, void *userdata) -{ - VolumeCatcherImpl *impl = dynamic_cast((VolumeCatcherImpl*)userdata); - llassert(impl); - - switch (llpa_context_get_state(context)) - { - case PA_CONTEXT_READY: - impl->mConnected = true; - impl->connected_okay(); - break; - case PA_CONTEXT_TERMINATED: - impl->mConnected = false; - break; - case PA_CONTEXT_FAILED: - impl->mConnected = false; - break; - default:; - } -} - -///////////////////////////////////////////////////// - -VolumeCatcher::VolumeCatcher() -{ - pimpl = new VolumeCatcherImpl(); -} - -VolumeCatcher::~VolumeCatcher() -{ - delete pimpl; - pimpl = nullptr; -} - -void VolumeCatcher::setVolume(F32 volume) -{ - llassert(pimpl); - pimpl->setVolume(volume); -} - -void VolumeCatcher::setPan(F32 pan) -{ - // TODO: implement this (if possible) -} - -void VolumeCatcher::pump() -{ - llassert(pimpl); - pimpl->pump(); -} diff --git a/indra/media_plugins/cef/linux_volume_catcher_pa_syms.inc b/indra/media_plugins/cef/linux_volume_catcher_pa_syms.inc deleted file mode 100755 index 4533362e61..0000000000 --- a/indra/media_plugins/cef/linux_volume_catcher_pa_syms.inc +++ /dev/null @@ -1,21 +0,0 @@ -// required symbols to grab -LL_GRAB_SYM(true, pa_context_connect, int, pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api) -LL_GRAB_SYM(true, pa_context_disconnect, void, pa_context *c) -LL_GRAB_SYM(true, pa_context_get_sink_input_info, pa_operation*, pa_context *c, uint32_t idx, pa_sink_input_info_cb_t cb, void *userdata) -LL_GRAB_SYM(true, pa_context_get_sink_input_info_list, pa_operation*, pa_context *c, pa_sink_input_info_cb_t cb, void *userdata) -LL_GRAB_SYM(true, pa_context_get_state, pa_context_state_t, pa_context *c) -LL_GRAB_SYM(true, pa_context_new_with_proplist, pa_context*, pa_mainloop_api *mainloop, const char *name, pa_proplist *proplist) -LL_GRAB_SYM(true, pa_context_set_sink_input_volume, pa_operation*, pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) -LL_GRAB_SYM(true, pa_context_set_state_callback, void, pa_context *c, pa_context_notify_cb_t cb, void *userdata) -LL_GRAB_SYM(true, pa_context_set_subscribe_callback, void, pa_context *c, pa_context_subscribe_cb_t cb, void *userdata) -LL_GRAB_SYM(true, pa_context_subscribe, pa_operation*, pa_context *c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void *userdata) -LL_GRAB_SYM(true, pa_context_unref, void, pa_context *c) -LL_GRAB_SYM(true, pa_cvolume_set, pa_cvolume*, pa_cvolume *a, unsigned channels, pa_volume_t v) -LL_GRAB_SYM(true, pa_operation_unref, void, pa_operation *o) -LL_GRAB_SYM(true, pa_proplist_free, void, pa_proplist* p) -LL_GRAB_SYM(true, pa_proplist_gets, const char*, pa_proplist *p, const char *key) -LL_GRAB_SYM(true, pa_proplist_new, pa_proplist*, void) -LL_GRAB_SYM(true, pa_proplist_sets, int, pa_proplist *p, const char *key, const char *value) -LL_GRAB_SYM(true, pa_sw_volume_from_linear, pa_volume_t, double v) - -// optional symbols to grab diff --git a/indra/media_plugins/cef/linux_volume_catcher_paglib_syms.inc b/indra/media_plugins/cef/linux_volume_catcher_paglib_syms.inc deleted file mode 100755 index 5fba60c188..0000000000 --- a/indra/media_plugins/cef/linux_volume_catcher_paglib_syms.inc +++ /dev/null @@ -1,6 +0,0 @@ -// required symbols to grab -LL_GRAB_SYM(true, pa_glib_mainloop_free, void, pa_glib_mainloop* g) -LL_GRAB_SYM(true, pa_glib_mainloop_get_api, pa_mainloop_api*, pa_glib_mainloop* g) -LL_GRAB_SYM(true, pa_glib_mainloop_new, pa_glib_mainloop *, GMainContext *c) - -// optional symbols to grab diff --git a/indra/media_plugins/cef/linux_volume_catcher_pw.cpp b/indra/media_plugins/cef/linux_volume_catcher_pw.cpp deleted file mode 100755 index b7171d10f9..0000000000 --- a/indra/media_plugins/cef/linux_volume_catcher_pw.cpp +++ /dev/null @@ -1,451 +0,0 @@ -/** - * @file linux_volume_catcher_pw.cpp - * @brief A Linux-specific, PipeWire-specific hack to detect and volume-adjust new audio sources - * - * @cond - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - * @endcond - */ - -/* - The high-level design is as follows: - 1) Connect to the PipeWire daemon - 2) Find all existing and new audio nodes - 3) Examine PID and parent PID's to see if it belongs to our process - 4) If so, tell PipeWire to adjust the volume of that node - 5) Keep a list of all audio nodes and adjust when we setVolume() - */ - -#include "linden_common.h" - -#include "volume_catcher.h" - -#include -#include - -extern "C" { -#include -#include -#include - -#include "apr_pools.h" -#include "apr_dso.h" -} - -#include "media_plugin_base.h" - -SymbolGrabber gSymbolGrabber; - -#include "linux_volume_catcher_pw_syms.inc" - -//////////////////////////////////////////////////// - -class VolumeCatcherImpl -{ -public: - VolumeCatcherImpl(); - ~VolumeCatcherImpl(); - - bool loadsyms(std::string pw_dso_name); - void init(); - void cleanup(); - - void pwLock(); - void pwUnlock(); - - void setVolume(F32 volume); - // void setPan(F32 pan); - - void handleRegistryEventGlobal( - uint32_t id, uint32_t permissions, const char* type, - uint32_t version, const struct spa_dict* props - ); - - class ChildNode - { - public: - bool mActive = false; - - pw_proxy* mProxy = nullptr; - spa_hook mNodeListener {}; - spa_hook mProxyListener {}; - VolumeCatcherImpl* mImpl = nullptr; - - void updateVolume(); - void destroy(); - }; - - bool mGotSyms = false; - - F32 mVolume = 1.0f; // max by default - // F32 mPan = 0.0f; // center - - pw_thread_loop* mThreadLoop; - pw_context* mContext; - pw_core* mCore; - pw_registry* mRegistry; - spa_hook mRegistryListener; - - std::unordered_set mChildNodes; - std::mutex mChildNodesMutex; -}; - -VolumeCatcherImpl::VolumeCatcherImpl() -{ - init(); -} - -VolumeCatcherImpl::~VolumeCatcherImpl() -{ - cleanup(); -} - -// static void debugClear() -// { -// auto file = fopen("/home/maki/git/firestorm-viewer/log.txt", "w"); -// fprintf(file, "\n"); -// fclose(file); -// } - -// static void debugPrint(const char* format, ...) -// { -// va_list args; -// va_start(args, format); -// auto file = fopen("/home/maki/git/firestorm-viewer/log.txt", "a"); -// vfprintf(file, format, args); -// fclose(file); -// va_end(args); -// } - -static void registryEventGlobal( - void *data, uint32_t id, uint32_t permissions, const char *type, - uint32_t version, const struct spa_dict *props -) -{ - static_cast(data)->handleRegistryEventGlobal( - id, permissions, type, version, props - ); -} - -static const struct pw_registry_events REGISTRY_EVENTS = { - .version = PW_VERSION_REGISTRY_EVENTS, - .global = registryEventGlobal, -}; - -bool VolumeCatcherImpl::loadsyms(std::string pw_dso_name) -{ - return gSymbolGrabber.grabSymbols({ pw_dso_name }); -} - -void VolumeCatcherImpl::init() -{ - // debugClear(); - // debugPrint("init\n"); - - mGotSyms = loadsyms("libpipewire-0.3.so.0"); - if (!mGotSyms) return; - - llpw_init(NULL, NULL); - - mThreadLoop = llpw_thread_loop_new("SL Plugin Volume Adjuster", NULL); - if (!mThreadLoop) return; - - pwLock(); - - mContext = llpw_context_new( - llpw_thread_loop_get_loop(mThreadLoop), NULL, 0 - ); - if (!mContext) return pwUnlock(); - - mCore = llpw_context_connect(mContext, NULL, 0); - if (!mCore) return pwUnlock(); - - mRegistry = pw_core_get_registry(mCore, PW_VERSION_REGISTRY, 0); - - // debugPrint("got registry\n"); - - spa_zero(mRegistryListener); - - pw_registry_add_listener( - mRegistry, &mRegistryListener, ®ISTRY_EVENTS, this - ); - - llpw_thread_loop_start(mThreadLoop); - - pwUnlock(); - - // debugPrint("started thread loop\n"); -} - -void VolumeCatcherImpl::cleanup() -{ - mChildNodesMutex.lock(); - for (auto* childNode : mChildNodes) { - childNode->destroy(); - } - mChildNodes.clear(); - mChildNodesMutex.unlock(); - - pwLock(); - if (mRegistry) llpw_proxy_destroy((struct pw_proxy*)mRegistry); - spa_zero(mRegistryListener); - if (mCore) llpw_core_disconnect(mCore); - if (mContext) llpw_context_destroy(mContext); - pwUnlock(); - - if (!mThreadLoop) return; - - llpw_thread_loop_stop(mThreadLoop); - llpw_thread_loop_destroy(mThreadLoop); - - // debugPrint("cleanup done\n"); -} - -void VolumeCatcherImpl::pwLock() { - if (!mThreadLoop) return; - llpw_thread_loop_lock(mThreadLoop); -} - -void VolumeCatcherImpl::pwUnlock() { - if (!mThreadLoop) return; - llpw_thread_loop_unlock(mThreadLoop); -} - -// #define INV_LERP(a, b, v) (v - a) / (b - a) - -// #include - -void VolumeCatcherImpl::ChildNode::updateVolume() -{ - if (!mActive) return; - - F32 volume = std::clamp(mImpl->mVolume, 0.0f, 1.0f); - // F32 pan = std::clamp(mImpl->mPan, -1.0f, 1.0f); - - // debugClear(); - // struct timeval time; - // gettimeofday(&time, NULL); - // double t = (double)time.tv_sec + (double)(time.tv_usec / 1000) / 1000; - // debugPrint("time is %f\n", t); - // F32 pan = std::sin(t * 2.0d); - // debugPrint("pan is %f\n", pan); - - // uint32_t channels = 2; - // float volumes[channels]; - // volumes[1] = INV_LERP(-1.0f, 0.0f, pan) * volume; // left - // volumes[0] = INV_LERP(1.0f, 0.0f, pan) * volume; // right - - uint32_t channels = 1; - float volumes[channels]; - volumes[0] = volume; - - uint8_t buffer[512]; - - spa_pod_builder builder; - spa_pod_builder_init(&builder, buffer, sizeof(buffer)); - - spa_pod_frame frame; - spa_pod_builder_push_object(&builder, &frame, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); - spa_pod_builder_prop(&builder, SPA_PROP_channelVolumes, 0); - spa_pod_builder_array(&builder, sizeof(float), SPA_TYPE_Float, channels, volumes); - spa_pod* pod = static_cast(spa_pod_builder_pop(&builder, &frame)); - - mImpl->pwLock(); - pw_node_set_param(mProxy, SPA_PARAM_Props, 0, pod); - mImpl->pwUnlock(); -} - -void VolumeCatcherImpl::ChildNode::destroy() -{ - if (!mActive) return; - mActive = false; - - mImpl->mChildNodesMutex.lock(); - mImpl->mChildNodes.erase(this); - mImpl->mChildNodesMutex.unlock(); - - spa_hook_remove(&mNodeListener); - spa_hook_remove(&mProxyListener); - - mImpl->pwLock(); - llpw_proxy_destroy(mProxy); - mImpl->pwUnlock(); -} - -static pid_t getParentPid(pid_t aPid) -{ - std::stringstream strm; - strm << "/proc/" << aPid << "/status"; - std::ifstream in{ strm.str() }; - - if (!in.is_open()) return 0; - - pid_t res {0}; - while (!in.eof() && res == 0) - { - std::string line; - line.resize( 1024, 0 ); - in.getline( &line[0], line.length() ); - - auto i = line.find("PPid:"); - - if (i == std::string::npos) continue; - - char const *pIn = line.c_str() + 5; // Skip over pid; - while (*pIn != 0 && isspace( *pIn )) ++pIn; - - if (*pIn) res = atoll(pIn); - } - return res; -} - -static bool isPluginPid(pid_t aPid) -{ - auto myPid = getpid(); - - do - { - if(aPid == myPid) return true; - aPid = getParentPid( aPid ); - } while(aPid > 1); - - return false; -} - -static void nodeEventInfo(void* data, const struct pw_node_info* info) -{ - const char* processId = spa_dict_lookup(info->props, PW_KEY_APP_PROCESS_ID); - if (processId == nullptr) return; - - pid_t pid = atoll(processId); - if (!isPluginPid(pid)) return; - - // const char* appName = spa_dict_lookup(info->props, PW_KEY_APP_NAME); - // debugPrint("got app: %s\n", appName); - - auto* const childNode = static_cast(data); - // debugPrint("init volume to: %f\n", childNode->mImpl->mDesiredVolume); - - childNode->updateVolume(); - - childNode->mImpl->mChildNodesMutex.lock(); - childNode->mImpl->mChildNodes.insert(childNode); - childNode->mImpl->mChildNodesMutex.unlock(); -} - -static const struct pw_node_events NODE_EVENTS = { - .version = PW_VERSION_CLIENT_EVENTS, - .info = nodeEventInfo, -}; - -static void proxyEventDestroy(void* data) -{ - auto* const childNode = static_cast(data); - childNode->destroy(); -} - -static void proxyEventRemoved(void* data) -{ - auto* const childNode = static_cast(data); - childNode->destroy(); -} - -static const struct pw_proxy_events PROXY_EVENTS = { - .version = PW_VERSION_PROXY_EVENTS, - .destroy = proxyEventDestroy, - .removed = proxyEventRemoved, -}; - -void VolumeCatcherImpl::handleRegistryEventGlobal( - uint32_t id, uint32_t permissions, const char *type, uint32_t version, - const struct spa_dict *props -) { - if (props == nullptr || strcmp(type, PW_TYPE_INTERFACE_Node) != 0) return; - - const char* mediaClass = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); - if (mediaClass == nullptr || strcmp(mediaClass, "Stream/Output/Audio") != 0) return; - - pw_proxy* proxy = static_cast( - pw_registry_bind(mRegistry, id, type, PW_VERSION_CLIENT, sizeof(ChildNode)) - ); - - auto* const childNode = static_cast(llpw_proxy_get_user_data(proxy)); - - childNode->mActive = true; - childNode->mProxy = proxy; - childNode->mImpl = this; - - pw_node_add_listener(proxy, &childNode->mNodeListener, &NODE_EVENTS, childNode); - llpw_proxy_add_listener(proxy, &childNode->mProxyListener, &PROXY_EVENTS, childNode); -} - -void VolumeCatcherImpl::setVolume(F32 volume) -{ - // debugPrint("setting all volume to: %f\n", volume); - - mVolume = volume; - - mChildNodesMutex.lock(); - std::unordered_set copyOfChildNodes(mChildNodes); - mChildNodesMutex.unlock(); - - // debugPrint("for %d nodes\n", copyOfChildNodes.size()); - - for (auto* childNode : copyOfChildNodes) { - childNode->updateVolume(); - } -} - -// void VolumeCatcherImpl::setPan(F32 pan) -// { -// mPan = pan; -// setVolume(mVolume); -// } - -///////////////////////////////////////////////////// - -VolumeCatcher::VolumeCatcher() -{ - pimpl = new VolumeCatcherImpl(); -} - -VolumeCatcher::~VolumeCatcher() -{ - delete pimpl; - pimpl = nullptr; -} - -void VolumeCatcher::setVolume(F32 volume) -{ - llassert(pimpl); - pimpl->setVolume(volume); -} - -void VolumeCatcher::setPan(F32 pan) -{ - // llassert(pimpl); - // pimpl->setPan(pan); -} - -void VolumeCatcher::pump() -{ -} diff --git a/indra/media_plugins/cef/linux_volume_catcher_pw_syms.inc b/indra/media_plugins/cef/linux_volume_catcher_pw_syms.inc deleted file mode 100644 index 14e4a42f1d..0000000000 --- a/indra/media_plugins/cef/linux_volume_catcher_pw_syms.inc +++ /dev/null @@ -1,22 +0,0 @@ -// required symbols to grab -LL_GRAB_SYM(true, pw_init, void, int *argc, char **argv[]); -// LL_GRAB_SYM(true, pw_main_loop_new, struct pw_main_loop *, const struct spa_dict *props); -// LL_GRAB_SYM(true, pw_main_loop_get_loop, struct pw_loop *, struct pw_main_loop *loop); -// LL_GRAB_SYM(true, pw_main_loop_destroy, void, struct pw_main_loop *loop); -// LL_GRAB_SYM(true, pw_main_loop_run, void, struct pw_main_loop *loop); -LL_GRAB_SYM(true, pw_context_new, struct pw_context *, struct pw_loop *main_loop, struct pw_properties *props, size_t user_data_size); -LL_GRAB_SYM(true, pw_context_destroy, void, struct pw_context *context); -LL_GRAB_SYM(true, pw_context_connect, struct pw_core *, struct pw_context *context, struct pw_properties *properties, size_t user_data_size); -LL_GRAB_SYM(true, pw_thread_loop_new, struct pw_thread_loop *, const char *name, const struct spa_dict *props); -LL_GRAB_SYM(true, pw_thread_loop_destroy, void, struct pw_thread_loop *loop); -LL_GRAB_SYM(true, pw_thread_loop_get_loop, struct pw_loop *, struct pw_thread_loop *loop); -LL_GRAB_SYM(true, pw_thread_loop_start, int, struct pw_thread_loop *loop); -LL_GRAB_SYM(true, pw_thread_loop_stop, void, struct pw_thread_loop *loop); -LL_GRAB_SYM(true, pw_thread_loop_lock, void, struct pw_thread_loop *loop); -LL_GRAB_SYM(true, pw_thread_loop_unlock, void, struct pw_thread_loop *loop); -LL_GRAB_SYM(true, pw_proxy_add_listener, void, struct pw_proxy *proxy, struct spa_hook *listener, const struct pw_proxy_events *events, void *data); -LL_GRAB_SYM(true, pw_proxy_destroy, void, struct pw_proxy *proxy); -LL_GRAB_SYM(true, pw_proxy_get_user_data, void *, struct pw_proxy *proxy); -LL_GRAB_SYM(true, pw_core_disconnect, int, struct pw_core *core); - -// optional symbols to grab diff --git a/indra/media_plugins/cef/media_plugin_cef.cpp b/indra/media_plugins/cef/media_plugin_cef.cpp index 60d91753d0..3cd9e620db 100644 --- a/indra/media_plugins/cef/media_plugin_cef.cpp +++ b/indra/media_plugins/cef/media_plugin_cef.cpp @@ -908,6 +908,13 @@ void MediaPluginCEF::receiveMessage(const char* message_string) { mEnableMediaPluginDebugging = message_in.getValueBoolean("enable"); } +#if LL_LINUX + else if (message_name == "enable_pipewire_volume_catcher") + { + bool enable = message_in.getValueBoolean("enable"); + mVolumeCatcher.onEnablePipeWireVolumeCatcher(enable); + } +#endif if (message_name == "pick_file_response") { LLSD file_list_llsd = message_in.getValueLLSD("file_list"); diff --git a/indra/media_plugins/cef/volume_catcher.h b/indra/media_plugins/cef/volume_catcher.h index 337f2913d3..0c644b8b42 100644 --- a/indra/media_plugins/cef/volume_catcher.h +++ b/indra/media_plugins/cef/volume_catcher.h @@ -31,24 +31,34 @@ #include "linden_common.h" -class VolumeCatcherImpl; +class VolumeCatcherImpl +{ +public: + virtual void setVolume(F32 volume) = 0; // 0.0 - 1.0 + + // Set the left-right pan of audio sources + // where -1.0 = left, 0 = center, and 1.0 = right + virtual void setPan(F32 pan) = 0; + + virtual void pump() = 0; // call this at least a few times a second if you can - it affects how quickly we can 'catch' a new audio source and adjust its volume +}; -class VolumeCatcher +class VolumeCatcher : public virtual VolumeCatcherImpl { - public: +public: VolumeCatcher(); ~VolumeCatcher(); - void setVolume(F32 volume); // 0.0 - 1.0 - - // Set the left-right pan of audio sources - // where -1.0 = left, 0 = center, and 1.0 = right - void setPan(F32 pan); + void setVolume(F32 volume); + void setPan(F32 pan); - void pump(); // call this at least a few times a second if you can - it affects how quickly we can 'catch' a new audio source and adjust its volume - - private: + void pump(); + + void onEnablePipeWireVolumeCatcher(bool enable); + +private: VolumeCatcherImpl *pimpl; + VolumeCatcherImpl *pimpl2; }; #endif // VOLUME_CATCHER_H -- cgit v1.2.3 From 576508ebab789207c5d82c13ff627cb623d74994 Mon Sep 17 00:00:00 2001 From: Maki Date: Sat, 20 Apr 2024 17:35:30 -0400 Subject: Use RAII for mutexes for PipeWire volume catcher --- .../media_plugins/cef/linux/volume_catcher_linux.h | 4 +-- .../cef/linux/volume_catcher_pipewire.cpp | 39 +++++++++++----------- 2 files changed, 21 insertions(+), 22 deletions(-) (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/linux/volume_catcher_linux.h b/indra/media_plugins/cef/linux/volume_catcher_linux.h index 5149dd62e0..0f11c537b1 100644 --- a/indra/media_plugins/cef/linux/volume_catcher_linux.h +++ b/indra/media_plugins/cef/linux/volume_catcher_linux.h @@ -90,8 +90,8 @@ public: // some of these should be private - void pwLock(); - void pwUnlock(); + void lock(); + void unlock(); void setVolume(F32 volume); void setPan(F32 pan); diff --git a/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp index a779cf868e..c05054cf52 100755 --- a/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp +++ b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp @@ -100,19 +100,20 @@ void VolumeCatcherPipeWire::init() if (!mThreadLoop) return; - pwLock(); + // i dont think we need to lock this early + // std::lock_guard pwLock(*this); mContext = llpw_context_new( llpw_thread_loop_get_loop(mThreadLoop), NULL, 0 ); if (!mContext) - return pwUnlock(); + return; mCore = llpw_context_connect(mContext, NULL, 0); if (!mCore) - return pwUnlock(); + return; mRegistry = pw_core_get_registry(mCore, PW_VERSION_REGISTRY, 0); @@ -126,26 +127,24 @@ void VolumeCatcherPipeWire::init() llpw_thread_loop_start(mThreadLoop); - pwUnlock(); - // debugPrint("started thread loop\n"); } void VolumeCatcherPipeWire::cleanup() { - mChildNodesMutex.lock(); + std::unique_lock childNodesLock(mChildNodesMutex); for (auto* childNode : mChildNodes) { childNode->destroy(); } mChildNodes.clear(); - mChildNodesMutex.unlock(); + childNodesLock.unlock(); - pwLock(); + std::unique_lock pwLock(*this); if (mRegistry) llpw_proxy_destroy((struct pw_proxy*)mRegistry); spa_zero(mRegistryListener); if (mCore) llpw_core_disconnect(mCore); if (mContext) llpw_context_destroy(mContext); - pwUnlock(); + pwLock.unlock(); if (!mThreadLoop) return; @@ -156,16 +155,16 @@ void VolumeCatcherPipeWire::cleanup() // debugPrint("cleanup done\n"); } -void VolumeCatcherPipeWire::pwLock() { +void VolumeCatcherPipeWire::lock() { if (!mThreadLoop) return; llpw_thread_loop_lock(mThreadLoop); } -void VolumeCatcherPipeWire::pwUnlock() { +void VolumeCatcherPipeWire::unlock() { if (!mThreadLoop) - return; + return; llpw_thread_loop_unlock(mThreadLoop); } @@ -210,9 +209,9 @@ void VolumeCatcherPipeWire::ChildNode::updateVolume() spa_pod_builder_array(&builder, sizeof(float), SPA_TYPE_Float, channels, volumes); spa_pod* pod = static_cast(spa_pod_builder_pop(&builder, &frame)); - mImpl->pwLock(); + std::lock_guard pwLock(*mImpl); + pw_node_set_param(mProxy, SPA_PARAM_Props, 0, pod); - mImpl->pwUnlock(); } void VolumeCatcherPipeWire::ChildNode::destroy() @@ -229,9 +228,9 @@ void VolumeCatcherPipeWire::ChildNode::destroy() spa_hook_remove(&mNodeListener); spa_hook_remove(&mProxyListener); - mImpl->pwLock(); + std::lock_guard pwLock(*mImpl); + llpw_proxy_destroy(mProxy); - mImpl->pwUnlock(); } static void nodeEventInfo(void* data, const struct pw_node_info* info) @@ -254,9 +253,9 @@ static void nodeEventInfo(void* data, const struct pw_node_info* info) childNode->updateVolume(); - childNode->mImpl->mChildNodesMutex.lock(); + std::lock_guard childNodesLock(childNode->mImpl->mChildNodesMutex); + childNode->mImpl->mChildNodes.insert(childNode); - childNode->mImpl->mChildNodesMutex.unlock(); } static const struct pw_node_events NODE_EVENTS = { @@ -314,9 +313,9 @@ void VolumeCatcherPipeWire::setVolume(F32 volume) mVolume = volume; - mChildNodesMutex.lock(); + std::unique_lock childNodeslock(mChildNodesMutex); std::unordered_set copyOfChildNodes(mChildNodes); - mChildNodesMutex.unlock(); + childNodeslock.unlock(); // debugPrint("for %d nodes\n", copyOfChildNodes.size()); -- cgit v1.2.3 From e6ca21549ee1d4a23c82e12d9215ce993d99ca3e Mon Sep 17 00:00:00 2001 From: Maki Date: Sat, 20 Apr 2024 17:50:43 -0400 Subject: Only use one impl in Linux volume catcher --- .../cef/linux/volume_catcher_linux.cpp | 59 +++++++--------------- indra/media_plugins/cef/volume_catcher.h | 1 - 2 files changed, 19 insertions(+), 41 deletions(-) (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/linux/volume_catcher_linux.cpp b/indra/media_plugins/cef/linux/volume_catcher_linux.cpp index 86cc329e66..3c037b45ba 100644 --- a/indra/media_plugins/cef/linux/volume_catcher_linux.cpp +++ b/indra/media_plugins/cef/linux/volume_catcher_linux.cpp @@ -28,9 +28,6 @@ #include "volume_catcher_linux.h" -#define PULSEAUDIO pimpl -#define PIPEWIRE pimpl2 - //////////////////////////////////////////////////// VolumeCatcher::VolumeCatcher() @@ -42,62 +39,44 @@ VolumeCatcher::VolumeCatcher() } void VolumeCatcher::onEnablePipeWireVolumeCatcher(bool enable) { - if (enable) { - // load pipewire - - if (PULSEAUDIO != nullptr) { - delete PULSEAUDIO; - PULSEAUDIO = nullptr; - } + if (pimpl != nullptr) + return; - if (PIPEWIRE == nullptr) { - // debugPrint("volume catcher using pipewire\n"); - PIPEWIRE = new VolumeCatcherPipeWire(); - } + if (enable) { + // debugPrint("volume catcher using pipewire\n"); + pimpl = new VolumeCatcherPipeWire(); } else { - // load pulseaudio - - if (PIPEWIRE != nullptr) { - delete PIPEWIRE; - PIPEWIRE = nullptr; - } - - if (PULSEAUDIO == nullptr) { - // debugPrint("volume catcher using pulseaudio\n"); - PULSEAUDIO = new VolumeCatcherPulseAudio(); - } + // debugPrint("volume catcher using pulseaudio\n"); + pimpl = new VolumeCatcherPulseAudio(); } } VolumeCatcher::~VolumeCatcher() { - if (PULSEAUDIO != nullptr) { - delete PULSEAUDIO; - PULSEAUDIO = nullptr; - } - - if (PIPEWIRE != nullptr) { - delete PIPEWIRE; - PIPEWIRE = nullptr; + if (pimpl != nullptr) { + delete pimpl; + pimpl = nullptr; } } void VolumeCatcher::setVolume(F32 volume) { - if (PULSEAUDIO != nullptr) PULSEAUDIO->setVolume(volume); - if (PIPEWIRE != nullptr) PIPEWIRE->setVolume(volume); + if (pimpl != nullptr) { + pimpl->setVolume(volume); + } } void VolumeCatcher::setPan(F32 pan) { - // not implemented for both - // if (PULSEAUDIO) PULSEAUDIO->setPan(pan); - // if (PIPEWIRE) PIPEWIRE->setPan(pan); + if (pimpl != nullptr) { + pimpl->setPan(pan); + } } void VolumeCatcher::pump() { - if (PULSEAUDIO != nullptr) PULSEAUDIO->pump(); - if (PIPEWIRE != nullptr) PIPEWIRE->pump(); // doesn't need it + if (pimpl != nullptr) { + pimpl->pump(); + } } diff --git a/indra/media_plugins/cef/volume_catcher.h b/indra/media_plugins/cef/volume_catcher.h index 0c644b8b42..e11ecf5881 100644 --- a/indra/media_plugins/cef/volume_catcher.h +++ b/indra/media_plugins/cef/volume_catcher.h @@ -58,7 +58,6 @@ public: private: VolumeCatcherImpl *pimpl; - VolumeCatcherImpl *pimpl2; }; #endif // VOLUME_CATCHER_H -- cgit v1.2.3 From 90cf3ee30bd082d1352a3182f598bc0d3860ef8b Mon Sep 17 00:00:00 2001 From: Maki Date: Sat, 20 Apr 2024 17:55:34 -0400 Subject: Fix more style issues and add missing RAII lock --- indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp index c05054cf52..213efbfcfb 100755 --- a/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp +++ b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp @@ -140,10 +140,13 @@ void VolumeCatcherPipeWire::cleanup() childNodesLock.unlock(); std::unique_lock pwLock(*this); - if (mRegistry) llpw_proxy_destroy((struct pw_proxy*)mRegistry); + if (mRegistry) + llpw_proxy_destroy((struct pw_proxy*)mRegistry); spa_zero(mRegistryListener); - if (mCore) llpw_core_disconnect(mCore); - if (mContext) llpw_context_destroy(mContext); + if (mCore) + llpw_core_disconnect(mCore); + if (mContext) + llpw_context_destroy(mContext); pwLock.unlock(); if (!mThreadLoop) @@ -221,9 +224,9 @@ void VolumeCatcherPipeWire::ChildNode::destroy() mActive = false; - mImpl->mChildNodesMutex.lock(); + std::unique_lock childNodesLock(mImpl->mChildNodesMutex); mImpl->mChildNodes.erase(this); - mImpl->mChildNodesMutex.unlock(); + childNodesLock.unlock(); spa_hook_remove(&mNodeListener); spa_hook_remove(&mProxyListener); -- cgit v1.2.3 From d09ec5e87bfd892306ccdba56944583bd9213aaf Mon Sep 17 00:00:00 2001 From: Maki Date: Sat, 20 Apr 2024 18:34:09 -0400 Subject: Rework macro magic for symbol grabber --- .../cef/linux/volume_catcher_pipewire.cpp | 2 - .../cef/linux/volume_catcher_pipewire_syms.inc | 42 ++++++++++--------- .../cef/linux/volume_catcher_pulseaudio.cpp | 6 +-- .../linux/volume_catcher_pulseaudio_glib_syms.inc | 10 +++-- .../cef/linux/volume_catcher_pulseaudio_syms.inc | 48 ++++++++++++---------- 5 files changed, 59 insertions(+), 49 deletions(-) (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp index 213efbfcfb..ca1808d63c 100755 --- a/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp +++ b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp @@ -45,8 +45,6 @@ extern "C" { } SymbolGrabber pwSymbolGrabber; -#undef LL_SYMBOL_GRABBER -#define LL_SYMBOL_GRABBER pwSymbolGrabber #include "volume_catcher_pipewire_syms.inc" diff --git a/indra/media_plugins/cef/linux/volume_catcher_pipewire_syms.inc b/indra/media_plugins/cef/linux/volume_catcher_pipewire_syms.inc index 14e4a42f1d..dbc0f5f169 100644 --- a/indra/media_plugins/cef/linux/volume_catcher_pipewire_syms.inc +++ b/indra/media_plugins/cef/linux/volume_catcher_pipewire_syms.inc @@ -1,22 +1,26 @@ +#define G pwSymbolGrabber + // required symbols to grab -LL_GRAB_SYM(true, pw_init, void, int *argc, char **argv[]); -// LL_GRAB_SYM(true, pw_main_loop_new, struct pw_main_loop *, const struct spa_dict *props); -// LL_GRAB_SYM(true, pw_main_loop_get_loop, struct pw_loop *, struct pw_main_loop *loop); -// LL_GRAB_SYM(true, pw_main_loop_destroy, void, struct pw_main_loop *loop); -// LL_GRAB_SYM(true, pw_main_loop_run, void, struct pw_main_loop *loop); -LL_GRAB_SYM(true, pw_context_new, struct pw_context *, struct pw_loop *main_loop, struct pw_properties *props, size_t user_data_size); -LL_GRAB_SYM(true, pw_context_destroy, void, struct pw_context *context); -LL_GRAB_SYM(true, pw_context_connect, struct pw_core *, struct pw_context *context, struct pw_properties *properties, size_t user_data_size); -LL_GRAB_SYM(true, pw_thread_loop_new, struct pw_thread_loop *, const char *name, const struct spa_dict *props); -LL_GRAB_SYM(true, pw_thread_loop_destroy, void, struct pw_thread_loop *loop); -LL_GRAB_SYM(true, pw_thread_loop_get_loop, struct pw_loop *, struct pw_thread_loop *loop); -LL_GRAB_SYM(true, pw_thread_loop_start, int, struct pw_thread_loop *loop); -LL_GRAB_SYM(true, pw_thread_loop_stop, void, struct pw_thread_loop *loop); -LL_GRAB_SYM(true, pw_thread_loop_lock, void, struct pw_thread_loop *loop); -LL_GRAB_SYM(true, pw_thread_loop_unlock, void, struct pw_thread_loop *loop); -LL_GRAB_SYM(true, pw_proxy_add_listener, void, struct pw_proxy *proxy, struct spa_hook *listener, const struct pw_proxy_events *events, void *data); -LL_GRAB_SYM(true, pw_proxy_destroy, void, struct pw_proxy *proxy); -LL_GRAB_SYM(true, pw_proxy_get_user_data, void *, struct pw_proxy *proxy); -LL_GRAB_SYM(true, pw_core_disconnect, int, struct pw_core *core); +LL_GRAB_SYM(G, true, pw_init, void, int *argc, char **argv[]); +// LL_GRAB_SYM(G, true, pw_main_loop_new, struct pw_main_loop *, const struct spa_dict *props); +// LL_GRAB_SYM(G, true, pw_main_loop_get_loop, struct pw_loop *, struct pw_main_loop *loop); +// LL_GRAB_SYM(G, true, pw_main_loop_destroy, void, struct pw_main_loop *loop); +// LL_GRAB_SYM(G, true, pw_main_loop_run, void, struct pw_main_loop *loop); +LL_GRAB_SYM(G, true, pw_context_new, struct pw_context *, struct pw_loop *main_loop, struct pw_properties *props, size_t user_data_size); +LL_GRAB_SYM(G, true, pw_context_destroy, void, struct pw_context *context); +LL_GRAB_SYM(G, true, pw_context_connect, struct pw_core *, struct pw_context *context, struct pw_properties *properties, size_t user_data_size); +LL_GRAB_SYM(G, true, pw_thread_loop_new, struct pw_thread_loop *, const char *name, const struct spa_dict *props); +LL_GRAB_SYM(G, true, pw_thread_loop_destroy, void, struct pw_thread_loop *loop); +LL_GRAB_SYM(G, true, pw_thread_loop_get_loop, struct pw_loop *, struct pw_thread_loop *loop); +LL_GRAB_SYM(G, true, pw_thread_loop_start, int, struct pw_thread_loop *loop); +LL_GRAB_SYM(G, true, pw_thread_loop_stop, void, struct pw_thread_loop *loop); +LL_GRAB_SYM(G, true, pw_thread_loop_lock, void, struct pw_thread_loop *loop); +LL_GRAB_SYM(G, true, pw_thread_loop_unlock, void, struct pw_thread_loop *loop); +LL_GRAB_SYM(G, true, pw_proxy_add_listener, void, struct pw_proxy *proxy, struct spa_hook *listener, const struct pw_proxy_events *events, void *data); +LL_GRAB_SYM(G, true, pw_proxy_destroy, void, struct pw_proxy *proxy); +LL_GRAB_SYM(G, true, pw_proxy_get_user_data, void *, struct pw_proxy *proxy); +LL_GRAB_SYM(G, true, pw_core_disconnect, int, struct pw_core *core); // optional symbols to grab + +#undef G diff --git a/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp index 20a6458ea3..f7f64ef76c 100755 --- a/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp +++ b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp @@ -49,8 +49,6 @@ extern "C" { } SymbolGrabber paSymbolGrabber; -#undef LL_SYMBOL_GRABBER -#define LL_SYMBOL_GRABBER paSymbolGrabber #include "volume_catcher_pulseaudio_syms.inc" #include "volume_catcher_pulseaudio_glib_syms.inc" @@ -95,7 +93,9 @@ void VolumeCatcherPulseAudio::init() // probably be loaded separately. Our Linux DSO framework needs refactoring, // we do this sort of thing a lot with practically identical logic... mGotSyms = loadsyms("libpulse-mainloop-glib.so.0"); - if (!mGotSyms) mGotSyms = loadsyms("libpulse.so.0"); + + if (!mGotSyms) + mGotSyms = loadsyms("libpulse.so.0"); if (!mGotSyms) return; diff --git a/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_glib_syms.inc b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_glib_syms.inc index 5fba60c188..e9b7196e51 100755 --- a/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_glib_syms.inc +++ b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_glib_syms.inc @@ -1,6 +1,10 @@ +#define G paSymbolGrabber + // required symbols to grab -LL_GRAB_SYM(true, pa_glib_mainloop_free, void, pa_glib_mainloop* g) -LL_GRAB_SYM(true, pa_glib_mainloop_get_api, pa_mainloop_api*, pa_glib_mainloop* g) -LL_GRAB_SYM(true, pa_glib_mainloop_new, pa_glib_mainloop *, GMainContext *c) +LL_GRAB_SYM(G, true, pa_glib_mainloop_free, void, pa_glib_mainloop* g) +LL_GRAB_SYM(G, true, pa_glib_mainloop_get_api, pa_mainloop_api*, pa_glib_mainloop* g) +LL_GRAB_SYM(G, true, pa_glib_mainloop_new, pa_glib_mainloop *, GMainContext *c) // optional symbols to grab + +#undef G diff --git a/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_syms.inc b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_syms.inc index fe4ad084b6..4859a34405 100755 --- a/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_syms.inc +++ b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_syms.inc @@ -1,25 +1,29 @@ +#define G paSymbolGrabber + // required symbols to grab -LL_GRAB_SYM(true, pa_context_connect, int, pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api) -LL_GRAB_SYM(true, pa_context_disconnect, void, pa_context *c) -LL_GRAB_SYM(true, pa_context_get_sink_input_info, pa_operation*, pa_context *c, uint32_t idx, pa_sink_input_info_cb_t cb, void *userdata) -LL_GRAB_SYM(true, pa_context_get_sink_input_info_list, pa_operation*, pa_context *c, pa_sink_input_info_cb_t cb, void *userdata) -LL_GRAB_SYM(true, pa_context_get_state, pa_context_state_t, pa_context *c) -LL_GRAB_SYM(true, pa_context_new_with_proplist, pa_context*, pa_mainloop_api *mainloop, const char *name, pa_proplist *proplist) -LL_GRAB_SYM(true, pa_context_set_sink_input_volume, pa_operation*, pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) -LL_GRAB_SYM(true, pa_context_set_state_callback, void, pa_context *c, pa_context_notify_cb_t cb, void *userdata) -LL_GRAB_SYM(true, pa_context_set_subscribe_callback, void, pa_context *c, pa_context_subscribe_cb_t cb, void *userdata) -LL_GRAB_SYM(true, pa_context_subscribe, pa_operation*, pa_context *c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void *userdata) -LL_GRAB_SYM(true, pa_context_unref, void, pa_context *c) -LL_GRAB_SYM(true, pa_cvolume_set, pa_cvolume*, pa_cvolume *a, unsigned channels, pa_volume_t v) -LL_GRAB_SYM(true, pa_operation_unref, void, pa_operation *o) -LL_GRAB_SYM(true, pa_proplist_free, void, pa_proplist* p) -LL_GRAB_SYM(true, pa_proplist_gets, const char*, pa_proplist *p, const char *key) -LL_GRAB_SYM(true, pa_proplist_new, pa_proplist*, void) -LL_GRAB_SYM(true, pa_proplist_sets, int, pa_proplist *p, const char *key, const char *value) -LL_GRAB_SYM(true, pa_sw_volume_from_linear, pa_volume_t, double v) -// LL_GRAB_SYM(true, pa_mainloop_free, void, pa_mainloop *m) -// LL_GRAB_SYM(true, pa_mainloop_get_api, pa_mainloop_api *, pa_mainloop *m) -// LL_GRAB_SYM(true, pa_mainloop_iterate, int, pa_mainloop *m, int block, int *retval) -// LL_GRAB_SYM(true, pa_mainloop_new, pa_mainloop *, void) +LL_GRAB_SYM(G, true, pa_context_connect, int, pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api) +LL_GRAB_SYM(G, true, pa_context_disconnect, void, pa_context *c) +LL_GRAB_SYM(G, true, pa_context_get_sink_input_info, pa_operation*, pa_context *c, uint32_t idx, pa_sink_input_info_cb_t cb, void *userdata) +LL_GRAB_SYM(G, true, pa_context_get_sink_input_info_list, pa_operation*, pa_context *c, pa_sink_input_info_cb_t cb, void *userdata) +LL_GRAB_SYM(G, true, pa_context_get_state, pa_context_state_t, pa_context *c) +LL_GRAB_SYM(G, true, pa_context_new_with_proplist, pa_context*, pa_mainloop_api *mainloop, const char *name, pa_proplist *proplist) +LL_GRAB_SYM(G, true, pa_context_set_sink_input_volume, pa_operation*, pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) +LL_GRAB_SYM(G, true, pa_context_set_state_callback, void, pa_context *c, pa_context_notify_cb_t cb, void *userdata) +LL_GRAB_SYM(G, true, pa_context_set_subscribe_callback, void, pa_context *c, pa_context_subscribe_cb_t cb, void *userdata) +LL_GRAB_SYM(G, true, pa_context_subscribe, pa_operation*, pa_context *c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void *userdata) +LL_GRAB_SYM(G, true, pa_context_unref, void, pa_context *c) +LL_GRAB_SYM(G, true, pa_cvolume_set, pa_cvolume*, pa_cvolume *a, unsigned channels, pa_volume_t v) +LL_GRAB_SYM(G, true, pa_operation_unref, void, pa_operation *o) +LL_GRAB_SYM(G, true, pa_proplist_free, void, pa_proplist* p) +LL_GRAB_SYM(G, true, pa_proplist_gets, const char*, pa_proplist *p, const char *key) +LL_GRAB_SYM(G, true, pa_proplist_new, pa_proplist*, void) +LL_GRAB_SYM(G, true, pa_proplist_sets, int, pa_proplist *p, const char *key, const char *value) +LL_GRAB_SYM(G, true, pa_sw_volume_from_linear, pa_volume_t, double v) +// LL_GRAB_SYM(G, true, pa_mainloop_free, void, pa_mainloop *m) +// LL_GRAB_SYM(G, true, pa_mainloop_get_api, pa_mainloop_api *, pa_mainloop *m) +// LL_GRAB_SYM(G, true, pa_mainloop_iterate, int, pa_mainloop *m, int block, int *retval) +// LL_GRAB_SYM(G, true, pa_mainloop_new, pa_mainloop *, void) // optional symbols to grab + +#undef G -- cgit v1.2.3 From cbd64336eac12f03013fd91c8068810b5239af82 Mon Sep 17 00:00:00 2001 From: Maki Date: Sun, 21 Apr 2024 20:25:31 -0400 Subject: Remove virtual inheritence for linux volume catcher --- indra/media_plugins/cef/linux/volume_catcher_linux.h | 4 ++-- indra/media_plugins/cef/volume_catcher.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/linux/volume_catcher_linux.h b/indra/media_plugins/cef/linux/volume_catcher_linux.h index 0f11c537b1..b7b4350ba8 100644 --- a/indra/media_plugins/cef/linux/volume_catcher_linux.h +++ b/indra/media_plugins/cef/linux/volume_catcher_linux.h @@ -49,7 +49,7 @@ extern "C" { #include "media_plugin_base.h" -class VolumeCatcherPulseAudio : public virtual VolumeCatcherImpl +class VolumeCatcherPulseAudio : public VolumeCatcherImpl { public: VolumeCatcherPulseAudio(); @@ -78,7 +78,7 @@ public: bool mGotSyms; }; -class VolumeCatcherPipeWire : public virtual VolumeCatcherImpl +class VolumeCatcherPipeWire : public VolumeCatcherImpl { public: VolumeCatcherPipeWire(); diff --git a/indra/media_plugins/cef/volume_catcher.h b/indra/media_plugins/cef/volume_catcher.h index e11ecf5881..3e53a7e961 100644 --- a/indra/media_plugins/cef/volume_catcher.h +++ b/indra/media_plugins/cef/volume_catcher.h @@ -43,7 +43,7 @@ public: virtual void pump() = 0; // call this at least a few times a second if you can - it affects how quickly we can 'catch' a new audio source and adjust its volume }; -class VolumeCatcher : public virtual VolumeCatcherImpl +class VolumeCatcher : public VolumeCatcherImpl { public: VolumeCatcher(); -- cgit v1.2.3 From 920fd136f4b3aa2c4111173432e7040613737bc8 Mon Sep 17 00:00:00 2001 From: Nicky Date: Sun, 21 Apr 2024 18:50:38 +0200 Subject: Stream the volume catcher a little: - Use LL_DEBUGS() for potential debug output. - Enclose mutex locking in their own scope, to make unlocking automatic and also limit the life time of a lock to as short as possible - Introduce mCleanupMutex to replace std::unique_lock pwLock(*this). I'm baffled using lock as a mutex like that did even compile. - Remove virtual inheritance, as it is not needed here. --- .../cef/linux/volume_catcher_linux.cpp | 28 ++-- .../media_plugins/cef/linux/volume_catcher_linux.h | 26 +--- .../cef/linux/volume_catcher_pipewire.cpp | 141 ++++++++++----------- 3 files changed, 83 insertions(+), 112 deletions(-) (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/linux/volume_catcher_linux.cpp b/indra/media_plugins/cef/linux/volume_catcher_linux.cpp index 3c037b45ba..439c0a6768 100644 --- a/indra/media_plugins/cef/linux/volume_catcher_linux.cpp +++ b/indra/media_plugins/cef/linux/volume_catcher_linux.cpp @@ -32,28 +32,29 @@ VolumeCatcher::VolumeCatcher() { - // only init once we receive which implementation to use - - // debugClear(); - // debugPrint("init volume catcher\n"); } -void VolumeCatcher::onEnablePipeWireVolumeCatcher(bool enable) { +void VolumeCatcher::onEnablePipeWireVolumeCatcher(bool enable) +{ if (pimpl != nullptr) return; - if (enable) { - // debugPrint("volume catcher using pipewire\n"); + if (enable) + { + LL_DEBUGS() << "volume catcher using pipewire" << LL_ENDL; pimpl = new VolumeCatcherPipeWire(); - } else { - // debugPrint("volume catcher using pulseaudio\n"); + } + else + { + LL_DEBUGS() << "volume catcher using pulseaudio" << LL_ENDL; pimpl = new VolumeCatcherPulseAudio(); } } VolumeCatcher::~VolumeCatcher() { - if (pimpl != nullptr) { + if (pimpl != nullptr) + { delete pimpl; pimpl = nullptr; } @@ -68,15 +69,12 @@ void VolumeCatcher::setVolume(F32 volume) void VolumeCatcher::setPan(F32 pan) { - if (pimpl != nullptr) { + if (pimpl != nullptr) pimpl->setPan(pan); - } } void VolumeCatcher::pump() { - if (pimpl != nullptr) { + if (pimpl != nullptr) pimpl->pump(); - } } - diff --git a/indra/media_plugins/cef/linux/volume_catcher_linux.h b/indra/media_plugins/cef/linux/volume_catcher_linux.h index b7b4350ba8..ff00d0672e 100644 --- a/indra/media_plugins/cef/linux/volume_catcher_linux.h +++ b/indra/media_plugins/cef/linux/volume_catcher_linux.h @@ -121,31 +121,15 @@ public: F32 mVolume = 1.0f; // max by default // F32 mPan = 0.0f; // center - pw_thread_loop* mThreadLoop; - pw_context* mContext; - pw_core* mCore; - pw_registry* mRegistry; + pw_thread_loop* mThreadLoop = nullptr; + pw_context* mContext = nullptr; + pw_core* mCore = nullptr; + pw_registry* mRegistry = nullptr; spa_hook mRegistryListener; std::unordered_set mChildNodes; std::mutex mChildNodesMutex; + std::mutex mCleanupMutex; }; -// static void debugClear() -// { -// auto file = fopen("volume-catcher-log.txt", "w"); -// fprintf(file, "\n"); -// fclose(file); -// } - -// static void debugPrint(const char* format, ...) -// { -// va_list args; -// va_start(args, format); -// auto file = fopen("volume-catcher-log.txt", "a"); -// vfprintf(file, format, args); -// fclose(file); -// va_end(args); -// } - #endif // VOLUME_CATCHER_LINUX_H diff --git a/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp index ca1808d63c..027fdb77f6 100755 --- a/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp +++ b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp @@ -62,8 +62,7 @@ VolumeCatcherPipeWire::~VolumeCatcherPipeWire() static void registryEventGlobal( void *data, uint32_t id, uint32_t permissions, const char *type, - uint32_t version, const struct spa_dict *props -) + uint32_t version, const struct spa_dict *props) { static_cast(data)->handleRegistryEventGlobal( id, permissions, type, version, props @@ -82,14 +81,14 @@ bool VolumeCatcherPipeWire::loadsyms(std::string pw_dso_name) void VolumeCatcherPipeWire::init() { - // debugPrint("init\n"); - + LL_DEBUGS() << "init" << LL_ENDL; + mGotSyms = loadsyms("libpipewire-0.3.so.0"); if (!mGotSyms) return; - // debugPrint("got syms\n"); + LL_DEBUGS() << "successfully got symbols" << LL_ENDL; llpw_init(NULL, NULL); @@ -115,7 +114,7 @@ void VolumeCatcherPipeWire::init() mRegistry = pw_core_get_registry(mCore, PW_VERSION_REGISTRY, 0); - // debugPrint("got registry\n"); + LL_DEBUGS() << "pw_core_get_registry: " << (mRegistry?"success":"nullptr") << LL_ENDL; spa_zero(mRegistryListener); @@ -125,27 +124,31 @@ void VolumeCatcherPipeWire::init() llpw_thread_loop_start(mThreadLoop); - // debugPrint("started thread loop\n"); + LL_DEBUGS() << "thread loop started" << LL_ENDL; } void VolumeCatcherPipeWire::cleanup() { - std::unique_lock childNodesLock(mChildNodesMutex); - for (auto* childNode : mChildNodes) { - childNode->destroy(); - } - mChildNodes.clear(); - childNodesLock.unlock(); - - std::unique_lock pwLock(*this); - if (mRegistry) - llpw_proxy_destroy((struct pw_proxy*)mRegistry); - spa_zero(mRegistryListener); - if (mCore) - llpw_core_disconnect(mCore); - if (mContext) - llpw_context_destroy(mContext); - pwLock.unlock(); + { + std::unique_lock childNodesLock(mChildNodesMutex); + for (auto *childNode: mChildNodes) + childNode->destroy(); + + mChildNodes.clear(); + } + + { + std::unique_lock pwLock(mCleanupMutex); + if (mRegistry) + llpw_proxy_destroy((struct pw_proxy *) mRegistry); + + spa_zero(mRegistryListener); + + if (mCore) + llpw_core_disconnect(mCore); + if (mContext) + llpw_context_destroy(mContext); + } if (!mThreadLoop) return; @@ -153,49 +156,33 @@ void VolumeCatcherPipeWire::cleanup() llpw_thread_loop_stop(mThreadLoop); llpw_thread_loop_destroy(mThreadLoop); - // debugPrint("cleanup done\n"); + LL_DEBUGS() << "cleanup done" << LL_ENDL; } -void VolumeCatcherPipeWire::lock() { +void VolumeCatcherPipeWire::lock() +{ if (!mThreadLoop) return; llpw_thread_loop_lock(mThreadLoop); } -void VolumeCatcherPipeWire::unlock() { +void VolumeCatcherPipeWire::unlock() +{ if (!mThreadLoop) return; llpw_thread_loop_unlock(mThreadLoop); } -// #define INV_LERP(a, b, v) (v - a) / (b - a) - -// #include - void VolumeCatcherPipeWire::ChildNode::updateVolume() { if (!mActive) return; F32 volume = std::clamp(mImpl->mVolume, 0.0f, 1.0f); - // F32 pan = std::clamp(mImpl->mPan, -1.0f, 1.0f); - - // debugClear(); - // struct timeval time; - // gettimeofday(&time, NULL); - // double t = (double)time.tv_sec + (double)(time.tv_usec / 1000) / 1000; - // debugPrint("time is %f\n", t); - // F32 pan = std::sin(t * 2.0d); - // debugPrint("pan is %f\n", pan); - - // uint32_t channels = 2; - // float volumes[channels]; - // volumes[1] = INV_LERP(-1.0f, 0.0f, pan) * volume; // left - // volumes[0] = INV_LERP(1.0f, 0.0f, pan) * volume; // right - - uint32_t channels = 1; + + const uint32_t channels = 1; float volumes[channels]; volumes[0] = volume; @@ -210,9 +197,10 @@ void VolumeCatcherPipeWire::ChildNode::updateVolume() spa_pod_builder_array(&builder, sizeof(float), SPA_TYPE_Float, channels, volumes); spa_pod* pod = static_cast(spa_pod_builder_pop(&builder, &frame)); - std::lock_guard pwLock(*mImpl); - - pw_node_set_param(mProxy, SPA_PARAM_Props, 0, pod); + { + std::lock_guard pwLock(*mImpl); + pw_node_set_param(mProxy, SPA_PARAM_Props, 0, pod); + } } void VolumeCatcherPipeWire::ChildNode::destroy() @@ -222,16 +210,18 @@ void VolumeCatcherPipeWire::ChildNode::destroy() mActive = false; - std::unique_lock childNodesLock(mImpl->mChildNodesMutex); - mImpl->mChildNodes.erase(this); - childNodesLock.unlock(); - + { + std::unique_lock childNodesLock(mImpl->mChildNodesMutex); + mImpl->mChildNodes.erase(this); + } + spa_hook_remove(&mNodeListener); spa_hook_remove(&mProxyListener); - std::lock_guard pwLock(*mImpl); - - llpw_proxy_destroy(mProxy); + { + std::lock_guard pwLock(*mImpl); + llpw_proxy_destroy(mProxy); + } } static void nodeEventInfo(void* data, const struct pw_node_info* info) @@ -246,17 +236,18 @@ static void nodeEventInfo(void* data, const struct pw_node_info* info) if (!isPluginPid(pid)) return; - // const char* appName = spa_dict_lookup(info->props, PW_KEY_APP_NAME); - // debugPrint("got app: %s\n", appName); + const char* appName = spa_dict_lookup(info->props, PW_KEY_APP_NAME); + LL_DEBUGS() << "got app: " << appName << LL_ENDL; auto* const childNode = static_cast(data); - // debugPrint("init volume to: %f\n", childNode->mImpl->mVolume); + LL_DEBUGS() << "init volume: " << childNode->mImpl->mVolume << LL_ENDL; childNode->updateVolume(); - std::lock_guard childNodesLock(childNode->mImpl->mChildNodesMutex); - - childNode->mImpl->mChildNodes.insert(childNode); + { + std::lock_guard childNodesLock(childNode->mImpl->mChildNodesMutex); + childNode->mImpl->mChildNodes.insert(childNode); + } } static const struct pw_node_events NODE_EVENTS = { @@ -284,9 +275,9 @@ static const struct pw_proxy_events PROXY_EVENTS = { void VolumeCatcherPipeWire::handleRegistryEventGlobal( uint32_t id, uint32_t permissions, const char *type, uint32_t version, - const struct spa_dict *props -) { - if (props == nullptr || strcmp(type, PW_TYPE_INTERFACE_Node) != 0) + const struct spa_dict *props) +{ + if (props == nullptr || type == nullptr || strcmp(type, PW_TYPE_INTERFACE_Node) != 0) return; const char* mediaClass = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); @@ -310,27 +301,25 @@ void VolumeCatcherPipeWire::handleRegistryEventGlobal( void VolumeCatcherPipeWire::setVolume(F32 volume) { - // debugPrint("setting all volume to: %f\n", volume); + LL_DEBUGS() << "setting volume to: " << volume << LL_ENDL; mVolume = volume; - std::unique_lock childNodeslock(mChildNodesMutex); - std::unordered_set copyOfChildNodes(mChildNodes); - childNodeslock.unlock(); + { + std::unique_lock childNodeslock(mChildNodesMutex); + std::unordered_set copyOfChildNodes(mChildNodes); - // debugPrint("for %d nodes\n", copyOfChildNodes.size()); + LL_DEBUGS() << "found " << copyOfChildNodes.size() << " child nodes" << LL_ENDL; - for (auto* childNode : copyOfChildNodes) { - childNode->updateVolume(); - } + for (auto* childNode : copyOfChildNodes) + childNode->updateVolume(); + } } void VolumeCatcherPipeWire::setPan(F32 pan) { - // mPan = pan; - // setVolume(mVolume); } void VolumeCatcherPipeWire::pump() { -} \ No newline at end of file +} -- cgit v1.2.3 From aa2813a8e2c878bcd521cba992fb898e541ab633 Mon Sep 17 00:00:00 2001 From: Nicky Date: Sun, 21 Apr 2024 18:53:44 +0200 Subject: Replace NULL with nullptr --- indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp index 027fdb77f6..8617468f17 100755 --- a/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp +++ b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp @@ -90,9 +90,9 @@ void VolumeCatcherPipeWire::init() LL_DEBUGS() << "successfully got symbols" << LL_ENDL; - llpw_init(NULL, NULL); + llpw_init(nullptr, nullptr); - mThreadLoop = llpw_thread_loop_new("SL Plugin Volume Adjuster", NULL); + mThreadLoop = llpw_thread_loop_new("SL Plugin Volume Adjuster", nullptr); if (!mThreadLoop) return; @@ -101,13 +101,13 @@ void VolumeCatcherPipeWire::init() // std::lock_guard pwLock(*this); mContext = llpw_context_new( - llpw_thread_loop_get_loop(mThreadLoop), NULL, 0 + llpw_thread_loop_get_loop(mThreadLoop), nullptr, 0 ); if (!mContext) return; - mCore = llpw_context_connect(mContext, NULL, 0); + mCore = llpw_context_connect(mContext, nullptr, 0); if (!mCore) return; -- cgit v1.2.3 From 08ec79d089aaa447f0f36be4463570d249fbbcc5 Mon Sep 17 00:00:00 2001 From: Nicky Date: Mon, 22 Apr 2024 09:32:54 +0200 Subject: Tabs to spaces --- .../cef/linux/volume_catcher_linux.cpp | 40 +-- .../cef/linux/volume_catcher_pipewire.cpp | 206 +++++------ .../cef/linux/volume_catcher_pulseaudio.cpp | 394 ++++++++++----------- 3 files changed, 320 insertions(+), 320 deletions(-) (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/linux/volume_catcher_linux.cpp b/indra/media_plugins/cef/linux/volume_catcher_linux.cpp index 439c0a6768..b4d20935e7 100644 --- a/indra/media_plugins/cef/linux/volume_catcher_linux.cpp +++ b/indra/media_plugins/cef/linux/volume_catcher_linux.cpp @@ -36,45 +36,45 @@ VolumeCatcher::VolumeCatcher() void VolumeCatcher::onEnablePipeWireVolumeCatcher(bool enable) { - if (pimpl != nullptr) - return; + if (pimpl != nullptr) + return; - if (enable) + if (enable) { - LL_DEBUGS() << "volume catcher using pipewire" << LL_ENDL; - pimpl = new VolumeCatcherPipeWire(); - } + LL_DEBUGS() << "volume catcher using pipewire" << LL_ENDL; + pimpl = new VolumeCatcherPipeWire(); + } else { - LL_DEBUGS() << "volume catcher using pulseaudio" << LL_ENDL; - pimpl = new VolumeCatcherPulseAudio(); - } + LL_DEBUGS() << "volume catcher using pulseaudio" << LL_ENDL; + pimpl = new VolumeCatcherPulseAudio(); + } } VolumeCatcher::~VolumeCatcher() { - if (pimpl != nullptr) + if (pimpl != nullptr) { - delete pimpl; - pimpl = nullptr; - } + delete pimpl; + pimpl = nullptr; + } } void VolumeCatcher::setVolume(F32 volume) { - if (pimpl != nullptr) { - pimpl->setVolume(volume); - } + if (pimpl != nullptr) { + pimpl->setVolume(volume); + } } void VolumeCatcher::setPan(F32 pan) { - if (pimpl != nullptr) - pimpl->setPan(pan); + if (pimpl != nullptr) + pimpl->setPan(pan); } void VolumeCatcher::pump() { - if (pimpl != nullptr) - pimpl->pump(); + if (pimpl != nullptr) + pimpl->pump(); } diff --git a/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp index 8617468f17..0fb9d26476 100755 --- a/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp +++ b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp @@ -52,77 +52,77 @@ SymbolGrabber pwSymbolGrabber; VolumeCatcherPipeWire::VolumeCatcherPipeWire() { - init(); + init(); } VolumeCatcherPipeWire::~VolumeCatcherPipeWire() { - cleanup(); + cleanup(); } static void registryEventGlobal( - void *data, uint32_t id, uint32_t permissions, const char *type, - uint32_t version, const struct spa_dict *props) + void *data, uint32_t id, uint32_t permissions, const char *type, + uint32_t version, const struct spa_dict *props) { - static_cast(data)->handleRegistryEventGlobal( - id, permissions, type, version, props - ); + static_cast(data)->handleRegistryEventGlobal( + id, permissions, type, version, props + ); } static const struct pw_registry_events REGISTRY_EVENTS = { - .version = PW_VERSION_REGISTRY_EVENTS, + .version = PW_VERSION_REGISTRY_EVENTS, .global = registryEventGlobal, }; bool VolumeCatcherPipeWire::loadsyms(std::string pw_dso_name) { - return pwSymbolGrabber.grabSymbols({ pw_dso_name }); + return pwSymbolGrabber.grabSymbols({ pw_dso_name }); } void VolumeCatcherPipeWire::init() { LL_DEBUGS() << "init" << LL_ENDL; - mGotSyms = loadsyms("libpipewire-0.3.so.0"); - - if (!mGotSyms) - return; + mGotSyms = loadsyms("libpipewire-0.3.so.0"); + + if (!mGotSyms) + return; LL_DEBUGS() << "successfully got symbols" << LL_ENDL; - llpw_init(nullptr, nullptr); + llpw_init(nullptr, nullptr); - mThreadLoop = llpw_thread_loop_new("SL Plugin Volume Adjuster", nullptr); + mThreadLoop = llpw_thread_loop_new("SL Plugin Volume Adjuster", nullptr); - if (!mThreadLoop) - return; + if (!mThreadLoop) + return; - // i dont think we need to lock this early - // std::lock_guard pwLock(*this); + // i dont think we need to lock this early + // std::lock_guard pwLock(*this); - mContext = llpw_context_new( - llpw_thread_loop_get_loop(mThreadLoop), nullptr, 0 - ); + mContext = llpw_context_new( + llpw_thread_loop_get_loop(mThreadLoop), nullptr, 0 + ); - if (!mContext) - return; + if (!mContext) + return; - mCore = llpw_context_connect(mContext, nullptr, 0); + mCore = llpw_context_connect(mContext, nullptr, 0); - if (!mCore) - return; + if (!mCore) + return; - mRegistry = pw_core_get_registry(mCore, PW_VERSION_REGISTRY, 0); + mRegistry = pw_core_get_registry(mCore, PW_VERSION_REGISTRY, 0); LL_DEBUGS() << "pw_core_get_registry: " << (mRegistry?"success":"nullptr") << LL_ENDL; - spa_zero(mRegistryListener); + spa_zero(mRegistryListener); - pw_registry_add_listener( - mRegistry, &mRegistryListener, ®ISTRY_EVENTS, this - ); + pw_registry_add_listener( + mRegistry, &mRegistryListener, ®ISTRY_EVENTS, this + ); - llpw_thread_loop_start(mThreadLoop); + llpw_thread_loop_start(mThreadLoop); LL_DEBUGS() << "thread loop started" << LL_ENDL; } @@ -150,52 +150,52 @@ void VolumeCatcherPipeWire::cleanup() llpw_context_destroy(mContext); } - if (!mThreadLoop) - return; + if (!mThreadLoop) + return; - llpw_thread_loop_stop(mThreadLoop); - llpw_thread_loop_destroy(mThreadLoop); + llpw_thread_loop_stop(mThreadLoop); + llpw_thread_loop_destroy(mThreadLoop); LL_DEBUGS() << "cleanup done" << LL_ENDL; } void VolumeCatcherPipeWire::lock() { - if (!mThreadLoop) - return; + if (!mThreadLoop) + return; - llpw_thread_loop_lock(mThreadLoop); + llpw_thread_loop_lock(mThreadLoop); } void VolumeCatcherPipeWire::unlock() { - if (!mThreadLoop) - return; + if (!mThreadLoop) + return; - llpw_thread_loop_unlock(mThreadLoop); + llpw_thread_loop_unlock(mThreadLoop); } void VolumeCatcherPipeWire::ChildNode::updateVolume() { - if (!mActive) - return; + if (!mActive) + return; - F32 volume = std::clamp(mImpl->mVolume, 0.0f, 1.0f); + F32 volume = std::clamp(mImpl->mVolume, 0.0f, 1.0f); - const uint32_t channels = 1; - float volumes[channels]; - volumes[0] = volume; + const uint32_t channels = 1; + float volumes[channels]; + volumes[0] = volume; - uint8_t buffer[512]; + uint8_t buffer[512]; - spa_pod_builder builder; - spa_pod_builder_init(&builder, buffer, sizeof(buffer)); + spa_pod_builder builder; + spa_pod_builder_init(&builder, buffer, sizeof(buffer)); - spa_pod_frame frame; - spa_pod_builder_push_object(&builder, &frame, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); - spa_pod_builder_prop(&builder, SPA_PROP_channelVolumes, 0); - spa_pod_builder_array(&builder, sizeof(float), SPA_TYPE_Float, channels, volumes); - spa_pod* pod = static_cast(spa_pod_builder_pop(&builder, &frame)); + spa_pod_frame frame; + spa_pod_builder_push_object(&builder, &frame, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); + spa_pod_builder_prop(&builder, SPA_PROP_channelVolumes, 0); + spa_pod_builder_array(&builder, sizeof(float), SPA_TYPE_Float, channels, volumes); + spa_pod* pod = static_cast(spa_pod_builder_pop(&builder, &frame)); { std::lock_guard pwLock(*mImpl); @@ -205,18 +205,18 @@ void VolumeCatcherPipeWire::ChildNode::updateVolume() void VolumeCatcherPipeWire::ChildNode::destroy() { - if (!mActive) - return; + if (!mActive) + return; - mActive = false; + mActive = false; { std::unique_lock childNodesLock(mImpl->mChildNodesMutex); mImpl->mChildNodes.erase(this); } - spa_hook_remove(&mNodeListener); - spa_hook_remove(&mProxyListener); + spa_hook_remove(&mNodeListener); + spa_hook_remove(&mProxyListener); { std::lock_guard pwLock(*mImpl); @@ -226,23 +226,23 @@ void VolumeCatcherPipeWire::ChildNode::destroy() static void nodeEventInfo(void* data, const struct pw_node_info* info) { - const char* processId = spa_dict_lookup(info->props, PW_KEY_APP_PROCESS_ID); + const char* processId = spa_dict_lookup(info->props, PW_KEY_APP_PROCESS_ID); + + if (processId == nullptr) + return; - if (processId == nullptr) - return; - - pid_t pid = atoll(processId); + pid_t pid = atoll(processId); - if (!isPluginPid(pid)) - return; + if (!isPluginPid(pid)) + return; const char* appName = spa_dict_lookup(info->props, PW_KEY_APP_NAME); LL_DEBUGS() << "got app: " << appName << LL_ENDL; - - auto* const childNode = static_cast(data); + + auto* const childNode = static_cast(data); LL_DEBUGS() << "init volume: " << childNode->mImpl->mVolume << LL_ENDL; - childNode->updateVolume(); + childNode->updateVolume(); { std::lock_guard childNodesLock(childNode->mImpl->mChildNodesMutex); @@ -251,59 +251,59 @@ static void nodeEventInfo(void* data, const struct pw_node_info* info) } static const struct pw_node_events NODE_EVENTS = { - .version = PW_VERSION_CLIENT_EVENTS, - .info = nodeEventInfo, + .version = PW_VERSION_CLIENT_EVENTS, + .info = nodeEventInfo, }; static void proxyEventDestroy(void* data) { - auto* const childNode = static_cast(data); - childNode->destroy(); + auto* const childNode = static_cast(data); + childNode->destroy(); } static void proxyEventRemoved(void* data) { - auto* const childNode = static_cast(data); - childNode->destroy(); + auto* const childNode = static_cast(data); + childNode->destroy(); } static const struct pw_proxy_events PROXY_EVENTS = { - .version = PW_VERSION_PROXY_EVENTS, + .version = PW_VERSION_PROXY_EVENTS, .destroy = proxyEventDestroy, - .removed = proxyEventRemoved, + .removed = proxyEventRemoved, }; void VolumeCatcherPipeWire::handleRegistryEventGlobal( - uint32_t id, uint32_t permissions, const char *type, uint32_t version, - const struct spa_dict *props) + uint32_t id, uint32_t permissions, const char *type, uint32_t version, + const struct spa_dict *props) { - if (props == nullptr || type == nullptr || strcmp(type, PW_TYPE_INTERFACE_Node) != 0) - return; - - const char* mediaClass = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); - - if (mediaClass == nullptr || strcmp(mediaClass, "Stream/Output/Audio") != 0) - return; - - pw_proxy* proxy = static_cast( - pw_registry_bind(mRegistry, id, type, PW_VERSION_CLIENT, sizeof(ChildNode)) - ); - - auto* const childNode = static_cast(llpw_proxy_get_user_data(proxy)); - - childNode->mActive = true; - childNode->mProxy = proxy; - childNode->mImpl = this; - - pw_node_add_listener(proxy, &childNode->mNodeListener, &NODE_EVENTS, childNode); - llpw_proxy_add_listener(proxy, &childNode->mProxyListener, &PROXY_EVENTS, childNode); + if (props == nullptr || type == nullptr || strcmp(type, PW_TYPE_INTERFACE_Node) != 0) + return; + + const char* mediaClass = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); + + if (mediaClass == nullptr || strcmp(mediaClass, "Stream/Output/Audio") != 0) + return; + + pw_proxy* proxy = static_cast( + pw_registry_bind(mRegistry, id, type, PW_VERSION_CLIENT, sizeof(ChildNode)) + ); + + auto* const childNode = static_cast(llpw_proxy_get_user_data(proxy)); + + childNode->mActive = true; + childNode->mProxy = proxy; + childNode->mImpl = this; + + pw_node_add_listener(proxy, &childNode->mNodeListener, &NODE_EVENTS, childNode); + llpw_proxy_add_listener(proxy, &childNode->mProxyListener, &PROXY_EVENTS, childNode); } void VolumeCatcherPipeWire::setVolume(F32 volume) { LL_DEBUGS() << "setting volume to: " << volume << LL_ENDL; - mVolume = volume; + mVolume = volume; { std::unique_lock childNodeslock(mChildNodesMutex); diff --git a/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp index f7f64ef76c..9417c49d38 100755 --- a/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp +++ b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp @@ -57,124 +57,124 @@ SymbolGrabber paSymbolGrabber; // PulseAudio requires a chain of callbacks with C linkage extern "C" { - void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *i, int eol, void *userdata); - void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata); - void callback_context_state(pa_context *context, void *userdata); + void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *i, int eol, void *userdata); + void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata); + void callback_context_state(pa_context *context, void *userdata); } VolumeCatcherPulseAudio::VolumeCatcherPulseAudio() - : mDesiredVolume(0.0f), - mMainloop(nullptr), - mPAContext(nullptr), - mConnected(false), - mGotSyms(false) + : mDesiredVolume(0.0f), + mMainloop(nullptr), + mPAContext(nullptr), + mConnected(false), + mGotSyms(false) { - init(); + init(); } VolumeCatcherPulseAudio::~VolumeCatcherPulseAudio() { - cleanup(); + cleanup(); } bool VolumeCatcherPulseAudio::loadsyms(std::string pulse_dso_name) { - return paSymbolGrabber.grabSymbols({ pulse_dso_name }); + return paSymbolGrabber.grabSymbols({ pulse_dso_name }); } void VolumeCatcherPulseAudio::init() { - // try to be as defensive as possible because PA's interface is a - // bit fragile and (for our purposes) we'd rather simply not function - // than crash - - // we cheat and rely upon libpulse-mainloop-glib.so.0 to pull-in - // libpulse.so.0 - this isn't a great assumption, and the two DSOs should - // probably be loaded separately. Our Linux DSO framework needs refactoring, - // we do this sort of thing a lot with practically identical logic... - mGotSyms = loadsyms("libpulse-mainloop-glib.so.0"); - - if (!mGotSyms) - mGotSyms = loadsyms("libpulse.so.0"); - - if (!mGotSyms) - return; - - mMainloop = llpa_glib_mainloop_new(g_main_context_default()); - - if (mMainloop) - { - pa_mainloop_api *api = llpa_glib_mainloop_get_api(mMainloop); - - if (api) - { - pa_proplist *proplist = llpa_proplist_new(); - - if (proplist) - { - llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "multimedia-player"); - llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "com.secondlife.viewer.mediaplugvoladjust"); - llpa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "SL Plugin Volume Adjuster"); - llpa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, "1"); - - // plain old pa_context_new() is broken! - mPAContext = llpa_context_new_with_proplist(api, nullptr, proplist); - - llpa_proplist_free(proplist); - } - } - } - - // Now we've set up a PA context and mainloop, try connecting the - // PA context to a PA daemon. - if (mPAContext) - { - llpa_context_set_state_callback(mPAContext, callback_context_state, this); - pa_context_flags_t cflags = (pa_context_flags)0; // maybe add PA_CONTEXT_NOAUTOSPAWN? - if (llpa_context_connect(mPAContext, nullptr, cflags, nullptr) >= 0) - { - // Okay! We haven't definitely connected, but we - // haven't definitely failed yet. - } - else - { - // Failed to connect to PA manager... we'll leave - // things like that. Perhaps we should try again later. - } - } + // try to be as defensive as possible because PA's interface is a + // bit fragile and (for our purposes) we'd rather simply not function + // than crash + + // we cheat and rely upon libpulse-mainloop-glib.so.0 to pull-in + // libpulse.so.0 - this isn't a great assumption, and the two DSOs should + // probably be loaded separately. Our Linux DSO framework needs refactoring, + // we do this sort of thing a lot with practically identical logic... + mGotSyms = loadsyms("libpulse-mainloop-glib.so.0"); + + if (!mGotSyms) + mGotSyms = loadsyms("libpulse.so.0"); + + if (!mGotSyms) + return; + + mMainloop = llpa_glib_mainloop_new(g_main_context_default()); + + if (mMainloop) + { + pa_mainloop_api *api = llpa_glib_mainloop_get_api(mMainloop); + + if (api) + { + pa_proplist *proplist = llpa_proplist_new(); + + if (proplist) + { + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "multimedia-player"); + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "com.secondlife.viewer.mediaplugvoladjust"); + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "SL Plugin Volume Adjuster"); + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, "1"); + + // plain old pa_context_new() is broken! + mPAContext = llpa_context_new_with_proplist(api, nullptr, proplist); + + llpa_proplist_free(proplist); + } + } + } + + // Now we've set up a PA context and mainloop, try connecting the + // PA context to a PA daemon. + if (mPAContext) + { + llpa_context_set_state_callback(mPAContext, callback_context_state, this); + pa_context_flags_t cflags = (pa_context_flags)0; // maybe add PA_CONTEXT_NOAUTOSPAWN? + if (llpa_context_connect(mPAContext, nullptr, cflags, nullptr) >= 0) + { + // Okay! We haven't definitely connected, but we + // haven't definitely failed yet. + } + else + { + // Failed to connect to PA manager... we'll leave + // things like that. Perhaps we should try again later. + } + } } void VolumeCatcherPulseAudio::cleanup() { - mConnected = false; + mConnected = false; - if (mGotSyms && mPAContext) - { - llpa_context_disconnect(mPAContext); - llpa_context_unref(mPAContext); - } + if (mGotSyms && mPAContext) + { + llpa_context_disconnect(mPAContext); + llpa_context_unref(mPAContext); + } - mPAContext = nullptr; + mPAContext = nullptr; - if (mGotSyms && mMainloop) - llpa_glib_mainloop_free(mMainloop); + if (mGotSyms && mMainloop) + llpa_glib_mainloop_free(mMainloop); - mMainloop = nullptr; + mMainloop = nullptr; } void VolumeCatcherPulseAudio::setVolume(F32 volume) { - mDesiredVolume = volume; - - if (!mGotSyms) - return; + mDesiredVolume = volume; - if (mConnected && mPAContext) - { - update_all_volumes(mDesiredVolume); - } + if (!mGotSyms) + return; - pump(); + if (mConnected && mPAContext) + { + update_all_volumes(mDesiredVolume); + } + + pump(); } void VolumeCatcherPulseAudio::setPan(F32 pan) @@ -183,140 +183,140 @@ void VolumeCatcherPulseAudio::setPan(F32 pan) void VolumeCatcherPulseAudio::pump() { - gboolean may_block = FALSE; - g_main_context_iteration(g_main_context_default(), may_block); + gboolean may_block = FALSE; + g_main_context_iteration(g_main_context_default(), may_block); } void VolumeCatcherPulseAudio::connected_okay() { - pa_operation *op; - - // fetch global list of existing sinkinputs - if ((op = llpa_context_get_sink_input_info_list(mPAContext, - callback_discovered_sinkinput, - this))) - { - llpa_operation_unref(op); - } - - // subscribe to future global sinkinput changes - llpa_context_set_subscribe_callback(mPAContext, - callback_subscription_alert, - this); - if ((op = llpa_context_subscribe(mPAContext, (pa_subscription_mask_t) - (PA_SUBSCRIPTION_MASK_SINK_INPUT), + pa_operation *op; + + // fetch global list of existing sinkinputs + if ((op = llpa_context_get_sink_input_info_list(mPAContext, + callback_discovered_sinkinput, + this))) + { + llpa_operation_unref(op); + } + + // subscribe to future global sinkinput changes + llpa_context_set_subscribe_callback(mPAContext, + callback_subscription_alert, + this); + if ((op = llpa_context_subscribe(mPAContext, (pa_subscription_mask_t) + (PA_SUBSCRIPTION_MASK_SINK_INPUT), nullptr, nullptr))) - { - llpa_operation_unref(op); - } + { + llpa_operation_unref(op); + } } void VolumeCatcherPulseAudio::update_all_volumes(F32 volume) { - for (std::set::iterator it = mSinkInputIndices.begin(); - it != mSinkInputIndices.end(); ++it) - { - update_index_volume(*it, volume); - } + for (std::set::iterator it = mSinkInputIndices.begin(); + it != mSinkInputIndices.end(); ++it) + { + update_index_volume(*it, volume); + } } void VolumeCatcherPulseAudio::update_index_volume(U32 index, F32 volume) { - static pa_cvolume cvol; - llpa_cvolume_set(&cvol, mSinkInputNumChannels[index], - llpa_sw_volume_from_linear(volume)); - - pa_context *c = mPAContext; - uint32_t idx = index; - const pa_cvolume *cvolumep = &cvol; - pa_context_success_cb_t cb = nullptr; // okay as null - void *userdata = nullptr; // okay as null - - pa_operation *op; - if ((op = llpa_context_set_sink_input_volume(c, idx, cvolumep, cb, userdata))) - llpa_operation_unref(op); + static pa_cvolume cvol; + llpa_cvolume_set(&cvol, mSinkInputNumChannels[index], + llpa_sw_volume_from_linear(volume)); + + pa_context *c = mPAContext; + uint32_t idx = index; + const pa_cvolume *cvolumep = &cvol; + pa_context_success_cb_t cb = nullptr; // okay as null + void *userdata = nullptr; // okay as null + + pa_operation *op; + if ((op = llpa_context_set_sink_input_volume(c, idx, cvolumep, cb, userdata))) + llpa_operation_unref(op); } void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *sii, int eol, void *userdata) { - VolumeCatcherPulseAudio *impl = dynamic_cast((VolumeCatcherPulseAudio*)userdata); - llassert(impl); - - if (0 == eol) - { - pa_proplist *proplist = sii->proplist; - pid_t sinkpid = atoll(llpa_proplist_gets(proplist, PA_PROP_APPLICATION_PROCESS_ID)); - - if (isPluginPid( sinkpid )) // does the discovered sinkinput belong to this process? - { - bool is_new = (impl->mSinkInputIndices.find(sii->index) == impl->mSinkInputIndices.end()); - - impl->mSinkInputIndices.insert(sii->index); - impl->mSinkInputNumChannels[sii->index] = sii->channel_map.channels; - - if (is_new) - { - // new! - impl->update_index_volume(sii->index, impl->mDesiredVolume); - } - else - { - // seen it already, do nothing. - } - } - } + VolumeCatcherPulseAudio *impl = dynamic_cast((VolumeCatcherPulseAudio*)userdata); + llassert(impl); + + if (0 == eol) + { + pa_proplist *proplist = sii->proplist; + pid_t sinkpid = atoll(llpa_proplist_gets(proplist, PA_PROP_APPLICATION_PROCESS_ID)); + + if (isPluginPid( sinkpid )) // does the discovered sinkinput belong to this process? + { + bool is_new = (impl->mSinkInputIndices.find(sii->index) == impl->mSinkInputIndices.end()); + + impl->mSinkInputIndices.insert(sii->index); + impl->mSinkInputNumChannels[sii->index] = sii->channel_map.channels; + + if (is_new) + { + // new! + impl->update_index_volume(sii->index, impl->mDesiredVolume); + } + else + { + // seen it already, do nothing. + } + } + } } void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata) { - VolumeCatcherPulseAudio *impl = dynamic_cast((VolumeCatcherPulseAudio*)userdata); - llassert(impl); + VolumeCatcherPulseAudio *impl = dynamic_cast((VolumeCatcherPulseAudio*)userdata); + llassert(impl); - switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) - { + switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) + { case PA_SUBSCRIPTION_EVENT_SINK_INPUT: - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) - { - // forget this sinkinput, if we were caring about it - impl->mSinkInputIndices.erase(index); - impl->mSinkInputNumChannels.erase(index); - } - else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) - { - // ask for more info about this new sinkinput - pa_operation *op; - if ((op = llpa_context_get_sink_input_info(impl->mPAContext, index, callback_discovered_sinkinput, impl))) - { - llpa_operation_unref(op); - } - } - else - { - // property change on this sinkinput - we don't care. - } - break; - - default:; - } + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) + { + // forget this sinkinput, if we were caring about it + impl->mSinkInputIndices.erase(index); + impl->mSinkInputNumChannels.erase(index); + } + else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) + { + // ask for more info about this new sinkinput + pa_operation *op; + if ((op = llpa_context_get_sink_input_info(impl->mPAContext, index, callback_discovered_sinkinput, impl))) + { + llpa_operation_unref(op); + } + } + else + { + // property change on this sinkinput - we don't care. + } + break; + + default:; + } } void callback_context_state(pa_context *context, void *userdata) { - VolumeCatcherPulseAudio *impl = dynamic_cast((VolumeCatcherPulseAudio*)userdata); - llassert(impl); - - switch (llpa_context_get_state(context)) - { - case PA_CONTEXT_READY: - impl->mConnected = true; - impl->connected_okay(); - break; - case PA_CONTEXT_TERMINATED: - impl->mConnected = false; - break; - case PA_CONTEXT_FAILED: - impl->mConnected = false; - break; - default:; - } + VolumeCatcherPulseAudio *impl = dynamic_cast((VolumeCatcherPulseAudio*)userdata); + llassert(impl); + + switch (llpa_context_get_state(context)) + { + case PA_CONTEXT_READY: + impl->mConnected = true; + impl->connected_okay(); + break; + case PA_CONTEXT_TERMINATED: + impl->mConnected = false; + break; + case PA_CONTEXT_FAILED: + impl->mConnected = false; + break; + default:; + } } -- cgit v1.2.3 From 3d1e7933e52a689bb7bcefcc22a1901a4ddf187b Mon Sep 17 00:00:00 2001 From: Maki Date: Wed, 24 Apr 2024 07:47:06 -0400 Subject: Fix building for other platforms than Linux for volume catcher --- indra/media_plugins/cef/volume_catcher.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/volume_catcher.h b/indra/media_plugins/cef/volume_catcher.h index 3e53a7e961..dc63e4be01 100644 --- a/indra/media_plugins/cef/volume_catcher.h +++ b/indra/media_plugins/cef/volume_catcher.h @@ -54,7 +54,9 @@ public: void pump(); +#if LL_LINUX void onEnablePipeWireVolumeCatcher(bool enable); +#endif private: VolumeCatcherImpl *pimpl; -- cgit v1.2.3 From 1f34890d7e0a95632a6b599503e823e84d05fc24 Mon Sep 17 00:00:00 2001 From: Maki Date: Fri, 26 Apr 2024 00:49:58 -0400 Subject: Fix null volume catcher on macOS --- indra/media_plugins/cef/CMakeLists.txt | 2 +- .../media_plugins/cef/mac_volume_catcher_null.cpp | 95 ---------------------- indra/media_plugins/cef/volume_catcher_null.cpp | 53 ++++++++++++ 3 files changed, 54 insertions(+), 96 deletions(-) delete mode 100644 indra/media_plugins/cef/mac_volume_catcher_null.cpp create mode 100644 indra/media_plugins/cef/volume_catcher_null.cpp (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/CMakeLists.txt b/indra/media_plugins/cef/CMakeLists.txt index 065aa4605a..2c4ccd46d7 100644 --- a/indra/media_plugins/cef/CMakeLists.txt +++ b/indra/media_plugins/cef/CMakeLists.txt @@ -53,7 +53,7 @@ if (LINUX) set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--build-id -Wl,-rpath,'$ORIGIN:$ORIGIN/../../lib'") list(APPEND media_plugin_cef_LINK_LIBRARIES llwindow ) elseif (DARWIN) - list(APPEND media_plugin_cef_SOURCE_FILES mac_volume_catcher_null.cpp) + list(APPEND media_plugin_cef_SOURCE_FILES volume_catcher_null.cpp) find_library(CORESERVICES_LIBRARY CoreServices) find_library(AUDIOUNIT_LIBRARY AudioUnit) set( media_plugin_cef_LINK_LIBRARIES diff --git a/indra/media_plugins/cef/mac_volume_catcher_null.cpp b/indra/media_plugins/cef/mac_volume_catcher_null.cpp deleted file mode 100644 index f4fcef71aa..0000000000 --- a/indra/media_plugins/cef/mac_volume_catcher_null.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @file windows_volume_catcher.cpp - * @brief A null implementation of volume level control of all audio channels opened by a process. - * We are using this for the macOS version for now until we can understand how to make the - * exitising mac_volume_catcher.cpp work without the (now, non-existant) QuickTime dependency - * - * @cond - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - * @endcond - */ - -#include "volume_catcher.h" -#include "llsingleton.h" -class VolumeCatcherImpl : public LLSingleton -{ - LLSINGLETON(VolumeCatcherImpl); - // This is a singleton class -- both callers and the component implementation should use getInstance() to find the instance. - ~VolumeCatcherImpl(); - -public: - - void setVolume(F32 volume); - void setPan(F32 pan); - -private: - F32 mVolume; - F32 mPan; - bool mSystemIsVistaOrHigher; -}; - -VolumeCatcherImpl::VolumeCatcherImpl() -: mVolume(1.0f), // default volume is max - mPan(0.f) // default pan is centered -{ -} - -VolumeCatcherImpl::~VolumeCatcherImpl() -{ -} - -void VolumeCatcherImpl::setVolume(F32 volume) -{ - mVolume = volume; -} - -void VolumeCatcherImpl::setPan(F32 pan) -{ // remember pan for calculating individual channel levels later - mPan = pan; -} - -///////////////////////////////////////////////////// - -VolumeCatcher::VolumeCatcher() -{ - pimpl = VolumeCatcherImpl::getInstance(); -} - -VolumeCatcher::~VolumeCatcher() -{ - // Let the instance persist until exit. -} - -void VolumeCatcher::setVolume(F32 volume) -{ - pimpl->setVolume(volume); -} - -void VolumeCatcher::setPan(F32 pan) -{ - pimpl->setPan(pan); -} - -void VolumeCatcher::pump() -{ - // No periodic tasks are necessary for this implementation. -} diff --git a/indra/media_plugins/cef/volume_catcher_null.cpp b/indra/media_plugins/cef/volume_catcher_null.cpp new file mode 100644 index 0000000000..39c0a609e9 --- /dev/null +++ b/indra/media_plugins/cef/volume_catcher_null.cpp @@ -0,0 +1,53 @@ +/** + * @file volume_catcher_null.cpp + * @brief A null implementation of volume level control of all audio channels opened by a process. + * We are using this for the macOS version for now until we can understand how to make the + * exitising mac_volume_catcher.cpp work without the (now, non-existant) QuickTime dependency + * + * @cond + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + * @endcond + */ + +#include "volume_catcher.h" + +///////////////////////////////////////////////////// + +VolumeCatcher::VolumeCatcher() +{ +} + +VolumeCatcher::~VolumeCatcher() +{ +} + +void VolumeCatcher::setVolume(F32 volume) +{ +} + +void VolumeCatcher::setPan(F32 pan) +{ +} + +void VolumeCatcher::pump() +{ +} -- cgit v1.2.3 From 1df3b323d879b3a72e47d6b866f6a7501af80aff Mon Sep 17 00:00:00 2001 From: Nicky Dasmijn Date: Sat, 4 May 2024 21:12:50 +0200 Subject: Update volume_catcher.h Add a virtual dtor --- indra/media_plugins/cef/volume_catcher.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/volume_catcher.h b/indra/media_plugins/cef/volume_catcher.h index dc63e4be01..37a1cfc706 100644 --- a/indra/media_plugins/cef/volume_catcher.h +++ b/indra/media_plugins/cef/volume_catcher.h @@ -34,6 +34,8 @@ class VolumeCatcherImpl { public: + virtual ~VolumeCatcherImpl() = default; + virtual void setVolume(F32 volume) = 0; // 0.0 - 1.0 // Set the left-right pan of audio sources -- cgit v1.2.3 From 91fcae72aa8a5c6c02a08f8fa582880ca5cf72c2 Mon Sep 17 00:00:00 2001 From: Nicky Date: Wed, 22 May 2024 12:58:40 +0200 Subject: Remove obsolete member mSystemIsVistaOrHigher --- indra/media_plugins/cef/windows_volume_catcher.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/windows_volume_catcher.cpp b/indra/media_plugins/cef/windows_volume_catcher.cpp index e7daeb5f74..1e52fee9de 100644 --- a/indra/media_plugins/cef/windows_volume_catcher.cpp +++ b/indra/media_plugins/cef/windows_volume_catcher.cpp @@ -44,7 +44,6 @@ public: private: F32 mVolume; F32 mPan; - bool mSystemIsVistaOrHigher; }; VolumeCatcherImpl::VolumeCatcherImpl() -- cgit v1.2.3 From 7d62d0c5752814f27a17c86618f628d0e4ff9b0d Mon Sep 17 00:00:00 2001 From: Nicky Date: Wed, 22 May 2024 13:20:39 +0200 Subject: Move Linux specific VolumeCatcherImport into the linux specific files. --- .../cef/linux/volume_catcher_linux.cpp | 12 +++++----- .../media_plugins/cef/linux/volume_catcher_linux.h | 26 +++++++++++++++++----- indra/media_plugins/cef/volume_catcher.h | 16 ++----------- 3 files changed, 27 insertions(+), 27 deletions(-) (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/linux/volume_catcher_linux.cpp b/indra/media_plugins/cef/linux/volume_catcher_linux.cpp index b4d20935e7..7d33242063 100644 --- a/indra/media_plugins/cef/linux/volume_catcher_linux.cpp +++ b/indra/media_plugins/cef/linux/volume_catcher_linux.cpp @@ -1,4 +1,4 @@ -/** +/** * @file volume_catcher.cpp * @brief Linux volume catcher which will pick an implementation to use * @@ -6,21 +6,21 @@ * $LicenseInfo:firstyear=2010&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ * @endcond @@ -28,8 +28,6 @@ #include "volume_catcher_linux.h" -//////////////////////////////////////////////////// - VolumeCatcher::VolumeCatcher() { } diff --git a/indra/media_plugins/cef/linux/volume_catcher_linux.h b/indra/media_plugins/cef/linux/volume_catcher_linux.h index ff00d0672e..9101575b70 100644 --- a/indra/media_plugins/cef/linux/volume_catcher_linux.h +++ b/indra/media_plugins/cef/linux/volume_catcher_linux.h @@ -1,26 +1,26 @@ -/** +/** * @file volume_catcher_impl.h - * @brief + * @brief * * @cond * $LicenseInfo:firstyear=2010&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ * @endcond @@ -49,6 +49,20 @@ extern "C" { #include "media_plugin_base.h" +class VolumeCatcherImpl +{ +public: + virtual ~VolumeCatcherImpl() = default; + + virtual void setVolume(F32 volume) = 0; // 0.0 - 1.0 + + // Set the left-right pan of audio sources + // where -1.0 = left, 0 = center, and 1.0 = right + virtual void setPan(F32 pan) = 0; + + virtual void pump() = 0; // call this at least a few times a second if you can - it affects how quickly we can 'catch' a new audio source and adjust its volume +}; + class VolumeCatcherPulseAudio : public VolumeCatcherImpl { public: diff --git a/indra/media_plugins/cef/volume_catcher.h b/indra/media_plugins/cef/volume_catcher.h index 51a309fb97..d6ac8e9159 100644 --- a/indra/media_plugins/cef/volume_catcher.h +++ b/indra/media_plugins/cef/volume_catcher.h @@ -31,21 +31,9 @@ #include "linden_common.h" -class VolumeCatcherImpl -{ -public: - virtual ~VolumeCatcherImpl() = default; - - virtual void setVolume(F32 volume) = 0; // 0.0 - 1.0 - - // Set the left-right pan of audio sources - // where -1.0 = left, 0 = center, and 1.0 = right - virtual void setPan(F32 pan) = 0; - - virtual void pump() = 0; // call this at least a few times a second if you can - it affects how quickly we can 'catch' a new audio source and adjust its volume -}; +class VolumeCatcherImpl; -class VolumeCatcher : public VolumeCatcherImpl +class VolumeCatcher { public: VolumeCatcher(); -- cgit v1.2.3 From 8789d372c7617d86a30395190db4dba3b8545226 Mon Sep 17 00:00:00 2001 From: Nicky Date: Wed, 22 May 2024 13:31:15 +0200 Subject: Tabs to spaces. --- .../media_plugins/cef/linux/volume_catcher_linux.h | 132 ++++++++++----------- indra/media_plugins/cef/volume_catcher.h | 14 +-- 2 files changed, 73 insertions(+), 73 deletions(-) (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/linux/volume_catcher_linux.h b/indra/media_plugins/cef/linux/volume_catcher_linux.h index 9101575b70..475e8ca52e 100644 --- a/indra/media_plugins/cef/linux/volume_catcher_linux.h +++ b/indra/media_plugins/cef/linux/volume_catcher_linux.h @@ -52,97 +52,97 @@ extern "C" { class VolumeCatcherImpl { public: - virtual ~VolumeCatcherImpl() = default; + virtual ~VolumeCatcherImpl() = default; - virtual void setVolume(F32 volume) = 0; // 0.0 - 1.0 + virtual void setVolume(F32 volume) = 0; // 0.0 - 1.0 - // Set the left-right pan of audio sources - // where -1.0 = left, 0 = center, and 1.0 = right - virtual void setPan(F32 pan) = 0; + // Set the left-right pan of audio sources + // where -1.0 = left, 0 = center, and 1.0 = right + virtual void setPan(F32 pan) = 0; - virtual void pump() = 0; // call this at least a few times a second if you can - it affects how quickly we can 'catch' a new audio source and adjust its volume + virtual void pump() = 0; // call this at least a few times a second if you can - it affects how quickly we can 'catch' a new audio source and adjust its volume }; class VolumeCatcherPulseAudio : public VolumeCatcherImpl { public: - VolumeCatcherPulseAudio(); - ~VolumeCatcherPulseAudio(); - - void setVolume(F32 volume); - void setPan(F32 pan); - void pump(); - - // for internal use - can't be private because used from our C callbacks - - bool loadsyms(std::string pa_dso_name); - void init(); - void cleanup(); - - void update_all_volumes(F32 volume); - void update_index_volume(U32 index, F32 volume); - void connected_okay(); - - std::set mSinkInputIndices; - std::map mSinkInputNumChannels; - F32 mDesiredVolume; - pa_glib_mainloop *mMainloop; - pa_context *mPAContext; - bool mConnected; - bool mGotSyms; + VolumeCatcherPulseAudio(); + ~VolumeCatcherPulseAudio(); + + void setVolume(F32 volume); + void setPan(F32 pan); + void pump(); + + // for internal use - can't be private because used from our C callbacks + + bool loadsyms(std::string pa_dso_name); + void init(); + void cleanup(); + + void update_all_volumes(F32 volume); + void update_index_volume(U32 index, F32 volume); + void connected_okay(); + + std::set mSinkInputIndices; + std::map mSinkInputNumChannels; + F32 mDesiredVolume; + pa_glib_mainloop *mMainloop; + pa_context *mPAContext; + bool mConnected; + bool mGotSyms; }; class VolumeCatcherPipeWire : public VolumeCatcherImpl { public: - VolumeCatcherPipeWire(); - ~VolumeCatcherPipeWire(); + VolumeCatcherPipeWire(); + ~VolumeCatcherPipeWire(); - bool loadsyms(std::string pw_dso_name); - void init(); - void cleanup(); + bool loadsyms(std::string pw_dso_name); + void init(); + void cleanup(); - // some of these should be private + // some of these should be private - void lock(); - void unlock(); + void lock(); + void unlock(); - void setVolume(F32 volume); - void setPan(F32 pan); - void pump(); + void setVolume(F32 volume); + void setPan(F32 pan); + void pump(); - void handleRegistryEventGlobal( - uint32_t id, uint32_t permissions, const char* type, - uint32_t version, const struct spa_dict* props - ); + void handleRegistryEventGlobal( + uint32_t id, uint32_t permissions, const char* type, + uint32_t version, const struct spa_dict* props + ); - class ChildNode - { - public: - bool mActive = false; + class ChildNode + { + public: + bool mActive = false; - pw_proxy* mProxy = nullptr; - spa_hook mNodeListener {}; - spa_hook mProxyListener {}; - VolumeCatcherPipeWire* mImpl = nullptr; + pw_proxy* mProxy = nullptr; + spa_hook mNodeListener {}; + spa_hook mProxyListener {}; + VolumeCatcherPipeWire* mImpl = nullptr; - void updateVolume(); - void destroy(); - }; + void updateVolume(); + void destroy(); + }; - bool mGotSyms = false; + bool mGotSyms = false; - F32 mVolume = 1.0f; // max by default - // F32 mPan = 0.0f; // center + F32 mVolume = 1.0f; // max by default + // F32 mPan = 0.0f; // center - pw_thread_loop* mThreadLoop = nullptr; - pw_context* mContext = nullptr; - pw_core* mCore = nullptr; - pw_registry* mRegistry = nullptr; - spa_hook mRegistryListener; + pw_thread_loop* mThreadLoop = nullptr; + pw_context* mContext = nullptr; + pw_core* mCore = nullptr; + pw_registry* mRegistry = nullptr; + spa_hook mRegistryListener; - std::unordered_set mChildNodes; - std::mutex mChildNodesMutex; + std::unordered_set mChildNodes; + std::mutex mChildNodesMutex; std::mutex mCleanupMutex; }; diff --git a/indra/media_plugins/cef/volume_catcher.h b/indra/media_plugins/cef/volume_catcher.h index d6ac8e9159..6933854e8e 100644 --- a/indra/media_plugins/cef/volume_catcher.h +++ b/indra/media_plugins/cef/volume_catcher.h @@ -36,20 +36,20 @@ class VolumeCatcherImpl; class VolumeCatcher { public: - VolumeCatcher(); - ~VolumeCatcher(); + VolumeCatcher(); + ~VolumeCatcher(); - void setVolume(F32 volume); - void setPan(F32 pan); + void setVolume(F32 volume); + void setPan(F32 pan); - void pump(); + void pump(); #if LL_LINUX - void onEnablePipeWireVolumeCatcher(bool enable); + void onEnablePipeWireVolumeCatcher(bool enable); #endif private: - VolumeCatcherImpl *pimpl; + VolumeCatcherImpl *pimpl; }; #endif // VOLUME_CATCHER_H -- cgit v1.2.3 From bdf46af9aff96a749dcf2612a2bdc6e8e394971e Mon Sep 17 00:00:00 2001 From: AiraYumi Date: Tue, 21 May 2024 20:52:38 -0400 Subject: fix "lines starting with tabs found" --- .../media_plugins/cef/linux/volume_catcher_linux.h | 18 +++++++-------- indra/media_plugins/cef/media_plugin_cef.cpp | 26 +++++++++++----------- 2 files changed, 22 insertions(+), 22 deletions(-) (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/linux/volume_catcher_linux.h b/indra/media_plugins/cef/linux/volume_catcher_linux.h index 475e8ca52e..505f9ffb31 100644 --- a/indra/media_plugins/cef/linux/volume_catcher_linux.h +++ b/indra/media_plugins/cef/linux/volume_catcher_linux.h @@ -112,22 +112,22 @@ public: void pump(); void handleRegistryEventGlobal( - uint32_t id, uint32_t permissions, const char* type, - uint32_t version, const struct spa_dict* props + uint32_t id, uint32_t permissions, const char* type, + uint32_t version, const struct spa_dict* props ); class ChildNode { public: - bool mActive = false; + bool mActive = false; - pw_proxy* mProxy = nullptr; - spa_hook mNodeListener {}; - spa_hook mProxyListener {}; - VolumeCatcherPipeWire* mImpl = nullptr; + pw_proxy* mProxy = nullptr; + spa_hook mNodeListener {}; + spa_hook mProxyListener {}; + VolumeCatcherPipeWire* mImpl = nullptr; - void updateVolume(); - void destroy(); + void updateVolume(); + void destroy(); }; bool mGotSyms = false; diff --git a/indra/media_plugins/cef/media_plugin_cef.cpp b/indra/media_plugins/cef/media_plugin_cef.cpp index ebd8642040..1346dd2a52 100644 --- a/indra/media_plugins/cef/media_plugin_cef.cpp +++ b/indra/media_plugins/cef/media_plugin_cef.cpp @@ -903,21 +903,21 @@ void MediaPluginCEF::receiveMessage(const char* message_string) keyEvent(key_event, native_key_data); #endif - } - else if (message_name == "enable_media_plugin_debugging") - { - mEnableMediaPluginDebugging = message_in.getValueBoolean("enable"); - } + } + else if (message_name == "enable_media_plugin_debugging") + { + mEnableMediaPluginDebugging = message_in.getValueBoolean("enable"); + } #if LL_LINUX - else if (message_name == "enable_pipewire_volume_catcher") - { - bool enable = message_in.getValueBoolean("enable"); - mVolumeCatcher.onEnablePipeWireVolumeCatcher(enable); - } + else if (message_name == "enable_pipewire_volume_catcher") + { + bool enable = message_in.getValueBoolean("enable"); + mVolumeCatcher.onEnablePipeWireVolumeCatcher(enable); + } #endif - if (message_name == "pick_file_response") - { - LLSD file_list_llsd = message_in.getValueLLSD("file_list"); + if (message_name == "pick_file_response") + { + LLSD file_list_llsd = message_in.getValueLLSD("file_list"); LLSD::array_const_iterator iter = file_list_llsd.beginArray(); LLSD::array_const_iterator end = file_list_llsd.endArray(); -- cgit v1.2.3 From f649f7ab2308047dc4a8ca3cbc331aed957543ff Mon Sep 17 00:00:00 2001 From: Maki Date: Fri, 24 May 2024 18:40:27 -0400 Subject: Reset memorized Chromium volume and apply temporary volume --- indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp index 0fb9d26476..27fea547c9 100755 --- a/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp +++ b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp @@ -175,6 +175,9 @@ void VolumeCatcherPipeWire::unlock() llpw_thread_loop_unlock(mThreadLoop); } +const uint32_t channels = 1; +const float resetVolumes[channels] = { 1.0f }; + void VolumeCatcherPipeWire::ChildNode::updateVolume() { if (!mActive) @@ -182,9 +185,7 @@ void VolumeCatcherPipeWire::ChildNode::updateVolume() F32 volume = std::clamp(mImpl->mVolume, 0.0f, 1.0f); - const uint32_t channels = 1; - float volumes[channels]; - volumes[0] = volume; + const float volumes[channels] = { volume }; uint8_t buffer[512]; @@ -193,8 +194,15 @@ void VolumeCatcherPipeWire::ChildNode::updateVolume() spa_pod_frame frame; spa_pod_builder_push_object(&builder, &frame, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); + + // resets system-wide memorized volume for chromium (not google chrome) to 100% spa_pod_builder_prop(&builder, SPA_PROP_channelVolumes, 0); + spa_pod_builder_array(&builder, sizeof(float), SPA_TYPE_Float, channels, resetVolumes); + + // sets temporary volume + spa_pod_builder_prop(&builder, SPA_PROP_softVolumes, 0); spa_pod_builder_array(&builder, sizeof(float), SPA_TYPE_Float, channels, volumes); + spa_pod* pod = static_cast(spa_pod_builder_pop(&builder, &frame)); { -- cgit v1.2.3 From 1745701280d8a18f51ef09aacba4f76a4c543681 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Thu, 18 Jul 2024 08:01:24 +0300 Subject: Remove trailing whitespaces to make pre-commit happy --- indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp | 10 +++++----- indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp | 10 +++++----- indra/media_plugins/cef/volume_catcher_null.cpp | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) (limited to 'indra/media_plugins/cef') diff --git a/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp index 27fea547c9..a8f1366d6f 100755 --- a/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp +++ b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp @@ -1,4 +1,4 @@ -/** +/** * @file volume_catcher_pipewire.cpp * @brief A Linux-specific, PipeWire-specific hack to detect and volume-adjust new audio sources * @@ -6,21 +6,21 @@ * $LicenseInfo:firstyear=2010&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ * @endcond diff --git a/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp index 9417c49d38..f8a48a91fd 100755 --- a/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp +++ b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp @@ -1,4 +1,4 @@ -/** +/** * @file volume_catcher_pulseaudio.cpp * @brief A Linux-specific, PulseAudio-specific hack to detect and volume-adjust new audio sources * @@ -6,21 +6,21 @@ * $LicenseInfo:firstyear=2010&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ * @endcond diff --git a/indra/media_plugins/cef/volume_catcher_null.cpp b/indra/media_plugins/cef/volume_catcher_null.cpp index c6028da45b..1f0f1e8e38 100644 --- a/indra/media_plugins/cef/volume_catcher_null.cpp +++ b/indra/media_plugins/cef/volume_catcher_null.cpp @@ -1,4 +1,4 @@ -/** +/** * @file volume_catcher_null.cpp * @brief A null implementation of volume level control of all audio channels opened by a process. * We are using this for the macOS version for now until we can understand how to make the -- cgit v1.2.3