diff options
24 files changed, 955 insertions, 291 deletions
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d5b4fab380..c6e6cb17af 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -155,7 +155,7 @@ jobs:              libgl1-mesa-dev libglu1-mesa-dev libxinerama-dev \              libxcursor-dev libxfixes-dev libgstreamer1.0-dev \              libgstreamer-plugins-base1.0-dev ninja-build libxft-dev \ -            llvm mold +            llvm mold libpipewire-0.3-dev        - name: Install windows dependencies          if: env.BUILD && runner.os == 'Windows' diff --git a/indra/cmake/FindPipeWire.cmake b/indra/cmake/FindPipeWire.cmake new file mode 100644 index 0000000000..868acf5ec1 --- /dev/null +++ b/indra/cmake/FindPipeWire.cmake @@ -0,0 +1,100 @@ +# cmake-format: off +# .rst: FindPipeWire +# ------- +# +# Try to find PipeWire on a Unix system. +# +# This will define the following variables: +# +# ``PIPEWIRE_FOUND`` True if (the requested version of) PipeWire is available +# ``PIPEWIRE_VERSION`` The version of PipeWire ``PIPEWIRE_LIBRARIES`` This can +# be passed to target_link_libraries() instead of the ``PipeWire::PipeWire`` +# target ``PIPEWIRE_INCLUDE_DIRS`` This should be passed to +# target_include_directories() if the target is not used for linking +# ``PIPEWIRE_COMPILE_FLAGS`` This should be passed to target_compile_options() +# if the target is not used for linking +# +# If ``PIPEWIRE_FOUND`` is TRUE, it will also define the following imported +# target: +# +# ``PipeWire::PipeWire`` The PipeWire library +# +# In general we recommend using the imported target, as it is easier to use. +# Bear in mind, however, that if the target is in the link interface of an +# exported library, it must be made available by the package config file. + +# ============================================================================= +# Copyright 2014 Alex Merry <alex.merry@kde.org> Copyright 2014 Martin Gräßlin +# <mgraesslin@kde.org> Copyright 2018-2020 Jan Grulich <jgrulich@redhat.com> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the copyright notice, this list +#    of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright notice, this +#    list of conditions and the following disclaimer in the documentation and/or +#    other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +#    derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# ============================================================================= +# cmake-format: on + +# Use pkg-config to get the directories and then use these values in the FIND_PATH() and FIND_LIBRARY() calls +find_package(PkgConfig QUIET) + +pkg_search_module(PKG_PIPEWIRE QUIET libpipewire-0.3) +pkg_search_module(PKG_SPA QUIET libspa-0.2) + +set(PIPEWIRE_COMPILE_FLAGS "${PKG_PIPEWIRE_CFLAGS}" "${PKG_SPA_CFLAGS}") +set(PIPEWIRE_VERSION "${PKG_PIPEWIRE_VERSION}") + +find_path( +  PIPEWIRE_INCLUDE_DIRS +  NAMES pipewire/pipewire.h +  HINTS ${PKG_PIPEWIRE_INCLUDE_DIRS} ${PKG_PIPEWIRE_INCLUDE_DIRS}/pipewire-0.3) + +find_path( +  SPA_INCLUDE_DIRS +  NAMES spa/param/props.h +  HINTS ${PKG_SPA_INCLUDE_DIRS} ${PKG_SPA_INCLUDE_DIRS}/spa-0.2) + +find_library( +  PIPEWIRE_LIBRARIES +  NAMES pipewire-0.3 +  HINTS ${PKG_PIPEWIRE_LIBRARY_DIRS}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( +  PipeWire +  FOUND_VAR PIPEWIRE_FOUND +  REQUIRED_VARS PIPEWIRE_LIBRARIES PIPEWIRE_INCLUDE_DIRS SPA_INCLUDE_DIRS +  VERSION_VAR PIPEWIRE_VERSION) + +if(PIPEWIRE_FOUND AND NOT TARGET PipeWire::PipeWire) +  add_library(PipeWire::PipeWire UNKNOWN IMPORTED) +  set_target_properties( +    PipeWire::PipeWire +    PROPERTIES IMPORTED_LOCATION "${PIPEWIRE_LIBRARIES}" +               INTERFACE_COMPILE_OPTIONS "${PIPEWIRE_COMPILE_FLAGS}" +               INTERFACE_INCLUDE_DIRECTORIES "${PIPEWIRE_INCLUDE_DIRS};${SPA_INCLUDE_DIRS}") +endif() + +mark_as_advanced(PIPEWIRE_LIBRARIES PIPEWIRE_INCLUDE_DIRS) + +include(FeatureSummary) +set_package_properties( +  PipeWire PROPERTIES +  URL "https://www.pipewire.org" +  DESCRIPTION "PipeWire - multimedia processing") diff --git a/indra/llplugin/llpluginclassmedia.cpp b/indra/llplugin/llpluginclassmedia.cpp index 5857ee32e7..4e5013ec8f 100644 --- a/indra/llplugin/llpluginclassmedia.cpp +++ b/indra/llplugin/llpluginclassmedia.cpp @@ -981,6 +981,15 @@ void LLPluginClassMedia::enableMediaPluginDebugging( bool enable )      sendMessage( message );  } +#if LL_LINUX +void LLPluginClassMedia::enablePipeWireVolumeCatcher( bool enable ) +{ +    LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "enable_pipewire_volume_catcher"); +    message.setValueBoolean( "enable", enable ); +    sendMessage( message ); +} +#endif +  void LLPluginClassMedia::setTarget(const std::string &target)  {      mTarget = target; diff --git a/indra/llplugin/llpluginclassmedia.h b/indra/llplugin/llpluginclassmedia.h index d74b790d8f..5d2f3bbb79 100644 --- a/indra/llplugin/llpluginclassmedia.h +++ b/indra/llplugin/llpluginclassmedia.h @@ -135,6 +135,10 @@ public:      // Text may be unicode (utf8 encoded)      bool textInput(const std::string &text, MASK modifiers, LLSD native_key_data); +#if LL_LINUX +    void enablePipeWireVolumeCatcher( bool enable ); +#endif +      static std::string sOIDcookieUrl;      static std::string sOIDcookieName;      static std::string sOIDcookieValue; diff --git a/indra/media_plugins/base/media_plugin_base.cpp b/indra/media_plugins/base/media_plugin_base.cpp index 3c603440df..b21f29ac32 100644 --- a/indra/media_plugins/base/media_plugin_base.cpp +++ b/indra/media_plugins/base/media_plugin_base.cpp @@ -183,13 +183,13 @@ bool SymbolGrabber::grabSymbols(std::vector< std::string > const &aDSONames)          return true;      //attempt to load the shared libraries -    apr_pool_create(&sSymPADSOMemoryPool, nullptr); +    apr_pool_create(&sSymDSOMemoryPool, nullptr);      for( std::vector< std::string >::const_iterator itr = aDSONames.begin(); itr != aDSONames.end(); ++itr )      {          apr_dso_handle_t *pDSO(NULL);          std::string strDSO{ *itr }; -        if( APR_SUCCESS == apr_dso_load( &pDSO, strDSO.c_str(), sSymPADSOMemoryPool )) +        if( APR_SUCCESS == apr_dso_load( &pDSO, strDSO.c_str(), sSymDSOMemoryPool ))              sLoadedLibraries.push_back( pDSO );          for( auto i = 0; i < gSymbolsToGrab.size(); ++i ) @@ -254,3 +254,50 @@ int WINAPI DllEntryPoint( HINSTANCE hInstance, unsigned long reason, void* param      return 1;  }  #endif + +#if LL_LINUX +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; +} +#endif diff --git a/indra/media_plugins/base/media_plugin_base.h b/indra/media_plugins/base/media_plugin_base.h index d5cb5ac550..a084fc9834 100644 --- a/indra/media_plugins/base/media_plugin_base.h +++ b/indra/media_plugins/base/media_plugin_base.h @@ -52,12 +52,19 @@ private:      std::vector< SymbolToGrab > gSymbolsToGrab;      bool sSymsGrabbed = false; -    apr_pool_t *sSymPADSOMemoryPool = nullptr; +    apr_pool_t *sSymDSOMemoryPool = nullptr;      std::vector<apr_dso_handle_t *> sLoadedLibraries;  };  extern SymbolGrabber gSymbolGrabber; -#define LL_GRAB_SYM(REQUIRED, SYMBOL_NAME, RETURN, ...) RETURN (*ll##SYMBOL_NAME)(__VA_ARGS__) = nullptr; size_t gRegistered##SYMBOL_NAME = gSymbolGrabber.registerSymbol( { REQUIRED, #SYMBOL_NAME , (apr_dso_handle_sym_t*)&ll##SYMBOL_NAME} ); + +// extern SymbolGrabber gSymbolGrabber; + +#define LL_GRAB_SYM(SYMBOL_GRABBER, REQUIRED, SYMBOL_NAME, RETURN, ...) \ +    RETURN (*ll##SYMBOL_NAME)(__VA_ARGS__) = nullptr; \ +    size_t gRegistered##SYMBOL_NAME = SYMBOL_GRABBER.registerSymbol( \ +        { REQUIRED, #SYMBOL_NAME , (apr_dso_handle_sym_t*)&ll##SYMBOL_NAME} \ +    );  #endif @@ -153,4 +160,7 @@ int init_media_plugin(      LLPluginInstance::sendMessageFunction *plugin_send_func,      void **plugin_user_data); - +#if LL_LINUX +pid_t getParentPid(pid_t aPid); +bool isPluginPid(pid_t aPid); +#endif diff --git a/indra/media_plugins/cef/CMakeLists.txt b/indra/media_plugins/cef/CMakeLists.txt index bbd2eb222a..2c4ccd46d7 100644 --- a/indra/media_plugins/cef/CMakeLists.txt +++ b/indra/media_plugins/cef/CMakeLists.txt @@ -33,14 +33,27 @@ if (LINUX)        message( "Looking for ${PULSE_FILE} ... found")      endif()    endforeach() -  message( "Building with linux volume catcher" ) -  set(LINUX_VOLUME_CATCHER linux_volume_catcher.cpp) +   +  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'")    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/linux/volume_catcher_linux.cpp b/indra/media_plugins/cef/linux/volume_catcher_linux.cpp new file mode 100644 index 0000000000..7d33242063 --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_linux.cpp @@ -0,0 +1,78 @@ +/** + * @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" + +VolumeCatcher::VolumeCatcher() +{ +} + +void VolumeCatcher::onEnablePipeWireVolumeCatcher(bool enable) +{ +    if (pimpl != nullptr) +        return; + +    if (enable) +    { +        LL_DEBUGS() << "volume catcher using pipewire" << LL_ENDL; +        pimpl = new VolumeCatcherPipeWire(); +    } +    else +    { +        LL_DEBUGS() << "volume catcher using pulseaudio" << LL_ENDL; +        pimpl = new VolumeCatcherPulseAudio(); +    } +} + +VolumeCatcher::~VolumeCatcher() +{ +    if (pimpl != nullptr) +    { +        delete pimpl; +        pimpl = nullptr; +    } +} + +void VolumeCatcher::setVolume(F32 volume) +{ +    if (pimpl != nullptr) { +        pimpl->setVolume(volume); +    } +} + +void VolumeCatcher::setPan(F32 pan) +{ +    if (pimpl != nullptr) +        pimpl->setPan(pan); +} + +void VolumeCatcher::pump() +{ +    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 new file mode 100644 index 0000000000..505f9ffb31 --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_linux.h @@ -0,0 +1,149 @@ +/** + * @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 <unordered_set> +#include <mutex> + +extern "C" { +// There's no special reason why we want the *glib* PA mainloop, but the generic polling implementation seems broken. +#include <pulse/glib-mainloop.h> +#include <pulse/context.h> + +#include <pipewire/pipewire.h> + +#include "apr_pools.h" +#include "apr_dso.h" +} + +#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: +    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<U32> mSinkInputIndices; +    std::map<U32,U32> mSinkInputNumChannels; +    F32 mDesiredVolume; +    pa_glib_mainloop *mMainloop; +    pa_context *mPAContext; +    bool mConnected; +    bool mGotSyms; +}; + +class VolumeCatcherPipeWire : public VolumeCatcherImpl +{ +public: +    VolumeCatcherPipeWire(); +    ~VolumeCatcherPipeWire(); + +    bool loadsyms(std::string pw_dso_name); +    void init(); +    void cleanup(); + +    // some of these should be private + +    void lock(); +    void unlock(); + +    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 = nullptr; +    pw_context* mContext = nullptr; +    pw_core* mCore = nullptr; +    pw_registry* mRegistry = nullptr; +    spa_hook mRegistryListener; + +    std::unordered_set<ChildNode*> mChildNodes; +    std::mutex mChildNodesMutex; +    std::mutex mCleanupMutex; +}; + +#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..27fea547c9 --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp @@ -0,0 +1,333 @@ +/**  + * @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 <spa/pod/builder.h> +#include <spa/param/props.h> +} + +SymbolGrabber 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<VolumeCatcherPipeWire*>(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() +{ +    LL_DEBUGS() << "init" << LL_ENDL; + +    mGotSyms = loadsyms("libpipewire-0.3.so.0"); + +    if (!mGotSyms) +        return; + +    LL_DEBUGS() << "successfully got symbols" << LL_ENDL; + +    llpw_init(nullptr, nullptr); + +    mThreadLoop = llpw_thread_loop_new("SL Plugin Volume Adjuster", nullptr); + +    if (!mThreadLoop) +        return; + +    // 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 +    ); + +    if (!mContext) +        return; + +    mCore = llpw_context_connect(mContext, nullptr, 0); + +    if (!mCore) +        return; + +    mRegistry = pw_core_get_registry(mCore, PW_VERSION_REGISTRY, 0); + +    LL_DEBUGS() << "pw_core_get_registry: " <<  (mRegistry?"success":"nullptr") << LL_ENDL; + +    spa_zero(mRegistryListener); + +    pw_registry_add_listener( +        mRegistry, &mRegistryListener, ®ISTRY_EVENTS, this +    ); + +    llpw_thread_loop_start(mThreadLoop); + +    LL_DEBUGS() << "thread loop started" << LL_ENDL; +} + +void VolumeCatcherPipeWire::cleanup() +{ +    { +        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; + +    llpw_thread_loop_stop(mThreadLoop); +    llpw_thread_loop_destroy(mThreadLoop); + +    LL_DEBUGS() << "cleanup done" << LL_ENDL; +} + +void VolumeCatcherPipeWire::lock() +{ +    if (!mThreadLoop) +        return; + +    llpw_thread_loop_lock(mThreadLoop); +} + +void VolumeCatcherPipeWire::unlock() +{ +    if (!mThreadLoop) +        return; + +    llpw_thread_loop_unlock(mThreadLoop); +} + +const uint32_t channels = 1; +const float resetVolumes[channels] = { 1.0f }; + +void VolumeCatcherPipeWire::ChildNode::updateVolume() +{ +    if (!mActive) +        return; + +    F32 volume = std::clamp(mImpl->mVolume, 0.0f, 1.0f); + +    const float volumes[channels] = { 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); + +    // 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*>(spa_pod_builder_pop(&builder, &frame)); + +    { +        std::lock_guard pwLock(*mImpl); +        pw_node_set_param(mProxy, SPA_PARAM_Props, 0, pod); +    } +} + +void VolumeCatcherPipeWire::ChildNode::destroy() +{ +    if (!mActive) +        return; + +    mActive = false; + +    { +        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); +    } +} + +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); +    LL_DEBUGS() << "got app: " << appName << LL_ENDL; + +    auto* const childNode = static_cast<VolumeCatcherPipeWire::ChildNode*>(data); +    LL_DEBUGS() << "init volume: " << childNode->mImpl->mVolume  << LL_ENDL; + +    childNode->updateVolume(); + +    { +        std::lock_guard childNodesLock(childNode->mImpl->mChildNodesMutex); +        childNode->mImpl->mChildNodes.insert(childNode); +    } +} + +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<VolumeCatcherPipeWire::ChildNode*>(data); +    childNode->destroy(); +} + +static void proxyEventRemoved(void* data) +{ +    auto* const childNode = static_cast<VolumeCatcherPipeWire::ChildNode*>(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  || 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_proxy*>( +        pw_registry_bind(mRegistry, id, type, PW_VERSION_CLIENT, sizeof(ChildNode)) +    ); + +    auto* const childNode = static_cast<ChildNode*>(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; + +    { +        std::unique_lock childNodeslock(mChildNodesMutex); +        std::unordered_set<ChildNode *> copyOfChildNodes(mChildNodes); + +        LL_DEBUGS() << "found " << copyOfChildNodes.size() << " child nodes" << LL_ENDL; + +        for (auto* childNode : copyOfChildNodes) +            childNode->updateVolume(); +    } +} + +void VolumeCatcherPipeWire::setPan(F32 pan) +{ +} + +void VolumeCatcherPipeWire::pump() +{ +} 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..dbc0f5f169 --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_pipewire_syms.inc @@ -0,0 +1,26 @@ +#define G pwSymbolGrabber + +// required symbols to grab +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.cpp b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp index 982b79f5b3..9417c49d38 100755 --- a/indra/media_plugins/cef/linux_volume_catcher.cpp +++ b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp @@ -1,26 +1,26 @@ -/** - * @file linux_volume_catcher.cpp +/**  + * @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 @@ -37,29 +37,22 @@  #include "linden_common.h" -#include "volume_catcher.h" -#include <set> -#include <map> -#include <iostream> +#include "volume_catcher_linux.h" +  extern "C" {  #include <glib.h>  #include <glib-object.h>  #include <pulse/introspect.h> -#include <pulse/context.h> -#include <pulse/subscribe.h> -#include <pulse/glib-mainloop.h> // 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 <pulse/subscribe.h>  } -#include "media_plugin_base.h" +SymbolGrabber paSymbolGrabber; -SymbolGrabber gSymbolGrabber; +#include "volume_catcher_pulseaudio_syms.inc" +#include "volume_catcher_pulseaudio_glib_syms.inc" -#include "linux_volume_catcher_pa_syms.inc" -#include "linux_volume_catcher_paglib_syms.inc"  ////////////////////////////////////////////////////  // PulseAudio requires a chain of callbacks with C linkage @@ -69,35 +62,7 @@ extern "C" {      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<U32> mSinkInputIndices; -    std::map<U32,U32> mSinkInputNumChannels; -    F32 mDesiredVolume; -    pa_glib_mainloop *mMainloop; -    pa_context *mPAContext; -    bool mConnected; -    bool mGotSyms; -}; - -VolumeCatcherImpl::VolumeCatcherImpl() +VolumeCatcherPulseAudio::VolumeCatcherPulseAudio()      : mDesiredVolume(0.0f),        mMainloop(nullptr),        mPAContext(nullptr), @@ -107,18 +72,17 @@ VolumeCatcherImpl::VolumeCatcherImpl()      init();  } -VolumeCatcherImpl::~VolumeCatcherImpl() +VolumeCatcherPulseAudio::~VolumeCatcherPulseAudio()  {      cleanup();  } -bool VolumeCatcherImpl::loadsyms(std::string pulse_dso_name) +bool VolumeCatcherPulseAudio::loadsyms(std::string pulse_dso_name)  { -    //return grab_pa_syms({pulse_dso_name}); -    return gSymbolGrabber.grabSymbols( { pulse_dso_name }) ; +    return paSymbolGrabber.grabSymbols({ pulse_dso_name });  } -void VolumeCatcherImpl::init() +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 @@ -129,7 +93,12 @@ 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) return; + +    if (!mGotSyms) +        mGotSyms = loadsyms("libpulse.so.0"); + +    if (!mGotSyms) +        return;      mMainloop = llpa_glib_mainloop_new(g_main_context_default()); @@ -175,7 +144,7 @@ void VolumeCatcherImpl::init()      }  } -void VolumeCatcherImpl::cleanup() +void VolumeCatcherPulseAudio::cleanup()  {      mConnected = false; @@ -193,11 +162,12 @@ void VolumeCatcherImpl::cleanup()      mMainloop = nullptr;  } -void VolumeCatcherImpl::setVolume(F32 volume) +void VolumeCatcherPulseAudio::setVolume(F32 volume)  {      mDesiredVolume = volume; -    if (!mGotSyms) return; +    if (!mGotSyms) +        return;      if (mConnected && mPAContext)      { @@ -207,13 +177,17 @@ void VolumeCatcherImpl::setVolume(F32 volume)      pump();  } -void VolumeCatcherImpl::pump() +void VolumeCatcherPulseAudio::setPan(F32 pan) +{ +} + +void VolumeCatcherPulseAudio::pump()  {      gboolean may_block = FALSE;      g_main_context_iteration(g_main_context_default(), may_block);  } -void VolumeCatcherImpl::connected_okay() +void VolumeCatcherPulseAudio::connected_okay()  {      pa_operation *op; @@ -237,7 +211,7 @@ void VolumeCatcherImpl::connected_okay()      }  } -void VolumeCatcherImpl::update_all_volumes(F32 volume) +void VolumeCatcherPulseAudio::update_all_volumes(F32 volume)  {      for (std::set<U32>::iterator it = mSinkInputIndices.begin();           it != mSinkInputIndices.end(); ++it) @@ -246,7 +220,7 @@ void VolumeCatcherImpl::update_all_volumes(F32 volume)      }  } -void VolumeCatcherImpl::update_index_volume(U32 index, F32 volume) +void VolumeCatcherPulseAudio::update_index_volume(U32 index, F32 volume)  {      static pa_cvolume cvol;      llpa_cvolume_set(&cvol, mSinkInputNumChannels[index], @@ -263,55 +237,9 @@ void VolumeCatcherImpl::update_index_volume(U32 index, F32 volume)          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*>((VolumeCatcherImpl*)userdata); +    VolumeCatcherPulseAudio *impl = dynamic_cast<VolumeCatcherPulseAudio*>((VolumeCatcherPulseAudio*)userdata);      llassert(impl);      if (0 == eol) @@ -341,7 +269,7 @@ void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info  void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata)  { -    VolumeCatcherImpl *impl = dynamic_cast<VolumeCatcherImpl*>((VolumeCatcherImpl*)userdata); +    VolumeCatcherPulseAudio *impl = dynamic_cast<VolumeCatcherPulseAudio*>((VolumeCatcherPulseAudio*)userdata);      llassert(impl);      switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) @@ -374,7 +302,7 @@ void callback_subscription_alert(pa_context *context, pa_subscription_event_type  void callback_context_state(pa_context *context, void *userdata)  { -    VolumeCatcherImpl *impl = dynamic_cast<VolumeCatcherImpl*>((VolumeCatcherImpl*)userdata); +    VolumeCatcherPulseAudio *impl = dynamic_cast<VolumeCatcherPulseAudio*>((VolumeCatcherPulseAudio*)userdata);      llassert(impl);      switch (llpa_context_get_state(context)) @@ -392,33 +320,3 @@ void callback_context_state(pa_context *context, void *userdata)      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_pulseaudio_glib_syms.inc b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_glib_syms.inc new file mode 100755 index 0000000000..e9b7196e51 --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_glib_syms.inc @@ -0,0 +1,10 @@ +#define G paSymbolGrabber + +// required symbols to grab +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 new file mode 100755 index 0000000000..4859a34405 --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_syms.inc @@ -0,0 +1,29 @@ +#define G paSymbolGrabber + +// required symbols to grab +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 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/media_plugin_cef.cpp b/indra/media_plugins/cef/media_plugin_cef.cpp index c1bba0fdd1..1346dd2a52 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 ea97a24947..6933854e8e 100644 --- a/indra/media_plugins/cef/volume_catcher.h +++ b/indra/media_plugins/cef/volume_catcher.h @@ -35,19 +35,20 @@ class VolumeCatcherImpl;  class VolumeCatcher  { - 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 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 +    void pump(); + +#if LL_LINUX +    void onEnablePipeWireVolumeCatcher(bool enable); +#endif - private: +private:      VolumeCatcherImpl *pimpl;  }; diff --git a/indra/media_plugins/cef/mac_volume_catcher_null.cpp b/indra/media_plugins/cef/volume_catcher_null.cpp index c479e24a95..c6028da45b 100644 --- a/indra/media_plugins/cef/mac_volume_catcher_null.cpp +++ b/indra/media_plugins/cef/volume_catcher_null.cpp @@ -1,5 +1,5 @@ -/** - * @file windows_volume_catcher.cpp +/**  + * @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 @@ -29,67 +29,25 @@   */  #include "volume_catcher.h" -#include "llsingleton.h" -class VolumeCatcherImpl : public LLSingleton<VolumeCatcherImpl> -{ -    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/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() diff --git a/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms_raw.inc b/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms_raw.inc index e5abf22203..6f5bb04bdf 100644 --- a/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms_raw.inc +++ b/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms_raw.inc @@ -1,67 +1,71 @@ -LL_GRAB_SYM(true, gst_buffer_new, GstBuffer*, void) -LL_GRAB_SYM(true, gst_structure_set_value, void, GstStructure *, const gchar *, const GValue*) -LL_GRAB_SYM(true, gst_init_check, gboolean, int *argc, char **argv[], GError ** err) -LL_GRAB_SYM(true, gst_message_get_type, GType, void) -LL_GRAB_SYM(true, gst_message_type_get_name, const gchar*, GstMessageType type) -LL_GRAB_SYM(true, gst_message_parse_error, void, GstMessage *message, GError **gerror, gchar **debug) -LL_GRAB_SYM(true, gst_message_parse_warning, void, GstMessage *message, GError **gerror, gchar **debug) -LL_GRAB_SYM(true, gst_message_parse_state_changed, void, GstMessage *message, GstState *oldstate, GstState *newstate, GstState *pending) -LL_GRAB_SYM(true, gst_element_set_state, GstStateChangeReturn, GstElement *element, GstState state) -LL_GRAB_SYM(true, gst_object_unref, void, gpointer object) -LL_GRAB_SYM(true, gst_object_get_type, GType, void) -LL_GRAB_SYM(true, gst_pipeline_get_type, GType, void) -LL_GRAB_SYM(true, gst_pipeline_get_bus, GstBus*, GstPipeline *pipeline) -LL_GRAB_SYM(true, gst_bus_add_watch, guint, GstBus * bus, GstBusFunc func, gpointer user_data) -LL_GRAB_SYM(true, gst_element_factory_make, GstElement*, const gchar *factoryname, const gchar *name) -LL_GRAB_SYM(true, gst_element_get_type, GType, void) -LL_GRAB_SYM(true, gst_static_pad_template_get, GstPadTemplate*, GstStaticPadTemplate *pad_template) -LL_GRAB_SYM(true, gst_element_class_add_pad_template, void, GstElementClass *klass, GstPadTemplate *temp) -LL_GRAB_SYM(true, gst_caps_from_string, GstCaps *, const gchar *string) -LL_GRAB_SYM(true, gst_caps_get_structure, GstStructure *, const GstCaps *caps, guint index) -LL_GRAB_SYM(true, gst_element_register, gboolean, GstPlugin *plugin, const gchar *name, guint rank, GType type) -LL_GRAB_SYM(true, gst_structure_get_int, gboolean, const GstStructure *structure, const gchar *fieldname, gint *value) -LL_GRAB_SYM(true, gst_structure_get_value, const GValue *, const GstStructure *structure, const gchar *fieldname) -LL_GRAB_SYM(true, gst_value_get_fraction_numerator, gint, const GValue *value) -LL_GRAB_SYM(true, gst_value_get_fraction_denominator, gint, const GValue *value) -LL_GRAB_SYM(true, gst_structure_get_name, const gchar *, const GstStructure *structure) -LL_GRAB_SYM(true, gst_element_seek, bool, GstElement *, gdouble, GstFormat, GstSeekFlags, GstSeekType, gint64, GstSeekType, gint64) +#define G gstSymbolGrabber -LL_GRAB_SYM(false, gst_registry_fork_set_enabled, void, gboolean enabled) -LL_GRAB_SYM(false, gst_segtrap_set_enabled, void, gboolean enabled) -LL_GRAB_SYM(false, gst_message_parse_buffering, void, GstMessage *message, gint *percent) -LL_GRAB_SYM(false, gst_message_parse_info, void, GstMessage *message, GError **gerror, gchar **debug) -LL_GRAB_SYM(false, gst_element_query_position, gboolean, GstElement *element, GstFormat *format, gint64 *cur) -LL_GRAB_SYM(false, gst_version, void, guint *major, guint *minor, guint *micro, guint *nano) +LL_GRAB_SYM(G, true, gst_buffer_new, GstBuffer*, void) +LL_GRAB_SYM(G, true, gst_structure_set_value, void, GstStructure *, const gchar *, const GValue*) +LL_GRAB_SYM(G, true, gst_init_check, gboolean, int *argc, char **argv[], GError ** err) +LL_GRAB_SYM(G, true, gst_message_get_type, GType, void) +LL_GRAB_SYM(G, true, gst_message_type_get_name, const gchar*, GstMessageType type) +LL_GRAB_SYM(G, true, gst_message_parse_error, void, GstMessage *message, GError **gerror, gchar **debug) +LL_GRAB_SYM(G, true, gst_message_parse_warning, void, GstMessage *message, GError **gerror, gchar **debug) +LL_GRAB_SYM(G, true, gst_message_parse_state_changed, void, GstMessage *message, GstState *oldstate, GstState *newstate, GstState *pending) +LL_GRAB_SYM(G, true, gst_element_set_state, GstStateChangeReturn, GstElement *element, GstState state) +LL_GRAB_SYM(G, true, gst_object_unref, void, gpointer object) +LL_GRAB_SYM(G, true, gst_object_get_type, GType, void) +LL_GRAB_SYM(G, true, gst_pipeline_get_type, GType, void) +LL_GRAB_SYM(G, true, gst_pipeline_get_bus, GstBus*, GstPipeline *pipeline) +LL_GRAB_SYM(G, true, gst_bus_add_watch, guint, GstBus * bus, GstBusFunc func, gpointer user_data) +LL_GRAB_SYM(G, true, gst_element_factory_make, GstElement*, const gchar *factoryname, const gchar *name) +LL_GRAB_SYM(G, true, gst_element_get_type, GType, void) +LL_GRAB_SYM(G, true, gst_static_pad_template_get, GstPadTemplate*, GstStaticPadTemplate *pad_template) +LL_GRAB_SYM(G, true, gst_element_class_add_pad_template, void, GstElementClass *klass, GstPadTemplate *temp) +LL_GRAB_SYM(G, true, gst_caps_from_string, GstCaps *, const gchar *string) +LL_GRAB_SYM(G, true, gst_caps_get_structure, GstStructure *, const GstCaps *caps, guint index) +LL_GRAB_SYM(G, true, gst_element_register, gboolean, GstPlugin *plugin, const gchar *name, guint rank, GType type) +LL_GRAB_SYM(G, true, gst_structure_get_int, gboolean, const GstStructure *structure, const gchar *fieldname, gint *value) +LL_GRAB_SYM(G, true, gst_structure_get_value, const GValue *, const GstStructure *structure, const gchar *fieldname) +LL_GRAB_SYM(G, true, gst_value_get_fraction_numerator, gint, const GValue *value) +LL_GRAB_SYM(G, true, gst_value_get_fraction_denominator, gint, const GValue *value) +LL_GRAB_SYM(G, true, gst_structure_get_name, const gchar *, const GstStructure *structure) +LL_GRAB_SYM(G, true, gst_element_seek, bool, GstElement *, gdouble, GstFormat, GstSeekFlags, GstSeekType, gint64, GstSeekType, gint64) -LL_GRAB_SYM( true, gst_message_parse_tag, void, GstMessage *, GstTagList **) -LL_GRAB_SYM( true, gst_tag_list_foreach, void, const GstTagList *, GstTagForeachFunc, gpointer) -LL_GRAB_SYM( true, gst_tag_list_get_tag_size, guint, const GstTagList *, const gchar *) -LL_GRAB_SYM( true, gst_tag_list_get_value_index, const GValue *, const GstTagList *, const gchar *, guint) +LL_GRAB_SYM(G, false, gst_registry_fork_set_enabled, void, gboolean enabled) +LL_GRAB_SYM(G, false, gst_segtrap_set_enabled, void, gboolean enabled) +LL_GRAB_SYM(G, false, gst_message_parse_buffering, void, GstMessage *message, gint *percent) +LL_GRAB_SYM(G, false, gst_message_parse_info, void, GstMessage *message, GError **gerror, gchar **debug) +LL_GRAB_SYM(G, false, gst_element_query_position, gboolean, GstElement *element, GstFormat *format, gint64 *cur) +LL_GRAB_SYM(G, false, gst_version, void, guint *major, guint *minor, guint *micro, guint *nano) -LL_GRAB_SYM( true, gst_caps_new_simple, GstCaps*, const char *, const char*, ... ) +LL_GRAB_SYM(G, true, gst_message_parse_tag, void, GstMessage *, GstTagList **) +LL_GRAB_SYM(G, true, gst_tag_list_foreach, void, const GstTagList *, GstTagForeachFunc, gpointer) +LL_GRAB_SYM(G, true, gst_tag_list_get_tag_size, guint, const GstTagList *, const gchar *) +LL_GRAB_SYM(G, true, gst_tag_list_get_value_index, const GValue *, const GstTagList *, const gchar *, guint) -LL_GRAB_SYM( true, gst_sample_get_caps, GstCaps*, GstSample* ) -LL_GRAB_SYM( true, gst_sample_get_buffer, GstBuffer*, GstSample* ) -LL_GRAB_SYM( true, gst_buffer_map, gboolean, GstBuffer*, GstMapInfo*, GstMapFlags ) -LL_GRAB_SYM( true, gst_buffer_unmap, void, GstBuffer*, GstMapInfo* ) +LL_GRAB_SYM(G, true, gst_caps_new_simple, GstCaps*, const char *, const char*, ... ) -LL_GRAB_SYM( true, gst_app_sink_set_caps, void, GstAppSink*, GstCaps const* ) -LL_GRAB_SYM( true, gst_app_sink_pull_sample, GstSample*, GstAppSink* ) +LL_GRAB_SYM(G, true, gst_sample_get_caps, GstCaps*, GstSample* ) +LL_GRAB_SYM(G, true, gst_sample_get_buffer, GstBuffer*, GstSample* ) +LL_GRAB_SYM(G, true, gst_buffer_map, gboolean, GstBuffer*, GstMapInfo*, GstMapFlags ) +LL_GRAB_SYM(G, true, gst_buffer_unmap, void, GstBuffer*, GstMapInfo* ) -LL_GRAB_SYM( true, g_free, void, gpointer ) -LL_GRAB_SYM( true, g_error_free, void, GError* ) +LL_GRAB_SYM(G, true, gst_app_sink_set_caps, void, GstAppSink*, GstCaps const* ) +LL_GRAB_SYM(G, true, gst_app_sink_pull_sample, GstSample*, GstAppSink* ) -LL_GRAB_SYM( true, g_main_context_pending, gboolean, GMainContext* ) -LL_GRAB_SYM( true, g_main_loop_get_context, GMainContext*, GMainLoop* ) -LL_GRAB_SYM( true, g_main_context_iteration, gboolean, GMainContext*, gboolean ) -LL_GRAB_SYM( true, g_main_loop_new, GMainLoop*, GMainContext*, gboolean ) -LL_GRAB_SYM( true, g_main_loop_quit, void, GMainLoop* ) -LL_GRAB_SYM( true, gst_mini_object_unref, void, GstMiniObject* ) -LL_GRAB_SYM( true, g_object_set, void, gpointer, gchar const*, ... ) -LL_GRAB_SYM( true, g_source_remove, gboolean, guint ) -LL_GRAB_SYM( true, g_value_get_string, gchar const*, GValue const* ) +LL_GRAB_SYM(G, true, g_free, void, gpointer ) +LL_GRAB_SYM(G, true, g_error_free, void, GError* ) -LL_GRAB_SYM( true, gst_debug_set_active, void, gboolean ) -LL_GRAB_SYM( true, gst_debug_add_log_function, void, GstLogFunction, gpointer, GDestroyNotify ) -LL_GRAB_SYM( true, gst_debug_set_default_threshold, void, GstDebugLevel ) -LL_GRAB_SYM( true, gst_debug_message_get , gchar const*, GstDebugMessage * )
\ No newline at end of file +LL_GRAB_SYM(G, true, g_main_context_pending, gboolean, GMainContext* ) +LL_GRAB_SYM(G, true, g_main_loop_get_context, GMainContext*, GMainLoop* ) +LL_GRAB_SYM(G, true, g_main_context_iteration, gboolean, GMainContext*, gboolean ) +LL_GRAB_SYM(G, true, g_main_loop_new, GMainLoop*, GMainContext*, gboolean ) +LL_GRAB_SYM(G, true, g_main_loop_quit, void, GMainLoop* ) +LL_GRAB_SYM(G, true, gst_mini_object_unref, void, GstMiniObject* ) +LL_GRAB_SYM(G, true, g_object_set, void, gpointer, gchar const*, ... ) +LL_GRAB_SYM(G, true, g_source_remove, gboolean, guint ) +LL_GRAB_SYM(G, true, g_value_get_string, gchar const*, GValue const* ) + +LL_GRAB_SYM(G, true, gst_debug_set_active, void, gboolean ) +LL_GRAB_SYM(G, true, gst_debug_add_log_function, void, GstLogFunction, gpointer, GDestroyNotify ) +LL_GRAB_SYM(G, true, gst_debug_set_default_threshold, void, GstDebugLevel ) +LL_GRAB_SYM(G, true, gst_debug_message_get , gchar const*, GstDebugMessage * ) + +#undef G diff --git a/indra/media_plugins/gstreamer10/media_plugin_gstreamer10.cpp b/indra/media_plugins/gstreamer10/media_plugin_gstreamer10.cpp index dbc544d96b..3f636915ea 100644 --- a/indra/media_plugins/gstreamer10/media_plugin_gstreamer10.cpp +++ b/indra/media_plugins/gstreamer10/media_plugin_gstreamer10.cpp @@ -41,9 +41,9 @@  extern "C" {  #include <gst/gst.h>  #include <gst/app/gstappsink.h> -  } -SymbolGrabber gSymbolGrabber; + +SymbolGrabber gstSymbolGrabber;  #include "llmediaimplgstreamer_syms_raw.inc" @@ -650,7 +650,7 @@ bool MediaPluginGStreamer10::startup()          vctDSONames.push_back( "libgstapp-1.0.so.0"  );          vctDSONames.push_back( "libglib-2.0.so.0" );          vctDSONames.push_back( "libgobject-2.0.so" ); -        if( !gSymbolGrabber.grabSymbols( vctDSONames ) ) +        if( !gstSymbolGrabber.grabSymbols( vctDSONames ) )              return false;          if (llgst_segtrap_set_enabled) @@ -712,7 +712,7 @@ bool MediaPluginGStreamer10::closedown()      if (!mDoneInit)          return false; // error -    gSymbolGrabber.ungrabSymbols(); +    gstSymbolGrabber.ungrabSymbols();      mDoneInit = false;      return true; diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 85f2b2d303..6424ae7da5 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -4920,6 +4920,17 @@      <key>Value</key>      <integer>0</integer>    </map> +  <key>MediaPluginPipeWireVolumeCatcher</key> +  <map> +    <key>Comment</key> +    <string>Use PipeWire instead of PulseAudio for controlling web media volume.</string> +    <key>Persist</key> +    <integer>1</integer> +    <key>Type</key> +    <string>Boolean</string> +    <key>Value</key> +    <integer>0</integer> +  </map>    <key>MediaControlFadeTime</key>    <map>      <key>Comment</key> diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index c4f3479204..c8b7a9c29b 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -1765,6 +1765,11 @@ LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_              bool media_plugin_debugging_enabled = gSavedSettings.getBOOL("MediaPluginDebugging");              media_source->enableMediaPluginDebugging( media_plugin_debugging_enabled  || clean_browser); +#if LL_LINUX +            bool media_plugin_pipewire_volume_catcher = gSavedSettings.getBOOL("MediaPluginPipeWireVolumeCatcher"); +            media_source->enablePipeWireVolumeCatcher( media_plugin_pipewire_volume_catcher ); +#endif +              // need to set agent string here before instance created              media_source->setBrowserUserAgent(LLViewerMedia::getInstance()->getCurrentUserAgent());  | 
