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