Merge lp:~thomas-voss/media-hub/move-playbin-implementation-to-cpp-file into lp:media-hub

Proposed by Thomas Voß
Status: Merged
Approved by: Ricardo Mendoza
Approved revision: 109
Merged at revision: 118
Proposed branch: lp:~thomas-voss/media-hub/move-playbin-implementation-to-cpp-file
Merge into: lp:media-hub
Prerequisite: lp:~thomas-voss/media-hub/introduce-recorder-observer-interface
Diff against target: 1177 lines (+608/-495)
4 files modified
src/core/media/CMakeLists.txt (+2/-0)
src/core/media/gstreamer/engine.cpp (+1/-1)
src/core/media/gstreamer/playbin.cpp (+556/-0)
src/core/media/gstreamer/playbin.h (+49/-494)
To merge this branch: bzr merge lp:~thomas-voss/media-hub/move-playbin-implementation-to-cpp-file
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Jim Hodapp (community) code Approve
Review via email: mp+242890@code.launchpad.net

Commit message

Move gstreamer::Playbin implementation to its own cpp file, thus internalizing the Hybris setup portions.
Make sure that media-hub-service knows about media::Player::Error::OutOfProcessBufferStreamingNotSupported by linking with media-hub-client.

Description of the change

Move gstreamer::Playbin implementation to its own cpp file, thus internalizing the Hybris setup portions.
Make sure that media-hub-service knows about media::Player::Error::OutOfProcessBufferStreamingNotSupported by linking with media-hub-client.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
104. By Thomas Voß

Remerge prereq branch.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Jim Hodapp (jhodapp) wrote :

Looks good

review: Approve (code)
105. By Thomas Voß

