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