Music Hub  ..
A session-wide music playback service
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros
playbin.h
Go to the documentation of this file.
1 /*
2  * Copyright © 2013 Canonical Ltd.
3  *
4  * This program is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License version 3,
6  * as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * Authored by: Thomas Voß <thomas.voss@canonical.com>
17  */
18 
19 #ifndef GSTREAMER_PLAYBIN_H_
20 #define GSTREAMER_PLAYBIN_H_
21 
22 #include "bus.h"
23 #include "../mpris/player.h"
24 
25 #include <hybris/media/media_codec_layer.h>
26 #include <hybris/media/surface_texture_client_hybris.h>
27 
28 #include <gio/gio.h>
29 #include <gst/gst.h>
30 
31 #include <chrono>
32 #include <string>
33 
34 // Uncomment to generate a dot file at the time that the pipeline
35 // goes to the PLAYING state. Make sure to export GST_DEBUG_DUMP_DOT_DIR
36 // before starting media-hub-server. To convert the dot file to something
37 // other image format, use: dot pipeline.dot -Tpng -o pipeline.png
38 //#define DEBUG_GST_PIPELINE
39 
40 namespace media = core::ubuntu::media;
41 
42 namespace gstreamer
43 {
44 struct Playbin
45 {
46  enum PlayFlags
47  {
48  GST_PLAY_FLAG_VIDEO = (1 << 0),
49  GST_PLAY_FLAG_AUDIO = (1 << 1),
50  GST_PLAY_FLAG_TEXT = (1 << 2)
51  };
52 
54  {
58  };
59 
60  static const std::string& pipeline_name()
61  {
62  static const std::string s{"playbin"};
63  return s;
64  }
65 
66  static void about_to_finish(GstElement*,
67  gpointer user_data)
68  {
69  auto thiz = static_cast<Playbin*>(user_data);
70  thiz->signals.about_to_finish();
71  }
72 
74  : pipeline(gst_element_factory_make("playbin", pipeline_name().c_str())),
75  bus{gst_element_get_bus(pipeline)},
77  video_sink(nullptr),
78  video_height(0),
79  video_width(0),
81  bus.on_new_message.connect(
82  std::bind(
84  this,
85  std::placeholders::_1))),
86  is_seeking(false)
87  {
88  if (!pipeline)
89  throw std::runtime_error("Could not create pipeline for playbin.");
90 
91  // Add audio and/or video sink elements depending on environment variables
92  // being set or not set
94 
95  g_signal_connect(
96  pipeline,
97  "about-to-finish",
98  G_CALLBACK(about_to_finish),
99  this
100  );
101 
102  // When a client of media-hub dies, call on_client_died
103  decoding_service_set_client_death_cb(&Playbin::on_client_died_cb, static_cast<void*>(this));
104  }
105 
107  {
108  if (pipeline)
109  gst_object_unref(pipeline);
110  }
111 
112  static void on_client_died_cb(void *context)
113  {
114  if (context)
115  {
116  Playbin *pb = static_cast<Playbin*>(context);
117  pb->on_client_died();
118  }
119  }
120 
122  {
123  std::cout << "Client died, resetting pipeline" << std::endl;
124  // When the client dies, tear down the current pipeline and get it
125  // in a state that is ready for the next client that connects to the
126  // service
127  reset_pipeline();
128  // Signal to the Player class that the client side has disconnected
129  signals.client_disconnected();
130  }
131 
133  {
134  std::cout << __PRETTY_FUNCTION__ << std::endl;
135  auto ret = gst_element_set_state(pipeline, GST_STATE_NULL);
136  switch(ret)
137  {
138  case GST_STATE_CHANGE_FAILURE:
139  std::cout << "Failed to reset the pipeline state. Client reconnect may not function properly." << std::endl;
140  break;
141  case GST_STATE_CHANGE_NO_PREROLL:
142  case GST_STATE_CHANGE_SUCCESS:
143  case GST_STATE_CHANGE_ASYNC:
144  break;
145  default:
146  std::cout << "Failed to reset the pipeline state. Client reconnect may not function properly." << std::endl;
147  }
149  }
150 
151  void on_new_message(const Bus::Message& message)
152  {
153  switch(message.type)
154  {
155  case GST_MESSAGE_ERROR:
156  signals.on_error(message.detail.error_warning_info);
157  break;
158  case GST_MESSAGE_WARNING:
159  signals.on_warning(message.detail.error_warning_info);
160  break;
161  case GST_MESSAGE_INFO:
162  signals.on_info(message.detail.error_warning_info);
163  break;
164  case GST_MESSAGE_TAG:
165  {
166  gchar *orientation;
167  if (gst_tag_list_get_string(message.detail.tag.tag_list, "image-orientation", &orientation))
168  {
169  // If the image-orientation tag is in the GstTagList, signal the Engine
170  signals.on_orientation_changed(orientation_lut(orientation));
171  g_free (orientation);
172  }
173 
174  signals.on_tag_available(message.detail.tag);
175  }
176  break;
177  case GST_MESSAGE_STATE_CHANGED:
178  signals.on_state_changed(message.detail.state_changed);
179  break;
180  case GST_MESSAGE_ASYNC_DONE:
181  if (is_seeking)
182  {
183  // FIXME: Pass the actual playback time position to the signal call
184  signals.on_seeked_to(0);
185  is_seeking = false;
186  }
187  break;
188  case GST_MESSAGE_EOS:
189  signals.on_end_of_stream();
190  default:
191  break;
192  }
193  }
194 
196  {
197  return bus;
198  }
199 
201  {
202  gint flags;
203  g_object_get (pipeline, "flags", &flags, nullptr);
204  flags |= GST_PLAY_FLAG_AUDIO;
205  flags |= GST_PLAY_FLAG_VIDEO;
206  flags &= ~GST_PLAY_FLAG_TEXT;
207  g_object_set (pipeline, "flags", flags, nullptr);
208 
209  if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME") != nullptr)
210  {
211  auto audio_sink = gst_element_factory_make (
212  ::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME"),
213  "audio-sink");
214 
215  std::cout << "audio_sink: " << ::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME") << std::endl;
216 
217  g_object_set (
218  pipeline,
219  "audio-sink",
220  audio_sink,
221  NULL);
222  }
223 
224  if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") != nullptr)
225  {
226  video_sink = gst_element_factory_make (
227  ::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME"),
228  "video-sink");
229 
230  std::cout << "video_sink: " << ::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") << std::endl;
231 
232  g_object_set (
233  pipeline,
234  "video-sink",
235  video_sink,
236  NULL);
237  }
238  }
239 
240  void create_video_sink(uint32_t texture_id)
241  {
242  std::cout << "Creating video sink for texture_id: " << texture_id << std::endl;
243 
244  if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") != nullptr)
245  {
246  g_object_get (pipeline, "video_sink", &video_sink, NULL);
247 
248  // Get the service-side BufferQueue (IGraphicBufferProducer) and associate it with
249  // the SurfaceTextureClientHybris instance
250  IGBPWrapperHybris igbp = decoding_service_get_igraphicbufferproducer();
251  SurfaceTextureClientHybris stc = surface_texture_client_create_by_igbp(igbp);
252  // Because mirsink is being loaded, we are definitely doing * hardware rendering.
253  surface_texture_client_set_hardware_rendering (stc, TRUE);
254  g_object_set (G_OBJECT (video_sink), "surface", static_cast<gpointer>(stc), static_cast<char*>(NULL));
255  }
256  }
257 
258  void set_volume(double new_volume)
259  {
260  g_object_set (pipeline, "volume", new_volume, NULL);
261  }
262 
264  static std::string get_audio_role_str(media::Player::AudioStreamRole audio_role)
265  {
266  switch (audio_role)
267  {
268  case media::Player::AudioStreamRole::alarm:
269  return "alarm";
270  break;
271  case media::Player::AudioStreamRole::alert:
272  return "alert";
273  break;
274  case media::Player::AudioStreamRole::multimedia:
275  return "multimedia";
276  break;
277  case media::Player::AudioStreamRole::phone:
278  return "phone";
279  break;
280  default:
281  return "multimedia";
282  break;
283  }
284  }
285 
286  media::Player::Orientation orientation_lut(const gchar *orientation)
287  {
288  if (g_strcmp0(orientation, "rotate-0") == 0)
289  return media::Player::Orientation::rotate0;
290  else if (g_strcmp0(orientation, "rotate-90") == 0)
291  return media::Player::Orientation::rotate90;
292  else if (g_strcmp0(orientation, "rotate-180") == 0)
293  return media::Player::Orientation::rotate180;
294  else if (g_strcmp0(orientation, "rotate-270") == 0)
295  return media::Player::Orientation::rotate270;
296  else
297  return media::Player::Orientation::rotate0;
298  }
299 
302  {
303  GstElement *audio_sink = NULL;
304  g_object_get (pipeline, "audio-sink", &audio_sink, NULL);
305 
306  std::string role_str("props,media.role=" + get_audio_role_str(new_audio_role));
307  std::cout << "Audio stream role: " << role_str << std::endl;
308 
309  GstStructure *props = gst_structure_from_string (role_str.c_str(), NULL);
310  if (audio_sink != nullptr && props != nullptr)
311  g_object_set (audio_sink, "stream-properties", props, NULL);
312  else
313  {
314  std::cerr <<
315  "Warning: couldn't set audio stream role - couldn't get audio_sink from pipeline" <<
316  std::endl;
317  }
318 
319  gst_structure_free (props);
320  }
321 
322  uint64_t position() const
323  {
324  int64_t pos = 0;
325  gst_element_query_position (pipeline, GST_FORMAT_TIME, &pos);
326 
327  // FIXME: this should be int64_t, but dbus-cpp doesn't seem to handle it correctly
328  return static_cast<uint64_t>(pos);
329  }
330 
331  uint64_t duration() const
332  {
333  int64_t dur = 0;
334  gst_element_query_duration (pipeline, GST_FORMAT_TIME, &dur);
335 
336  // FIXME: this should be int64_t, but dbus-cpp doesn't seem to handle it correctly
337  return static_cast<uint64_t>(dur);
338  }
339 
340  void set_uri(const std::string& uri)
341  {
342  g_object_set(pipeline, "uri", uri.c_str(), NULL);
343  if (is_video_file(uri))
345  else if (is_audio_file(uri))
347  }
348 
349  std::string uri() const
350  {
351  gchar* data = nullptr;
352  g_object_get(pipeline, "current-uri", &data, nullptr);
353 
354  std::string result((data == nullptr ? "" : data));
355  g_free(data);
356 
357  return result;
358  }
359 
360  bool set_state_and_wait(GstState new_state)
361  {
362  static const std::chrono::nanoseconds state_change_timeout
363  {
364  // We choose a quite high value here as tests are run under valgrind
365  // and gstreamer pipeline setup/state changes take longer in that scenario.
366  // The value does not negatively impact runtime performance.
367  std::chrono::milliseconds{5000}
368  };
369 
370  auto ret = gst_element_set_state(pipeline, new_state);
371  bool result = false; GstState current, pending;
372  switch(ret)
373  {
374  case GST_STATE_CHANGE_FAILURE:
375  result = false; break;
376  case GST_STATE_CHANGE_NO_PREROLL:
377  case GST_STATE_CHANGE_SUCCESS:
378  result = true; break;
379  case GST_STATE_CHANGE_ASYNC:
380  result = GST_STATE_CHANGE_SUCCESS == gst_element_get_state(
381  pipeline,
382  &current,
383  &pending,
384  state_change_timeout.count());
385 
386  if (new_state == GST_STATE_PLAYING)
387  {
388  // Get the video height/width from the video sink
390 #ifdef DEBUG_GST_PIPELINE
391  std::cout << "Dumping pipeline dot file" << std::endl;
392  GST_DEBUG_BIN_TO_DOT_FILE((GstBin*)pipeline, GST_DEBUG_GRAPH_SHOW_ALL, "pipeline");
393 #endif
394  }
395  break;
396  }
397 
398  return result;
399  }
400 
401  bool seek(const std::chrono::microseconds& ms)
402  {
403  is_seeking = true;
404  return gst_element_seek_simple(
405  pipeline,
406  GST_FORMAT_TIME,
407  (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT),
408  ms.count() * 1000);
409  }
410 
412  {
413  if (video_sink != nullptr && g_strcmp0(::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME"), "mirsink") == 0)
414  {
415  g_object_get (video_sink, "height", &video_height, nullptr);
416  g_object_get (video_sink, "width", &video_width, nullptr);
417  std::cout << "video_height: " << video_height << ", video_width: " << video_width << std::endl;
418  signals.on_add_frame_dimension(video_height, video_width);
419  }
420  else
421  std::cerr << "Could not get the height/width of each video frame" << std::endl;
422  }
423 
424  int get_video_height() const
425  {
426  return video_height;
427  }
428 
429  int get_video_width() const
430  {
431  return video_width;
432  }
433 
434  std::string get_file_content_type(const std::string& uri) const
435  {
436  if (uri.empty())
437  return std::string();
438 
439  std::string filename(uri);
440  std::cout << "filename: " << filename << std::endl;
441  size_t pos = uri.find("file://");
442  if (pos != std::string::npos)
443  filename = uri.substr(pos + 7, std::string::npos);
444  else
445  // Anything other than a file, for now claim that the type
446  // is both audio and video.
447  // FIXME: implement true net stream sampling and get the type from GstCaps
448  return std::string("audio/video/");
449 
450 
451  GError *error = nullptr;
452  std::unique_ptr<GFile, void(*)(void *)> file(
453  g_file_new_for_path(filename.c_str()), g_object_unref);
454  std::unique_ptr<GFileInfo, void(*)(void *)> info(
455  g_file_query_info(
456  file.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE ","
457  G_FILE_ATTRIBUTE_ETAG_VALUE, G_FILE_QUERY_INFO_NONE,
458  /* cancellable */ NULL, &error),
459  g_object_unref);
460  if (!info)
461  {
462  std::string error_str(error->message);
463  g_error_free(error);
464 
465  std::cout << "Failed to query the URI for the presence of video content: "
466  << error_str << std::endl;
467  return std::string();
468  }
469 
470  std::string content_type(g_file_info_get_attribute_string(
471  info.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE));
472 
473  return content_type;
474  }
475 
476  bool is_audio_file(const std::string& uri) const
477  {
478  if (uri.empty())
479  return false;
480 
481  if (get_file_content_type(uri).find("audio/") == 0)
482  {
483  std::cout << "Found audio content" << std::endl;
484  return true;
485  }
486 
487  return false;
488  }
489 
490  bool is_video_file(const std::string& uri) const
491  {
492  if (uri.empty())
493  return false;
494 
495  if (get_file_content_type(uri).find("video/") == 0)
496  {
497  std::cout << "Found video content" << std::endl;
498  return true;
499  }
500 
501  return false;
502  }
503 
505  {
506  return file_type;
507  }
508 
509  GstElement* pipeline;
512  SurfaceTextureClientHybris stc_hybris;
513  GstElement* video_sink;
514  uint32_t video_height;
515  uint32_t video_width;
516  core::Connection on_new_message_connection;
518  struct
519  {
520  core::Signal<void> about_to_finish;
521  core::Signal<Bus::Message::Detail::ErrorWarningInfo> on_error;
522  core::Signal<Bus::Message::Detail::ErrorWarningInfo> on_warning;
523  core::Signal<Bus::Message::Detail::ErrorWarningInfo> on_info;
524  core::Signal<Bus::Message::Detail::Tag> on_tag_available;
525  core::Signal<Bus::Message::Detail::StateChanged> on_state_changed;
526  core::Signal<uint64_t> on_seeked_to;
527  core::Signal<void> on_end_of_stream;
528  core::Signal<media::Player::PlaybackStatus> on_playback_status_changed;
529  core::Signal<media::Player::Orientation> on_orientation_changed;
530  core::Signal<uint32_t, uint32_t> on_add_frame_dimension;
531  core::Signal<void> client_disconnected;
532  } signals;
533 };
534 }
535 
536 #endif // GSTREAMER_PLAYBIN_H_
core::Signal< Bus::Message::Detail::StateChanged > on_state_changed
Definition: playbin.h:525
core::Signal< Message > on_new_message
Definition: bus.h:303
core::Signal< Bus::Message::Detail::Tag > on_tag_available
Definition: playbin.h:524
bool set_state_and_wait(GstState new_state)
Definition: playbin.h:360
void on_client_died()
Definition: playbin.h:121
void get_video_dimensions()
Definition: playbin.h:411
void set_audio_stream_role(media::Player::AudioStreamRole new_audio_role)
Definition: playbin.h:301
GstMessageType type
Definition: bus.h:181
Definition: bus.h:33
bool seek(const std::chrono::microseconds &ms)
Definition: playbin.h:401
static void about_to_finish(GstElement *, gpointer user_data)
Definition: playbin.h:66
uint64_t duration() const
Definition: playbin.h:331
core::Signal< void > about_to_finish
Definition: playbin.h:520
void reset_pipeline()
Definition: playbin.h:132
int get_video_width() const
Definition: playbin.h:429
core::Signal< Bus::Message::Detail::ErrorWarningInfo > on_warning
Definition: playbin.h:522
GstElement * video_sink
Definition: playbin.h:513
core::Connection on_new_message_connection
Definition: playbin.h:516
int get_video_height() const
Definition: playbin.h:424
core::Signal< uint64_t > on_seeked_to
Definition: playbin.h:526
void on_new_message(const Bus::Message &message)
Definition: playbin.h:151
core::Signal< media::Player::Orientation > on_orientation_changed
Definition: playbin.h:529
media::Player::Orientation orientation_lut(const gchar *orientation)
Definition: playbin.h:286
SurfaceTextureClientHybris stc_hybris
Definition: playbin.h:512
MediaFileType media_file_type() const
Definition: playbin.h:504
std::string uri() const
Definition: playbin.h:349
void setup_pipeline_for_audio_video()
Definition: playbin.h:200
union gstreamer::Bus::Message::Detail detail
uint32_t video_height
Definition: playbin.h:514
GstElement * pipeline
Definition: playbin.h:509
static std::string get_audio_role_str(media::Player::AudioStreamRole audio_role)
Definition: playbin.h:264
core::Signal< Bus::Message::Detail::ErrorWarningInfo > on_info
Definition: playbin.h:523
core::Signal< media::Player::PlaybackStatus > on_playback_status_changed
Definition: playbin.h:528
core::Signal< void > on_end_of_stream
Definition: playbin.h:527
std::string get_file_content_type(const std::string &uri) const
Definition: playbin.h:434
struct gstreamer::Playbin::@11 signals
void set_uri(const std::string &uri)
Definition: playbin.h:340
struct gstreamer::Bus::Message::Detail::Tag tag
bool is_audio_file(const std::string &uri) const
Definition: playbin.h:476
bool is_video_file(const std::string &uri) const
Definition: playbin.h:490
struct gstreamer::Bus::Message::Detail::ErrorWarningInfo error_warning_info
core::Signal< uint32_t, uint32_t > on_add_frame_dimension
Definition: playbin.h:530
void set_volume(double new_volume)
Definition: playbin.h:258
core::Signal< Bus::Message::Detail::ErrorWarningInfo > on_error
Definition: playbin.h:521
void create_video_sink(uint32_t texture_id)
Definition: playbin.h:240
gstreamer::Bus bus
Definition: playbin.h:510
MediaFileType file_type
Definition: playbin.h:511
core::Signal< void > client_disconnected
Definition: playbin.h:531
uint32_t video_width
Definition: playbin.h:515
uint64_t position() const
Definition: playbin.h:322
static const std::string & pipeline_name()
Definition: playbin.h:60
static void on_client_died_cb(void *context)
Definition: playbin.h:112
gstreamer::Bus & message_bus()
Definition: playbin.h:195
struct gstreamer::Bus::Message::Detail::StateChanged state_changed