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