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