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