Line data Source code
1 : /*
2 : * Copyright (C) 2025 Sky UK
3 : *
4 : * This library is free software; you can redistribute it and/or
5 : * modify it under the terms of the GNU Lesser General Public
6 : * License as published by the Free Software Foundation;
7 : * version 2.1 of the License.
8 : *
9 : * This library is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 : * Lesser General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU Lesser General Public
15 : * License along with this library; if not, write to the Free Software
16 : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 : */
18 :
19 : #include "PullModeVideoPlaybackDelegate.h"
20 : #include "GStreamerEMEUtils.h"
21 : #include "GStreamerMSEUtils.h"
22 : #include "GstreamerCatLog.h"
23 :
24 : #define GST_CAT_DEFAULT rialtoGStreamerCat
25 :
26 138 : PullModeVideoPlaybackDelegate::PullModeVideoPlaybackDelegate(GstElement *sink) : PullModePlaybackDelegate(sink)
27 : {
28 69 : m_mediaSourceType = firebolt::rialto::MediaSourceType::VIDEO;
29 69 : m_isAsync = true;
30 : }
31 :
32 208 : GstStateChangeReturn PullModeVideoPlaybackDelegate::changeState(GstStateChange transition)
33 : {
34 208 : switch (transition)
35 : {
36 35 : case GST_STATE_CHANGE_READY_TO_PAUSED:
37 : {
38 35 : if (!attachToMediaClientAndSetStreamsNumber(m_maxWidth, m_maxHeight))
39 : {
40 1 : return GST_STATE_CHANGE_FAILURE;
41 : }
42 :
43 34 : std::shared_ptr<GStreamerMSEMediaPlayerClient> client = m_mediaPlayerManager.getMediaPlayerClient();
44 34 : if (!client)
45 : {
46 0 : GST_ERROR_OBJECT(m_sink, "MediaPlayerClient is nullptr");
47 0 : return GST_STATE_CHANGE_FAILURE;
48 : }
49 :
50 34 : std::unique_lock lock{m_propertyMutex};
51 34 : if (m_rectangleSettingQueued)
52 : {
53 1 : GST_DEBUG_OBJECT(m_sink, "Set queued video rectangle");
54 1 : m_rectangleSettingQueued = false;
55 1 : client->setVideoRectangle(m_videoRectangle);
56 : }
57 34 : break;
58 68 : }
59 173 : default:
60 173 : break;
61 : }
62 207 : return PullModePlaybackDelegate::changeState(transition);
63 : }
64 :
65 36 : gboolean PullModeVideoPlaybackDelegate::handleEvent(GstPad *pad, GstObject *parent, GstEvent *event)
66 : {
67 36 : switch (GST_EVENT_TYPE(event))
68 : {
69 35 : case GST_EVENT_CAPS:
70 : {
71 35 : GstCaps *caps{nullptr};
72 35 : gst_event_parse_caps(event, &caps);
73 35 : if (m_sourceAttached)
74 : {
75 1 : GST_INFO_OBJECT(m_sink, "Source already attached. Skip calling attachSource");
76 1 : break;
77 : }
78 :
79 34 : GST_INFO_OBJECT(m_sink, "Attaching VIDEO source with caps %" GST_PTR_FORMAT, caps);
80 :
81 34 : std::unique_ptr<firebolt::rialto::IMediaPipeline::MediaSource> vsource = createMediaSource(caps);
82 34 : if (vsource)
83 : {
84 34 : std::shared_ptr<GStreamerMSEMediaPlayerClient> client = m_mediaPlayerManager.getMediaPlayerClient();
85 67 : if ((!client) || (!client->attachSource(vsource, RIALTO_MSE_BASE_SINK(m_sink),
86 67 : PullModePlaybackDelegate::shared_from_this())))
87 : {
88 1 : GST_ERROR_OBJECT(m_sink, "Failed to attach VIDEO source");
89 : }
90 : else
91 : {
92 33 : m_sourceAttached = true;
93 :
94 : // check if READY -> PAUSED was requested before source was attached
95 33 : if (GST_STATE_NEXT(m_sink) == GST_STATE_PAUSED)
96 : {
97 33 : client->pause(m_sourceId);
98 : }
99 33 : std::unique_lock lock{m_propertyMutex};
100 33 : if (m_immediateOutputQueued)
101 : {
102 1 : GST_DEBUG_OBJECT(m_sink, "Set queued immediate-output");
103 1 : m_immediateOutputQueued = false;
104 1 : if (!client->setImmediateOutput(m_sourceId, m_immediateOutput))
105 : {
106 0 : GST_ERROR_OBJECT(m_sink, "Could not set immediate-output");
107 : }
108 : }
109 33 : if (m_reportDecodeErrorsQueued)
110 : {
111 1 : GST_DEBUG_OBJECT(m_sink, "Set queued report-decode-errors");
112 1 : m_reportDecodeErrorsQueued = false;
113 1 : if (!client->setReportDecodeErrors(m_sourceId, m_reportDecodeErrors))
114 : {
115 0 : GST_ERROR_OBJECT(m_sink, "Could not set report-decode-errors");
116 : }
117 : }
118 33 : if (m_syncmodeStreamingQueued)
119 : {
120 2 : GST_DEBUG_OBJECT(m_sink, "Set queued syncmode-streaming");
121 2 : m_syncmodeStreamingQueued = false;
122 2 : if (!client->setStreamSyncMode(m_sourceId, m_syncmodeStreaming))
123 : {
124 1 : GST_ERROR_OBJECT(m_sink, "Could not set syncmode-streaming");
125 : }
126 : }
127 33 : if (m_videoMuteQueued)
128 : {
129 1 : GST_DEBUG_OBJECT(m_sink, "Set queued show-video-window");
130 1 : m_videoMuteQueued = false;
131 1 : client->setMute(m_videoMute, m_sourceId);
132 : }
133 33 : }
134 34 : }
135 : else
136 : {
137 0 : GST_ERROR_OBJECT(m_sink, "Failed to create VIDEO source");
138 : }
139 :
140 34 : break;
141 : }
142 1 : default:
143 1 : break;
144 : }
145 36 : return PullModePlaybackDelegate::handleEvent(pad, parent, event);
146 : }
147 :
148 18 : void PullModeVideoPlaybackDelegate::getProperty(const Property &type, GValue *value)
149 : {
150 18 : switch (type)
151 : {
152 5 : case Property::WindowSet:
153 : {
154 5 : std::string rectangleValue;
155 5 : auto client = m_mediaPlayerManager.getMediaPlayerClient();
156 :
157 : {
158 5 : std::unique_lock lock{m_propertyMutex};
159 5 : if (!client)
160 : {
161 : // Return the default value and
162 : // queue a setting event (for the default value) so that it will become true when
163 : // the client connects...
164 2 : GST_DEBUG_OBJECT(m_sink, "Return default rectangle setting, and queue an event to set the default upon "
165 : "client connect");
166 2 : m_rectangleSettingQueued = true;
167 2 : rectangleValue = m_videoRectangle;
168 : }
169 5 : } // lock released here
170 :
171 5 : if (client)
172 : {
173 3 : rectangleValue = client->getVideoRectangle();
174 : }
175 5 : g_value_set_string(value, rectangleValue.c_str());
176 5 : break;
177 : }
178 2 : case Property::MaxVideoWidth:
179 : {
180 2 : g_value_set_uint(value, m_maxWidth);
181 2 : break;
182 : }
183 2 : case Property::MaxVideoHeight:
184 : {
185 2 : g_value_set_uint(value, m_maxHeight);
186 2 : break;
187 : }
188 1 : case Property::FrameStepOnPreroll:
189 : {
190 1 : g_value_set_boolean(value, m_stepOnPrerollEnabled);
191 1 : break;
192 : }
193 3 : case Property::QueuedFrames:
194 : {
195 3 : uint32_t queuedFrames = 0;
196 :
197 3 : std::unique_lock lock{m_propertyMutex};
198 3 : auto client = m_mediaPlayerManager.getMediaPlayerClient();
199 3 : if (!client)
200 : {
201 1 : GST_DEBUG_OBJECT(m_sink, "Getting queued frames: no client, returning 0");
202 : }
203 : else
204 : {
205 2 : lock.unlock();
206 2 : if (!client->getQueuedFrames(m_sourceId, queuedFrames))
207 : {
208 1 : GST_ERROR_OBJECT(m_sink, "Could not get queued frames");
209 : }
210 : }
211 :
212 3 : g_value_set_uint(value, queuedFrames);
213 3 : break;
214 : }
215 3 : case Property::ImmediateOutput:
216 : {
217 3 : bool immediateOutputValue = false;
218 3 : auto client = m_mediaPlayerManager.getMediaPlayerClient();
219 :
220 : {
221 3 : std::unique_lock lock{m_propertyMutex};
222 3 : if (!client)
223 : {
224 : // Return the default value and
225 : // queue a setting event (for the default value) so that it will become true when
226 : // the client connects...
227 1 : GST_DEBUG_OBJECT(m_sink,
228 : "Return default immediate-output setting, and queue an event to set the default "
229 : "upon client connect");
230 1 : m_immediateOutputQueued = true;
231 1 : immediateOutputValue = m_immediateOutput;
232 : }
233 : else
234 : {
235 2 : immediateOutputValue = m_immediateOutput;
236 : }
237 3 : } // lock released here
238 :
239 3 : if (client)
240 : {
241 2 : if (!client->getImmediateOutput(m_sourceId, immediateOutputValue))
242 : {
243 1 : GST_ERROR_OBJECT(m_sink, "Could not get immediate-output");
244 : }
245 : }
246 3 : g_value_set_boolean(value, immediateOutputValue);
247 3 : break;
248 : }
249 2 : case Property::VideoPts:
250 : {
251 2 : int64_t videoPts = 0;
252 :
253 2 : std::unique_lock lock{m_propertyMutex};
254 2 : auto client = m_mediaPlayerManager.getMediaPlayerClient();
255 2 : if (!client)
256 : {
257 1 : GST_DEBUG_OBJECT(m_sink, "Getting video PTS: no client, returning 0");
258 : }
259 : else
260 : {
261 1 : lock.unlock();
262 1 : gint64 position = client->getPosition(m_sourceId);
263 1 : videoPts = ((position / 100000) * 9LL); // 90Khz PTS
264 : }
265 :
266 2 : g_value_set_int64(value, videoPts);
267 2 : break;
268 : }
269 0 : default:
270 : {
271 0 : PullModePlaybackDelegate::getProperty(type, value);
272 0 : break;
273 : }
274 : }
275 18 : }
276 :
277 97 : void PullModeVideoPlaybackDelegate::setProperty(const Property &type, const GValue *value)
278 : {
279 97 : std::shared_ptr<GStreamerMSEMediaPlayerClient> client = m_mediaPlayerManager.getMediaPlayerClient();
280 97 : switch (type)
281 : {
282 4 : case Property::WindowSet:
283 : {
284 4 : const gchar *rectangle = g_value_get_string(value);
285 4 : if (!rectangle)
286 : {
287 1 : GST_WARNING_OBJECT(m_sink, "Rectangle string not valid");
288 1 : break;
289 : }
290 6 : std::string videoRectangle{rectangle};
291 3 : bool shouldSetRectangle = false;
292 : {
293 3 : std::unique_lock lock{m_propertyMutex};
294 3 : m_videoRectangle = videoRectangle;
295 3 : if (!client)
296 : {
297 2 : GST_DEBUG_OBJECT(m_sink, "Rectangle setting enqueued");
298 2 : m_rectangleSettingQueued = true;
299 : }
300 : else
301 : {
302 1 : shouldSetRectangle = true;
303 : }
304 3 : } // lock released here
305 :
306 3 : if (shouldSetRectangle)
307 : {
308 1 : client->setVideoRectangle(videoRectangle);
309 : }
310 3 : break;
311 : }
312 2 : case Property::MaxVideoWidth:
313 2 : m_maxWidth = g_value_get_uint(value);
314 2 : break;
315 2 : case Property::MaxVideoHeight:
316 2 : m_maxHeight = g_value_get_uint(value);
317 2 : break;
318 5 : case Property::FrameStepOnPreroll:
319 : {
320 5 : bool stepOnPrerollEnabled = g_value_get_boolean(value);
321 5 : if (client && stepOnPrerollEnabled && !m_stepOnPrerollEnabled)
322 : {
323 2 : GST_INFO_OBJECT(m_sink, "Frame stepping on preroll");
324 2 : client->renderFrame(m_sourceId);
325 : }
326 5 : m_stepOnPrerollEnabled = stepOnPrerollEnabled;
327 5 : break;
328 : }
329 4 : case Property::ReportDecodeErrors:
330 : {
331 4 : bool reportDecodeErrors = g_value_get_boolean(value);
332 4 : std::unique_lock lock{m_propertyMutex};
333 4 : m_reportDecodeErrors = reportDecodeErrors;
334 4 : if (!client)
335 : {
336 2 : GST_DEBUG_OBJECT(m_sink, "Report decode errors setting enqueued");
337 2 : m_reportDecodeErrorsQueued = true;
338 : }
339 : else
340 : {
341 2 : lock.unlock();
342 2 : if (!client->setReportDecodeErrors(m_sourceId, reportDecodeErrors))
343 : {
344 1 : GST_ERROR_OBJECT(m_sink, "Could not set report-decode-errors");
345 : }
346 : }
347 4 : break;
348 : }
349 4 : case Property::ImmediateOutput:
350 : {
351 4 : bool immediateOutput = (g_value_get_boolean(value) != FALSE);
352 4 : bool shouldSetImmediateOutput = false;
353 : {
354 4 : std::unique_lock lock{m_propertyMutex};
355 4 : m_immediateOutput = immediateOutput;
356 4 : if (!client)
357 : {
358 2 : GST_DEBUG_OBJECT(m_sink, "Immediate output setting enqueued");
359 2 : m_immediateOutputQueued = true;
360 : }
361 : else
362 : {
363 2 : shouldSetImmediateOutput = true;
364 : }
365 4 : } // lock released here
366 :
367 4 : if (shouldSetImmediateOutput)
368 : {
369 2 : if (!client->setImmediateOutput(m_sourceId, immediateOutput))
370 : {
371 1 : GST_ERROR_OBJECT(m_sink, "Could not set immediate-output");
372 : }
373 : }
374 4 : break;
375 : }
376 4 : case Property::SyncmodeStreaming:
377 : {
378 4 : bool syncmodeStreaming = (g_value_get_boolean(value) != FALSE);
379 4 : bool shouldSetSyncMode = false;
380 : {
381 4 : std::unique_lock lock{m_propertyMutex};
382 4 : m_syncmodeStreaming = syncmodeStreaming;
383 4 : if (!client)
384 : {
385 2 : GST_DEBUG_OBJECT(m_sink, "Syncmode streaming setting enqueued");
386 2 : m_syncmodeStreamingQueued = true;
387 : }
388 : else
389 : {
390 2 : shouldSetSyncMode = true;
391 : }
392 4 : } // lock released here
393 :
394 4 : if (shouldSetSyncMode)
395 : {
396 2 : if (!client->setStreamSyncMode(m_sourceId, syncmodeStreaming))
397 : {
398 1 : GST_ERROR_OBJECT(m_sink, "Could not set syncmode-streaming");
399 : }
400 : }
401 4 : break;
402 : }
403 2 : case Property::ShowVideoWindow:
404 : {
405 2 : bool videoMute = (g_value_get_boolean(value) == FALSE);
406 2 : bool shouldSetMute = false;
407 : {
408 2 : std::unique_lock lock{m_propertyMutex};
409 2 : m_videoMute = videoMute;
410 2 : if (!client || !m_sourceAttached)
411 : {
412 1 : GST_DEBUG_OBJECT(m_sink, "Show video window setting enqueued");
413 1 : m_videoMuteQueued = true;
414 : }
415 : else
416 : {
417 1 : shouldSetMute = true;
418 : }
419 2 : } // lock released here
420 :
421 2 : if (shouldSetMute)
422 : {
423 1 : client->setMute(videoMute, m_sourceId);
424 : }
425 2 : break;
426 : }
427 70 : default:
428 : {
429 70 : PullModePlaybackDelegate::setProperty(type, value);
430 70 : break;
431 : }
432 : }
433 97 : }
434 :
435 1 : void PullModeVideoPlaybackDelegate::handleQos(uint64_t processed, uint64_t dropped) const
436 : {
437 1 : GstBus *bus = gst_element_get_bus(m_sink);
438 : /* Hardcode isLive to FALSE and set invalid timestamps */
439 1 : GstMessage *message = gst_message_new_qos(GST_OBJECT(m_sink), FALSE, GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE,
440 : GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE);
441 :
442 1 : gst_message_set_qos_stats(message, GST_FORMAT_BUFFERS, processed, dropped);
443 1 : gst_bus_post(bus, message);
444 1 : gst_object_unref(bus);
445 : }
446 :
447 : std::unique_ptr<firebolt::rialto::IMediaPipeline::MediaSource>
448 34 : PullModeVideoPlaybackDelegate::createMediaSource(GstCaps *caps) const
449 : {
450 34 : GstStructure *structure = gst_caps_get_structure(caps, 0);
451 34 : const gchar *strct_name = gst_structure_get_name(structure);
452 :
453 34 : firebolt::rialto::SegmentAlignment alignment = get_segment_alignment(structure);
454 34 : std::shared_ptr<firebolt::rialto::CodecData> codecData = get_codec_data(structure);
455 34 : firebolt::rialto::StreamFormat format = get_stream_format(structure);
456 :
457 34 : gint width{0};
458 34 : gint height{0};
459 34 : gst_structure_get_int(structure, "width", &width);
460 34 : gst_structure_get_int(structure, "height", &height);
461 :
462 34 : if (strct_name)
463 : {
464 34 : std::string mimeType{};
465 34 : if (g_str_has_prefix(strct_name, "video/x-h264"))
466 : {
467 29 : mimeType = "video/h264";
468 : }
469 5 : else if (g_str_has_prefix(strct_name, "video/x-h265"))
470 : {
471 4 : mimeType = "video/h265";
472 :
473 4 : uint32_t dolbyVisionProfile{0};
474 4 : if (get_dv_profile(structure, dolbyVisionProfile))
475 : {
476 1 : return std::make_unique<firebolt::rialto::IMediaPipeline::MediaSourceVideoDolbyVision>(mimeType,
477 : dolbyVisionProfile,
478 1 : m_hasDrm, width,
479 : height, alignment,
480 1 : format, codecData);
481 : }
482 : }
483 : else
484 : {
485 1 : mimeType = strct_name;
486 : }
487 :
488 33 : GST_INFO_OBJECT(m_sink, "%s video media source created", mimeType.c_str());
489 66 : return std::make_unique<firebolt::rialto::IMediaPipeline::MediaSourceVideo>(mimeType, m_hasDrm, width, height,
490 33 : alignment, format, codecData);
491 34 : }
492 : else
493 : {
494 0 : GST_ERROR_OBJECT(m_sink,
495 : "Empty caps' structure name! Failed to set mime type when constructing video media source");
496 : }
497 :
498 0 : return nullptr;
499 34 : }
|