Line data Source code
1 : /*
2 : * If not stated otherwise in this file or this component's LICENSE file the
3 : * following copyright and licenses apply:
4 : *
5 : * Copyright 2024 Sky UK
6 : *
7 : * Licensed under the Apache License, Version 2.0 (the "License");
8 : * you may not use this file except in compliance with the License.
9 : * You may obtain a copy of the License at
10 : *
11 : * http://www.apache.org/licenses/LICENSE-2.0
12 : *
13 : * Unless required by applicable law or agreed to in writing, software
14 : * distributed under the License is distributed on an "AS IS" BASIS,
15 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 : * See the License for the specific language governing permissions and
17 : * limitations under the License.
18 : */
19 :
20 : #include "GstProtectionMetadataHelperFactory.h"
21 : #include "GstTextTrackSinkFactory.h"
22 : #include "RialtoServerLogging.h"
23 : #include <stdexcept>
24 :
25 : #include "GstRialtoTextTrackSinkPrivate.h"
26 : #include <atomic>
27 : #include <cstdlib>
28 : #include <gst/base/gstbasetransform.h>
29 : #include <gst/gst.h>
30 : G_BEGIN_DECLS
31 :
32 : enum
33 : {
34 : PROP_0,
35 : PROP_MUTE,
36 : PROP_TEXT_TRACK_IDENTIFIER,
37 : PROP_VIDEO_DECODER,
38 : PROP_POSITION,
39 : PROP_LAST
40 : };
41 :
42 : #define GST_RIALTO_TEXT_TRACK_SINK_TYPE (gst_rialto_text_track_sink_get_type())
43 : #define GST_RIALTO_TEXT_TRACK_SINK(obj) \
44 : (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_RIALTO_TEXT_TRACK_SINK_TYPE, GstRialtoTextTrackSink))
45 : #define GST_RIALTO_TEXT_TRACK_SINK_CLASS(klass) \
46 : (G_TYPE_CHECK_CLASS_CAST((klass), GST_RIALTO_TEXT_TRACK_SINK_TYPE, GstRialtoTextTrackSinkClass))
47 :
48 : typedef struct _GstRialtoTextTrackSink GstRialtoTextTrackSink;
49 : typedef struct _GstRialtoTextTrackSinkClass GstRialtoTextTrackSinkClass;
50 : typedef struct firebolt::rialto::server::GstRialtoTextTrackSinkPrivate GstRialtoTextTrackSinkPrivate;
51 :
52 : GType gst_rialto_text_track_sink_get_type(void); // NOLINT(build/function_format)
53 :
54 : struct _GstRialtoTextTrackSink
55 : {
56 : GstBaseSink parent;
57 : GstRialtoTextTrackSinkPrivate *priv;
58 : };
59 :
60 : struct _GstRialtoTextTrackSinkClass
61 : {
62 : GstBaseSink parentClass;
63 : };
64 :
65 : G_END_DECLS
66 :
67 : static GstStaticPadTemplate sinkTemplate =
68 : GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
69 : GST_STATIC_CAPS("application/ttml+xml; text/vtt; application/x-subtitle-vtt; text/x-raw; "
70 : "subtitle/x-subtitle-cc"));
71 :
72 : GST_DEBUG_CATEGORY(gst_rialto_text_track_sink_debug_category);
73 : #define GST_CAT_DEFAULT gst_rialto_text_track_sink_debug_category
74 :
75 : #define gst_rialto_text_track_sink_parent_class parent_class
76 0 : G_DEFINE_TYPE_WITH_PRIVATE(GstRialtoTextTrackSink, gst_rialto_text_track_sink, GST_TYPE_BASE_SINK);
77 :
78 : static void gst_rialto_text_track_sink_finalize(GObject *object); // NOLINT(build/function_format)
79 : static GstFlowReturn gst_rialto_text_track_sink_render(GstBaseSink *sink, // NOLINT(build/function_format)
80 : GstBuffer *buffer);
81 : static gboolean gst_rialto_text_track_sink_set_caps(GstBaseSink *sink, GstCaps *caps); // NOLINT(build/function_format)
82 : static gboolean gst_rialto_text_track_sink_start(GstBaseSink *sink); // NOLINT(build/function_format)
83 : static gboolean gst_rialto_text_track_sink_stop(GstBaseSink *sink); // NOLINT(build/function_format)
84 : static gboolean gst_rialto_text_track_sink_event(GstBaseSink *sink, GstEvent *event); // NOLINT(build/function_format)
85 : static GstStateChangeReturn
86 : gst_rialto_text_track_sink_change_state(GstElement *element, GstStateChange transition); // NOLINT(build/function_format)
87 : static void gst_rialto_text_track_sink_get_property(GObject *object, guint propId, // NOLINT(build/function_format)
88 : GValue *value, GParamSpec *pspec);
89 : static void gst_rialto_text_track_sink_set_property(GObject *object, guint propId, // NOLINT(build/function_format)
90 : const GValue *value, GParamSpec *pspec);
91 : static gboolean gst_rialto_text_track_sink_query(GstElement *element, GstQuery *query); // NOLINT(build/function_format)
92 :
93 0 : static void gst_rialto_text_track_sink_class_init(GstRialtoTextTrackSinkClass *klass) // NOLINT(build/function_format)
94 : {
95 0 : GST_DEBUG_CATEGORY_INIT(gst_rialto_text_track_sink_debug_category, "rialto_text_track_sink", 0,
96 : "TextTrack Sink for Rialto");
97 :
98 0 : GObjectClass *gobjectClass = G_OBJECT_CLASS(klass);
99 0 : GstElementClass *elementClass = GST_ELEMENT_CLASS(klass);
100 0 : GstBaseSinkClass *baseSinkClass = GST_BASE_SINK_CLASS(klass);
101 :
102 0 : gobjectClass->finalize = GST_DEBUG_FUNCPTR(gst_rialto_text_track_sink_finalize);
103 0 : gobjectClass->get_property = GST_DEBUG_FUNCPTR(gst_rialto_text_track_sink_get_property);
104 0 : gobjectClass->set_property = GST_DEBUG_FUNCPTR(gst_rialto_text_track_sink_set_property);
105 0 : baseSinkClass->start = GST_DEBUG_FUNCPTR(gst_rialto_text_track_sink_start);
106 0 : baseSinkClass->stop = GST_DEBUG_FUNCPTR(gst_rialto_text_track_sink_stop);
107 0 : baseSinkClass->render = GST_DEBUG_FUNCPTR(gst_rialto_text_track_sink_render);
108 0 : baseSinkClass->set_caps = GST_DEBUG_FUNCPTR(gst_rialto_text_track_sink_set_caps);
109 0 : baseSinkClass->event = GST_DEBUG_FUNCPTR(gst_rialto_text_track_sink_event);
110 0 : elementClass->change_state = GST_DEBUG_FUNCPTR(gst_rialto_text_track_sink_change_state);
111 0 : elementClass->query = GST_DEBUG_FUNCPTR(gst_rialto_text_track_sink_query);
112 :
113 0 : g_object_class_install_property(gobjectClass, PROP_MUTE,
114 : g_param_spec_boolean("mute", "Mute", "Mute subtitles", FALSE, G_PARAM_READWRITE));
115 :
116 0 : g_object_class_install_property(gobjectClass, PROP_TEXT_TRACK_IDENTIFIER,
117 : g_param_spec_string("text-track-identifier", "Text Track Identifier",
118 : "Identifier of text track", nullptr,
119 : GParamFlags(G_PARAM_READWRITE)));
120 :
121 0 : g_object_class_install_property(gobjectClass, PROP_VIDEO_DECODER,
122 : g_param_spec_uint64("video-decoder", "Video Decoder", "Video Decoder", 0,
123 : G_MAXUINT64, 0, GParamFlags(G_PARAM_WRITABLE)));
124 :
125 0 : g_object_class_install_property(gobjectClass, PROP_POSITION,
126 : g_param_spec_uint64("position", "Position", "Position", 0, G_MAXUINT64, 0,
127 : GParamFlags(G_PARAM_READWRITE)));
128 :
129 0 : gst_element_class_add_pad_template(elementClass, gst_static_pad_template_get(&sinkTemplate));
130 0 : gst_element_class_set_static_metadata(elementClass, "Rialto TextTrack Sink", "Sink/Parser/Subtitle",
131 : "Rialto TextTrack Sink", "SKY");
132 : }
133 :
134 0 : static void gst_rialto_text_track_sink_init(GstRialtoTextTrackSink *self) // NOLINT(build/function_format)
135 : {
136 : GstRialtoTextTrackSinkPrivate *priv =
137 0 : reinterpret_cast<GstRialtoTextTrackSinkPrivate *>(gst_rialto_text_track_sink_get_instance_private(self));
138 :
139 0 : self->priv = new (priv) GstRialtoTextTrackSinkPrivate();
140 :
141 0 : gst_base_sink_set_async_enabled(GST_BASE_SINK(self), FALSE);
142 : }
143 :
144 0 : static void gst_rialto_text_track_sink_finalize(GObject *object) // NOLINT(build/function_format)
145 : {
146 0 : GstRialtoTextTrackSink *self = GST_RIALTO_TEXT_TRACK_SINK(object);
147 : GstRialtoTextTrackSinkPrivate *priv =
148 0 : reinterpret_cast<GstRialtoTextTrackSinkPrivate *>(gst_rialto_text_track_sink_get_instance_private(self));
149 :
150 0 : priv->~GstRialtoTextTrackSinkPrivate();
151 :
152 0 : GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object));
153 : }
154 :
155 0 : static gboolean gst_rialto_text_track_sink_start(GstBaseSink *sink) // NOLINT(build/function_format)
156 : {
157 0 : const char *wayland_display = std::getenv("WAYLAND_DISPLAY");
158 0 : if (!wayland_display)
159 : {
160 0 : GST_ERROR_OBJECT(sink, "Failed to get WAYLAND_DISPLAY env variable");
161 0 : return false;
162 : }
163 :
164 0 : std::string display{wayland_display};
165 0 : GstRialtoTextTrackSink *self = GST_RIALTO_TEXT_TRACK_SINK(sink);
166 : try
167 : {
168 0 : self->priv->m_textTrackSession =
169 0 : firebolt::rialto::server::ITextTrackSessionFactory::getFactory().createTextTrackSession(display);
170 : }
171 0 : catch (const std::exception &e)
172 : {
173 0 : GST_ERROR_OBJECT(sink, "Failed to create TextTrackSession. Reason '%s'", e.what());
174 0 : return false;
175 : }
176 :
177 0 : GST_INFO_OBJECT(sink, "Successfully started TextTrack sink");
178 0 : return true;
179 : }
180 :
181 0 : static gboolean gst_rialto_text_track_sink_stop(GstBaseSink *sink) // NOLINT(build/function_format)
182 : {
183 0 : GstRialtoTextTrackSink *self = GST_RIALTO_TEXT_TRACK_SINK(sink);
184 0 : self->priv->m_textTrackSession.reset();
185 :
186 0 : GST_INFO_OBJECT(sink, "Successfully stopped TextTrack sink");
187 0 : return true;
188 : }
189 :
190 0 : static GstFlowReturn gst_rialto_text_track_sink_render(GstBaseSink *sink, GstBuffer *buffer) // NOLINT(build/function_format)
191 : {
192 0 : GstRialtoTextTrackSink *textTrackSink = GST_RIALTO_TEXT_TRACK_SINK(sink);
193 :
194 : GstMapInfo info;
195 0 : if (gst_buffer_map(buffer, &info, GST_MAP_READ))
196 : {
197 0 : std::string data(reinterpret_cast<char *>(info.data), info.size);
198 0 : int64_t displayOffset{0};
199 0 : if (GST_BUFFER_OFFSET_NONE != GST_BUFFER_OFFSET(buffer))
200 : {
201 0 : displayOffset = static_cast<int64_t>(GST_BUFFER_OFFSET(buffer));
202 : }
203 0 : textTrackSink->priv->m_textTrackSession->sendData(data, 0 - displayOffset);
204 :
205 0 : gst_buffer_unmap(buffer, &info);
206 : }
207 : else
208 : {
209 0 : GST_ERROR_OBJECT(textTrackSink, "Failed to map buffer");
210 0 : return GST_FLOW_ERROR;
211 : }
212 :
213 0 : return GST_FLOW_OK;
214 : }
215 :
216 0 : static gboolean gst_rialto_text_track_sink_set_caps(GstBaseSink *sink, GstCaps *caps) // NOLINT(build/function_format)
217 : {
218 0 : GST_INFO_OBJECT(sink, "Setting caps %" GST_PTR_FORMAT, caps);
219 0 : GstRialtoTextTrackSink *textTrackSink = GST_RIALTO_TEXT_TRACK_SINK(sink);
220 :
221 0 : GstStructure *structure = gst_caps_get_structure(caps, 0);
222 0 : const gchar *mimeName = gst_structure_get_name(structure);
223 :
224 0 : if (g_str_has_prefix(mimeName, "text/vtt") || g_str_has_prefix(mimeName, "application/x-subtitle-vtt"))
225 : {
226 0 : GST_INFO_OBJECT(sink, "Setting session to WebVTT");
227 0 : textTrackSink->priv->m_textTrackSession->setSessionWebVTTSelection();
228 : }
229 0 : else if (g_str_has_prefix(mimeName, "application/ttml+xml"))
230 : {
231 0 : GST_INFO_OBJECT(sink, "Setting session to TTML");
232 0 : textTrackSink->priv->m_textTrackSession->setSessionTTMLSelection();
233 : }
234 0 : else if (g_str_has_prefix(mimeName, "subtitle/x-subtitle-cc"))
235 : {
236 0 : GST_INFO_OBJECT(sink, "Setting session to CC");
237 0 : std::string identifier = "CC1";
238 0 : if (textTrackSink->priv->m_textTrackIdentifier.empty())
239 : {
240 0 : GST_WARNING_OBJECT(sink, "No text track identifier set, defaulting to %s", identifier.c_str());
241 : }
242 : else
243 : {
244 0 : identifier = textTrackSink->priv->m_textTrackIdentifier;
245 : }
246 0 : textTrackSink->priv->m_textTrackSession->setSessionCCSelection(identifier);
247 :
248 0 : if (textTrackSink->priv->m_videoDecoderIdentifier)
249 : {
250 0 : textTrackSink->priv->m_textTrackSession->associateVideoDecoder(textTrackSink->priv->m_videoDecoderIdentifier);
251 : }
252 : }
253 : else
254 : {
255 0 : GST_ERROR_OBJECT(sink, "Invalid mime name '%s'", mimeName);
256 0 : return FALSE;
257 : }
258 :
259 0 : textTrackSink->priv->m_textTrackSession->mute(textTrackSink->priv->m_isMuted);
260 :
261 0 : std::unique_lock lock{textTrackSink->priv->m_mutex};
262 0 : textTrackSink->priv->m_capsSet = true;
263 0 : if (textTrackSink->priv->m_queuedPosition.has_value())
264 : {
265 0 : textTrackSink->priv->m_textTrackSession->setPosition(textTrackSink->priv->m_queuedPosition.value() / GST_MSECOND);
266 0 : textTrackSink->priv->m_queuedPosition.reset();
267 : }
268 :
269 0 : return TRUE;
270 : }
271 :
272 0 : static gboolean gst_rialto_text_track_sink_event(GstBaseSink *sink, GstEvent *event) // NOLINT(build/function_format)
273 : {
274 0 : GstRialtoTextTrackSink *textTrackSink = GST_RIALTO_TEXT_TRACK_SINK(sink);
275 0 : GST_DEBUG_OBJECT(textTrackSink, "handling event %" GST_PTR_FORMAT, event);
276 :
277 0 : switch (GST_EVENT_TYPE(event))
278 : {
279 0 : case GST_EVENT_FLUSH_START:
280 : {
281 0 : if (!textTrackSink->priv->m_textTrackSession->resetSession(textTrackSink->priv->m_isMuted))
282 : {
283 0 : GST_ERROR_OBJECT(textTrackSink, "Failed to reset TextTrack session");
284 : }
285 0 : break;
286 : }
287 0 : case GST_EVENT_FLUSH_STOP:
288 : {
289 0 : break;
290 : }
291 0 : default:
292 : {
293 0 : break;
294 : }
295 : }
296 :
297 0 : return GST_BASE_SINK_CLASS(parent_class)->event(sink, event);
298 : }
299 :
300 : static GstStateChangeReturn
301 0 : gst_rialto_text_track_sink_change_state(GstElement *element, GstStateChange transition) // NOLINT(build/function_format)
302 : {
303 0 : GstRialtoTextTrackSink *textTrackSink = GST_RIALTO_TEXT_TRACK_SINK(element);
304 :
305 0 : GstState current_state = GST_STATE_TRANSITION_CURRENT(transition);
306 0 : GstState next_state = GST_STATE_TRANSITION_NEXT(transition);
307 0 : GST_INFO_OBJECT(textTrackSink, "State change: (%s) -> (%s)", gst_element_state_get_name(current_state),
308 : gst_element_state_get_name(next_state));
309 :
310 0 : switch (transition)
311 : {
312 0 : case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
313 : {
314 0 : if (!textTrackSink->priv->m_textTrackSession->play())
315 : {
316 0 : GST_ERROR_OBJECT(textTrackSink, "Failed to play textTrack session");
317 0 : return GST_STATE_CHANGE_FAILURE;
318 : }
319 0 : break;
320 : }
321 0 : case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
322 : {
323 0 : if (!textTrackSink->priv->m_textTrackSession->pause())
324 : {
325 0 : GST_ERROR_OBJECT(textTrackSink, "Failed to pause textTrack session");
326 0 : return GST_STATE_CHANGE_FAILURE;
327 : }
328 :
329 0 : break;
330 : }
331 0 : default:
332 0 : break;
333 : }
334 :
335 0 : GstStateChangeReturn result = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition);
336 0 : if (G_UNLIKELY(result == GST_STATE_CHANGE_FAILURE))
337 : {
338 0 : GST_WARNING_OBJECT(textTrackSink, "State change failed");
339 0 : return result;
340 : }
341 :
342 0 : return GST_STATE_CHANGE_SUCCESS;
343 : }
344 :
345 0 : static void gst_rialto_text_track_sink_get_property(GObject *object, guint propId, // NOLINT(build/function_format)
346 : GValue *value, GParamSpec *pspec)
347 : {
348 0 : GstRialtoTextTrackSink *textTrackSink = GST_RIALTO_TEXT_TRACK_SINK(object);
349 0 : if (!textTrackSink)
350 : {
351 0 : GST_ERROR_OBJECT(textTrackSink, "Sink not initalised");
352 0 : return;
353 : }
354 0 : GstRialtoTextTrackSinkPrivate *priv = textTrackSink->priv;
355 :
356 0 : switch (propId)
357 : {
358 0 : case PROP_MUTE:
359 : {
360 0 : g_value_set_boolean(value, priv->m_isMuted.load());
361 0 : break;
362 : }
363 0 : case PROP_TEXT_TRACK_IDENTIFIER:
364 : {
365 0 : g_value_set_string(value, priv->m_textTrackIdentifier.c_str());
366 0 : break;
367 : }
368 0 : case PROP_POSITION:
369 : {
370 : // Thunder ITextTrack does not provide getPosition API so we are unable to determine current position
371 0 : g_value_set_uint64(value, GST_CLOCK_TIME_NONE);
372 0 : break;
373 : }
374 0 : default:
375 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec);
376 0 : break;
377 : }
378 : }
379 :
380 0 : static void gst_rialto_text_track_sink_set_property(GObject *object, guint propId, // NOLINT(build/function_format)
381 : const GValue *value, GParamSpec *pspec)
382 : {
383 0 : GstRialtoTextTrackSink *textTrackSink = GST_RIALTO_TEXT_TRACK_SINK(object);
384 0 : if (!textTrackSink)
385 : {
386 0 : GST_ERROR_OBJECT(textTrackSink, "Sink not initalised");
387 0 : return;
388 : }
389 0 : GstRialtoTextTrackSinkPrivate *priv = textTrackSink->priv;
390 :
391 0 : switch (propId)
392 : {
393 0 : case PROP_MUTE:
394 : {
395 0 : priv->m_isMuted = g_value_get_boolean(value);
396 0 : if (priv->m_textTrackSession)
397 : {
398 0 : priv->m_textTrackSession->mute(priv->m_isMuted);
399 : }
400 0 : break;
401 : }
402 0 : case PROP_TEXT_TRACK_IDENTIFIER:
403 : {
404 0 : priv->m_textTrackIdentifier = g_value_get_string(value);
405 0 : if (priv->m_textTrackSession && priv->m_textTrackSession->isClosedCaptions())
406 : {
407 0 : priv->m_textTrackSession->setSessionCCSelection(priv->m_textTrackIdentifier);
408 : }
409 :
410 0 : break;
411 : }
412 0 : case PROP_VIDEO_DECODER:
413 : {
414 0 : priv->m_videoDecoderIdentifier = g_value_get_uint64(value);
415 0 : if (priv->m_textTrackSession && priv->m_textTrackSession->isClosedCaptions())
416 : {
417 0 : priv->m_textTrackSession->associateVideoDecoder(priv->m_videoDecoderIdentifier);
418 : }
419 :
420 0 : break;
421 : }
422 0 : case PROP_POSITION:
423 : {
424 0 : guint64 position = g_value_get_uint64(value);
425 0 : std::unique_lock lock{priv->m_mutex};
426 0 : if (priv->m_textTrackSession && priv->m_capsSet)
427 : {
428 0 : priv->m_textTrackSession->setPosition(position / GST_MSECOND);
429 : }
430 : else
431 : {
432 0 : priv->m_queuedPosition = position;
433 : }
434 0 : break;
435 : }
436 0 : default:
437 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec);
438 0 : break;
439 : }
440 : }
441 :
442 0 : static gboolean gst_rialto_text_track_sink_query(GstElement *element, GstQuery *query) // NOLINT(build/function_format)
443 : {
444 0 : GstRialtoTextTrackSink *sink = GST_RIALTO_TEXT_TRACK_SINK(element);
445 0 : GST_DEBUG_OBJECT(sink, "handling query '%s'", GST_QUERY_TYPE_NAME(query));
446 0 : switch (GST_QUERY_TYPE(query))
447 : {
448 0 : case GST_QUERY_POSITION:
449 : {
450 : GstFormat fmt;
451 0 : gst_query_parse_position(query, &fmt, NULL);
452 0 : switch (fmt)
453 : {
454 0 : case GST_FORMAT_TIME:
455 : {
456 : // GST_CLOCK_TIME_NONE has to be returned here, because otherwise whole pipeline returns incorrect position
457 0 : gst_query_set_position(query, fmt, GST_CLOCK_TIME_NONE);
458 0 : break;
459 : }
460 0 : default:
461 0 : break;
462 : }
463 0 : return TRUE;
464 : }
465 0 : default:
466 0 : break;
467 : }
468 0 : GstElement *parent = GST_ELEMENT(&sink->parent);
469 0 : return GST_ELEMENT_CLASS(parent_class)->query(parent, query);
470 : }
471 :
472 : namespace firebolt::rialto::server
473 : {
474 1 : std::shared_ptr<IGstTextTrackSinkFactory> IGstTextTrackSinkFactory::createFactory()
475 : {
476 1 : std::shared_ptr<IGstTextTrackSinkFactory> factory;
477 :
478 : try
479 : {
480 1 : factory = std::make_shared<GstTextTrackSinkFactory>();
481 : }
482 0 : catch (const std::exception &e)
483 : {
484 0 : RIALTO_SERVER_LOG_ERROR("Failed to create the textTrackSink element factory, reason: %s", e.what());
485 : }
486 :
487 1 : return factory;
488 : }
489 :
490 0 : GstElement *GstTextTrackSinkFactory::createGstTextTrackSink() const
491 : {
492 0 : GstElement *elem = GST_ELEMENT(g_object_new(GST_RIALTO_TEXT_TRACK_SINK_TYPE, nullptr));
493 :
494 0 : return elem;
495 : }
496 :
497 : }; // namespace firebolt::rialto::server
|