diff options
Diffstat (limited to 'indra/media_plugins')
31 files changed, 2329 insertions, 2386 deletions
| diff --git a/indra/media_plugins/CMakeLists.txt b/indra/media_plugins/CMakeLists.txt index 972bb7dd2d..78bdba8f18 100644 --- a/indra/media_plugins/CMakeLists.txt +++ b/indra/media_plugins/CMakeLists.txt @@ -2,9 +2,15 @@  add_subdirectory(base) +if (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") +    add_subdirectory(example) +    add_subdirectory(libvlc) +endif (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") +  if (LINUX) -    #add_subdirectory(gstreamer010) +    add_subdirectory(cef)      add_subdirectory(example) +    add_subdirectory(libvlc)  endif (LINUX)  if (DARWIN) diff --git a/indra/media_plugins/base/CMakeLists.txt b/indra/media_plugins/base/CMakeLists.txt index 64b6a4228d..57e3782ada 100644 --- a/indra/media_plugins/base/CMakeLists.txt +++ b/indra/media_plugins/base/CMakeLists.txt @@ -12,13 +12,6 @@ include(PluginAPI)  ### media_plugin_base -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_base_SOURCE_FILES      media_plugin_base.cpp @@ -31,6 +24,7 @@ set(media_plugin_base_HEADER_FILES  )  add_library(media_plugin_base +    STATIC      ${media_plugin_base_SOURCE_FILES}          ) diff --git a/indra/media_plugins/base/media_plugin_base.cpp b/indra/media_plugins/base/media_plugin_base.cpp index 545eee25a9..d54e67c532 100644 --- a/indra/media_plugins/base/media_plugin_base.cpp +++ b/indra/media_plugins/base/media_plugin_base.cpp @@ -167,10 +167,60 @@ void MediaPluginBase::sendStatus()      sendMessage(message);  } +#if LL_LINUX || __FreeBSD__ + +size_t SymbolGrabber::registerSymbol( SymbolToGrab aSymbol ) +{ +    gSymbolsToGrab.emplace_back(aSymbol); +    return gSymbolsToGrab.size(); +} + +bool SymbolGrabber::grabSymbols(std::vector< std::string > const &aDSONames) +{ +    std::cerr << "SYMBOLS: " << gSymbolsToGrab.size() << std::endl; + +    if (sSymsGrabbed) +        return true; + +    //attempt to load the shared libraries +    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(), sSymDSOMemoryPool )) +            sLoadedLibraries.push_back( pDSO ); + +        for( auto i = 0; i < gSymbolsToGrab.size(); ++i ) +        { +            if( !*gSymbolsToGrab[i].mPPFunc ) +                apr_dso_sym( gSymbolsToGrab[i].mPPFunc, pDSO, gSymbolsToGrab[i].mName ); +        } +    } + +    bool sym_error = false; + +    for( auto i = 0; i < gSymbolsToGrab.size(); ++i ) +    { +        if( gSymbolsToGrab[ i ].mRequired && ! *gSymbolsToGrab[ i ].mPPFunc ) +            sym_error = true; +    } + +    sSymsGrabbed = !sym_error; +    return sSymsGrabbed; +} + +void SymbolGrabber::ungrabSymbols() +{ + +} +#endif +  #if LL_WINDOWS  # define LLSYMEXPORT __declspec(dllexport) -#elif LL_LINUX +#elif LL_LINUX || __FreeBSD__  # define LLSYMEXPORT __attribute__ ((visibility("default")))  #else  # define LLSYMEXPORT /**/ @@ -204,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 f65c712a66..a6f3bc7d71 100644 --- a/indra/media_plugins/base/media_plugin_base.h +++ b/indra/media_plugins/base/media_plugin_base.h @@ -32,6 +32,41 @@  #include "llpluginmessage.h"  #include "llpluginmessageclasses.h" +#if LL_LINUX || __FreeBSD__ + +struct SymbolToGrab +{ +    bool mRequired; +    char const *mName; +    apr_dso_handle_sym_t *mPPFunc; +}; + +class SymbolGrabber +{ +public: +    size_t registerSymbol( SymbolToGrab aSymbol ); +    bool grabSymbols(std::vector< std::string > const &aDSONames); +    void ungrabSymbols(); + +private: +    std::vector< SymbolToGrab > gSymbolsToGrab; + +    bool sSymsGrabbed = false; +    apr_pool_t *sSymDSOMemoryPool = nullptr; +    std::vector<apr_dso_handle_t *> sLoadedLibraries; +}; + +extern SymbolGrabber gSymbolGrabber; + +// 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  class MediaPluginBase  { @@ -46,7 +81,6 @@ public:      static void staticReceiveMessage(const char *message_string, void **user_data);  protected: -     /** Plugin status. */      typedef enum      { @@ -126,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 410778114d..7f44270fe7 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,10 +24,36 @@ 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() +   +  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 @@ -60,6 +78,7 @@ add_library(media_plugin_cef  target_link_libraries(media_plugin_cef          media_plugin_base          ll::cef +        ll::glib_headers  )  if (WINDOWS) @@ -84,9 +103,42 @@ if (DARWIN)    add_custom_command(TARGET media_plugin_cef      POST_BUILD COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change "@executable_path/Chromium Embedded Framework"          "@executable_path/../../../../Frameworks/Chromium Embedded Framework.framework/Chromium Embedded Framework" +        -change "/opt/local/lib/libopenjp2.7.dylib" "@loader_path/../../Frameworks/libopenjp2.7.dylib" +        -change "/opt/local/lib/libpng16.16.dylib" "@loader_path/../../Frameworks/libpng16.16.dylib" +        -change "/opt/local/lib/libjpeg.8.dylib" "@loader_path/../../Frameworks/libjpeg.8.dylib" +        -change "/opt/local/lib/libfreetype.6.dylib" "@loader_path/../../Frameworks/libfreetype.6.dylib" +        -change "/opt/local/lib/libaprutil-1.0.dylib" "@loader_path/../../Frameworks/libaprutil-1.0.dylib" +        -change "/opt/local/lib/libiconv.2.dylib" "@loader_path/../../Frameworks/libiconv.2.dylib" +        -change "/opt/local/lib/libapr-1.0.dylib" "@loader_path/../../Frameworks/libapr-1.0.dylib" +        -change "/opt/local/lib/libexpat.1.dylib" "@loader_path/../../Frameworks/libexpat.1.dylib" +        -change "/opt/local/lib/libjsoncpp.25.dylib" "@loader_path/../../Frameworks/libjsoncpp.25.dylib" +        -change "/opt/local/lib/libz.1.dylib" "@loader_path/../../Frameworks/libz.1.dylib" +        -change "/opt/local/libexec/boost/1.76/lib/libboost_context-mt.dylib" "@loader_path/../../Frameworks/libboost_context-mt.dylib" +        -change "/opt/local/libexec/boost/1.76/lib/libboost_fiber-mt.dylib" "@loader_path/../../Frameworks/libboost_fiber-mt.dylib" +        -change "/opt/local/libexec/boost/1.76/lib/libboost_filesystem-mt.dylib" "@loader_path/../../Frameworks/libboost_filesystem-mt.dylib" +        -change "/opt/local/libexec/boost/1.76/lib/libboost_program_options-mt.dylib" "@loader_path/../../Frameworks/libboost_program_options-mt.dylib" +        -change "/opt/local/libexec/boost/1.76/lib/libboost_regex-mt.dylib" "@loader_path/../../Frameworks/libboost_regex-mt.dylib" +        -change "/opt/local/libexec/boost/1.76/lib/libboost_system-mt.dylib" "@loader_path/../../Frameworks/libboost_system-mt.dylib" +        -change "/opt/local/libexec/boost/1.76/lib/libboost_thread-mt.dylib" "@loader_path/../../Frameworks/libboost_thread-mt.dylib" +        -change "/opt/local/lib/liburiparser.1.dylib" "@loader_path/../../Frameworks/liburiparser.1.dylib" +        -change "/opt/local/lib/libnghttp2.14.dylib" "@loader_path/../../Frameworks/libnghttp2.14.dylib" +        -change "/usr/local/lib/libxmlrpc-epi.0.dylib" "@loader_path/../../Frameworks/libxmlrpc-epi.0.dylib"          "$<TARGET_FILE:media_plugin_cef>"      VERBATIM      COMMENT "Fixing path to CEF Framework"    )  endif (DARWIN) + +if (INSTALL) +    if (DARWIN) +        set(_LIB llplugin) +    elseif (EXISTS ${CMAKE_SYSROOT}/usr/lib/${ARCH}-linux-gnu) +        set(_LIB lib/${ARCH}-linux-gnu) +    elseif (EXISTS /lib64) +        set(_LIB lib64) +    else (DARWIN) +        set(_LIB lib) +    endif (DARWIN) +    install(TARGETS ${PROJECT_NAME} DESTINATION ${_LIB}) +endif (INSTALL) 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_pulseaudio.cpp b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp new file mode 100755 index 0000000000..9417c49d38 --- /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 <glib.h> +#include <glib-object.h> + +#include <pulse/introspect.h> + +#include <pulse/subscribe.h> +} + +SymbolGrabber 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<U32>::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*>((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*>((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*>((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..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/media_plugin_cef.cpp b/indra/media_plugins/cef/media_plugin_cef.cpp index 64fc7e452b..9c205b558c 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"); @@ -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"); @@ -1050,6 +1057,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 +1109,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  };  //////////////////////////////////////////////////////////////////////////////// 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/example/CMakeLists.txt b/indra/media_plugins/example/CMakeLists.txt index 7d3e7f663b..86e982fbb4 100644 --- a/indra/media_plugins/example/CMakeLists.txt +++ b/indra/media_plugins/example/CMakeLists.txt @@ -13,14 +13,6 @@ include(ExamplePlugin)  ### media_plugin_example -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_example_SOURCE_FILES      media_plugin_example.cpp      ) diff --git a/indra/media_plugins/gstreamer010/CMakeLists.txt b/indra/media_plugins/gstreamer010/CMakeLists.txt deleted file mode 100644 index 38fc8201bf..0000000000 --- a/indra/media_plugins/gstreamer010/CMakeLists.txt +++ /dev/null @@ -1,46 +0,0 @@ -# -*- cmake -*- - -project(media_plugin_gstreamer010) - -include(00-Common) -include(LLCommon) -include(LLImage) -include(LLMath) -include(LLWindow) -include(Linking) -include(PluginAPI) -include(OpenGL) - -include(GStreamer010Plugin) - -### media_plugin_gstreamer010 - -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_gstreamer010_SOURCE_FILES -    media_plugin_gstreamer010.cpp -    llmediaimplgstreamer_syms.cpp -    llmediaimplgstreamervidplug.cpp -    ) - -set(media_plugin_gstreamer010_HEADER_FILES -    llmediaimplgstreamervidplug.h -    llmediaimplgstreamer_syms.h -    llmediaimplgstreamertriviallogging.h -    ) - -add_library(media_plugin_gstreamer010 -    SHARED -    ${media_plugin_gstreamer010_SOURCE_FILES} -    ) - -target_link_libraries(media_plugin_gstreamer010 -        media_plugin_base -        ll::gstreamer -  ) diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms.cpp b/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms.cpp deleted file mode 100644 index dcc04b37e4..0000000000 --- a/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms.cpp +++ /dev/null @@ -1,167 +0,0 @@ -/** - * @file llmediaimplgstreamer_syms.cpp - * @brief dynamic GStreamer symbol-grabbing code - * - * @cond - * $LicenseInfo:firstyear=2007&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 - */ - -#if LL_GSTREAMER010_ENABLED - -#include <string> - -extern "C" { -#include <gst/gst.h> - -#include "apr_pools.h" -#include "apr_dso.h" -} - -#include "llmediaimplgstreamertriviallogging.h" - -#define LL_GST_SYM(REQ, GSTSYM, RTN, ...) RTN (*ll##GSTSYM)(__VA_ARGS__) = NULL -#include "llmediaimplgstreamer_syms_raw.inc" -#include "llmediaimplgstreamer_syms_rawv.inc" -#undef LL_GST_SYM - -// a couple of stubs for disgusting reasons -GstDebugCategory* -ll_gst_debug_category_new(gchar *name, guint color, gchar *description) -{ -    static GstDebugCategory dummy; -    return &dummy; -} -void ll_gst_debug_register_funcptr(GstDebugFuncPtr func, gchar* ptrname) -{ -} - -static bool sSymsGrabbed = false; -static apr_pool_t *sSymGSTDSOMemoryPool = NULL; -static apr_dso_handle_t *sSymGSTDSOHandleG = NULL; -static apr_dso_handle_t *sSymGSTDSOHandleV = NULL; - - -bool grab_gst_syms(std::string gst_dso_name, -           std::string gst_dso_name_vid) -{ -    if (sSymsGrabbed) -    { -        // already have grabbed good syms -        return TRUE; -    } - -    bool sym_error = false; -    bool rtn = false; -    apr_status_t rv; -    apr_dso_handle_t *sSymGSTDSOHandle = NULL; - -#define LL_GST_SYM(REQ, GSTSYM, RTN, ...) do{rv = apr_dso_sym((apr_dso_handle_sym_t*)&ll##GSTSYM, sSymGSTDSOHandle, #GSTSYM); if (rv != APR_SUCCESS) {INFOMSG("Failed to grab symbol: %s", #GSTSYM); if (REQ) sym_error = true;} else DEBUGMSG("grabbed symbol: %s from %p", #GSTSYM, (void*)ll##GSTSYM);}while(0) - -    //attempt to load the shared libraries -    apr_pool_create(&sSymGSTDSOMemoryPool, NULL); - -    if ( APR_SUCCESS == (rv = apr_dso_load(&sSymGSTDSOHandle, -                           gst_dso_name.c_str(), -                           sSymGSTDSOMemoryPool) )) -    { -        INFOMSG("Found DSO: %s", gst_dso_name.c_str()); -#include "llmediaimplgstreamer_syms_raw.inc" - -        if ( sSymGSTDSOHandle ) -        { -            sSymGSTDSOHandleG = sSymGSTDSOHandle; -            sSymGSTDSOHandle = NULL; -        } - -        if ( APR_SUCCESS == -             (rv = apr_dso_load(&sSymGSTDSOHandle, -                    gst_dso_name_vid.c_str(), -                    sSymGSTDSOMemoryPool) )) -        { -            INFOMSG("Found DSO: %s", gst_dso_name_vid.c_str()); -#include "llmediaimplgstreamer_syms_rawv.inc" -            rtn = !sym_error; -        } -        else -        { -            INFOMSG("Couldn't load DSO: %s", gst_dso_name_vid.c_str()); -            rtn = false; // failure -        } -    } -    else -    { -        INFOMSG("Couldn't load DSO: %s", gst_dso_name.c_str()); -        rtn = false; // failure -    } - -    if (sym_error) -    { -        WARNMSG("Failed to find necessary symbols in GStreamer libraries."); -    } - -    if ( sSymGSTDSOHandle ) -    { -        sSymGSTDSOHandleV = sSymGSTDSOHandle; -        sSymGSTDSOHandle = NULL; -    } -#undef LL_GST_SYM - -    sSymsGrabbed = !!rtn; -    return rtn; -} - - -void ungrab_gst_syms() -{ -    // should be safe to call regardless of whether we've -    // actually grabbed syms. - -    if ( sSymGSTDSOHandleG ) -    { -        apr_dso_unload(sSymGSTDSOHandleG); -        sSymGSTDSOHandleG = NULL; -    } - -    if ( sSymGSTDSOHandleV ) -    { -        apr_dso_unload(sSymGSTDSOHandleV); -        sSymGSTDSOHandleV = NULL; -    } - -    if ( sSymGSTDSOMemoryPool ) -    { -        apr_pool_destroy(sSymGSTDSOMemoryPool); -        sSymGSTDSOMemoryPool = NULL; -    } - -    // NULL-out all of the symbols we'd grabbed -#define LL_GST_SYM(REQ, GSTSYM, RTN, ...) do{ll##GSTSYM = NULL;}while(0) -#include "llmediaimplgstreamer_syms_raw.inc" -#include "llmediaimplgstreamer_syms_rawv.inc" -#undef LL_GST_SYM - -    sSymsGrabbed = false; -} - - -#endif // LL_GSTREAMER010_ENABLED diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms.h b/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms.h deleted file mode 100644 index 57d446c7df..0000000000 --- a/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms.h +++ /dev/null @@ -1,74 +0,0 @@ -/** - * @file llmediaimplgstreamer_syms.h - * @brief dynamic GStreamer symbol-grabbing code - * - * @cond - * $LicenseInfo:firstyear=2007&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 "linden_common.h" - -#if LL_GSTREAMER010_ENABLED - -extern "C" { -#include <gst/gst.h> -} - -bool grab_gst_syms(std::string gst_dso_name, -           std::string gst_dso_name_vid); -void ungrab_gst_syms(); - -#define LL_GST_SYM(REQ, GSTSYM, RTN, ...) extern RTN (*ll##GSTSYM)(__VA_ARGS__) -#include "llmediaimplgstreamer_syms_raw.inc" -#include "llmediaimplgstreamer_syms_rawv.inc" -#undef LL_GST_SYM - -// regrettable hacks to give us better runtime compatibility with older systems -#define llg_return_if_fail(COND) do{if (!(COND)) return;}while(0) -#define llg_return_val_if_fail(COND,V) do{if (!(COND)) return V;}while(0) - -// regrettable hacks because GStreamer was not designed for runtime loading -#undef GST_TYPE_MESSAGE -#define GST_TYPE_MESSAGE (llgst_message_get_type()) -#undef GST_TYPE_OBJECT -#define GST_TYPE_OBJECT (llgst_object_get_type()) -#undef GST_TYPE_PIPELINE -#define GST_TYPE_PIPELINE (llgst_pipeline_get_type()) -#undef GST_TYPE_ELEMENT -#define GST_TYPE_ELEMENT (llgst_element_get_type()) -#undef GST_TYPE_VIDEO_SINK -#define GST_TYPE_VIDEO_SINK (llgst_video_sink_get_type()) -// more regrettable hacks to stub-out these .h-exposed GStreamer internals -void ll_gst_debug_register_funcptr(GstDebugFuncPtr func, gchar* ptrname); -#undef _gst_debug_register_funcptr -#define _gst_debug_register_funcptr ll_gst_debug_register_funcptr -GstDebugCategory* ll_gst_debug_category_new(gchar *name, guint color, gchar *description); -#undef _gst_debug_category_new -#define _gst_debug_category_new ll_gst_debug_category_new -#undef __gst_debug_enabled -#define __gst_debug_enabled (0) - -// more hacks -#define LLGST_MESSAGE_TYPE_NAME(M) (llgst_message_type_get_name(GST_MESSAGE_TYPE(M))) - -#endif // LL_GSTREAMER010_ENABLED diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms_raw.inc b/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms_raw.inc deleted file mode 100644 index b33e59363d..0000000000 --- a/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms_raw.inc +++ /dev/null @@ -1,51 +0,0 @@ - -// required symbols to grab -LL_GST_SYM(true, gst_pad_peer_accept_caps, gboolean, GstPad *pad, GstCaps *caps); -LL_GST_SYM(true, gst_buffer_new, GstBuffer*, void); -LL_GST_SYM(true, gst_buffer_set_caps, void, GstBuffer*, GstCaps *); -LL_GST_SYM(true, gst_structure_set_value, void, GstStructure *, const gchar *, const GValue*); -LL_GST_SYM(true, gst_init_check, gboolean, int *argc, char **argv[], GError ** err); -LL_GST_SYM(true, gst_message_get_type, GType, void); -LL_GST_SYM(true, gst_message_type_get_name, const gchar*, GstMessageType type); -LL_GST_SYM(true, gst_message_parse_error, void, GstMessage *message, GError **gerror, gchar **debug); -LL_GST_SYM(true, gst_message_parse_warning, void, GstMessage *message, GError **gerror, gchar **debug); -LL_GST_SYM(true, gst_message_parse_state_changed, void, GstMessage *message, GstState *oldstate, GstState *newstate, GstState *pending); -LL_GST_SYM(true, gst_element_set_state, GstStateChangeReturn, GstElement *element, GstState state); -LL_GST_SYM(true, gst_object_unref, void, gpointer object); -LL_GST_SYM(true, gst_object_get_type, GType, void); -LL_GST_SYM(true, gst_pipeline_get_type, GType, void); -LL_GST_SYM(true, gst_pipeline_get_bus, GstBus*, GstPipeline *pipeline); -LL_GST_SYM(true, gst_bus_add_watch, guint, GstBus * bus, GstBusFunc func, gpointer user_data); -LL_GST_SYM(true, gst_element_factory_make, GstElement*, const gchar *factoryname, const gchar *name); -LL_GST_SYM(true, gst_element_get_type, GType, void); -LL_GST_SYM(true, gst_static_pad_template_get, GstPadTemplate*, GstStaticPadTemplate *pad_template); -LL_GST_SYM(true, gst_element_class_add_pad_template, void, GstElementClass *klass, GstPadTemplate *temp); -LL_GST_SYM(true, gst_element_class_set_details, void, GstElementClass *klass, const GstElementDetails *details); -LL_GST_SYM(true, gst_caps_unref, void, GstCaps* caps); -LL_GST_SYM(true, gst_caps_ref, GstCaps *, GstCaps* caps); -//LL_GST_SYM(true, gst_caps_is_empty, gboolean, const GstCaps *caps); -LL_GST_SYM(true, gst_caps_from_string, GstCaps *, const gchar *string); -LL_GST_SYM(true, gst_caps_replace, void, GstCaps **caps, GstCaps *newcaps); -LL_GST_SYM(true, gst_caps_get_structure, GstStructure *, const GstCaps *caps, guint index); -LL_GST_SYM(true, gst_caps_copy, GstCaps *, const GstCaps * caps); -//LL_GST_SYM(true, gst_caps_intersect, GstCaps *, const GstCaps *caps1, const GstCaps *caps2); -LL_GST_SYM(true, gst_element_register, gboolean, GstPlugin *plugin, const gchar *name, guint rank, GType type); -LL_GST_SYM(true, _gst_plugin_register_static, void, GstPluginDesc *desc); -LL_GST_SYM(true, gst_structure_get_int, gboolean, const GstStructure *structure, const gchar *fieldname, gint *value); -LL_GST_SYM(true, gst_structure_get_value, G_CONST_RETURN GValue *, const GstStructure *structure, const gchar *fieldname); -LL_GST_SYM(true, gst_value_get_fraction_numerator, gint, const GValue *value); -LL_GST_SYM(true, gst_value_get_fraction_denominator, gint, const GValue *value); -LL_GST_SYM(true, gst_structure_get_name, G_CONST_RETURN gchar *, const GstStructure *structure); -LL_GST_SYM(true, gst_element_seek, bool, GstElement *, gdouble, GstFormat, GstSeekFlags, GstSeekType, gint64, GstSeekType, gint64); - -// optional symbols to grab -LL_GST_SYM(false, gst_registry_fork_set_enabled, void, gboolean enabled); -LL_GST_SYM(false, gst_segtrap_set_enabled, void, gboolean enabled); -LL_GST_SYM(false, gst_message_parse_buffering, void, GstMessage *message, gint *percent); -LL_GST_SYM(false, gst_message_parse_info, void, GstMessage *message, GError **gerror, gchar **debug); -LL_GST_SYM(false, gst_element_query_position, gboolean, GstElement *element, GstFormat *format, gint64 *cur); -LL_GST_SYM(false, gst_version, void, guint *major, guint *minor, guint *micro, guint *nano); - -// GStreamer 'internal' symbols which may not be visible in some runtimes but are still used in expanded GStreamer header macros - yuck!  We'll substitute our own stubs for these. -//LL_GST_SYM(true, _gst_debug_register_funcptr, void, GstDebugFuncPtr func, gchar* ptrname); -//LL_GST_SYM(true, _gst_debug_category_new, GstDebugCategory *, gchar *name, guint color, gchar *description); diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms_rawv.inc b/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms_rawv.inc deleted file mode 100644 index 14fbcb48b9..0000000000 --- a/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms_rawv.inc +++ /dev/null @@ -1,5 +0,0 @@ - -// required symbols to grab -LL_GST_SYM(true, gst_video_sink_get_type, GType, void); - -// optional symbols to grab diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamertriviallogging.h b/indra/media_plugins/gstreamer010/llmediaimplgstreamertriviallogging.h deleted file mode 100644 index 43ebad6744..0000000000 --- a/indra/media_plugins/gstreamer010/llmediaimplgstreamertriviallogging.h +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @file llmediaimplgstreamertriviallogging.h - * @brief minimal logging utilities. - * - * @cond - * $LicenseInfo:firstyear=2009&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 __LLMEDIAIMPLGSTREAMERTRIVIALLOGGING_H__ -#define __LLMEDIAIMPLGSTREAMERTRIVIALLOGGING_H__ - -#include <cstdio> - -extern "C" { -#include <sys/types.h> -#include <unistd.h> -} - -///////////////////////////////////////////////////////////////////////// -// Debug/Info/Warning macros. -#define MSGMODULEFOO "(media plugin)" -#define STDERRMSG(...) do{\ -    fprintf(stderr, " pid:%d: ", (int)getpid());\ -    fprintf(stderr, MSGMODULEFOO " %s:%d: ", __FUNCTION__, __LINE__);\ -    fprintf(stderr, __VA_ARGS__);\ -    fputc('\n',stderr);\ -  }while(0) -#define NULLMSG(...) do{}while(0) - -#define DEBUGMSG NULLMSG -#define INFOMSG  STDERRMSG -#define WARNMSG  STDERRMSG -///////////////////////////////////////////////////////////////////////// - -#endif /* __LLMEDIAIMPLGSTREAMERTRIVIALLOGGING_H__ */ diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamervidplug.cpp b/indra/media_plugins/gstreamer010/llmediaimplgstreamervidplug.cpp deleted file mode 100644 index acec0f2399..0000000000 --- a/indra/media_plugins/gstreamer010/llmediaimplgstreamervidplug.cpp +++ /dev/null @@ -1,526 +0,0 @@ -/** - * @file llmediaimplgstreamervidplug.h - * @brief Video-consuming static GStreamer plugin for gst-to-LLMediaImpl - * - * @cond - * $LicenseInfo:firstyear=2007&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 - */ - -#if LL_GSTREAMER010_ENABLED - -#include "linden_common.h" - -#include <gst/gst.h> -#include <gst/video/video.h> -#include <gst/video/gstvideosink.h> - -#include "llmediaimplgstreamer_syms.h" -#include "llmediaimplgstreamertriviallogging.h" - -#include "llmediaimplgstreamervidplug.h" - - -GST_DEBUG_CATEGORY_STATIC (gst_slvideo_debug); -#define GST_CAT_DEFAULT gst_slvideo_debug - - -#define SLV_SIZECAPS ", width=(int)[1,2048], height=(int)[1,2048] " -#define SLV_ALLCAPS GST_VIDEO_CAPS_RGBx SLV_SIZECAPS - -static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ( -    (gchar*)"sink", -    GST_PAD_SINK, -    GST_PAD_ALWAYS, -    GST_STATIC_CAPS (SLV_ALLCAPS) -    ); - -GST_BOILERPLATE (GstSLVideo, gst_slvideo, GstVideoSink, -    GST_TYPE_VIDEO_SINK); - -static void gst_slvideo_set_property (GObject * object, guint prop_id, -                      const GValue * value, -                      GParamSpec * pspec); -static void gst_slvideo_get_property (GObject * object, guint prop_id, -                      GValue * value, GParamSpec * pspec); - -static void -gst_slvideo_base_init (gpointer gclass) -{ -    static GstElementDetails element_details = { -        (gchar*)"PluginTemplate", -        (gchar*)"Generic/PluginTemplate", -        (gchar*)"Generic Template Element", -        (gchar*)"Linden Lab" -    }; -    GstElementClass *element_class = GST_ELEMENT_CLASS (gclass); - -    llgst_element_class_add_pad_template (element_class, -                  llgst_static_pad_template_get (&sink_factory)); -    llgst_element_class_set_details (element_class, &element_details); -} - - -static void -gst_slvideo_finalize (GObject * object) -{ -    GstSLVideo *slvideo; -    slvideo = GST_SLVIDEO (object); -    if (slvideo->caps) -    { -        llgst_caps_unref(slvideo->caps); -    } - -    G_OBJECT_CLASS(parent_class)->finalize (object); -} - - -static GstFlowReturn -gst_slvideo_show_frame (GstBaseSink * bsink, GstBuffer * buf) -{ -    GstSLVideo *slvideo; -    llg_return_val_if_fail (buf != NULL, GST_FLOW_ERROR); - -    slvideo = GST_SLVIDEO(bsink); - -    DEBUGMSG("transferring a frame of %dx%d <- %p (%d)", -         slvideo->width, slvideo->height, GST_BUFFER_DATA(buf), -         slvideo->format); - -    if (GST_BUFFER_DATA(buf)) -    { -        // copy frame and frame info into neutral territory -        GST_OBJECT_LOCK(slvideo); -        slvideo->retained_frame_ready = TRUE; -        slvideo->retained_frame_width = slvideo->width; -        slvideo->retained_frame_height = slvideo->height; -        slvideo->retained_frame_format = slvideo->format; -        int rowbytes = -            SLVPixelFormatBytes[slvideo->retained_frame_format] * -            slvideo->retained_frame_width; -        int needbytes = rowbytes * slvideo->retained_frame_width; -        // resize retained frame hunk only if necessary -        if (needbytes != slvideo->retained_frame_allocbytes) -        { -            delete[] slvideo->retained_frame_data; -            slvideo->retained_frame_data = new unsigned char[needbytes]; -            slvideo->retained_frame_allocbytes = needbytes; - -        } -        // copy the actual frame data to neutral territory - -        // flipped, for GL reasons -        for (int ypos=0; ypos<slvideo->height; ++ypos) -        { -            memcpy(&slvideo->retained_frame_data[(slvideo->height-1-ypos)*rowbytes], -                   &(((unsigned char*)GST_BUFFER_DATA(buf))[ypos*rowbytes]), -                   rowbytes); -        } -        // done with the shared data -        GST_OBJECT_UNLOCK(slvideo); -    } - -    return GST_FLOW_OK; -} - - -static GstStateChangeReturn -gst_slvideo_change_state(GstElement * element, GstStateChange transition) -{ -    GstSLVideo *slvideo; -    GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; - -    slvideo = GST_SLVIDEO (element); - -    switch (transition) { -    case GST_STATE_CHANGE_NULL_TO_READY: -        break; -    case GST_STATE_CHANGE_READY_TO_PAUSED: -        break; -    case GST_STATE_CHANGE_PAUSED_TO_PLAYING: -        break; -    default: -        break; -    } - -    ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); -    if (ret == GST_STATE_CHANGE_FAILURE) -        return ret; - -    switch (transition) { -    case GST_STATE_CHANGE_PLAYING_TO_PAUSED: -        break; -    case GST_STATE_CHANGE_PAUSED_TO_READY: -        slvideo->fps_n = 0; -        slvideo->fps_d = 1; -        GST_VIDEO_SINK_WIDTH(slvideo) = 0; -        GST_VIDEO_SINK_HEIGHT(slvideo) = 0; -        break; -    case GST_STATE_CHANGE_READY_TO_NULL: -        break; -    default: -        break; -    } - -    return ret; -} - - -static GstCaps * -gst_slvideo_get_caps (GstBaseSink * bsink) -{ -    GstSLVideo *slvideo; -    slvideo = GST_SLVIDEO(bsink); - -    return llgst_caps_ref (slvideo->caps); -} - - -/* this function handles the link with other elements */ -static gboolean -gst_slvideo_set_caps (GstBaseSink * bsink, GstCaps * caps) -{ -    GstSLVideo *filter; -    GstStructure *structure; - -    GST_DEBUG ("set caps with %" GST_PTR_FORMAT, caps); - -    filter = GST_SLVIDEO(bsink); - -    int width, height; -    gboolean ret; -    const GValue *fps; -    const GValue *par; -    structure = llgst_caps_get_structure (caps, 0); -    ret = llgst_structure_get_int (structure, "width", &width); -    ret = ret && llgst_structure_get_int (structure, "height", &height); -    fps = llgst_structure_get_value (structure, "framerate"); -    ret = ret && (fps != NULL); -    par = llgst_structure_get_value (structure, "pixel-aspect-ratio"); -    if (!ret) -        return FALSE; - -    INFOMSG("** filter caps set with width=%d, height=%d", width, height); - -    GST_OBJECT_LOCK(filter); - -    filter->width = width; -    filter->height = height; - -    filter->fps_n = llgst_value_get_fraction_numerator(fps); -    filter->fps_d = llgst_value_get_fraction_denominator(fps); -    if (par) -    { -        filter->par_n = llgst_value_get_fraction_numerator(par); -        filter->par_d = llgst_value_get_fraction_denominator(par); -    } -    else -    { -        filter->par_n = 1; -        filter->par_d = 1; -    } -    GST_VIDEO_SINK_WIDTH(filter) = width; -    GST_VIDEO_SINK_HEIGHT(filter) = height; - -    // crufty lump - we *always* accept *only* RGBX now. -    /* -    filter->format = SLV_PF_UNKNOWN; -    if (0 == strcmp(llgst_structure_get_name(structure), -            "video/x-raw-rgb")) -    { -        int red_mask; -        int green_mask; -        int blue_mask; -        llgst_structure_get_int(structure, "red_mask", &red_mask); -        llgst_structure_get_int(structure, "green_mask", &green_mask); -        llgst_structure_get_int(structure, "blue_mask", &blue_mask); -        if ((unsigned int)red_mask   == 0xFF000000 && -            (unsigned int)green_mask == 0x00FF0000 && -            (unsigned int)blue_mask  == 0x0000FF00) -        { -            filter->format = SLV_PF_RGBX; -            //fprintf(stderr, "\n\nPIXEL FORMAT RGB\n\n"); -        } else if ((unsigned int)red_mask   == 0x0000FF00 && -               (unsigned int)green_mask == 0x00FF0000 && -               (unsigned int)blue_mask  == 0xFF000000) -        { -            filter->format = SLV_PF_BGRX; -            //fprintf(stderr, "\n\nPIXEL FORMAT BGR\n\n"); -        } -        }*/ - -    filter->format = SLV_PF_RGBX; - -    GST_OBJECT_UNLOCK(filter); - -    return TRUE; -} - - -static gboolean -gst_slvideo_start (GstBaseSink * bsink) -{ -    gboolean ret = TRUE; - -    GST_SLVIDEO(bsink); - -    return ret; -} - -static gboolean -gst_slvideo_stop (GstBaseSink * bsink) -{ -    GstSLVideo *slvideo; -    slvideo = GST_SLVIDEO(bsink); - -    // free-up retained frame buffer -    GST_OBJECT_LOCK(slvideo); -    slvideo->retained_frame_ready = FALSE; -    delete[] slvideo->retained_frame_data; -    slvideo->retained_frame_data = NULL; -    slvideo->retained_frame_allocbytes = 0; -    GST_OBJECT_UNLOCK(slvideo); - -    return TRUE; -} - - -static GstFlowReturn -gst_slvideo_buffer_alloc (GstBaseSink * bsink, guint64 offset, guint size, -              GstCaps * caps, GstBuffer ** buf) -{ -    gint width, height; -    GstStructure *structure = NULL; -    GstSLVideo *slvideo; -    slvideo = GST_SLVIDEO(bsink); - -    // caps == requested caps -    // we can ignore these and reverse-negotiate our preferred dimensions with -    // the peer if we like - we need to do this to obey dynamic resize requests -    // flowing in from the app. -    structure = llgst_caps_get_structure (caps, 0); -    if (!llgst_structure_get_int(structure, "width", &width) || -        !llgst_structure_get_int(structure, "height", &height)) -    { -        GST_WARNING_OBJECT (slvideo, "no width/height in caps %" GST_PTR_FORMAT, caps); -        return GST_FLOW_NOT_NEGOTIATED; -    } - -    GstBuffer *newbuf = llgst_buffer_new(); -    bool made_bufferdata_ptr = false; -#define MAXDEPTHHACK 4 - -    GST_OBJECT_LOCK(slvideo); -    if (slvideo->resize_forced_always) // app is giving us a fixed size to work with -    { -        gint slwantwidth, slwantheight; -        slwantwidth = slvideo->resize_try_width; -        slwantheight = slvideo->resize_try_height; - -        if (slwantwidth != width || -            slwantheight != height) -        { -            // don't like requested caps, we will issue our own suggestion - copy -            // the requested caps but substitute our own width and height and see -            // if our peer is happy with that. - -            GstCaps *desired_caps; -            GstStructure *desired_struct; -            desired_caps = llgst_caps_copy (caps); -            desired_struct = llgst_caps_get_structure (desired_caps, 0); - -            GValue value = {0}; -            g_value_init(&value, G_TYPE_INT); -            g_value_set_int(&value, slwantwidth); -            llgst_structure_set_value (desired_struct, "width", &value); -            g_value_unset(&value); -            g_value_init(&value, G_TYPE_INT); -            g_value_set_int(&value, slwantheight); -            llgst_structure_set_value (desired_struct, "height", &value); - -            if (llgst_pad_peer_accept_caps (GST_VIDEO_SINK_PAD (slvideo), -                            desired_caps)) -            { -                // todo: re-use buffers from a pool? -                // todo: set MALLOCDATA to null, set DATA to point straight to shm? - -                // peer likes our cap suggestion -                DEBUGMSG("peer loves us :)"); -                GST_BUFFER_SIZE(newbuf) = slwantwidth * slwantheight * MAXDEPTHHACK; -                GST_BUFFER_MALLOCDATA(newbuf) = (guint8*)g_malloc(GST_BUFFER_SIZE(newbuf)); -                GST_BUFFER_DATA(newbuf) = GST_BUFFER_MALLOCDATA(newbuf); -                llgst_buffer_set_caps (GST_BUFFER_CAST(newbuf), desired_caps); - -                made_bufferdata_ptr = true; -            } else { -                // peer hates our cap suggestion -                INFOMSG("peer hates us :("); -                llgst_caps_unref(desired_caps); -            } -        } -    } - -    GST_OBJECT_UNLOCK(slvideo); - -    if (!made_bufferdata_ptr) // need to fallback to malloc at original size -    { -        GST_BUFFER_SIZE(newbuf) = width * height * MAXDEPTHHACK; -        GST_BUFFER_MALLOCDATA(newbuf) = (guint8*)g_malloc(GST_BUFFER_SIZE(newbuf)); -        GST_BUFFER_DATA(newbuf) = GST_BUFFER_MALLOCDATA(newbuf); -        llgst_buffer_set_caps (GST_BUFFER_CAST(newbuf), caps); -    } - -    *buf = GST_BUFFER_CAST(newbuf); - -    return GST_FLOW_OK; -} - - -/* initialize the plugin's class */ -static void -gst_slvideo_class_init (GstSLVideoClass * klass) -{ -    GObjectClass *gobject_class; -    GstElementClass *gstelement_class; -    GstBaseSinkClass *gstbasesink_class; - -    gobject_class = (GObjectClass *) klass; -    gstelement_class = (GstElementClass *) klass; -    gstbasesink_class = (GstBaseSinkClass *) klass; - -    gobject_class->finalize = gst_slvideo_finalize; -    gobject_class->set_property = gst_slvideo_set_property; -    gobject_class->get_property = gst_slvideo_get_property; - -    gstelement_class->change_state = gst_slvideo_change_state; - -#define LLGST_DEBUG_FUNCPTR(p) (p) -    gstbasesink_class->get_caps = LLGST_DEBUG_FUNCPTR (gst_slvideo_get_caps); -    gstbasesink_class->set_caps = LLGST_DEBUG_FUNCPTR( gst_slvideo_set_caps); -    gstbasesink_class->buffer_alloc=LLGST_DEBUG_FUNCPTR(gst_slvideo_buffer_alloc); -    //gstbasesink_class->get_times = LLGST_DEBUG_FUNCPTR (gst_slvideo_get_times); -    gstbasesink_class->preroll = LLGST_DEBUG_FUNCPTR (gst_slvideo_show_frame); -    gstbasesink_class->render = LLGST_DEBUG_FUNCPTR (gst_slvideo_show_frame); - -    gstbasesink_class->start = LLGST_DEBUG_FUNCPTR (gst_slvideo_start); -    gstbasesink_class->stop = LLGST_DEBUG_FUNCPTR (gst_slvideo_stop); - -    //  gstbasesink_class->unlock = LLGST_DEBUG_FUNCPTR (gst_slvideo_unlock); -#undef LLGST_DEBUG_FUNCPTR -} - - -/* initialize the new element - * instantiate pads and add them to element - * set functions - * initialize structure - */ -static void -gst_slvideo_init (GstSLVideo * filter, -          GstSLVideoClass * gclass) -{ -    filter->caps = NULL; -    filter->width = -1; -    filter->height = -1; - -    // this is the info we share with the client app -    GST_OBJECT_LOCK(filter); -    filter->retained_frame_ready = FALSE; -    filter->retained_frame_data = NULL; -    filter->retained_frame_allocbytes = 0; -    filter->retained_frame_width = filter->width; -    filter->retained_frame_height = filter->height; -    filter->retained_frame_format = SLV_PF_UNKNOWN; -    GstCaps *caps = llgst_caps_from_string (SLV_ALLCAPS); -    llgst_caps_replace (&filter->caps, caps); -    filter->resize_forced_always = false; -    filter->resize_try_width = -1; -    filter->resize_try_height = -1; -    GST_OBJECT_UNLOCK(filter); -} - -static void -gst_slvideo_set_property (GObject * object, guint prop_id, -              const GValue * value, GParamSpec * pspec) -{ -    llg_return_if_fail (GST_IS_SLVIDEO (object)); - -    switch (prop_id) { -    default: -        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); -        break; -    } -} - -static void -gst_slvideo_get_property (GObject * object, guint prop_id, -              GValue * value, GParamSpec * pspec) -{ -    llg_return_if_fail (GST_IS_SLVIDEO (object)); - -    switch (prop_id) { -    default: -        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); -        break; -    } -} - - -/* entry point to initialize the plug-in - * initialize the plug-in itself - * register the element factories and pad templates - * register the features - */ -static gboolean -plugin_init (GstPlugin * plugin) -{ -    DEBUGMSG("PLUGIN INIT"); - -    GST_DEBUG_CATEGORY_INIT (gst_slvideo_debug, (gchar*)"private-slvideo-plugin", -                 0, (gchar*)"Second Life Video Sink"); - -    return llgst_element_register (plugin, "private-slvideo", -                       GST_RANK_NONE, GST_TYPE_SLVIDEO); -} - -/* this is the structure that gstreamer looks for to register plugins - */ -/* NOTE: Can't rely upon GST_PLUGIN_DEFINE_STATIC to self-register, since -   some g++ versions buggily avoid __attribute__((constructor)) functions - -   so we provide an explicit plugin init function. - */ -#define PACKAGE (gchar*)"packagehack" -// this macro quietly refers to PACKAGE internally -GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, -           GST_VERSION_MINOR, -           (gchar*)"private-slvideoplugin", -           (gchar*)"SL Video sink plugin", -           plugin_init, (gchar*)"1.0", (gchar*)"LGPL", -           (gchar*)"Second Life", -           (gchar*)"http://www.secondlife.com/"); -#undef PACKAGE -void gst_slvideo_init_class (void) -{ -    ll_gst_plugin_register_static (&gst_plugin_desc); -    DEBUGMSG("CLASS INIT"); -} - -#endif // LL_GSTREAMER010_ENABLED diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamervidplug.h b/indra/media_plugins/gstreamer010/llmediaimplgstreamervidplug.h deleted file mode 100644 index d4e07daf4f..0000000000 --- a/indra/media_plugins/gstreamer010/llmediaimplgstreamervidplug.h +++ /dev/null @@ -1,105 +0,0 @@ -/** - * @file llmediaimplgstreamervidplug.h - * @brief Video-consuming static GStreamer plugin for gst-to-LLMediaImpl - * - * @cond - * $LicenseInfo:firstyear=2007&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 __GST_SLVIDEO_H__ -#define __GST_SLVIDEO_H__ - -#if LL_GSTREAMER010_ENABLED - -extern "C" { -#include <gst/gst.h> -#include <gst/video/video.h> -#include <gst/video/gstvideosink.h> -} - -G_BEGIN_DECLS - -/* #defines don't like whitespacey bits */ -#define GST_TYPE_SLVIDEO \ -  (gst_slvideo_get_type()) -#define GST_SLVIDEO(obj) \ -  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SLVIDEO,GstSLVideo)) -#define GST_SLVIDEO_CLASS(klass) \ -  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SLVIDEO,GstSLVideoClass)) -#define GST_IS_SLVIDEO(obj) \ -  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SLVIDEO)) -#define GST_IS_SLVIDEO_CLASS(klass) \ -  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SLVIDEO)) - -typedef struct _GstSLVideo      GstSLVideo; -typedef struct _GstSLVideoClass GstSLVideoClass; - -typedef enum { -    SLV_PF_UNKNOWN = 0, -    SLV_PF_RGBX    = 1, -    SLV_PF_BGRX    = 2, -    SLV__END       = 3 -} SLVPixelFormat; -const int SLVPixelFormatBytes[SLV__END] = {1, 4, 4}; - -struct _GstSLVideo -{ -    GstVideoSink video_sink; - -    GstCaps *caps; - -    int fps_n, fps_d; -    int par_n, par_d; -    int height, width; -    SLVPixelFormat format; - -    // SHARED WITH APPLICATION: -    // Access to the following should be protected by GST_OBJECT_LOCK() on -    // the GstSLVideo object, and should be totally consistent upon UNLOCK -    // (i.e. all written at once to reflect the current retained frame info -    // when the retained frame is updated.) -    bool retained_frame_ready; // new frame ready since flag last reset. (*TODO: could get the writer to wait on a semaphore instead of having the reader poll, potentially making dropped frames somewhat cheaper.) -    unsigned char*  retained_frame_data; -    int retained_frame_allocbytes; -    int retained_frame_width, retained_frame_height; -    SLVPixelFormat retained_frame_format; -    // sticky resize info -    bool resize_forced_always; -    int resize_try_width; -    int resize_try_height; -}; - -struct _GstSLVideoClass -{ -    GstVideoSinkClass parent_class; -}; - -GType gst_slvideo_get_type (void); - -void gst_slvideo_init_class (void); - -G_END_DECLS - -#endif // LL_GSTREAMER010_ENABLED - -#endif /* __GST_SLVIDEO_H__ */ diff --git a/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp b/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp deleted file mode 100644 index 97d1d7d7b5..0000000000 --- a/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp +++ /dev/null @@ -1,1266 +0,0 @@ -/** - * @file media_plugin_gstreamer010.cpp - * @brief GStreamer-0.10 plugin for LLMedia API plugin system - * - * @cond - * $LicenseInfo:firstyear=2007&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 "linden_common.h" - -#include "llgl.h" - -#include "llplugininstance.h" -#include "llpluginmessage.h" -#include "llpluginmessageclasses.h" -#include "media_plugin_base.h" - -#if LL_GSTREAMER010_ENABLED - -extern "C" { -#include <gst/gst.h> -} - -#include "llmediaimplgstreamer.h" -#include "llmediaimplgstreamertriviallogging.h" - -#include "llmediaimplgstreamervidplug.h" - -#include "llmediaimplgstreamer_syms.h" - -////////////////////////////////////////////////////////////////////////////// -// -class MediaPluginGStreamer010 : public MediaPluginBase -{ -public: -    MediaPluginGStreamer010(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data); -    ~MediaPluginGStreamer010(); - -    /* virtual */ void receiveMessage(const char *message_string); - -    static bool startup(); -    static bool closedown(); - -    gboolean processGSTEvents(GstBus     *bus, -                  GstMessage *message); - -private: -    std::string getVersion(); -    bool navigateTo( const std::string urlIn ); -    bool seek( double time_sec ); -    bool setVolume( float volume ); - -    // misc -    bool pause(); -    bool stop(); -    bool play(double rate); -    bool getTimePos(double &sec_out); - -    static const double MIN_LOOP_SEC = 1.0F; - -    bool mIsLooping; - -    enum ECommand { -        COMMAND_NONE, -        COMMAND_STOP, -        COMMAND_PLAY, -        COMMAND_FAST_FORWARD, -        COMMAND_FAST_REWIND, -        COMMAND_PAUSE, -        COMMAND_SEEK, -    }; -    ECommand mCommand; - -private: -    bool unload(); -    bool load(); - -    bool update(int milliseconds); -        void mouseDown( int x, int y ); -        void mouseUp( int x, int y ); -        void mouseMove( int x, int y ); - -        void sizeChanged(); - -    static bool mDoneInit; - -    guint mBusWatchID; - -    float mVolume; - -    int mDepth; - -    // media NATURAL size -    int mNaturalWidth; -    int mNaturalHeight; -    // media current size -    int mCurrentWidth; -    int mCurrentHeight; -    int mCurrentRowbytes; -      // previous media size so we can detect changes -      int mPreviousWidth; -      int mPreviousHeight; -    // desired render size from host -    int mWidth; -    int mHeight; -    // padded texture size we need to write into -    int mTextureWidth; -    int mTextureHeight; - -    int mTextureFormatPrimary; -    int mTextureFormatType; - -    bool mSeekWanted; -    double mSeekDestination; - -    // Very GStreamer-specific -    GMainLoop *mPump; // event pump for this media -    GstElement *mPlaybin; -    GstElement *mVisualizer; -    GstSLVideo *mVideoSink; -}; - -//static -bool MediaPluginGStreamer010::mDoneInit = false; - -MediaPluginGStreamer010::MediaPluginGStreamer010( -    LLPluginInstance::sendMessageFunction host_send_func, -    void *host_user_data ) : -    MediaPluginBase(host_send_func, host_user_data), -    mBusWatchID ( 0 ), -    mCurrentRowbytes ( 4 ), -    mTextureFormatPrimary ( GL_RGBA ), -    mTextureFormatType ( GL_UNSIGNED_INT_8_8_8_8_REV ), -    mSeekWanted(false), -    mSeekDestination(0.0), -    mPump ( NULL ), -    mPlaybin ( NULL ), -    mVisualizer ( NULL ), -    mVideoSink ( NULL ), -    mCommand ( COMMAND_NONE ) -{ -    std::ostringstream str; -    INFOMSG("MediaPluginGStreamer010 constructor - my PID=%u", U32(getpid())); -} - -/////////////////////////////////////////////////////////////////////////////// -// -//#define LL_GST_REPORT_STATE_CHANGES -#ifdef LL_GST_REPORT_STATE_CHANGES -static char* get_gst_state_name(GstState state) -{ -    switch (state) { -    case GST_STATE_VOID_PENDING: return "VOID_PENDING"; -    case GST_STATE_NULL: return "NULL"; -    case GST_STATE_READY: return "READY"; -    case GST_STATE_PAUSED: return "PAUSED"; -    case GST_STATE_PLAYING: return "PLAYING"; -    } -    return "(unknown)"; -} -#endif // LL_GST_REPORT_STATE_CHANGES - -gboolean -MediaPluginGStreamer010::processGSTEvents(GstBus     *bus, -                      GstMessage *message) -{ -    if (!message) -        return TRUE; // shield against GStreamer bug - -    if (GST_MESSAGE_TYPE(message) != GST_MESSAGE_STATE_CHANGED && -        GST_MESSAGE_TYPE(message) != GST_MESSAGE_BUFFERING) -    { -        DEBUGMSG("Got GST message type: %s", -            LLGST_MESSAGE_TYPE_NAME (message)); -    } -    else -    { -        // TODO: grok 'duration' message type -        DEBUGMSG("Got GST message type: %s", -             LLGST_MESSAGE_TYPE_NAME (message)); -    } - -    switch (GST_MESSAGE_TYPE (message)) { -    case GST_MESSAGE_BUFFERING: { -        // NEEDS GST 0.10.11+ -        if (llgst_message_parse_buffering) -        { -            gint percent = 0; -            llgst_message_parse_buffering(message, &percent); -            DEBUGMSG("GST buffering: %d%%", percent); -        } -        break; -    } -    case GST_MESSAGE_STATE_CHANGED: { -        GstState old_state; -        GstState new_state; -        GstState pending_state; -        llgst_message_parse_state_changed(message, -                        &old_state, -                        &new_state, -                        &pending_state); -#ifdef LL_GST_REPORT_STATE_CHANGES -        // not generally very useful, and rather spammy. -        DEBUGMSG("state change (old,<new>,pending): %s,<%s>,%s", -             get_gst_state_name(old_state), -             get_gst_state_name(new_state), -             get_gst_state_name(pending_state)); -#endif // LL_GST_REPORT_STATE_CHANGES - -        switch (new_state) { -        case GST_STATE_VOID_PENDING: -            break; -        case GST_STATE_NULL: -            break; -        case GST_STATE_READY: -            setStatus(STATUS_LOADED); -            break; -        case GST_STATE_PAUSED: -            setStatus(STATUS_PAUSED); -            break; -        case GST_STATE_PLAYING: -            setStatus(STATUS_PLAYING); -            break; -        } -        break; -    } -    case GST_MESSAGE_ERROR: { -        GError *err = NULL; -        gchar *debug = NULL; - -        llgst_message_parse_error (message, &err, &debug); -        WARNMSG("GST error: %s", err?err->message:"(unknown)"); -        if (err) -            g_error_free (err); -        g_free (debug); - -        mCommand = COMMAND_STOP; - -        setStatus(STATUS_ERROR); - -        break; -    } -    case GST_MESSAGE_INFO: { -        if (llgst_message_parse_info) -        { -            GError *err = NULL; -            gchar *debug = NULL; - -            llgst_message_parse_info (message, &err, &debug); -            INFOMSG("GST info: %s", err?err->message:"(unknown)"); -            if (err) -                g_error_free (err); -            g_free (debug); -        } -        break; -    } -    case GST_MESSAGE_WARNING: { -        GError *err = NULL; -        gchar *debug = NULL; - -        llgst_message_parse_warning (message, &err, &debug); -        WARNMSG("GST warning: %s", err?err->message:"(unknown)"); -        if (err) -            g_error_free (err); -        g_free (debug); - -        break; -    } -    case GST_MESSAGE_EOS: -        /* end-of-stream */ -        DEBUGMSG("GST end-of-stream."); -        if (mIsLooping) -        { -            DEBUGMSG("looping media..."); -            double eos_pos_sec = 0.0F; -            bool got_eos_position = getTimePos(eos_pos_sec); - -            if (got_eos_position && eos_pos_sec < MIN_LOOP_SEC) -            { -                // if we know that the movie is really short, don't -                // loop it else it can easily become a time-hog -                // because of GStreamer spin-up overhead -                DEBUGMSG("really short movie (%0.3fsec) - not gonna loop this, pausing instead.", eos_pos_sec); -                // inject a COMMAND_PAUSE -                mCommand = COMMAND_PAUSE; -            } -            else -            { -#undef LLGST_LOOP_BY_SEEKING -// loop with a stop-start instead of a seek, because it actually seems rather -// faster than seeking on remote streams. -#ifdef LLGST_LOOP_BY_SEEKING -                // first, try looping by an explicit rewind -                bool seeksuccess = seek(0.0); -                if (seeksuccess) -                { -                    play(1.0); -                } -                else -#endif // LLGST_LOOP_BY_SEEKING -                {  // use clumsy stop-start to loop -                    DEBUGMSG("didn't loop by rewinding - stopping and starting instead..."); -                    stop(); -                    play(1.0); -                } -            } -        } -        else // not a looping media -        { -            // inject a COMMAND_STOP -            mCommand = COMMAND_STOP; -        } -        break; -    default: -        /* unhandled message */ -        break; -    } - -    /* we want to be notified again the next time there is a message -     * on the bus, so return true (false means we want to stop watching -     * for messages on the bus and our callback should not be called again) -     */ -    return TRUE; -} - -extern "C" { -gboolean -llmediaimplgstreamer_bus_callback (GstBus     *bus, -                   GstMessage *message, -                   gpointer    data) -{ -    MediaPluginGStreamer010 *impl = (MediaPluginGStreamer010*)data; -    return impl->processGSTEvents(bus, message); -} -} // extern "C" - - - -bool -MediaPluginGStreamer010::navigateTo ( const std::string urlIn ) -{ -    if (!mDoneInit) -        return false; // error - -    setStatus(STATUS_LOADING); - -    DEBUGMSG("Setting media URI: %s", urlIn.c_str()); - -    mSeekWanted = false; - -    if (NULL == mPump || -        NULL == mPlaybin) -    { -        setStatus(STATUS_ERROR); -        return false; // error -    } - -    // set URI -    g_object_set (G_OBJECT (mPlaybin), "uri", urlIn.c_str(), NULL); -    //g_object_set (G_OBJECT (mPlaybin), "uri", "file:///tmp/movie", NULL); - -    // navigateTo implicitly plays, too. -    play(1.0); - -    return true; -} - - -bool -MediaPluginGStreamer010::update(int milliseconds) -{ -    if (!mDoneInit) -        return false; // error - -    DEBUGMSG("updating media..."); - -    // sanity check -    if (NULL == mPump || -        NULL == mPlaybin) -    { -        DEBUGMSG("dead media..."); -        return false; -    } - -    // see if there's an outstanding seek wanted -    if (mSeekWanted && -        // bleh, GST has to be happy that the movie is really truly playing -        // or it may quietly ignore the seek (with rtsp:// at least). -        (GST_STATE(mPlaybin) == GST_STATE_PLAYING)) -    { -        seek(mSeekDestination); -        mSeekWanted = false; -    } - -    // *TODO: time-limit - but there isn't a lot we can do here, most -    // time is spent in gstreamer's own opaque worker-threads.  maybe -    // we can do something sneaky like only unlock the video object -    // for 'milliseconds' and otherwise hold the lock. -    while (g_main_context_pending(g_main_loop_get_context(mPump))) -    { -           g_main_context_iteration(g_main_loop_get_context(mPump), FALSE); -    } - -    // check for availability of a new frame - -    if (mVideoSink) -    { -            GST_OBJECT_LOCK(mVideoSink); -        if (mVideoSink->retained_frame_ready) -        { -            DEBUGMSG("NEW FRAME READY"); - -            if (mVideoSink->retained_frame_width != mCurrentWidth || -                mVideoSink->retained_frame_height != mCurrentHeight) -                // *TODO: also check for change in format -            { -                // just resize container, don't consume frame -                int neww = mVideoSink->retained_frame_width; -                int newh = mVideoSink->retained_frame_height; - -                int newd = 4; -                mTextureFormatPrimary = GL_RGBA; -                mTextureFormatType = GL_UNSIGNED_INT_8_8_8_8_REV; - -                /* -                int newd = SLVPixelFormatBytes[mVideoSink->retained_frame_format]; -                if (SLV_PF_BGRX == mVideoSink->retained_frame_format) -                { -                    mTextureFormatPrimary = GL_BGRA; -                    mTextureFormatType = GL_UNSIGNED_INT_8_8_8_8_REV; -                } -                else -                { -                    mTextureFormatPrimary = GL_RGBA; -                    mTextureFormatType = GL_UNSIGNED_INT_8_8_8_8_REV; -                } -                */ - -                GST_OBJECT_UNLOCK(mVideoSink); - -                mCurrentRowbytes = neww * newd; -                DEBUGMSG("video container resized to %dx%d", -                     neww, newh); - -                mDepth = newd; -                mCurrentWidth = neww; -                mCurrentHeight = newh; -                sizeChanged(); -                return true; -            } - -            if (mPixels && -                mCurrentHeight <= mHeight && -                mCurrentWidth <= mWidth && -                !mTextureSegmentName.empty()) -            { -                // we're gonna totally consume this frame - reset 'ready' flag -                mVideoSink->retained_frame_ready = FALSE; -                int destination_rowbytes = mWidth * mDepth; -                for (int row=0; row<mCurrentHeight; ++row) -                { -                    memcpy(&mPixels -                            [destination_rowbytes * row], -                           &mVideoSink->retained_frame_data -                            [mCurrentRowbytes * row], -                           mCurrentRowbytes); -                } - -                GST_OBJECT_UNLOCK(mVideoSink); -                DEBUGMSG("NEW FRAME REALLY TRULY CONSUMED, TELLING HOST"); - -                setDirty(0,0,mCurrentWidth,mCurrentHeight); -            } -            else -            { -                // new frame ready, but we're not ready to -                // consume it. - -                GST_OBJECT_UNLOCK(mVideoSink); - -                DEBUGMSG("NEW FRAME not consumed, still waiting for a shm segment and/or shm resize"); -            } - -            return true; -        } -        else -        { -            // nothing to do yet. -            GST_OBJECT_UNLOCK(mVideoSink); -            return true; -        } -    } - -    return true; -} - - -void -MediaPluginGStreamer010::mouseDown( int x, int y ) -{ -  // do nothing -} - -void -MediaPluginGStreamer010::mouseUp( int x, int y ) -{ -  // do nothing -} - -void -MediaPluginGStreamer010::mouseMove( int x, int y ) -{ -  // do nothing -} - - -bool -MediaPluginGStreamer010::pause() -{ -    DEBUGMSG("pausing media..."); -    // todo: error-check this? -    if (mDoneInit && mPlaybin) -    { -        llgst_element_set_state(mPlaybin, GST_STATE_PAUSED); -        return true; -    } -    return false; -} - -bool -MediaPluginGStreamer010::stop() -{ -    DEBUGMSG("stopping media..."); -    // todo: error-check this? -    if (mDoneInit && mPlaybin) -    { -        llgst_element_set_state(mPlaybin, GST_STATE_READY); -        return true; -    } -    return false; -} - -bool -MediaPluginGStreamer010::play(double rate) -{ -    // NOTE: we don't actually support non-natural rate. - -        DEBUGMSG("playing media... rate=%f", rate); -    // todo: error-check this? -    if (mDoneInit && mPlaybin) -    { -        llgst_element_set_state(mPlaybin, GST_STATE_PLAYING); -        return true; -    } -    return false; -} - -bool -MediaPluginGStreamer010::setVolume( float volume ) -{ -    // we try to only update volume as conservatively as -    // possible, as many gst-plugins-base versions up to at least -    // November 2008 have critical race-conditions in setting volume - sigh -    if (mVolume == volume) -        return true; // nothing to do, everything's fine - -    mVolume = volume; -    if (mDoneInit && mPlaybin) -    { -        g_object_set(mPlaybin, "volume", mVolume, NULL); -        return true; -    } - -    return false; -} - -bool -MediaPluginGStreamer010::seek(double time_sec) -{ -    bool success = false; -    if (mDoneInit && mPlaybin) -    { -        success = llgst_element_seek(mPlaybin, 1.0F, GST_FORMAT_TIME, -                GstSeekFlags(GST_SEEK_FLAG_FLUSH | -                         GST_SEEK_FLAG_KEY_UNIT), -                GST_SEEK_TYPE_SET, gint64(time_sec*GST_SECOND), -                GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); -    } -    DEBUGMSG("MEDIA SEEK REQUEST to %fsec result was %d", -         float(time_sec), int(success)); -    return success; -} - -bool -MediaPluginGStreamer010::getTimePos(double &sec_out) -{ -    bool got_position = false; -    if (mDoneInit && mPlaybin) -    { -        gint64 pos; -        GstFormat timefmt = GST_FORMAT_TIME; -        got_position = -            llgst_element_query_position && -            llgst_element_query_position(mPlaybin, -                             &timefmt, -                             &pos); -        got_position = got_position -            && (timefmt == GST_FORMAT_TIME); -        // GStreamer may have other ideas, but we consider the current position -        // undefined if not PLAYING or PAUSED -        got_position = got_position && -            (GST_STATE(mPlaybin) == GST_STATE_PLAYING || -             GST_STATE(mPlaybin) == GST_STATE_PAUSED); -        if (got_position && !GST_CLOCK_TIME_IS_VALID(pos)) -        { -            if (GST_STATE(mPlaybin) == GST_STATE_PLAYING) -            { -                // if we're playing then we treat an invalid clock time -                // as 0, for complicated reasons (insert reason here) -                pos = 0; -            } -            else -            { -                got_position = false; -            } - -        } -        // If all the preconditions succeeded... we can trust the result. -        if (got_position) -        { -            sec_out = double(pos) / double(GST_SECOND); // gst to sec -        } -    } -    return got_position; -} - -bool -MediaPluginGStreamer010::load() -{ -    if (!mDoneInit) -        return false; // error - -    setStatus(STATUS_LOADING); - -    DEBUGMSG("setting up media..."); - -    mIsLooping = false; -    mVolume = 0.1234567; // minor hack to force an initial volume update - -    // Create a pumpable main-loop for this media -    mPump = g_main_loop_new (NULL, FALSE); -    if (!mPump) -    { -        setStatus(STATUS_ERROR); -        return false; // error -    } - -    // instantiate a playbin element to do the hard work -    mPlaybin = llgst_element_factory_make ("playbin", "play"); -    if (!mPlaybin) -    { -        setStatus(STATUS_ERROR); -        return false; // error -    } - -    // get playbin's bus -    GstBus *bus = llgst_pipeline_get_bus (GST_PIPELINE (mPlaybin)); -    if (!bus) -    { -        setStatus(STATUS_ERROR); -        return false; // error -    } -    mBusWatchID = llgst_bus_add_watch (bus, -                       llmediaimplgstreamer_bus_callback, -                       this); -    llgst_object_unref (bus); - -#if 0 // not quite stable/correct yet -    // get a visualizer element (bonus feature!) -    char* vis_name = getenv("LL_GST_VIS_NAME"); -    if (!vis_name || -        (vis_name && std::string(vis_name)!="none")) -    { -        if (vis_name) -        { -            mVisualizer = llgst_element_factory_make (vis_name, "vis"); -        } -        if (!mVisualizer) -        { -            mVisualizer = llgst_element_factory_make ("libvisual_jess", "vis"); -            if (!mVisualizer) -            { -                mVisualizer = llgst_element_factory_make ("goom", "vis"); -                if (!mVisualizer) -                { -                    mVisualizer = llgst_element_factory_make ("libvisual_lv_scope", "vis"); -                    if (!mVisualizer) -                    { -                        // That's okay, we don't NEED this. -                    } -                } -            } -        } -    } -#endif - -    if (NULL == getenv("LL_GSTREAMER_EXTERNAL")) { -        // instantiate a custom video sink -        mVideoSink = -            GST_SLVIDEO(llgst_element_factory_make ("private-slvideo", "slvideo")); -        if (!mVideoSink) -        { -            WARNMSG("Could not instantiate private-slvideo element."); -            // todo: cleanup. -            setStatus(STATUS_ERROR); -            return false; // error -        } - -        // connect the pieces -        g_object_set(mPlaybin, "video-sink", mVideoSink, NULL); -    } - -    if (mVisualizer) -    { -        g_object_set(mPlaybin, "vis-plugin", mVisualizer, NULL); -    } - -    return true; -} - -bool -MediaPluginGStreamer010::unload () -{ -    if (!mDoneInit) -        return false; // error - -    DEBUGMSG("unloading media..."); - -    // stop getting callbacks for this bus -    g_source_remove(mBusWatchID); -    mBusWatchID = 0; - -    if (mPlaybin) -    { -        llgst_element_set_state (mPlaybin, GST_STATE_NULL); -        llgst_object_unref (GST_OBJECT (mPlaybin)); -        mPlaybin = NULL; -    } - -    if (mVisualizer) -    { -        llgst_object_unref (GST_OBJECT (mVisualizer)); -        mVisualizer = NULL; -    } - -    if (mPump) -    { -        g_main_loop_quit(mPump); -        mPump = NULL; -    } - -    mVideoSink = NULL; - -    setStatus(STATUS_NONE); - -    return true; -} - - -//static -bool -MediaPluginGStreamer010::startup() -{ -    // first - check if GStreamer is explicitly disabled -    if (NULL != getenv("LL_DISABLE_GSTREAMER")) -        return false; - -    // only do global GStreamer initialization once. -    if (!mDoneInit) -    { -        g_thread_init(NULL); - -        // Init the glib type system - we need it. -        g_type_init(); - -        // Get symbols! -#if LL_DARWIN -        if (! grab_gst_syms("libgstreamer-0.10.dylib", -                    "libgstvideo-0.10.dylib") ) -#elseif LL_WINDOWS -        if (! grab_gst_syms("libgstreamer-0.10.dll", -                    "libgstvideo-0.10.dll") ) -#else // linux or other ELFy unixoid -        if (! grab_gst_syms("libgstreamer-0.10.so.0", -                    "libgstvideo-0.10.so.0") ) -#endif -        { -            WARNMSG("Couldn't find suitable GStreamer 0.10 support on this system - video playback disabled."); -            return false; -        } - -        if (llgst_segtrap_set_enabled) -        { -            llgst_segtrap_set_enabled(FALSE); -        } -        else -        { -            WARNMSG("gst_segtrap_set_enabled() is not available; plugin crashes won't be caught."); -        } - -#if LL_LINUX -        // Gstreamer tries a fork during init, waitpid-ing on it, -        // which conflicts with any installed SIGCHLD handler... -        struct sigaction tmpact, oldact; -        if (llgst_registry_fork_set_enabled) { -            // if we can disable SIGCHLD-using forking behaviour, -            // do it. -            llgst_registry_fork_set_enabled(false); -        } -        else { -            // else temporarily install default SIGCHLD handler -            // while GStreamer initialises -            tmpact.sa_handler = SIG_DFL; -            sigemptyset( &tmpact.sa_mask ); -            tmpact.sa_flags = SA_SIGINFO; -            sigaction(SIGCHLD, &tmpact, &oldact); -        } -#endif // LL_LINUX - -        // Protect against GStreamer resetting the locale, yuck. -        static std::string saved_locale; -        saved_locale = setlocale(LC_ALL, NULL); - -        // finally, try to initialize GStreamer! -        GError *err = NULL; -        gboolean init_gst_success = llgst_init_check(NULL, NULL, &err); - -        // restore old locale -        setlocale(LC_ALL, saved_locale.c_str() ); - -#if LL_LINUX -        // restore old SIGCHLD handler -        if (!llgst_registry_fork_set_enabled) -            sigaction(SIGCHLD, &oldact, NULL); -#endif // LL_LINUX - -        if (!init_gst_success) // fail -        { -            if (err) -            { -                WARNMSG("GST init failed: %s", err->message); -                g_error_free(err); -            } -            else -            { -                WARNMSG("GST init failed for unspecified reason."); -            } -            return false; -        } - -        // Init our custom plugins - only really need do this once. -        gst_slvideo_init_class(); - -        mDoneInit = true; -    } - -    return true; -} - - -void -MediaPluginGStreamer010::sizeChanged() -{ -    // the shared writing space has possibly changed size/location/whatever - -    // Check to see whether the movie's NATURAL size has been set yet -    if (1 == mNaturalWidth && -        1 == mNaturalHeight) -    { -        mNaturalWidth = mCurrentWidth; -        mNaturalHeight = mCurrentHeight; -        DEBUGMSG("Media NATURAL size better detected as %dx%d", -             mNaturalWidth, mNaturalHeight); -    } - -    // if the size has changed then the shm has changed and the app needs telling -    if (mCurrentWidth != mPreviousWidth || -        mCurrentHeight != mPreviousHeight) -    { -        mPreviousWidth = mCurrentWidth; -        mPreviousHeight = mCurrentHeight; - -        LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_request"); -        message.setValue("name", mTextureSegmentName); -        message.setValueS32("width", mNaturalWidth); -        message.setValueS32("height", mNaturalHeight); -        DEBUGMSG("<--- Sending size change request to application with name: '%s' - natural size is %d x %d", mTextureSegmentName.c_str(), mNaturalWidth, mNaturalHeight); -        sendMessage(message); -    } -} - - - -//static -bool -MediaPluginGStreamer010::closedown() -{ -    if (!mDoneInit) -        return false; // error - -    ungrab_gst_syms(); - -    mDoneInit = false; - -    return true; -} - -MediaPluginGStreamer010::~MediaPluginGStreamer010() -{ -    DEBUGMSG("MediaPluginGStreamer010 destructor"); - -    closedown(); - -    DEBUGMSG("GStreamer010 closing down"); -} - - -std::string -MediaPluginGStreamer010::getVersion() -{ -    std::string plugin_version = "GStreamer010 media plugin, GStreamer version "; -    if (mDoneInit && -        llgst_version) -    { -        guint major, minor, micro, nano; -        llgst_version(&major, &minor, µ, &nano); -        plugin_version += llformat("%u.%u.%u.%u (runtime), %u.%u.%u.%u (headers)", (unsigned int)major, (unsigned int)minor, (unsigned int)micro, (unsigned int)nano, (unsigned int)GST_VERSION_MAJOR, (unsigned int)GST_VERSION_MINOR, (unsigned int)GST_VERSION_MICRO, (unsigned int)GST_VERSION_NANO); -    } -    else -    { -        plugin_version += "(unknown)"; -    } -    return plugin_version; -} - -void MediaPluginGStreamer010::receiveMessage(const char *message_string) -{ -    //std::cerr << "MediaPluginGStreamer010::receiveMessage: received message: \"" << message_string << "\"" << std::endl; - -    LLPluginMessage message_in; - -    if(message_in.parse(message_string) >= 0) -    { -        std::string message_class = message_in.getClass(); -        std::string message_name = message_in.getName(); -        if(message_class == LLPLUGIN_MESSAGE_CLASS_BASE) -        { -            if(message_name == "init") -            { -                LLPluginMessage message("base", "init_response"); -                LLSD versions = LLSD::emptyMap(); -                versions[LLPLUGIN_MESSAGE_CLASS_BASE] = LLPLUGIN_MESSAGE_CLASS_BASE_VERSION; -                versions[LLPLUGIN_MESSAGE_CLASS_MEDIA] = LLPLUGIN_MESSAGE_CLASS_MEDIA_VERSION; -                versions[LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME] = LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME_VERSION; -                message.setValueLLSD("versions", versions); - -                if ( load() ) -                { -                    DEBUGMSG("GStreamer010 media instance set up"); -                } -                else -                { -                    WARNMSG("GStreamer010 media instance failed to set up"); -                } - -                message.setValue("plugin_version", getVersion()); -                sendMessage(message); -            } -            else if(message_name == "idle") -            { -                // no response is necessary here. -                double time = message_in.getValueReal("time"); - -                // Convert time to milliseconds for update() -                update((int)(time * 1000.0f)); -            } -            else if(message_name == "cleanup") -            { -                unload(); -                closedown(); -            } -            else if(message_name == "shm_added") -            { -                SharedSegmentInfo info; -                info.mAddress = message_in.getValuePointer("address"); -                info.mSize = (size_t)message_in.getValueS32("size"); -                std::string name = message_in.getValue("name"); - -                std::ostringstream str; -                INFOMSG("MediaPluginGStreamer010::receiveMessage: shared memory added, name: %s, size: %d, address: %p", name.c_str(), int(info.mSize), info.mAddress); - -                mSharedSegments.insert(SharedSegmentMap::value_type(name, info)); -            } -            else if(message_name == "shm_remove") -            { -                std::string name = message_in.getValue("name"); - -                DEBUGMSG("MediaPluginGStreamer010::receiveMessage: shared memory remove, name = %s", name.c_str()); - -                SharedSegmentMap::iterator iter = mSharedSegments.find(name); -                if(iter != mSharedSegments.end()) -                { -                    if(mPixels == iter->second.mAddress) -                    { -                        // This is the currently active pixel buffer.  Make sure we stop drawing to it. -                        mPixels = NULL; -                        mTextureSegmentName.clear(); - -                        // Make sure the movie decoder is no longer pointed at the shared segment. -                        sizeChanged(); -                    } -                    mSharedSegments.erase(iter); -                } -                else -                { -                    WARNMSG("MediaPluginGStreamer010::receiveMessage: unknown shared memory region!"); -                } - -                // Send the response so it can be cleaned up. -                LLPluginMessage message("base", "shm_remove_response"); -                message.setValue("name", name); -                sendMessage(message); -            } -            else -            { -                std::ostringstream str; -                INFOMSG("MediaPluginGStreamer010::receiveMessage: unknown base message: %s", message_name.c_str()); -            } -        } -        else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA) -        { -            if(message_name == "init") -            { -                // Plugin gets to decide the texture parameters to use. -                LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "texture_params"); -                // lame to have to decide this now, it depends on the movie.  Oh well. -                mDepth = 4; - -                mCurrentWidth = 1; -                mCurrentHeight = 1; -                mPreviousWidth = 1; -                mPreviousHeight = 1; -                mNaturalWidth = 1; -                mNaturalHeight = 1; -                mWidth = 1; -                mHeight = 1; -                mTextureWidth = 1; -                mTextureHeight = 1; - -                message.setValueU32("format", GL_RGBA); -                message.setValueU32("type", GL_UNSIGNED_INT_8_8_8_8_REV); - -                message.setValueS32("depth", mDepth); -                message.setValueS32("default_width", mWidth); -                message.setValueS32("default_height", mHeight); -                message.setValueU32("internalformat", GL_RGBA8); -                message.setValueBoolean("coords_opengl", true); // true == use OpenGL-style coordinates, false == (0,0) is upper left. -                message.setValueBoolean("allow_downsample", true); // we respond with grace and performance if asked to downscale -                sendMessage(message); -            } -            else if(message_name == "size_change") -            { -                std::string name = message_in.getValue("name"); -                S32 width = message_in.getValueS32("width"); -                S32 height = message_in.getValueS32("height"); -                S32 texture_width = message_in.getValueS32("texture_width"); -                S32 texture_height = message_in.getValueS32("texture_height"); - -                std::ostringstream str; -                INFOMSG("---->Got size change instruction from application with shm name: %s - size is %d x %d", name.c_str(), width, height); - -                LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_response"); -                message.setValue("name", name); -                message.setValueS32("width", width); -                message.setValueS32("height", height); -                message.setValueS32("texture_width", texture_width); -                message.setValueS32("texture_height", texture_height); -                sendMessage(message); - -                if(!name.empty()) -                { -                    // Find the shared memory region with this name -                    SharedSegmentMap::iterator iter = mSharedSegments.find(name); -                    if(iter != mSharedSegments.end()) -                    { -                        INFOMSG("*** Got size change with matching shm, new size is %d x %d", width, height); -                        INFOMSG("*** Got size change with matching shm, texture size size is %d x %d", texture_width, texture_height); - -                        mPixels = (unsigned char*)iter->second.mAddress; -                        mTextureSegmentName = name; -                        mWidth = width; -                        mHeight = height; - -                        if (texture_width > 1 || -                            texture_height > 1) // not a dummy size from the app, a real explicit forced size -                        { -                            INFOMSG("**** = REAL RESIZE REQUEST FROM APP"); - -                            GST_OBJECT_LOCK(mVideoSink); -                            mVideoSink->resize_forced_always = true; -                            mVideoSink->resize_try_width = texture_width; -                            mVideoSink->resize_try_height = texture_height; -                            GST_OBJECT_UNLOCK(mVideoSink); -                        } - -                        mTextureWidth = texture_width; -                        mTextureHeight = texture_height; -                    } -                } -            } -            else if(message_name == "load_uri") -            { -                std::string uri = message_in.getValue("uri"); -                navigateTo( uri ); -                sendStatus(); -            } -            else if(message_name == "mouse_event") -            { -                std::string event = message_in.getValue("event"); -                S32 x = message_in.getValueS32("x"); -                S32 y = message_in.getValueS32("y"); - -                if(event == "down") -                { -                    mouseDown(x, y); -                } -                else if(event == "up") -                { -                    mouseUp(x, y); -                } -                else if(event == "move") -                { -                    mouseMove(x, y); -                }; -            }; -        } -        else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME) -        { -            if(message_name == "stop") -            { -                stop(); -            } -            else if(message_name == "start") -            { -                double rate = 0.0; -                if(message_in.hasValue("rate")) -                { -                    rate = message_in.getValueReal("rate"); -                } -                // NOTE: we don't actually support rate. -                play(rate); -            } -            else if(message_name == "pause") -            { -                pause(); -            } -            else if(message_name == "seek") -            { -                double time = message_in.getValueReal("time"); -                // defer the actual seek in case we haven't -                // really truly started yet in which case there -                // is nothing to seek upon -                mSeekWanted = true; -                mSeekDestination = time; -            } -            else if(message_name == "set_loop") -            { -                bool loop = message_in.getValueBoolean("loop"); -                mIsLooping = loop; -            } -            else if(message_name == "set_volume") -            { -                double volume = message_in.getValueReal("volume"); -                setVolume(volume); -            } -        } -        else -        { -            INFOMSG("MediaPluginGStreamer010::receiveMessage: unknown message class: %s", message_class.c_str()); -        } -    } -} - -int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data) -{ -    if (MediaPluginGStreamer010::startup()) -    { -        MediaPluginGStreamer010 *self = new MediaPluginGStreamer010(host_send_func, host_user_data); -        *plugin_send_func = MediaPluginGStreamer010::staticReceiveMessage; -        *plugin_user_data = (void*)self; - -        return 0; // okay -    } -    else -    { -        return -1; // failed to init -    } -} - -#else // LL_GSTREAMER010_ENABLED - -// Stubbed-out class with constructor/destructor (necessary or windows linker -// will just think its dead code and optimize it all out) -class MediaPluginGStreamer010 : public MediaPluginBase -{ -public: -    MediaPluginGStreamer010(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data); -    ~MediaPluginGStreamer010(); -    /* virtual */ void receiveMessage(const char *message_string); -}; - -MediaPluginGStreamer010::MediaPluginGStreamer010( -    LLPluginInstance::sendMessageFunction host_send_func, -    void *host_user_data ) : -    MediaPluginBase(host_send_func, host_user_data) -{ -    // no-op -} - -MediaPluginGStreamer010::~MediaPluginGStreamer010() -{ -    // no-op -} - -void MediaPluginGStreamer010::receiveMessage(const char *message_string) -{ -    // no-op -} - -// We're building without GStreamer enabled.  Just refuse to initialize. -int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data) -{ -    return -1; -} - -#endif // LL_GSTREAMER010_ENABLED diff --git a/indra/media_plugins/gstreamer10/CMakeLists.txt b/indra/media_plugins/gstreamer10/CMakeLists.txt new file mode 100644 index 0000000000..f9898368cc --- /dev/null +++ b/indra/media_plugins/gstreamer10/CMakeLists.txt @@ -0,0 +1,52 @@ +# -*- cmake -*- + +project(media_plugin_gstreamer) + +include(00-Common) +include(LLCommon) +include(LLImage) +include(LLMath) +include(LLWindow) +include(Linking) +include(PluginAPI) +include(OpenGL) +include(GLIB) + +include(GStreamer10Plugin) + +### media_plugin_gstreamer10 + +set(${PROJECT_NAME}_SOURCE_FILES +    media_plugin_gstreamer10.cpp +    ) + +set(media_plugin_gstreamer10_HEADER_FILES +    llmediaimplgstreamer_syms.h +    llmediaimplgstreamertriviallogging.h +    ) + +add_library(${PROJECT_NAME} +    SHARED +    ${${PROJECT_NAME}_SOURCE_FILES} +) + +target_link_libraries(${PROJECT_NAME} media_plugin_base ll::gstreamer10 ) + +if (WINDOWS) +  set_target_properties( +    media_plugin_gstreamer10 +    PROPERTIES +    LINK_FLAGS "/MANIFEST:NO /SAFESEH:NO /NODEFAULTLIB:LIBCMT" +    ) +endif (WINDOWS) + +if (INSTALL) +	if (EXISTS ${CMAKE_SYSROOT}/usr/lib/${ARCH}-linux-gnu) +		set(_LIB lib/${ARCH}-linux-gnu) +	elseif (EXISTS /lib64) +		set(_LIB lib64) +	else () +		set(_LIB lib) +	endif () +	install(TARGETS ${PROJECT_NAME} DESTINATION ${_LIB}) +endif () diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamer.h b/indra/media_plugins/gstreamer10/llmediaimplgstreamer.h index cae11a5cb3..cae11a5cb3 100644 --- a/indra/media_plugins/gstreamer010/llmediaimplgstreamer.h +++ b/indra/media_plugins/gstreamer10/llmediaimplgstreamer.h diff --git a/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms_raw.inc b/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms_raw.inc new file mode 100644 index 0000000000..6f5bb04bdf --- /dev/null +++ b/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms_raw.inc @@ -0,0 +1,71 @@ +#define G gstSymbolGrabber + +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(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(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(G, true, gst_caps_new_simple, GstCaps*, const char *, const char*, ... ) + +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(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(G, true, g_free, void, gpointer ) +LL_GRAB_SYM(G, true, g_error_free, void, GError* ) + +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 new file mode 100644 index 0000000000..3f636915ea --- /dev/null +++ b/indra/media_plugins/gstreamer10/media_plugin_gstreamer10.cpp @@ -0,0 +1,958 @@ +/**  + * @file media_plugin_gstreamer10.cpp + * @brief GStreamer-1.0 plugin for LLMedia API plugin system + * + * @cond + * $LicenseInfo:firstyear=2016&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2016, Linden Research, Inc. / Nicky Dasmijn + *  + * 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 + */ + +#define FLIP_Y + +#include "linden_common.h" + +#include "llgl.h" + +#include "llplugininstance.h" +#include "llpluginmessage.h" +#include "llpluginmessageclasses.h" +#include "media_plugin_base.h" + +#define G_DISABLE_CAST_CHECKS +extern "C" { +#include <gst/gst.h> +#include <gst/app/gstappsink.h> +} + +SymbolGrabber gstSymbolGrabber; + +#include "llmediaimplgstreamer_syms_raw.inc" + +static inline void llgst_caps_unref( GstCaps * caps ) +{ +    llgst_mini_object_unref( GST_MINI_OBJECT_CAST( caps ) ); +} + +static inline void llgst_sample_unref( GstSample *aSample ) +{ +    llgst_mini_object_unref( GST_MINI_OBJECT_CAST( aSample ) ); +} + +////////////////////////////////////////////////////////////////////////////// +// +class MediaPluginGStreamer10 : public MediaPluginBase +{ +public: +    MediaPluginGStreamer10(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data); +    ~MediaPluginGStreamer10(); + +    /* virtual */ void receiveMessage(const char *message_string); + +    static bool startup(); +    static bool closedown(); + +    gboolean processGSTEvents(GstBus *bus, GstMessage *message); + +private: +    std::string getVersion(); +    bool navigateTo( const std::string urlIn ); +    bool seek( double time_sec ); +    bool setVolume( float volume ); +     +    // misc +    bool pause(); +    bool stop(); +    bool play(double rate); +    bool getTimePos(double &sec_out); + +    double MIN_LOOP_SEC = 1.0F; +    U32 INTERNAL_TEXTURE_SIZE = 1024; +     +    bool mIsLooping; + +    enum ECommand { +        COMMAND_NONE, +        COMMAND_STOP, +        COMMAND_PLAY, +        COMMAND_FAST_FORWARD, +        COMMAND_FAST_REWIND, +        COMMAND_PAUSE, +        COMMAND_SEEK, +    }; +    ECommand mCommand; + +private: +    bool unload(); +    bool load(); + +    bool update(int milliseconds); +    void mouseDown( int x, int y ); +    void mouseUp( int x, int y ); +    void mouseMove( int x, int y ); + +    static bool mDoneInit; +     +    guint mBusWatchID; +     +    float mVolume; + +    int mDepth; + +    // padded texture size we need to write into +    int mTextureWidth; +    int mTextureHeight; +     +    bool mSeekWanted; +    double mSeekDestination; +     +    // Very GStreamer-specific +    GMainLoop *mPump; // event pump for this media +    GstElement *mPlaybin; +    GstAppSink *mAppSink; +}; + +//static +bool MediaPluginGStreamer10::mDoneInit = false; + +MediaPluginGStreamer10::MediaPluginGStreamer10( LLPluginInstance::sendMessageFunction host_send_func, +                                                void *host_user_data ) +    : MediaPluginBase(host_send_func, host_user_data) +    , mBusWatchID ( 0 ) +    , mSeekWanted(false) +    , mSeekDestination(0.0) +    , mPump ( nullptr ) +    , mPlaybin ( nullptr ) +    , mAppSink ( nullptr ) +    , mCommand ( COMMAND_NONE ) +{ +} + +gboolean MediaPluginGStreamer10::processGSTEvents(GstBus *bus, GstMessage *message) +{ +    if (!message)  +        return TRUE; // shield against GStreamer bug + +    switch (GST_MESSAGE_TYPE (message)) +    { +        case GST_MESSAGE_BUFFERING: +        { +            // NEEDS GST 0.10.11+ +            if (llgst_message_parse_buffering) +            { +                gint percent = 0; +                llgst_message_parse_buffering(message, &percent); +            } +            break; +        } +        case GST_MESSAGE_STATE_CHANGED: +        { +            GstState old_state; +            GstState new_state; +            GstState pending_state; +            llgst_message_parse_state_changed(message, +                                              &old_state, +                                              &new_state, +                                              &pending_state); + +            switch (new_state) +            { +                case GST_STATE_VOID_PENDING: +                    break; +                case GST_STATE_NULL: +                    break; +                case GST_STATE_READY: +                    setStatus(STATUS_LOADED); +                    break; +                case GST_STATE_PAUSED: +                    setStatus(STATUS_PAUSED); +                    break; +                case GST_STATE_PLAYING: +                    setStatus(STATUS_PLAYING); +                    break; +            } +            break; +        } +        case GST_MESSAGE_ERROR: +        { +            GError *err = nullptr; +            gchar *debug = nullptr; + +            llgst_message_parse_error (message, &err, &debug); +            if (err) +                llg_error_free (err); +            llg_free (debug); + +            mCommand = COMMAND_STOP; + +            setStatus(STATUS_ERROR); + +            break; +        } +        case GST_MESSAGE_INFO: +        { +            if (llgst_message_parse_info) +            { +                GError *err = nullptr; +                gchar *debug = nullptr; +             +                llgst_message_parse_info (message, &err, &debug); +                if (err) +                    llg_error_free (err); +                llg_free (debug); +            } +            break; +        } +        case GST_MESSAGE_WARNING: +        { +            GError *err = nullptr; +            gchar *debug = nullptr; +             +            llgst_message_parse_warning (message, &err, &debug); +            if (err) +                llg_error_free (err); +            llg_free (debug); + +            break; +        } +        case GST_MESSAGE_EOS: +            /* end-of-stream */ +            if (mIsLooping) +            { +                double eos_pos_sec = 0.0F; +                bool got_eos_position = getTimePos(eos_pos_sec); +                 +                if (got_eos_position && eos_pos_sec < MIN_LOOP_SEC) +                { +                    // if we know that the movie is really short, don't +                    // loop it else it can easily become a time-hog +                    // because of GStreamer spin-up overhead +                    // inject a COMMAND_PAUSE +                    mCommand = COMMAND_PAUSE; +                } +                else +                { +                    stop(); +                    play(1.0); +                } +            } +            else // not a looping media +            { +                // inject a COMMAND_STOP +                mCommand = COMMAND_STOP; +            } +            break; +        default: +            /* unhandled message */ +            break; +    } + +    /* we want to be notified again the next time there is a message +     * on the bus, so return true (false means we want to stop watching +     * for messages on the bus and our callback should not be called again) +     */ +    return TRUE; +} + +extern "C" { +    gboolean llmediaimplgstreamer_bus_callback (GstBus     *bus, +                                                GstMessage *message, +                                                gpointer    data) +    { +        MediaPluginGStreamer10 *impl = (MediaPluginGStreamer10*)data; +        return impl->processGSTEvents(bus, message); +    } +} // extern "C" + + + +bool MediaPluginGStreamer10::navigateTo ( const std::string urlIn ) +{ +    if (!mDoneInit) +        return false; // error + +    setStatus(STATUS_LOADING); + +    mSeekWanted = false; + +    if (nullptr == mPump ||  nullptr == mPlaybin) +    { +        setStatus(STATUS_ERROR); +        return false; // error +    } + +    llg_object_set (G_OBJECT (mPlaybin), "uri", urlIn.c_str(), nullptr); + +    // navigateTo implicitly plays, too. +    play(1.0); + +    return true; +} + + +class GstSampleUnref +{ +    GstSample *mT; +public: +    GstSampleUnref( GstSample *aT ) +        : mT( aT ) +    { llassert_always( mT ); } + +    ~GstSampleUnref( ) +    { llgst_sample_unref( mT ); } +}; + +bool MediaPluginGStreamer10::update(int milliseconds) +{ +    if (!mDoneInit) +        return false; // error + +    //  DEBUGMSG("updating media..."); +     +    // sanity check +    if (nullptr == mPump || nullptr == mPlaybin) +    { +        return false; +    } + +    // see if there's an outstanding seek wanted +    if (mSeekWanted && +        // bleh, GST has to be happy that the movie is really truly playing +        // or it may quietly ignore the seek (with rtsp:// at least). +        (GST_STATE(mPlaybin) == GST_STATE_PLAYING)) +    { +        seek(mSeekDestination); +        mSeekWanted = false; +    } + +    // *TODO: time-limit - but there isn't a lot we can do here, most +    // time is spent in gstreamer's own opaque worker-threads.  maybe +    // we can do something sneaky like only unlock the video object +    // for 'milliseconds' and otherwise hold the lock. +    while (llg_main_context_pending(llg_main_loop_get_context(mPump))) +    { +           llg_main_context_iteration(llg_main_loop_get_context(mPump), FALSE); +    } + +    // check for availability of a new frame +     +    if( !mAppSink ) +        return true; + +    if( GST_STATE(mPlaybin) != GST_STATE_PLAYING) // Do not try to pull a sample if not in playing state +        return true; +     +    GstSample *pSample = llgst_app_sink_pull_sample( mAppSink ); +    if(!pSample) +        return false; // Done playing + +    GstSampleUnref oSampleUnref( pSample ); +    GstCaps *pCaps = llgst_sample_get_caps ( pSample ); +    if (!pCaps) +        return false; + +    gint width = 0, height = 0; +    GstStructure *pStruct = llgst_caps_get_structure ( pCaps, 0); + +    if(!llgst_structure_get_int ( pStruct, "width", &width) ) +        width = 0; +    if(!llgst_structure_get_int ( pStruct, "height", &height) ) +        height = 0; + +    if( !mPixels || width == 0 || height == 0) +        return true; +     +    GstBuffer *pBuffer = llgst_sample_get_buffer ( pSample ); +    GstMapInfo map; +    llgst_buffer_map ( pBuffer, &map, GST_MAP_READ); + +    // Our render buffer is always 1kx1k + +    U32 rowSkip = INTERNAL_TEXTURE_SIZE / mTextureHeight; +    U32 colSkip = INTERNAL_TEXTURE_SIZE / mTextureWidth; + +    for (int row = 0; row < mTextureHeight; ++row) +    { +        U8 const *pTexelIn = map.data + (row*rowSkip * width *3); +#ifndef FLIP_Y +        U8 *pTexelOut = mPixels + (row * mTextureWidth * mDepth ); +#else +        U8 *pTexelOut = mPixels + ((mTextureHeight-row-1) * mTextureWidth * mDepth ); +#endif       +        for( int col = 0; col < mTextureWidth; ++col ) +        { +            pTexelOut[ 0 ] = pTexelIn[0]; +            pTexelOut[ 1 ] = pTexelIn[1]; +            pTexelOut[ 2 ] = pTexelIn[2]; +            pTexelOut += mDepth; +            pTexelIn += colSkip*3; +        } +    } + +    llgst_buffer_unmap( pBuffer, &map ); +    setDirty(0,0,mTextureWidth,mTextureHeight); + +    return true; +} + +void MediaPluginGStreamer10::mouseDown( int x, int y ) +{ +  // do nothing +} + +void MediaPluginGStreamer10::mouseUp( int x, int y ) +{ +  // do nothing +} + +void MediaPluginGStreamer10::mouseMove( int x, int y ) +{ +  // do nothing +} + + +bool MediaPluginGStreamer10::pause() +{ +    // todo: error-check this? +    if (mDoneInit && mPlaybin) +    { +        llgst_element_set_state(mPlaybin, GST_STATE_PAUSED); +        return true; +    } +    return false; +} + +bool MediaPluginGStreamer10::stop() +{ +    // todo: error-check this? +    if (mDoneInit && mPlaybin) +    { +        llgst_element_set_state(mPlaybin, GST_STATE_READY); +        return true; +    } +    return false; +} + +bool MediaPluginGStreamer10::play(double rate) +{ +    // NOTE: we don't actually support non-natural rate. + +    // todo: error-check this? +    if (mDoneInit && mPlaybin) +    { +        llgst_element_set_state(mPlaybin, GST_STATE_PLAYING); +        return true; +    } +    return false; +} + +bool MediaPluginGStreamer10::setVolume( float volume ) +{ +    // we try to only update volume as conservatively as +    // possible, as many gst-plugins-base versions up to at least +    // November 2008 have critical race-conditions in setting volume - sigh +    if (mVolume == volume) +        return true; // nothing to do, everything's fine + +    mVolume = volume; +    if (mDoneInit && mPlaybin) +    { +        llg_object_set(mPlaybin, "volume", mVolume, nullptr); +        return true; +    } + +    return false; +} + +bool MediaPluginGStreamer10::seek(double time_sec) +{ +    bool success = false; +    if (mDoneInit && mPlaybin) +    { +        success = llgst_element_seek(mPlaybin, 1.0F, GST_FORMAT_TIME, +                GstSeekFlags(GST_SEEK_FLAG_FLUSH | +                         GST_SEEK_FLAG_KEY_UNIT), +                GST_SEEK_TYPE_SET, gint64(time_sec*GST_SECOND), +                GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); +    } +    return success; +} + +bool MediaPluginGStreamer10::getTimePos(double &sec_out) +{ +    bool got_position = false; +    if (mDoneInit && mPlaybin) +    { +        gint64 pos(0); +        GstFormat timefmt = GST_FORMAT_TIME; +        got_position = +            llgst_element_query_position && +            llgst_element_query_position(mPlaybin, +                             &timefmt, +                             &pos); +        got_position = got_position +            && (timefmt == GST_FORMAT_TIME); +        // GStreamer may have other ideas, but we consider the current position +        // undefined if not PLAYING or PAUSED +        got_position = got_position && +            (GST_STATE(mPlaybin) == GST_STATE_PLAYING || +             GST_STATE(mPlaybin) == GST_STATE_PAUSED); +        if (got_position && !GST_CLOCK_TIME_IS_VALID(pos)) +        { +            if (GST_STATE(mPlaybin) == GST_STATE_PLAYING) +            { +                // if we're playing then we treat an invalid clock time +                // as 0, for complicated reasons (insert reason here) +                pos = 0; +            } +            else +            { +                got_position = false; +            } +             +        } +        // If all the preconditions succeeded... we can trust the result. +        if (got_position) +        { +            sec_out = double(pos) / double(GST_SECOND); // gst to sec +        } +    } +    return got_position; +} + +bool MediaPluginGStreamer10::load() +{ +    if (!mDoneInit) +        return false; // error + +    setStatus(STATUS_LOADING); + +    mIsLooping = false; +    mVolume = 0.1234567f; // minor hack to force an initial volume update + +    // Create a pumpable main-loop for this media +    mPump = llg_main_loop_new (nullptr, FALSE); +    if (!mPump) +    { +        setStatus(STATUS_ERROR); +        return false; // error +    } + +    // instantiate a playbin element to do the hard work +    mPlaybin = llgst_element_factory_make ("playbin", ""); +    if (!mPlaybin) +    { +        setStatus(STATUS_ERROR); +        return false; // error +    } + +    // get playbin's bus +    GstBus *bus = llgst_pipeline_get_bus (GST_PIPELINE (mPlaybin)); +    if (!bus) +    { +        setStatus(STATUS_ERROR); +        return false; // error +    } +    mBusWatchID = llgst_bus_add_watch (bus, +                       llmediaimplgstreamer_bus_callback, +                       this); +    llgst_object_unref (bus); + +    mAppSink = (GstAppSink*)(llgst_element_factory_make ("appsink", "")); + +    GstCaps* pCaps = llgst_caps_new_simple( "video/x-raw", +                                            "format", G_TYPE_STRING, "RGB", +                                            "width", G_TYPE_INT, INTERNAL_TEXTURE_SIZE, +                                            "height", G_TYPE_INT, INTERNAL_TEXTURE_SIZE, +                                            nullptr ); + +    llgst_app_sink_set_caps( mAppSink, pCaps ); +    llgst_caps_unref( pCaps ); + +    if (!mAppSink) +    { +        setStatus(STATUS_ERROR); +        return false; +    } +     +    llg_object_set(mPlaybin, "video-sink", mAppSink, nullptr); + +    return true; +} + +bool MediaPluginGStreamer10::unload () +{ +    if (!mDoneInit) +        return false; // error + +    // stop getting callbacks for this bus +    llg_source_remove(mBusWatchID); +    mBusWatchID = 0; + +    if (mPlaybin) +    { +        llgst_element_set_state (mPlaybin, GST_STATE_NULL); +        llgst_object_unref (GST_OBJECT (mPlaybin)); +        mPlaybin = nullptr; +    } + +    if (mPump) +    { +        llg_main_loop_quit(mPump); +        mPump = nullptr; +    } + +    mAppSink = nullptr; + +    setStatus(STATUS_NONE); + +    return true; +} + +void LogFunction(GstDebugCategory *category, GstDebugLevel level, const gchar *file, const gchar *function, gint line, GObject *object, GstDebugMessage *message, gpointer user_data ) +{ +    std::cerr << file << ":" << line << "(" << function << "): " << llgst_debug_message_get( message ) << std::endl; +} + +//static +bool MediaPluginGStreamer10::startup() +{ +    // first - check if GStreamer is explicitly disabled +    if (nullptr != getenv("LL_DISABLE_GSTREAMER")) +        return false; + +    // only do global GStreamer initialization once. +    if (!mDoneInit) +    { +        ll_init_apr(); + +        // Get symbols! +        std::vector< std::string > vctDSONames; +        vctDSONames.push_back( "libgstreamer-1.0.so.0"  ); +        vctDSONames.push_back( "libgstapp-1.0.so.0"  ); +        vctDSONames.push_back( "libglib-2.0.so.0" ); +        vctDSONames.push_back( "libgobject-2.0.so" ); +        if( !gstSymbolGrabber.grabSymbols( vctDSONames ) ) +            return false; + +        if (llgst_segtrap_set_enabled) +        { +            llgst_segtrap_set_enabled(FALSE); +        } + +        // Gstreamer tries a fork during init, waitpid-ing on it, +        // which conflicts with any installed SIGCHLD handler... +        struct sigaction tmpact, oldact; +        if (llgst_registry_fork_set_enabled ) { +            // if we can disable SIGCHLD-using forking behaviour, +            // do it. +            llgst_registry_fork_set_enabled(false); +        } +        else { +            // else temporarily install default SIGCHLD handler +            // while GStreamer initialises +            tmpact.sa_handler = SIG_DFL; +            sigemptyset( &tmpact.sa_mask ); +            tmpact.sa_flags = SA_SIGINFO; +            sigaction(SIGCHLD, &tmpact, &oldact); +        } +        // Protect against GStreamer resetting the locale, yuck. +        static std::string saved_locale; +        saved_locale = setlocale(LC_ALL, nullptr); +         +        llgst_debug_set_default_threshold( GST_LEVEL_WARNING ); +        llgst_debug_add_log_function( LogFunction, nullptr, nullptr ); +        llgst_debug_set_active( false ); + +        // finally, try to initialize GStreamer! +        GError *err = nullptr; +        gboolean init_gst_success = llgst_init_check(nullptr, nullptr, &err); + +        // restore old locale +        setlocale(LC_ALL, saved_locale.c_str() ); + +        // restore old SIGCHLD handler +        if (!llgst_registry_fork_set_enabled) +            sigaction(SIGCHLD, &oldact, nullptr); + +        if (!init_gst_success) // fail +        { +            if (err) +                llg_error_free(err); +            return false; +        } +         +        mDoneInit = true; +    } + +    return true; +} + +//static +bool MediaPluginGStreamer10::closedown() +{ +    if (!mDoneInit) +        return false; // error + +    gstSymbolGrabber.ungrabSymbols(); +    mDoneInit = false; + +    return true; +} + +MediaPluginGStreamer10::~MediaPluginGStreamer10() +{ +    closedown(); +} + +std::string MediaPluginGStreamer10::getVersion() +{ +    std::string plugin_version = "GStreamer10 media plugin, GStreamer version "; +    if (mDoneInit && +        llgst_version) +    { +        guint major, minor, micro, nano; +        llgst_version(&major, &minor, µ, &nano); +        plugin_version += llformat("%u.%u.%u.%u (runtime), %u.%u.%u.%u (headers)", (unsigned int)major, (unsigned int)minor, +                                   (unsigned int)micro, (unsigned int)nano, (unsigned int)GST_VERSION_MAJOR, (unsigned int)GST_VERSION_MINOR, +                                   (unsigned int)GST_VERSION_MICRO, (unsigned int)GST_VERSION_NANO); +    } +    else +    { +        plugin_version += "(unknown)"; +    } +    return plugin_version; +} + +void MediaPluginGStreamer10::receiveMessage(const char *message_string) +{ +    LLPluginMessage message_in; + +    if(message_in.parse(message_string) >= 0) +    { +        std::string message_class = message_in.getClass(); +        std::string message_name = message_in.getName(); + +        if(message_class == LLPLUGIN_MESSAGE_CLASS_BASE) +        { +            if(message_name == "init") +            { +                LLPluginMessage message("base", "init_response"); +                LLSD versions = LLSD::emptyMap(); +                versions[LLPLUGIN_MESSAGE_CLASS_BASE] = LLPLUGIN_MESSAGE_CLASS_BASE_VERSION; +                versions[LLPLUGIN_MESSAGE_CLASS_MEDIA] = LLPLUGIN_MESSAGE_CLASS_MEDIA_VERSION; +                versions[LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME] = LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME_VERSION; +                message.setValueLLSD("versions", versions); + +                load(); + +                message.setValue("plugin_version", getVersion()); +                sendMessage(message); +            } +            else if(message_name == "idle") +            { +                // no response is necessary here. +                double time = message_in.getValueReal("time"); +                 +                // Convert time to milliseconds for update() +                update((int)(time * 1000.0f)); +            } +            else if(message_name == "cleanup") +            { +                unload(); +                closedown(); +            } +            else if(message_name == "shm_added") +            { +                SharedSegmentInfo info; +                info.mAddress = message_in.getValuePointer("address"); +                info.mSize = (size_t)message_in.getValueS32("size"); +                std::string name = message_in.getValue("name"); + +                mSharedSegments.insert(SharedSegmentMap::value_type(name, info)); +            } +            else if(message_name == "shm_remove") +            { +                std::string name = message_in.getValue("name"); + +                SharedSegmentMap::iterator iter = mSharedSegments.find(name); +                if(iter != mSharedSegments.end()) +                { +                    if(mPixels == iter->second.mAddress) +                    { +                        // This is the currently active pixel buffer.  Make sure we stop drawing to it. +                        mPixels = nullptr; +                        mTextureSegmentName.clear(); +                    } +                    mSharedSegments.erase(iter); +                } + +                // Send the response so it can be cleaned up. +                LLPluginMessage message("base", "shm_remove_response"); +                message.setValue("name", name); +                sendMessage(message); +            } +        } +        else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA) +        { +            if(message_name == "init") +            { +                // Plugin gets to decide the texture parameters to use. +                LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "texture_params"); +                // lame to have to decide this now, it depends on the movie.  Oh well. +                mDepth = 4; + +                mTextureWidth = 1; +                mTextureHeight = 1; + +                message.setValueU32("format", GL_RGBA); +                message.setValueU32("type", GL_UNSIGNED_INT_8_8_8_8_REV); + +                message.setValueS32("depth", mDepth); +                message.setValueS32("default_width", INTERNAL_TEXTURE_SIZE ); +                message.setValueS32("default_height", INTERNAL_TEXTURE_SIZE ); +                message.setValueU32("internalformat", GL_RGBA8); +                message.setValueBoolean("coords_opengl", true); // true == use OpenGL-style coordinates, false == (0,0) is upper left. +                message.setValueBoolean("allow_downsample", true); // we respond with grace and performance if asked to downscale +                sendMessage(message); +            } +            else if(message_name == "size_change") +            { +                std::string name = message_in.getValue("name"); +                S32 width = message_in.getValueS32("width"); +                S32 height = message_in.getValueS32("height"); +                S32 texture_width = message_in.getValueS32("texture_width"); +                S32 texture_height = message_in.getValueS32("texture_height"); + +                LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_response"); +                message.setValue("name", name); +                message.setValueS32("width", width); +                message.setValueS32("height", height); +                message.setValueS32("texture_width", texture_width); +                message.setValueS32("texture_height", texture_height); +                sendMessage(message); + +                if(!name.empty()) +                { +                    // Find the shared memory region with this name +                    SharedSegmentMap::iterator iter = mSharedSegments.find(name); +                    if(iter != mSharedSegments.end()) +                    { +                        mPixels = (unsigned char*)iter->second.mAddress; +                        mTextureSegmentName = name; + +                        mTextureWidth = texture_width; +                        mTextureHeight = texture_height; +                        memset( mPixels, 0, mTextureWidth*mTextureHeight*mDepth ); +                    } +                     +                    LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_request"); +                    message.setValue("name", mTextureSegmentName); +                    message.setValueS32("width", INTERNAL_TEXTURE_SIZE ); +                    message.setValueS32("height", INTERNAL_TEXTURE_SIZE ); +                    sendMessage(message); + +                } +            } +            else if(message_name == "load_uri") +            { +                std::string uri = message_in.getValue("uri"); +                navigateTo( uri ); +                sendStatus();        +            } +            else if(message_name == "mouse_event") +            { +                std::string event = message_in.getValue("event"); +                S32 x = message_in.getValueS32("x"); +                S32 y = message_in.getValueS32("y"); +                 +                if(event == "down") +                { +                    mouseDown(x, y); +                } +                else if(event == "up") +                { +                    mouseUp(x, y); +                } +                else if(event == "move") +                { +                    mouseMove(x, y); +                }; +            }; +        } +        else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME) +        { +            if(message_name == "stop") +            { +                stop(); +            } +            else if(message_name == "start") +            { +                double rate = 0.0; +                if(message_in.hasValue("rate")) +                { +                    rate = message_in.getValueReal("rate"); +                } +                // NOTE: we don't actually support rate. +                play(rate); +            } +            else if(message_name == "pause") +            { +                pause(); +            } +            else if(message_name == "seek") +            { +                double time = message_in.getValueReal("time"); +                // defer the actual seek in case we haven't +                // really truly started yet in which case there +                // is nothing to seek upon +                mSeekWanted = true; +                mSeekDestination = time; +            } +            else if(message_name == "set_loop") +            { +                bool loop = message_in.getValueBoolean("loop"); +                mIsLooping = loop; +            } +            else if(message_name == "set_volume") +            { +                double volume = message_in.getValueReal("volume"); +                setVolume(volume); +            } +        } +    } +} + +int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data) +{ +    if( MediaPluginGStreamer10::startup() ) +    { +        MediaPluginGStreamer10 *self = new MediaPluginGStreamer10(host_send_func, host_user_data); +        *plugin_send_func = MediaPluginGStreamer10::staticReceiveMessage; +        *plugin_user_data = (void*)self; +         +        return 0; // okay +    } +    else  +    { +        return -1; // failed to init +    } +} diff --git a/indra/media_plugins/libvlc/CMakeLists.txt b/indra/media_plugins/libvlc/CMakeLists.txt index a3c1c4ef99..1710d18f4d 100644 --- a/indra/media_plugins/libvlc/CMakeLists.txt +++ b/indra/media_plugins/libvlc/CMakeLists.txt @@ -13,13 +13,6 @@ include(LibVLCPlugin)  ### media_plugin_libvlc -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_libvlc_SOURCE_FILES      media_plugin_libvlc.cpp @@ -54,4 +47,46 @@ if (DARWIN)      LINK_FLAGS "-exported_symbols_list ${CMAKE_CURRENT_SOURCE_DIR}/../base/media_plugin_base.exp"    ) +  add_custom_command(TARGET ${PROJECT_NAME} +    POST_BUILD COMMAND ${CMAKE_INSTALL_NAME_TOOL} +        -change "/opt/local/lib/libopenjp2.7.dylib" "@loader_path/../../Frameworks/libopenjp2.7.dylib" +        -change "/opt/local/lib/libpng16.16.dylib" "@loader_path/../../Frameworks/libpng16.16.dylib" +        -change "/opt/local/lib/libjpeg.8.dylib" "@loader_path/../../Frameworks/libjpeg.8.dylib" +        -change "/opt/local/lib/libfreetype.6.dylib" "@loader_path/../../Frameworks/libfreetype.6.dylib" +        -change "/opt/local/lib/libaprutil-1.0.dylib" "@loader_path/../../Frameworks/libaprutil-1.0.dylib" +        -change "/opt/local/lib/libiconv.2.dylib" "@loader_path/../../Frameworks/libiconv.2.dylib" +        -change "/opt/local/lib/libapr-1.0.dylib" "@loader_path/../../Frameworks/libapr-1.0.dylib" +        -change "/opt/local/lib/libexpat.1.dylib" "@loader_path/../../Frameworks/libexpat.1.dylib" +        -change "/opt/local/lib/libjsoncpp.25.dylib" "@loader_path/../../Frameworks/libjsoncpp.25.dylib" +        -change "/opt/local/lib/libz.1.dylib" "@loader_path/../../Frameworks/libz.1.dylib" +        -change "/opt/local/libexec/boost/1.76/lib/libboost_context-mt.dylib" "@loader_path/../../Frameworks/libboost_context-mt.dylib" +        -change "/opt/local/libexec/boost/1.76/lib/libboost_fiber-mt.dylib" "@loader_path/../../Frameworks/libboost_fiber-mt.dylib" +        -change "/opt/local/libexec/boost/1.76/lib/libboost_filesystem-mt.dylib" "@loader_path/../../Frameworks/libboost_filesystem-mt.dylib" +        -change "/opt/local/libexec/boost/1.76/lib/libboost_program_options-mt.dylib" "@loader_path/../../Frameworks/libboost_program_options-mt.dylib" +        -change "/opt/local/libexec/boost/1.76/lib/libboost_regex-mt.dylib" "@loader_path/../../Frameworks/libboost_regex-mt.dylib" +        -change "/opt/local/libexec/boost/1.76/lib/libboost_system-mt.dylib" "@loader_path/../../Frameworks/libboost_system-mt.dylib" +        -change "/opt/local/libexec/boost/1.76/lib/libboost_thread-mt.dylib" "@loader_path/../../Frameworks/libboost_thread-mt.dylib" +        -change "/opt/local/lib/liburiparser.1.dylib" "@loader_path/../../Frameworks/liburiparser.1.dylib" +        -change "/opt/local/lib/libnghttp2.14.dylib" "@loader_path/../../Frameworks/libnghttp2.14.dylib" +        -change "/usr/local/lib/libxmlrpc-epi.0.dylib" "@loader_path/../../Frameworks/libxmlrpc-epi.0.dylib" +        -change "@rpath/libvlc.dylib" "@loader_path/plugins/libvlc.dylib" +        -change "@rpath/libvlccore.dylib" "@loader_path/plugins/libvlccore.dylib" +        "$<TARGET_FILE:media_plugin_libvlc>" +    VERBATIM +    COMMENT "Fixing paths to LibVLC media plugin dependencies" +  ) +  endif (DARWIN) + +if (INSTALL) +    if (DARWIN) +        set(_LIB llplugin) +    elseif (EXISTS ${CMAKE_SYSROOT}/usr/lib/${ARCH}-linux-gnu) +        set(_LIB lib/${ARCH}-linux-gnu) +    elseif (EXISTS /lib64) +        set(_LIB lib64) +    else (DARWIN) +        set(_LIB lib) +    endif (DARWIN) +    install(TARGETS ${PROJECT_NAME} DESTINATION ${_LIB}) +endif (INSTALL) | 
