Music Hub  ..
A session-wide music playback service
playbin.cpp
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 
20 
22 
23 #if defined(MEDIA_HUB_HAVE_HYBRIS_MEDIA_COMPAT_LAYER)
24 #include <hybris/media/surface_texture_client_hybris.h>
25 #include <hybris/media/media_codec_layer.h>
26 
27 #include <utility>
28 
29 namespace
30 {
31 void setup_video_sink_for_buffer_streaming(GstElement* video_sink)
32 {
33  // Get the service-side BufferQueue (IGraphicBufferProducer) and associate it with
34  // the SurfaceTextureClientHybris instance
35  IGBPWrapperHybris igbp = decoding_service_get_igraphicbufferproducer();
36  SurfaceTextureClientHybris stc = surface_texture_client_create_by_igbp(igbp);
37  // Because mirsink is being loaded, we are definitely doing * hardware rendering.
38  surface_texture_client_set_hardware_rendering (stc, TRUE);
39  g_object_set (G_OBJECT (video_sink), "surface", static_cast<gpointer>(stc), static_cast<char*>(NULL));
40 }
41 }
42 #else // MEDIA_HUB_HAVE_HYBRIS_MEDIA_COMPAT_LAYER
43 namespace
44 {
45 void setup_video_sink_for_buffer_streaming(GstElement*)
46 {
47  throw core::ubuntu::media::Player::Error::OutOfProcessBufferStreamingNotSupported{};
48 }
49 }
50 #endif // MEDIA_HUB_HAVE_HYBRIS_MEDIA_COMPAT_LAYER
51 
52 namespace
53 {
54 bool is_mir_video_sink()
55 {
56  return g_strcmp0(::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME"), "mirsink") == 0;
57 }
58 }
59 // Uncomment to generate a dot file at the time that the pipeline
60 // goes to the PLAYING state. Make sure to export GST_DEBUG_DUMP_DOT_DIR
61 // before starting media-hub-server. To convert the dot file to something
62 // other image format, use: dot pipeline.dot -Tpng -o pipeline.png
63 //#define DEBUG_GST_PIPELINE
64 
65 namespace media = core::ubuntu::media;
67 
69 {
70  static const std::string s{"playbin"};
71  return s;
72 }
73 
74 void gstreamer::Playbin::about_to_finish(GstElement*, gpointer user_data)
75 {
76  auto thiz = static_cast<Playbin*>(user_data);
77  thiz->signals.about_to_finish();
78 }
79 
81  GstElement *source,
82  gpointer user_data)
83 {
84  if (user_data == nullptr)
85  return;
86 
87  static_cast<Playbin*>(user_data)->setup_source(source);
88 }
89 
91  : pipeline(gst_element_factory_make("playbin", pipeline_name().c_str())),
92  bus{gst_element_get_bus(pipeline)},
93  file_type(MEDIA_FILE_TYPE_NONE),
94  video_sink(nullptr),
95  on_new_message_connection(
96  bus.on_new_message.connect(
97  std::bind(
99  this,
100  std::placeholders::_1))),
101  is_seeking(false),
102  previous_position(0),
103  player_lifetime(media::Player::Lifetime::normal),
104  is_eos(false)
105 {
106  if (!pipeline)
107  throw std::runtime_error("Could not create pipeline for playbin.");
108 
109  is_eos = false;
110 
111  // Add audio and/or video sink elements depending on environment variables
112  // being set or not set
113  setup_pipeline_for_audio_video();
114 
115  g_signal_connect(
116  pipeline,
117  "about-to-finish",
118  G_CALLBACK(about_to_finish),
119  this
120  );
121 
122  g_signal_connect(
123  pipeline,
124  "source-setup",
125  G_CALLBACK(source_setup),
126  this
127  );
128 }
129 
131 {
132  if (pipeline)
133  gst_object_unref(pipeline);
134 }
135 
137 {
138  std::cout << "Client died, resetting pipeline" << std::endl;
139  // When the client dies, tear down the current pipeline and get it
140  // in a state that is ready for the next client that connects to the
141  // service
142 
143  // Don't reset the pipeline if we want to resume
144  if (player_lifetime != media::Player::Lifetime::resumable) {
145  reset_pipeline();
146  }
147  // Signal to the Player class that the client side has disconnected
148  signals.client_disconnected();
149 }
150 
152 {
153  std::cout << __PRETTY_FUNCTION__ << std::endl;
154  auto ret = gst_element_set_state(pipeline, GST_STATE_NULL);
155  switch(ret)
156  {
157  case GST_STATE_CHANGE_FAILURE:
158  std::cout << "Failed to reset the pipeline state. Client reconnect may not function properly." << std::endl;
159  break;
160  case GST_STATE_CHANGE_NO_PREROLL:
161  case GST_STATE_CHANGE_SUCCESS:
162  case GST_STATE_CHANGE_ASYNC:
163  break;
164  default:
165  std::cout << "Failed to reset the pipeline state. Client reconnect may not function properly." << std::endl;
166  }
167  file_type = MEDIA_FILE_TYPE_NONE;
168 }
169 
171 {
172  switch(message.type)
173  {
174  case GST_MESSAGE_ERROR:
175  signals.on_error(message.detail.error_warning_info);
176  break;
177  case GST_MESSAGE_WARNING:
178  signals.on_warning(message.detail.error_warning_info);
179  break;
180  case GST_MESSAGE_INFO:
181  signals.on_info(message.detail.error_warning_info);
182  break;
183  case GST_MESSAGE_TAG:
184  {
185  gchar *orientation;
186  if (gst_tag_list_get_string(message.detail.tag.tag_list, "image-orientation", &orientation))
187  {
188  // If the image-orientation tag is in the GstTagList, signal the Engine
189  signals.on_orientation_changed(orientation_lut(orientation));
190  g_free (orientation);
191  }
192 
193  signals.on_tag_available(message.detail.tag);
194  }
195  break;
196  case GST_MESSAGE_STATE_CHANGED:
197  signals.on_state_changed(message.detail.state_changed);
198  break;
199  case GST_MESSAGE_ASYNC_DONE:
200  if (is_seeking)
201  {
202  // FIXME: Pass the actual playback time position to the signal call
203  signals.on_seeked_to(0);
204  is_seeking = false;
205  }
206  break;
207  case GST_MESSAGE_EOS:
208  is_eos = true;
209  signals.on_end_of_stream();
210  default:
211  break;
212  }
213 }
214 
216 {
217  return bus;
218 }
219 
221 {
222  gint flags;
223  g_object_get (pipeline, "flags", &flags, nullptr);
224  flags |= GST_PLAY_FLAG_AUDIO;
225  flags |= GST_PLAY_FLAG_VIDEO;
226  flags &= ~GST_PLAY_FLAG_TEXT;
227  g_object_set (pipeline, "flags", flags, nullptr);
228 
229  if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME") != nullptr)
230  {
231  auto audio_sink = gst_element_factory_make (
232  ::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME"),
233  "audio-sink");
234 
235  std::cout << "audio_sink: " << ::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME") << std::endl;
236 
237  g_object_set (
238  pipeline,
239  "audio-sink",
240  audio_sink,
241  NULL);
242  }
243 
244  if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") != nullptr)
245  {
246  video_sink = gst_element_factory_make (
247  ::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME"),
248  "video-sink");
249 
250  std::cout << "video_sink: " << ::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") << std::endl;
251 
252  g_object_set (
253  pipeline,
254  "video-sink",
255  video_sink,
256  NULL);
257  }
258 }
259 
261 {
262  if (not video_sink) throw std::logic_error
263  {
264  "No video sink configured for the current pipeline"
265  };
266 
267  setup_video_sink_for_buffer_streaming(video_sink);
268 }
269 
270 void gstreamer::Playbin::set_volume(double new_volume)
271 {
272  g_object_set (pipeline, "volume", new_volume, NULL);
273 }
274 
277 {
278  switch (audio_role)
279  {
280  case media::Player::AudioStreamRole::alarm:
281  return "alarm";
282  break;
283  case media::Player::AudioStreamRole::alert:
284  return "alert";
285  break;
286  case media::Player::AudioStreamRole::multimedia:
287  return "multimedia";
288  break;
289  case media::Player::AudioStreamRole::phone:
290  return "phone";
291  break;
292  default:
293  return "multimedia";
294  break;
295  }
296 }
297 
299 {
300  if (g_strcmp0(orientation, "rotate-0") == 0)
301  return media::Player::Orientation::rotate0;
302  else if (g_strcmp0(orientation, "rotate-90") == 0)
303  return media::Player::Orientation::rotate90;
304  else if (g_strcmp0(orientation, "rotate-180") == 0)
305  return media::Player::Orientation::rotate180;
306  else if (g_strcmp0(orientation, "rotate-270") == 0)
307  return media::Player::Orientation::rotate270;
308  else
309  return media::Player::Orientation::rotate0;
310 }
311 
314 {
315  GstElement *audio_sink = NULL;
316  g_object_get (pipeline, "audio-sink", &audio_sink, NULL);
317 
318  std::string role_str("props,media.role=" + get_audio_role_str(new_audio_role));
319  std::cout << "Audio stream role: " << role_str << std::endl;
320 
321  GstStructure *props = gst_structure_from_string (role_str.c_str(), NULL);
322  if (audio_sink != nullptr && props != nullptr)
323  g_object_set (audio_sink, "stream-properties", props, NULL);
324  else
325  {
326  std::cerr <<
327  "Warning: couldn't set audio stream role - couldn't get audio_sink from pipeline" <<
328  std::endl;
329  }
330 
331  gst_structure_free (props);
332 }
333 
335 {
336  player_lifetime = lifetime;
337 }
338 
340 {
341  int64_t pos = 0;
342  gst_element_query_position (pipeline, GST_FORMAT_TIME, &pos);
343 
344  // This prevents a 0 position from being reported to the app which happens while seeking.
345  // This is covering over a GStreamer issue
346  if ((static_cast<uint64_t>(pos) < duration()) && is_seeking && pos == 0)
347  {
348  return previous_position;
349  }
350 
351  // Save the current position to use just in case it's needed the next time position is
352  // requested
353  previous_position = static_cast<uint64_t>(pos);
354 
355  // FIXME: this should be int64_t, but dbus-cpp doesn't seem to handle it correctly
356  return static_cast<uint64_t>(pos);
357 }
358 
360 {
361  int64_t dur = 0;
362  gst_element_query_duration (pipeline, GST_FORMAT_TIME, &dur);
363 
364  // FIXME: this should be int64_t, but dbus-cpp doesn't seem to handle it correctly
365  return static_cast<uint64_t>(dur);
366 }
367 
369  const std::string& uri,
371  bool do_pipeline_reset)
372 {
373  if (do_pipeline_reset)
374  reset_pipeline();
375 
376  g_object_set(pipeline, "uri", uri.c_str(), NULL);
377  if (is_video_file(uri))
378  file_type = MEDIA_FILE_TYPE_VIDEO;
379  else if (is_audio_file(uri))
380  file_type = MEDIA_FILE_TYPE_AUDIO;
381 
382  request_headers = headers;
383 }
384 
385 void gstreamer::Playbin::setup_source(GstElement *source)
386 {
387  if (source == NULL || request_headers.empty())
388  return;
389 
390  if (request_headers.find("Cookie") != request_headers.end()) {
391  if (g_object_class_find_property(G_OBJECT_GET_CLASS(source),
392  "cookies") != NULL) {
393  gchar ** cookies = g_strsplit(request_headers["Cookie"].c_str(), ";", 0);
394  g_object_set(source, "cookies", cookies, NULL);
395  g_strfreev(cookies);
396  }
397  }
398 
399  if (request_headers.find("User-Agent") != request_headers.end()) {
400  if (g_object_class_find_property(G_OBJECT_GET_CLASS(source),
401  "user-agent") != NULL) {
402  g_object_set(source, "user-agent", request_headers["User-Agent"].c_str(), NULL);
403  }
404  }
405 }
406 
407 std::string gstreamer::Playbin::uri() const
408 {
409  gchar* data = nullptr;
410  g_object_get(pipeline, "current-uri", &data, nullptr);
411 
412  std::string result((data == nullptr ? "" : data));
413  g_free(data);
414 
415  return result;
416 }
417 
419 {
420  static const std::chrono::nanoseconds state_change_timeout
421  {
422  // We choose a quite high value here as tests are run under valgrind
423  // and gstreamer pipeline setup/state changes take longer in that scenario.
424  // The value does not negatively impact runtime performance.
425  std::chrono::milliseconds{5000}
426  };
427 
428  auto ret = gst_element_set_state(pipeline, new_state);
429 
430  std::cout << __PRETTY_FUNCTION__ << ": requested state change." << std::endl;
431 
432  bool result = false; GstState current, pending;
433  switch(ret)
434  {
435  case GST_STATE_CHANGE_FAILURE:
436  result = false; break;
437  case GST_STATE_CHANGE_NO_PREROLL:
438  case GST_STATE_CHANGE_SUCCESS:
439  result = true; break;
440  case GST_STATE_CHANGE_ASYNC:
441  result = GST_STATE_CHANGE_SUCCESS == gst_element_get_state(
442  pipeline,
443  &current,
444  &pending,
445  state_change_timeout.count());
446  break;
447  }
448 
449  // We only should query the pipeline if we actually succeeded in
450  // setting the requested state. Also don't send on_video_dimensions_changed
451  // signal during EOS.
452  if (result && new_state == GST_STATE_PLAYING && !is_eos)
453  {
454  // Get the video height/width from the video sink
455  try
456  {
457  signals.on_video_dimensions_changed(get_video_dimensions());
458  }
459  catch (const std::exception& e)
460  {
461  std::cerr << "Problem querying video dimensions: " << e.what() << std::endl;
462  }
463  catch (...)
464  {
465  std::cerr << "Problem querying video dimensions." << std::endl;
466  }
467 
468 #ifdef DEBUG_GST_PIPELINE
469  std::cout << "Dumping pipeline dot file" << std::endl;
470  GST_DEBUG_BIN_TO_DOT_FILE((GstBin*)pipeline, GST_DEBUG_GRAPH_SHOW_ALL, "pipeline");
471 #endif
472  }
473 
474  return result;
475 }
476 
477 bool gstreamer::Playbin::seek(const std::chrono::microseconds& ms)
478 {
479  is_seeking = true;
480  return gst_element_seek_simple(
481  pipeline,
482  GST_FORMAT_TIME,
483  (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT),
484  ms.count() * 1000);
485 }
486 
488 {
489  if (not video_sink || not is_mir_video_sink())
490  throw std::runtime_error
491  {
492  "Missing video sink or video sink does not support query of width and height."
493  };
494 
495  // Initialize to default value prior to querying actual values from the sink.
496  uint32_t video_width = 0, video_height = 0;
497  g_object_get (video_sink, "height", &video_height, nullptr);
498  g_object_get (video_sink, "width", &video_width, nullptr);
499  // TODO(tvoss): We should probably check here if width and height are valid.
501  {
504  };
505 }
506 
507 std::string gstreamer::Playbin::get_file_content_type(const std::string& uri) const
508 {
509  if (uri.empty())
510  return std::string();
511 
512  std::string filename(uri);
513  std::cout << "filename: " << filename << std::endl;
514  size_t pos = uri.find("file://");
515  if (pos != std::string::npos)
516  filename = uri.substr(pos + 7, std::string::npos);
517  else
518  // Anything other than a file, for now claim that the type
519  // is both audio and video.
520  // FIXME: implement true net stream sampling and get the type from GstCaps
521  return std::string("audio/video/");
522 
523 
524  GError *error = nullptr;
525  std::unique_ptr<GFile, void(*)(void *)> file(
526  g_file_new_for_path(filename.c_str()), g_object_unref);
527  std::unique_ptr<GFileInfo, void(*)(void *)> info(
528  g_file_query_info(
529  file.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE ","
530  G_FILE_ATTRIBUTE_ETAG_VALUE, G_FILE_QUERY_INFO_NONE,
531  /* cancellable */ NULL, &error),
532  g_object_unref);
533  if (!info)
534  {
535  std::string error_str(error->message);
536  g_error_free(error);
537 
538  std::cout << "Failed to query the URI for the presence of video content: "
539  << error_str << std::endl;
540  return std::string();
541  }
542 
543  std::string content_type(g_file_info_get_attribute_string(
544  info.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE));
545 
546  return content_type;
547 }
548 
549 bool gstreamer::Playbin::is_audio_file(const std::string& uri) const
550 {
551  if (uri.empty())
552  return false;
553 
554  if (get_file_content_type(uri).find("audio/") == 0)
555  {
556  std::cout << "Found audio content" << std::endl;
557  return true;
558  }
559 
560  return false;
561 }
562 
563 bool gstreamer::Playbin::is_video_file(const std::string& uri) const
564 {
565  if (uri.empty())
566  return false;
567 
568  if (get_file_content_type(uri).find("video/") == 0)
569  {
570  std::cout << "Found video content" << std::endl;
571  return true;
572  }
573 
574  return false;
575 }
576 
578 {
579  return file_type;
580 }
static std::string get_audio_role_str(core::ubuntu::media::Player::AudioStreamRole audio_role)
Definition: playbin.cpp:276
bool set_state_and_wait(GstState new_state)
Definition: playbin.cpp:418
static void source_setup(GstElement *, GstElement *source, gpointer user_data)
Definition: playbin.cpp:80
void setup_source(GstElement *source)
Definition: playbin.cpp:385
std::tuple< Height, Width > Dimensions
Height and Width of a video.
Definition: dimensions.h:139
void set_uri(const std::string &uri, const core::ubuntu::media::Player::HeadersType &headers, bool do_pipeline_reset=true)
Definition: playbin.cpp:368
core::ubuntu::media::video::Dimensions get_video_dimensions() const
Definition: playbin.cpp:487
GstMessageType type
Definition: bus.h:181
bool seek(const std::chrono::microseconds &ms)
Definition: playbin.cpp:477
uint64_t duration() const
Definition: playbin.cpp:359
void reset_pipeline()
Definition: playbin.cpp:151
static const std::string & pipeline_name()
Definition: playbin.cpp:68
void set_lifetime(core::ubuntu::media::Player::Lifetime)
Definition: playbin.cpp:334
struct gstreamer::Playbin::@12 signals
void set_audio_stream_role(core::ubuntu::media::Player::AudioStreamRole new_audio_role)
Definition: playbin.cpp:313
std::map< std::string, std::string > HeadersType
Definition: player.h:49
void on_new_message(const Bus::Message &message)
Definition: playbin.cpp:170
core::ubuntu::media::Player::Orientation orientation_lut(const gchar *orientation)
Definition: playbin.cpp:298
gstreamer::Bus & message_bus()
Definition: playbin.cpp:215
core::Signal< void > about_to_finish
Definition: playbin.h:123
std::string uri() const
Definition: playbin.cpp:407
void setup_pipeline_for_audio_video()
Definition: playbin.cpp:220
union gstreamer::Bus::Message::Detail detail
GstElement * pipeline
Definition: playbin.h:111
std::string get_file_content_type(const std::string &uri) const
Definition: playbin.cpp:507
struct gstreamer::Bus::Message::Detail::Tag tag
bool is_audio_file(const std::string &uri) const
Definition: playbin.cpp:549
bool is_video_file(const std::string &uri) const
Definition: playbin.cpp:563
struct gstreamer::Bus::Message::Detail::ErrorWarningInfo error_warning_info
void set_volume(double new_volume)
Definition: playbin.cpp:270
void create_video_sink(uint32_t texture_id)
Definition: playbin.cpp:260
uint64_t position() const
Definition: playbin.cpp:339
IntWrapper is a type-safe integer that allows for encoding/enforcing semantics by means of tags...
Definition: dimensions.h:68
MediaFileType media_file_type() const
Definition: playbin.cpp:577
struct gstreamer::Bus::Message::Detail::StateChanged state_changed