[ Jim Hodapp ]
* Resubmitting with prerequisite branch (LP: #1331041)
[ Justin McPherson ]
* Resubmitting with prerequisite branch (LP: #1331041)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
106. By Thomas Voß

[ Jim Hodapp ]
* Error reporting all the way up to the app level from the playbin
  pipeline.
[ Ubuntu daily release ]
* New rebuild forced
[ Jim Hodapp ]
* Don't auto-resume playback of videos after a phone call ends. (LP:
  #1411273)
[ Ubuntu daily release ]
* New rebuild forced
[ Ricardo Salveti de Araujo ]
* service_implementation: adding debug for call started/ended signals.
  Make sure account and connection are available when setting up
  account manager (patch from Gustavo Boiko). call_monitor: don't
  check caps when hooking up on/off signals, until bug 1409125 is
  fixed. Enable parallel building . (LP: #1409125)
[ Jim Hodapp ]
* Pause playback when recording begins. (LP: #1398047)
[ Ricardo Salveti de Araujo ]
* call_monitor.cpp: waiting for bridge to be up, and also protecting
  the on_change call (LP: #1408137)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
107. By Thomas Voß

Merge prereq branch.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Francis Ginther (fginther) wrote :

The jenkins node for the i386 build failed, I've restarted a new ci run.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
108. By Thomas Voß

Merge prereq branch.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
109. By Thomas Voß

* debian/control:
  - Removing pre-depends that are not required
  - Bumping standards-version to 3.9.6
[ Ricardo Salveti de Araujo ]
* Migrating tests to use ogg instead of mp3/avi removed:
  tests/h264.avi tests/test.mp3 added: tests/test-audio-1.ogg
  tests/test-video.ogg tests/test.mp3 renamed: tests/test.ogg =>
  tests/test-audio.ogg

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/core/media/CMakeLists.txt'
2--- src/core/media/CMakeLists.txt 2015-03-12 11:42:10 +0000
3+++ src/core/media/CMakeLists.txt 2015-03-12 11:42:10 +0000
4@@ -93,6 +93,7 @@
5 recorder_observer.cpp
6 hybris_recorder_observer.cpp
7 gstreamer/engine.cpp
8+ gstreamer/playbin.cpp
9
10 player_skeleton.cpp
11 player_implementation.cpp
12@@ -105,6 +106,7 @@
13 target_link_libraries(
14 media-hub-service
15
16+ media-hub-client
17 media-hub-common
18 call-monitor
19 ${DBUS_LIBRARIES}
20
21=== modified file 'src/core/media/gstreamer/engine.cpp'
22--- src/core/media/gstreamer/engine.cpp 2015-03-12 11:42:10 +0000
23+++ src/core/media/gstreamer/engine.cpp 2015-03-12 11:42:10 +0000
24@@ -333,7 +333,7 @@
25
26 bool gstreamer::Engine::open_resource_for_uri(const media::Track::UriType& uri)
27 {
28- d->playbin.set_uri(uri);
29+ d->playbin.set_uri(uri, core::ubuntu::media::Player::HeadersType{});
30 return true;
31 }
32
33
34=== added file 'src/core/media/gstreamer/playbin.cpp'
35--- src/core/media/gstreamer/playbin.cpp 1970-01-01 00:00:00 +0000
36+++ src/core/media/gstreamer/playbin.cpp 2015-03-12 11:42:10 +0000
37@@ -0,0 +1,556 @@
38+/*
39+ * Copyright © 2013 Canonical Ltd.
40+ *
41+ * This program is free software: you can redistribute it and/or modify it
42+ * under the terms of the GNU Lesser General Public License version 3,
43+ * as published by the Free Software Foundation.
44+ *
45+ * This program is distributed in the hope that it will be useful,
46+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
47+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
48+ * GNU Lesser General Public License for more details.
49+ *
50+ * You should have received a copy of the GNU Lesser General Public License
51+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
52+ *
53+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
54+ */
55+
56+#include <core/media/gstreamer/playbin.h>
57+
58+#include <core/media/gstreamer/engine.h>
59+
60+#if defined(MEDIA_HUB_HAVE_HYBRIS_MEDIA_COMPAT_LAYER)
61+#include <hybris/media/surface_texture_client_hybris.h>
62+#include <hybris/media/media_codec_layer.h>
63+
64+namespace
65+{
66+void setup_video_sink_for_buffer_streaming(GstElement* video_sink)
67+{
68+ // Get the service-side BufferQueue (IGraphicBufferProducer) and associate it with
69+ // the SurfaceTextureClientHybris instance
70+ IGBPWrapperHybris igbp = decoding_service_get_igraphicbufferproducer();
71+ SurfaceTextureClientHybris stc = surface_texture_client_create_by_igbp(igbp);
72+ // Because mirsink is being loaded, we are definitely doing * hardware rendering.
73+ surface_texture_client_set_hardware_rendering (stc, TRUE);
74+ g_object_set (G_OBJECT (video_sink), "surface", static_cast<gpointer>(stc), static_cast<char*>(NULL));
75+}
76+}
77+#else // MEDIA_HUB_HAVE_HYBRIS_MEDIA_COMPAT_LAYER
78+namespace
79+{
80+void setup_video_sink_for_buffer_streaming(GstElement*)
81+{
82+ throw core::ubuntu::media::Player::Error::OutOfProcessBufferStreamingNotSupported{};
83+}
84+}
85+#endif // MEDIA_HUB_HAVE_HYBRIS_MEDIA_COMPAT_LAYER
86+
87+namespace
88+{
89+bool is_mir_video_sink()
90+{
91+ return g_strcmp0(::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME"), "mirsink") == 0;
92+}
93+}
94+// Uncomment to generate a dot file at the time that the pipeline
95+// goes to the PLAYING state. Make sure to export GST_DEBUG_DUMP_DOT_DIR
96+// before starting media-hub-server. To convert the dot file to something
97+// other image format, use: dot pipeline.dot -Tpng -o pipeline.png
98+//#define DEBUG_GST_PIPELINE
99+
100+namespace media = core::ubuntu::media;
101+namespace video = core::ubuntu::media::video;
102+
103+const std::string& gstreamer::Playbin::pipeline_name()
104+{
105+ static const std::string s{"playbin"};
106+ return s;
107+}
108+
109+void gstreamer::Playbin::about_to_finish(GstElement*, gpointer user_data)
110+{
111+ auto thiz = static_cast<Playbin*>(user_data);
112+ thiz->signals.about_to_finish();
113+}
114+
115+void gstreamer::Playbin::source_setup(GstElement*,
116+ GstElement *source,
117+ gpointer user_data)
118+{
119+ if (user_data == nullptr)
120+ return;
121+
122+ static_cast<Playbin*>(user_data)->setup_source(source);
123+}
124+
125+gstreamer::Playbin::Playbin()
126+ : pipeline(gst_element_factory_make("playbin", pipeline_name().c_str())),
127+ bus{gst_element_get_bus(pipeline)},
128+ file_type(MEDIA_FILE_TYPE_NONE),
129+ video_sink(nullptr),
130+ on_new_message_connection(
131+ bus.on_new_message.connect(
132+ std::bind(
133+ &Playbin::on_new_message,
134+ this,
135+ std::placeholders::_1))),
136+ is_seeking(false),
137+ player_lifetime(media::Player::Lifetime::normal)
138+{
139+ if (!pipeline)
140+ throw std::runtime_error("Could not create pipeline for playbin.");
141+
142+ // Add audio and/or video sink elements depending on environment variables
143+ // being set or not set
144+ setup_pipeline_for_audio_video();
145+
146+ g_signal_connect(
147+ pipeline,
148+ "about-to-finish",
149+ G_CALLBACK(about_to_finish),
150+ this
151+ );
152+
153+ g_signal_connect(
154+ pipeline,
155+ "source-setup",
156+ G_CALLBACK(source_setup),
157+ this
158+ );
159+}
160+
161+gstreamer::Playbin::~Playbin()
162+{
163+ if (pipeline)
164+ gst_object_unref(pipeline);
165+}
166+
167+void gstreamer::Playbin::reset()
168+{
169+ std::cout << "Client died, resetting pipeline" << std::endl;
170+ // When the client dies, tear down the current pipeline and get it
171+ // in a state that is ready for the next client that connects to the
172+ // service
173+
174+ // Don't reset the pipeline if we want to resume
175+ if (player_lifetime != media::Player::Lifetime::resumable) {
176+ reset_pipeline();
177+ }
178+ // Signal to the Player class that the client side has disconnected
179+ signals.client_disconnected();
180+}
181+
182+void gstreamer::Playbin::reset_pipeline()
183+{
184+ std::cout << __PRETTY_FUNCTION__ << std::endl;
185+ auto ret = gst_element_set_state(pipeline, GST_STATE_NULL);
186+ switch(ret)
187+ {
188+ case GST_STATE_CHANGE_FAILURE:
189+ std::cout << "Failed to reset the pipeline state. Client reconnect may not function properly." << std::endl;
190+ break;
191+ case GST_STATE_CHANGE_NO_PREROLL:
192+ case GST_STATE_CHANGE_SUCCESS:
193+ case GST_STATE_CHANGE_ASYNC:
194+ break;
195+ default:
196+ std::cout << "Failed to reset the pipeline state. Client reconnect may not function properly." << std::endl;
197+ }
198+ file_type = MEDIA_FILE_TYPE_NONE;
199+}
200+
201+void gstreamer::Playbin::on_new_message(const Bus::Message& message)
202+{
203+ switch(message.type)
204+ {
205+ case GST_MESSAGE_ERROR:
206+ signals.on_error(message.detail.error_warning_info);
207+ break;
208+ case GST_MESSAGE_WARNING:
209+ signals.on_warning(message.detail.error_warning_info);
210+ break;
211+ case GST_MESSAGE_INFO:
212+ signals.on_info(message.detail.error_warning_info);
213+ break;
214+ case GST_MESSAGE_TAG:
215+ {
216+ gchar *orientation;
217+ if (gst_tag_list_get_string(message.detail.tag.tag_list, "image-orientation", &orientation))
218+ {
219+ // If the image-orientation tag is in the GstTagList, signal the Engine
220+ signals.on_orientation_changed(orientation_lut(orientation));
221+ g_free (orientation);
222+ }
223+
224+ signals.on_tag_available(message.detail.tag);
225+ }
226+ break;
227+ case GST_MESSAGE_STATE_CHANGED:
228+ signals.on_state_changed(message.detail.state_changed);
229+ break;
230+ case GST_MESSAGE_ASYNC_DONE:
231+ if (is_seeking)
232+ {
233+ // FIXME: Pass the actual playback time position to the signal call
234+ signals.on_seeked_to(0);
235+ is_seeking = false;
236+ }
237+ break;
238+ case GST_MESSAGE_EOS:
239+ signals.on_end_of_stream();
240+ default:
241+ break;
242+ }
243+}
244+
245+gstreamer::Bus& gstreamer::Playbin::message_bus()
246+{
247+ return bus;
248+}
249+
250+void gstreamer::Playbin::setup_pipeline_for_audio_video()
251+{
252+ gint flags;
253+ g_object_get (pipeline, "flags", &flags, nullptr);
254+ flags |= GST_PLAY_FLAG_AUDIO;
255+ flags |= GST_PLAY_FLAG_VIDEO;
256+ flags &= ~GST_PLAY_FLAG_TEXT;
257+ g_object_set (pipeline, "flags", flags, nullptr);
258+
259+ if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME") != nullptr)
260+ {
261+ auto audio_sink = gst_element_factory_make (
262+ ::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME"),
263+ "audio-sink");
264+
265+ std::cout << "audio_sink: " << ::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME") << std::endl;
266+
267+ g_object_set (
268+ pipeline,
269+ "audio-sink",
270+ audio_sink,
271+ NULL);
272+ }
273+
274+ if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") != nullptr)
275+ {
276+ video_sink = gst_element_factory_make (
277+ ::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME"),
278+ "video-sink");
279+
280+ std::cout << "video_sink: " << ::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") << std::endl;
281+
282+ g_object_set (
283+ pipeline,
284+ "video-sink",
285+ video_sink,
286+ NULL);
287+ }
288+}
289+
290+void gstreamer::Playbin::create_video_sink(uint32_t)
291+{
292+ if (not video_sink) throw std::logic_error
293+ {
294+ "No video sink configured for the current pipeline"
295+ };
296+
297+ setup_video_sink_for_buffer_streaming(video_sink);
298+}
299+
300+void gstreamer::Playbin::set_volume(double new_volume)
301+{
302+ g_object_set (pipeline, "volume", new_volume, NULL);
303+}
304+
305+/** Translate the AudioStreamRole enum into a string */
306+std::string gstreamer::Playbin::get_audio_role_str(media::Player::AudioStreamRole audio_role)
307+{
308+ switch (audio_role)
309+ {
310+ case media::Player::AudioStreamRole::alarm:
311+ return "alarm";
312+ break;
313+ case media::Player::AudioStreamRole::alert:
314+ return "alert";
315+ break;
316+ case media::Player::AudioStreamRole::multimedia:
317+ return "multimedia";
318+ break;
319+ case media::Player::AudioStreamRole::phone:
320+ return "phone";
321+ break;
322+ default:
323+ return "multimedia";
324+ break;
325+ }
326+}
327+
328+media::Player::Orientation gstreamer::Playbin::orientation_lut(const gchar *orientation)
329+{
330+ if (g_strcmp0(orientation, "rotate-0") == 0)
331+ return media::Player::Orientation::rotate0;
332+ else if (g_strcmp0(orientation, "rotate-90") == 0)
333+ return media::Player::Orientation::rotate90;
334+ else if (g_strcmp0(orientation, "rotate-180") == 0)
335+ return media::Player::Orientation::rotate180;
336+ else if (g_strcmp0(orientation, "rotate-270") == 0)
337+ return media::Player::Orientation::rotate270;
338+ else
339+ return media::Player::Orientation::rotate0;
340+}
341+
342+/** Sets the new audio stream role on the pulsesink in playbin */
343+void gstreamer::Playbin::set_audio_stream_role(media::Player::AudioStreamRole new_audio_role)
344+{
345+ GstElement *audio_sink = NULL;
346+ g_object_get (pipeline, "audio-sink", &audio_sink, NULL);
347+
348+ std::string role_str("props,media.role=" + get_audio_role_str(new_audio_role));
349+ std::cout << "Audio stream role: " << role_str << std::endl;
350+
351+ GstStructure *props = gst_structure_from_string (role_str.c_str(), NULL);
352+ if (audio_sink != nullptr && props != nullptr)
353+ g_object_set (audio_sink, "stream-properties", props, NULL);
354+ else
355+ {
356+ std::cerr <<
357+ "Warning: couldn't set audio stream role - couldn't get audio_sink from pipeline" <<
358+ std::endl;
359+ }
360+
361+ gst_structure_free (props);
362+}
363+
364+void gstreamer::Playbin::set_lifetime(media::Player::Lifetime lifetime)
365+{
366+ player_lifetime = lifetime;
367+}
368+
369+uint64_t gstreamer::Playbin::position() const
370+{
371+ int64_t pos = 0;
372+ gst_element_query_position (pipeline, GST_FORMAT_TIME, &pos);
373+
374+ // FIXME: this should be int64_t, but dbus-cpp doesn't seem to handle it correctly
375+ return static_cast<uint64_t>(pos);
376+}
377+
378+uint64_t gstreamer::Playbin::duration() const
379+{
380+ int64_t dur = 0;
381+ gst_element_query_duration (pipeline, GST_FORMAT_TIME, &dur);
382+
383+ // FIXME: this should be int64_t, but dbus-cpp doesn't seem to handle it correctly
384+ return static_cast<uint64_t>(dur);
385+}
386+
387+void gstreamer::Playbin::set_uri(
388+ const std::string& uri,
389+ const core::ubuntu::media::Player::HeadersType& headers = core::ubuntu::media::Player::HeadersType())
390+{
391+ reset_pipeline();
392+
393+ g_object_set(pipeline, "uri", uri.c_str(), NULL);
394+ if (is_video_file(uri))
395+ file_type = MEDIA_FILE_TYPE_VIDEO;
396+ else if (is_audio_file(uri))
397+ file_type = MEDIA_FILE_TYPE_AUDIO;
398+
399+ request_headers = headers;
400+}
401+
402+void gstreamer::Playbin::setup_source(GstElement *source)
403+{
404+ if (source == NULL || request_headers.empty())
405+ return;
406+
407+ if (request_headers.find("Cookie") != request_headers.end()) {
408+ if (g_object_class_find_property(G_OBJECT_GET_CLASS(source),
409+ "cookies") != NULL) {
410+ gchar ** cookies = g_strsplit(request_headers["Cookie"].c_str(), ";", 0);
411+ g_object_set(source, "cookies", cookies, NULL);
412+ g_strfreev(cookies);
413+ }
414+ }
415+
416+ if (request_headers.find("User-Agent") != request_headers.end()) {
417+ if (g_object_class_find_property(G_OBJECT_GET_CLASS(source),
418+ "user-agent") != NULL) {
419+ g_object_set(source, "user-agent", request_headers["User-Agent"].c_str(), NULL);
420+ }
421+ }
422+}
423+
424+std::string gstreamer::Playbin::uri() const
425+{
426+ gchar* data = nullptr;
427+ g_object_get(pipeline, "current-uri", &data, nullptr);
428+
429+ std::string result((data == nullptr ? "" : data));
430+ g_free(data);
431+
432+ return result;
433+}
434+
435+bool gstreamer::Playbin::set_state_and_wait(GstState new_state)
436+{
437+ static const std::chrono::nanoseconds state_change_timeout
438+ {
439+ // We choose a quite high value here as tests are run under valgrind
440+ // and gstreamer pipeline setup/state changes take longer in that scenario.
441+ // The value does not negatively impact runtime performance.
442+ std::chrono::milliseconds{5000}
443+ };
444+
445+ auto ret = gst_element_set_state(pipeline, new_state);
446+ bool result = false; GstState current, pending;
447+ switch(ret)
448+ {
449+ case GST_STATE_CHANGE_FAILURE:
450+ result = false; break;
451+ case GST_STATE_CHANGE_NO_PREROLL:
452+ case GST_STATE_CHANGE_SUCCESS:
453+ result = true; break;
454+ case GST_STATE_CHANGE_ASYNC:
455+ result = GST_STATE_CHANGE_SUCCESS == gst_element_get_state(
456+ pipeline,
457+ &current,
458+ &pending,
459+ state_change_timeout.count());
460+ break;
461+ }
462+
463+ // We only should query the pipeline if we actually succeeded in
464+ // setting the requested state.
465+ if (result && new_state == GST_STATE_PLAYING)
466+ {
467+ // Get the video height/width from the video sink
468+ try
469+ {
470+ signals.on_video_dimensions_changed(get_video_dimensions());
471+ }
472+ catch (const std::exception& e)
473+ {
474+ std::cerr << "Problem querying video dimensions: " << e.what() << std::endl;
475+ }
476+ catch (...)
477+ {
478+ std::cerr << "Problem querying video dimensions." << std::endl;
479+ }
480+
481+#ifdef DEBUG_GST_PIPELINE
482+ std::cout << "Dumping pipeline dot file" << std::endl;
483+ GST_DEBUG_BIN_TO_DOT_FILE((GstBin*)pipeline, GST_DEBUG_GRAPH_SHOW_ALL, "pipeline");
484+#endif
485+ }
486+
487+ return result;
488+}
489+
490+bool gstreamer::Playbin::seek(const std::chrono::microseconds& ms)
491+{
492+ is_seeking = true;
493+ return gst_element_seek_simple(
494+ pipeline,
495+ GST_FORMAT_TIME,
496+ (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT),
497+ ms.count() * 1000);
498+}
499+
500+core::ubuntu::media::video::Dimensions gstreamer::Playbin::get_video_dimensions() const
501+{
502+ if (not video_sink || not is_mir_video_sink())
503+ throw std::runtime_error
504+ {
505+ "Missing video sink or video sink does not support query of width and height."
506+ };
507+
508+ // Initialize to default value prior to querying actual values from the sink.
509+ uint32_t video_width = 0, video_height = 0;
510+ g_object_get (video_sink, "height", &video_height, nullptr);
511+ g_object_get (video_sink, "width", &video_width, nullptr);
512+ // TODO(tvoss): We should probably check here if width and height are valid.
513+ return core::ubuntu::media::video::Dimensions
514+ {
515+ core::ubuntu::media::video::Height{video_height},
516+ core::ubuntu::media::video::Width{video_width}
517+ };
518+}
519+
520+std::string gstreamer::Playbin::get_file_content_type(const std::string& uri) const
521+{
522+ if (uri.empty())
523+ return std::string();
524+
525+ std::string filename(uri);
526+ std::cout << "filename: " << filename << std::endl;
527+ size_t pos = uri.find("file://");
528+ if (pos != std::string::npos)
529+ filename = uri.substr(pos + 7, std::string::npos);
530+ else
531+ // Anything other than a file, for now claim that the type
532+ // is both audio and video.
533+ // FIXME: implement true net stream sampling and get the type from GstCaps
534+ return std::string("audio/video/");
535+
536+
537+ GError *error = nullptr;
538+ std::unique_ptr<GFile, void(*)(void *)> file(
539+ g_file_new_for_path(filename.c_str()), g_object_unref);
540+ std::unique_ptr<GFileInfo, void(*)(void *)> info(
541+ g_file_query_info(
542+ file.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE ","
543+ G_FILE_ATTRIBUTE_ETAG_VALUE, G_FILE_QUERY_INFO_NONE,
544+ /* cancellable */ NULL, &error),
545+ g_object_unref);
546+ if (!info)
547+ {
548+ std::string error_str(error->message);
549+ g_error_free(error);
550+
551+ std::cout << "Failed to query the URI for the presence of video content: "
552+ << error_str << std::endl;
553+ return std::string();
554+ }
555+
556+ std::string content_type(g_file_info_get_attribute_string(
557+ info.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE));
558+
559+ return content_type;
560+}
561+
562+bool gstreamer::Playbin::is_audio_file(const std::string& uri) const
563+{
564+ if (uri.empty())
565+ return false;
566+
567+ if (get_file_content_type(uri).find("audio/") == 0)
568+ {
569+ std::cout << "Found audio content" << std::endl;
570+ return true;
571+ }
572+
573+ return false;
574+}
575+
576+bool gstreamer::Playbin::is_video_file(const std::string& uri) const
577+{
578+ if (uri.empty())
579+ return false;
580+
581+ if (get_file_content_type(uri).find("video/") == 0)
582+ {
583+ std::cout << "Found video content" << std::endl;
584+ return true;
585+ }
586+
587+ return false;
588+}
589+
590+gstreamer::Playbin::MediaFileType gstreamer::Playbin::media_file_type() const
591+{
592+ return file_type;
593+}
594
595=== modified file 'src/core/media/gstreamer/playbin.h'
596--- src/core/media/gstreamer/playbin.h 2015-03-12 11:42:10 +0000
597+++ src/core/media/gstreamer/playbin.h 2015-03-12 11:42:10 +0000
598@@ -19,12 +19,11 @@
599 #ifndef GSTREAMER_PLAYBIN_H_
600 #define GSTREAMER_PLAYBIN_H_
601
602+#include <core/media/player.h>
603+
604 #include "bus.h"
605 #include "../mpris/player.h"
606
607-#include <hybris/media/surface_texture_client_hybris.h>
608-#include <hybris/media/media_codec_layer.h>
609-
610 #include <gio/gio.h>
611 #include <gst/gst.h>
612
613@@ -37,8 +36,6 @@
614 // other image format, use: dot pipeline.dot -Tpng -o pipeline.png
615 //#define DEBUG_GST_PIPELINE
616
617-namespace media = core::ubuntu::media;
618-
619 namespace gstreamer
620 {
621 struct Playbin
622@@ -57,508 +54,66 @@
623 MEDIA_FILE_TYPE_VIDEO
624 };
625
626- static const std::string& pipeline_name()
627- {
628- static const std::string s{"playbin"};
629- return s;
630- }
631-
632- static void about_to_finish(GstElement*,
633- gpointer user_data)
634- {
635- auto thiz = static_cast<Playbin*>(user_data);
636- thiz->signals.about_to_finish();
637- }
638+ static std::string get_audio_role_str(core::ubuntu::media::Player::AudioStreamRole audio_role);
639+
640+ static const std::string& pipeline_name();
641+
642+ static void about_to_finish(GstElement*, gpointer user_data);
643
644 static void source_setup(GstElement*,
645 GstElement *source,
646- gpointer user_data)
647- {
648- if (user_data == nullptr)
649- return;
650-
651- static_cast<Playbin*>(user_data)->setup_source(source);
652- }
653-
654- Playbin()
655- : pipeline(gst_element_factory_make("playbin", pipeline_name().c_str())),
656- bus{gst_element_get_bus(pipeline)},
657- file_type(MEDIA_FILE_TYPE_NONE),
658- video_sink(nullptr),
659- on_new_message_connection(
660- bus.on_new_message.connect(
661- std::bind(
662- &Playbin::on_new_message,
663- this,
664- std::placeholders::_1))),
665- is_seeking(false),
666- player_lifetime(media::Player::Lifetime::normal)
667- {
668- if (!pipeline)
669- throw std::runtime_error("Could not create pipeline for playbin.");
670-
671- // Add audio and/or video sink elements depending on environment variables
672- // being set or not set
673- setup_pipeline_for_audio_video();
674-
675- g_signal_connect(
676- pipeline,
677- "about-to-finish",
678- G_CALLBACK(about_to_finish),
679- this
680- );
681-
682- g_signal_connect(
683- pipeline,
684- "source-setup",
685- G_CALLBACK(source_setup),
686- this
687- );
688-
689- }
690-
691- ~Playbin()
692- {
693- if (pipeline)
694- gst_object_unref(pipeline);
695- }
696-
697- void reset()
698- {
699- std::cout << "Client died, resetting pipeline" << std::endl;
700- // When the client dies, tear down the current pipeline and get it
701- // in a state that is ready for the next client that connects to the
702- // service
703-
704- // Don't reset the pipeline if we want to resume
705- if (player_lifetime != media::Player::Lifetime::resumable) {
706- reset_pipeline();
707- }
708-
709- // Signal to the Player class that the client side has disconnected
710- signals.client_disconnected();
711- }
712-
713- void reset_pipeline()
714- {
715- std::cout << __PRETTY_FUNCTION__ << std::endl;
716- auto ret = gst_element_set_state(pipeline, GST_STATE_NULL);
717- switch(ret)
718- {
719- case GST_STATE_CHANGE_FAILURE:
720- std::cout << "Failed to reset the pipeline state. Client reconnect may not function properly." << std::endl;
721- break;
722- case GST_STATE_CHANGE_NO_PREROLL:
723- case GST_STATE_CHANGE_SUCCESS:
724- case GST_STATE_CHANGE_ASYNC:
725- break;
726- default:
727- std::cout << "Failed to reset the pipeline state. Client reconnect may not function properly." << std::endl;
728- }
729- file_type = MEDIA_FILE_TYPE_NONE;
730- }
731-
732- void on_new_message(const Bus::Message& message)
733- {
734- switch(message.type)
735- {
736- case GST_MESSAGE_ERROR:
737- signals.on_error(message.detail.error_warning_info);
738- break;
739- case GST_MESSAGE_WARNING:
740- signals.on_warning(message.detail.error_warning_info);
741- break;
742- case GST_MESSAGE_INFO:
743- signals.on_info(message.detail.error_warning_info);
744- break;
745- case GST_MESSAGE_TAG:
746- {
747- gchar *orientation;
748- if (gst_tag_list_get_string(message.detail.tag.tag_list, "image-orientation", &orientation))
749- {
750- // If the image-orientation tag is in the GstTagList, signal the Engine
751- signals.on_orientation_changed(orientation_lut(orientation));
752- g_free (orientation);
753- }
754-
755- signals.on_tag_available(message.detail.tag);
756- }
757- break;
758- case GST_MESSAGE_STATE_CHANGED:
759- signals.on_state_changed(message.detail.state_changed);
760- break;
761- case GST_MESSAGE_ASYNC_DONE:
762- if (is_seeking)
763- {
764- // FIXME: Pass the actual playback time position to the signal call
765- signals.on_seeked_to(0);
766- is_seeking = false;
767- }
768- break;
769- case GST_MESSAGE_EOS:
770- signals.on_end_of_stream();
771- default:
772- break;
773- }
774- }
775-
776- gstreamer::Bus& message_bus()
777- {
778- return bus;
779- }
780-
781- void setup_pipeline_for_audio_video()
782- {
783- gint flags;
784- g_object_get (pipeline, "flags", &flags, nullptr);
785- flags |= GST_PLAY_FLAG_AUDIO;
786- flags |= GST_PLAY_FLAG_VIDEO;
787- flags &= ~GST_PLAY_FLAG_TEXT;
788- g_object_set (pipeline, "flags", flags, nullptr);
789-
790- if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME") != nullptr)
791- {
792- auto audio_sink = gst_element_factory_make (
793- ::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME"),
794- "audio-sink");
795-
796- std::cout << "audio_sink: " << ::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME") << std::endl;
797-
798- g_object_set (
799- pipeline,
800- "audio-sink",
801- audio_sink,
802- NULL);
803- }
804-
805- if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") != nullptr)
806- {
807- video_sink = gst_element_factory_make (
808- ::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME"),
809- "video-sink");
810-
811- std::cout << "video_sink: " << ::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") << std::endl;
812-
813- g_object_set (
814- pipeline,
815- "video-sink",
816- video_sink,
817- NULL);
818- }
819- }
820-
821- void create_video_sink(uint32_t texture_id)
822- {
823- std::cout << "Creating video sink for texture_id: " << texture_id << std::endl;
824-
825- if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME") != nullptr)
826- {
827- g_object_get (pipeline, "video_sink", &video_sink, NULL);
828-
829- // Get the service-side BufferQueue (IGraphicBufferProducer) and associate it with
830- // the SurfaceTextureClientHybris instance
831- IGBPWrapperHybris igbp = decoding_service_get_igraphicbufferproducer();
832- SurfaceTextureClientHybris stc = surface_texture_client_create_by_igbp(igbp);
833- // Because mirsink is being loaded, we are definitely doing * hardware rendering.
834- surface_texture_client_set_hardware_rendering (stc, TRUE);
835- g_object_set (G_OBJECT (video_sink), "surface", static_cast<gpointer>(stc), static_cast<char*>(NULL));
836- }
837- }
838-
839- void set_volume(double new_volume)
840- {
841- g_object_set (pipeline, "volume", new_volume, NULL);
842- }
843-
844- /** Translate the AudioStreamRole enum into a string */
845- static std::string get_audio_role_str(media::Player::AudioStreamRole audio_role)
846- {
847- switch (audio_role)
848- {
849- case media::Player::AudioStreamRole::alarm:
850- return "alarm";
851- break;
852- case media::Player::AudioStreamRole::alert:
853- return "alert";
854- break;
855- case media::Player::AudioStreamRole::multimedia:
856- return "multimedia";
857- break;
858- case media::Player::AudioStreamRole::phone:
859- return "phone";
860- break;
861- default:
862- return "multimedia";
863- break;
864- }
865- }
866-
867- media::Player::Orientation orientation_lut(const gchar *orientation)
868- {
869- if (g_strcmp0(orientation, "rotate-0") == 0)
870- return media::Player::Orientation::rotate0;
871- else if (g_strcmp0(orientation, "rotate-90") == 0)
872- return media::Player::Orientation::rotate90;
873- else if (g_strcmp0(orientation, "rotate-180") == 0)
874- return media::Player::Orientation::rotate180;
875- else if (g_strcmp0(orientation, "rotate-270") == 0)
876- return media::Player::Orientation::rotate270;
877- else
878- return media::Player::Orientation::rotate0;
879- }
880+ gpointer user_data);
881+
882+ Playbin();
883+ ~Playbin();
884+
885+ void reset();
886+ void reset_pipeline();
887+
888+ void on_new_message(const Bus::Message& message);
889+
890+ gstreamer::Bus& message_bus();
891+
892+ void setup_pipeline_for_audio_video();
893+
894+ void create_video_sink(uint32_t texture_id);
895+
896+ void set_volume(double new_volume);
897+
898+ void set_lifetime(core::ubuntu::media::Player::Lifetime);
899+ core::ubuntu::media::Player::Orientation orientation_lut(const gchar *orientation);
900
901 /** Sets the new audio stream role on the pulsesink in playbin */
902- void set_audio_stream_role(media::Player::AudioStreamRole new_audio_role)
903- {
904- GstElement *audio_sink = NULL;
905- g_object_get (pipeline, "audio-sink", &audio_sink, NULL);
906-
907- std::string role_str("props,media.role=" + get_audio_role_str(new_audio_role));
908- std::cout << "Audio stream role: " << role_str << std::endl;
909-
910- GstStructure *props = gst_structure_from_string (role_str.c_str(), NULL);
911- if (audio_sink != nullptr && props != nullptr)
912- g_object_set (audio_sink, "stream-properties", props, NULL);
913- else
914- {
915- std::cerr <<
916- "Warning: couldn't set audio stream role - couldn't get audio_sink from pipeline" <<
917- std::endl;
918- }
919-
920- gst_structure_free (props);
921- }
922-
923- void set_lifetime(media::Player::Lifetime lifetime)
924- {
925- player_lifetime = lifetime;
926- }
927-
928- uint64_t position() const
929- {
930- int64_t pos = 0;
931- gst_element_query_position (pipeline, GST_FORMAT_TIME, &pos);
932-
933- // FIXME: this should be int64_t, but dbus-cpp doesn't seem to handle it correctly
934- return static_cast<uint64_t>(pos);
935- }
936-
937- uint64_t duration() const
938- {
939- int64_t dur = 0;
940- gst_element_query_duration (pipeline, GST_FORMAT_TIME, &dur);
941-
942- // FIXME: this should be int64_t, but dbus-cpp doesn't seem to handle it correctly
943- return static_cast<uint64_t>(dur);
944- }
945-
946- void set_uri(const std::string& uri,
947- const core::ubuntu::media::Player::HeadersType& headers = core::ubuntu::media::Player::HeadersType())
948- {
949- reset_pipeline();
950-
951- g_object_set(pipeline, "uri", uri.c_str(), NULL);
952- if (is_video_file(uri))
953- file_type = MEDIA_FILE_TYPE_VIDEO;
954- else if (is_audio_file(uri))
955- file_type = MEDIA_FILE_TYPE_AUDIO;
956-
957- request_headers = headers;
958- }
959-
960- void setup_source(GstElement *source)
961- {
962- if (source == NULL || request_headers.empty())
963- return;
964-
965- if (request_headers.find("Cookie") != request_headers.end()) {
966- if (g_object_class_find_property(G_OBJECT_GET_CLASS(source),
967- "cookies") != NULL) {
968- gchar ** cookies = g_strsplit(request_headers["Cookie"].c_str(), ";", 0);
969- g_object_set(source, "cookies", cookies, NULL);
970- g_strfreev(cookies);
971- }
972- }
973-
974- if (request_headers.find("User-Agent") != request_headers.end()) {
975- if (g_object_class_find_property(G_OBJECT_GET_CLASS(source),
976- "user-agent") != NULL) {
977- g_object_set(source, "user-agent", request_headers["User-Agent"].c_str(), NULL);
978- }
979- }
980- }
981-
982- std::string uri() const
983- {
984- gchar* data = nullptr;
985- g_object_get(pipeline, "current-uri", &data, nullptr);
986-
987- std::string result((data == nullptr ? "" : data));
988- g_free(data);
989-
990- return result;
991- }
992-
993- bool set_state_and_wait(GstState new_state)
994- {
995- static const std::chrono::nanoseconds state_change_timeout
996- {
997- // We choose a quite high value here as tests are run under valgrind
998- // and gstreamer pipeline setup/state changes take longer in that scenario.
999- // The value does not negatively impact runtime performance.
1000- std::chrono::milliseconds{5000}
1001- };
1002-
1003- auto ret = gst_element_set_state(pipeline, new_state);
1004- bool result = false; GstState current, pending;
1005- switch(ret)
1006- {
1007- case GST_STATE_CHANGE_FAILURE:
1008- result = false; break;
1009- case GST_STATE_CHANGE_NO_PREROLL:
1010- case GST_STATE_CHANGE_SUCCESS:
1011- result = true; break;
1012- case GST_STATE_CHANGE_ASYNC:
1013- result = GST_STATE_CHANGE_SUCCESS == gst_element_get_state(
1014- pipeline,
1015- &current,
1016- &pending,
1017- state_change_timeout.count());
1018- break;
1019- }
1020-
1021- // The state change has to have been successful to make
1022- // sure that we indeed reached the requested new state.
1023- if (result && new_state == GST_STATE_PLAYING)
1024- {
1025- // Get the video height/width from the video sink
1026- if (has_video_sink_with_height_and_width())
1027- signals.on_video_dimensions_changed(get_video_dimensions());
1028-#ifdef DEBUG_GST_PIPELINE
1029- std::cout << "Dumping pipeline dot file" << std::endl;
1030- GST_DEBUG_BIN_TO_DOT_FILE((GstBin*)pipeline, GST_DEBUG_GRAPH_SHOW_ALL, "pipeline");
1031-#endif
1032- }
1033-
1034- return result;
1035- }
1036-
1037- bool seek(const std::chrono::microseconds& ms)
1038- {
1039- is_seeking = true;
1040- return gst_element_seek_simple(
1041- pipeline,
1042- GST_FORMAT_TIME,
1043- (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT),
1044- ms.count() * 1000);
1045- }
1046-
1047- bool has_video_sink_with_height_and_width()
1048- {
1049- return video_sink != nullptr && g_strcmp0(::getenv("CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME"), "mirsink") == 0;
1050- }
1051-
1052- core::ubuntu::media::video::Dimensions get_video_dimensions()
1053- {
1054- if (not has_video_sink_with_height_and_width())
1055- throw std::runtime_error{"Could not get the height/width of each video frame"};
1056-
1057- uint32_t video_height = 0, video_width = 0;
1058- g_object_get (video_sink, "height", &video_height, nullptr);
1059- g_object_get (video_sink, "width", &video_width, nullptr);
1060- std::cout << "video_height: " << video_height << ", video_width: " << video_width << std::endl;
1061-
1062- return core::ubuntu::media::video::Dimensions
1063- {
1064- core::ubuntu::media::video::Height{video_height},
1065- core::ubuntu::media::video::Width{video_width}
1066- };
1067- }
1068-
1069- std::string get_file_content_type(const std::string& uri) const
1070- {
1071- if (uri.empty())
1072- return std::string();
1073-
1074- std::string filename(uri);
1075- std::cout << "filename: " << filename << std::endl;
1076- size_t pos = uri.find("file://");
1077- if (pos != std::string::npos)
1078- filename = uri.substr(pos + 7, std::string::npos);
1079- else
1080- // Anything other than a file, for now claim that the type
1081- // is both audio and video.
1082- // FIXME: implement true net stream sampling and get the type from GstCaps
1083- return std::string("audio/video/");
1084-
1085-
1086- GError *error = nullptr;
1087- std::unique_ptr<GFile, void(*)(void *)> file(
1088- g_file_new_for_path(filename.c_str()), g_object_unref);
1089- std::unique_ptr<GFileInfo, void(*)(void *)> info(
1090- g_file_query_info(
1091- file.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE ","
1092- G_FILE_ATTRIBUTE_ETAG_VALUE, G_FILE_QUERY_INFO_NONE,
1093- /* cancellable */ NULL, &error),
1094- g_object_unref);
1095- if (!info)
1096- {
1097- std::string error_str(error->message);
1098- g_error_free(error);
1099-
1100- std::cout << "Failed to query the URI for the presence of video content: "
1101- << error_str << std::endl;
1102- return std::string();
1103- }
1104-
1105- std::string content_type(g_file_info_get_attribute_string(
1106- info.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE));
1107-
1108- return content_type;
1109- }
1110-
1111- bool is_audio_file(const std::string& uri) const
1112- {
1113- if (uri.empty())
1114- return false;
1115-
1116- if (get_file_content_type(uri).find("audio/") == 0)
1117- {
1118- std::cout << "Found audio content" << std::endl;
1119- return true;
1120- }
1121-
1122- return false;
1123- }
1124-
1125- bool is_video_file(const std::string& uri) const
1126- {
1127- if (uri.empty())
1128- return false;
1129-
1130- if (get_file_content_type(uri).find("video/") == 0)
1131- {
1132- std::cout << "Found video content" << std::endl;
1133- return true;
1134- }
1135-
1136- return false;
1137- }
1138-
1139- MediaFileType media_file_type() const
1140- {
1141- return file_type;
1142- }
1143+ void set_audio_stream_role(core::ubuntu::media::Player::AudioStreamRole new_audio_role);
1144+
1145+ uint64_t position() const;
1146+ uint64_t duration() const;
1147+
1148+ void set_uri(const std::string& uri, const core::ubuntu::media::Player::HeadersType& headers);
1149+ std::string uri() const;
1150+
1151+ void setup_source(GstElement *source);
1152+
1153+ bool set_state_and_wait(GstState new_state);
1154+ bool seek(const std::chrono::microseconds& ms);
1155+
1156+ core::ubuntu::media::video::Dimensions get_video_dimensions() const;
1157+
1158+ std::string get_file_content_type(const std::string& uri) const;
1159+
1160+ bool is_audio_file(const std::string& uri) const;
1161+ bool is_video_file(const std::string& uri) const;
1162+
1163+ MediaFileType media_file_type() const;
1164
1165 GstElement* pipeline;
1166 gstreamer::Bus bus;
1167 MediaFileType file_type;
1168- SurfaceTextureClientHybris stc_hybris;
1169 GstElement* video_sink;
1170 core::Connection on_new_message_connection;
1171 bool is_seeking;
1172 core::ubuntu::media::Player::HeadersType request_headers;
1173- media::Player::Lifetime player_lifetime;
1174+ core::ubuntu::media::Player::Lifetime player_lifetime;
1175 struct
1176 {
1177 core::Signal<void> about_to_finish;

Subscribers

People subscribed via source and target branches