Line data Source code
1 : /*
2 : * GPAC - Multimedia Framework C SDK
3 : *
4 : * Authors: Deniz Ugur, Romain Bouqueau, Sohaib Larbi
5 : * Copyright (c) Motion Spell
6 : * All rights reserved
7 : *
8 : * This file is part of the GPAC/GStreamer wrapper
9 : *
10 : * This GPAC/GStreamer wrapper is free software; you can redistribute it
11 : * and/or modify it under the terms of the GNU Affero General Public License
12 : * as published by the Free Software Foundation; either version 3, or (at
13 : * your option) any later version.
14 : *
15 : * This GPAC/GStreamer wrapper is distributed in the hope that it will be
16 : * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
17 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 : * GNU Affero General Public License for more details.
19 : *
20 : * You should have received a copy of the GNU Affero General Public
21 : * License along with this library; see the file LICENSE. If not, write to
22 : * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 : *
24 : */
25 :
26 : #include "elements/gstgpactf.h"
27 : #include "elements/gstgpacsink.h"
28 :
29 : GST_DEBUG_CATEGORY_STATIC(gst_gpac_tf_debug);
30 : #define GST_CAT_DEFAULT gst_gpac_tf_debug
31 :
32 : // #MARK: Pad Class
33 27973 : G_DEFINE_TYPE(GstGpacTransformPad, gst_gpac_tf_pad, GST_TYPE_AGGREGATOR_PAD);
34 :
35 : enum
36 : {
37 : GPAC_PROP_PAD_0,
38 : GPAC_PROP_PAD_PID,
39 : };
40 :
41 : // #MARK: [P] Properties
42 : static void
43 46 : gst_gpac_tf_pad_set_property(GObject* object,
44 : guint prop_id,
45 : const GValue* value,
46 : GParamSpec* pspec)
47 : {
48 46 : GstGpacTransformPad* pad = GST_GPAC_TF_PAD(object);
49 46 : g_return_if_fail(GST_IS_GPAC_TF_PAD(object));
50 :
51 46 : switch (prop_id) {
52 46 : case GPAC_PROP_PAD_PID:
53 46 : pad->pid = g_value_get_pointer(value);
54 46 : break;
55 :
56 0 : default:
57 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
58 0 : break;
59 : }
60 : }
61 :
62 : static void
63 13928 : gst_gpac_tf_pad_get_property(GObject* object,
64 : guint prop_id,
65 : GValue* value,
66 : GParamSpec* pspec)
67 : {
68 13928 : GstGpacTransformPad* pad = GST_GPAC_TF_PAD(object);
69 13928 : g_return_if_fail(GST_IS_GPAC_TF_PAD(object));
70 :
71 13928 : switch (prop_id) {
72 13928 : case GPAC_PROP_PAD_PID:
73 13928 : g_value_set_pointer(value, pad->pid);
74 13928 : break;
75 :
76 0 : default:
77 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
78 0 : break;
79 : }
80 : }
81 :
82 : // #MARK: [P] Initialization
83 : static void
84 20 : gst_gpac_tf_pad_init(GstGpacTransformPad* pad)
85 : {
86 20 : GpacPadPrivate* priv = g_new0(GpacPadPrivate, 1);
87 20 : priv->self = GST_PAD(pad);
88 20 : priv->idr_period = GST_CLOCK_TIME_NONE;
89 20 : priv->idr_last = GST_CLOCK_TIME_NONE;
90 20 : priv->idr_next = GST_CLOCK_TIME_NONE;
91 20 : gst_pad_set_element_private(GST_PAD(pad), priv);
92 :
93 20 : pad->pid = NULL;
94 20 : };
95 :
96 : static void
97 0 : gst_gpac_tf_pad_finalize(GObject* object)
98 : {
99 0 : GstGpacTransformPad* pad = GST_GPAC_TF_PAD(object);
100 0 : GpacPadPrivate* priv = gst_pad_get_element_private(GST_PAD(pad));
101 :
102 0 : if (priv) {
103 0 : if (priv->caps)
104 0 : gst_caps_unref(priv->caps);
105 0 : if (priv->segment)
106 0 : gst_segment_free(priv->segment);
107 0 : if (priv->tags)
108 0 : gst_tag_list_unref(priv->tags);
109 0 : g_free(priv);
110 0 : gst_pad_set_element_private(GST_PAD(pad), NULL);
111 : }
112 :
113 0 : G_OBJECT_CLASS(gst_gpac_tf_pad_parent_class)->finalize(object);
114 0 : }
115 :
116 : static void
117 1 : gst_gpac_tf_pad_class_init(GstGpacTransformPadClass* klass)
118 : {
119 1 : GObjectClass* gobject_class = (GObjectClass*)klass;
120 :
121 1 : gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_gpac_tf_pad_finalize);
122 1 : gobject_class->set_property = GST_DEBUG_FUNCPTR(gst_gpac_tf_pad_set_property);
123 1 : gobject_class->get_property = GST_DEBUG_FUNCPTR(gst_gpac_tf_pad_get_property);
124 :
125 1 : g_object_class_install_property(
126 : gobject_class,
127 : GPAC_PROP_PAD_PID,
128 : g_param_spec_pointer("pid",
129 : "GPAC's Filter PID",
130 : "The PID of the filter in the GPAC session",
131 : G_PARAM_READWRITE));
132 1 : };
133 :
134 : // #MARK: Element Class
135 : #define gst_gpac_tf_parent_class parent_class
136 12464 : G_DEFINE_TYPE(GstGpacTransform, gst_gpac_tf, GST_TYPE_AGGREGATOR);
137 :
138 : // #MARK: Properties
139 : static void
140 14 : gst_gpac_tf_set_property(GObject* object,
141 : guint prop_id,
142 : const GValue* value,
143 : GParamSpec* pspec)
144 : {
145 14 : GstGpacTransform* gpac_tf = GST_GPAC_TF(object);
146 14 : g_return_if_fail(GST_IS_GPAC_TF(object));
147 14 : if (gpac_set_property(GPAC_PROP_CTX(GPAC_CTX), prop_id, value, pspec))
148 12 : return;
149 :
150 : // Handle the element properties
151 2 : if (IS_ELEMENT_PROPERTY(prop_id)) {
152 2 : switch (prop_id) {
153 2 : case GPAC_PROP_SEGDUR:
154 2 : gpac_tf->global_idr_period =
155 2 : ((guint64)g_value_get_float(value)) * GST_SECOND;
156 2 : GST_DEBUG_OBJECT(object,
157 : "Set global IDR period to %" GST_TIME_FORMAT,
158 : GST_TIME_ARGS(gpac_tf->global_idr_period));
159 2 : break;
160 :
161 0 : default:
162 0 : break;
163 : }
164 2 : return;
165 : }
166 :
167 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
168 : }
169 :
170 : static void
171 0 : gst_gpac_tf_get_property(GObject* object,
172 : guint prop_id,
173 : GValue* value,
174 : GParamSpec* pspec)
175 : {
176 0 : GstGpacTransform* gpac_tf = GST_GPAC_TF(object);
177 0 : g_return_if_fail(GST_IS_GPAC_TF(object));
178 0 : if (gpac_get_property(GPAC_PROP_CTX(GPAC_CTX), prop_id, value, pspec))
179 0 : return;
180 :
181 : // Handle the element properties
182 0 : if (IS_ELEMENT_PROPERTY(prop_id)) {
183 0 : switch (prop_id) {
184 0 : case GPAC_PROP_SEGDUR:
185 0 : g_value_set_float(value,
186 0 : ((float)gpac_tf->global_idr_period) / GST_SECOND);
187 0 : break;
188 :
189 0 : default:
190 0 : break;
191 : }
192 0 : return;
193 : }
194 :
195 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
196 : }
197 :
198 : // #MARK: Helper Functions
199 : static gboolean
200 2527 : gpac_prepare_pids(GstElement* element)
201 : {
202 2527 : GstGpacTransform* gpac_tf = GST_GPAC_TF(element);
203 : GstIterator* pad_iter;
204 2527 : GValue item = G_VALUE_INIT;
205 2527 : gboolean done = FALSE;
206 2527 : gboolean ret = FALSE;
207 :
208 : // Track configuration status
209 : GHashTable* pids_before_reset =
210 2527 : g_hash_table_new(g_direct_hash, g_direct_equal);
211 2527 : GHashTable* pids_seen = g_hash_table_new(g_direct_hash, g_direct_equal);
212 :
213 : // Iterate over the pads
214 2527 : pad_iter = gst_element_iterate_sink_pads(element);
215 12458 : while (!done) {
216 9931 : switch (gst_iterator_next(pad_iter, &item)) {
217 7404 : case GST_ITERATOR_OK: {
218 7404 : GstPad* pad = g_value_get_object(&item);
219 7404 : GstAggregatorPad* agg_pad = GST_AGGREGATOR_PAD(pad);
220 7404 : GpacPadPrivate* priv = gst_pad_get_element_private(pad);
221 :
222 : // Get the PID
223 7404 : GF_FilterPid* pid = NULL;
224 7404 : g_object_get(agg_pad, "pid", &pid, NULL);
225 :
226 : // Create the PID if necessary
227 7404 : if (pid == NULL) {
228 23 : pid = gpac_pid_new(GPAC_SESS_CTX(GPAC_CTX));
229 23 : if (G_UNLIKELY(pid == NULL)) {
230 0 : GST_ELEMENT_ERROR(
231 : element, STREAM, FAILED, (NULL), ("Failed to create PID"));
232 0 : goto fail;
233 : }
234 23 : g_object_set(agg_pad, "pid", pid, NULL);
235 :
236 : // Share the pad private data
237 23 : gf_filter_pid_set_udta(pid, priv);
238 : }
239 :
240 7404 : if (priv->flags) {
241 21 : if (G_UNLIKELY(!gpac_pid_reconfigure(element, priv, pid))) {
242 0 : GST_ELEMENT_ERROR(
243 : element, STREAM, FAILED, (NULL), ("Failed to reconfigure PID"));
244 0 : goto fail;
245 : }
246 21 : priv->flags = 0;
247 : }
248 :
249 : // Track the PID
250 7404 : g_hash_table_insert(pids_seen, pid, GINT_TO_POINTER(1));
251 :
252 7404 : g_value_reset(&item);
253 7404 : break;
254 : }
255 0 : case GST_ITERATOR_RESYNC:
256 0 : gst_iterator_resync(pad_iter);
257 :
258 : // Add seen PIDs to the before reset list
259 0 : g_hash_table_foreach(
260 : pids_seen, (GHFunc)g_hash_table_insert, pids_before_reset);
261 0 : g_hash_table_remove_all(pids_seen);
262 :
263 0 : break;
264 0 : case GST_ITERATOR_ERROR:
265 0 : GST_ELEMENT_ERROR(
266 : element, STREAM, FAILED, (NULL), ("Failed to iterate over pads"));
267 0 : goto fail;
268 2527 : case GST_ITERATOR_DONE:
269 2527 : done = TRUE;
270 2527 : ret = TRUE;
271 2527 : break;
272 : }
273 : }
274 :
275 : // Check for PIDs that are no longer needed
276 : GHashTableIter iter;
277 : gpointer key, value;
278 2527 : g_hash_table_iter_init(&iter, pids_before_reset);
279 5054 : while (g_hash_table_iter_next(&iter, &key, &value)) {
280 0 : if (!g_hash_table_contains(pids_seen, key)) {
281 0 : GF_FilterPid* pid = key;
282 0 : gpac_pid_del(pid);
283 : }
284 : }
285 :
286 2527 : fail:
287 : // Clean up
288 2527 : g_value_unset(&item);
289 2527 : gst_iterator_free(pad_iter);
290 2527 : g_hash_table_destroy(pids_before_reset);
291 2527 : g_hash_table_destroy(pids_seen);
292 2527 : return ret;
293 : }
294 :
295 : // #MARK: Aggregator
296 : GstFlowReturn
297 2511 : gst_gpac_tf_consume(GstAggregator* agg, Bool is_eos)
298 : {
299 2511 : GstFlowReturn flow_ret = GST_FLOW_OK;
300 2511 : GstGpacTransform* gpac_tf = GST_GPAC_TF(agg);
301 2511 : GstSegment* segment = &GST_AGGREGATOR_PAD(agg->srcpad)->segment;
302 :
303 2511 : GST_DEBUG_OBJECT(agg, "Consuming output...");
304 :
305 : void* output;
306 : GPAC_FilterPPRet ret;
307 5052 : while ((ret = gpac_memio_consume(GPAC_SESS_CTX(GPAC_CTX), &output))) {
308 2541 : if (ret & GPAC_FILTER_PP_RET_ERROR) {
309 : // An error occurred, stop processing
310 0 : goto error;
311 : }
312 :
313 2541 : if (ret == GPAC_FILTER_PP_RET_EMPTY) {
314 : // No data available
315 888 : GST_DEBUG_OBJECT(agg, "No more data available, exiting");
316 888 : return is_eos ? GST_FLOW_EOS : GST_FLOW_OK;
317 : }
318 :
319 1653 : gboolean had_signal = (ret & GPAC_FILTER_PP_RET_SIGNAL) != 0;
320 1653 : if (ret > GPAC_MAY_HAVE_BUFFER) {
321 1653 : if (output) {
322 : // Notify the selected sample
323 44 : gst_aggregator_selected_samples(agg,
324 44 : GST_BUFFER_PTS(output),
325 44 : GST_BUFFER_DTS(output),
326 44 : GST_BUFFER_DURATION(output),
327 : NULL);
328 : }
329 :
330 1653 : if (HAS_FLAG(ret, GPAC_FILTER_PP_RET_BUFFER)) {
331 : // Send the buffer
332 0 : GST_DEBUG_OBJECT(agg, "Sending buffer");
333 0 : flow_ret = gst_aggregator_finish_buffer(agg, GST_BUFFER(output));
334 0 : GST_DEBUG_OBJECT(agg, "Buffer sent!");
335 1653 : } else if (HAS_FLAG(ret, GPAC_FILTER_PP_RET_BUFFER_LIST)) {
336 : // Send the buffer list
337 30 : GST_DEBUG_OBJECT(agg, "Sending buffer list");
338 30 : GstBufferList* buffer_list = GST_BUFFER_LIST(output);
339 :
340 : // Show in debug the buffer list pts and dts (running time) and flags
341 1109 : for (guint i = 0; i < gst_buffer_list_length(buffer_list); i++) {
342 1079 : GstBuffer* buffer = gst_buffer_list_get(buffer_list, i);
343 : // Calculate pts and dts in running time
344 1079 : guint64 pts = gst_segment_to_running_time(
345 : segment, GST_FORMAT_TIME, GST_BUFFER_PTS(buffer));
346 1079 : guint64 dts = gst_segment_to_running_time(
347 : segment, GST_FORMAT_TIME, GST_BUFFER_DTS(buffer));
348 1079 : guint64 duration = gst_segment_to_running_time(
349 : segment, GST_FORMAT_TIME, GST_BUFFER_DURATION(buffer));
350 1079 : GST_DEBUG_OBJECT(
351 : agg,
352 : "Buffer %d: PTS: %" GST_TIME_FORMAT ", DTS: %" GST_TIME_FORMAT
353 : ", Duration: %" GST_TIME_FORMAT ", IsHeader: %s",
354 : i,
355 : GST_TIME_ARGS(pts),
356 : GST_TIME_ARGS(dts),
357 : GST_TIME_ARGS(duration),
358 : GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_HEADER) ? "Yes"
359 : : "No");
360 : }
361 :
362 30 : flow_ret = gst_aggregator_finish_buffer_list(agg, buffer_list);
363 30 : GST_DEBUG_OBJECT(agg, "Buffer list sent!");
364 1623 : } else if (HAS_FLAG(ret, GPAC_FILTER_PP_RET_NULL)) {
365 : // If we had signals, consume all of them first
366 1623 : if (had_signal)
367 0 : continue;
368 :
369 1623 : if (is_eos)
370 5 : return GST_FLOW_EOS;
371 :
372 1618 : GST_DEBUG_OBJECT(agg,
373 : "Sending sync buffer, possibly connected to fakesink");
374 :
375 : // We send only one buffer regardless of potential pending buffers
376 1618 : GstBuffer* buffer = gpac_tf->sync_buffer;
377 1618 : if (!buffer)
378 416 : buffer = gst_buffer_new();
379 :
380 : // Send the sync buffer
381 1618 : flow_ret = gst_aggregator_finish_buffer(agg, buffer);
382 :
383 : // Buffer is transferred to the aggregator, so we set it to NULL
384 1618 : if (gpac_tf->sync_buffer)
385 1202 : gpac_tf->sync_buffer = NULL;
386 :
387 1618 : return flow_ret;
388 : } else {
389 0 : GST_ELEMENT_WARNING(
390 : agg, STREAM, FAILED, (NULL), ("Unknown return value: %d", ret));
391 0 : g_warn_if_reached();
392 : }
393 :
394 30 : if (flow_ret != GST_FLOW_OK) {
395 0 : GST_ELEMENT_ERROR(agg,
396 : STREAM,
397 : FAILED,
398 : (NULL),
399 : ("Failed to finish buffer, ret: %d", flow_ret));
400 0 : return flow_ret;
401 : }
402 : }
403 : }
404 :
405 0 : error:
406 0 : GST_ELEMENT_ERROR(agg, STREAM, FAILED, (NULL), ("Failed to consume output"));
407 0 : return GST_FLOW_ERROR;
408 : }
409 :
410 : static gboolean
411 137 : gst_gpac_tf_sink_event(GstAggregator* agg,
412 : GstAggregatorPad* pad,
413 : GstEvent* event)
414 : {
415 137 : GstGpacTransform* gpac_tf = GST_GPAC_TF(GST_ELEMENT(agg));
416 137 : GpacPadPrivate* priv = gst_pad_get_element_private(GST_PAD(pad));
417 :
418 137 : switch (GST_EVENT_TYPE(event)) {
419 25 : case GST_EVENT_CAPS: {
420 25 : if (priv->caps)
421 5 : gst_caps_unref(priv->caps);
422 :
423 : GstCaps* caps;
424 25 : gst_event_parse_caps(event, &caps);
425 25 : priv->caps = gst_caps_ref(caps);
426 25 : priv->flags |= GPAC_PAD_CAPS_SET;
427 25 : break;
428 : }
429 :
430 23 : case GST_EVENT_SEGMENT: {
431 23 : if (priv->segment)
432 3 : gst_segment_free(priv->segment);
433 :
434 : const GstSegment* segment;
435 23 : gst_event_parse_segment(event, &segment);
436 23 : priv->segment = gst_segment_copy(segment);
437 23 : priv->flags |= GPAC_PAD_SEGMENT_SET;
438 23 : priv->dts_offset_set = FALSE;
439 :
440 23 : gboolean is_video_pad = gst_pad_get_pad_template(GST_PAD(pad)) ==
441 23 : gst_gpac_get_sink_template(GPAC_TEMPLATE_VIDEO);
442 23 : gboolean is_only_pad = g_list_length(GST_ELEMENT(agg)->sinkpads) == 1;
443 :
444 : // Update the segment and global offset only if video pad or the only pad
445 23 : if (is_video_pad || is_only_pad) {
446 20 : gst_aggregator_update_segment(agg, gst_segment_copy(segment));
447 20 : gpac_memio_set_global_offset(GPAC_SESS_CTX(GPAC_CTX), segment);
448 : }
449 :
450 : // Check if playback rate is equal to 1.0
451 23 : if (segment->rate != 1.0)
452 0 : GST_FIXME_OBJECT(agg,
453 : "Handling of non-1.0 playback rate is not "
454 : "implemented yet");
455 :
456 23 : break;
457 : }
458 :
459 25 : case GST_EVENT_TAG: {
460 25 : if (priv->tags)
461 6 : gst_tag_list_unref(priv->tags);
462 :
463 : GstTagList* tags;
464 25 : gst_event_parse_tag(event, &tags);
465 25 : priv->tags = gst_tag_list_ref(tags);
466 25 : priv->flags |= GPAC_PAD_TAGS_SET;
467 25 : break;
468 : }
469 :
470 20 : case GST_EVENT_EOS: {
471 : // Set this pad as EOS
472 20 : priv->eos = TRUE;
473 :
474 : // Are all pads EOS?
475 20 : gboolean all_eos = TRUE;
476 20 : GList* sinkpads = GST_ELEMENT(agg)->sinkpads;
477 52 : for (GList* l = sinkpads; l; l = l->next) {
478 39 : GstAggregatorPad* agg_pad = GST_AGGREGATOR_PAD(l->data);
479 39 : GpacPadPrivate* priv = gst_pad_get_element_private(GST_PAD(agg_pad));
480 39 : if (!priv->eos) {
481 7 : all_eos = FALSE;
482 7 : break;
483 : }
484 : }
485 :
486 20 : if (!all_eos) {
487 7 : GST_DEBUG_OBJECT(agg, "Not all pads are EOS, not sending EOS to GPAC");
488 7 : break;
489 : }
490 :
491 : // If all pads are EOS, send EOS to the source
492 13 : GST_DEBUG_OBJECT(agg, "All pads are EOS, sending EOS to GPAC");
493 13 : gpac_memio_set_eos(GPAC_SESS_CTX(GPAC_CTX), TRUE);
494 13 : gpac_session_run(GPAC_SESS_CTX(GPAC_CTX), TRUE);
495 13 : gst_gpac_tf_consume(agg, GST_EVENT_TYPE(event) == GST_EVENT_EOS);
496 13 : break;
497 : }
498 :
499 0 : case GST_EVENT_FLUSH_START:
500 0 : gpac_session_run(GPAC_SESS_CTX(GPAC_CTX), TRUE);
501 0 : gst_gpac_tf_consume(agg, GST_EVENT_TYPE(event) == GST_EVENT_EOS);
502 0 : break;
503 :
504 44 : default:
505 44 : break;
506 : }
507 :
508 137 : return GST_AGGREGATOR_CLASS(parent_class)->sink_event(agg, pad, event);
509 : }
510 :
511 : gboolean
512 14 : gst_gpac_tf_negotiated_src_caps(GstAggregator* agg, GstCaps* caps)
513 : {
514 14 : GstGpacTransform* gpac_tf = GST_GPAC_TF(GST_ELEMENT(agg));
515 14 : GstElement* element = GST_ELEMENT(agg);
516 14 : GObjectClass* klass = G_OBJECT_CLASS(G_TYPE_INSTANCE_GET_CLASS(
517 : G_OBJECT(element), GST_TYPE_GPAC_TF, GstGpacTransformClass));
518 14 : GstGpacParams* params = GST_GPAC_GET_PARAMS(klass);
519 :
520 : // Check if this is element is inside our sink bin
521 14 : GstObject* sink_bin = gst_element_get_parent(element);
522 14 : if (GST_IS_GPAC_SINK(sink_bin)) {
523 : // We might already have a destination set
524 5 : if ((params->is_single && params->info->destination) ||
525 4 : GPAC_PROP_CTX(GPAC_CTX)->destination) {
526 2 : return TRUE;
527 : }
528 : }
529 :
530 12 : return gpac_memio_set_gst_caps(GPAC_SESS_CTX(GPAC_CTX), caps);
531 : }
532 :
533 : void
534 4637 : gst_gpac_request_idr(GstAggregator* agg, GstPad* pad, GstBuffer* buffer)
535 : {
536 4637 : GstGpacTransform* gpac_tf = GST_GPAC_TF(GST_ELEMENT(agg));
537 4637 : GpacPadPrivate* priv = gst_pad_get_element_private(pad);
538 : GstEvent* gst_event;
539 :
540 : // Skip if this is not a key frame
541 4637 : if (GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DELTA_UNIT))
542 4538 : return;
543 :
544 : // Skip if we don't have a valid PTS
545 99 : if (!GST_BUFFER_PTS_IS_VALID(buffer))
546 0 : return;
547 :
548 : // Decide on which IDR period to use
549 99 : guint64 idr_period = GST_CLOCK_TIME_NONE;
550 99 : if (priv->idr_period != GST_CLOCK_TIME_NONE) {
551 79 : idr_period = priv->idr_period;
552 : // Preserve the IDR period sent by gpac
553 79 : gpac_tf->gpac_idr_period = idr_period;
554 : }
555 :
556 : // Use the global IDR period if available
557 99 : if (gpac_tf->global_idr_period)
558 4 : idr_period = gpac_tf->global_idr_period;
559 :
560 : // Skip if we don't have an IDR period
561 99 : if (idr_period == GST_CLOCK_TIME_NONE)
562 16 : return;
563 :
564 166 : priv->idr_last = gst_segment_to_running_time(
565 83 : priv->segment, GST_FORMAT_TIME, GST_BUFFER_PTS(buffer));
566 :
567 83 : GST_DEBUG_OBJECT(agg,
568 : "Key frame received at %" GST_TIME_FORMAT,
569 : GST_TIME_ARGS(priv->idr_last));
570 :
571 : // If this is the first IDR, request it immediately
572 83 : if (priv->idr_next == GST_CLOCK_TIME_NONE) {
573 8 : priv->idr_next = priv->idr_last + idr_period;
574 8 : goto request;
575 : }
576 :
577 : // If this IDR arrived before the next scheduled IDR, ignore
578 75 : if (priv->idr_last < priv->idr_next) {
579 56 : guint64 diff = priv->idr_next - priv->idr_last;
580 56 : GST_DEBUG_OBJECT(agg,
581 : "IDR arrived %" GST_TIME_FORMAT
582 : " before the next IDR on pad %s",
583 : GST_TIME_ARGS(diff),
584 : GST_PAD_NAME(pad));
585 56 : return;
586 : }
587 :
588 : // Check if this IDR was on time
589 19 : guint64 diff = priv->idr_last - priv->idr_next;
590 19 : if (diff)
591 0 : GST_ELEMENT_WARNING(agg,
592 : STREAM,
593 : FAILED,
594 : ("IDR was late by %" GST_TIME_FORMAT
595 : " on pad %s, reconsider encoding options",
596 : GST_TIME_ARGS(diff),
597 : GST_PAD_NAME(pad)),
598 : (NULL));
599 :
600 : // Schedule the next IDR at the desired time, regardless of whether current
601 : // one was late or not
602 19 : priv->idr_next += idr_period;
603 :
604 27 : request:
605 : // Send the next IDR request
606 : gst_event =
607 27 : gst_video_event_new_upstream_force_key_unit(priv->idr_next, TRUE, 1);
608 27 : GST_DEBUG_OBJECT(
609 : agg, "Requesting IDR at %" GST_TIME_FORMAT, GST_TIME_ARGS(priv->idr_next));
610 27 : if (!gst_pad_push_event(pad, gst_event))
611 0 : GST_ELEMENT_WARNING(
612 : agg, STREAM, FAILED, (NULL), ("Failed to push the force key unit event"));
613 : }
614 :
615 : static GstFlowReturn
616 2511 : gst_gpac_tf_aggregate(GstAggregator* agg, gboolean timeout)
617 : {
618 2511 : GstGpacTransform* gpac_tf = GST_GPAC_TF(GST_ELEMENT(agg));
619 : GstIterator* pad_iter;
620 2511 : GValue item = G_VALUE_INIT;
621 2511 : gboolean done = FALSE;
622 2511 : gboolean has_buffers = TRUE;
623 :
624 : // Check and create PIDs if necessary
625 2511 : if (!gpac_prepare_pids(GST_ELEMENT(agg))) {
626 0 : GST_ELEMENT_ERROR(agg, STREAM, FAILED, (NULL), ("Failed to prepare PIDs"));
627 0 : return GST_FLOW_ERROR;
628 : }
629 :
630 2511 : GST_DEBUG_OBJECT(agg, "Aggregating buffers");
631 :
632 : // Create the temporary queue
633 2511 : GQueue* queue = g_queue_new();
634 :
635 : // Keep consuming buffers until all pads are drained
636 8130 : while (has_buffers) {
637 5619 : has_buffers = FALSE;
638 5619 : done = FALSE;
639 :
640 : // Iterate over the pads
641 5619 : pad_iter = gst_element_iterate_sink_pads(GST_ELEMENT(agg));
642 27930 : while (!done) {
643 22311 : switch (gst_iterator_next(pad_iter, &item)) {
644 16692 : case GST_ITERATOR_OK: {
645 16692 : GF_FilterPid* pid = NULL;
646 16692 : GstPad* pad = g_value_get_object(&item);
647 16692 : GpacPadPrivate* priv = gst_pad_get_element_private(pad);
648 : GstBuffer* buffer =
649 16692 : gst_aggregator_pad_pop_buffer(GST_AGGREGATOR_PAD(pad));
650 :
651 : // Continue if no buffer is available
652 16692 : if (!buffer) {
653 10168 : GST_DEBUG_OBJECT(
654 : agg, "No buffer available on pad %s", GST_PAD_NAME(pad));
655 10168 : goto next;
656 : }
657 :
658 : // We found at least one buffer, continue the outer loop
659 6524 : has_buffers = TRUE;
660 :
661 : // Skip droppable/gap buffers
662 6524 : if (GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_GAP)) {
663 0 : GST_DEBUG_OBJECT(
664 : agg, "Gap buffer received on pad %s", GST_PAD_NAME(pad));
665 0 : goto next;
666 : }
667 :
668 : // Send the key frame request
669 : // Only send IDR request for video pads
670 13048 : if (gst_pad_get_pad_template(pad) ==
671 6524 : gst_gpac_get_sink_template(GPAC_TEMPLATE_VIDEO)) {
672 4637 : gst_gpac_request_idr(agg, pad, buffer);
673 : }
674 :
675 : // Get the PID
676 6524 : g_object_get(GST_AGGREGATOR_PAD(pad), "pid", &pid, NULL);
677 6524 : g_assert(pid);
678 :
679 : // Create the packet
680 6524 : GF_FilterPacket* packet = gpac_pck_new_from_buffer(buffer, priv, pid);
681 6524 : if (!packet) {
682 0 : GST_ELEMENT_ERROR(agg,
683 : STREAM,
684 : FAILED,
685 : (NULL),
686 : ("Failed to create packet from buffer"));
687 0 : goto next;
688 : }
689 :
690 : // Enqueue the packet
691 6524 : g_queue_push_tail(queue, packet);
692 :
693 : // Select the highest PTS for sync buffer
694 6524 : gboolean is_video_pad =
695 6524 : gst_pad_get_pad_template(GST_PAD(pad)) ==
696 6524 : gst_gpac_get_sink_template(GPAC_TEMPLATE_VIDEO);
697 6524 : gboolean is_only_pad = g_list_length(GST_ELEMENT(agg)->sinkpads) == 1;
698 6524 : if (is_video_pad || is_only_pad) {
699 4637 : if (gpac_tf->sync_buffer) {
700 3427 : guint64 current_pts = GST_BUFFER_PTS(buffer);
701 3427 : guint64 sync_pts = GST_BUFFER_PTS(gpac_tf->sync_buffer);
702 3427 : if (current_pts > sync_pts) {
703 1588 : gst_buffer_replace(&gpac_tf->sync_buffer, buffer);
704 : }
705 : } else {
706 : // If no sync buffer exists, create one
707 1210 : gpac_tf->sync_buffer = gst_buffer_ref(buffer);
708 : }
709 : }
710 :
711 1887 : next:
712 16692 : if (buffer)
713 6524 : gst_buffer_unref(buffer);
714 16692 : g_value_reset(&item);
715 16692 : break;
716 : }
717 0 : case GST_ITERATOR_RESYNC:
718 0 : gst_iterator_resync(pad_iter);
719 0 : GST_ELEMENT_WARNING(agg,
720 : STREAM,
721 : FAILED,
722 : (NULL),
723 : ("Data structure changed during pad iteration, "
724 : "discarding all packets"));
725 0 : g_queue_clear_full(queue, (GDestroyNotify)gf_filter_pck_unref);
726 0 : break;
727 5619 : case GST_ITERATOR_ERROR:
728 : case GST_ITERATOR_DONE:
729 5619 : done = TRUE;
730 5619 : break;
731 : }
732 : }
733 : }
734 :
735 : // Clean up
736 2511 : g_value_unset(&item);
737 2511 : gst_iterator_free(pad_iter);
738 :
739 : // Check if we have any packets to send
740 2511 : if (g_queue_is_empty(queue)) {
741 13 : GST_DEBUG_OBJECT(agg, "No packets to send, returning EOS");
742 13 : g_queue_free(queue);
743 13 : return GST_FLOW_EOS;
744 : }
745 :
746 : // Merge the queues
747 9022 : while (!g_queue_is_empty(queue)) {
748 6524 : gpointer pck = g_queue_pop_head(queue);
749 6524 : g_queue_push_tail(gpac_tf->queue, pck);
750 : }
751 2498 : g_queue_free(queue);
752 :
753 : // Run the filter session
754 2498 : if (gpac_session_run(GPAC_SESS_CTX(GPAC_CTX), FALSE) != GF_OK) {
755 0 : GST_ELEMENT_ERROR(
756 : agg, STREAM, FAILED, (NULL), ("Failed to run the GPAC session"));
757 0 : return GST_FLOW_ERROR;
758 : }
759 :
760 : // Consume the output
761 2498 : return gst_gpac_tf_consume(agg, FALSE);
762 : }
763 :
764 : // #MARK: Pad Management
765 : static GstAggregatorPad*
766 20 : gst_gpac_tf_create_new_pad(GstAggregator* element,
767 : GstPadTemplate* templ,
768 : const gchar* pad_name,
769 : const GstCaps* caps)
770 : {
771 20 : GstElementClass* klass = GST_ELEMENT_GET_CLASS(element);
772 20 : GstGpacTransform* agg = GST_GPAC_TF(element);
773 : gchar* name;
774 : gint pad_id;
775 :
776 : #define TEMPLATE_CHECK(prefix, count_field) \
777 : if (templ == gst_element_class_get_pad_template(klass, prefix "_%u")) { \
778 : agg->count_field++; \
779 : if (pad_name != NULL && sscanf(pad_name, prefix "_%u", &pad_id) == 1) { \
780 : name = g_strdup(pad_name); \
781 : } else { \
782 : pad_id = agg->audio_pad_count + agg->video_pad_count + \
783 : agg->subtitle_pad_count + agg->caption_pad_count; \
784 : name = g_strdup_printf(prefix "_%u", pad_id); \
785 : } \
786 : } else
787 :
788 : // Check the pad template and decide the pad name
789 20 : TEMPLATE_CHECK("video", video_pad_count)
790 3 : TEMPLATE_CHECK("audio", audio_pad_count)
791 0 : TEMPLATE_CHECK("subtitle", subtitle_pad_count)
792 0 : TEMPLATE_CHECK("caption", caption_pad_count)
793 : {
794 0 : GST_ELEMENT_WARNING(
795 : agg, STREAM, FAILED, (NULL), ("This is not our template!"));
796 0 : return NULL;
797 : }
798 :
799 : #undef TEMPLATE_CHECK
800 :
801 20 : GST_DEBUG_OBJECT(agg, "Creating new pad %s", name);
802 :
803 : // Create the pad
804 20 : GstGpacTransformPad* pad = g_object_new(GST_TYPE_GPAC_TF_PAD,
805 : "name",
806 : name,
807 : "direction",
808 20 : templ->direction,
809 : "template",
810 : templ,
811 : NULL);
812 20 : g_free(name);
813 :
814 : // Initialize the private data
815 20 : GpacPadPrivate* priv = gst_pad_get_element_private(GST_PAD(pad));
816 20 : priv->id = pad_id;
817 20 : if (caps) {
818 0 : priv->caps = gst_caps_copy(caps);
819 0 : priv->flags |= GPAC_PAD_CAPS_SET;
820 : }
821 :
822 20 : return GST_AGGREGATOR_PAD(pad);
823 : }
824 :
825 : // #MARK: Lifecycle
826 : static void
827 29 : gst_gpac_tf_reset(GstGpacTransform* tf)
828 : {
829 29 : gboolean done = FALSE;
830 29 : GValue item = G_VALUE_INIT;
831 29 : GstIterator* pad_iter = gst_element_iterate_sink_pads(GST_ELEMENT(tf));
832 81 : while (!done) {
833 52 : switch (gst_iterator_next(pad_iter, &item)) {
834 23 : case GST_ITERATOR_OK: {
835 23 : GF_FilterPid* pid = NULL;
836 23 : GstPad* pad = g_value_get_object(&item);
837 23 : GpacPadPrivate* priv = gst_pad_get_element_private(pad);
838 :
839 : // Reset the PID
840 23 : g_object_set(GST_AGGREGATOR_PAD(pad), "pid", NULL, NULL);
841 23 : break;
842 : }
843 29 : case GST_ITERATOR_RESYNC:
844 : case GST_ITERATOR_ERROR:
845 : case GST_ITERATOR_DONE:
846 29 : done = TRUE;
847 29 : break;
848 : }
849 : }
850 29 : g_value_unset(&item);
851 29 : gst_iterator_free(pad_iter);
852 :
853 : // Empty the queue
854 29 : if (tf->queue)
855 16 : g_queue_clear_full(tf->queue, (GDestroyNotify)gf_filter_pck_unref);
856 29 : }
857 :
858 : static gboolean
859 16 : gst_gpac_tf_start(GstAggregator* aggregator)
860 : {
861 16 : GstGpacTransform* gpac_tf = GST_GPAC_TF(aggregator);
862 16 : GstElement* element = GST_ELEMENT(aggregator);
863 16 : GObjectClass* klass = G_OBJECT_CLASS(G_TYPE_INSTANCE_GET_CLASS(
864 : G_OBJECT(element), GST_TYPE_GPAC_TF, GstGpacTransformClass));
865 16 : GstGpacParams* params = GST_GPAC_GET_PARAMS(klass);
866 : GstSegment segment;
867 :
868 : // Check if we have the graph property set
869 16 : if (!params->is_single && !GPAC_PROP_CTX(GPAC_CTX)->graph) {
870 0 : GST_ELEMENT_ERROR(
871 : element, STREAM, FAILED, (NULL), ("Graph property must be set"));
872 0 : return FALSE;
873 : }
874 :
875 : // Convert the properties to arguments
876 16 : if (!gpac_apply_properties(GPAC_PROP_CTX(GPAC_CTX))) {
877 0 : GST_ELEMENT_ERROR(
878 : element, LIBRARY, INIT, (NULL), ("Failed to apply properties"));
879 0 : return FALSE;
880 : }
881 : // Initialize the GPAC context
882 16 : if (!gpac_init(GPAC_CTX, element)) {
883 0 : GST_ELEMENT_ERROR(
884 : element, LIBRARY, INIT, (NULL), ("Failed to initialize GPAC context"));
885 0 : return FALSE;
886 : }
887 :
888 : // Create the session
889 16 : if (!gpac_session_init(GPAC_SESS_CTX(GPAC_CTX), element, params)) {
890 0 : GST_ELEMENT_ERROR(
891 : element, LIBRARY, INIT, (NULL), ("Failed to initialize GPAC session"));
892 0 : return FALSE;
893 : }
894 :
895 : // Create the memory input
896 16 : gpac_return_val_if_fail(
897 : gpac_memio_new(GPAC_SESS_CTX(GPAC_CTX), GPAC_MEMIO_DIR_IN), FALSE);
898 16 : gpac_memio_assign_queue(
899 : GPAC_SESS_CTX(GPAC_CTX), GPAC_MEMIO_DIR_IN, gpac_tf->queue);
900 :
901 : // Open the session
902 16 : gchar* graph = NULL;
903 16 : if (params->is_single) {
904 12 : if (params->info->default_options) {
905 11 : GList* props = GPAC_PROP_CTX(GPAC_CTX)->properties;
906 11 : GString* options = g_string_new(NULL);
907 :
908 : // Only override if not set already
909 34 : for (guint32 i = 0; params->info->default_options[i].name; i++) {
910 23 : gboolean found = FALSE;
911 :
912 44 : for (GList* l = props; l != NULL; l = l->next) {
913 21 : gchar* prop = (gchar*)l->data;
914 :
915 21 : g_autofree gchar* prefix =
916 21 : g_strdup_printf("--%s", params->info->default_options[i].name);
917 21 : if (g_str_has_prefix(prop, prefix)) {
918 0 : found = TRUE;
919 0 : break;
920 : }
921 : }
922 :
923 23 : if (!found) {
924 23 : const gchar* name = params->info->default_options[i].name;
925 23 : const gchar* value = params->info->default_options[i].value;
926 23 : g_string_append_printf(options, "%s=%s:", name, value);
927 : }
928 : }
929 :
930 : // Remove the trailing colon
931 11 : if (options->len > 0)
932 11 : g_string_truncate(options, options->len - 1);
933 :
934 11 : if (options->len > 0)
935 : graph =
936 11 : g_strdup_printf("%s:%s", params->info->filter_name, options->str);
937 : else
938 0 : graph = g_strdup(params->info->filter_name);
939 11 : g_string_free(options, TRUE);
940 : } else {
941 2 : graph = g_strdup(params->info->filter_name);
942 : }
943 : } else {
944 8 : graph = g_strdup(GPAC_PROP_CTX(GPAC_CTX)->graph);
945 : }
946 :
947 : // Set the destination override on session context
948 16 : GPAC_SESS_CTX(GPAC_CTX)->destination = GPAC_PROP_CTX(GPAC_CTX)->destination;
949 :
950 16 : gpac_return_val_if_fail(gpac_session_open(GPAC_SESS_CTX(GPAC_CTX), graph),
951 : FALSE);
952 16 : g_free(graph);
953 :
954 : // Create the memory output
955 16 : gboolean is_inside_sink = GST_IS_GPAC_SINK(gst_element_get_parent(element));
956 16 : gboolean requires_memout =
957 16 : params->info && GPAC_SE_IS_REQUIRES_MEMOUT(params->info->flags);
958 20 : requires_memout = !is_inside_sink || (is_inside_sink && requires_memout) ||
959 4 : GPAC_PROP_CTX(GPAC_CTX)->destination;
960 :
961 16 : if (requires_memout) {
962 13 : gpac_return_val_if_fail(
963 : gpac_memio_new(GPAC_SESS_CTX(GPAC_CTX), GPAC_MEMIO_DIR_OUT), FALSE);
964 : }
965 :
966 : // Check if the session has an output
967 16 : if (!gpac_session_has_output(GPAC_SESS_CTX(GPAC_CTX))) {
968 0 : GST_ELEMENT_ERROR(
969 : element, STREAM, FAILED, (NULL), ("Session has no output"));
970 0 : return FALSE;
971 : }
972 :
973 : // Initialize the PIDs for all pads
974 16 : if (!gpac_prepare_pids(element)) {
975 0 : GST_ELEMENT_ERROR(
976 : element, LIBRARY, FAILED, (NULL), ("Failed to prepare PIDs"));
977 0 : return FALSE;
978 : }
979 16 : GST_DEBUG_OBJECT(element, "GPAC session started");
980 :
981 : // Initialize the segment
982 16 : gst_segment_init(&segment, GST_FORMAT_TIME);
983 16 : gst_aggregator_update_segment(aggregator, &segment);
984 16 : return TRUE;
985 : }
986 :
987 : static gboolean
988 16 : gst_gpac_tf_stop(GstAggregator* aggregator)
989 : {
990 16 : GstElement* element = GST_ELEMENT(aggregator);
991 16 : GstGpacTransform* gpac_tf = GST_GPAC_TF(aggregator);
992 16 : GObjectClass* klass = G_OBJECT_CLASS(G_TYPE_INSTANCE_GET_CLASS(
993 : G_OBJECT(element), GST_TYPE_GPAC_TF, GstGpacTransformClass));
994 16 : GstGpacParams* params = GST_GPAC_GET_PARAMS(klass);
995 :
996 : // Reset the element
997 16 : gst_gpac_tf_reset(gpac_tf);
998 :
999 : // Abort the session
1000 16 : gpac_memio_set_eos(GPAC_SESS_CTX(GPAC_CTX), TRUE);
1001 16 : gpac_session_abort(GPAC_SESS_CTX(GPAC_CTX));
1002 :
1003 : // Close the session
1004 16 : if (!gpac_session_close(GPAC_SESS_CTX(GPAC_CTX),
1005 : GPAC_PROP_CTX(GPAC_CTX)->print_stats)) {
1006 0 : GST_ELEMENT_ERROR(
1007 : element, LIBRARY, SHUTDOWN, (NULL), ("Failed to close GPAC session"));
1008 0 : return FALSE;
1009 : }
1010 :
1011 : // Destroy the GPAC context
1012 16 : gpac_destroy(GPAC_CTX);
1013 16 : GST_DEBUG_OBJECT(element, "GPAC session stopped");
1014 16 : return TRUE;
1015 : }
1016 :
1017 : static void
1018 0 : gst_gpac_tf_finalize(GObject* object)
1019 : {
1020 0 : GstGpacTransform* gpac_tf = GST_GPAC_TF(object);
1021 0 : GPAC_PropertyContext* ctx = GPAC_PROP_CTX(GPAC_CTX);
1022 :
1023 : // Free the properties
1024 0 : g_list_free(ctx->properties);
1025 0 : ctx->properties = NULL;
1026 :
1027 0 : if (ctx->props_as_argv) {
1028 0 : for (u32 i = 0; ctx->props_as_argv[i]; i++)
1029 0 : g_free(ctx->props_as_argv[i]);
1030 0 : g_free((void*)ctx->props_as_argv);
1031 : }
1032 :
1033 : // Free the queue
1034 0 : if (gpac_tf->queue) {
1035 0 : g_assert(g_queue_is_empty(gpac_tf->queue));
1036 0 : g_queue_free(gpac_tf->queue);
1037 0 : gpac_tf->queue = NULL;
1038 : }
1039 :
1040 0 : G_OBJECT_CLASS(parent_class)->finalize(object);
1041 0 : }
1042 :
1043 : // #MARK: Initialization
1044 : static void
1045 13 : gst_gpac_tf_init(GstGpacTransform* tf)
1046 : {
1047 13 : gst_gpac_tf_reset(tf);
1048 13 : tf->queue = g_queue_new();
1049 13 : }
1050 :
1051 : static void
1052 2 : gst_gpac_tf_class_init(GstGpacTransformClass* klass)
1053 : {
1054 : // Initialize the class
1055 2 : GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
1056 2 : GstElementClass* gstelement_class = GST_ELEMENT_CLASS(klass);
1057 2 : GstAggregatorClass* gstaggregator_class = GST_AGGREGATOR_CLASS(klass);
1058 2 : parent_class = g_type_class_peek_parent(klass);
1059 :
1060 : // Install the pad templates
1061 2 : gpac_install_sink_pad_templates(gstelement_class);
1062 :
1063 : // Set the pad management functions
1064 2 : gstaggregator_class->create_new_pad =
1065 2 : GST_DEBUG_FUNCPTR(gst_gpac_tf_create_new_pad);
1066 :
1067 : // Set the aggregator functions
1068 2 : gstaggregator_class->sink_event = GST_DEBUG_FUNCPTR(gst_gpac_tf_sink_event);
1069 2 : gstaggregator_class->aggregate = GST_DEBUG_FUNCPTR(gst_gpac_tf_aggregate);
1070 2 : gstaggregator_class->negotiated_src_caps =
1071 2 : GST_DEBUG_FUNCPTR(gst_gpac_tf_negotiated_src_caps);
1072 2 : gstaggregator_class->start = GST_DEBUG_FUNCPTR(gst_gpac_tf_start);
1073 2 : gstaggregator_class->stop = GST_DEBUG_FUNCPTR(gst_gpac_tf_stop);
1074 :
1075 2 : gst_type_mark_as_plugin_api(GST_TYPE_GPAC_TF_PAD, 0);
1076 2 : }
1077 :
1078 : // #MARK: Registration
1079 : static void
1080 8 : gst_gpac_tf_subclass_init(GstGpacTransformClass* klass)
1081 : {
1082 8 : GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
1083 8 : GstElementClass* gstelement_class = GST_ELEMENT_CLASS(klass);
1084 8 : GstGpacParams* params = GST_GPAC_GET_PARAMS(klass);
1085 :
1086 : // Set the finalizer
1087 8 : gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_gpac_tf_finalize);
1088 :
1089 : // Set the property handlers
1090 8 : gobject_class->set_property = GST_DEBUG_FUNCPTR(gst_gpac_tf_set_property);
1091 8 : gobject_class->get_property = GST_DEBUG_FUNCPTR(gst_gpac_tf_get_property);
1092 8 : gpac_install_global_properties(gobject_class);
1093 8 : gpac_install_local_properties(
1094 : gobject_class, GPAC_PROP_PRINT_STATS, GPAC_PROP_0);
1095 :
1096 : // Add the subclass-specific properties and pad templates
1097 8 : if (params->is_single) {
1098 6 : if (params->is_inside_sink) {
1099 1 : gst_element_class_add_static_pad_template(gstelement_class,
1100 : &internal_pad_template);
1101 : } else {
1102 5 : gst_element_class_add_static_pad_template(gstelement_class,
1103 5 : ¶ms->info->src_template);
1104 : }
1105 :
1106 : // Set property blacklist
1107 6 : GList* blacklist = NULL;
1108 6 : if (params->info->default_options) {
1109 3 : filter_option* opt = params->info->default_options;
1110 10 : for (u32 i = 0; opt[i].name; i++) {
1111 7 : if (opt[i].forced)
1112 4 : blacklist = g_list_append(blacklist, (gpointer)opt[i].name);
1113 : }
1114 : }
1115 :
1116 : // Install the filter properties
1117 6 : gpac_install_filter_properties(
1118 6 : gobject_class, blacklist, params->info->filter_name);
1119 6 : g_list_free(blacklist);
1120 :
1121 : // Install the signals if not inside the sink bin
1122 : // They would be installed by the sink bin itself
1123 6 : if (!params->is_inside_sink && params->info->signal_presets) {
1124 0 : gpac_install_signals_by_presets(gobject_class,
1125 0 : params->info->signal_presets);
1126 : }
1127 :
1128 : // Check if we have any filter options to expose
1129 8 : for (u32 i = 0; i < G_N_ELEMENTS(filter_options); i++) {
1130 6 : filter_option_overrides* opts = &filter_options[i];
1131 6 : if (g_strcmp0(opts->filter_name, params->info->filter_name) == 0) {
1132 8 : for (u32 j = 0; opts->options[j]; j++) {
1133 4 : guint32 prop_id = opts->options[j];
1134 4 : gpac_install_local_properties(gobject_class, prop_id, GPAC_PROP_0);
1135 : }
1136 4 : break;
1137 : }
1138 : }
1139 : } else {
1140 2 : gpac_install_src_pad_templates(gstelement_class);
1141 2 : gpac_install_local_properties(
1142 : gobject_class, GPAC_PROP_GRAPH, GPAC_PROP_DESTINATION, GPAC_PROP_0);
1143 :
1144 2 : if (!params->is_inside_sink)
1145 1 : gpac_install_all_signals(gobject_class);
1146 :
1147 : // We don't know which filters will be used, so we expose all options
1148 4 : for (u32 i = 0; i < G_N_ELEMENTS(filter_options); i++) {
1149 2 : filter_option_overrides* opts = &filter_options[i];
1150 4 : for (u32 j = 0; opts->options[j]; j++) {
1151 2 : guint32 prop_id = opts->options[j];
1152 2 : gpac_install_local_properties(gobject_class, prop_id, GPAC_PROP_0);
1153 : }
1154 : }
1155 : }
1156 :
1157 : // Set the metadata
1158 8 : const gchar* longname = "gpac transformer";
1159 8 : if (params->is_single) {
1160 6 : if (!g_strcmp0(params->info->filter_name, params->info->alias_name))
1161 : longname =
1162 2 : g_strdup_printf("gpac %s transformer", params->info->filter_name);
1163 : else
1164 4 : longname = g_strdup_printf("gpac %s (%s) transformer",
1165 4 : params->info->alias_name,
1166 4 : params->info->filter_name);
1167 : }
1168 8 : gst_element_class_set_static_metadata(
1169 : gstelement_class,
1170 : longname,
1171 : "Aggregator/Transform",
1172 : "Aggregates and transforms incoming data via GPAC",
1173 : "Deniz Ugur <deniz.ugur@motionspell.com>");
1174 8 : }
1175 :
1176 : gboolean
1177 2 : gst_gpac_tf_register(GstPlugin* plugin)
1178 : {
1179 : GType type;
1180 : GstGpacParams* params;
1181 2 : GTypeInfo subclass_typeinfo = {
1182 : sizeof(GstGpacTransformClass),
1183 : NULL, // base_init
1184 : NULL, // base_finalize
1185 : (GClassInitFunc)gst_gpac_tf_subclass_init,
1186 : NULL, // class_finalize
1187 : NULL, // class_data
1188 : sizeof(GstGpacTransform),
1189 : 0,
1190 : NULL, // instance_init
1191 : };
1192 :
1193 2 : GST_DEBUG_CATEGORY_INIT(gst_gpac_tf_debug, "gpactf", 0, "GPAC Transform");
1194 :
1195 : // Register the regular transform element
1196 2 : GST_LOG("Registering regular gpac transform element");
1197 2 : params = g_new0(GstGpacParams, 1);
1198 2 : params->is_single = FALSE;
1199 2 : params->is_inside_sink = FALSE;
1200 2 : type = g_type_register_static(
1201 : GST_TYPE_GPAC_TF, "GstGpacTransformRegular", &subclass_typeinfo, 0);
1202 2 : g_type_set_qdata(type, GST_GPAC_PARAMS_QDATA, params);
1203 2 : if (!gst_element_register(plugin, "gpactf", GST_RANK_PRIMARY, type)) {
1204 0 : GST_ERROR_OBJECT(plugin,
1205 : "Failed to register regular gpac transform element");
1206 0 : return FALSE;
1207 : }
1208 :
1209 : // Register subelements
1210 10 : for (u32 i = 0; i < G_N_ELEMENTS(subelements); i++) {
1211 8 : subelement_info* info = &subelements[i];
1212 :
1213 8 : if (info->flags & GPAC_SE_SINK_ONLY) {
1214 2 : GST_DEBUG_OBJECT(plugin,
1215 : "Subelement %s is a sink only element, skipping",
1216 : info->alias_name);
1217 2 : continue;
1218 : }
1219 :
1220 : // Register the sub transform element
1221 6 : GST_LOG("Registering %s transform subelement", info->filter_name);
1222 6 : params = g_new0(GstGpacParams, 1);
1223 6 : params->is_single = TRUE;
1224 6 : params->is_inside_sink = FALSE;
1225 6 : params->info = info;
1226 6 : const gchar* name = g_strdup_printf("gpac%s", info->alias_name);
1227 : const gchar* type_name =
1228 6 : g_strdup_printf("GstGpacTransform%c%s",
1229 6 : g_ascii_toupper(info->alias_name[0]),
1230 6 : info->alias_name + 1);
1231 :
1232 6 : type = g_type_register_static(
1233 : GST_TYPE_GPAC_TF, type_name, &subclass_typeinfo, 0);
1234 6 : g_type_set_qdata(type, GST_GPAC_PARAMS_QDATA, params);
1235 6 : if (!gst_element_register(plugin, name, GST_RANK_SECONDARY, type)) {
1236 0 : GST_ERROR_OBJECT(
1237 : plugin, "Failed to register %s transform subelement", info->alias_name);
1238 0 : return FALSE;
1239 : }
1240 : }
1241 :
1242 2 : return TRUE;
1243 : }
1244 :
1245 2 : GST_ELEMENT_REGISTER_DEFINE_CUSTOM(gpac_tf, gst_gpac_tf_register);
1246 :
1247 : // #MARK: Private registration
1248 : GType
1249 4 : gst_gpac_tf_register_custom(subelement_info* se_info,
1250 : gboolean is_inside_sink,
1251 : gboolean is_single)
1252 : {
1253 : const gchar* type_name =
1254 4 : g_strdup_printf("GstGpacTransformPrivate%c%s",
1255 4 : g_ascii_toupper(se_info->alias_name[0]),
1256 4 : se_info->alias_name + 1);
1257 :
1258 : // Check if the type is already registered
1259 4 : if (g_type_from_name(type_name) != G_TYPE_INVALID) {
1260 0 : GST_WARNING("Type %s is already registered, returning existing type",
1261 : type_name);
1262 0 : return g_type_from_name(type_name);
1263 : }
1264 :
1265 : GType type;
1266 4 : GTypeInfo type_info = {
1267 : sizeof(GstGpacTransformClass),
1268 : NULL, // base_init
1269 : NULL, // base_finalize
1270 : (GClassInitFunc)gst_gpac_tf_subclass_init,
1271 : NULL, // class_finalize
1272 : NULL, // class_data
1273 : sizeof(GstGpacTransform),
1274 : 0,
1275 : NULL,
1276 : };
1277 :
1278 4 : GST_DEBUG_CATEGORY_INIT(gst_gpac_tf_debug, "gpactf", 0, "GPAC Transform");
1279 :
1280 : // Register the custom transform element
1281 4 : GST_LOG("Creating a private gpac transform element with subelement info: %p",
1282 : se_info);
1283 :
1284 4 : GstGpacParams* params = g_new0(GstGpacParams, 1);
1285 4 : params->is_single = is_single;
1286 4 : params->is_inside_sink = is_inside_sink;
1287 4 : params->info = se_info;
1288 :
1289 4 : type = g_type_register_static(GST_TYPE_GPAC_TF, type_name, &type_info, 0);
1290 4 : g_type_set_qdata(type, GST_GPAC_PARAMS_QDATA, params);
1291 4 : return type;
1292 : }
|