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 27907 : 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 13895 : gst_gpac_tf_pad_get_property(GObject* object,
64 : guint prop_id,
65 : GValue* value,
66 : GParamSpec* pspec)
67 : {
68 13895 : GstGpacTransformPad* pad = GST_GPAC_TF_PAD(object);
69 13895 : g_return_if_fail(GST_IS_GPAC_TF_PAD(object));
70 :
71 13895 : switch (prop_id) {
72 13895 : case GPAC_PROP_PAD_PID:
73 13895 : g_value_set_pointer(value, pad->pid);
74 13895 : 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 12428 : 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 2515 : gpac_prepare_pids(GstElement* element)
201 : {
202 2515 : GstGpacTransform* gpac_tf = GST_GPAC_TF(element);
203 : GstIterator* pad_iter;
204 2515 : GValue item = G_VALUE_INIT;
205 2515 : gboolean done = FALSE;
206 2515 : gboolean ret = FALSE;
207 :
208 : // Track configuration status
209 : GHashTable* pids_before_reset =
210 2515 : g_hash_table_new(g_direct_hash, g_direct_equal);
211 2515 : GHashTable* pids_seen = g_hash_table_new(g_direct_hash, g_direct_equal);
212 :
213 : // Iterate over the pads
214 2515 : pad_iter = gst_element_iterate_sink_pads(element);
215 12381 : while (!done) {
216 9866 : switch (gst_iterator_next(pad_iter, &item)) {
217 7351 : case GST_ITERATOR_OK: {
218 7351 : GstPad* pad = g_value_get_object(&item);
219 7351 : GstAggregatorPad* agg_pad = GST_AGGREGATOR_PAD(pad);
220 7351 : GpacPadPrivate* priv = gst_pad_get_element_private(pad);
221 :
222 : // Get the PID
223 7351 : GF_FilterPid* pid = NULL;
224 7351 : g_object_get(agg_pad, "pid", &pid, NULL);
225 :
226 : // Create the PID if necessary
227 7351 : 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 7351 : 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 7351 : g_hash_table_insert(pids_seen, pid, GINT_TO_POINTER(1));
251 :
252 7351 : g_value_reset(&item);
253 7351 : 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 2515 : case GST_ITERATOR_DONE:
269 2515 : done = TRUE;
270 2515 : ret = TRUE;
271 2515 : break;
272 : }
273 : }
274 :
275 : // Check for PIDs that are no longer needed
276 : GHashTableIter iter;
277 : gpointer key, value;
278 2515 : g_hash_table_iter_init(&iter, pids_before_reset);
279 5030 : 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 2515 : fail:
287 : // Clean up
288 2515 : g_value_unset(&item);
289 2515 : gst_iterator_free(pad_iter);
290 2515 : g_hash_table_destroy(pids_before_reset);
291 2515 : g_hash_table_destroy(pids_seen);
292 2515 : return ret;
293 : }
294 :
295 : // #MARK: Aggregator
296 : GstFlowReturn
297 2499 : gst_gpac_tf_consume(GstAggregator* agg, Bool is_eos)
298 : {
299 2499 : GstFlowReturn flow_ret = GST_FLOW_OK;
300 2499 : GstGpacTransform* gpac_tf = GST_GPAC_TF(agg);
301 2499 : GstSegment* segment = &GST_AGGREGATOR_PAD(agg->srcpad)->segment;
302 :
303 2499 : GST_DEBUG_OBJECT(agg, "Consuming output...");
304 :
305 : void* output;
306 : GPAC_FilterPPRet ret;
307 5028 : while ((ret = gpac_memio_consume(GPAC_SESS_CTX(GPAC_CTX), &output))) {
308 2529 : if (ret & GPAC_FILTER_PP_RET_ERROR) {
309 : // An error occurred, stop processing
310 0 : goto error;
311 : }
312 :
313 2529 : if (ret == GPAC_FILTER_PP_RET_EMPTY) {
314 : // No data available
315 890 : GST_DEBUG_OBJECT(agg, "No more data available, exiting");
316 890 : return is_eos ? GST_FLOW_EOS : GST_FLOW_OK;
317 : }
318 :
319 1639 : gboolean had_signal = (ret & GPAC_FILTER_PP_RET_SIGNAL) != 0;
320 1639 : if (ret > GPAC_MAY_HAVE_BUFFER) {
321 1639 : 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 1639 : 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 1639 : } 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 1609 : } else if (HAS_FLAG(ret, GPAC_FILTER_PP_RET_NULL)) {
365 : // If we had signals, consume all of them first
366 1609 : if (had_signal)
367 0 : continue;
368 :
369 1609 : if (is_eos)
370 5 : return GST_FLOW_EOS;
371 :
372 1604 : 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 1604 : GstBuffer* buffer = gpac_tf->sync_buffer;
377 1604 : if (!buffer)
378 399 : buffer = gst_buffer_new();
379 :
380 : // Send the sync buffer
381 1604 : flow_ret = gst_aggregator_finish_buffer(agg, buffer);
382 :
383 : // Buffer is transferred to the aggregator, so we set it to NULL
384 1604 : if (gpac_tf->sync_buffer)
385 1205 : gpac_tf->sync_buffer = NULL;
386 :
387 1604 : 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 : GF_FilterPid* pid = NULL;
473 20 : g_object_get(GST_AGGREGATOR_PAD(pad), "pid", &pid, NULL);
474 20 : g_assert(pid != NULL);
475 20 : gpac_memio_set_eos(GPAC_SESS_CTX(GPAC_CTX), pid);
476 20 : priv->eos = TRUE;
477 :
478 : // Are all pads EOS?
479 20 : gboolean all_eos = TRUE;
480 20 : GList* sinkpads = GST_ELEMENT(agg)->sinkpads;
481 52 : for (GList* l = sinkpads; l; l = l->next) {
482 39 : GstAggregatorPad* agg_pad = GST_AGGREGATOR_PAD(l->data);
483 39 : GpacPadPrivate* priv = gst_pad_get_element_private(GST_PAD(agg_pad));
484 39 : if (!priv->eos) {
485 7 : all_eos = FALSE;
486 7 : break;
487 : }
488 : }
489 :
490 20 : if (!all_eos) {
491 7 : GST_DEBUG_OBJECT(agg, "Not all pads are EOS, not sending EOS to GPAC");
492 7 : break;
493 : }
494 :
495 : // If all pads are EOS, send EOS to the source
496 13 : GST_DEBUG_OBJECT(agg, "All pads are EOS, sending EOS to GPAC");
497 13 : gpac_session_run(GPAC_SESS_CTX(GPAC_CTX), TRUE);
498 13 : gst_gpac_tf_consume(agg, GST_EVENT_TYPE(event) == GST_EVENT_EOS);
499 13 : break;
500 : }
501 :
502 0 : case GST_EVENT_FLUSH_START:
503 0 : gpac_session_run(GPAC_SESS_CTX(GPAC_CTX), TRUE);
504 0 : gst_gpac_tf_consume(agg, GST_EVENT_TYPE(event) == GST_EVENT_EOS);
505 0 : break;
506 :
507 44 : default:
508 44 : break;
509 : }
510 :
511 137 : return GST_AGGREGATOR_CLASS(parent_class)->sink_event(agg, pad, event);
512 : }
513 :
514 : gboolean
515 14 : gst_gpac_tf_negotiated_src_caps(GstAggregator* agg, GstCaps* caps)
516 : {
517 14 : GstGpacTransform* gpac_tf = GST_GPAC_TF(GST_ELEMENT(agg));
518 14 : GstElement* element = GST_ELEMENT(agg);
519 14 : GObjectClass* klass = G_OBJECT_CLASS(G_TYPE_INSTANCE_GET_CLASS(
520 : G_OBJECT(element), GST_TYPE_GPAC_TF, GstGpacTransformClass));
521 14 : GstGpacParams* params = GST_GPAC_GET_PARAMS(klass);
522 :
523 : // Check if this is element is inside our sink bin
524 14 : GstObject* sink_bin = gst_element_get_parent(element);
525 14 : if (GST_IS_GPAC_SINK(sink_bin)) {
526 : // We might already have a destination set
527 5 : if ((params->is_single && params->info->destination) ||
528 4 : GPAC_PROP_CTX(GPAC_CTX)->destination) {
529 2 : return TRUE;
530 : }
531 : }
532 :
533 12 : return gpac_memio_set_gst_caps(GPAC_SESS_CTX(GPAC_CTX), caps);
534 : }
535 :
536 : void
537 4637 : gst_gpac_request_idr(GstAggregator* agg, GstPad* pad, GstBuffer* buffer)
538 : {
539 4637 : GstGpacTransform* gpac_tf = GST_GPAC_TF(GST_ELEMENT(agg));
540 4637 : GpacPadPrivate* priv = gst_pad_get_element_private(pad);
541 : GstEvent* gst_event;
542 :
543 : // Skip if this is not a key frame
544 4637 : if (GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DELTA_UNIT))
545 4538 : return;
546 :
547 : // Skip if we don't have a valid PTS
548 99 : if (!GST_BUFFER_PTS_IS_VALID(buffer))
549 0 : return;
550 :
551 : // Decide on which IDR period to use
552 99 : guint64 idr_period = GST_CLOCK_TIME_NONE;
553 99 : if (priv->idr_period != GST_CLOCK_TIME_NONE) {
554 79 : idr_period = priv->idr_period;
555 : // Preserve the IDR period sent by gpac
556 79 : gpac_tf->gpac_idr_period = idr_period;
557 : }
558 :
559 : // Use the global IDR period if available
560 99 : if (gpac_tf->global_idr_period)
561 4 : idr_period = gpac_tf->global_idr_period;
562 :
563 : // Skip if we don't have an IDR period
564 99 : if (idr_period == GST_CLOCK_TIME_NONE)
565 16 : return;
566 :
567 166 : priv->idr_last = gst_segment_to_running_time(
568 83 : priv->segment, GST_FORMAT_TIME, GST_BUFFER_PTS(buffer));
569 :
570 83 : GST_DEBUG_OBJECT(agg,
571 : "Key frame received at %" GST_TIME_FORMAT,
572 : GST_TIME_ARGS(priv->idr_last));
573 :
574 : // If this is the first IDR, request it immediately
575 83 : if (priv->idr_next == GST_CLOCK_TIME_NONE) {
576 8 : priv->idr_next = priv->idr_last + idr_period;
577 8 : goto request;
578 : }
579 :
580 : // If this IDR arrived before the next scheduled IDR, ignore
581 75 : if (priv->idr_last < priv->idr_next) {
582 56 : guint64 diff = priv->idr_next - priv->idr_last;
583 56 : GST_DEBUG_OBJECT(agg,
584 : "IDR arrived %" GST_TIME_FORMAT
585 : " before the next IDR on pad %s",
586 : GST_TIME_ARGS(diff),
587 : GST_PAD_NAME(pad));
588 56 : return;
589 : }
590 :
591 : // Check if this IDR was on time
592 19 : guint64 diff = priv->idr_last - priv->idr_next;
593 19 : if (diff)
594 0 : GST_ELEMENT_WARNING(agg,
595 : STREAM,
596 : FAILED,
597 : ("IDR was late by %" GST_TIME_FORMAT
598 : " on pad %s, reconsider encoding options",
599 : GST_TIME_ARGS(diff),
600 : GST_PAD_NAME(pad)),
601 : (NULL));
602 :
603 : // Schedule the next IDR at the desired time, regardless of whether current
604 : // one was late or not
605 19 : priv->idr_next += idr_period;
606 :
607 27 : request:
608 : // Send the next IDR request
609 : gst_event =
610 27 : gst_video_event_new_upstream_force_key_unit(priv->idr_next, TRUE, 1);
611 27 : GST_DEBUG_OBJECT(
612 : agg, "Requesting IDR at %" GST_TIME_FORMAT, GST_TIME_ARGS(priv->idr_next));
613 27 : if (!gst_pad_push_event(pad, gst_event))
614 0 : GST_ELEMENT_WARNING(
615 : agg, STREAM, FAILED, (NULL), ("Failed to push the force key unit event"));
616 : }
617 :
618 : static GstFlowReturn
619 2499 : gst_gpac_tf_aggregate(GstAggregator* agg, gboolean timeout)
620 : {
621 2499 : GstGpacTransform* gpac_tf = GST_GPAC_TF(GST_ELEMENT(agg));
622 : GstIterator* pad_iter;
623 2499 : GValue item = G_VALUE_INIT;
624 2499 : gboolean done = FALSE;
625 2499 : gboolean has_buffers = TRUE;
626 :
627 : // Check and create PIDs if necessary
628 2499 : if (!gpac_prepare_pids(GST_ELEMENT(agg))) {
629 0 : GST_ELEMENT_ERROR(agg, STREAM, FAILED, (NULL), ("Failed to prepare PIDs"));
630 0 : return GST_FLOW_ERROR;
631 : }
632 :
633 2499 : GST_DEBUG_OBJECT(agg, "Aggregating buffers");
634 :
635 : // Create the temporary queue
636 2499 : GQueue* queue = g_queue_new();
637 :
638 : // Keep consuming buffers until all pads are drained
639 8115 : while (has_buffers) {
640 5616 : has_buffers = FALSE;
641 5616 : done = FALSE;
642 :
643 : // Iterate over the pads
644 5616 : pad_iter = gst_element_iterate_sink_pads(GST_ELEMENT(agg));
645 27903 : while (!done) {
646 22287 : switch (gst_iterator_next(pad_iter, &item)) {
647 16671 : case GST_ITERATOR_OK: {
648 16671 : GF_FilterPid* pid = NULL;
649 16671 : GstPad* pad = g_value_get_object(&item);
650 16671 : GpacPadPrivate* priv = gst_pad_get_element_private(pad);
651 : GstBuffer* buffer =
652 16671 : gst_aggregator_pad_pop_buffer(GST_AGGREGATOR_PAD(pad));
653 :
654 : // Continue if no buffer is available
655 16671 : if (!buffer) {
656 10147 : GST_DEBUG_OBJECT(
657 : agg, "No buffer available on pad %s", GST_PAD_NAME(pad));
658 10147 : goto next;
659 : }
660 :
661 : // We found at least one buffer, continue the outer loop
662 6524 : has_buffers = TRUE;
663 :
664 : // Skip droppable/gap buffers
665 6524 : if (GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_GAP)) {
666 0 : GST_DEBUG_OBJECT(
667 : agg, "Gap buffer received on pad %s", GST_PAD_NAME(pad));
668 0 : goto next;
669 : }
670 :
671 : // Send the key frame request
672 : // Only send IDR request for video pads
673 13048 : if (gst_pad_get_pad_template(pad) ==
674 6524 : gst_gpac_get_sink_template(GPAC_TEMPLATE_VIDEO)) {
675 4637 : gst_gpac_request_idr(agg, pad, buffer);
676 : }
677 :
678 : // Get the PID
679 6524 : g_object_get(GST_AGGREGATOR_PAD(pad), "pid", &pid, NULL);
680 6524 : g_assert(pid);
681 :
682 : // Create the packet
683 6524 : GF_FilterPacket* packet = gpac_pck_new_from_buffer(buffer, priv, pid);
684 6524 : if (!packet) {
685 0 : GST_ELEMENT_ERROR(agg,
686 : STREAM,
687 : FAILED,
688 : (NULL),
689 : ("Failed to create packet from buffer"));
690 0 : goto next;
691 : }
692 :
693 : // Enqueue the packet
694 6524 : g_queue_push_tail(queue, packet);
695 :
696 : // Select the highest PTS for sync buffer
697 6524 : gboolean is_video_pad =
698 6524 : gst_pad_get_pad_template(GST_PAD(pad)) ==
699 6524 : gst_gpac_get_sink_template(GPAC_TEMPLATE_VIDEO);
700 6524 : gboolean is_only_pad = g_list_length(GST_ELEMENT(agg)->sinkpads) == 1;
701 6524 : if (is_video_pad || is_only_pad) {
702 4637 : if (gpac_tf->sync_buffer) {
703 3424 : guint64 current_pts = GST_BUFFER_PTS(buffer);
704 3424 : guint64 sync_pts = GST_BUFFER_PTS(gpac_tf->sync_buffer);
705 3424 : if (current_pts > sync_pts) {
706 1374 : gst_buffer_replace(&gpac_tf->sync_buffer, buffer);
707 : }
708 : } else {
709 : // If no sync buffer exists, create one
710 1213 : gpac_tf->sync_buffer = gst_buffer_ref(buffer);
711 : }
712 : }
713 :
714 1887 : next:
715 16671 : if (buffer)
716 6524 : gst_buffer_unref(buffer);
717 16671 : g_value_reset(&item);
718 16671 : break;
719 : }
720 0 : case GST_ITERATOR_RESYNC:
721 0 : gst_iterator_resync(pad_iter);
722 0 : GST_ELEMENT_WARNING(agg,
723 : STREAM,
724 : FAILED,
725 : (NULL),
726 : ("Data structure changed during pad iteration, "
727 : "discarding all packets"));
728 0 : g_queue_clear_full(queue, (GDestroyNotify)gf_filter_pck_unref);
729 0 : break;
730 5616 : case GST_ITERATOR_ERROR:
731 : case GST_ITERATOR_DONE:
732 5616 : done = TRUE;
733 5616 : break;
734 : }
735 : }
736 : }
737 :
738 : // Clean up
739 2499 : g_value_unset(&item);
740 2499 : gst_iterator_free(pad_iter);
741 :
742 : // Check if we have any packets to send
743 2499 : if (g_queue_is_empty(queue)) {
744 13 : GST_DEBUG_OBJECT(agg, "No packets to send, returning EOS");
745 13 : g_queue_free(queue);
746 13 : return GST_FLOW_EOS;
747 : }
748 :
749 : // Merge the queues
750 9010 : while (!g_queue_is_empty(queue)) {
751 6524 : gpointer pck = g_queue_pop_head(queue);
752 6524 : g_queue_push_tail(gpac_tf->queue, pck);
753 : }
754 2486 : g_queue_free(queue);
755 :
756 : // Run the filter session
757 2486 : if (gpac_session_run(GPAC_SESS_CTX(GPAC_CTX), FALSE) != GF_OK) {
758 0 : GST_ELEMENT_ERROR(
759 : agg, STREAM, FAILED, (NULL), ("Failed to run the GPAC session"));
760 0 : return GST_FLOW_ERROR;
761 : }
762 :
763 : // Consume the output
764 2486 : return gst_gpac_tf_consume(agg, FALSE);
765 : }
766 :
767 : // #MARK: Pad Management
768 : static GstAggregatorPad*
769 20 : gst_gpac_tf_create_new_pad(GstAggregator* element,
770 : GstPadTemplate* templ,
771 : const gchar* pad_name,
772 : const GstCaps* caps)
773 : {
774 20 : GstElementClass* klass = GST_ELEMENT_GET_CLASS(element);
775 20 : GstGpacTransform* agg = GST_GPAC_TF(element);
776 : gchar* name;
777 : gint pad_id;
778 :
779 : #define TEMPLATE_CHECK(prefix, count_field) \
780 : if (templ == gst_element_class_get_pad_template(klass, prefix "_%u")) { \
781 : agg->count_field++; \
782 : if (pad_name != NULL && sscanf(pad_name, prefix "_%u", &pad_id) == 1) { \
783 : name = g_strdup(pad_name); \
784 : } else { \
785 : pad_id = \
786 : agg->audio_pad_count + agg->video_pad_count + agg->subtitle_pad_count; \
787 : name = g_strdup_printf(prefix "_%u", pad_id); \
788 : } \
789 : } else
790 :
791 : // Check the pad template and decide the pad name
792 20 : TEMPLATE_CHECK("video", video_pad_count)
793 3 : TEMPLATE_CHECK("audio", audio_pad_count)
794 0 : TEMPLATE_CHECK("subtitle", subtitle_pad_count)
795 : {
796 0 : GST_ELEMENT_WARNING(
797 : agg, STREAM, FAILED, (NULL), ("This is not our template!"));
798 0 : return NULL;
799 : }
800 :
801 : #undef TEMPLATE_CHECK
802 :
803 20 : GST_DEBUG_OBJECT(agg, "Creating new pad %s", name);
804 :
805 : // Create the pad
806 20 : GstGpacTransformPad* pad = g_object_new(GST_TYPE_GPAC_TF_PAD,
807 : "name",
808 : name,
809 : "direction",
810 20 : templ->direction,
811 : "template",
812 : templ,
813 : NULL);
814 20 : g_free(name);
815 :
816 : // Initialize the private data
817 20 : GpacPadPrivate* priv = gst_pad_get_element_private(GST_PAD(pad));
818 20 : priv->id = pad_id;
819 20 : if (caps) {
820 0 : priv->caps = gst_caps_copy(caps);
821 0 : priv->flags |= GPAC_PAD_CAPS_SET;
822 : }
823 :
824 20 : return GST_AGGREGATOR_PAD(pad);
825 : }
826 :
827 : // #MARK: Lifecycle
828 : static void
829 29 : gst_gpac_tf_reset(GstGpacTransform* tf)
830 : {
831 29 : gboolean done = FALSE;
832 29 : GValue item = G_VALUE_INIT;
833 29 : GstIterator* pad_iter = gst_element_iterate_sink_pads(GST_ELEMENT(tf));
834 81 : while (!done) {
835 52 : switch (gst_iterator_next(pad_iter, &item)) {
836 23 : case GST_ITERATOR_OK: {
837 23 : GF_FilterPid* pid = NULL;
838 23 : GstPad* pad = g_value_get_object(&item);
839 23 : GpacPadPrivate* priv = gst_pad_get_element_private(pad);
840 :
841 : // Reset the PID
842 23 : g_object_set(GST_AGGREGATOR_PAD(pad), "pid", NULL, NULL);
843 23 : break;
844 : }
845 29 : case GST_ITERATOR_RESYNC:
846 : case GST_ITERATOR_ERROR:
847 : case GST_ITERATOR_DONE:
848 29 : done = TRUE;
849 29 : break;
850 : }
851 : }
852 29 : g_value_unset(&item);
853 29 : gst_iterator_free(pad_iter);
854 :
855 : // Empty the queue
856 29 : if (tf->queue) {
857 16 : if (!g_queue_is_empty(tf->queue))
858 0 : GST_ERROR_OBJECT(tf,
859 : "GPAC queue not empty during reset, pipeline error?");
860 16 : g_queue_clear(tf->queue);
861 : }
862 29 : }
863 :
864 : static gboolean
865 16 : gst_gpac_tf_start(GstAggregator* aggregator)
866 : {
867 16 : GstGpacTransform* gpac_tf = GST_GPAC_TF(aggregator);
868 16 : GstElement* element = GST_ELEMENT(aggregator);
869 16 : GObjectClass* klass = G_OBJECT_CLASS(G_TYPE_INSTANCE_GET_CLASS(
870 : G_OBJECT(element), GST_TYPE_GPAC_TF, GstGpacTransformClass));
871 16 : GstGpacParams* params = GST_GPAC_GET_PARAMS(klass);
872 : GstSegment segment;
873 :
874 : // Check if we have the graph property set
875 16 : if (!params->is_single && !GPAC_PROP_CTX(GPAC_CTX)->graph) {
876 0 : GST_ELEMENT_ERROR(
877 : element, STREAM, FAILED, (NULL), ("Graph property must be set"));
878 0 : return FALSE;
879 : }
880 :
881 : // Convert the properties to arguments
882 16 : if (!gpac_apply_properties(GPAC_PROP_CTX(GPAC_CTX))) {
883 0 : GST_ELEMENT_ERROR(
884 : element, LIBRARY, INIT, (NULL), ("Failed to apply properties"));
885 0 : return FALSE;
886 : }
887 : // Initialize the GPAC context
888 16 : if (!gpac_init(GPAC_CTX, element)) {
889 0 : GST_ELEMENT_ERROR(
890 : element, LIBRARY, INIT, (NULL), ("Failed to initialize GPAC context"));
891 0 : return FALSE;
892 : }
893 :
894 : // Create the session
895 16 : if (!gpac_session_init(GPAC_SESS_CTX(GPAC_CTX), element, params)) {
896 0 : GST_ELEMENT_ERROR(
897 : element, LIBRARY, INIT, (NULL), ("Failed to initialize GPAC session"));
898 0 : return FALSE;
899 : }
900 :
901 : // Create the memory input
902 16 : gpac_return_val_if_fail(
903 : gpac_memio_new(GPAC_SESS_CTX(GPAC_CTX), GPAC_MEMIO_DIR_IN), FALSE);
904 16 : gpac_memio_assign_queue(
905 : GPAC_SESS_CTX(GPAC_CTX), GPAC_MEMIO_DIR_IN, gpac_tf->queue);
906 :
907 : // Open the session
908 16 : gchar* graph = NULL;
909 16 : if (params->is_single) {
910 12 : if (params->info->default_options) {
911 11 : GList* props = GPAC_PROP_CTX(GPAC_CTX)->properties;
912 11 : GString* options = g_string_new(NULL);
913 :
914 : // Only override if not set already
915 34 : for (guint32 i = 0; params->info->default_options[i].name; i++) {
916 23 : gboolean found = FALSE;
917 :
918 44 : for (GList* l = props; l != NULL; l = l->next) {
919 21 : gchar* prop = (gchar*)l->data;
920 :
921 21 : g_autofree gchar* prefix =
922 21 : g_strdup_printf("--%s", params->info->default_options[i].name);
923 21 : if (g_str_has_prefix(prop, prefix)) {
924 0 : found = TRUE;
925 0 : break;
926 : }
927 : }
928 :
929 23 : if (!found) {
930 23 : const gchar* name = params->info->default_options[i].name;
931 23 : const gchar* value = params->info->default_options[i].value;
932 23 : g_string_append_printf(options, "%s=%s:", name, value);
933 : }
934 : }
935 :
936 : // Remove the trailing colon
937 11 : if (options->len > 0)
938 11 : g_string_truncate(options, options->len - 1);
939 :
940 11 : if (options->len > 0)
941 : graph =
942 11 : g_strdup_printf("%s:%s", params->info->filter_name, options->str);
943 : else
944 0 : graph = g_strdup(params->info->filter_name);
945 11 : g_string_free(options, TRUE);
946 : } else {
947 2 : graph = g_strdup(params->info->filter_name);
948 : }
949 : } else {
950 8 : graph = g_strdup(GPAC_PROP_CTX(GPAC_CTX)->graph);
951 : }
952 :
953 : // Set the destination override on session context
954 16 : GPAC_SESS_CTX(GPAC_CTX)->destination = GPAC_PROP_CTX(GPAC_CTX)->destination;
955 :
956 16 : gpac_return_val_if_fail(gpac_session_open(GPAC_SESS_CTX(GPAC_CTX), graph),
957 : FALSE);
958 16 : g_free(graph);
959 :
960 : // Create the memory output
961 16 : gboolean is_inside_sink = GST_IS_GPAC_SINK(gst_element_get_parent(element));
962 16 : gboolean requires_memout =
963 16 : params->info && GPAC_SE_IS_REQUIRES_MEMOUT(params->info->flags);
964 20 : requires_memout = !is_inside_sink || (is_inside_sink && requires_memout) ||
965 4 : GPAC_PROP_CTX(GPAC_CTX)->destination;
966 :
967 16 : if (requires_memout) {
968 13 : gpac_return_val_if_fail(
969 : gpac_memio_new(GPAC_SESS_CTX(GPAC_CTX), GPAC_MEMIO_DIR_OUT), FALSE);
970 : }
971 :
972 : // Check if the session has an output
973 16 : if (!gpac_session_has_output(GPAC_SESS_CTX(GPAC_CTX))) {
974 0 : GST_ELEMENT_ERROR(
975 : element, STREAM, FAILED, (NULL), ("Session has no output"));
976 0 : return FALSE;
977 : }
978 :
979 : // Initialize the PIDs for all pads
980 16 : if (!gpac_prepare_pids(element)) {
981 0 : GST_ELEMENT_ERROR(
982 : element, LIBRARY, FAILED, (NULL), ("Failed to prepare PIDs"));
983 0 : return FALSE;
984 : }
985 16 : GST_DEBUG_OBJECT(element, "GPAC session started");
986 :
987 : // Initialize the segment
988 16 : gst_segment_init(&segment, GST_FORMAT_TIME);
989 16 : gst_aggregator_update_segment(aggregator, &segment);
990 16 : return TRUE;
991 : }
992 :
993 : static gboolean
994 16 : gst_gpac_tf_stop(GstAggregator* aggregator)
995 : {
996 16 : GstElement* element = GST_ELEMENT(aggregator);
997 16 : GstGpacTransform* gpac_tf = GST_GPAC_TF(aggregator);
998 16 : GObjectClass* klass = G_OBJECT_CLASS(G_TYPE_INSTANCE_GET_CLASS(
999 : G_OBJECT(element), GST_TYPE_GPAC_TF, GstGpacTransformClass));
1000 16 : GstGpacParams* params = GST_GPAC_GET_PARAMS(klass);
1001 :
1002 : // Abort the session
1003 16 : gpac_memio_set_eos(GPAC_SESS_CTX(GPAC_CTX), NULL);
1004 16 : gpac_session_abort(GPAC_SESS_CTX(GPAC_CTX));
1005 :
1006 : // Close the session
1007 16 : if (!gpac_session_close(GPAC_SESS_CTX(GPAC_CTX),
1008 : GPAC_PROP_CTX(GPAC_CTX)->print_stats)) {
1009 0 : GST_ELEMENT_ERROR(
1010 : element, LIBRARY, SHUTDOWN, (NULL), ("Failed to close GPAC session"));
1011 0 : return FALSE;
1012 : }
1013 :
1014 : // Reset the element
1015 16 : gst_gpac_tf_reset(gpac_tf);
1016 :
1017 : // Destroy the GPAC context
1018 16 : gpac_destroy(GPAC_CTX);
1019 16 : GST_DEBUG_OBJECT(element, "GPAC session stopped");
1020 16 : return TRUE;
1021 : }
1022 :
1023 : static void
1024 0 : gst_gpac_tf_finalize(GObject* object)
1025 : {
1026 0 : GstGpacTransform* gpac_tf = GST_GPAC_TF(object);
1027 0 : GPAC_PropertyContext* ctx = GPAC_PROP_CTX(GPAC_CTX);
1028 :
1029 : // Free the properties
1030 0 : g_list_free(ctx->properties);
1031 0 : ctx->properties = NULL;
1032 :
1033 0 : if (ctx->props_as_argv) {
1034 0 : for (u32 i = 0; ctx->props_as_argv[i]; i++)
1035 0 : g_free(ctx->props_as_argv[i]);
1036 0 : g_free((void*)ctx->props_as_argv);
1037 : }
1038 :
1039 : // Free the queue
1040 0 : if (gpac_tf->queue) {
1041 0 : g_assert(g_queue_is_empty(gpac_tf->queue));
1042 0 : g_queue_free(gpac_tf->queue);
1043 0 : gpac_tf->queue = NULL;
1044 : }
1045 :
1046 0 : G_OBJECT_CLASS(parent_class)->finalize(object);
1047 0 : }
1048 :
1049 : // #MARK: Initialization
1050 : static void
1051 13 : gst_gpac_tf_init(GstGpacTransform* tf)
1052 : {
1053 13 : gst_gpac_tf_reset(tf);
1054 13 : tf->queue = g_queue_new();
1055 13 : }
1056 :
1057 : static void
1058 2 : gst_gpac_tf_class_init(GstGpacTransformClass* klass)
1059 : {
1060 : // Initialize the class
1061 2 : GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
1062 2 : GstElementClass* gstelement_class = GST_ELEMENT_CLASS(klass);
1063 2 : GstAggregatorClass* gstaggregator_class = GST_AGGREGATOR_CLASS(klass);
1064 2 : parent_class = g_type_class_peek_parent(klass);
1065 :
1066 : // Install the pad templates
1067 2 : gpac_install_sink_pad_templates(gstelement_class);
1068 :
1069 : // Set the pad management functions
1070 2 : gstaggregator_class->create_new_pad =
1071 2 : GST_DEBUG_FUNCPTR(gst_gpac_tf_create_new_pad);
1072 :
1073 : // Set the aggregator functions
1074 2 : gstaggregator_class->sink_event = GST_DEBUG_FUNCPTR(gst_gpac_tf_sink_event);
1075 2 : gstaggregator_class->aggregate = GST_DEBUG_FUNCPTR(gst_gpac_tf_aggregate);
1076 2 : gstaggregator_class->negotiated_src_caps =
1077 2 : GST_DEBUG_FUNCPTR(gst_gpac_tf_negotiated_src_caps);
1078 2 : gstaggregator_class->start = GST_DEBUG_FUNCPTR(gst_gpac_tf_start);
1079 2 : gstaggregator_class->stop = GST_DEBUG_FUNCPTR(gst_gpac_tf_stop);
1080 :
1081 2 : gst_type_mark_as_plugin_api(GST_TYPE_GPAC_TF_PAD, 0);
1082 2 : }
1083 :
1084 : // #MARK: Registration
1085 : static void
1086 8 : gst_gpac_tf_subclass_init(GstGpacTransformClass* klass)
1087 : {
1088 8 : GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
1089 8 : GstElementClass* gstelement_class = GST_ELEMENT_CLASS(klass);
1090 8 : GstGpacParams* params = GST_GPAC_GET_PARAMS(klass);
1091 :
1092 : // Set the finalizer
1093 8 : gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_gpac_tf_finalize);
1094 :
1095 : // Set the property handlers
1096 8 : gobject_class->set_property = GST_DEBUG_FUNCPTR(gst_gpac_tf_set_property);
1097 8 : gobject_class->get_property = GST_DEBUG_FUNCPTR(gst_gpac_tf_get_property);
1098 8 : gpac_install_global_properties(gobject_class);
1099 8 : gpac_install_local_properties(
1100 : gobject_class, GPAC_PROP_PRINT_STATS, GPAC_PROP_0);
1101 :
1102 : // Add the subclass-specific properties and pad templates
1103 8 : if (params->is_single) {
1104 6 : if (params->is_inside_sink) {
1105 1 : gst_element_class_add_static_pad_template(gstelement_class,
1106 : &internal_pad_template);
1107 : } else {
1108 5 : gst_element_class_add_static_pad_template(gstelement_class,
1109 5 : ¶ms->info->src_template);
1110 : }
1111 :
1112 : // Set property blacklist
1113 6 : GList* blacklist = NULL;
1114 6 : if (params->info->default_options) {
1115 3 : filter_option* opt = params->info->default_options;
1116 10 : for (u32 i = 0; opt[i].name; i++) {
1117 7 : if (opt[i].forced)
1118 4 : blacklist = g_list_append(blacklist, (gpointer)opt[i].name);
1119 : }
1120 : }
1121 :
1122 : // Install the filter properties
1123 6 : gpac_install_filter_properties(
1124 6 : gobject_class, blacklist, params->info->filter_name);
1125 6 : g_list_free(blacklist);
1126 :
1127 : // Install the signals if not inside the sink bin
1128 : // They would be installed by the sink bin itself
1129 6 : if (!params->is_inside_sink && params->info->signal_presets) {
1130 0 : gpac_install_signals_by_presets(gobject_class,
1131 0 : params->info->signal_presets);
1132 : }
1133 :
1134 : // Check if we have any filter options to expose
1135 8 : for (u32 i = 0; i < G_N_ELEMENTS(filter_options); i++) {
1136 6 : filter_option_overrides* opts = &filter_options[i];
1137 6 : if (g_strcmp0(opts->filter_name, params->info->filter_name) == 0) {
1138 8 : for (u32 j = 0; opts->options[j]; j++) {
1139 4 : guint32 prop_id = opts->options[j];
1140 4 : gpac_install_local_properties(gobject_class, prop_id, GPAC_PROP_0);
1141 : }
1142 4 : break;
1143 : }
1144 : }
1145 : } else {
1146 2 : gpac_install_src_pad_templates(gstelement_class);
1147 2 : gpac_install_local_properties(
1148 : gobject_class, GPAC_PROP_GRAPH, GPAC_PROP_DESTINATION, GPAC_PROP_0);
1149 :
1150 2 : if (!params->is_inside_sink)
1151 1 : gpac_install_all_signals(gobject_class);
1152 :
1153 : // We don't know which filters will be used, so we expose all options
1154 4 : for (u32 i = 0; i < G_N_ELEMENTS(filter_options); i++) {
1155 2 : filter_option_overrides* opts = &filter_options[i];
1156 4 : for (u32 j = 0; opts->options[j]; j++) {
1157 2 : guint32 prop_id = opts->options[j];
1158 2 : gpac_install_local_properties(gobject_class, prop_id, GPAC_PROP_0);
1159 : }
1160 : }
1161 : }
1162 :
1163 : // Set the metadata
1164 8 : const gchar* longname = "gpac transformer";
1165 8 : if (params->is_single) {
1166 6 : if (!g_strcmp0(params->info->filter_name, params->info->alias_name))
1167 : longname =
1168 2 : g_strdup_printf("gpac %s transformer", params->info->filter_name);
1169 : else
1170 4 : longname = g_strdup_printf("gpac %s (%s) transformer",
1171 4 : params->info->alias_name,
1172 4 : params->info->filter_name);
1173 : }
1174 8 : gst_element_class_set_static_metadata(
1175 : gstelement_class,
1176 : longname,
1177 : "Aggregator/Transform",
1178 : "Aggregates and transforms incoming data via GPAC",
1179 : "Deniz Ugur <deniz.ugur@motionspell.com>");
1180 8 : }
1181 :
1182 : gboolean
1183 2 : gst_gpac_tf_register(GstPlugin* plugin)
1184 : {
1185 : GType type;
1186 : GstGpacParams* params;
1187 2 : GTypeInfo subclass_typeinfo = {
1188 : sizeof(GstGpacTransformClass),
1189 : NULL, // base_init
1190 : NULL, // base_finalize
1191 : (GClassInitFunc)gst_gpac_tf_subclass_init,
1192 : NULL, // class_finalize
1193 : NULL, // class_data
1194 : sizeof(GstGpacTransform),
1195 : 0,
1196 : NULL, // instance_init
1197 : };
1198 :
1199 2 : GST_DEBUG_CATEGORY_INIT(gst_gpac_tf_debug, "gpactf", 0, "GPAC Transform");
1200 :
1201 : // Register the regular transform element
1202 2 : GST_LOG("Registering regular gpac transform element");
1203 2 : params = g_new0(GstGpacParams, 1);
1204 2 : params->is_single = FALSE;
1205 2 : params->is_inside_sink = FALSE;
1206 2 : type = g_type_register_static(
1207 : GST_TYPE_GPAC_TF, "GstGpacTransformRegular", &subclass_typeinfo, 0);
1208 2 : g_type_set_qdata(type, GST_GPAC_PARAMS_QDATA, params);
1209 2 : if (!gst_element_register(plugin, "gpactf", GST_RANK_PRIMARY, type)) {
1210 0 : GST_ERROR_OBJECT(plugin,
1211 : "Failed to register regular gpac transform element");
1212 0 : return FALSE;
1213 : }
1214 :
1215 : // Register subelements
1216 10 : for (u32 i = 0; i < G_N_ELEMENTS(subelements); i++) {
1217 8 : subelement_info* info = &subelements[i];
1218 :
1219 8 : if (info->flags & GPAC_SE_SINK_ONLY) {
1220 2 : GST_DEBUG_OBJECT(plugin,
1221 : "Subelement %s is a sink only element, skipping",
1222 : info->alias_name);
1223 2 : continue;
1224 : }
1225 :
1226 : // Register the sub transform element
1227 6 : GST_LOG("Registering %s transform subelement", info->filter_name);
1228 6 : params = g_new0(GstGpacParams, 1);
1229 6 : params->is_single = TRUE;
1230 6 : params->is_inside_sink = FALSE;
1231 6 : params->info = info;
1232 6 : const gchar* name = g_strdup_printf("gpac%s", info->alias_name);
1233 : const gchar* type_name =
1234 6 : g_strdup_printf("GstGpacTransform%c%s",
1235 6 : g_ascii_toupper(info->alias_name[0]),
1236 6 : info->alias_name + 1);
1237 :
1238 6 : type = g_type_register_static(
1239 : GST_TYPE_GPAC_TF, type_name, &subclass_typeinfo, 0);
1240 6 : g_type_set_qdata(type, GST_GPAC_PARAMS_QDATA, params);
1241 6 : if (!gst_element_register(plugin, name, GST_RANK_SECONDARY, type)) {
1242 0 : GST_ERROR_OBJECT(
1243 : plugin, "Failed to register %s transform subelement", info->alias_name);
1244 0 : return FALSE;
1245 : }
1246 : }
1247 :
1248 2 : return TRUE;
1249 : }
1250 :
1251 2 : GST_ELEMENT_REGISTER_DEFINE_CUSTOM(gpac_tf, gst_gpac_tf_register);
1252 :
1253 : // #MARK: Private registration
1254 : GType
1255 4 : gst_gpac_tf_register_custom(subelement_info* se_info,
1256 : gboolean is_inside_sink,
1257 : gboolean is_single)
1258 : {
1259 : const gchar* type_name =
1260 4 : g_strdup_printf("GstGpacTransformPrivate%c%s",
1261 4 : g_ascii_toupper(se_info->alias_name[0]),
1262 4 : se_info->alias_name + 1);
1263 :
1264 : // Check if the type is already registered
1265 4 : if (g_type_from_name(type_name) != G_TYPE_INVALID) {
1266 0 : GST_WARNING("Type %s is already registered, returning existing type",
1267 : type_name);
1268 0 : return g_type_from_name(type_name);
1269 : }
1270 :
1271 : GType type;
1272 4 : GTypeInfo type_info = {
1273 : sizeof(GstGpacTransformClass),
1274 : NULL, // base_init
1275 : NULL, // base_finalize
1276 : (GClassInitFunc)gst_gpac_tf_subclass_init,
1277 : NULL, // class_finalize
1278 : NULL, // class_data
1279 : sizeof(GstGpacTransform),
1280 : 0,
1281 : NULL,
1282 : };
1283 :
1284 4 : GST_DEBUG_CATEGORY_INIT(gst_gpac_tf_debug, "gpactf", 0, "GPAC Transform");
1285 :
1286 : // Register the custom transform element
1287 4 : GST_LOG("Creating a private gpac transform element with subelement info: %p",
1288 : se_info);
1289 :
1290 4 : GstGpacParams* params = g_new0(GstGpacParams, 1);
1291 4 : params->is_single = is_single;
1292 4 : params->is_inside_sink = is_inside_sink;
1293 4 : params->info = se_info;
1294 :
1295 4 : type = g_type_register_static(GST_TYPE_GPAC_TF, type_name, &type_info, 0);
1296 4 : g_type_set_qdata(type, GST_GPAC_PARAMS_QDATA, params);
1297 4 : return type;
1298 : }
|