PageRenderTime 135ms CodeModel.GetById 47ms app.highlight 79ms RepoModel.GetById 1ms app.codeStats 1ms

/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 1266 lines | 940 code | 175 blank | 151 comment | 168 complexity | cdb98e3ccfa05ac3cb26c84b93afd6c9 MD5 | raw file
   1/** 
   2 * @file media_plugin_gstreamer010.cpp
   3 * @brief GStreamer-0.10 plugin for LLMedia API plugin system
   4 *
   5 * @cond
   6 * $LicenseInfo:firstyear=2007&license=viewerlgpl$
   7 * Second Life Viewer Source Code
   8 * Copyright (C) 2010, Linden Research, Inc.
   9 * 
  10 * This library is free software; you can redistribute it and/or
  11 * modify it under the terms of the GNU Lesser General Public
  12 * License as published by the Free Software Foundation;
  13 * version 2.1 of the License only.
  14 * 
  15 * This library is distributed in the hope that it will be useful,
  16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  18 * Lesser General Public License for more details.
  19 * 
  20 * You should have received a copy of the GNU Lesser General Public
  21 * License along with this library; if not, write to the Free Software
  22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  23 * 
  24 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
  25 * $/LicenseInfo$
  26 * @endcond
  27 */
  28
  29#include "linden_common.h"
  30
  31#include "llgl.h"
  32
  33#include "llplugininstance.h"
  34#include "llpluginmessage.h"
  35#include "llpluginmessageclasses.h"
  36#include "media_plugin_base.h"
  37
  38#if LL_GSTREAMER010_ENABLED
  39
  40extern "C" {
  41#include <gst/gst.h>
  42}
  43
  44#include "llmediaimplgstreamer.h"
  45#include "llmediaimplgstreamertriviallogging.h"
  46
  47#include "llmediaimplgstreamervidplug.h"
  48
  49#include "llmediaimplgstreamer_syms.h"
  50
  51//////////////////////////////////////////////////////////////////////////////
  52//
  53class MediaPluginGStreamer010 : public MediaPluginBase
  54{
  55public:
  56	MediaPluginGStreamer010(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data);
  57	~MediaPluginGStreamer010();
  58
  59	/* virtual */ void receiveMessage(const char *message_string);
  60
  61	static bool startup();
  62	static bool closedown();
  63
  64	gboolean processGSTEvents(GstBus     *bus,
  65				  GstMessage *message);
  66
  67private:
  68	std::string getVersion();
  69	bool navigateTo( const std::string urlIn );
  70	bool seek( double time_sec );
  71	bool setVolume( float volume );
  72	
  73	// misc
  74	bool pause();
  75	bool stop();
  76	bool play(double rate);
  77	bool getTimePos(double &sec_out);
  78
  79	static const double MIN_LOOP_SEC = 1.0F;
  80
  81	bool mIsLooping;
  82
  83	enum ECommand {
  84		COMMAND_NONE,
  85		COMMAND_STOP,
  86		COMMAND_PLAY,
  87		COMMAND_FAST_FORWARD,
  88		COMMAND_FAST_REWIND,
  89		COMMAND_PAUSE,
  90		COMMAND_SEEK,
  91	};
  92	ECommand mCommand;
  93
  94private:
  95	bool unload();
  96	bool load();
  97
  98	bool update(int milliseconds);
  99        void mouseDown( int x, int y );
 100        void mouseUp( int x, int y );
 101        void mouseMove( int x, int y );
 102
 103        void sizeChanged();
 104	
 105	static bool mDoneInit;
 106	
 107	guint mBusWatchID;
 108	
 109	float mVolume;
 110
 111	int mDepth;
 112
 113	// media NATURAL size
 114	int mNaturalWidth;
 115	int mNaturalHeight;
 116	// media current size
 117	int mCurrentWidth;
 118	int mCurrentHeight;
 119	int mCurrentRowbytes;
 120	  // previous media size so we can detect changes
 121	  int mPreviousWidth;
 122	  int mPreviousHeight;
 123	// desired render size from host
 124	int mWidth;
 125	int mHeight;
 126	// padded texture size we need to write into
 127	int mTextureWidth;
 128	int mTextureHeight;
 129	
 130	int mTextureFormatPrimary;
 131	int mTextureFormatType;
 132
 133	bool mSeekWanted;
 134	double mSeekDestination;
 135	
 136	// Very GStreamer-specific
 137	GMainLoop *mPump; // event pump for this media
 138	GstElement *mPlaybin;
 139	GstElement *mVisualizer;
 140	GstSLVideo *mVideoSink;
 141};
 142
 143//static
 144bool MediaPluginGStreamer010::mDoneInit = false;
 145
 146MediaPluginGStreamer010::MediaPluginGStreamer010(
 147	LLPluginInstance::sendMessageFunction host_send_func,
 148	void *host_user_data ) :
 149	MediaPluginBase(host_send_func, host_user_data),
 150	mBusWatchID ( 0 ),
 151	mCurrentRowbytes ( 4 ),
 152	mTextureFormatPrimary ( GL_RGBA ),
 153	mTextureFormatType ( GL_UNSIGNED_INT_8_8_8_8_REV ),
 154	mSeekWanted(false),
 155	mSeekDestination(0.0),
 156	mPump ( NULL ),
 157	mPlaybin ( NULL ),
 158	mVisualizer ( NULL ),
 159	mVideoSink ( NULL ),
 160	mCommand ( COMMAND_NONE )
 161{
 162	std::ostringstream str;
 163	INFOMSG("MediaPluginGStreamer010 constructor - my PID=%u", U32(getpid()));
 164}
 165
 166///////////////////////////////////////////////////////////////////////////////
 167//
 168//#define LL_GST_REPORT_STATE_CHANGES
 169#ifdef LL_GST_REPORT_STATE_CHANGES
 170static char* get_gst_state_name(GstState state)
 171{
 172	switch (state) {
 173	case GST_STATE_VOID_PENDING: return "VOID_PENDING";
 174	case GST_STATE_NULL: return "NULL";
 175	case GST_STATE_READY: return "READY";
 176	case GST_STATE_PAUSED: return "PAUSED";
 177	case GST_STATE_PLAYING: return "PLAYING";
 178	}
 179	return "(unknown)";
 180}
 181#endif // LL_GST_REPORT_STATE_CHANGES
 182
 183gboolean
 184MediaPluginGStreamer010::processGSTEvents(GstBus     *bus,
 185					  GstMessage *message)
 186{
 187	if (!message) 
 188		return TRUE; // shield against GStreamer bug
 189
 190	if (GST_MESSAGE_TYPE(message) != GST_MESSAGE_STATE_CHANGED &&
 191	    GST_MESSAGE_TYPE(message) != GST_MESSAGE_BUFFERING)
 192	{
 193		DEBUGMSG("Got GST message type: %s",
 194			LLGST_MESSAGE_TYPE_NAME (message));
 195	}
 196	else
 197	{
 198		// TODO: grok 'duration' message type
 199		DEBUGMSG("Got GST message type: %s",
 200			 LLGST_MESSAGE_TYPE_NAME (message));
 201	}
 202
 203	switch (GST_MESSAGE_TYPE (message)) {
 204	case GST_MESSAGE_BUFFERING: {
 205		// NEEDS GST 0.10.11+
 206		if (llgst_message_parse_buffering)
 207		{
 208			gint percent = 0;
 209			llgst_message_parse_buffering(message, &percent);
 210			DEBUGMSG("GST buffering: %d%%", percent);
 211		}
 212		break;
 213	}
 214	case GST_MESSAGE_STATE_CHANGED: {
 215		GstState old_state;
 216		GstState new_state;
 217		GstState pending_state;
 218		llgst_message_parse_state_changed(message,
 219						&old_state,
 220						&new_state,
 221						&pending_state);
 222#ifdef LL_GST_REPORT_STATE_CHANGES
 223		// not generally very useful, and rather spammy.
 224		DEBUGMSG("state change (old,<new>,pending): %s,<%s>,%s",
 225			 get_gst_state_name(old_state),
 226			 get_gst_state_name(new_state),
 227			 get_gst_state_name(pending_state));
 228#endif // LL_GST_REPORT_STATE_CHANGES
 229
 230		switch (new_state) {
 231		case GST_STATE_VOID_PENDING:
 232			break;
 233		case GST_STATE_NULL:
 234			break;
 235		case GST_STATE_READY:
 236			setStatus(STATUS_LOADED);
 237			break;
 238		case GST_STATE_PAUSED:
 239			setStatus(STATUS_PAUSED);
 240			break;
 241		case GST_STATE_PLAYING:
 242			setStatus(STATUS_PLAYING);
 243			break;
 244		}
 245		break;
 246	}
 247	case GST_MESSAGE_ERROR: {
 248		GError *err = NULL;
 249		gchar *debug = NULL;
 250
 251		llgst_message_parse_error (message, &err, &debug);
 252		WARNMSG("GST error: %s", err?err->message:"(unknown)");
 253		if (err)
 254			g_error_free (err);
 255		g_free (debug);
 256
 257		mCommand = COMMAND_STOP;
 258
 259		setStatus(STATUS_ERROR);
 260
 261		break;
 262	}
 263	case GST_MESSAGE_INFO: {
 264		if (llgst_message_parse_info)
 265		{
 266			GError *err = NULL;
 267			gchar *debug = NULL;
 268			
 269			llgst_message_parse_info (message, &err, &debug);
 270			INFOMSG("GST info: %s", err?err->message:"(unknown)");
 271			if (err)
 272				g_error_free (err);
 273			g_free (debug);
 274		}
 275		break;
 276	}
 277	case GST_MESSAGE_WARNING: {
 278		GError *err = NULL;
 279		gchar *debug = NULL;
 280
 281		llgst_message_parse_warning (message, &err, &debug);
 282		WARNMSG("GST warning: %s", err?err->message:"(unknown)");
 283		if (err)
 284			g_error_free (err);
 285		g_free (debug);
 286
 287		break;
 288	}
 289	case GST_MESSAGE_EOS:
 290		/* end-of-stream */
 291		DEBUGMSG("GST end-of-stream.");
 292		if (mIsLooping)
 293		{
 294			DEBUGMSG("looping media...");
 295			double eos_pos_sec = 0.0F;
 296			bool got_eos_position = getTimePos(eos_pos_sec);
 297
 298			if (got_eos_position && eos_pos_sec < MIN_LOOP_SEC)
 299			{
 300				// if we know that the movie is really short, don't
 301				// loop it else it can easily become a time-hog
 302				// because of GStreamer spin-up overhead
 303				DEBUGMSG("really short movie (%0.3fsec) - not gonna loop this, pausing instead.", eos_pos_sec);
 304				// inject a COMMAND_PAUSE
 305				mCommand = COMMAND_PAUSE;
 306			}
 307			else
 308			{
 309#undef LLGST_LOOP_BY_SEEKING
 310// loop with a stop-start instead of a seek, because it actually seems rather
 311// faster than seeking on remote streams.
 312#ifdef LLGST_LOOP_BY_SEEKING
 313				// first, try looping by an explicit rewind
 314				bool seeksuccess = seek(0.0);
 315				if (seeksuccess)
 316				{
 317					play(1.0);
 318				}
 319				else
 320#endif // LLGST_LOOP_BY_SEEKING
 321				{  // use clumsy stop-start to loop
 322					DEBUGMSG("didn't loop by rewinding - stopping and starting instead...");
 323					stop();
 324					play(1.0);
 325				}
 326			}
 327		}
 328		else // not a looping media
 329		{
 330			// inject a COMMAND_STOP
 331			mCommand = COMMAND_STOP;
 332		}
 333		break;
 334	default:
 335		/* unhandled message */
 336		break;
 337	}
 338
 339	/* we want to be notified again the next time there is a message
 340	 * on the bus, so return true (false means we want to stop watching
 341	 * for messages on the bus and our callback should not be called again)
 342	 */
 343	return TRUE;
 344}
 345
 346extern "C" {
 347gboolean
 348llmediaimplgstreamer_bus_callback (GstBus     *bus,
 349				   GstMessage *message,
 350				   gpointer    data)
 351{
 352	MediaPluginGStreamer010 *impl = (MediaPluginGStreamer010*)data;
 353	return impl->processGSTEvents(bus, message);
 354}
 355} // extern "C"
 356
 357
 358
 359bool
 360MediaPluginGStreamer010::navigateTo ( const std::string urlIn )
 361{
 362	if (!mDoneInit)
 363		return false; // error
 364
 365	setStatus(STATUS_LOADING);
 366
 367	DEBUGMSG("Setting media URI: %s", urlIn.c_str());
 368
 369	mSeekWanted = false;
 370
 371	if (NULL == mPump ||
 372	    NULL == mPlaybin)
 373	{
 374		setStatus(STATUS_ERROR);
 375		return false; // error
 376	}
 377
 378	// set URI
 379	g_object_set (G_OBJECT (mPlaybin), "uri", urlIn.c_str(), NULL);
 380	//g_object_set (G_OBJECT (mPlaybin), "uri", "file:///tmp/movie", NULL);
 381
 382	// navigateTo implicitly plays, too.
 383	play(1.0);
 384
 385	return true;
 386}
 387
 388
 389bool
 390MediaPluginGStreamer010::update(int milliseconds)
 391{
 392	if (!mDoneInit)
 393		return false; // error
 394
 395	DEBUGMSG("updating media...");
 396	
 397	// sanity check
 398	if (NULL == mPump ||
 399	    NULL == mPlaybin)
 400	{
 401		DEBUGMSG("dead media...");
 402		return false;
 403	}
 404
 405	// see if there's an outstanding seek wanted
 406	if (mSeekWanted &&
 407	    // bleh, GST has to be happy that the movie is really truly playing
 408	    // or it may quietly ignore the seek (with rtsp:// at least).
 409	    (GST_STATE(mPlaybin) == GST_STATE_PLAYING))
 410	{
 411		seek(mSeekDestination);
 412		mSeekWanted = false;
 413	}
 414
 415	// *TODO: time-limit - but there isn't a lot we can do here, most
 416	// time is spent in gstreamer's own opaque worker-threads.  maybe
 417	// we can do something sneaky like only unlock the video object
 418	// for 'milliseconds' and otherwise hold the lock.
 419	while (g_main_context_pending(g_main_loop_get_context(mPump)))
 420	{
 421	       g_main_context_iteration(g_main_loop_get_context(mPump), FALSE);
 422	}
 423
 424	// check for availability of a new frame
 425	
 426	if (mVideoSink)
 427	{
 428	        GST_OBJECT_LOCK(mVideoSink);
 429		if (mVideoSink->retained_frame_ready)
 430		{
 431			DEBUGMSG("NEW FRAME READY");
 432
 433			if (mVideoSink->retained_frame_width != mCurrentWidth ||
 434			    mVideoSink->retained_frame_height != mCurrentHeight)
 435				// *TODO: also check for change in format
 436			{
 437				// just resize container, don't consume frame
 438				int neww = mVideoSink->retained_frame_width;
 439				int newh = mVideoSink->retained_frame_height;
 440
 441				int newd = 4;
 442				mTextureFormatPrimary = GL_RGBA;
 443				mTextureFormatType = GL_UNSIGNED_INT_8_8_8_8_REV;
 444
 445				/*
 446				int newd = SLVPixelFormatBytes[mVideoSink->retained_frame_format];
 447				if (SLV_PF_BGRX == mVideoSink->retained_frame_format)
 448				{
 449					mTextureFormatPrimary = GL_BGRA;
 450					mTextureFormatType = GL_UNSIGNED_INT_8_8_8_8_REV;
 451				}
 452				else
 453				{
 454					mTextureFormatPrimary = GL_RGBA;
 455					mTextureFormatType = GL_UNSIGNED_INT_8_8_8_8_REV;
 456				}
 457				*/
 458
 459				GST_OBJECT_UNLOCK(mVideoSink);
 460
 461				mCurrentRowbytes = neww * newd;
 462				DEBUGMSG("video container resized to %dx%d",
 463					 neww, newh);
 464
 465				mDepth = newd;
 466				mCurrentWidth = neww;
 467				mCurrentHeight = newh;
 468				sizeChanged();
 469				return true;
 470			}
 471
 472			if (mPixels &&
 473			    mCurrentHeight <= mHeight &&
 474			    mCurrentWidth <= mWidth &&
 475			    !mTextureSegmentName.empty())
 476			{
 477				// we're gonna totally consume this frame - reset 'ready' flag
 478				mVideoSink->retained_frame_ready = FALSE;
 479				int destination_rowbytes = mWidth * mDepth;
 480				for (int row=0; row<mCurrentHeight; ++row)
 481				{
 482					memcpy(&mPixels
 483					        [destination_rowbytes * row],
 484					       &mVideoSink->retained_frame_data
 485					        [mCurrentRowbytes * row],
 486					       mCurrentRowbytes);
 487				}
 488
 489				GST_OBJECT_UNLOCK(mVideoSink);
 490				DEBUGMSG("NEW FRAME REALLY TRULY CONSUMED, TELLING HOST");
 491
 492				setDirty(0,0,mCurrentWidth,mCurrentHeight);
 493			}
 494			else
 495			{
 496				// new frame ready, but we're not ready to
 497				// consume it.
 498
 499				GST_OBJECT_UNLOCK(mVideoSink);
 500
 501				DEBUGMSG("NEW FRAME not consumed, still waiting for a shm segment and/or shm resize");
 502			}
 503
 504			return true;
 505		}
 506		else
 507		{
 508			// nothing to do yet.
 509			GST_OBJECT_UNLOCK(mVideoSink);
 510			return true;
 511		}
 512	}
 513
 514	return true;
 515}
 516
 517
 518void
 519MediaPluginGStreamer010::mouseDown( int x, int y )
 520{
 521  // do nothing
 522}
 523
 524void
 525MediaPluginGStreamer010::mouseUp( int x, int y )
 526{
 527  // do nothing
 528}
 529
 530void
 531MediaPluginGStreamer010::mouseMove( int x, int y )
 532{
 533  // do nothing
 534}
 535
 536
 537bool
 538MediaPluginGStreamer010::pause()
 539{
 540	DEBUGMSG("pausing media...");
 541	// todo: error-check this?
 542	if (mDoneInit && mPlaybin)
 543	{
 544		llgst_element_set_state(mPlaybin, GST_STATE_PAUSED);
 545		return true;
 546	}
 547	return false;
 548}
 549
 550bool
 551MediaPluginGStreamer010::stop()
 552{
 553	DEBUGMSG("stopping media...");
 554	// todo: error-check this?
 555	if (mDoneInit && mPlaybin)
 556	{
 557		llgst_element_set_state(mPlaybin, GST_STATE_READY);
 558		return true;
 559	}
 560	return false;
 561}
 562
 563bool
 564MediaPluginGStreamer010::play(double rate)
 565{
 566	// NOTE: we don't actually support non-natural rate.
 567
 568        DEBUGMSG("playing media... rate=%f", rate);
 569	// todo: error-check this?
 570	if (mDoneInit && mPlaybin)
 571	{
 572		llgst_element_set_state(mPlaybin, GST_STATE_PLAYING);
 573		return true;
 574	}
 575	return false;
 576}
 577
 578bool
 579MediaPluginGStreamer010::setVolume( float volume )
 580{
 581	// we try to only update volume as conservatively as
 582	// possible, as many gst-plugins-base versions up to at least
 583	// November 2008 have critical race-conditions in setting volume - sigh
 584	if (mVolume == volume)
 585		return true; // nothing to do, everything's fine
 586
 587	mVolume = volume;
 588	if (mDoneInit && mPlaybin)
 589	{
 590		g_object_set(mPlaybin, "volume", mVolume, NULL);
 591		return true;
 592	}
 593
 594	return false;
 595}
 596
 597bool
 598MediaPluginGStreamer010::seek(double time_sec)
 599{
 600	bool success = false;
 601	if (mDoneInit && mPlaybin)
 602	{
 603		success = llgst_element_seek(mPlaybin, 1.0F, GST_FORMAT_TIME,
 604				GstSeekFlags(GST_SEEK_FLAG_FLUSH |
 605					     GST_SEEK_FLAG_KEY_UNIT),
 606				GST_SEEK_TYPE_SET, gint64(time_sec*GST_SECOND),
 607				GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
 608	}
 609	DEBUGMSG("MEDIA SEEK REQUEST to %fsec result was %d",
 610		 float(time_sec), int(success));
 611	return success;
 612}
 613
 614bool
 615MediaPluginGStreamer010::getTimePos(double &sec_out)
 616{
 617	bool got_position = false;
 618	if (mDoneInit && mPlaybin)
 619	{
 620		gint64 pos;
 621		GstFormat timefmt = GST_FORMAT_TIME;
 622		got_position =
 623			llgst_element_query_position &&
 624			llgst_element_query_position(mPlaybin,
 625						     &timefmt,
 626						     &pos);
 627		got_position = got_position
 628			&& (timefmt == GST_FORMAT_TIME);
 629		// GStreamer may have other ideas, but we consider the current position
 630		// undefined if not PLAYING or PAUSED
 631		got_position = got_position &&
 632			(GST_STATE(mPlaybin) == GST_STATE_PLAYING ||
 633			 GST_STATE(mPlaybin) == GST_STATE_PAUSED);
 634		if (got_position && !GST_CLOCK_TIME_IS_VALID(pos))
 635		{
 636			if (GST_STATE(mPlaybin) == GST_STATE_PLAYING)
 637			{
 638				// if we're playing then we treat an invalid clock time
 639				// as 0, for complicated reasons (insert reason here)
 640				pos = 0;
 641			}
 642			else
 643			{
 644				got_position = false;
 645			}
 646			
 647		}
 648		// If all the preconditions succeeded... we can trust the result.
 649		if (got_position)
 650		{
 651			sec_out = double(pos) / double(GST_SECOND); // gst to sec
 652		}
 653	}
 654	return got_position;
 655}
 656
 657bool
 658MediaPluginGStreamer010::load()
 659{
 660	if (!mDoneInit)
 661		return false; // error
 662
 663	setStatus(STATUS_LOADING);
 664
 665	DEBUGMSG("setting up media...");
 666
 667	mIsLooping = false;
 668	mVolume = 0.1234567; // minor hack to force an initial volume update
 669
 670	// Create a pumpable main-loop for this media
 671	mPump = g_main_loop_new (NULL, FALSE);
 672	if (!mPump)
 673	{
 674		setStatus(STATUS_ERROR);
 675		return false; // error
 676	}
 677
 678	// instantiate a playbin element to do the hard work
 679	mPlaybin = llgst_element_factory_make ("playbin", "play");
 680	if (!mPlaybin)
 681	{
 682		setStatus(STATUS_ERROR);
 683		return false; // error
 684	}
 685
 686	// get playbin's bus
 687	GstBus *bus = llgst_pipeline_get_bus (GST_PIPELINE (mPlaybin));
 688	if (!bus)
 689	{
 690		setStatus(STATUS_ERROR);
 691		return false; // error
 692	}
 693	mBusWatchID = llgst_bus_add_watch (bus,
 694					   llmediaimplgstreamer_bus_callback,
 695					   this);
 696	llgst_object_unref (bus);
 697
 698#if 0 // not quite stable/correct yet
 699	// get a visualizer element (bonus feature!)
 700	char* vis_name = getenv("LL_GST_VIS_NAME");
 701	if (!vis_name ||
 702	    (vis_name && std::string(vis_name)!="none"))
 703	{
 704		if (vis_name)
 705		{
 706			mVisualizer = llgst_element_factory_make (vis_name, "vis");
 707		}
 708		if (!mVisualizer)
 709		{
 710			mVisualizer = llgst_element_factory_make ("libvisual_jess", "vis");
 711			if (!mVisualizer)
 712			{
 713				mVisualizer = llgst_element_factory_make ("goom", "vis");
 714				if (!mVisualizer)
 715				{
 716					mVisualizer = llgst_element_factory_make ("libvisual_lv_scope", "vis");
 717					if (!mVisualizer)
 718					{
 719						// That's okay, we don't NEED this.
 720					}
 721				}
 722			}
 723		}
 724	}
 725#endif
 726
 727	if (NULL == getenv("LL_GSTREAMER_EXTERNAL")) {
 728		// instantiate a custom video sink
 729		mVideoSink =
 730			GST_SLVIDEO(llgst_element_factory_make ("private-slvideo", "slvideo"));
 731		if (!mVideoSink)
 732		{
 733			WARNMSG("Could not instantiate private-slvideo element.");
 734			// todo: cleanup.
 735			setStatus(STATUS_ERROR);
 736			return false; // error
 737		}
 738
 739		// connect the pieces
 740		g_object_set(mPlaybin, "video-sink", mVideoSink, NULL);
 741	}
 742
 743	if (mVisualizer)
 744	{
 745		g_object_set(mPlaybin, "vis-plugin", mVisualizer, NULL);
 746	}
 747
 748	return true;
 749}
 750
 751bool
 752MediaPluginGStreamer010::unload ()
 753{
 754	if (!mDoneInit)
 755		return false; // error
 756
 757	DEBUGMSG("unloading media...");
 758	
 759	// stop getting callbacks for this bus
 760	g_source_remove(mBusWatchID);
 761	mBusWatchID = 0;
 762
 763	if (mPlaybin)
 764	{
 765		llgst_element_set_state (mPlaybin, GST_STATE_NULL);
 766		llgst_object_unref (GST_OBJECT (mPlaybin));
 767		mPlaybin = NULL;
 768	}
 769
 770	if (mVisualizer)
 771	{
 772		llgst_object_unref (GST_OBJECT (mVisualizer));
 773		mVisualizer = NULL;
 774	}
 775
 776	if (mPump)
 777	{
 778		g_main_loop_quit(mPump);
 779		mPump = NULL;
 780	}
 781
 782	mVideoSink = NULL;
 783
 784	setStatus(STATUS_NONE);
 785
 786	return true;
 787}
 788
 789
 790//static
 791bool
 792MediaPluginGStreamer010::startup()
 793{
 794	// first - check if GStreamer is explicitly disabled
 795	if (NULL != getenv("LL_DISABLE_GSTREAMER"))
 796		return false;
 797
 798	// only do global GStreamer initialization once.
 799	if (!mDoneInit)
 800	{
 801		g_thread_init(NULL);
 802
 803		// Init the glib type system - we need it.
 804		g_type_init();
 805
 806		// Get symbols!
 807#if LL_DARWIN
 808		if (! grab_gst_syms("libgstreamer-0.10.dylib",
 809				    "libgstvideo-0.10.dylib") )
 810#elseif LL_WINDOWS
 811		if (! grab_gst_syms("libgstreamer-0.10.dll",
 812				    "libgstvideo-0.10.dll") )
 813#else // linux or other ELFy unixoid
 814		if (! grab_gst_syms("libgstreamer-0.10.so.0",
 815				    "libgstvideo-0.10.so.0") )
 816#endif
 817		{
 818			WARNMSG("Couldn't find suitable GStreamer 0.10 support on this system - video playback disabled.");
 819			return false;
 820		}
 821
 822		if (llgst_segtrap_set_enabled)
 823		{
 824			llgst_segtrap_set_enabled(FALSE);
 825		}
 826		else
 827		{
 828			WARNMSG("gst_segtrap_set_enabled() is not available; plugin crashes won't be caught.");
 829		}
 830
 831#if LL_LINUX
 832		// Gstreamer tries a fork during init, waitpid-ing on it,
 833		// which conflicts with any installed SIGCHLD handler...
 834		struct sigaction tmpact, oldact;
 835		if (llgst_registry_fork_set_enabled) {
 836			// if we can disable SIGCHLD-using forking behaviour,
 837			// do it.
 838			llgst_registry_fork_set_enabled(false);
 839		}
 840		else {
 841			// else temporarily install default SIGCHLD handler
 842			// while GStreamer initialises
 843			tmpact.sa_handler = SIG_DFL;
 844			sigemptyset( &tmpact.sa_mask );
 845			tmpact.sa_flags = SA_SIGINFO;
 846			sigaction(SIGCHLD, &tmpact, &oldact);
 847		}
 848#endif // LL_LINUX
 849
 850		// Protect against GStreamer resetting the locale, yuck.
 851		static std::string saved_locale;
 852		saved_locale = setlocale(LC_ALL, NULL);
 853
 854		// finally, try to initialize GStreamer!
 855		GError *err = NULL;
 856		gboolean init_gst_success = llgst_init_check(NULL, NULL, &err);
 857
 858		// restore old locale
 859		setlocale(LC_ALL, saved_locale.c_str() );
 860
 861#if LL_LINUX
 862		// restore old SIGCHLD handler
 863		if (!llgst_registry_fork_set_enabled)
 864			sigaction(SIGCHLD, &oldact, NULL);
 865#endif // LL_LINUX
 866
 867		if (!init_gst_success) // fail
 868		{
 869			if (err)
 870			{
 871				WARNMSG("GST init failed: %s", err->message);
 872				g_error_free(err);
 873			}
 874			else
 875			{
 876				WARNMSG("GST init failed for unspecified reason.");
 877			}
 878			return false;
 879		}
 880		
 881		// Init our custom plugins - only really need do this once.
 882		gst_slvideo_init_class();
 883
 884		mDoneInit = true;
 885	}
 886
 887	return true;
 888}
 889
 890
 891void
 892MediaPluginGStreamer010::sizeChanged()
 893{
 894	// the shared writing space has possibly changed size/location/whatever
 895
 896	// Check to see whether the movie's NATURAL size has been set yet
 897	if (1 == mNaturalWidth &&
 898	    1 == mNaturalHeight)
 899	{
 900		mNaturalWidth = mCurrentWidth;
 901		mNaturalHeight = mCurrentHeight;
 902		DEBUGMSG("Media NATURAL size better detected as %dx%d",
 903			 mNaturalWidth, mNaturalHeight);
 904	}
 905
 906	// if the size has changed then the shm has changed and the app needs telling
 907	if (mCurrentWidth != mPreviousWidth ||
 908	    mCurrentHeight != mPreviousHeight)
 909	{
 910		mPreviousWidth = mCurrentWidth;
 911		mPreviousHeight = mCurrentHeight;
 912
 913		LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_request");
 914		message.setValue("name", mTextureSegmentName);
 915		message.setValueS32("width", mNaturalWidth);
 916		message.setValueS32("height", mNaturalHeight);
 917		DEBUGMSG("<--- Sending size change request to application with name: '%s' - natural size is %d x %d", mTextureSegmentName.c_str(), mNaturalWidth, mNaturalHeight);
 918		sendMessage(message);
 919	}
 920}
 921
 922
 923
 924//static
 925bool
 926MediaPluginGStreamer010::closedown()
 927{
 928	if (!mDoneInit)
 929		return false; // error
 930
 931	ungrab_gst_syms();
 932
 933	mDoneInit = false;
 934
 935	return true;
 936}
 937
 938MediaPluginGStreamer010::~MediaPluginGStreamer010()
 939{
 940	DEBUGMSG("MediaPluginGStreamer010 destructor");
 941
 942	closedown();
 943
 944	DEBUGMSG("GStreamer010 closing down");
 945}
 946
 947
 948std::string
 949MediaPluginGStreamer010::getVersion()
 950{
 951	std::string plugin_version = "GStreamer010 media plugin, GStreamer version ";
 952	if (mDoneInit &&
 953	    llgst_version)
 954	{
 955		guint major, minor, micro, nano;
 956		llgst_version(&major, &minor, &micro, &nano);
 957		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);
 958	}
 959	else
 960	{
 961		plugin_version += "(unknown)";
 962	}
 963	return plugin_version;
 964}
 965
 966void MediaPluginGStreamer010::receiveMessage(const char *message_string)
 967{
 968	//std::cerr << "MediaPluginGStreamer010::receiveMessage: received message: \"" << message_string << "\"" << std::endl;
 969
 970	LLPluginMessage message_in;
 971
 972	if(message_in.parse(message_string) >= 0)
 973	{
 974		std::string message_class = message_in.getClass();
 975		std::string message_name = message_in.getName();
 976		if(message_class == LLPLUGIN_MESSAGE_CLASS_BASE)
 977		{
 978			if(message_name == "init")
 979			{
 980				LLPluginMessage message("base", "init_response");
 981				LLSD versions = LLSD::emptyMap();
 982				versions[LLPLUGIN_MESSAGE_CLASS_BASE] = LLPLUGIN_MESSAGE_CLASS_BASE_VERSION;
 983				versions[LLPLUGIN_MESSAGE_CLASS_MEDIA] = LLPLUGIN_MESSAGE_CLASS_MEDIA_VERSION;
 984				versions[LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME] = LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME_VERSION;
 985				message.setValueLLSD("versions", versions);
 986
 987				if ( load() )
 988				{
 989					DEBUGMSG("GStreamer010 media instance set up");
 990				}
 991				else
 992				{
 993					WARNMSG("GStreamer010 media instance failed to set up");
 994				}
 995
 996				message.setValue("plugin_version", getVersion());
 997				sendMessage(message);
 998			}
 999			else if(message_name == "idle")
1000			{
1001				// no response is necessary here.
1002				double time = message_in.getValueReal("time");
1003				
1004				// Convert time to milliseconds for update()
1005				update((int)(time * 1000.0f));
1006			}
1007			else if(message_name == "cleanup")
1008			{
1009				unload();
1010				closedown();
1011			}
1012			else if(message_name == "shm_added")
1013			{
1014				SharedSegmentInfo info;
1015				info.mAddress = message_in.getValuePointer("address");
1016				info.mSize = (size_t)message_in.getValueS32("size");
1017				std::string name = message_in.getValue("name");
1018
1019				std::ostringstream str;
1020				INFOMSG("MediaPluginGStreamer010::receiveMessage: shared memory added, name: %s, size: %d, address: %p", name.c_str(), int(info.mSize), info.mAddress);
1021
1022				mSharedSegments.insert(SharedSegmentMap::value_type(name, info));
1023			}
1024			else if(message_name == "shm_remove")
1025			{
1026				std::string name = message_in.getValue("name");
1027
1028				DEBUGMSG("MediaPluginGStreamer010::receiveMessage: shared memory remove, name = %s", name.c_str());
1029				
1030				SharedSegmentMap::iterator iter = mSharedSegments.find(name);
1031				if(iter != mSharedSegments.end())
1032				{
1033					if(mPixels == iter->second.mAddress)
1034					{
1035						// This is the currently active pixel buffer.  Make sure we stop drawing to it.
1036						mPixels = NULL;
1037						mTextureSegmentName.clear();
1038						
1039						// Make sure the movie decoder is no longer pointed at the shared segment.
1040						sizeChanged();						
1041					}
1042					mSharedSegments.erase(iter);
1043				}
1044				else
1045				{
1046					WARNMSG("MediaPluginGStreamer010::receiveMessage: unknown shared memory region!");
1047				}
1048
1049				// Send the response so it can be cleaned up.
1050				LLPluginMessage message("base", "shm_remove_response");
1051				message.setValue("name", name);
1052				sendMessage(message);
1053			}
1054			else
1055			{
1056				std::ostringstream str;
1057				INFOMSG("MediaPluginGStreamer010::receiveMessage: unknown base message: %s", message_name.c_str());
1058			}
1059		}
1060		else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA)
1061		{
1062			if(message_name == "init")
1063			{
1064				// Plugin gets to decide the texture parameters to use.
1065				LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "texture_params");
1066				// lame to have to decide this now, it depends on the movie.  Oh well.
1067				mDepth = 4;
1068
1069				mCurrentWidth = 1;
1070				mCurrentHeight = 1;
1071				mPreviousWidth = 1;
1072				mPreviousHeight = 1;
1073				mNaturalWidth = 1;
1074				mNaturalHeight = 1;
1075				mWidth = 1;
1076				mHeight = 1;
1077				mTextureWidth = 1;
1078				mTextureHeight = 1;
1079
1080				message.setValueU32("format", GL_RGBA);
1081				message.setValueU32("type", GL_UNSIGNED_INT_8_8_8_8_REV);
1082
1083				message.setValueS32("depth", mDepth);
1084				message.setValueS32("default_width", mWidth);
1085				message.setValueS32("default_height", mHeight);
1086				message.setValueU32("internalformat", GL_RGBA8);
1087				message.setValueBoolean("coords_opengl", true);	// true == use OpenGL-style coordinates, false == (0,0) is upper left.
1088				message.setValueBoolean("allow_downsample", true); // we respond with grace and performance if asked to downscale
1089				sendMessage(message);
1090			}
1091			else if(message_name == "size_change")
1092			{
1093				std::string name = message_in.getValue("name");
1094				S32 width = message_in.getValueS32("width");
1095				S32 height = message_in.getValueS32("height");
1096				S32 texture_width = message_in.getValueS32("texture_width");
1097				S32 texture_height = message_in.getValueS32("texture_height");
1098
1099				std::ostringstream str;
1100				INFOMSG("---->Got size change instruction from application with shm name: %s - size is %d x %d", name.c_str(), width, height);
1101
1102				LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_response");
1103				message.setValue("name", name);
1104				message.setValueS32("width", width);
1105				message.setValueS32("height", height);
1106				message.setValueS32("texture_width", texture_width);
1107				message.setValueS32("texture_height", texture_height);
1108				sendMessage(message);
1109
1110				if(!name.empty())
1111				{
1112					// Find the shared memory region with this name
1113					SharedSegmentMap::iterator iter = mSharedSegments.find(name);
1114					if(iter != mSharedSegments.end())
1115					{
1116						INFOMSG("*** Got size change with matching shm, new size is %d x %d", width, height);
1117						INFOMSG("*** Got size change with matching shm, texture size size is %d x %d", texture_width, texture_height);
1118
1119						mPixels = (unsigned char*)iter->second.mAddress;
1120						mTextureSegmentName = name;
1121						mWidth = width;
1122						mHeight = height;
1123
1124						if (texture_width > 1 ||
1125						    texture_height > 1) // not a dummy size from the app, a real explicit forced size
1126						{
1127							INFOMSG("**** = REAL RESIZE REQUEST FROM APP");
1128							
1129							GST_OBJECT_LOCK(mVideoSink);
1130							mVideoSink->resize_forced_always = true;
1131							mVideoSink->resize_try_width = texture_width;
1132							mVideoSink->resize_try_height = texture_height;
1133							GST_OBJECT_UNLOCK(mVideoSink);
1134 						}
1135
1136						mTextureWidth = texture_width;
1137						mTextureHeight = texture_height;
1138					}
1139				}
1140			}
1141			else if(message_name == "load_uri")
1142			{
1143				std::string uri = message_in.getValue("uri");
1144				navigateTo( uri );
1145				sendStatus();		
1146			}
1147			else if(message_name == "mouse_event")
1148			{
1149				std::string event = message_in.getValue("event");
1150				S32 x = message_in.getValueS32("x");
1151				S32 y = message_in.getValueS32("y");
1152				
1153				if(event == "down")
1154				{
1155					mouseDown(x, y);
1156				}
1157				else if(event == "up")
1158				{
1159					mouseUp(x, y);
1160				}
1161				else if(event == "move")
1162				{
1163					mouseMove(x, y);
1164				};
1165			};
1166		}
1167		else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME)
1168		{
1169			if(message_name == "stop")
1170			{
1171				stop();
1172			}
1173			else if(message_name == "start")
1174			{
1175				double rate = 0.0;
1176				if(message_in.hasValue("rate"))
1177				{
1178					rate = message_in.getValueReal("rate");
1179				}
1180				// NOTE: we don't actually support rate.
1181				play(rate);
1182			}
1183			else if(message_name == "pause")
1184			{
1185				pause();
1186			}
1187			else if(message_name == "seek")
1188			{
1189				double time = message_in.getValueReal("time");
1190				// defer the actual seek in case we haven't
1191				// really truly started yet in which case there
1192				// is nothing to seek upon
1193				mSeekWanted = true;
1194				mSeekDestination = time;
1195			}
1196			else if(message_name == "set_loop")
1197			{
1198				bool loop = message_in.getValueBoolean("loop");
1199				mIsLooping = loop;
1200			}
1201			else if(message_name == "set_volume")
1202			{
1203				double volume = message_in.getValueReal("volume");
1204				setVolume(volume);
1205			}
1206		}
1207		else
1208		{
1209			INFOMSG("MediaPluginGStreamer010::receiveMessage: unknown message class: %s", message_class.c_str());
1210		}
1211	}
1212}
1213
1214int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data)
1215{
1216	if (MediaPluginGStreamer010::startup())
1217	{
1218		MediaPluginGStreamer010 *self = new MediaPluginGStreamer010(host_send_func, host_user_data);
1219		*plugin_send_func = MediaPluginGStreamer010::staticReceiveMessage;
1220		*plugin_user_data = (void*)self;
1221		
1222		return 0; // okay
1223	}
1224	else 
1225	{
1226		return -1; // failed to init
1227	}
1228}
1229
1230#else // LL_GSTREAMER010_ENABLED
1231
1232// Stubbed-out class with constructor/destructor (necessary or windows linker
1233// will just think its dead code and optimize it all out)
1234class MediaPluginGStreamer010 : public MediaPluginBase
1235{
1236public:
1237	MediaPluginGStreamer010(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data);
1238	~MediaPluginGStreamer010();
1239	/* virtual */ void receiveMessage(const char *message_string);
1240};
1241
1242MediaPluginGStreamer010::MediaPluginGStreamer010(
1243	LLPluginInstance::sendMessageFunction host_send_func,
1244	void *host_user_data ) :
1245	MediaPluginBase(host_send_func, host_user_data)
1246{
1247    // no-op
1248}
1249
1250MediaPluginGStreamer010::~MediaPluginGStreamer010()
1251{
1252    // no-op
1253}
1254
1255void MediaPluginGStreamer010::receiveMessage(const char *message_string)
1256{
1257    // no-op 
1258}
1259
1260// We're building without GStreamer enabled.  Just refuse to initialize.
1261int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data)
1262{
1263    return -1;
1264}
1265
1266#endif // LL_GSTREAMER010_ENABLED