Apertura del repositorio

This commit is contained in:
2025-05-24 18:09:39 -03:00
parent 76e6359dad
commit d883ddd0d0
35253 changed files with 2891973 additions and 2 deletions
+20
View File
@@ -0,0 +1,20 @@
# -*- cmake -*-
add_subdirectory(base)
if (LINUX)
add_subdirectory(gstreamer10)
add_subdirectory(cef)
#add_subdirectory(example)
endif (LINUX)
if (DARWIN)
add_subdirectory(cef)
add_subdirectory(libvlc)
#add_subdirectory(example)
endif (DARWIN)
if (WINDOWS)
add_subdirectory(cef)
add_subdirectory(libvlc)
#add_subdirectory(example)
endif (WINDOWS)
+38
View File
@@ -0,0 +1,38 @@
# -*- cmake -*-
project(media_plugin_base)
include(00-Common)
include(LLCommon)
include(LLImage)
include(LLWindow)
include(Linking)
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
)
set(media_plugin_base_HEADER_FILES
CMakeLists.txt
media_plugin_base.h
)
add_library(media_plugin_base
${media_plugin_base_SOURCE_FILES}
)
target_link_libraries( media_plugin_base llplugin )
target_include_directories( media_plugin_base INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
@@ -0,0 +1,206 @@
/**
* @file media_plugin_base.cpp
* @brief Media plugin base class for LLMedia API plugin system
*
* All plugins should be a subclass of MediaPluginBase.
*
* @cond
* $LicenseInfo:firstyear=2008&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 "media_plugin_base.h"
// TODO: Make sure that the only symbol exported from this library is LLPluginInitEntryPoint
////////////////////////////////////////////////////////////////////////////////
/// Media plugin constructor.
///
/// @param[in] host_send_func Function for sending messages from plugin to plugin loader shell
/// @param[in] host_user_data Message data for messages from plugin to plugin loader shell
MediaPluginBase::MediaPluginBase(
LLPluginInstance::sendMessageFunction host_send_func,
void *host_user_data )
{
mHostSendFunction = host_send_func;
mHostUserData = host_user_data;
mDeleteMe = false;
mPixels = 0;
mWidth = 0;
mHeight = 0;
mTextureWidth = 0;
mTextureHeight = 0;
mDepth = 0;
mStatus = STATUS_NONE;
}
/**
* Converts current media status enum value into string (STATUS_LOADING into "loading", etc.)
*
* @return Media status string ("loading", "playing", "paused", etc)
*
*/
std::string MediaPluginBase::statusString()
{
std::string result;
switch(mStatus)
{
case STATUS_LOADING: result = "loading"; break;
case STATUS_LOADED: result = "loaded"; break;
case STATUS_ERROR: result = "error"; break;
case STATUS_PLAYING: result = "playing"; break;
case STATUS_PAUSED: result = "paused"; break;
case STATUS_DONE: result = "done"; break;
default:
// keep the empty string
break;
}
return result;
}
/**
* Set media status.
*
* @param[in] status Media status (STATUS_LOADING, STATUS_PLAYING, STATUS_PAUSED, etc)
*
*/
void MediaPluginBase::setStatus(EStatus status)
{
if(mStatus != status)
{
mStatus = status;
sendStatus();
}
}
/**
* Receive message from plugin loader shell.
*
* @param[in] message_string Message string
* @param[in] user_data Message data
*
*/
void MediaPluginBase::staticReceiveMessage(const char *message_string, void **user_data)
{
MediaPluginBase *self = (MediaPluginBase*)*user_data;
if(self != NULL)
{
self->receiveMessage(message_string);
// If the plugin has processed the delete message, delete it.
if(self->mDeleteMe)
{
delete self;
*user_data = NULL;
}
}
}
/**
* Send message to plugin loader shell.
*
* @param[in] message Message data being sent to plugin loader shell
*
*/
void MediaPluginBase::sendMessage(const LLPluginMessage &message)
{
std::string output = message.generate();
mHostSendFunction(output.c_str(), &mHostUserData);
}
/**
* Notifies plugin loader shell that part of display area needs to be redrawn.
*
* @param[in] left Left X coordinate of area to redraw (0,0 is at top left corner)
* @param[in] top Top Y coordinate of area to redraw (0,0 is at top left corner)
* @param[in] right Right X-coordinate of area to redraw (0,0 is at top left corner)
* @param[in] bottom Bottom Y-coordinate of area to redraw (0,0 is at top left corner)
*
*/
void MediaPluginBase::setDirty(int left, int top, int right, int bottom)
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "updated");
message.setValueS32("left", left);
message.setValueS32("top", top);
message.setValueS32("right", right);
message.setValueS32("bottom", bottom);
sendMessage(message);
}
/**
* Sends "media_status" message to plugin loader shell ("loading", "playing", "paused", etc.)
*
*/
void MediaPluginBase::sendStatus()
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "media_status");
message.setValue("status", statusString());
sendMessage(message);
}
#if LL_WINDOWS
# define LLSYMEXPORT __declspec(dllexport)
#elif LL_LINUX
# define LLSYMEXPORT __attribute__ ((visibility("default")))
#else
# define LLSYMEXPORT /**/
#endif
extern "C"
{
LLSYMEXPORT int LLPluginInitEntryPoint(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data);
}
/**
* Plugin initialization and entry point. Establishes communication channel for messages between plugin and plugin loader shell. TODO:DOC - Please check!
*
* @param[in] host_send_func Function for sending messages from plugin to plugin loader shell
* @param[in] host_user_data Message data for messages from plugin to plugin loader shell
* @param[out] plugin_send_func Function for plugin to receive messages from plugin loader shell
* @param[out] plugin_user_data Pointer to plugin instance
*
* @return int, where 0=success
*
*/
LLSYMEXPORT int
LLPluginInitEntryPoint(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data)
{
return init_media_plugin(host_send_func, host_user_data, plugin_send_func, plugin_user_data);
}
#ifdef WIN32
int WINAPI DllEntryPoint( HINSTANCE hInstance, unsigned long reason, void* params )
{
return 1;
}
#endif
+2
View File
@@ -0,0 +1,2 @@
_LLPluginInitEntryPoint
@@ -0,0 +1,129 @@
/**
* @file media_plugin_base.h
* @brief Media plugin base class for LLMedia API plugin system
*
* @cond
* $LicenseInfo:firstyear=2008&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 "llplugininstance.h"
#include "llpluginmessage.h"
#include "llpluginmessageclasses.h"
class MediaPluginBase
{
public:
MediaPluginBase(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data);
/** Media plugin destructor. */
virtual ~MediaPluginBase() {}
/** Handle received message from plugin loader shell. */
virtual void receiveMessage(const char *message_string) = 0;
static void staticReceiveMessage(const char *message_string, void **user_data);
protected:
/** Plugin status. */
typedef enum
{
STATUS_NONE,
STATUS_LOADING,
STATUS_LOADED,
STATUS_ERROR,
STATUS_PLAYING,
STATUS_PAUSED,
STATUS_DONE
} EStatus;
/** Plugin shared memory. */
class SharedSegmentInfo
{
public:
/** Shared memory address. */
void *mAddress;
/** Shared memory size. */
size_t mSize;
};
void sendMessage(const LLPluginMessage &message);
void sendStatus();
std::string statusString();
void setStatus(EStatus status);
/// Note: The quicktime plugin overrides this to add current time and duration to the message.
virtual void setDirty(int left, int top, int right, int bottom);
/** Map of shared memory names to shared memory. */
typedef std::map<std::string, SharedSegmentInfo> SharedSegmentMap;
/** Function to send message from plugin to plugin loader shell. */
LLPluginInstance::sendMessageFunction mHostSendFunction;
/** Message data being sent to plugin loader shell by mHostSendFunction. */
void *mHostUserData;
/** Flag to delete plugin instance (self). */
bool mDeleteMe;
/** Pixel array to display. TODO:DOC are pixels always 24-bit RGB format, aligned on 32-bit boundary? Also: calling this a pixel array may be misleading since 1 pixel > 1 char. */
unsigned char* mPixels;
/** TODO:DOC what's this for -- does a texture have its own piece of shared memory? updated on size_change_request, cleared on shm_remove */
std::string mTextureSegmentName;
/** Width of plugin display in pixels. */
int mWidth;
/** Height of plugin display in pixels. */
int mHeight;
/** Width of plugin texture. */
int mTextureWidth;
/** Height of plugin texture. */
int mTextureHeight;
/** Pixel depth (pixel size in bytes). */
int mDepth;
/** Current status of plugin. */
EStatus mStatus;
/** Map of shared memory segments. */
SharedSegmentMap mSharedSegments;
};
/** The plugin <b>must</b> define this function to create its instance.
* It should look something like this:
* @code
* {
* MediaPluginFoo *self = new MediaPluginFoo(host_send_func, host_user_data);
* *plugin_send_func = MediaPluginFoo::staticReceiveMessage;
* *plugin_user_data = (void*)self;
*
* return 0;
* }
* @endcode
*/
int init_media_plugin(
LLPluginInstance::sendMessageFunction host_send_func,
void *host_user_data,
LLPluginInstance::sendMessageFunction *plugin_send_func,
void **plugin_user_data);
+105
View File
@@ -0,0 +1,105 @@
# -*- cmake -*-
project(media_plugin_cef)
include(Boost)
include(00-Common)
include(LLCommon)
include(LLWindow)
include(Linking)
include(PluginAPI)
# include(MediaPluginBase) # disable Linux volume catcher for the time being
include(CEFPlugin)
### 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
)
set(media_plugin_cef_HEADER_FILES
volume_catcher.h
)
add_compile_definitions(LL_VOLUME_CATCHER)
# 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.")
if(NOT LINUX_VOLUME_CATCHER)
set(LINUX_VOLUME_CATCHER dummy_volume_catcher.cpp)
message( "Building with dummy volume catcher" )
endif(NOT LINUX_VOLUME_CATCHER)
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 ${MEDIA_PLUGIN_BASE_LIBRARIES})
elseif (DARWIN)
list(APPEND media_plugin_cef_SOURCE_FILES mac_volume_catcher_null.cpp)
find_library(CORESERVICES_LIBRARY CoreServices)
find_library(AUDIOUNIT_LIBRARY AudioUnit)
set( media_plugin_cef_LINK_LIBRARIES
${CORESERVICES_LIBRARY} # for Component Manager calls
${AUDIOUNIT_LIBRARY} # for AudioUnit calls
)
elseif (WINDOWS)
list(APPEND media_plugin_cef_SOURCE_FILES windows_volume_catcher.cpp)
endif (LINUX)
list(APPEND media_plugin_cef_SOURCE_FILES ${media_plugin_cef_HEADER_FILES})
add_library(media_plugin_cef
SHARED
${media_plugin_cef_SOURCE_FILES}
)
#add_dependencies(media_plugin_cef
# ${MEDIA_PLUGIN_BASE_LIBRARIES}
#)
target_link_libraries(media_plugin_cef
media_plugin_base
ll::cef
)
if (WINDOWS)
set_target_properties(
media_plugin_cef
PROPERTIES
LINK_FLAGS "/MANIFEST:NO /NODEFAULTLIB:LIBCMT /IGNORE:4099"
)
endif (WINDOWS)
if (DARWIN)
# Don't prepend 'lib' to the executable name, and don't embed a full path in the library's install name
set_target_properties(
media_plugin_cef
PROPERTIES
PREFIX ""
BUILD_WITH_INSTALL_RPATH 1
INSTALL_NAME_DIR "@executable_path"
CXX_FLAGS "-std=c++11 -stdlib=libc++"
LINK_FLAGS "-stdlib=libc++ -exported_symbols_list ${CMAKE_CURRENT_SOURCE_DIR}/../base/media_plugin_base.exp"
)
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"
"$<TARGET_FILE:media_plugin_cef>"
VERBATIM
COMMENT "Fixing path to CEF Framework"
)
endif (DARWIN)
@@ -0,0 +1,57 @@
/**
* @file dummy_volume_catcher.cpp
* @brief A null implementation of the "VolumeCatcher" class for platforms where it's not implemented yet.
*
* @cond
* $LicenseInfo:firstyear=2010&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
* @endcond
*/
#include "volume_catcher.h"
class VolumeCatcherImpl
{
};
/////////////////////////////////////////////////////
VolumeCatcher::VolumeCatcher()
{
pimpl = NULL;
}
VolumeCatcher::~VolumeCatcher()
{
}
void VolumeCatcher::setVolume(F32 volume)
{
}
void VolumeCatcher::setPan(F32 pan)
{
}
void VolumeCatcher::pump()
{
}
+512
View File
@@ -0,0 +1,512 @@
/**
* @file linux_volume_catcher.cpp
* @brief A Linux-specific, PulseAudio-specific hack to detect and volume-adjust new audio sources
*
* @cond
* $LicenseInfo:firstyear=2010&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
* @endcond
*/
/*
The high-level design is as follows:
1) Connect to the PulseAudio daemon
2) Watch for the creation of new audio players connecting to the daemon (this includes ALSA clients running on the PulseAudio emulation layer, such as Flash plugins)
3) Examine any new audio player's PID to see if it belongs to our own process
4) If so, tell PA to adjust the volume of that audio player ('sink input' in PA parlance)
5) Keep a list of all living audio players that we care about, adjust the volumes of all of them when we get a new setVolume() call
*/
#include "linden_common.h"
#include "volume_catcher.h"
extern "C" {
#include <pulse/introspect.h>
#include <pulse/context.h>
#include <pulse/subscribe.h>
#include <pulse/mainloop.h>
#include "apr_pools.h"
#include "apr_dso.h"
}
////////////////////////////////////////////////////
#define DEBUGMSG(...) do {} while(0)
#define INFOMSG(...) do {} while(0)
#define WARNMSG(...) do {} while(0)
#define LL_PA_SYM(REQUIRED, PASYM, RTN, ...) RTN (*ll##PASYM)(__VA_ARGS__) = NULL
#include "linux_volume_catcher_pa_syms.inc"
#undef LL_PA_SYM
static bool sSymsGrabbed = false;
static apr_pool_t *sSymPADSOMemoryPool = NULL;
static apr_dso_handle_t *sSymPADSOHandleG = NULL;
bool grab_pa_syms(std::string pulse_dso_name)
{
if (sSymsGrabbed)
{
// already have grabbed good syms
return true;
}
bool sym_error = false;
bool rtn = false;
apr_status_t rv;
apr_dso_handle_t *sSymPADSOHandle = NULL;
#define LL_PA_SYM(REQUIRED, PASYM, RTN, ...) do{rv = apr_dso_sym((apr_dso_handle_sym_t*)&ll##PASYM, sSymPADSOHandle, #PASYM); if (rv != APR_SUCCESS) { if (REQUIRED) sym_error = true;} } while(0);
//attempt to load the shared library
apr_pool_create(&sSymPADSOMemoryPool, NULL);
if ( APR_SUCCESS == (rv = apr_dso_load(&sSymPADSOHandle,
pulse_dso_name.c_str(),
sSymPADSOMemoryPool) ))
{
INFOMSG("Found DSO: %s", pulse_dso_name.c_str());
#include "linux_volume_catcher_pa_syms.inc"
if ( sSymPADSOHandle )
{
sSymPADSOHandleG = sSymPADSOHandle;
sSymPADSOHandle = NULL;
}
rtn = !sym_error;
}
else
{
INFOMSG("Couldn't load DSO: %s", pulse_dso_name.c_str());
rtn = false; // failure
}
if (sym_error)
{
WARNMSG("Failed to find necessary symbols in PulseAudio libraries.");
}
#undef LL_PA_SYM
sSymsGrabbed = rtn;
return rtn;
}
void ungrab_pa_syms()
{
// should be safe to call regardless of whether we've
// actually grabbed syms.
if ( sSymPADSOHandleG )
{
apr_dso_unload(sSymPADSOHandleG);
sSymPADSOHandleG = NULL;
}
if ( sSymPADSOMemoryPool )
{
apr_pool_destroy(sSymPADSOMemoryPool);
sSymPADSOMemoryPool = NULL;
}
// NULL-out all of the symbols we'd grabbed
#define LL_PA_SYM(REQUIRED, PASYM, RTN, ...) do{ll##PASYM = NULL;}while(0)
#include "linux_volume_catcher_pa_syms.inc"
#undef LL_PA_SYM
sSymsGrabbed = false;
}
////////////////////////////////////////////////////
// PulseAudio requires a chain of callbacks with C linkage
extern "C" {
void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *i, int eol, void *userdata);
void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata);
void callback_context_state(pa_context *context, void *userdata);
}
class VolumeCatcherImpl
{
public:
VolumeCatcherImpl();
~VolumeCatcherImpl();
void setVolume(F32 volume);
void pump(void);
// for internal use - can't be private because used from our C callbacks
bool loadsyms(std::string pulse_dso_name);
void init();
void cleanup();
void update_all_volumes(F32 volume);
void update_index_volume(U32 index, F32 volume);
void connected_okay();
std::set<U32> mSinkInputIndices;
std::map<U32,U32> mSinkInputNumChannels;
F32 mDesiredVolume;
pa_mainloop *mMainloop;
pa_context *mPAContext;
bool mConnected;
bool mGotSyms;
};
VolumeCatcherImpl::VolumeCatcherImpl()
: mDesiredVolume(0.0f),
mMainloop(NULL),
mPAContext(NULL),
mConnected(false),
mGotSyms(false)
{
init();
}
VolumeCatcherImpl::~VolumeCatcherImpl()
{
cleanup();
}
bool VolumeCatcherImpl::loadsyms(std::string pulse_dso_name)
{
return grab_pa_syms(pulse_dso_name);
}
void VolumeCatcherImpl::init()
{
// try to be as defensive as possible because PA's interface is a
// bit fragile and (for our purposes) we'd rather simply not function
// than crash
mGotSyms = loadsyms("libpulse.so.0");
if (!mGotSyms) return;
mMainloop = llpa_mainloop_new();
if (mMainloop)
{
pa_mainloop_api *api = llpa_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, NULL, 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, NULL, cflags, NULL) >= 0)
{
// Okay! We haven't definitely connected, but we
// haven't definitely failed yet.
}
else
{
// Failed to connect to PA manager... we'll leave
// things like that. Perhaps we should try again later.
}
}
}
void VolumeCatcherImpl::cleanup()
{
mConnected = false;
if (mGotSyms && mPAContext)
{
llpa_context_disconnect(mPAContext);
llpa_context_unref(mPAContext);
}
mPAContext = NULL;
if (mGotSyms && mMainloop)
{
llpa_mainloop_free(mMainloop);
}
mMainloop = NULL;
}
void VolumeCatcherImpl::setVolume(F32 volume)
{
mDesiredVolume = volume;
if (!mGotSyms) return;
if (mConnected && mPAContext)
{
update_all_volumes(mDesiredVolume);
}
pump();
}
void VolumeCatcherImpl::pump()
{
// <FS:Beq> PR#17 makidoll: Fix Linux volume catcher
if (mGotSyms && mMainloop) {
llpa_mainloop_iterate(mMainloop, 0, NULL);
}
// </FS:Beq>
}
void VolumeCatcherImpl::connected_okay()
{
pa_operation *op;
// fetch global list of existing sinkinputs
if ((op = llpa_context_get_sink_input_info_list(mPAContext,
callback_discovered_sinkinput,
this)))
{
llpa_operation_unref(op);
}
// subscribe to future global sinkinput changes
llpa_context_set_subscribe_callback(mPAContext,
callback_subscription_alert,
this);
if ((op = llpa_context_subscribe(mPAContext, (pa_subscription_mask_t)
(PA_SUBSCRIPTION_MASK_SINK_INPUT),
NULL, NULL)))
{
llpa_operation_unref(op);
}
}
void VolumeCatcherImpl::update_all_volumes(F32 volume)
{
for (std::set<U32>::iterator it = mSinkInputIndices.begin();
it != mSinkInputIndices.end(); ++it)
{
update_index_volume(*it, volume);
}
}
void VolumeCatcherImpl::update_index_volume(U32 index, F32 volume)
{
static pa_cvolume cvol;
llpa_cvolume_set(&cvol, mSinkInputNumChannels[index],
llpa_sw_volume_from_linear(volume));
pa_context *c = mPAContext;
uint32_t idx = index;
const pa_cvolume *cvolumep = &cvol;
pa_context_success_cb_t cb = NULL; // okay as null
void *userdata = NULL; // okay as null
pa_operation *op;
if ((op = llpa_context_set_sink_input_volume(c, idx, cvolumep, cb, userdata)))
{
llpa_operation_unref(op);
}
}
pid_t getParentPid( pid_t aPid )
{
std::stringstream strm;
strm << "/proc/" << aPid << "/status";
std::ifstream in{ strm.str() };
if( !in.is_open() )
return 0;
pid_t res {0};
while( !in.eof() && res == 0 )
{
std::string line;
line.resize( 1024, 0 );
in.getline( &line[0], line.length() );
auto i = line.find( "PPid:" );
if( i == std::string::npos )
continue;
char const *pIn = line.c_str() + 5; // Skip over pid;
while( *pIn != 0 && isspace( *pIn ) )
++pIn;
if( *pIn )
res = atoll( pIn );
}
return res;
}
bool isPluginPid( pid_t aPid )
{
auto myPid = getpid();
do
{
if( aPid == myPid )
return true;
aPid = getParentPid( aPid );
} while( aPid > 1 );
return false;
}
void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *sii, int eol, void *userdata)
{
VolumeCatcherImpl *impl = dynamic_cast<VolumeCatcherImpl*>((VolumeCatcherImpl*)userdata);
llassert(impl);
if (0 == eol)
{
pa_proplist *proplist = sii->proplist;
const char* str = llpa_proplist_gets(proplist, PA_PROP_APPLICATION_PROCESS_ID);
if (!str)
{
return;
}
pid_t sinkpid = atoll(str);
if (isPluginPid( sinkpid )) // does the discovered sinkinput belong to this process?
{
bool is_new = (impl->mSinkInputIndices.find(sii->index) == impl->mSinkInputIndices.end());
impl->mSinkInputIndices.insert(sii->index);
impl->mSinkInputNumChannels[sii->index] = sii->channel_map.channels;
if (is_new)
{
// new!
impl->update_index_volume(sii->index, impl->mDesiredVolume);
}
else
{
// seen it already, do nothing.
}
}
}
}
void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata)
{
VolumeCatcherImpl *impl = dynamic_cast<VolumeCatcherImpl*>((VolumeCatcherImpl*)userdata);
llassert(impl);
switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)
{
case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
{
// forget this sinkinput, if we were caring about it
impl->mSinkInputIndices.erase(index);
impl->mSinkInputNumChannels.erase(index);
}
else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW)
{
// ask for more info about this new sinkinput
pa_operation *op;
if ((op = llpa_context_get_sink_input_info(impl->mPAContext, index, callback_discovered_sinkinput, impl)))
{
llpa_operation_unref(op);
}
}
else
{
// property change on this sinkinput - we don't care.
}
break;
default:;
}
}
void callback_context_state(pa_context *context, void *userdata)
{
VolumeCatcherImpl *impl = dynamic_cast<VolumeCatcherImpl*>((VolumeCatcherImpl*)userdata);
llassert(impl);
switch (llpa_context_get_state(context))
{
case PA_CONTEXT_READY:
impl->mConnected = true;
impl->connected_okay();
break;
case PA_CONTEXT_TERMINATED:
impl->mConnected = false;
break;
case PA_CONTEXT_FAILED:
impl->mConnected = false;
break;
default:;
}
}
/////////////////////////////////////////////////////
VolumeCatcher::VolumeCatcher()
{
pimpl = new VolumeCatcherImpl();
}
VolumeCatcher::~VolumeCatcher()
{
delete pimpl;
pimpl = NULL;
}
void VolumeCatcher::setVolume(F32 volume)
{
llassert(pimpl);
pimpl->setVolume(volume);
}
void VolumeCatcher::setPan(F32 pan)
{
// TODO: implement this (if possible)
}
void VolumeCatcher::pump()
{
llassert(pimpl);
pimpl->pump();
}
+26
View File
@@ -0,0 +1,26 @@
// required symbols to grab
LL_PA_SYM(true, pa_context_connect, int, pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api);
LL_PA_SYM(true, pa_context_disconnect, void, pa_context *c);
LL_PA_SYM(true, pa_context_get_sink_input_info, pa_operation*, pa_context *c, uint32_t idx, pa_sink_input_info_cb_t cb, void *userdata);
LL_PA_SYM(true, pa_context_get_sink_input_info_list, pa_operation*, pa_context *c, pa_sink_input_info_cb_t cb, void *userdata);
LL_PA_SYM(true, pa_context_get_state, pa_context_state_t, pa_context *c);
LL_PA_SYM(true, pa_context_new_with_proplist, pa_context*, pa_mainloop_api *mainloop, const char *name, pa_proplist *proplist);
LL_PA_SYM(true, pa_context_set_sink_input_volume, pa_operation*, pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);
LL_PA_SYM(true, pa_context_set_state_callback, void, pa_context *c, pa_context_notify_cb_t cb, void *userdata);
LL_PA_SYM(true, pa_context_set_subscribe_callback, void, pa_context *c, pa_context_subscribe_cb_t cb, void *userdata);
LL_PA_SYM(true, pa_context_subscribe, pa_operation*, pa_context *c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void *userdata);
LL_PA_SYM(true, pa_context_unref, void, pa_context *c);
LL_PA_SYM(true, pa_cvolume_set, pa_cvolume*, pa_cvolume *a, unsigned channels, pa_volume_t v);
LL_PA_SYM(true, pa_operation_unref, void, pa_operation *o);
LL_PA_SYM(true, pa_proplist_free, void, pa_proplist* p);
LL_PA_SYM(true, pa_proplist_gets, const char*, pa_proplist *p, const char *key);
LL_PA_SYM(true, pa_proplist_new, pa_proplist*, void);
LL_PA_SYM(true, pa_proplist_sets, int, pa_proplist *p, const char *key, const char *value);
LL_PA_SYM(true, pa_sw_volume_from_linear, pa_volume_t, double v);
LL_PA_SYM(true, pa_mainloop_free, void, pa_mainloop* m);
LL_PA_SYM(true, pa_mainloop_get_api, pa_mainloop_api*, pa_mainloop* m);
LL_PA_SYM(true, pa_mainloop_new, pa_mainloop*, void);
LL_PA_SYM(true, pa_mainloop_iterate, int, pa_mainloop*, int, int*);
// optional symbols to grab
@@ -0,0 +1,9 @@
// <FS:Zi> No longer needed
/*
// required symbols to grab
LL_PA_SYM(true, pa_glib_mainloop_free, void, pa_glib_mainloop* g);
LL_PA_SYM(true, pa_glib_mainloop_get_api, pa_mainloop_api*, pa_glib_mainloop* g);
LL_PA_SYM(true, pa_glib_mainloop_new, pa_glib_mainloop *, GMainContext *c);
// optional symbols to grab
*/
@@ -0,0 +1,275 @@
/**
* @file mac_volume_catcher.cpp
* @brief A macOS specific hack to control the volume level of all audio channels opened by a process.
*
* @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
*/
/**************************************************************************************************************
This code works by using CaptureComponent to capture the "Default Output" audio component
(kAudioUnitType_Output/kAudioUnitSubType_DefaultOutput) and delegating all calls to the original component.
It does this just to keep track of all instances of the default output component, so that it can set the
kHALOutputParam_Volume parameter on all of them to adjust the output volume.
**************************************************************************************************************/
#include "volume_catcher.h"
#include <QuickTime/QuickTime.h>
#include <AudioUnit/AudioUnit.h>
#include <list>
#if LL_DARWIN
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
struct VolumeCatcherStorage;
class VolumeCatcherImpl
{
public:
void setVolume(F32 volume);
void setPan(F32 pan);
void setInstanceVolume(VolumeCatcherStorage *instance);
std::list<VolumeCatcherStorage*> mComponentInstances;
Component mOriginalDefaultOutput;
Component mVolumeAdjuster;
static VolumeCatcherImpl *getInstance();
private:
// This is a singleton class -- both callers and the component implementation should use getInstance() to find the instance.
VolumeCatcherImpl();
static VolumeCatcherImpl *sInstance;
// The singlar instance of this class is expected to last until the process exits.
// To ensure this, we declare the destructor here but never define it, so any code which attempts to destroy the instance will not link.
~VolumeCatcherImpl();
F32 mVolume;
F32 mPan;
};
VolumeCatcherImpl *VolumeCatcherImpl::sInstance = NULL;;
struct VolumeCatcherStorage
{
ComponentInstance self;
ComponentInstance delegate;
};
static ComponentResult volume_catcher_component_entry(ComponentParameters *cp, Handle componentStorage);
static ComponentResult volume_catcher_component_open(VolumeCatcherStorage *storage, ComponentInstance self);
static ComponentResult volume_catcher_component_close(VolumeCatcherStorage *storage, ComponentInstance self);
VolumeCatcherImpl *VolumeCatcherImpl::getInstance()
{
if(!sInstance)
{
sInstance = new VolumeCatcherImpl;
}
return sInstance;
}
VolumeCatcherImpl::VolumeCatcherImpl()
{
mVolume = 1.0; // default to full volume
mPan = 0.0; // and center pan
ComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
// Find the original default output component
mOriginalDefaultOutput = FindNextComponent(NULL, &desc);
// Register our own output component with the same parameters
mVolumeAdjuster = RegisterComponent(&desc, NewComponentRoutineUPP(volume_catcher_component_entry), 0, NULL, NULL, NULL);
// Capture the original component, so we always get found instead.
CaptureComponent(mOriginalDefaultOutput, mVolumeAdjuster);
}
static ComponentResult volume_catcher_component_entry(ComponentParameters *cp, Handle componentStorage)
{
ComponentResult result = badComponentSelector;
VolumeCatcherStorage *storage = (VolumeCatcherStorage*)componentStorage;
switch(cp->what)
{
case kComponentOpenSelect:
// std::cerr << "kComponentOpenSelect" << std::endl;
result = CallComponentFunctionWithStorageProcInfo((Handle)storage, cp, (ProcPtr)volume_catcher_component_open, uppCallComponentOpenProcInfo);
break;
case kComponentCloseSelect:
// std::cerr << "kComponentCloseSelect" << std::endl;
result = CallComponentFunctionWithStorageProcInfo((Handle)storage, cp, (ProcPtr)volume_catcher_component_close, uppCallComponentCloseProcInfo);
// CallComponentFunctionWithStorageProcInfo
break;
default:
// std::cerr << "Delegating selector: " << cp->what << " to component instance " << storage->delegate << std::endl;
result = DelegateComponentCall(cp, storage->delegate);
break;
}
return result;
}
static ComponentResult volume_catcher_component_open(VolumeCatcherStorage *storage, ComponentInstance self)
{
ComponentResult result = noErr;
VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
storage = new VolumeCatcherStorage;
storage->self = self;
storage->delegate = NULL;
result = OpenAComponent(impl->mOriginalDefaultOutput, &(storage->delegate));
if(result != noErr)
{
// std::cerr << "OpenAComponent result = " << result << ", component ref = " << storage->delegate << std::endl;
// If we failed to open the delagate component, our open is going to fail. Clean things up.
delete storage;
}
else
{
// Success -- set up this component's storage
SetComponentInstanceStorage(self, (Handle)storage);
// add this instance to the global list
impl->mComponentInstances.push_back(storage);
// and set up the initial volume
impl->setInstanceVolume(storage);
}
return result;
}
static ComponentResult volume_catcher_component_close(VolumeCatcherStorage *storage, ComponentInstance self)
{
ComponentResult result = noErr;
if(storage)
{
if(storage->delegate)
{
CloseComponent(storage->delegate);
storage->delegate = NULL;
}
VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
impl->mComponentInstances.remove(storage);
delete[] storage;
}
return result;
}
void VolumeCatcherImpl::setVolume(F32 volume)
{
VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
impl->mVolume = volume;
// Iterate through all known instances, setting the volume on each.
for(std::list<VolumeCatcherStorage*>::iterator iter = mComponentInstances.begin(); iter != mComponentInstances.end(); ++iter)
{
impl->setInstanceVolume(*iter);
}
}
void VolumeCatcherImpl::setPan(F32 pan)
{
VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
impl->mPan = pan;
// TODO: implement this.
// This will probably require adding a "panner" audio unit to the chain somehow.
// There's also a "3d mixer" component that we might be able to use...
}
void VolumeCatcherImpl::setInstanceVolume(VolumeCatcherStorage *instance)
{
// std::cerr << "Setting volume on component instance: " << (instance->delegate) << " to " << mVolume << std::endl;
OSStatus err = noErr;
if(instance && instance->delegate)
{
err = AudioUnitSetParameter(
instance->delegate,
kHALOutputParam_Volume,
kAudioUnitScope_Global,
0,
mVolume,
0);
}
if(err)
{
// std::cerr << " AudioUnitSetParameter returned " << err << std::endl;
}
}
/////////////////////////////////////////////////////
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.
}
#if LL_DARWIN
#pragma GCC diagnostic warning "-Wdeprecated-declarations"
#endif
@@ -0,0 +1,95 @@
/**
* @file windows_volume_catcher.cpp
* @brief A null implementation of volume level control of all audio channels opened by a process.
* We are using this for the macOS version for now until we can understand how to make the
* exitising mac_volume_catcher.cpp work without the (now, non-existant) QuickTime dependency
*
* @cond
* $LicenseInfo:firstyear=2010&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
* @endcond
*/
#include "volume_catcher.h"
#include "llsingleton.h"
class VolumeCatcherImpl : public LLSingleton<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.
}
File diff suppressed because it is too large Load Diff
+54
View File
@@ -0,0 +1,54 @@
/**
* @file volume_catcher.h
* @brief Interface to a class with platform-specific implementations that allows control of the audio volume of all sources in the current process.
*
* @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_H
#define VOLUME_CATCHER_H
#include "linden_common.h"
class VolumeCatcherImpl;
class VolumeCatcher
{
public:
VolumeCatcher();
~VolumeCatcher();
void setVolume(F32 volume); // 0.0 - 1.0
// Set the left-right pan of audio sources
// where -1.0 = left, 0 = center, and 1.0 = right
void setPan(F32 pan);
void pump(); // call this at least a few times a second if you can - it affects how quickly we can 'catch' a new audio source and adjust its volume
private:
VolumeCatcherImpl *pimpl;
};
#endif // VOLUME_CATCHER_H
@@ -0,0 +1,104 @@
/**
* @file windows_volume_catcher.cpp
* @brief A Windows implementation of volume level control of all audio channels opened by a process.
*
* @cond
* $LicenseInfo:firstyear=2010&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
* @endcond
*/
#include "volume_catcher.h"
#include "llsingleton.h"
#include <windows.h>
#include <mmeapi.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;
// set both left/right to same volume
// TODO: use pan value to set independently
DWORD left_channel = (DWORD)(mVolume * 65535.0f);
DWORD right_channel = (DWORD)(mVolume * 65535.0f);
DWORD hw_volume = left_channel << 16 | right_channel;
::waveOutSetVolume(NULL, hw_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.
}
@@ -0,0 +1,54 @@
# -*- cmake -*-
project(media_plugin_example)
include(00-Common)
include(LLCommon)
include(LLImage)
include(LLWindow)
include(Linking)
include(PluginAPI)
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
)
add_library(media_plugin_example
SHARED
${media_plugin_example_SOURCE_FILES}
)
target_link_libraries(media_plugin_example media_plugin_base )
if (WINDOWS)
set_target_properties(
media_plugin_example
PROPERTIES
LINK_FLAGS "/MANIFEST:NO /NODEFAULTLIB:LIBCMT"
)
endif (WINDOWS)
if (DARWIN)
# Don't prepend 'lib' to the executable name, and don't embed a full path in the library's install name
set_target_properties(
media_plugin_example
PROPERTIES
PREFIX ""
BUILD_WITH_INSTALL_RPATH 1
INSTALL_NAME_DIR "@executable_path"
LINK_FLAGS "-exported_symbols_list ${CMAKE_CURRENT_SOURCE_DIR}/../base/media_plugin_base.exp"
)
endif (DARWIN)
@@ -0,0 +1,395 @@
/**
* @file media_plugin_example.cpp
* @brief Example plugin for LLMedia API plugin system
*
* @cond
* $LicenseInfo:firstyear=2008&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"
#include <time.h>
////////////////////////////////////////////////////////////////////////////////
//
class mediaPluginExample :
public MediaPluginBase
{
public:
mediaPluginExample(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data);
~mediaPluginExample();
/*virtual*/ void receiveMessage(const char* message_string);
private:
bool init();
void update(F64 milliseconds);
bool mFirstTime;
time_t mLastUpdateTime;
enum Constants { ENumObjects = 64 };
unsigned char* mBackgroundPixels;
int mColorR[ENumObjects];
int mColorG[ENumObjects];
int mColorB[ENumObjects];
int mXpos[ENumObjects];
int mYpos[ENumObjects];
int mXInc[ENumObjects];
int mYInc[ENumObjects];
int mBlockSize[ENumObjects];
};
////////////////////////////////////////////////////////////////////////////////
//
mediaPluginExample::mediaPluginExample(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data) :
MediaPluginBase(host_send_func, host_user_data)
{
mFirstTime = true;
mTextureWidth = 0;
mTextureHeight = 0;
mWidth = 0;
mHeight = 0;
mDepth = 4;
mPixels = 0;
mLastUpdateTime = 0;
mBackgroundPixels = 0;
}
////////////////////////////////////////////////////////////////////////////////
//
mediaPluginExample::~mediaPluginExample()
{
}
////////////////////////////////////////////////////////////////////////////////
//
void mediaPluginExample::receiveMessage(const char* message_string)
{
// std::cerr << "MediaPluginWebKit::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_BROWSER] = LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER_VERSION;
message.setValueLLSD("versions", versions);
std::string plugin_version = "Example plugin 0.0.0";
message.setValue("plugin_version", plugin_version);
sendMessage(message);
}
else if (message_name == "idle")
{
// no response is necessary here.
F64 time = message_in.getValueReal("time");
// Convert time to milliseconds for update()
update((int)(time * 1000.0f));
}
else if (message_name == "cleanup")
{
LLPluginMessage message("base", "goodbye");
sendMessage(message);
mDeleteMe = true;
}
else if (message_name == "force_exit")
{
mDeleteMe = true;
}
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 = NULL;
mTextureSegmentName.clear();
}
mSharedSegments.erase(iter);
}
else
{
// std::cerr << "MediaPluginWebKit::receiveMessage: unknown shared memory region!" << std::endl;
}
// Send the response so it can be cleaned up.
LLPluginMessage message("base", "shm_remove_response");
message.setValue("name", name);
sendMessage(message);
}
else
{
// std::cerr << "MediaPluginWebKit::receiveMessage: unknown base message: " << message_name << std::endl;
}
}
else if (message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA)
{
if (message_name == "init")
{
// Plugin gets to decide the texture parameters to use.
mDepth = 4;
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "texture_params");
message.setValueS32("default_width", 1024);
message.setValueS32("default_height", 1024);
message.setValueS32("depth", mDepth);
message.setValueU32("internalformat", GL_RGB);
message.setValueU32("format", GL_RGBA);
message.setValueU32("type", GL_UNSIGNED_BYTE);
message.setValueBoolean("coords_opengl", true);
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");
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;
mWidth = width;
mHeight = height;
mTextureWidth = texture_width;
mTextureHeight = 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);
mFirstTime = true;
mLastUpdateTime = 0;
}
else if (message_name == "load_uri")
{
}
else if (message_name == "mouse_event")
{
std::string event = message_in.getValue("event");
if (event == "down")
{
}
else if (event == "up")
{
}
else if (event == "double_click")
{
}
}
}
else
{
};
}
}
////////////////////////////////////////////////////////////////////////////////
//
void mediaPluginExample::update(F64 milliseconds)
{
if (mWidth < 1 || mWidth > 2048 || mHeight < 1 || mHeight > 2048)
return;
if (mPixels == 0)
return;
if (mFirstTime)
{
for (int n = 0; n < ENumObjects; ++n)
{
mXpos[n] = (mWidth / 2) + rand() % (mWidth / 16) - (mWidth / 32);
mYpos[n] = (mHeight / 2) + rand() % (mHeight / 16) - (mHeight / 32);
mColorR[n] = rand() % 0x60 + 0x60;
mColorG[n] = rand() % 0x60 + 0x60;
mColorB[n] = rand() % 0x60 + 0x60;
mXInc[n] = 0;
while (mXInc[n] == 0)
mXInc[n] = rand() % 7 - 3;
mYInc[n] = 0;
while (mYInc[n] == 0)
mYInc[n] = rand() % 9 - 4;
mBlockSize[n] = rand() % 0x30 + 0x10;
};
delete[] mBackgroundPixels;
mBackgroundPixels = new unsigned char[mWidth * mHeight * mDepth];
mFirstTime = false;
};
if (time(NULL) > mLastUpdateTime + 3)
{
const int num_squares = rand() % 20 + 4;
int sqr1_r = rand() % 0x80 + 0x20;
int sqr1_g = rand() % 0x80 + 0x20;
int sqr1_b = rand() % 0x80 + 0x20;
int sqr2_r = rand() % 0x80 + 0x20;
int sqr2_g = rand() % 0x80 + 0x20;
int sqr2_b = rand() % 0x80 + 0x20;
for (int y1 = 0; y1 < num_squares; ++y1)
{
for (int x1 = 0; x1 < num_squares; ++x1)
{
int px_start = mWidth * x1 / num_squares;
int px_end = (mWidth * (x1 + 1)) / num_squares;
int py_start = mHeight * y1 / num_squares;
int py_end = (mHeight * (y1 + 1)) / num_squares;
for (int y2 = py_start; y2 < py_end; ++y2)
{
for (int x2 = px_start; x2 < px_end; ++x2)
{
int rowspan = mWidth * mDepth;
if ((y1 % 2) ^ (x1 % 2))
{
mBackgroundPixels[y2 * rowspan + x2 * mDepth + 0] = sqr1_r;
mBackgroundPixels[y2 * rowspan + x2 * mDepth + 1] = sqr1_g;
mBackgroundPixels[y2 * rowspan + x2 * mDepth + 2] = sqr1_b;
}
else
{
mBackgroundPixels[y2 * rowspan + x2 * mDepth + 0] = sqr2_r;
mBackgroundPixels[y2 * rowspan + x2 * mDepth + 1] = sqr2_g;
mBackgroundPixels[y2 * rowspan + x2 * mDepth + 2] = sqr2_b;
};
};
};
};
};
time(&mLastUpdateTime);
};
memcpy(mPixels, mBackgroundPixels, mWidth * mHeight * mDepth);
for (int n = 0; n < ENumObjects; ++n)
{
if (rand() % 50 == 0)
{
mXInc[n] = 0;
while (mXInc[n] == 0)
mXInc[n] = rand() % 7 - 3;
mYInc[n] = 0;
while (mYInc[n] == 0)
mYInc[n] = rand() % 9 - 4;
};
if (mXpos[n] + mXInc[n] < 0 || mXpos[n] + mXInc[n] >= mWidth - mBlockSize[n])
mXInc[n] = -mXInc[n];
if (mYpos[n] + mYInc[n] < 0 || mYpos[n] + mYInc[n] >= mHeight - mBlockSize[n])
mYInc[n] = -mYInc[n];
mXpos[n] += mXInc[n];
mYpos[n] += mYInc[n];
for (int y = 0; y < mBlockSize[n]; ++y)
{
for (int x = 0; x < mBlockSize[n]; ++x)
{
mPixels[(mXpos[n] + x) * mDepth + (mYpos[n] + y) * mDepth * mWidth + 0] = mColorR[n];
mPixels[(mXpos[n] + x) * mDepth + (mYpos[n] + y) * mDepth * mWidth + 1] = mColorG[n];
mPixels[(mXpos[n] + x) * mDepth + (mYpos[n] + y) * mDepth * mWidth + 2] = mColorB[n];
};
};
};
setDirty(0, 0, mWidth, mHeight);
};
////////////////////////////////////////////////////////////////////////////////
//
bool mediaPluginExample::init()
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "name_text");
message.setValue("name", "Example Plugin");
sendMessage(message);
return true;
};
////////////////////////////////////////////////////////////////////////////////
//
int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func,
void* host_user_data,
LLPluginInstance::sendMessageFunction *plugin_send_func,
void **plugin_user_data)
{
mediaPluginExample* self = new mediaPluginExample(host_send_func, host_user_data);
*plugin_send_func = mediaPluginExample::staticReceiveMessage;
*plugin_user_data = (void*)self;
return 0;
}
@@ -0,0 +1,47 @@
# -*- cmake -*-
project(media_plugin_gstreamer010)
include(00-Common)
include(LLCommon)
include(LLImage)
include(LLMath)
include(LLWindow)
include(Linking)
include(PluginAPI)
include(OpenGL)
include(MediaPluginBase) # <FS:Zi> Linux volume catcher
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
)
@@ -0,0 +1,53 @@
/**
* @file llmediaimplgstreamer.h
* @author Tofu Linden
* @brief implementation that supports media playback via GStreamer.
*
* @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
*/
// header guard
#ifndef llmediaimplgstreamer_h
#define llmediaimplgstreamer_h
#if LL_GSTREAMER010_ENABLED
extern "C" {
#include <stdio.h>
#include <gst/gst.h>
#include "apr_pools.h"
#include "apr_dso.h"
}
extern "C" {
gboolean llmediaimplgstreamer_bus_callback (GstBus *bus,
GstMessage *message,
gpointer data);
}
#endif // LL_GSTREAMER010_ENABLED
#endif // llmediaimplgstreamer_h
@@ -0,0 +1,167 @@
/**
* @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
@@ -0,0 +1,74 @@
/**
* @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
@@ -0,0 +1,59 @@
// 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);
// [ML] Symbols needed to make "Now playing" work in standard builds
LL_GST_SYM(true, gst_message_parse_tag, void, GstMessage *, GstTagList **);
LL_GST_SYM(true, gst_tag_list_foreach, void, const GstTagList *, GstTagForeachFunc, gpointer);
LL_GST_SYM(true, gst_tag_list_free, void, GstTagList *);
LL_GST_SYM(true, gst_tag_list_get_tag_size, guint, const GstTagList *, const gchar *);
LL_GST_SYM(true, gst_tag_list_get_value_index, const GValue *, const GstTagList *, const gchar *, guint);
// [/ML]
@@ -0,0 +1,5 @@
// required symbols to grab
LL_GST_SYM(true, gst_video_sink_get_type, GType, void);
// optional symbols to grab
@@ -0,0 +1,55 @@
/**
* @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__ */
@@ -0,0 +1,526 @@
/**
* @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*)"Firestorm",
(gchar*)"http://www.firestormviewer.org");
#undef PACKAGE
void gst_slvideo_init_class (void)
{
ll_gst_plugin_register_static (&gst_plugin_desc);
DEBUGMSG("CLASS INIT");
}
#endif // LL_GSTREAMER010_ENABLED
@@ -0,0 +1,105 @@
/**
* @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__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,49 @@
# -*- cmake -*-
project(media_plugin_gstreamer10)
include(00-Common)
include(LLCommon)
include(LLImage)
include(LLMath)
include(LLWindow)
include(Linking)
include(PluginAPI)
include(OpenGL)
include(GLIB)
include(GStreamer10Plugin)
### media_plugin_gstreamer10
if(NOT WORD_SIZE EQUAL 32)
if(NOT WINDOWS) # not windows therefore gcc LINUX and DARWIN
add_definitions(-fPIC)
endif()
endif(NOT WORD_SIZE EQUAL 32)
set(media_plugin_gstreamer10_SOURCE_FILES
media_plugin_gstreamer10.cpp
llmediaimplgstreamer_syms.cpp
)
set(media_plugin_gstreamer10_HEADER_FILES
llmediaimplgstreamer_syms.h
llmediaimplgstreamertriviallogging.h
)
add_library(media_plugin_gstreamer10
SHARED
${media_plugin_gstreamer10_SOURCE_FILES}
)
target_link_libraries(media_plugin_gstreamer10
media_plugin_base
ll::gstreamer
)
@@ -0,0 +1,53 @@
/**
* @file llmediaimplgstreamer.h
* @author Tofu Linden
* @brief implementation that supports media playback via GStreamer.
*
* @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
*/
// header guard
#ifndef llmediaimplgstreamer_h
#define llmediaimplgstreamer_h
#if LL_GSTREAMER010_ENABLED
extern "C" {
#include <stdio.h>
#include <gst/gst.h>
#include "apr_pools.h"
#include "apr_dso.h"
}
extern "C" {
gboolean llmediaimplgstreamer_bus_callback (GstBus *bus,
GstMessage *message,
gpointer data);
}
#endif // LL_GSTREAMER010_ENABLED
#endif // llmediaimplgstreamer_h
@@ -0,0 +1,197 @@
/**
* @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
*/
#include <string>
#include <iostream>
#include <vector>
#ifdef LL_WINDOWS
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0502
#include <Windows.h>
#endif
#include "linden_common.h"
extern "C" {
#include <gst/gst.h>
#include <gst/app/gstappsink.h>
}
#include "apr_pools.h"
#include "apr_dso.h"
#ifdef LL_WINDOWS
#ifndef _M_AMD64
#define GSTREAMER_REG_KEY "Software\\GStreamer1.0\\x86"
#define GSTREAMER_DIR_SUFFIX "1.0\\x86\\bin\\"
#else
#define GSTREAMER_REG_KEY "Software\\GStreamer1.0\\x86_64"
#define GSTREAMER_DIR_SUFFIX "1.0\\x86_64\\bin\\"
#endif
bool openRegKey( HKEY &aKey )
{
// Try native (32 bit view/64 bit view) of registry first.
if( ERROR_SUCCESS == ::RegOpenKeyExA( HKEY_LOCAL_MACHINE, GSTREAMER_REG_KEY, 0, KEY_QUERY_VALUE, &aKey ) )
return true;
// If native view fails, use 32 bit view or registry.
if( ERROR_SUCCESS == ::RegOpenKeyExA( HKEY_LOCAL_MACHINE, GSTREAMER_REG_KEY, 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &aKey ) )
return true;
return false;
}
std::string getGStreamerDir()
{
std::string ret;
HKEY hKey;
if( openRegKey( hKey ) )
{
DWORD dwLen(0);
::RegQueryValueExA( hKey, "InstallDir", nullptr, nullptr, nullptr, &dwLen );
if( dwLen > 0 )
{
std::vector< char > vctBuffer;
vctBuffer.resize( dwLen );
::RegQueryValueExA( hKey, "InstallDir", nullptr, nullptr, reinterpret_cast< LPBYTE>(&vctBuffer[ 0 ]), &dwLen );
ret = &vctBuffer[0];
if( ret[ dwLen-1 ] != '\\' )
ret += "\\";
ret += GSTREAMER_DIR_SUFFIX;
SetDllDirectoryA( ret.c_str() );
}
::RegCloseKey( hKey );
}
return ret;
}
#else
std::string getGStreamerDir() { return ""; }
#endif
#define LL_GST_SYM(REQ, GSTSYM, RTN, ...) RTN (*ll##GSTSYM)(__VA_ARGS__) = NULL;
#include "llmediaimplgstreamer_syms_raw.inc"
#undef LL_GST_SYM
struct Symloader
{
bool mRequired;
char const *mName;
apr_dso_handle_sym_t *mPPFunc;
};
#define LL_GST_SYM(REQ, GSTSYM, RTN, ...) { REQ, #GSTSYM , (apr_dso_handle_sym_t*)&ll##GSTSYM},
Symloader sSyms[] = {
#include "llmediaimplgstreamer_syms_raw.inc"
{ false, 0, 0 } };
#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;
std::vector< apr_dso_handle_t* > sLoadedLibraries;
bool grab_gst_syms( std::vector< std::string > const &aDSONames )
{
if (sSymsGrabbed)
return true;
//attempt to load the shared libraries
apr_pool_create(&sSymGSTDSOMemoryPool, NULL);
for( std::vector< std::string >::const_iterator itr = aDSONames.begin(); itr != aDSONames.end(); ++itr )
{
apr_dso_handle_t *pDSO(NULL);
std::string strDSO = getGStreamerDir() + *itr;
if( APR_SUCCESS == apr_dso_load( &pDSO, strDSO.c_str(), sSymGSTDSOMemoryPool ))
sLoadedLibraries.push_back( pDSO );
for( int i = 0; sSyms[ i ].mName; ++i )
{
if( !*sSyms[ i ].mPPFunc )
{
apr_dso_sym( sSyms[ i ].mPPFunc, pDSO, sSyms[ i ].mName );
}
}
}
std::stringstream strm;
bool sym_error = false;
for( int i = 0; sSyms[ i ].mName; ++i )
{
if( sSyms[ i ].mRequired && ! *sSyms[ i ].mPPFunc )
{
sym_error = true;
strm << sSyms[ i ].mName << std::endl;
}
}
sSymsGrabbed = !sym_error;
return sSymsGrabbed;
}
void ungrab_gst_syms()
{
// should be safe to call regardless of whether we've
// actually grabbed syms.
for( std::vector< apr_dso_handle_t* >::iterator itr = sLoadedLibraries.begin(); itr != sLoadedLibraries.end(); ++itr )
apr_dso_unload( *itr );
sLoadedLibraries.clear();
if ( sSymGSTDSOMemoryPool )
{
apr_pool_destroy(sSymGSTDSOMemoryPool);
sSymGSTDSOMemoryPool = NULL;
}
for( int i = 0; sSyms[ i ].mName; ++i )
*sSyms[ i ].mPPFunc = NULL;
sSymsGrabbed = false;
}
@@ -0,0 +1,68 @@
/**
* @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"
#include <vector>
extern "C" {
#include <gst/gst.h>
}
bool grab_gst_syms( std::vector< std::string > const&);
void ungrab_gst_syms();
#define LL_GST_SYM(REQ, GSTSYM, RTN, ...) extern RTN (*ll##GSTSYM)(__VA_ARGS__);
#include "llmediaimplgstreamer_syms_raw.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)))
@@ -0,0 +1,68 @@
LL_GST_SYM(true, gst_buffer_new, GstBuffer*, void)
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_caps_from_string, GstCaps *, const gchar *string)
LL_GST_SYM(true, gst_caps_get_structure, GstStructure *, const GstCaps *caps, guint index)
LL_GST_SYM(true, gst_element_register, gboolean, GstPlugin *plugin, const gchar *name, guint rank, GType type)
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, const 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, const gchar *, const GstStructure *structure)
LL_GST_SYM(true, gst_element_seek, bool, GstElement *, gdouble, GstFormat, GstSeekFlags, GstSeekType, gint64, GstSeekType, gint64)
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)
LL_GST_SYM( true, gst_message_parse_tag, void, GstMessage *, GstTagList **)
LL_GST_SYM( true, gst_tag_list_foreach, void, const GstTagList *, GstTagForeachFunc, gpointer)
LL_GST_SYM( true, gst_tag_list_get_tag_size, guint, const GstTagList *, const gchar *)
LL_GST_SYM( true, gst_tag_list_get_value_index, const GValue *, const GstTagList *, const gchar *, guint)
LL_GST_SYM( true, gst_caps_new_simple, GstCaps*, const char *, const char*, ... )
LL_GST_SYM( true, gst_sample_get_caps, GstCaps*, GstSample* )
LL_GST_SYM( true, gst_sample_get_buffer, GstBuffer*, GstSample* )
LL_GST_SYM( true, gst_buffer_map, gboolean, GstBuffer*, GstMapInfo*, GstMapFlags )
LL_GST_SYM( true, gst_buffer_unmap, void, GstBuffer*, GstMapInfo* )
LL_GST_SYM( true, gst_app_sink_set_caps, void, GstAppSink*, GstCaps const* )
LL_GST_SYM( true, gst_app_sink_pull_sample, GstSample*, GstAppSink* )
LL_GST_SYM( true, g_free, void, gpointer )
LL_GST_SYM( true, g_error_free, void, GError* )
LL_GST_SYM( true, g_main_context_pending, gboolean, GMainContext* )
LL_GST_SYM( true, g_main_loop_get_context, GMainContext*, GMainLoop* )
LL_GST_SYM( true, g_main_context_iteration, gboolean, GMainContext*, gboolean )
LL_GST_SYM( true, g_main_loop_new, GMainLoop*, GMainContext*, gboolean )
LL_GST_SYM( true, g_main_loop_quit, void, GMainLoop* )
LL_GST_SYM( true, gst_mini_object_unref, void, GstMiniObject* )
LL_GST_SYM( true, g_object_set, void, gpointer, gchar const*, ... )
LL_GST_SYM( true, g_source_remove, gboolean, guint )
LL_GST_SYM( true, g_value_get_string, gchar const*, GValue const* )
LL_GST_SYM( true, gst_debug_set_active, void, gboolean )
LL_GST_SYM( true, gst_debug_add_log_function, void, GstLogFunction, gpointer, GDestroyNotify )
LL_GST_SYM( true, gst_debug_set_default_threshold, void, GstDebugLevel )
LL_GST_SYM( true, gst_debug_message_get , gchar const*, GstDebugMessage * )
@@ -0,0 +1,980 @@
/**
* @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>
}
#include "llmediaimplgstreamer.h"
#include "llmediaimplgstreamer_syms.h"
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 ( NULL )
, mPlaybin ( NULL )
, mAppSink ( NULL )
, 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 = NULL;
gchar *debug = NULL;
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 = NULL;
gchar *debug = NULL;
llgst_message_parse_info (message, &err, &debug);
if (err)
llg_error_free (err);
llg_free (debug);
}
break;
}
case GST_MESSAGE_WARNING:
{
GError *err = NULL;
gchar *debug = NULL;
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 (NULL == mPump || NULL == mPlaybin)
{
setStatus(STATUS_ERROR);
return false; // error
}
llg_object_set (G_OBJECT (mPlaybin), "uri", urlIn.c_str(), NULL);
// 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 (NULL == mPump || NULL == 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, height;
GstStructure *pStruct = llgst_caps_get_structure ( pCaps, 0);
int res = llgst_structure_get_int ( pStruct, "width", &width);
res |= llgst_structure_get_int ( pStruct, "height", &height);
if( !mPixels )
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, NULL);
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 (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", "");
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,
NULL );
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, NULL);
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 = NULL;
}
if (mPump)
{
llg_main_loop_quit(mPump);
mPump = NULL;
}
mAppSink = NULL;
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 )
#ifndef LL_LINUX // Docu says we need G_GNUC_NO_INSTRUMENT, but GCC says 'error'
G_GNUC_NO_INSTRUMENT
#endif
{
#ifdef LL_LINUX
std::cerr << file << ":" << line << "(" << function << "): " << llgst_debug_message_get( message ) << std::endl;
#endif
}
//static
bool MediaPluginGStreamer10::startup()
{
// first - check if GStreamer is explicitly disabled
if (NULL != getenv("LL_DISABLE_GSTREAMER"))
return false;
// only do global GStreamer initialization once.
if (!mDoneInit)
{
ll_init_apr();
// Get symbols!
std::vector< std::string > vctDSONames;
#if LL_DARWIN
#elif LL_WINDOWS
vctDSONames.push_back( "libgstreamer-1.0-0.dll" );
vctDSONames.push_back( "libgstapp-1.0-0.dll" );
vctDSONames.push_back( "libglib-2.0-0.dll" );
vctDSONames.push_back( "libgobject-2.0-0.dll" );
#else // linux or other ELFy unixoid
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" );
#endif
if( !grab_gst_syms( vctDSONames ) )
{
return false;
}
if (llgst_segtrap_set_enabled)
{
llgst_segtrap_set_enabled(FALSE);
}
#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);
// _putenv_s( "GST_PLUGIN_PATH", "E:\\gstreamer\\1.0\\x86\\lib\\gstreamer-1.0" );
llgst_debug_set_default_threshold( GST_LEVEL_WARNING );
llgst_debug_add_log_function( LogFunction, NULL, NULL );
llgst_debug_set_active( false );
// 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)
{
llg_error_free(err);
}
return false;
}
mDoneInit = true;
}
return true;
}
//static
bool MediaPluginGStreamer10::closedown()
{
if (!mDoneInit)
return false; // error
ungrab_gst_syms();
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, &micro, &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 = NULL;
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
}
}
+62
View File
@@ -0,0 +1,62 @@
# -*- cmake -*-
project(media_plugin_libvlc)
include(00-Common)
include(LLCommon)
include(LLImage)
include(LLWindow)
include(Linking)
include(PluginAPI)
include(MediaPluginBase) # <FS:Zi> Linux volume catcher
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
)
add_library(media_plugin_libvlc
SHARED
${media_plugin_libvlc_SOURCE_FILES}
)
target_link_libraries(media_plugin_libvlc
media_plugin_base
ll::libvlc
)
if (WINDOWS)
set_target_properties(
media_plugin_libvlc
PROPERTIES
LINK_FLAGS "/MANIFEST:NO /NODEFAULTLIB:LIBCMT"
)
endif (WINDOWS)
if (DARWIN)
# Don't prepend 'lib' to the executable name, and don't embed a full path in the library's install name
set_target_properties(
media_plugin_libvlc
PROPERTIES
PREFIX ""
BUILD_WITH_INSTALL_RPATH 1
INSTALL_NAME_DIR "@executable_path"
LINK_FLAGS "-exported_symbols_list ${CMAKE_CURRENT_SOURCE_DIR}/../base/media_plugin_base.exp"
)
endif (DARWIN)
if( LINUX )
set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--build-id -Wl,-rpath,'$ORIGIN:$ORIGIN/../../lib'")
endif()
@@ -0,0 +1,716 @@
/**
* @file media_plugin_libvlc.cpp
* @brief LibVLC plugin for LLMedia API plugin system
*
* @cond
* $LicenseInfo:firstyear=2008&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 defined(_MSC_VER)
#include <basetsd.h>
typedef SSIZE_T ssize_t;
#endif
#include "vlc/vlc.h"
#include "vlc/libvlc_version.h"
#if LL_WINDOWS
// needed for waveOut call - see below for description
#include <mmsystem.h>
#endif
////////////////////////////////////////////////////////////////////////////////
//
class MediaPluginLibVLC :
public MediaPluginBase
{
public:
MediaPluginLibVLC(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data);
~MediaPluginLibVLC();
/*virtual*/ void receiveMessage(const char* message_string);
private:
bool init();
void initVLC();
void playMedia();
void resetVLC();
void setVolume(const F64 volume);
void setVolumeVLC();
void updateTitle(const char* title);
static void* lock(void* data, void** p_pixels);
static void unlock(void* data, void* id, void* const* raw_pixels);
static void display(void* data, void* id);
/*virtual*/ void setDirty(int left, int top, int right, int bottom) /* override, but that is not supported in gcc 4.6 */;
void setDurationDirty();
static void eventCallbacks(const libvlc_event_t* event, void* ptr);
libvlc_instance_t* mLibVLC;
libvlc_media_t* mLibVLCMedia;
libvlc_media_player_t* mLibVLCMediaPlayer;
struct mLibVLCContext
{
unsigned char* texture_pixels;
libvlc_media_player_t* mp;
MediaPluginLibVLC* parent;
};
struct mLibVLCContext mLibVLCCallbackContext;
std::string mURL;
F64 mCurVolume;
bool mIsLooping;
F64 mCurTime;
F64 mDuration;
EStatus mVlcStatus;
};
////////////////////////////////////////////////////////////////////////////////
//
MediaPluginLibVLC::MediaPluginLibVLC(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data) :
MediaPluginBase(host_send_func, host_user_data)
{
mTextureWidth = 0;
mTextureHeight = 0;
mWidth = 0;
mHeight = 0;
mDepth = 4;
mPixels = 0;
mLibVLC = 0;
mLibVLCMedia = 0;
mLibVLCMediaPlayer = 0;
mCurVolume = 0.0;
mIsLooping = false;
mCurTime = 0.0;
mDuration = 0.0;
mURL = std::string();
mVlcStatus = STATUS_NONE;
setStatus(STATUS_NONE);
}
////////////////////////////////////////////////////////////////////////////////
//
MediaPluginLibVLC::~MediaPluginLibVLC()
{
}
/////////////////////////////////////////////////////////////////////////////////
//
void* MediaPluginLibVLC::lock(void* data, void** p_pixels)
{
struct mLibVLCContext* context = (mLibVLCContext*)data;
*p_pixels = context->texture_pixels;
return NULL;
}
/////////////////////////////////////////////////////////////////////////////////
//
void MediaPluginLibVLC::unlock(void* data, void* id, void* const* raw_pixels)
{
// nothing to do here for the moment
// we *could* modify pixels here to, for example, Y flip, but this is done with
// a VLC video filter transform.
}
////////////////////////////////////////////////////////////////////////////////
//
void MediaPluginLibVLC::display(void* data, void* id)
{
struct mLibVLCContext* context = (mLibVLCContext*)data;
context->parent->setDirty(0, 0, context->parent->mWidth, context->parent->mHeight);
}
////////////////////////////////////////////////////////////////////////////////
//
void MediaPluginLibVLC::initVLC()
{
char const* vlc_argv[] =
{
"--no-xlib",
"--video-filter=transform{type=vflip}", // MAINT-6578 Y flip textures in plugin vs client
};
#if LL_DARWIN
setenv("VLC_PLUGIN_PATH", ".", 1);
#endif
int vlc_argc = sizeof(vlc_argv) / sizeof(*vlc_argv);
mLibVLC = libvlc_new(vlc_argc, vlc_argv);
if (!mLibVLC)
{
// for the moment, if this fails, the plugin will fail and
// the media sub-system will tell the viewer something went wrong.
}
}
////////////////////////////////////////////////////////////////////////////////
//
void MediaPluginLibVLC::resetVLC()
{
libvlc_media_player_stop(mLibVLCMediaPlayer);
libvlc_media_player_release(mLibVLCMediaPlayer);
libvlc_release(mLibVLC);
}
////////////////////////////////////////////////////////////////////////////////
// *virtual*
void MediaPluginLibVLC::setDirty(int left, int top, int right, int bottom)
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "updated");
message.setValueS32("left", left);
message.setValueS32("top", top);
message.setValueS32("right", right);
message.setValueS32("bottom", bottom);
message.setValueReal("current_time", mCurTime);
message.setValueReal("duration", mDuration);
message.setValueReal("current_rate", 1.0f);
sendMessage(message);
}
////////////////////////////////////////////////////////////////////////////////
// *virtual*
void MediaPluginLibVLC::setDurationDirty()
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "updated");
message.setValueReal("current_time", mCurTime);
message.setValueReal("duration", mDuration);
message.setValueReal("current_rate", 1.0f);
sendMessage(message);
}
////////////////////////////////////////////////////////////////////////////////
//
void MediaPluginLibVLC::eventCallbacks(const libvlc_event_t* event, void* ptr)
{
MediaPluginLibVLC* parent = (MediaPluginLibVLC*)ptr;
if (parent == 0)
{
return;
}
switch (event->type)
{
case libvlc_MediaPlayerOpening:
parent->mVlcStatus = STATUS_LOADING;
break;
case libvlc_MediaPlayerPlaying:
parent->mDuration = (float)(libvlc_media_get_duration(parent->mLibVLCMedia)) / 1000.0f;
parent->mVlcStatus = STATUS_PLAYING;
parent->setVolumeVLC();
parent->setDurationDirty();
break;
case libvlc_MediaPlayerPaused:
parent->mVlcStatus = STATUS_PAUSED;
break;
case libvlc_MediaPlayerStopped:
parent->mVlcStatus = STATUS_DONE;
break;
case libvlc_MediaPlayerEndReached:
parent->mVlcStatus = STATUS_DONE;
parent->mCurTime = parent->mDuration;
parent->setDurationDirty();
break;
case libvlc_MediaPlayerEncounteredError:
parent->mVlcStatus = STATUS_ERROR;
break;
case libvlc_MediaPlayerTimeChanged:
parent->mCurTime = (float)libvlc_media_player_get_time(parent->mLibVLCMediaPlayer) / 1000.0f;
if (parent->mVlcStatus == STATUS_DONE && libvlc_media_player_is_playing(parent->mLibVLCMediaPlayer))
{
parent->mVlcStatus = STATUS_PLAYING;
}
parent->setDurationDirty();
break;
case libvlc_MediaPlayerPositionChanged:
break;
case libvlc_MediaPlayerLengthChanged:
parent->mDuration = (float)libvlc_media_get_duration(parent->mLibVLCMedia) / 1000.0f;
parent->setDurationDirty();
break;
case libvlc_MediaPlayerTitleChanged:
{
char* title = libvlc_media_get_meta(parent->mLibVLCMedia, libvlc_meta_Title);
if (title)
{
parent->updateTitle(title);
}
}
break;
}
}
////////////////////////////////////////////////////////////////////////////////
//
void MediaPluginLibVLC::playMedia()
{
if (mURL.length() == 0)
{
return;
}
// A new call to play the media is received after the initial one. Typically
// this is due to a size change request either as the media naturally resizes
// to the size of the prim container, or else, as a 2D window is resized by the
// user. Stopping the media, helps avoid a race condition where the media pixel
// buffer size is out of sync with the declared size (width/height) for a frame
// or two and the plugin crashes as VLC tries to decode a frame into unallocated
// memory.
if (mLibVLCMediaPlayer)
{
libvlc_media_player_stop(mLibVLCMediaPlayer);
}
mLibVLCMedia = libvlc_media_new_location(mLibVLC, mURL.c_str());
if (!mLibVLCMedia)
{
mLibVLCMediaPlayer = 0;
setStatus(STATUS_ERROR);
return;
}
mLibVLCMediaPlayer = libvlc_media_player_new_from_media(mLibVLCMedia);
if (!mLibVLCMediaPlayer)
{
setStatus(STATUS_ERROR);
return;
}
// listen to events
libvlc_event_manager_t* em = libvlc_media_player_event_manager(mLibVLCMediaPlayer);
if (em)
{
libvlc_event_attach(em, libvlc_MediaPlayerOpening, eventCallbacks, this);
libvlc_event_attach(em, libvlc_MediaPlayerPlaying, eventCallbacks, this);
libvlc_event_attach(em, libvlc_MediaPlayerPaused, eventCallbacks, this);
libvlc_event_attach(em, libvlc_MediaPlayerStopped, eventCallbacks, this);
libvlc_event_attach(em, libvlc_MediaPlayerEndReached, eventCallbacks, this);
libvlc_event_attach(em, libvlc_MediaPlayerEncounteredError, eventCallbacks, this);
libvlc_event_attach(em, libvlc_MediaPlayerTimeChanged, eventCallbacks, this);
libvlc_event_attach(em, libvlc_MediaPlayerPositionChanged, eventCallbacks, this);
libvlc_event_attach(em, libvlc_MediaPlayerLengthChanged, eventCallbacks, this);
libvlc_event_attach(em, libvlc_MediaPlayerTitleChanged, eventCallbacks, this);
}
libvlc_video_set_callbacks(mLibVLCMediaPlayer, lock, unlock, display, &mLibVLCCallbackContext);
libvlc_video_set_format(mLibVLCMediaPlayer, "RV32", mWidth, mHeight, mWidth * mDepth);
mLibVLCCallbackContext.parent = this;
mLibVLCCallbackContext.texture_pixels = mPixels;
mLibVLCCallbackContext.mp = mLibVLCMediaPlayer;
// Send a "navigate begin" event.
// This is really a browser message but the QuickTime plugin did it and
// the media system relies on this message to update internal state so we must send it too
// Note: see "navigate_complete" message below too
// https://jira.secondlife.com/browse/MAINT-6528
LLPluginMessage message_begin(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "navigate_begin");
message_begin.setValue("uri", mURL);
message_begin.setValueBoolean("history_back_available", false);
message_begin.setValueBoolean("history_forward_available", false);
sendMessage(message_begin);
// volume level gets set before VLC is initialized (thanks media system) so we have to record
// it in mCurVolume and set it again here so that volume levels are correctly initialized
setVolume(mCurVolume);
setStatus(STATUS_LOADED);
// note this relies on the "set_loop" message arriving before the "start" (play) one
// but that appears to always be the case
if (mIsLooping)
{
libvlc_media_add_option(mLibVLCMedia, "input-repeat=65535");
}
libvlc_media_player_play(mLibVLCMediaPlayer);
// send a "location_changed" message - this informs the media system
// that a new URL is the 'current' one and is used extensively.
// Again, this is really a browser message but we will use it here.
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "location_changed");
message.setValue("uri", mURL);
sendMessage(message);
// Send a "navigate complete" event.
// This is really a browser message but the QuickTime plugin did it and
// the media system relies on this message to update internal state so we must send it too
// Note: see "navigate_begin" message above too
// https://jira.secondlife.com/browse/MAINT-6528
LLPluginMessage message_complete(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "navigate_complete");
message_complete.setValue("uri", mURL);
message_complete.setValueS32("result_code", 200);
message_complete.setValue("result_string", "OK");
sendMessage(message_complete);
}
////////////////////////////////////////////////////////////////////////////////
//
void MediaPluginLibVLC::updateTitle(const char* title)
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "name_text");
message.setValue("name", title);
sendMessage(message);
}
void MediaPluginLibVLC::setVolumeVLC()
{
if (mLibVLCMediaPlayer)
{
int vlc_vol = (int)(mCurVolume * 100);
int result = libvlc_audio_set_volume(mLibVLCMediaPlayer, vlc_vol);
if (result == 0)
{
// volume change was accepted by LibVLC
}
else
{
// volume change was NOT accepted by LibVLC and not actioned
}
#if LL_WINDOWS
// https ://jira.secondlife.com/browse/MAINT-8119
// CEF media plugin uses code in media_plugins/cef/windows_volume_catcher.cpp to
// set the actual output volume of the plugin process since there is no API in
// CEF to otherwise do this.
// There are explicit calls to change the volume in LibVLC but sometimes they
// are ignored SLPlugin.exe process volume is set to 0 so you never heard audio
// from the VLC media stream.
// The right way to solve this is to move the volume catcher stuff out of
// the CEF plugin and into it's own folder under media_plugins and have it referenced
// by both CEF and VLC. That's for later. The code there boils down to this so for
// now, as we approach a release, the less risky option is to do it directly vs
// calls to volume catcher code.
DWORD left_channel = (DWORD)(mCurVolume * 65535.0f);
DWORD right_channel = (DWORD)(mCurVolume * 65535.0f);
DWORD hw_volume = left_channel << 16 | right_channel;
waveOutSetVolume(NULL, hw_volume);
#endif
}
else
{
// volume change was requested but VLC wasn't ready.
// that's okay though because we saved the value in mCurVolume and
// the next volume change after the VLC system is initilzied will set it
}
}
////////////////////////////////////////////////////////////////////////////////
//
void MediaPluginLibVLC::setVolume(const F64 volume)
{
mCurVolume = volume;
setVolumeVLC();
}
////////////////////////////////////////////////////////////////////////////////
//
void MediaPluginLibVLC::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")
{
initVLC();
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);
std::ostringstream s;
s << "LibVLC plugin ";
s << LIBVLC_VERSION_MAJOR;
s << ".";
s << LIBVLC_VERSION_MINOR;
s << ".";
s << LIBVLC_VERSION_REVISION;
message.setValue("plugin_version", s.str());
sendMessage(message);
}
else if (message_name == "idle")
{
setStatus(mVlcStatus);
}
else if (message_name == "cleanup")
{
resetVLC();
}
else if (message_name == "force_exit")
{
mDeleteMe = true;
}
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)
{
libvlc_media_player_stop(mLibVLCMediaPlayer);
libvlc_media_player_release(mLibVLCMediaPlayer);
mLibVLCMediaPlayer = 0;
mPixels = NULL;
mTextureSegmentName.clear();
}
mSharedSegments.erase(iter);
}
else
{
//std::cerr << "MediaPluginWebKit::receiveMessage: unknown shared memory region!" << std::endl;
}
// Send the response so it can be cleaned up.
LLPluginMessage message("base", "shm_remove_response");
message.setValue("name", name);
sendMessage(message);
}
else
{
//std::cerr << "MediaPluginWebKit::receiveMessage: unknown base message: " << message_name << std::endl;
}
}
else if (message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA)
{
if (message_name == "init")
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "texture_params");
message.setValueS32("default_width", 1024);
message.setValueS32("default_height", 1024);
message.setValueS32("depth", mDepth);
message.setValueU32("internalformat", GL_RGB);
message.setValueU32("format", GL_BGRA_EXT);
message.setValueU32("type", GL_UNSIGNED_BYTE);
message.setValueBoolean("coords_opengl", true);
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");
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;
mWidth = width;
mHeight = height;
mTextureWidth = texture_width;
mTextureHeight = texture_height;
libvlc_time_t time = (libvlc_time_t)(1000.0 * mCurTime);
playMedia();
if (mLibVLCMediaPlayer)
{
libvlc_media_player_set_time(mLibVLCMediaPlayer, time);
time = libvlc_media_player_get_time(mLibVLCMediaPlayer);
if (time < 0)
{
// -1 if there is no media
mCurTime = 0;
}
else
{
mCurTime = (F64)time / 1000.0;
}
}
};
};
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);
}
else if (message_name == "load_uri")
{
mURL = message_in.getValue("uri");
playMedia();
}
}
else
if (message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME)
{
if (message_name == "stop")
{
if (mLibVLCMediaPlayer)
{
libvlc_media_player_stop(mLibVLCMediaPlayer);
}
}
else if (message_name == "start")
{
if (mLibVLCMediaPlayer)
{
if (mVlcStatus == STATUS_DONE && !libvlc_media_player_is_playing(mLibVLCMediaPlayer))
{
// stop or vlc will ignore 'play', it will just
// make an MediaPlayerEndReached event even if
// seek was used
libvlc_media_player_stop(mLibVLCMediaPlayer);
}
libvlc_media_player_play(mLibVLCMediaPlayer);
}
}
else if (message_name == "pause")
{
if (mLibVLCMediaPlayer)
{
libvlc_media_player_set_pause(mLibVLCMediaPlayer, 1);
}
}
else if (message_name == "seek")
{
if (mLibVLCMediaPlayer)
{
libvlc_time_t time = (libvlc_time_t)(1000.0 * message_in.getValueReal("time"));
libvlc_media_player_set_time(mLibVLCMediaPlayer, time);
time = libvlc_media_player_get_time(mLibVLCMediaPlayer);
if (time < 0)
{
// -1 if there is no media
mCurTime = 0;
}
else
{
mCurTime = (F64)time / 1000.0;
}
if (!libvlc_media_player_is_playing(mLibVLCMediaPlayer))
{
// if paused, won't trigger update, update now
setDurationDirty();
}
}
}
else if (message_name == "set_loop")
{
bool loop = message_in.getValueBoolean("loop");
mIsLooping = loop;
}
else if (message_name == "set_volume")
{
// volume comes in 0 -> 1.0
F64 volume = message_in.getValueReal("volume");
setVolume(volume);
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
//
bool MediaPluginLibVLC::init()
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "name_text");
message.setValue("name", "LibVLC Plugin");
sendMessage(message);
return true;
};
////////////////////////////////////////////////////////////////////////////////
//
int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func,
void* host_user_data,
LLPluginInstance::sendMessageFunction *plugin_send_func,
void **plugin_user_data)
{
MediaPluginLibVLC* self = new MediaPluginLibVLC(host_send_func, host_user_data);
*plugin_send_func = MediaPluginLibVLC::staticReceiveMessage;
*plugin_user_data = (void*)self;
return 0;
}