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 120 : PullModeVideoPlaybackDelegate::PullModeVideoPlaybackDelegate(GstElement *sink) : PullModePlaybackDelegate(sink)
27 : {
28 60 : m_mediaSourceType = firebolt::rialto::MediaSourceType::VIDEO;
29 60 : m_isAsync = true;
30 : }
31 :
32 180 : GstStateChangeReturn PullModeVideoPlaybackDelegate::changeState(GstStateChange transition)
33 : {
34 180 : switch (transition)
35 : {
36 30 : case GST_STATE_CHANGE_READY_TO_PAUSED:
37 : {
38 30 : if (!attachToMediaClientAndSetStreamsNumber(m_maxWidth, m_maxHeight))
39 : {
40 1 : return GST_STATE_CHANGE_FAILURE;
41 : }
42 :
43 29 : std::shared_ptr<GStreamerMSEMediaPlayerClient> client = m_mediaPlayerManager.getMediaPlayerClient();
44 29 : if (!client)
45 : {
46 0 : GST_ERROR_OBJECT(m_sink, "MediaPlayerClient is nullptr");
47 0 : return GST_STATE_CHANGE_FAILURE;
48 : }
49 :
50 29 : std::unique_lock lock{m_propertyMutex};
51 29 : 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 29 : break;
58 58 : }
59 150 : default:
60 150 : break;
61 : }
62 179 : return PullModePlaybackDelegate::changeState(transition);
63 : }
64 :
65 31 : gboolean PullModeVideoPlaybackDelegate::handleEvent(GstPad *pad, GstObject *parent, GstEvent *event)
66 : {
67 31 : switch (GST_EVENT_TYPE(event))
68 : {
69 30 : case GST_EVENT_CAPS:
70 : {
71 30 : GstCaps *caps{nullptr};
72 30 : gst_event_parse_caps(event, &caps);
73 30 : if (m_sourceAttached)
74 : {
75 1 : GST_INFO_OBJECT(m_sink, "Source already attached. Skip calling attachSource");
76 1 : break;
77 : }
78 :
79 29 : GST_INFO_OBJECT(m_sink, "Attaching VIDEO source with caps %" GST_PTR_FORMAT, caps);
80 :
81 29 : std::unique_ptr<firebolt::rialto::IMediaPipeline::MediaSource> vsource = createMediaSource(caps);
82 29 : if (vsource)
83 : {
84 29 : std::shared_ptr<GStreamerMSEMediaPlayerClient> client = m_mediaPlayerManager.getMediaPlayerClient();
85 57 : if ((!client) || (!client->attachSource(vsource, RIALTO_MSE_BASE_SINK(m_sink),
86 57 : PullModePlaybackDelegate::shared_from_this())))
87 : {
88 1 : GST_ERROR_OBJECT(m_sink, "Failed to attach VIDEO source");
89 : }
90 : else
91 : {
92 28 : m_sourceAttached = true;
93 :
94 : // check if READY -> PAUSED was requested before source was attached
95 28 : if (GST_STATE_NEXT(m_sink) == GST_STATE_PAUSED)
96 : {
97 28 : client->pause(m_sourceId);
98 : }
99 28 : std::unique_lock lock{m_propertyMutex};
100 28 : 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 28 : if (m_syncmodeStreamingQueued)
110 : {
111 2 : GST_DEBUG_OBJECT(m_sink, "Set queued syncmode-streaming");
112 2 : m_syncmodeStreamingQueued = false;
113 2 : if (!client->setStreamSyncMode(m_sourceId, m_syncmodeStreaming))
114 : {
115 1 : GST_ERROR_OBJECT(m_sink, "Could not set syncmode-streaming");
116 : }
117 : }
118 28 : if (m_videoMuteQueued)
119 : {
120 1 : GST_DEBUG_OBJECT(m_sink, "Set queued show-video-window");
121 1 : m_videoMuteQueued = false;
122 1 : client->setMute(m_videoMute, m_sourceId);
123 : }
124 28 : }
125 29 : }
126 : else
127 : {
128 0 : GST_ERROR_OBJECT(m_sink, "Failed to create VIDEO source");
129 : }
130 :
131 29 : break;
132 : }
133 1 : default:
134 1 : break;
135 : }
136 31 : return PullModePlaybackDelegate::handleEvent(pad, parent, event);
137 : }
138 :
139 15 : void PullModeVideoPlaybackDelegate::getProperty(const Property &type, GValue *value)
140 : {
141 15 : switch (type)
142 : {
143 5 : case Property::WindowSet:
144 : {
145 5 : std::string rectangleValue;
146 5 : auto client = m_mediaPlayerManager.getMediaPlayerClient();
147 :
148 : {
149 5 : std::unique_lock lock{m_propertyMutex};
150 5 : if (!client)
151 : {
152 : // Return the default value and
153 : // queue a setting event (for the default value) so that it will become true when
154 : // the client connects...
155 2 : GST_DEBUG_OBJECT(m_sink, "Return default rectangle setting, and queue an event to set the default upon "
156 : "client connect");
157 2 : m_rectangleSettingQueued = true;
158 2 : rectangleValue = m_videoRectangle;
159 : }
160 5 : } // lock released here
161 :
162 5 : if (client)
163 : {
164 3 : rectangleValue = client->getVideoRectangle();
165 : }
166 5 : g_value_set_string(value, rectangleValue.c_str());
167 5 : break;
168 : }
169 2 : case Property::MaxVideoWidth:
170 : {
171 2 : g_value_set_uint(value, m_maxWidth);
172 2 : break;
173 : }
174 2 : case Property::MaxVideoHeight:
175 : {
176 2 : g_value_set_uint(value, m_maxHeight);
177 2 : break;
178 : }
179 1 : case Property::FrameStepOnPreroll:
180 : {
181 1 : g_value_set_boolean(value, m_stepOnPrerollEnabled);
182 1 : break;
183 : }
184 3 : case Property::ImmediateOutput:
185 : {
186 3 : bool immediateOutputValue = false;
187 3 : auto client = m_mediaPlayerManager.getMediaPlayerClient();
188 :
189 : {
190 3 : std::unique_lock lock{m_propertyMutex};
191 3 : if (!client)
192 : {
193 : // Return the default value and
194 : // queue a setting event (for the default value) so that it will become true when
195 : // the client connects...
196 1 : GST_DEBUG_OBJECT(m_sink,
197 : "Return default immediate-output setting, and queue an event to set the default "
198 : "upon client connect");
199 1 : m_immediateOutputQueued = true;
200 1 : immediateOutputValue = m_immediateOutput;
201 : }
202 : else
203 : {
204 2 : immediateOutputValue = m_immediateOutput;
205 : }
206 3 : } // lock released here
207 :
208 3 : if (client)
209 : {
210 2 : if (!client->getImmediateOutput(m_sourceId, immediateOutputValue))
211 : {
212 1 : GST_ERROR_OBJECT(m_sink, "Could not get immediate-output");
213 : }
214 : }
215 3 : g_value_set_boolean(value, immediateOutputValue);
216 3 : break;
217 : }
218 2 : case Property::VideoPts:
219 : {
220 2 : int64_t videoPts = 0;
221 :
222 2 : std::unique_lock lock{m_propertyMutex};
223 2 : auto client = m_mediaPlayerManager.getMediaPlayerClient();
224 2 : if (!client)
225 : {
226 1 : GST_DEBUG_OBJECT(m_sink, "Getting video PTS: no client, returning 0");
227 : }
228 : else
229 : {
230 1 : lock.unlock();
231 1 : gint64 position = client->getPosition(m_sourceId);
232 1 : videoPts = ((position / 100000) * 9LL); // 90Khz PTS
233 : }
234 :
235 2 : g_value_set_int64(value, videoPts);
236 2 : break;
237 : }
238 0 : default:
239 : {
240 0 : PullModePlaybackDelegate::getProperty(type, value);
241 0 : break;
242 : }
243 : }
244 15 : }
245 :
246 83 : void PullModeVideoPlaybackDelegate::setProperty(const Property &type, const GValue *value)
247 : {
248 83 : std::shared_ptr<GStreamerMSEMediaPlayerClient> client = m_mediaPlayerManager.getMediaPlayerClient();
249 83 : switch (type)
250 : {
251 4 : case Property::WindowSet:
252 : {
253 4 : const gchar *rectangle = g_value_get_string(value);
254 4 : if (!rectangle)
255 : {
256 1 : GST_WARNING_OBJECT(m_sink, "Rectangle string not valid");
257 1 : break;
258 : }
259 6 : std::string videoRectangle{rectangle};
260 3 : bool shouldSetRectangle = false;
261 : {
262 3 : std::unique_lock lock{m_propertyMutex};
263 3 : m_videoRectangle = videoRectangle;
264 3 : if (!client)
265 : {
266 2 : GST_DEBUG_OBJECT(m_sink, "Rectangle setting enqueued");
267 2 : m_rectangleSettingQueued = true;
268 : }
269 : else
270 : {
271 1 : shouldSetRectangle = true;
272 : }
273 3 : } // lock released here
274 :
275 3 : if (shouldSetRectangle)
276 : {
277 1 : client->setVideoRectangle(videoRectangle);
278 : }
279 3 : break;
280 : }
281 2 : case Property::MaxVideoWidth:
282 2 : m_maxWidth = g_value_get_uint(value);
283 2 : break;
284 2 : case Property::MaxVideoHeight:
285 2 : m_maxHeight = g_value_get_uint(value);
286 2 : break;
287 5 : case Property::FrameStepOnPreroll:
288 : {
289 5 : bool stepOnPrerollEnabled = g_value_get_boolean(value);
290 5 : if (client && stepOnPrerollEnabled && !m_stepOnPrerollEnabled)
291 : {
292 2 : GST_INFO_OBJECT(m_sink, "Frame stepping on preroll");
293 2 : client->renderFrame(m_sourceId);
294 : }
295 5 : m_stepOnPrerollEnabled = stepOnPrerollEnabled;
296 5 : break;
297 : }
298 4 : case Property::ImmediateOutput:
299 : {
300 4 : bool immediateOutput = (g_value_get_boolean(value) != FALSE);
301 4 : bool shouldSetImmediateOutput = false;
302 : {
303 4 : std::unique_lock lock{m_propertyMutex};
304 4 : m_immediateOutput = immediateOutput;
305 4 : if (!client)
306 : {
307 2 : GST_DEBUG_OBJECT(m_sink, "Immediate output setting enqueued");
308 2 : m_immediateOutputQueued = true;
309 : }
310 : else
311 : {
312 2 : shouldSetImmediateOutput = true;
313 : }
314 4 : } // lock released here
315 :
316 4 : if (shouldSetImmediateOutput)
317 : {
318 2 : if (!client->setImmediateOutput(m_sourceId, immediateOutput))
319 : {
320 1 : GST_ERROR_OBJECT(m_sink, "Could not set immediate-output");
321 : }
322 : }
323 4 : break;
324 : }
325 4 : case Property::SyncmodeStreaming:
326 : {
327 4 : bool syncmodeStreaming = (g_value_get_boolean(value) != FALSE);
328 4 : bool shouldSetSyncMode = false;
329 : {
330 4 : std::unique_lock lock{m_propertyMutex};
331 4 : m_syncmodeStreaming = syncmodeStreaming;
332 4 : if (!client)
333 : {
334 2 : GST_DEBUG_OBJECT(m_sink, "Syncmode streaming setting enqueued");
335 2 : m_syncmodeStreamingQueued = true;
336 : }
337 : else
338 : {
339 2 : shouldSetSyncMode = true;
340 : }
341 4 : } // lock released here
342 :
343 4 : if (shouldSetSyncMode)
344 : {
345 2 : if (!client->setStreamSyncMode(m_sourceId, syncmodeStreaming))
346 : {
347 1 : GST_ERROR_OBJECT(m_sink, "Could not set syncmode-streaming");
348 : }
349 : }
350 4 : break;
351 : }
352 2 : case Property::ShowVideoWindow:
353 : {
354 2 : bool videoMute = (g_value_get_boolean(value) == FALSE);
355 2 : bool shouldSetMute = false;
356 : {
357 2 : std::unique_lock lock{m_propertyMutex};
358 2 : m_videoMute = videoMute;
359 2 : if (!client || !m_sourceAttached)
360 : {
361 1 : GST_DEBUG_OBJECT(m_sink, "Show video window setting enqueued");
362 1 : m_videoMuteQueued = true;
363 : }
364 : else
365 : {
366 1 : shouldSetMute = true;
367 : }
368 2 : } // lock released here
369 :
370 2 : if (shouldSetMute)
371 : {
372 1 : client->setMute(videoMute, m_sourceId);
373 : }
374 2 : break;
375 : }
376 60 : default:
377 : {
378 60 : PullModePlaybackDelegate::setProperty(type, value);
379 60 : break;
380 : }
381 : }
382 83 : }
383 :
384 1 : void PullModeVideoPlaybackDelegate::handleQos(uint64_t processed, uint64_t dropped) const
385 : {
386 1 : GstBus *bus = gst_element_get_bus(m_sink);
387 : /* Hardcode isLive to FALSE and set invalid timestamps */
388 1 : GstMessage *message = gst_message_new_qos(GST_OBJECT(m_sink), FALSE, GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE,
389 : GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE);
390 :
391 1 : gst_message_set_qos_stats(message, GST_FORMAT_BUFFERS, processed, dropped);
392 1 : gst_bus_post(bus, message);
393 1 : gst_object_unref(bus);
394 : }
395 :
396 : std::unique_ptr<firebolt::rialto::IMediaPipeline::MediaSource>
397 29 : PullModeVideoPlaybackDelegate::createMediaSource(GstCaps *caps) const
398 : {
399 29 : GstStructure *structure = gst_caps_get_structure(caps, 0);
400 29 : const gchar *strct_name = gst_structure_get_name(structure);
401 :
402 29 : firebolt::rialto::SegmentAlignment alignment = get_segment_alignment(structure);
403 29 : std::shared_ptr<firebolt::rialto::CodecData> codecData = get_codec_data(structure);
404 29 : firebolt::rialto::StreamFormat format = get_stream_format(structure);
405 :
406 29 : gint width{0};
407 29 : gint height{0};
408 29 : gst_structure_get_int(structure, "width", &width);
409 29 : gst_structure_get_int(structure, "height", &height);
410 :
411 29 : if (strct_name)
412 : {
413 29 : std::string mimeType{};
414 29 : if (g_str_has_prefix(strct_name, "video/x-h264"))
415 : {
416 24 : mimeType = "video/h264";
417 : }
418 5 : else if (g_str_has_prefix(strct_name, "video/x-h265"))
419 : {
420 4 : mimeType = "video/h265";
421 :
422 4 : uint32_t dolbyVisionProfile{0};
423 4 : if (get_dv_profile(structure, dolbyVisionProfile))
424 : {
425 1 : return std::make_unique<firebolt::rialto::IMediaPipeline::MediaSourceVideoDolbyVision>(mimeType,
426 : dolbyVisionProfile,
427 1 : m_hasDrm, width,
428 : height, alignment,
429 1 : format, codecData);
430 : }
431 : }
432 : else
433 : {
434 1 : mimeType = strct_name;
435 : }
436 :
437 28 : GST_INFO_OBJECT(m_sink, "%s video media source created", mimeType.c_str());
438 56 : return std::make_unique<firebolt::rialto::IMediaPipeline::MediaSourceVideo>(mimeType, m_hasDrm, width, height,
439 28 : alignment, format, codecData);
440 29 : }
441 : else
442 : {
443 0 : GST_ERROR_OBJECT(m_sink,
444 : "Empty caps' structure name! Failed to set mime type when constructing video media source");
445 : }
446 :
447 0 : return nullptr;
448 29 : }
|