media_plugin_gstreamer.cpp 27 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135
  1. /**
  2. * @file media_plugin_gstreamer.cpp
  3. * @brief GStreamer-1.0 plugin for LLMedia API plugin system
  4. *
  5. * $LicenseInfo:firstyear=2007&license=viewergpl$
  6. *
  7. * Copyright (c) 2007-2009, Linden Research, Inc.
  8. *
  9. * Second Life Viewer Source Code
  10. * The source code in this file ("Source Code") is provided by Linden Lab
  11. * to you under the terms of the GNU General Public License, version 2.0
  12. * ("GPL"), unless you have obtained a separate licensing agreement
  13. * ("Other License"), formally executed by you and Linden Lab. Terms of
  14. * the GPL can be found in doc/GPL-license.txt in this distribution, or
  15. * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  16. *
  17. * There are special exceptions to the terms and conditions of the GPL as
  18. * it is applied to this Source Code. View the full text of the exception
  19. * in the file doc/FLOSS-exception.txt in this software distribution, or
  20. * online at
  21. * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  22. *
  23. * By copying, modifying or distributing this software, you acknowledge
  24. * that you have read and understood your obligations described above,
  25. * and agree to abide by those obligations.
  26. *
  27. * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  28. * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  29. * COMPLETENESS OR PERFORMANCE.
  30. * $/LicenseInfo$
  31. */
  32. #include "linden_common.h"
  33. #include "llgl.h"
  34. #include "llplugininstance.h"
  35. #include "llpluginmessage.h"
  36. #include "llpluginmessageclasses.h"
  37. #include "media_plugin_base.h"
  38. #include "volume_catcher.h"
  39. #define G_DISABLE_CAST_CHECKS
  40. #if LL_CLANG
  41. // When using C++11, clang warns about the "register" type usage in gst/gst.h
  42. // (actually stemming from glib-2.0/gobject/gtype.h), while this is a C
  43. // header (and gcc is fine with it)... And with C++17, clang just errors out...
  44. // So, let's nullify the "register" keyword before including the headers. HB
  45. # define register
  46. #endif
  47. #include "gst/gst.h"
  48. #include "gst/app/gstappsink.h"
  49. #include "llmediaimplgstreamer.h"
  50. #include "llmediaimplgstreamer_syms.h"
  51. constexpr double MIN_LOOP_SEC = 1.0F;
  52. constexpr U32 INTERNAL_TEXTURE_SIZE = 1024;
  53. #if 0
  54. struct LLStreamMetadata
  55. {
  56. std::string mArtist;
  57. std::string mTitle;
  58. };
  59. // Extract stream metadata so we can report back into the client what is playing
  60. static void extract_metadata(const GstTagList* list, const gchar* tag,
  61. gpointer user_data)
  62. {
  63. if (!user_data) return;
  64. LLStreamMetadata* outp = reinterpret_cast<LLStreamMetadata*>(user_data);
  65. std::string* outstrp = NULL;
  66. if (strcmp(tag, "title") == 0)
  67. {
  68. outstrp = &outp->mTitle;
  69. }
  70. else if (strcmp(tag, "artist") == 0)
  71. {
  72. outstrp = &outp->mArtist;
  73. }
  74. if (!outstrp) return;
  75. for (int i = 0, num = llgst_tag_list_get_tag_size(list, tag); i < num; ++i)
  76. {
  77. const GValue* val = llgst_tag_list_get_value_index(list, tag, i);
  78. if (G_VALUE_HOLDS_STRING(val))
  79. {
  80. outstrp->assign(g_value_get_string(val));
  81. }
  82. }
  83. }
  84. #endif
  85. LL_INLINE static void llgst_caps_unref(GstCaps* caps)
  86. {
  87. llgst_mini_object_unref(GST_MINI_OBJECT_CAST(caps));
  88. }
  89. LL_INLINE static void llgst_sample_unref(GstSample* sample)
  90. {
  91. llgst_mini_object_unref(GST_MINI_OBJECT_CAST(sample));
  92. }
  93. class MediaPluginGStreamer : public MediaPluginBase
  94. {
  95. public:
  96. MediaPluginGStreamer(LLPluginInstance::sendMessageFunction host_send_func,
  97. void* host_user_data);
  98. ~MediaPluginGStreamer() override;
  99. void receiveMessage(const char* message_string) override;
  100. static bool startup();
  101. static bool closedown();
  102. gboolean processGSTEvents(GstBus* bus, GstMessage* message);
  103. private:
  104. bool load();
  105. bool unload();
  106. std::string getVersion();
  107. bool navigateTo(const std::string url);
  108. bool seek(double time_sec);
  109. bool setVolume(float volume);
  110. bool pause();
  111. bool stop();
  112. bool play(double rate);
  113. bool getTimePos(double& sec_out);
  114. bool update(int milliseconds);
  115. void mouseDown(int x, int y);
  116. void mouseUp(int x, int y);
  117. void mouseMove(int x, int y);
  118. private:
  119. // Very GStreamer-specific
  120. GMainLoop* mPump; // event pump for this media
  121. GstElement* mPlaybin;
  122. GstAppSink* mAppSink;
  123. VolumeCatcher mVolumeCatcher;
  124. enum ECommand {
  125. COMMAND_NONE,
  126. COMMAND_STOP,
  127. COMMAND_PLAY,
  128. COMMAND_FAST_FORWARD,
  129. COMMAND_FAST_REWIND,
  130. COMMAND_PAUSE,
  131. COMMAND_SEEK,
  132. };
  133. ECommand mCommand;
  134. guint mBusWatchID;
  135. float mVolume;
  136. int mDepth;
  137. // padded texture size we need to write into
  138. int mTextureWidth;
  139. int mTextureHeight;
  140. double mSeekDestination;
  141. bool mSeekWanted;
  142. bool mIsLooping;
  143. bool mEnableMediaPluginDebugging;
  144. static bool mDoneInit;
  145. };
  146. //static
  147. bool MediaPluginGStreamer::mDoneInit = false;
  148. MediaPluginGStreamer::MediaPluginGStreamer(LLPluginInstance::sendMessageFunction host_send_func,
  149. void* host_user_data)
  150. : MediaPluginBase(host_send_func, host_user_data),
  151. mBusWatchID(0),
  152. mSeekWanted(false),
  153. mIsLooping(false),
  154. mSeekDestination(0.0),
  155. mPump(NULL),
  156. mPlaybin(NULL),
  157. mAppSink(NULL),
  158. mCommand(COMMAND_NONE),
  159. mEnableMediaPluginDebugging(false)
  160. {
  161. // Ensure the volume is set at maximum in the system mixer: the plugin got
  162. // its own volume control.
  163. mVolumeCatcher.setVolume(1.f);
  164. }
  165. gboolean MediaPluginGStreamer::processGSTEvents(GstBus* bus,
  166. GstMessage* message)
  167. {
  168. if (!message)
  169. {
  170. return TRUE; // shield against GStreamer bug
  171. }
  172. switch (GST_MESSAGE_TYPE(message))
  173. {
  174. case GST_MESSAGE_BUFFERING:
  175. {
  176. if (llgst_message_parse_buffering)
  177. {
  178. gint percent = 0;
  179. llgst_message_parse_buffering(message, &percent);
  180. }
  181. break;
  182. }
  183. case GST_MESSAGE_STATE_CHANGED:
  184. {
  185. GstState old_state;
  186. GstState new_state;
  187. GstState pending_state;
  188. llgst_message_parse_state_changed(message, &old_state, &new_state,
  189. &pending_state);
  190. switch (new_state)
  191. {
  192. case GST_STATE_VOID_PENDING:
  193. case GST_STATE_NULL:
  194. break;
  195. case GST_STATE_READY:
  196. setStatus(STATUS_LOADED);
  197. break;
  198. case GST_STATE_PAUSED:
  199. setStatus(STATUS_PAUSED);
  200. break;
  201. case GST_STATE_PLAYING:
  202. setStatus(STATUS_PLAYING);
  203. }
  204. break;
  205. }
  206. #if 0
  207. case GST_MESSAGE_TAG:
  208. {
  209. LLStreamMetadata mdata;
  210. GstTagList* tags = NULL;
  211. llgst_message_parse_tag(message, &tags);
  212. llgst_tag_list_foreach(tags, extract_metadata, &mdata);
  213. llgst_tag_list_unref(tags);
  214. LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "name_text");
  215. message.setValue("name", mdata.mTitle);
  216. message.setValue("artist", mdata.mArtist);
  217. sendMessage(message);
  218. break;
  219. }
  220. #endif
  221. case GST_MESSAGE_ERROR:
  222. {
  223. GError* err = NULL;
  224. gchar* debug = NULL;
  225. llgst_message_parse_error(message, &err, &debug);
  226. if (err)
  227. {
  228. llg_error_free(err);
  229. }
  230. llg_free(debug);
  231. mCommand = COMMAND_STOP;
  232. setStatus(STATUS_ERROR);
  233. break;
  234. }
  235. case GST_MESSAGE_INFO:
  236. {
  237. if (llgst_message_parse_info)
  238. {
  239. GError* err = NULL;
  240. gchar* debug = NULL;
  241. llgst_message_parse_info(message, &err, &debug);
  242. if (err)
  243. {
  244. llg_error_free(err);
  245. }
  246. llg_free(debug);
  247. }
  248. break;
  249. }
  250. case GST_MESSAGE_WARNING:
  251. {
  252. GError* err = NULL;
  253. gchar* debug = NULL;
  254. llgst_message_parse_info(message, &err, &debug);
  255. if (err)
  256. {
  257. llg_error_free(err);
  258. }
  259. llg_free(debug);
  260. break;
  261. }
  262. case GST_MESSAGE_EOS: // end-of-stream
  263. {
  264. if (mIsLooping)
  265. {
  266. double eos_pos_sec = 0.0F;
  267. bool got_eos_position = getTimePos(eos_pos_sec);
  268. if (got_eos_position && eos_pos_sec < MIN_LOOP_SEC)
  269. {
  270. // If we know that the movie is really short, do not loop
  271. // it else it can easily become a time-hog because of
  272. // GStreamer spin-up overhead; inject a COMMAND_PAUSE
  273. mCommand = COMMAND_PAUSE;
  274. }
  275. else
  276. {
  277. stop();
  278. play(1.0);
  279. }
  280. }
  281. else // not a looping media
  282. {
  283. // Inject a COMMAND_STOP
  284. mCommand = COMMAND_STOP;
  285. }
  286. break;
  287. }
  288. default: // Unhandled message
  289. break;
  290. }
  291. // We want to be notified again the next time there is a message on the
  292. // bus, so return true (false means we want to stop watching for messages
  293. // on the bus and our callback should not be called again)
  294. return TRUE;
  295. }
  296. extern "C" {
  297. gboolean llmediaimplgstreamer_bus_callback(GstBus* bus, GstMessage* message,
  298. gpointer data)
  299. {
  300. MediaPluginGStreamer* impl = (MediaPluginGStreamer*)data;
  301. return impl && impl->processGSTEvents(bus, message);
  302. }
  303. } // extern "C"
  304. bool MediaPluginGStreamer::navigateTo(const std::string url)
  305. {
  306. if (!mDoneInit)
  307. {
  308. return false; // Error
  309. }
  310. setStatus(STATUS_LOADING);
  311. mSeekWanted = false;
  312. if (!mPump || !mPlaybin)
  313. {
  314. setStatus(STATUS_ERROR);
  315. return false; // Error
  316. }
  317. // Send a "navigate begin" event. This is really a browser message but the
  318. // QuickTime plugin does it and the media system relies on this message to
  319. // update internal state so we must send it too.
  320. // Note: see "navigate_complete" message below too.
  321. LLPluginMessage message_begin(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER,
  322. "navigate_begin");
  323. message_begin.setValue("uri", url);
  324. message_begin.setValueBoolean("history_back_available", false);
  325. message_begin.setValueBoolean("history_forward_available", false);
  326. sendMessage(message_begin);
  327. llg_object_set(G_OBJECT(mPlaybin), "uri", url.c_str(), NULL);
  328. // navigateTo implicitly plays, too.
  329. play(1.0);
  330. // Send a "location_changed" message; this informs the media system that a
  331. // new URL is the 'current' one and is used extensively. Again, this is
  332. // really a browser message but we will use it here.
  333. LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER,
  334. "location_changed");
  335. message.setValue("uri", url);
  336. sendMessage(message);
  337. // Send a "navigate complete" event.
  338. LLPluginMessage message_complete(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER,
  339. "navigate_complete");
  340. message_complete.setValue("uri", url);
  341. message_complete.setValueS32("result_code", 200);
  342. message_complete.setValue("result_string", "OK");
  343. sendMessage(message_complete);
  344. return true;
  345. }
  346. class GstSampleUnref
  347. {
  348. public:
  349. GstSampleUnref(GstSample* aT)
  350. : mT(aT)
  351. {
  352. llassert_always(mT);
  353. }
  354. ~GstSampleUnref()
  355. {
  356. llgst_sample_unref(mT);
  357. }
  358. private:
  359. GstSample* mT;
  360. };
  361. bool MediaPluginGStreamer::update(int milliseconds)
  362. {
  363. if (!mDoneInit)
  364. {
  365. return false; // error
  366. }
  367. // Sanity check
  368. if (!mPump || !mPlaybin)
  369. {
  370. return false;
  371. }
  372. // See if there is an outstanding seek wanted
  373. if (mSeekWanted &&
  374. // Bleh, GST has to be happy that the movie is really truly playing or
  375. // it may quietly ignore the seek (with rtsp:// at least).
  376. GST_STATE(mPlaybin) == GST_STATE_PLAYING)
  377. {
  378. seek(mSeekDestination);
  379. mSeekWanted = false;
  380. }
  381. // *TODO: time-limit - but there is not a lot we can do here, most time is
  382. // spent in gstreamer's own opaque worker-threads. Maybe we can do
  383. // something sneaky like only unlock the video object for 'milliseconds'
  384. // and otherwise hold the lock.
  385. while (llg_main_context_pending(llg_main_loop_get_context(mPump)))
  386. {
  387. llg_main_context_iteration(llg_main_loop_get_context(mPump), FALSE);
  388. }
  389. // Check for availability of a new frame
  390. if (!mAppSink)
  391. {
  392. return true;
  393. }
  394. // Do not try to pull a sample if not in playing state
  395. if (GST_STATE(mPlaybin) != GST_STATE_PLAYING)
  396. {
  397. return true;
  398. }
  399. GstSample* samplep = llgst_app_sink_pull_sample(mAppSink);
  400. if (!samplep)
  401. {
  402. return false; // Done playing
  403. }
  404. GstSampleUnref oSampleUnref(samplep);
  405. GstCaps* capsp = llgst_sample_get_caps(samplep);
  406. if (!capsp)
  407. {
  408. return false;
  409. }
  410. gint width, height;
  411. GstStructure* pStruct = llgst_caps_get_structure(capsp, 0);
  412. llgst_structure_get_int(pStruct, "width", &width);
  413. llgst_structure_get_int(pStruct, "height", &height);
  414. if (!mPixels)
  415. {
  416. return true;
  417. }
  418. GstBuffer* bufferp = llgst_sample_get_buffer(samplep);
  419. GstMapInfo map;
  420. llgst_buffer_map(bufferp, &map, GST_MAP_READ);
  421. // Our render buffer is always INTERNAL_TEXTURE_SIZExINTERNAL_TEXTURE_SIZE
  422. // Downsample if the viewer requested a smaller texture (zoomed out).
  423. // *TODO: investigate how to use a gstreamer scale filter rather than this
  424. // quick and dirty scale, which will not give any nice results when scaling
  425. // very low.
  426. U32 row_skip = INTERNAL_TEXTURE_SIZE / mTextureHeight;
  427. U32 col_skip = INTERNAL_TEXTURE_SIZE / mTextureWidth;
  428. U8* dest = mPixels + (mTextureHeight - 1) * mTextureWidth * mDepth;
  429. for (int row = 0; row < mTextureHeight; ++row)
  430. {
  431. const U8* texel_in = map.data + row * row_skip * width * 3;
  432. U8* texel_out = dest - row * mTextureWidth * mDepth;
  433. for (int col = 0; col < width && col < mTextureWidth; ++col)
  434. {
  435. texel_out[0] = texel_in[0];
  436. texel_out[1] = texel_in[1];
  437. texel_out[2] = texel_in[2];
  438. texel_out += mDepth;
  439. texel_in += col_skip * 3;
  440. }
  441. }
  442. llgst_buffer_unmap(bufferp, &map);
  443. setDirty(0, 0, mTextureWidth, mTextureHeight);
  444. return true;
  445. }
  446. void MediaPluginGStreamer::mouseDown(int x, int y)
  447. {
  448. // Do nothing
  449. }
  450. void MediaPluginGStreamer::mouseUp(int x, int y)
  451. {
  452. // Do nothing
  453. }
  454. void MediaPluginGStreamer::mouseMove(int x, int y)
  455. {
  456. // Do nothing
  457. }
  458. bool MediaPluginGStreamer::pause()
  459. {
  460. // *TODO: error-check this ?
  461. if (mDoneInit && mPlaybin)
  462. {
  463. llgst_element_set_state(mPlaybin, GST_STATE_PAUSED);
  464. return true;
  465. }
  466. return false;
  467. }
  468. bool MediaPluginGStreamer::stop()
  469. {
  470. // *TODO: error-check this ?
  471. if (mDoneInit && mPlaybin)
  472. {
  473. llgst_element_set_state(mPlaybin, GST_STATE_READY);
  474. return true;
  475. }
  476. return false;
  477. }
  478. // NOTE: we do not actually support non-natural rate.
  479. bool MediaPluginGStreamer::play(double rate)
  480. {
  481. // *TODO: error-check this ?
  482. if (mDoneInit && mPlaybin)
  483. {
  484. llgst_element_set_state(mPlaybin, GST_STATE_PLAYING);
  485. return true;
  486. }
  487. return false;
  488. }
  489. bool MediaPluginGStreamer::setVolume(float volume)
  490. {
  491. // We try to only update volume as conservatively as possible, as many
  492. // gst-plugins-base versions up to at least November 2008 have critical
  493. // race-conditions in setting volume - sigh
  494. if (mVolume == volume)
  495. {
  496. return true; // Nothing to do, everything is fine
  497. }
  498. mVolume = volume;
  499. if (mDoneInit && mPlaybin)
  500. {
  501. llg_object_set(mPlaybin, "volume", mVolume, NULL);
  502. return true;
  503. }
  504. return false;
  505. }
  506. bool MediaPluginGStreamer::seek(double time_sec)
  507. {
  508. bool success = false;
  509. if (mDoneInit && mPlaybin)
  510. {
  511. success = llgst_element_seek(mPlaybin, 1.0F, GST_FORMAT_TIME,
  512. GstSeekFlags(GST_SEEK_FLAG_FLUSH |
  513. GST_SEEK_FLAG_KEY_UNIT),
  514. GST_SEEK_TYPE_SET,
  515. gint64(time_sec * GST_SECOND),
  516. GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
  517. }
  518. return success;
  519. }
  520. bool MediaPluginGStreamer::getTimePos(double& sec_out)
  521. {
  522. bool got_position = false;
  523. if (mDoneInit && mPlaybin)
  524. {
  525. gint64 pos = 0;
  526. GstFormat timefmt = GST_FORMAT_TIME;
  527. got_position = llgst_element_query_position &&
  528. llgst_element_query_position(mPlaybin, &timefmt, &pos);
  529. got_position &= timefmt == GST_FORMAT_TIME;
  530. // GStreamer may have other ideas, but we consider the current position
  531. // undefined if not PLAYING or PAUSED
  532. got_position &= GST_STATE(mPlaybin) == GST_STATE_PLAYING ||
  533. GST_STATE(mPlaybin) == GST_STATE_PAUSED;
  534. if (got_position && !GST_CLOCK_TIME_IS_VALID(pos))
  535. {
  536. if (GST_STATE(mPlaybin) == GST_STATE_PLAYING)
  537. {
  538. // If we are playing then we treat an invalid clock time as 0,
  539. // for complicated reasons (insert reason here)
  540. pos = 0;
  541. }
  542. else
  543. {
  544. got_position = false;
  545. }
  546. }
  547. // If all the preconditions succeeded... we can trust the result.
  548. if (got_position)
  549. {
  550. sec_out = double(pos) / double(GST_SECOND); // gst to sec
  551. }
  552. }
  553. return got_position;
  554. }
  555. bool MediaPluginGStreamer::load()
  556. {
  557. if (!mDoneInit)
  558. {
  559. return false; // error
  560. }
  561. setStatus(STATUS_LOADING);
  562. mIsLooping = false;
  563. mVolume = 0.1234567f; // Minor hack to force an initial volume update
  564. // Create a pumpable main-loop for this media
  565. mPump = llg_main_loop_new(NULL, FALSE);
  566. if (!mPump)
  567. {
  568. setStatus(STATUS_ERROR);
  569. return false; // error
  570. }
  571. // Instantiate a playbin element to do the hard work
  572. mPlaybin = llgst_element_factory_make("playbin", "");
  573. if (!mPlaybin)
  574. {
  575. setStatus(STATUS_ERROR);
  576. return false; // error
  577. }
  578. // get playbin's bus
  579. GstBus* bus = llgst_pipeline_get_bus(GST_PIPELINE (mPlaybin));
  580. if (!bus)
  581. {
  582. setStatus(STATUS_ERROR);
  583. return false; // error
  584. }
  585. mBusWatchID = llgst_bus_add_watch(bus, llmediaimplgstreamer_bus_callback,
  586. this);
  587. llgst_object_unref(bus);
  588. mAppSink = (GstAppSink*)llgst_element_factory_make("appsink", "");
  589. GstCaps* capsp = llgst_caps_new_simple("video/x-raw", "format",
  590. G_TYPE_STRING, "RGB",
  591. "width", G_TYPE_INT,
  592. INTERNAL_TEXTURE_SIZE,
  593. "height", G_TYPE_INT,
  594. INTERNAL_TEXTURE_SIZE,
  595. NULL);
  596. llgst_app_sink_set_caps(mAppSink, capsp);
  597. llgst_caps_unref(capsp);
  598. if (!mAppSink)
  599. {
  600. setStatus(STATUS_ERROR);
  601. return false;
  602. }
  603. llg_object_set(mPlaybin, "video-sink", mAppSink, NULL);
  604. return true;
  605. }
  606. bool MediaPluginGStreamer::unload()
  607. {
  608. if (!mDoneInit)
  609. {
  610. return false; // error
  611. }
  612. // Stop getting callbacks for this bus
  613. llg_source_remove(mBusWatchID);
  614. mBusWatchID = 0;
  615. if (mPlaybin)
  616. {
  617. llgst_element_set_state(mPlaybin, GST_STATE_NULL);
  618. llgst_object_unref(GST_OBJECT (mPlaybin));
  619. mPlaybin = NULL;
  620. }
  621. if (mPump)
  622. {
  623. llg_main_loop_quit(mPump);
  624. mPump = NULL;
  625. }
  626. mAppSink = NULL;
  627. setStatus(STATUS_NONE);
  628. return true;
  629. }
  630. void LogFunction(GstDebugCategory* category, GstDebugLevel level,
  631. const gchar* file, const gchar* function, gint line,
  632. GObject* object, GstDebugMessage* message,
  633. gpointer user_data)
  634. {
  635. std::cerr << file << ": " << line << "(" << function << "): "
  636. << llgst_debug_message_get(message) << std::endl;
  637. }
  638. //static
  639. bool MediaPluginGStreamer::startup()
  640. {
  641. // Only do global GStreamer initialization once.
  642. if (mDoneInit)
  643. {
  644. return true;
  645. }
  646. ll_init_apr();
  647. // Get symbols
  648. std::vector<std::string> dso_names;
  649. #if LL_WINDOWS
  650. dso_names.push_back("libgstreamer-1.0-0.dll");
  651. dso_names.push_back("libgstapp-1.0-0.dll");
  652. dso_names.push_back("libglib-2.0-0.dll");
  653. dso_names.push_back("libgobject-2.0-0.dll");
  654. #elif LL_DARWIN
  655. dso_names.push_back("libgstreamer-1.0.0.dylib");
  656. dso_names.push_back("libgstapp-1.0.0.dylib");
  657. dso_names.push_back("libglib-2.0.0.dylib");
  658. dso_names.push_back("libgobject-2.0.0.dylib");
  659. #else // Linux or other ELFy unixoid
  660. dso_names.push_back("libgstreamer-1.0.so.0");
  661. dso_names.push_back("libgstapp-1.0.so.0");
  662. dso_names.push_back("libglib-2.0.so.0");
  663. dso_names.push_back("libgobject-2.0.so.0");
  664. #endif
  665. if (!grab_gst_syms(dso_names))
  666. {
  667. return false;
  668. }
  669. if (llgst_segtrap_set_enabled)
  670. {
  671. llgst_segtrap_set_enabled(FALSE);
  672. }
  673. #if LL_LINUX
  674. // Gstreamer tries a fork during init, waitpid-ing on it, which conflicts
  675. // with any installed SIGCHLD handler...
  676. struct sigaction tmpact, oldact;
  677. if (llgst_registry_fork_set_enabled)
  678. {
  679. // If we can disable SIGCHLD-using forking behaviour, do it.
  680. llgst_registry_fork_set_enabled(FALSE);
  681. }
  682. else
  683. {
  684. // Else temporarily install default SIGCHLD handler while GStreamer
  685. // initialises
  686. tmpact.sa_handler = SIG_DFL;
  687. sigemptyset(&tmpact.sa_mask);
  688. tmpact.sa_flags = SA_SIGINFO;
  689. sigaction(SIGCHLD, &tmpact, &oldact);
  690. }
  691. #endif
  692. // Protect against GStreamer resetting the locale, yuck.
  693. static std::string saved_locale;
  694. saved_locale = setlocale(LC_ALL, NULL);
  695. llgst_debug_set_default_threshold(GST_LEVEL_WARNING);
  696. llgst_debug_add_log_function(LogFunction, NULL, NULL);
  697. llgst_debug_set_active(FALSE);
  698. // Finally, try to initialize GStreamer !
  699. GError* err = NULL;
  700. gboolean init_gst_success = llgst_init_check(NULL, NULL, &err);
  701. // Restore old locale
  702. setlocale(LC_ALL, saved_locale.c_str());
  703. #if LL_LINUX
  704. // Restore old SIGCHLD handler
  705. if (!llgst_registry_fork_set_enabled)
  706. {
  707. sigaction(SIGCHLD, &oldact, NULL);
  708. }
  709. #endif
  710. if (!init_gst_success) // Failed
  711. {
  712. if (err)
  713. {
  714. llg_error_free(err);
  715. }
  716. return false;
  717. }
  718. mDoneInit = true;
  719. return true;
  720. }
  721. //static
  722. bool MediaPluginGStreamer::closedown()
  723. {
  724. if (!mDoneInit)
  725. {
  726. return false; // Error
  727. }
  728. ungrab_gst_syms();
  729. mDoneInit = false;
  730. return true;
  731. }
  732. MediaPluginGStreamer::~MediaPluginGStreamer()
  733. {
  734. closedown();
  735. }
  736. std::string MediaPluginGStreamer::getVersion()
  737. {
  738. std::string plugin_version = "GStreamer media plugin, GStreamer version ";
  739. if (mDoneInit && llgst_version)
  740. {
  741. guint major, minor, micro, nano;
  742. llgst_version(&major, &minor, &micro, &nano);
  743. plugin_version += llformat("%u.%u.%u.%u (runtime), %u.%u.%u.%u (headers)",
  744. (unsigned int)major, (unsigned int)minor,
  745. (unsigned int)micro, (unsigned int)nano,
  746. (unsigned int)GST_VERSION_MAJOR,
  747. (unsigned int)GST_VERSION_MINOR,
  748. (unsigned int)GST_VERSION_MICRO,
  749. (unsigned int)GST_VERSION_NANO);
  750. }
  751. else
  752. {
  753. plugin_version += "(unknown)";
  754. }
  755. return plugin_version;
  756. }
  757. void MediaPluginGStreamer::receiveMessage(const char* message_string)
  758. {
  759. #if LL_DEBUG // Very spammy...
  760. if (mEnableMediaPluginDebugging)
  761. {
  762. std::cerr << LL_FUNC << ": received message: \"" << message_string
  763. << "\"" << std::endl;
  764. }
  765. #endif
  766. LLPluginMessage message_in;
  767. if (message_in.parse(message_string) >= 0)
  768. {
  769. std::string message_class = message_in.getClass();
  770. std::string message_name = message_in.getName();
  771. if (message_class == LLPLUGIN_MESSAGE_CLASS_BASE)
  772. {
  773. if (message_name == "init")
  774. {
  775. LLPluginMessage message(message_class, "init_response");
  776. LLSD versions = LLSD::emptyMap();
  777. versions[LLPLUGIN_MESSAGE_CLASS_BASE] =
  778. LLPLUGIN_MESSAGE_CLASS_BASE_VERSION;
  779. versions[LLPLUGIN_MESSAGE_CLASS_MEDIA] =
  780. LLPLUGIN_MESSAGE_CLASS_MEDIA_VERSION;
  781. versions[LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME] =
  782. LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME_VERSION;
  783. message.setValueLLSD("versions", versions);
  784. load();
  785. message.setValue("plugin_version", getVersion());
  786. sendMessage(message);
  787. }
  788. else if (message_name == "idle")
  789. {
  790. // No response is necessary here.
  791. double time = message_in.getValueReal("time");
  792. // Convert time to milliseconds for update()
  793. update((int)(time * 1000.0f));
  794. #if 0 // Not really used for now...
  795. mVolumeCatcher.pump();
  796. #endif
  797. }
  798. else if (message_name == "cleanup")
  799. {
  800. unload();
  801. closedown();
  802. LLPluginMessage message(message_class, "goodbye");
  803. sendMessage(message);
  804. }
  805. else if (message_name == "force_exit")
  806. {
  807. mDeleteMe = true;
  808. }
  809. else if (message_name == "shm_added")
  810. {
  811. SharedSegmentInfo info;
  812. info.mAddress = message_in.getValuePointer("address");
  813. info.mSize = (size_t)message_in.getValueS32("size");
  814. std::string name = message_in.getValue("name");
  815. mSharedSegments.insert(SharedSegmentMap::value_type(name,
  816. info));
  817. }
  818. else if (message_name == "shm_remove")
  819. {
  820. std::string name = message_in.getValue("name");
  821. SharedSegmentMap::iterator iter = mSharedSegments.find(name);
  822. if (iter != mSharedSegments.end())
  823. {
  824. if (mPixels == iter->second.mAddress)
  825. {
  826. // This is the currently active pixel buffer. Make sure
  827. // we stop drawing to it.
  828. mPixels = NULL;
  829. mTextureSegmentName.clear();
  830. }
  831. mSharedSegments.erase(iter);
  832. }
  833. // Send the response so it can be cleaned up.
  834. LLPluginMessage message(message_class, "shm_remove_response");
  835. message.setValue("name", name);
  836. sendMessage(message);
  837. }
  838. }
  839. else if (message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA)
  840. {
  841. if (message_name == "init")
  842. {
  843. // Plugin gets to decide the texture parameters to use.
  844. LLPluginMessage message(message_class, "texture_params");
  845. // Lame to have to decide this now, it depends on the movie.
  846. // Oh well.
  847. mDepth = 4;
  848. mTextureWidth = 1;
  849. mTextureHeight = 1;
  850. message.setValueU32("format", GL_RGBA);
  851. message.setValueU32("type", GL_UNSIGNED_INT_8_8_8_8_REV);
  852. message.setValueS32("depth", mDepth);
  853. message.setValueS32("default_width", INTERNAL_TEXTURE_SIZE);
  854. message.setValueS32("default_height", INTERNAL_TEXTURE_SIZE);
  855. message.setValueU32("internalformat", GL_RGBA8);
  856. message.setValueBoolean("coords_opengl", true);
  857. // We respond with grace and performance if asked to downscale
  858. message.setValueBoolean("allow_downsample", true);
  859. sendMessage(message);
  860. }
  861. else if (message_name == "size_change")
  862. {
  863. std::string name = message_in.getValue("name");
  864. S32 width = message_in.getValueS32("width");
  865. S32 height = message_in.getValueS32("height");
  866. S32 texture_width = message_in.getValueS32("texture_width");
  867. S32 texture_height = message_in.getValueS32("texture_height");
  868. LLPluginMessage message(message_class, "size_change_response");
  869. message.setValue("name", name);
  870. message.setValueS32("width", width);
  871. message.setValueS32("height", height);
  872. message.setValueS32("texture_width", texture_width);
  873. message.setValueS32("texture_height", texture_height);
  874. sendMessage(message);
  875. if (!name.empty())
  876. {
  877. // Find the shared memory region with this name
  878. SharedSegmentMap::iterator it = mSharedSegments.find(name);
  879. if (it != mSharedSegments.end())
  880. {
  881. mTextureSegmentName = name;
  882. mTextureWidth = texture_width;
  883. mTextureHeight = texture_height;
  884. mPixels = (unsigned char*)it->second.mAddress;
  885. if (mPixels)
  886. {
  887. memset(mPixels, 0,
  888. mTextureWidth * mTextureHeight * mDepth);
  889. }
  890. }
  891. LLPluginMessage message(message_class,
  892. "size_change_request");
  893. message.setValue("name", mTextureSegmentName);
  894. message.setValueS32("width", INTERNAL_TEXTURE_SIZE);
  895. message.setValueS32("height", INTERNAL_TEXTURE_SIZE);
  896. sendMessage(message);
  897. }
  898. }
  899. else if (message_name == "load_uri")
  900. {
  901. std::string uri = message_in.getValue("uri");
  902. navigateTo(uri);
  903. sendStatus();
  904. }
  905. else if (message_name == "mouse_event")
  906. {
  907. std::string event = message_in.getValue("event");
  908. S32 x = message_in.getValueS32("x");
  909. S32 y = message_in.getValueS32("y");
  910. if (event == "down")
  911. {
  912. mouseDown(x, y);
  913. }
  914. else if (event == "up")
  915. {
  916. mouseUp(x, y);
  917. }
  918. else if (event == "move")
  919. {
  920. mouseMove(x, y);
  921. }
  922. }
  923. else if (message_name == "enable_media_plugin_debugging")
  924. {
  925. mEnableMediaPluginDebugging = message_in.getValueBoolean("enable");
  926. llgst_debug_set_active(mEnableMediaPluginDebugging);
  927. }
  928. }
  929. else if (message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME)
  930. {
  931. if (message_name == "stop")
  932. {
  933. stop();
  934. }
  935. else if (message_name == "start")
  936. {
  937. double rate = 0.0;
  938. if (message_in.hasValue("rate"))
  939. {
  940. rate = message_in.getValueReal("rate");
  941. }
  942. // Note: we do not actually support rate.
  943. play(rate);
  944. }
  945. else if (message_name == "pause")
  946. {
  947. pause();
  948. }
  949. else if (message_name == "seek")
  950. {
  951. double time = message_in.getValueReal("time");
  952. // Defer the actual seek in case we have not really truly
  953. // started yet in which case there is nothing to seek upon.
  954. mSeekWanted = true;
  955. mSeekDestination = time;
  956. }
  957. else if (message_name == "set_loop")
  958. {
  959. bool loop = message_in.getValueBoolean("loop");
  960. mIsLooping = loop;
  961. }
  962. else if (message_name == "set_volume")
  963. {
  964. double volume = message_in.getValueReal("volume");
  965. setVolume(volume);
  966. }
  967. }
  968. }
  969. }
  970. int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func,
  971. void* host_user_data,
  972. LLPluginInstance::sendMessageFunction* plugin_send_func,
  973. void** plugin_user_data)
  974. {
  975. if (!MediaPluginGStreamer::startup())
  976. {
  977. return -1; // Failed to init
  978. }
  979. MediaPluginGStreamer* self = new MediaPluginGStreamer(host_send_func,
  980. host_user_data);
  981. *plugin_send_func = MediaPluginGStreamer::staticReceiveMessage;
  982. *plugin_user_data = (void*)self;
  983. return 0; // Success
  984. }