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 namespace media = core::ubuntu::media;
35 
36 namespace gstreamer
37 {
38 struct Playbin
39 {
40  enum PlayFlags
41  {
42  GST_PLAY_FLAG_VIDEO = (1 << 0),
43  GST_PLAY_FLAG_AUDIO = (1 << 1),
44  GST_PLAY_FLAG_TEXT = (1 << 2)
45  };
46 
48  {
52  };
53 
54  static const std::string& pipeline_name()
55  {
56  static const std::string s{"playbin"};
57  return s;
58  }
59 
60  static void about_to_finish(GstElement*,
61  gpointer user_data)
62  {
63  auto thiz = static_cast<Playbin*>(user_data);
64  thiz->signals.about_to_finish();
65  }
66 
68  : pipeline(gst_element_factory_make("playbin", pipeline_name().c_str())),
69  bus{gst_element_get_bus(pipeline)},
72  bus.on_new_message.connect(
73  std::bind(
75  this,
76  std::placeholders::_1))),
77  is_seeking(false)
78  {
79  if (!pipeline)
80  throw std::runtime_error("Could not create pipeline for playbin.");
81 
82  // Add audio and/or video sink elements depending on environment variables
83  // being set or not set
85 
86  g_signal_connect(
87  pipeline,
88  "about-to-finish",
89  G_CALLBACK(about_to_finish),
90  this
91  );
92 
93  // When a client of media-hub dies, call on_client_died
94  decoding_service_set_client_death_cb(&Playbin::on_client_died_cb, static_cast<void*>(this));
95  }
96 
98  {
99  if (pipeline)
100  gst_object_unref(pipeline);
101  }
102 
103  static void on_client_died_cb(void *context)
104  {
105  if (context)
106  {
107  Playbin *pb = static_cast<Playbin*>(context);
108  pb->on_client_died();
109  }
110  }
111 
113  {
114  std::cout << "Client died, resetting pipeline" << std::endl;
115  // When the client dies, tear down the current pipeline and get it
116  // in a state that is ready for the next client that connects to the
117  // service
118  reset_pipeline();
119  // Signal to the Player class that the client side has disconnected
120  signals.client_disconnected();
121  }
122 
124  {
125  std::cout << __PRETTY_FUNCTION__ << std::endl;
126  auto ret = gst_element_set_state(pipeline, GST_STATE_NULL);
127  switch(ret)
128  {
129  case GST_STATE_CHANGE_FAILURE:
130  std::cout << "Failed to reset the pipeline state. Client reconnect may not function properly." << std::endl;
131  break;
132  case GST_STATE_CHANGE_NO_PREROLL:
133  case GST_STATE_CHANGE_SUCCESS:
134  case GST_STATE_CHANGE_ASYNC:
135  break;
136  default:
137  std::cout << "Failed to reset the pipeline state. Client reconnect may not function properly." << std::endl;
138  }
140  }
141 
142  void on_new_message(const Bus::Message& message)
143  {
144  switch(message.type)
145  {
146  case GST_MESSAGE_ERROR:
147  signals.on_error(message.detail.error_warning_info);
148  break;
149  case GST_MESSAGE_WARNING:
150  signals.on_warning(message.detail.error_warning_info);
151  break;
152  case GST_MESSAGE_INFO:
153  signals.on_info(message.detail.error_warning_info);
154  break;
155  case GST_MESSAGE_TAG:
156  signals.on_tag_available(message.detail.tag);
157  break;
158  case GST_MESSAGE_STATE_CHANGED:
159  signals.on_state_changed(message.detail.state_changed);
160  break;
161  case GST_MESSAGE_ASYNC_DONE:
162  if (is_seeking)
163  {
164  // FIXME: Pass the actual playback time position to the signal call
165  signals.on_seeked_to(0);
166  is_seeking = false;
167  }
168  break;
169  case GST_MESSAGE_EOS:
170  signals.on_end_of_stream();
171  default:
172  break;
173  }
174  }
175 
177  {
178  return bus;
179  }
180 
182  {
183  gint flags;
184  g_object_get (pipeline, "flags", &flags, nullptr);
185  flags |= GST_PLAY_FLAG_AUDIO;
186  flags |= GST_PLAY_FLAG_VIDEO;
187  flags &= ~GST_PLAY_FLAG_TEXT;
188  g_object_set (pipeline, "flags", flags, nullptr);
189 
190  if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME") != nullptr)
191  {
192  auto audio_sink = gst_element_factory_make (
193  ::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME"),
194  "audio-sink");
195 
196  std::cout << "audio_sink: " << ::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME") << std::endl;
197 
198  g_object_set (
199  pipeline,
200  "audio-sink",
201  audio_sink,
202  NULL);
203  }
204 
205  if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") != nullptr)
206  {
207  auto video_sink = gst_element_factory_make (
208  ::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME"),
209  "video-sink");
210 
211  std::cout << "video_sink: " << ::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") << std::endl;
212 
213  g_object_set (
214  pipeline,
215  "video-sink",
216  video_sink,
217  NULL);
218  }
219  }
220 
221  void create_video_sink(uint32_t texture_id)
222  {
223  std::cout << "Creating video sink for texture_id: " << texture_id << std::endl;
224 
225  if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") != nullptr)
226  {
227  GstElement *video_sink = NULL;
228  g_object_get (pipeline, "video_sink", &video_sink, NULL);
229 
230  // Get the service-side BufferQueue (IGraphicBufferProducer) and associate it with
231  // the SurfaceTextureClientHybris instance
232  IGBPWrapperHybris igbp = decoding_service_get_igraphicbufferproducer();
233  SurfaceTextureClientHybris stc = surface_texture_client_create_by_igbp(igbp);
234  // Because mirsink is being loaded, we are definitely doing * hardware rendering.
235  surface_texture_client_set_hardware_rendering (stc, TRUE);
236  g_object_set (G_OBJECT (video_sink), "surface", static_cast<gpointer>(stc), static_cast<char*>(NULL));
237  }
238  }
239 
240  void set_volume(double new_volume)
241  {
242  g_object_set(pipeline, "volume", new_volume, NULL);
243  }
244 
245  uint64_t position() const
246  {
247  int64_t pos = 0;
248  gst_element_query_position (pipeline, GST_FORMAT_TIME, &pos);
249 
250  // FIXME: this should be int64_t, but dbus-cpp doesn't seem to handle it correctly
251  return static_cast<uint64_t>(pos);
252  }
253 
254  uint64_t duration() const
255  {
256  int64_t dur = 0;
257  gst_element_query_duration (pipeline, GST_FORMAT_TIME, &dur);
258 
259  // FIXME: this should be int64_t, but dbus-cpp doesn't seem to handle it correctly
260  return static_cast<uint64_t>(dur);
261  }
262 
263  void set_uri(const std::string& uri)
264  {
265  g_object_set(pipeline, "uri", uri.c_str(), NULL);
266  if (is_video_file(uri))
268  else if (is_audio_file(uri))
270  }
271 
272  std::string uri() const
273  {
274  gchar* data = nullptr;
275  g_object_get(pipeline, "current-uri", &data, nullptr);
276 
277  std::string result((data == nullptr ? "" : data));
278  g_free(data);
279 
280  return result;
281  }
282 
283  bool set_state_and_wait(GstState new_state)
284  {
285  static const std::chrono::nanoseconds state_change_timeout
286  {
287  // We choose a quite high value here as tests are run under valgrind
288  // and gstreamer pipeline setup/state changes take longer in that scenario.
289  // The value does not negatively impact runtime performance.
290  std::chrono::milliseconds{5000}
291  };
292 
293  auto ret = gst_element_set_state(pipeline, new_state);
294  bool result = false; GstState current, pending;
295  switch(ret)
296  {
297  case GST_STATE_CHANGE_FAILURE:
298  result = false; break;
299  case GST_STATE_CHANGE_NO_PREROLL:
300  case GST_STATE_CHANGE_SUCCESS:
301  result = true; break;
302  case GST_STATE_CHANGE_ASYNC:
303  result = GST_STATE_CHANGE_SUCCESS == gst_element_get_state(
304  pipeline,
305  &current,
306  &pending,
307  state_change_timeout.count());
308  break;
309  }
310 
311  return result;
312  }
313 
314  bool seek(const std::chrono::microseconds& ms)
315  {
316  is_seeking = true;
317  return gst_element_seek_simple(
318  pipeline,
319  GST_FORMAT_TIME,
320  (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT),
321  ms.count() * 1000);
322  }
323 
324  std::string get_file_content_type(const std::string& uri) const
325  {
326  if (uri.empty())
327  return std::string();
328 
329  std::string filename(uri);
330  std::cout << "filename: " << filename << std::endl;
331  size_t pos = uri.find("file://");
332  if (pos != std::string::npos)
333  filename = uri.substr(pos + 7, std::string::npos);
334  else
335  // Anything other than a file, for now claim that the type
336  // is both audio and video.
337  // FIXME: implement true net stream sampling and get the type from GstCaps
338  return std::string("audio/video/");
339 
340 
341  GError *error = nullptr;
342  std::unique_ptr<GFile, void(*)(void *)> file(
343  g_file_new_for_path(filename.c_str()), g_object_unref);
344  std::unique_ptr<GFileInfo, void(*)(void *)> info(
345  g_file_query_info(
346  file.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE ","
347  G_FILE_ATTRIBUTE_ETAG_VALUE, G_FILE_QUERY_INFO_NONE,
348  /* cancellable */ NULL, &error),
349  g_object_unref);
350  if (!info)
351  {
352  std::string error_str(error->message);
353  g_error_free(error);
354 
355  std::cout << "Failed to query the URI for the presence of video content: "
356  << error_str << std::endl;
357  return std::string();
358  }
359 
360  std::string content_type(g_file_info_get_attribute_string(
361  info.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE));
362 
363  return content_type;
364  }
365 
366  bool is_audio_file(const std::string& uri) const
367  {
368  if (uri.empty())
369  return false;
370 
371  if (get_file_content_type(uri).find("audio/") == 0)
372  {
373  std::cout << "Found audio content" << std::endl;
374  return true;
375  }
376 
377  return false;
378  }
379 
380  bool is_video_file(const std::string& uri) const
381  {
382  if (uri.empty())
383  return false;
384 
385  if (get_file_content_type(uri).find("video/") == 0)
386  {
387  std::cout << "Found video content" << std::endl;
388  return true;
389  }
390 
391  return false;
392  }
393 
395  {
396  return file_type;
397  }
398 
399  GstElement* pipeline;
402  SurfaceTextureClientHybris stc_hybris;
403  core::Connection on_new_message_connection;
405  struct
406  {
407  core::Signal<void> about_to_finish;
408  core::Signal<Bus::Message::Detail::ErrorWarningInfo> on_error;
409  core::Signal<Bus::Message::Detail::ErrorWarningInfo> on_warning;
410  core::Signal<Bus::Message::Detail::ErrorWarningInfo> on_info;
411  core::Signal<Bus::Message::Detail::Tag> on_tag_available;
412  core::Signal<Bus::Message::Detail::StateChanged> on_state_changed;
413  core::Signal<uint64_t> on_seeked_to;
414  core::Signal<void> on_end_of_stream;
415  core::Signal<media::Player::PlaybackStatus> on_playback_status_changed;
416  core::Signal<void> client_disconnected;
417  } signals;
418 };
419 }
420 
421 #endif // GSTREAMER_PLAYBIN_H_
core::Signal< Bus::Message::Detail::StateChanged > on_state_changed
Definition: playbin.h:412
core::Signal< Message > on_new_message
Definition: bus.h:303
core::Signal< Bus::Message::Detail::Tag > on_tag_available
Definition: playbin.h:411
bool set_state_and_wait(GstState new_state)
Definition: playbin.h:283
void on_client_died()
Definition: playbin.h:112
GstMessageType type
Definition: bus.h:181
bool seek(const std::chrono::microseconds &ms)
Definition: playbin.h:314
static void about_to_finish(GstElement *, gpointer user_data)
Definition: playbin.h:60
uint64_t duration() const
Definition: playbin.h:254
core::Signal< void > about_to_finish
Definition: playbin.h:407
void reset_pipeline()
Definition: playbin.h:123
core::Signal< Bus::Message::Detail::ErrorWarningInfo > on_warning
Definition: playbin.h:409
core::Connection on_new_message_connection
Definition: playbin.h:403
core::Signal< uint64_t > on_seeked_to
Definition: playbin.h:413
void on_new_message(const Bus::Message &message)
Definition: playbin.h:142
SurfaceTextureClientHybris stc_hybris
Definition: playbin.h:402
MediaFileType media_file_type() const
Definition: playbin.h:394
std::string uri() const
Definition: playbin.h:272
void setup_pipeline_for_audio_video()
Definition: playbin.h:181
union gstreamer::Bus::Message::Detail detail
GstElement * pipeline
Definition: playbin.h:399
core::Signal< Bus::Message::Detail::ErrorWarningInfo > on_info
Definition: playbin.h:410
core::Signal< media::Player::PlaybackStatus > on_playback_status_changed
Definition: playbin.h:415
core::Signal< void > on_end_of_stream
Definition: playbin.h:414
std::string get_file_content_type(const std::string &uri) const
Definition: playbin.h:324
struct gstreamer::Playbin::@11 signals
void set_uri(const std::string &uri)
Definition: playbin.h:263
struct gstreamer::Bus::Message::Detail::Tag tag
bool is_audio_file(const std::string &uri) const
Definition: playbin.h:366
bool is_video_file(const std::string &uri) const
Definition: playbin.h:380
struct gstreamer::Bus::Message::Detail::ErrorWarningInfo error_warning_info
void set_volume(double new_volume)
Definition: playbin.h:240
core::Signal< Bus::Message::Detail::ErrorWarningInfo > on_error
Definition: playbin.h:408
void create_video_sink(uint32_t texture_id)
Definition: playbin.h:221
gstreamer::Bus bus
Definition: playbin.h:400
MediaFileType file_type
Definition: playbin.h:401
core::Signal< void > client_disconnected
Definition: playbin.h:416
uint64_t position() const
Definition: playbin.h:245
static const std::string & pipeline_name()
Definition: playbin.h:54
static void on_client_died_cb(void *context)
Definition: playbin.h:103
gstreamer::Bus & message_bus()
Definition: playbin.h:176
struct gstreamer::Bus::Message::Detail::StateChanged state_changed