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/surface_texture_client_hybris.h>
26 #include <hybris/media/media_codec_layer.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 
104  {
105  if (pipeline)
106  gst_object_unref(pipeline);
107  }
108 
109  void reset()
110  {
111  std::cout << "Client died, resetting pipeline" << std::endl;
112  // When the client dies, tear down the current pipeline and get it
113  // in a state that is ready for the next client that connects to the
114  // service
115  reset_pipeline();
116  // Signal to the Player class that the client side has disconnected
117  signals.client_disconnected();
118  }
119 
121  {
122  std::cout << __PRETTY_FUNCTION__ << std::endl;
123  auto ret = gst_element_set_state(pipeline, GST_STATE_NULL);
124  switch(ret)
125  {
126  case GST_STATE_CHANGE_FAILURE:
127  std::cout << "Failed to reset the pipeline state. Client reconnect may not function properly." << std::endl;
128  break;
129  case GST_STATE_CHANGE_NO_PREROLL:
130  case GST_STATE_CHANGE_SUCCESS:
131  case GST_STATE_CHANGE_ASYNC:
132  break;
133  default:
134  std::cout << "Failed to reset the pipeline state. Client reconnect may not function properly." << std::endl;
135  }
137  }
138 
139  void on_new_message(const Bus::Message& message)
140  {
141  switch(message.type)
142  {
143  case GST_MESSAGE_ERROR:
144  signals.on_error(message.detail.error_warning_info);
145  break;
146  case GST_MESSAGE_WARNING:
147  signals.on_warning(message.detail.error_warning_info);
148  break;
149  case GST_MESSAGE_INFO:
150  signals.on_info(message.detail.error_warning_info);
151  break;
152  case GST_MESSAGE_TAG:
153  {
154  gchar *orientation;
155  if (gst_tag_list_get_string(message.detail.tag.tag_list, "image-orientation", &orientation))
156  {
157  // If the image-orientation tag is in the GstTagList, signal the Engine
158  signals.on_orientation_changed(orientation_lut(orientation));
159  g_free (orientation);
160  }
161 
162  signals.on_tag_available(message.detail.tag);
163  }
164  break;
165  case GST_MESSAGE_STATE_CHANGED:
166  signals.on_state_changed(message.detail.state_changed);
167  break;
168  case GST_MESSAGE_ASYNC_DONE:
169  if (is_seeking)
170  {
171  // FIXME: Pass the actual playback time position to the signal call
172  signals.on_seeked_to(0);
173  is_seeking = false;
174  }
175  break;
176  case GST_MESSAGE_EOS:
177  signals.on_end_of_stream();
178  default:
179  break;
180  }
181  }
182 
184  {
185  return bus;
186  }
187 
189  {
190  gint flags;
191  g_object_get (pipeline, "flags", &flags, nullptr);
192  flags |= GST_PLAY_FLAG_AUDIO;
193  flags |= GST_PLAY_FLAG_VIDEO;
194  flags &= ~GST_PLAY_FLAG_TEXT;
195  g_object_set (pipeline, "flags", flags, nullptr);
196 
197  if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME") != nullptr)
198  {
199  auto audio_sink = gst_element_factory_make (
200  ::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME"),
201  "audio-sink");
202 
203  std::cout << "audio_sink: " << ::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME") << std::endl;
204 
205  g_object_set (
206  pipeline,
207  "audio-sink",
208  audio_sink,
209  NULL);
210  }
211 
212  if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") != nullptr)
213  {
214  video_sink = gst_element_factory_make (
215  ::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME"),
216  "video-sink");
217 
218  std::cout << "video_sink: " << ::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") << std::endl;
219 
220  g_object_set (
221  pipeline,
222  "video-sink",
223  video_sink,
224  NULL);
225  }
226  }
227 
228  void create_video_sink(uint32_t texture_id)
229  {
230  std::cout << "Creating video sink for texture_id: " << texture_id << std::endl;
231 
232  if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") != nullptr)
233  {
234  g_object_get (pipeline, "video_sink", &video_sink, NULL);
235 
236  // Get the service-side BufferQueue (IGraphicBufferProducer) and associate it with
237  // the SurfaceTextureClientHybris instance
238  IGBPWrapperHybris igbp = decoding_service_get_igraphicbufferproducer();
239  SurfaceTextureClientHybris stc = surface_texture_client_create_by_igbp(igbp);
240  // Because mirsink is being loaded, we are definitely doing * hardware rendering.
241  surface_texture_client_set_hardware_rendering (stc, TRUE);
242  g_object_set (G_OBJECT (video_sink), "surface", static_cast<gpointer>(stc), static_cast<char*>(NULL));
243  }
244  }
245 
246  void set_volume(double new_volume)
247  {
248  g_object_set (pipeline, "volume", new_volume, NULL);
249  }
250 
252  static std::string get_audio_role_str(media::Player::AudioStreamRole audio_role)
253  {
254  switch (audio_role)
255  {
256  case media::Player::AudioStreamRole::alarm:
257  return "alarm";
258  break;
259  case media::Player::AudioStreamRole::alert:
260  return "alert";
261  break;
262  case media::Player::AudioStreamRole::multimedia:
263  return "multimedia";
264  break;
265  case media::Player::AudioStreamRole::phone:
266  return "phone";
267  break;
268  default:
269  return "multimedia";
270  break;
271  }
272  }
273 
274  media::Player::Orientation orientation_lut(const gchar *orientation)
275  {
276  if (g_strcmp0(orientation, "rotate-0") == 0)
277  return media::Player::Orientation::rotate0;
278  else if (g_strcmp0(orientation, "rotate-90") == 0)
279  return media::Player::Orientation::rotate90;
280  else if (g_strcmp0(orientation, "rotate-180") == 0)
281  return media::Player::Orientation::rotate180;
282  else if (g_strcmp0(orientation, "rotate-270") == 0)
283  return media::Player::Orientation::rotate270;
284  else
285  return media::Player::Orientation::rotate0;
286  }
287 
290  {
291  GstElement *audio_sink = NULL;
292  g_object_get (pipeline, "audio-sink", &audio_sink, NULL);
293 
294  std::string role_str("props,media.role=" + get_audio_role_str(new_audio_role));
295  std::cout << "Audio stream role: " << role_str << std::endl;
296 
297  GstStructure *props = gst_structure_from_string (role_str.c_str(), NULL);
298  if (audio_sink != nullptr && props != nullptr)
299  g_object_set (audio_sink, "stream-properties", props, NULL);
300  else
301  {
302  std::cerr <<
303  "Warning: couldn't set audio stream role - couldn't get audio_sink from pipeline" <<
304  std::endl;
305  }
306 
307  gst_structure_free (props);
308  }
309 
310  uint64_t position() const
311  {
312  int64_t pos = 0;
313  gst_element_query_position (pipeline, GST_FORMAT_TIME, &pos);
314 
315  // FIXME: this should be int64_t, but dbus-cpp doesn't seem to handle it correctly
316  return static_cast<uint64_t>(pos);
317  }
318 
319  uint64_t duration() const
320  {
321  int64_t dur = 0;
322  gst_element_query_duration (pipeline, GST_FORMAT_TIME, &dur);
323 
324  // FIXME: this should be int64_t, but dbus-cpp doesn't seem to handle it correctly
325  return static_cast<uint64_t>(dur);
326  }
327 
328  void set_uri(const std::string& uri)
329  {
330  g_object_set(pipeline, "uri", uri.c_str(), NULL);
331  if (is_video_file(uri))
333  else if (is_audio_file(uri))
335  }
336 
337  std::string uri() const
338  {
339  gchar* data = nullptr;
340  g_object_get(pipeline, "current-uri", &data, nullptr);
341 
342  std::string result((data == nullptr ? "" : data));
343  g_free(data);
344 
345  return result;
346  }
347 
348  bool set_state_and_wait(GstState new_state)
349  {
350  static const std::chrono::nanoseconds state_change_timeout
351  {
352  // We choose a quite high value here as tests are run under valgrind
353  // and gstreamer pipeline setup/state changes take longer in that scenario.
354  // The value does not negatively impact runtime performance.
355  std::chrono::milliseconds{5000}
356  };
357 
358  auto ret = gst_element_set_state(pipeline, new_state);
359  bool result = false; GstState current, pending;
360  switch(ret)
361  {
362  case GST_STATE_CHANGE_FAILURE:
363  result = false; break;
364  case GST_STATE_CHANGE_NO_PREROLL:
365  case GST_STATE_CHANGE_SUCCESS:
366  result = true; break;
367  case GST_STATE_CHANGE_ASYNC:
368  result = GST_STATE_CHANGE_SUCCESS == gst_element_get_state(
369  pipeline,
370  &current,
371  &pending,
372  state_change_timeout.count());
373 
374  if (new_state == GST_STATE_PLAYING)
375  {
376  // Get the video height/width from the video sink
378 #ifdef DEBUG_GST_PIPELINE
379  std::cout << "Dumping pipeline dot file" << std::endl;
380  GST_DEBUG_BIN_TO_DOT_FILE((GstBin*)pipeline, GST_DEBUG_GRAPH_SHOW_ALL, "pipeline");
381 #endif
382  }
383  break;
384  }
385 
386  return result;
387  }
388 
389  bool seek(const std::chrono::microseconds& ms)
390  {
391  is_seeking = true;
392  return gst_element_seek_simple(
393  pipeline,
394  GST_FORMAT_TIME,
395  (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT),
396  ms.count() * 1000);
397  }
398 
400  {
401  if (video_sink != nullptr && g_strcmp0(::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME"), "mirsink") == 0)
402  {
403  g_object_get (video_sink, "height", &video_height, nullptr);
404  g_object_get (video_sink, "width", &video_width, nullptr);
405  std::cout << "video_height: " << video_height << ", video_width: " << video_width << std::endl;
406  signals.on_add_frame_dimension(video_height, video_width);
407  }
408  else
409  std::cerr << "Could not get the height/width of each video frame" << std::endl;
410  }
411 
412  int get_video_height() const
413  {
414  return video_height;
415  }
416 
417  int get_video_width() const
418  {
419  return video_width;
420  }
421 
422  std::string get_file_content_type(const std::string& uri) const
423  {
424  if (uri.empty())
425  return std::string();
426 
427  std::string filename(uri);
428  std::cout << "filename: " << filename << std::endl;
429  size_t pos = uri.find("file://");
430  if (pos != std::string::npos)
431  filename = uri.substr(pos + 7, std::string::npos);
432  else
433  // Anything other than a file, for now claim that the type
434  // is both audio and video.
435  // FIXME: implement true net stream sampling and get the type from GstCaps
436  return std::string("audio/video/");
437 
438 
439  GError *error = nullptr;
440  std::unique_ptr<GFile, void(*)(void *)> file(
441  g_file_new_for_path(filename.c_str()), g_object_unref);
442  std::unique_ptr<GFileInfo, void(*)(void *)> info(
443  g_file_query_info(
444  file.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE ","
445  G_FILE_ATTRIBUTE_ETAG_VALUE, G_FILE_QUERY_INFO_NONE,
446  /* cancellable */ NULL, &error),
447  g_object_unref);
448  if (!info)
449  {
450  std::string error_str(error->message);
451  g_error_free(error);
452 
453  std::cout << "Failed to query the URI for the presence of video content: "
454  << error_str << std::endl;
455  return std::string();
456  }
457 
458  std::string content_type(g_file_info_get_attribute_string(
459  info.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE));
460 
461  return content_type;
462  }
463 
464  bool is_audio_file(const std::string& uri) const
465  {
466  if (uri.empty())
467  return false;
468 
469  if (get_file_content_type(uri).find("audio/") == 0)
470  {
471  std::cout << "Found audio content" << std::endl;
472  return true;
473  }
474 
475  return false;
476  }
477 
478  bool is_video_file(const std::string& uri) const
479  {
480  if (uri.empty())
481  return false;
482 
483  if (get_file_content_type(uri).find("video/") == 0)
484  {
485  std::cout << "Found video content" << std::endl;
486  return true;
487  }
488 
489  return false;
490  }
491 
493  {
494  return file_type;
495  }
496 
497  GstElement* pipeline;
500  SurfaceTextureClientHybris stc_hybris;
501  GstElement* video_sink;
502  uint32_t video_height;
503  uint32_t video_width;
504  core::Connection on_new_message_connection;
506  struct
507  {
508  core::Signal<void> about_to_finish;
509  core::Signal<Bus::Message::Detail::ErrorWarningInfo> on_error;
510  core::Signal<Bus::Message::Detail::ErrorWarningInfo> on_warning;
511  core::Signal<Bus::Message::Detail::ErrorWarningInfo> on_info;
512  core::Signal<Bus::Message::Detail::Tag> on_tag_available;
513  core::Signal<Bus::Message::Detail::StateChanged> on_state_changed;
514  core::Signal<uint64_t> on_seeked_to;
515  core::Signal<void> on_end_of_stream;
516  core::Signal<media::Player::PlaybackStatus> on_playback_status_changed;
517  core::Signal<media::Player::Orientation> on_orientation_changed;
518  core::Signal<uint32_t, uint32_t> on_add_frame_dimension;
519  core::Signal<void> client_disconnected;
520  } signals;
521 };
522 }
523 
524 #endif // GSTREAMER_PLAYBIN_H_
core::Signal< Bus::Message::Detail::StateChanged > on_state_changed
Definition: playbin.h:513
core::Signal< Message > on_new_message
Definition: bus.h:303
core::Signal< Bus::Message::Detail::Tag > on_tag_available
Definition: playbin.h:512
bool set_state_and_wait(GstState new_state)
Definition: playbin.h:348
void get_video_dimensions()
Definition: playbin.h:399
void set_audio_stream_role(media::Player::AudioStreamRole new_audio_role)
Definition: playbin.h:289
GstMessageType type
Definition: bus.h:181
Definition: bus.h:33
bool seek(const std::chrono::microseconds &ms)
Definition: playbin.h:389
static void about_to_finish(GstElement *, gpointer user_data)
Definition: playbin.h:66
uint64_t duration() const
Definition: playbin.h:319
core::Signal< void > about_to_finish
Definition: playbin.h:508
void reset_pipeline()
Definition: playbin.h:120
int get_video_width() const
Definition: playbin.h:417
core::Signal< Bus::Message::Detail::ErrorWarningInfo > on_warning
Definition: playbin.h:510
GstElement * video_sink
Definition: playbin.h:501
core::Connection on_new_message_connection
Definition: playbin.h:504
int get_video_height() const
Definition: playbin.h:412
core::Signal< uint64_t > on_seeked_to
Definition: playbin.h:514
void on_new_message(const Bus::Message &message)
Definition: playbin.h:139
core::Signal< media::Player::Orientation > on_orientation_changed
Definition: playbin.h:517
media::Player::Orientation orientation_lut(const gchar *orientation)
Definition: playbin.h:274
SurfaceTextureClientHybris stc_hybris
Definition: playbin.h:500
MediaFileType media_file_type() const
Definition: playbin.h:492
std::string uri() const
Definition: playbin.h:337
void setup_pipeline_for_audio_video()
Definition: playbin.h:188
union gstreamer::Bus::Message::Detail detail
uint32_t video_height
Definition: playbin.h:502
GstElement * pipeline
Definition: playbin.h:497
static std::string get_audio_role_str(media::Player::AudioStreamRole audio_role)
Definition: playbin.h:252
core::Signal< Bus::Message::Detail::ErrorWarningInfo > on_info
Definition: playbin.h:511
core::Signal< media::Player::PlaybackStatus > on_playback_status_changed
Definition: playbin.h:516
core::Signal< void > on_end_of_stream
Definition: playbin.h:515
std::string get_file_content_type(const std::string &uri) const
Definition: playbin.h:422
struct gstreamer::Playbin::@11 signals
void set_uri(const std::string &uri)
Definition: playbin.h:328
struct gstreamer::Bus::Message::Detail::Tag tag
bool is_audio_file(const std::string &uri) const
Definition: playbin.h:464
bool is_video_file(const std::string &uri) const
Definition: playbin.h:478
struct gstreamer::Bus::Message::Detail::ErrorWarningInfo error_warning_info
core::Signal< uint32_t, uint32_t > on_add_frame_dimension
Definition: playbin.h:518
void set_volume(double new_volume)
Definition: playbin.h:246
core::Signal< Bus::Message::Detail::ErrorWarningInfo > on_error
Definition: playbin.h:509
void create_video_sink(uint32_t texture_id)
Definition: playbin.h:228
gstreamer::Bus bus
Definition: playbin.h:498
MediaFileType file_type
Definition: playbin.h:499
core::Signal< void > client_disconnected
Definition: playbin.h:519
uint32_t video_width
Definition: playbin.h:503
uint64_t position() const
Definition: playbin.h:310
static const std::string & pipeline_name()
Definition: playbin.h:60
gstreamer::Bus & message_bus()
Definition: playbin.h:183
struct gstreamer::Bus::Message::Detail::StateChanged state_changed