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 "lib/memio.h"
27 : #include "gpacmessages.h"
28 : #include "lib/caps.h"
29 : #include "lib/pid.h"
30 : #include "post-process/common.h"
31 : #include "post-process/registry.h"
32 : #include <gst/video/video-event.h>
33 :
34 : static GF_Err
35 : gpac_default_memin_process_cb(GF_Filter* filter);
36 :
37 : static Bool
38 : gpac_default_memin_process_event_cb(GF_Filter* filter,
39 : const GF_FilterEvent* evt);
40 :
41 : static GF_Err
42 : gpac_default_memout_initialize_cb(GF_Filter* filter);
43 :
44 : static GF_Err
45 : gpac_default_memout_process_cb(GF_Filter* filter);
46 :
47 : static Bool
48 : gpac_default_memout_process_event_cb(GF_Filter* filter,
49 : const GF_FilterEvent* evt);
50 :
51 : static Bool
52 : gpac_default_memout_use_alias_cb(GF_Filter* filter,
53 : const char* url,
54 : const char* mime);
55 :
56 : static GF_FilterProbeScore
57 : gpac_default_memout_probe_url_cb(const char* url, const char* mime);
58 :
59 : static GF_Err
60 : gpac_default_memout_configure_pid_cb(GF_Filter* filter,
61 : GF_FilterPid* PID,
62 : Bool is_remove);
63 :
64 : static const GF_FilterCapability DefaultMemOutCaps[] = {
65 : CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
66 : CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "*"),
67 : };
68 :
69 : #define OFFS(_n) #_n, offsetof(GPAC_MemOutPrivateContext, _n)
70 : static const GF_FilterArgs MemoutArgs[] = {
71 : { OFFS(dst),
72 : "location of destination resource",
73 : GF_PROP_NAME,
74 : NULL,
75 : NULL,
76 : 0 },
77 : { OFFS(ext), "file extension", GF_PROP_NAME, NULL, NULL, 0 },
78 : { 0 }
79 : };
80 :
81 : GF_FilterRegister MemOutRegister = {
82 : .name = "memout",
83 : .args = MemoutArgs,
84 : .private_size = sizeof(GPAC_MemOutPrivateContext),
85 : .max_extra_pids = -1,
86 : .priority = -1,
87 : .flags =
88 : GF_FS_REG_FORCE_REMUX | GF_FS_REG_TEMP_INIT | GF_FS_REG_EXPLICIT_ONLY,
89 : .caps = DefaultMemOutCaps,
90 : .nb_caps = G_N_ELEMENTS(DefaultMemOutCaps),
91 : .initialize = gpac_default_memout_initialize_cb,
92 : .process = gpac_default_memout_process_cb,
93 : .process_event = gpac_default_memout_process_event_cb,
94 : .use_alias = gpac_default_memout_use_alias_cb,
95 : .probe_url = gpac_default_memout_probe_url_cb,
96 : .configure_pid = gpac_default_memout_configure_pid_cb,
97 : };
98 :
99 : GF_Err
100 29 : gpac_memio_new(GPAC_SessionContext* sess, GPAC_MemIoDirection dir)
101 : {
102 29 : GF_Err e = GF_OK;
103 29 : GF_Filter* memio = NULL;
104 :
105 29 : if (dir == GPAC_MEMIO_DIR_IN) {
106 16 : memio = sess->memin = gf_fs_new_filter(sess->session, "memin", 0, &e);
107 16 : if (!sess->memin) {
108 0 : GST_ELEMENT_ERROR(sess->element,
109 : LIBRARY,
110 : INIT,
111 : (NULL),
112 : ("Failed to create memin filter"));
113 0 : return e;
114 : }
115 16 : gf_filter_set_process_ckb(memio, gpac_default_memin_process_cb);
116 16 : gf_filter_set_process_event_ckb(memio, gpac_default_memin_process_event_cb);
117 : } else {
118 13 : gf_fs_add_filter_register(sess->session, &MemOutRegister);
119 13 : gchar* filter_name = "memout";
120 13 : gboolean link_to_last_filter = sess->params && sess->params->is_single;
121 :
122 : // Try to retrieve a destination
123 13 : const gchar* dst = NULL;
124 13 : if (sess->destination) {
125 : // If a destination is provided, use it
126 1 : dst = sess->destination;
127 1 : link_to_last_filter = TRUE;
128 12 : } else if (sess->params && sess->params->info &&
129 12 : sess->params->info->destination) {
130 : // If the session parameters have a destination, use it
131 1 : dst = sess->params->info->destination;
132 : }
133 :
134 : // If we have a specific destination, use it
135 13 : if (dst)
136 2 : filter_name = g_strdup_printf("memout:dst=%s", dst);
137 :
138 13 : memio = sess->memout = gf_fs_load_filter(sess->session, filter_name, &e);
139 13 : if (!sess->memout) {
140 0 : GST_ELEMENT_ERROR(
141 : sess->element, LIBRARY, INIT, (NULL), ("Failed to load memout filter"));
142 0 : return e;
143 : }
144 :
145 : // If we are in single filter mode (explicit destination), we explicitly
146 : // connect to the last loaded filter to avoid connecting to the memin filter
147 : // unnecessarily
148 13 : if (link_to_last_filter) {
149 13 : u32 count = gf_fs_get_filters_count(sess->session);
150 13 : GF_Filter* filter = gf_fs_get_filter(sess->session, count - 2);
151 13 : if (filter) {
152 : // Connect the memout filter to the last filter
153 13 : if (gf_filter_set_source(memio, filter, NULL) != GF_OK) {
154 0 : GST_ELEMENT_ERROR(sess->element,
155 : LIBRARY,
156 : INIT,
157 : (NULL),
158 : ("Failed to connect memout filter to the last "
159 : "loaded filter %s",
160 : gf_filter_get_name(filter)));
161 0 : return GF_BAD_PARAM;
162 : }
163 : } else {
164 0 : GST_ELEMENT_ERROR(sess->element,
165 : LIBRARY,
166 : INIT,
167 : (NULL),
168 : ("No filters found in the session"));
169 0 : return GF_BAD_PARAM;
170 : }
171 : }
172 : }
173 :
174 : // Set the runtime user data
175 29 : GPAC_MemIoContext* rt_udta = g_new0(GPAC_MemIoContext, 1);
176 29 : if (!rt_udta) {
177 0 : GST_ELEMENT_ERROR(sess->element,
178 : LIBRARY,
179 : INIT,
180 : (NULL),
181 : ("Failed to allocate memory for runtime user data"));
182 0 : return GF_OUT_OF_MEM;
183 : }
184 29 : gpac_return_if_fail(gf_filter_set_rt_udta(memio, rt_udta));
185 29 : rt_udta->dir = dir;
186 29 : rt_udta->global_offset = GST_CLOCK_TIME_NONE;
187 29 : rt_udta->sess = sess;
188 :
189 29 : return e;
190 : }
191 :
192 : void
193 16 : gpac_memio_free(GPAC_SessionContext* sess)
194 : {
195 16 : if (sess->memin)
196 16 : gf_free(gf_filter_get_rt_udta(sess->memin));
197 :
198 16 : if (sess->memout)
199 13 : gf_free(gf_filter_get_rt_udta(sess->memout));
200 16 : }
201 :
202 : void
203 16 : gpac_memio_assign_queue(GPAC_SessionContext* sess,
204 : GPAC_MemIoDirection dir,
205 : GQueue* queue)
206 : {
207 16 : GPAC_MemIoContext* rt_udta = gf_filter_get_rt_udta(
208 : dir == GPAC_MEMIO_DIR_IN ? sess->memin : sess->memout);
209 16 : if (!rt_udta) {
210 0 : GST_ELEMENT_ERROR(sess->element,
211 : LIBRARY,
212 : FAILED,
213 : (NULL),
214 : ("Failed to get runtime user data"));
215 0 : return;
216 : }
217 16 : rt_udta->queue = queue;
218 : }
219 :
220 : void
221 29 : gpac_memio_set_eos(GPAC_SessionContext* sess, gboolean eos)
222 : {
223 29 : if (!sess->memin)
224 0 : return;
225 :
226 29 : GPAC_MemIoContext* rt_udta = gf_filter_get_rt_udta(sess->memin);
227 29 : if (!rt_udta) {
228 0 : GST_ELEMENT_ERROR(sess->element,
229 : LIBRARY,
230 : FAILED,
231 : (NULL),
232 : ("Failed to get runtime user data"));
233 0 : return;
234 : }
235 29 : rt_udta->eos = eos;
236 : }
237 :
238 : gboolean
239 12 : gpac_memio_set_gst_caps(GPAC_SessionContext* sess, GstCaps* caps)
240 : {
241 12 : if (!sess->memout)
242 3 : return TRUE;
243 :
244 : // Save the current caps
245 9 : guint cur_nb_caps = 0;
246 : const GF_FilterCapability* current_caps =
247 9 : gf_filter_get_caps(sess->memout, &cur_nb_caps);
248 :
249 : // Convert the caps to GF_FilterCapability
250 9 : guint new_nb_caps = 0;
251 9 : GF_FilterCapability* gf_caps = gpac_gstcaps_to_gfcaps(caps, &new_nb_caps);
252 9 : if (!gf_caps) {
253 0 : GST_ELEMENT_ERROR(sess->element,
254 : STREAM,
255 : FAILED,
256 : (NULL),
257 : ("Failed to convert the caps to GF_FilterCapability"));
258 0 : return FALSE;
259 : }
260 :
261 : // Set the capabilities
262 9 : if (gf_filter_override_caps(sess->memout, gf_caps, new_nb_caps) != GF_OK) {
263 0 : GST_ELEMENT_ERROR(sess->element,
264 : STREAM,
265 : FAILED,
266 : (NULL),
267 : ("Failed to set the caps on the memory output filter, "
268 : "reverting to the previous caps"));
269 0 : gf_free(gf_caps);
270 0 : gf_filter_override_caps(sess->memout, current_caps, cur_nb_caps);
271 0 : return FALSE;
272 : }
273 :
274 : // Reconnect the pipeline
275 9 : u32 count = gf_fs_get_filters_count(sess->session);
276 36 : for (u32 i = 0; i < count; i++) {
277 27 : GF_Filter* filter = gf_fs_get_filter(sess->session, i);
278 27 : gf_filter_reconnect_output(filter, NULL);
279 : }
280 :
281 9 : return TRUE;
282 : }
283 :
284 : GPAC_FilterPPRet
285 2541 : gpac_memio_consume(GPAC_SessionContext* sess, void** outptr)
286 : {
287 2541 : if (!sess->memout)
288 14 : return GPAC_FILTER_PP_RET_NULL;
289 :
290 : // Context
291 2527 : guint32 pid_to_consume = 0;
292 2527 : GF_FilterPid* best_ipid = NULL;
293 2527 : GPAC_MemOutPIDContext* best_pctx = NULL;
294 2527 : GPAC_FilterPPRet ret = GPAC_FILTER_PP_RET_INVALID;
295 :
296 : // Find the PID to consume
297 11490 : for (u32 i = 0; i < gf_filter_get_ipid_count(sess->memout); i++) {
298 8963 : GF_FilterPid* ipid = gf_filter_get_ipid(sess->memout, i);
299 : GPAC_MemOutPIDContext* pctx =
300 8963 : (GPAC_MemOutPIDContext*)gf_filter_pid_get_udta(ipid);
301 8963 : GPAC_MemOutPIDFlags udta_flags = gf_filter_pid_get_udta_flags(ipid);
302 :
303 : // If we should not consume this PID, call the consume callback
304 : // and continue to the next one
305 8963 : if (udta_flags & GPAC_MEMOUT_PID_FLAG_DONT_CONSUME) {
306 8045 : ret |= pctx->entry->consume(sess->memout, ipid, NULL);
307 8045 : continue;
308 : }
309 :
310 : // Check if there are more than one PID to consume
311 918 : if (pid_to_consume > 0) {
312 0 : GST_ELEMENT_ERROR(sess->element,
313 : STREAM,
314 : FAILED,
315 : (NULL),
316 : ("Multiple PIDs to consume, this is not supported by "
317 : "the memout filter"));
318 0 : return GPAC_FILTER_PP_RET_ERROR;
319 : }
320 :
321 : // We can consume this PID
322 918 : pid_to_consume++;
323 918 : best_ipid = ipid;
324 918 : best_pctx = pctx;
325 : }
326 :
327 2527 : if (!best_ipid) {
328 1609 : *outptr = NULL;
329 : // No PID to consume
330 : return ret == GPAC_FILTER_PP_RET_INVALID
331 : ? GPAC_FILTER_PP_RET_EMPTY
332 1609 : : ret; // If we have a signal, return it
333 : }
334 :
335 : // We can consume the PID
336 918 : ret |= best_pctx->entry->consume(sess->memout, best_ipid, outptr);
337 918 : return ret;
338 : }
339 :
340 : void
341 20 : gpac_memio_set_global_offset(GPAC_SessionContext* sess,
342 : const GstSegment* segment)
343 : {
344 20 : if (!sess->memout)
345 3 : return;
346 :
347 17 : GPAC_MemIoContext* ctx = gf_filter_get_rt_udta(sess->memout);
348 17 : if (!ctx) {
349 0 : GST_ELEMENT_ERROR(sess->element,
350 : LIBRARY,
351 : FAILED,
352 : (NULL),
353 : ("Failed to get runtime user data"));
354 0 : return;
355 : }
356 :
357 : // Get the segment offset
358 17 : guint64 offset = segment->base + segment->start + segment->offset;
359 17 : if (offset == GST_CLOCK_TIME_NONE)
360 0 : return;
361 17 : if (ctx->global_offset == GST_CLOCK_TIME_NONE || !ctx->is_continuous) {
362 17 : ctx->global_offset = MIN(offset, ctx->global_offset);
363 0 : } else if (ctx->global_offset > offset) {
364 0 : GST_ELEMENT_WARNING(
365 : sess->element,
366 : STREAM,
367 : FAILED,
368 : (NULL),
369 : ("Cannot set a global offset smaller than the current one"));
370 : }
371 : }
372 :
373 : //////////////////////////////////////////////////////////////////////////
374 : // #MARK: Default Callbacks
375 : //////////////////////////////////////////////////////////////////////////
376 : static GF_Err
377 226621 : gpac_default_memin_process_cb(GF_Filter* filter)
378 : {
379 226621 : GPAC_MemIoContext* ctx = (GPAC_MemIoContext*)gf_filter_get_rt_udta(filter);
380 :
381 : // Flush the queue
382 226621 : GF_FilterPacket* packet = NULL;
383 233145 : while ((packet = g_queue_pop_head(ctx->queue)))
384 6524 : gf_filter_pck_send(packet);
385 :
386 : // All packets are sent, check if the EOS is set
387 226621 : if (ctx->eos) {
388 : // Set the EOS on all output PIDs
389 33 : for (guint i = 0; i < gf_filter_get_opid_count(filter); i++)
390 20 : gf_filter_pid_set_eos(gf_filter_get_opid(filter, i));
391 13 : return GF_EOS;
392 : }
393 :
394 226608 : return GF_OK;
395 : }
396 :
397 : static Bool
398 29 : gpac_default_memin_process_event_cb(GF_Filter* filter,
399 : const GF_FilterEvent* evt)
400 : {
401 29 : if (evt->base.type == GF_FEVT_TRANSPORT_HINTS) {
402 8 : GF_FilterPid* pid = evt->base.on_pid;
403 8 : GF_Fraction intra_period = evt->transport_hints.seg_duration;
404 8 : GpacPadPrivate* priv = gf_filter_pid_get_udta(pid);
405 8 : GstElement* element = gst_pad_get_parent_element(priv->self);
406 :
407 : // Set the IDR period
408 8 : priv->idr_period =
409 8 : gf_timestamp_rescale(intra_period.num, intra_period.den, GST_SECOND);
410 :
411 : // Determine the next IDR frame
412 8 : if (priv->idr_last != GST_CLOCK_TIME_NONE) {
413 0 : priv->idr_next = priv->idr_last + priv->idr_period;
414 :
415 : // Request a new IDR, immediately
416 : GstEvent* gst_event =
417 0 : gst_video_event_new_upstream_force_key_unit(priv->idr_next, TRUE, 1);
418 0 : if (!gst_pad_push_event(priv->self, gst_event)) {
419 0 : GST_ELEMENT_WARNING(element,
420 : STREAM,
421 : FAILED,
422 : (NULL),
423 : ("Failed to push the force key unit event"));
424 : }
425 : }
426 :
427 8 : return GF_TRUE;
428 : }
429 21 : return GF_FALSE;
430 : }
431 :
432 : static GF_Err
433 21 : gpac_default_memout_initialize_cb(GF_Filter* filter)
434 : {
435 : GPAC_MemOutPrivateContext* ctx =
436 21 : (GPAC_MemOutPrivateContext*)gf_filter_get_udta(filter);
437 :
438 : // ext only used if not an alias, otherwise figure out from dst
439 21 : const char* ext = gf_filter_is_alias(filter) ? NULL : ctx->ext;
440 21 : if (!ext && ctx->dst) {
441 10 : ext = gf_file_ext_start(ctx->dst);
442 10 : if (ext)
443 10 : ext++;
444 : }
445 :
446 21 : GF_LOG(GF_LOG_INFO,
447 : GF_LOG_CORE,
448 : ("memout initialize ext %s dst %s is_alias %d\n",
449 : ext ? ext : "none",
450 : ctx->dst ? ctx->dst : "none",
451 : gf_filter_is_alias(filter)));
452 21 : if (!ext)
453 11 : return GF_OK;
454 :
455 10 : ctx->caps[0].code = GF_PROP_PID_STREAM_TYPE;
456 10 : ctx->caps[0].val = PROP_UINT(GF_STREAM_FILE);
457 10 : ctx->caps[0].flags = GF_CAPS_INPUT;
458 :
459 10 : ctx->caps[1].code = GF_PROP_PID_FILE_EXT;
460 10 : ctx->caps[1].val = PROP_STRING(ext);
461 10 : ctx->caps[1].flags = GF_CAPS_INPUT;
462 10 : gf_filter_override_caps(filter, ctx->caps, 2);
463 :
464 10 : return GF_OK;
465 : }
466 :
467 : static GF_Err
468 262 : gpac_default_memout_process_cb(GF_Filter* filter)
469 : {
470 262 : GPAC_MemIoContext* ctx = (GPAC_MemIoContext*)gf_filter_get_rt_udta(filter);
471 :
472 1036 : for (u32 i = 0; i < gf_filter_get_ipid_count(filter); i++) {
473 774 : GF_Err e = GF_OK;
474 774 : GF_FilterPid* ipid = gf_filter_get_ipid(filter, i);
475 : GPAC_MemOutPIDContext* pctx =
476 774 : (GPAC_MemOutPIDContext*)gf_filter_pid_get_udta(ipid);
477 :
478 : // Get the packet
479 774 : GF_FilterPacket* pck = gf_filter_pid_get_packet(ipid);
480 :
481 : // If we have a post-process context, process the packet
482 774 : if (pctx && pctx->entry)
483 774 : e = pctx->entry->post_process(filter, ipid, pck);
484 :
485 774 : if (pck)
486 211 : gf_filter_pid_drop_packet(ipid);
487 774 : if (e != GF_OK)
488 0 : return e;
489 : }
490 :
491 262 : return GF_OK;
492 : }
493 :
494 : static Bool
495 0 : gpac_default_memout_process_event_cb(GF_Filter* filter,
496 : const GF_FilterEvent* evt)
497 : {
498 : GPAC_MemOutPIDContext* pctx =
499 0 : (GPAC_MemOutPIDContext*)gf_filter_pid_get_udta(evt->base.on_pid);
500 :
501 : // If we have a post-process context, process the event
502 0 : if (pctx && pctx->entry)
503 0 : return pctx->entry->process_event(filter, evt);
504 0 : return GF_FALSE; // No post-process context, do not process the event
505 : }
506 :
507 : static Bool
508 8 : gpac_default_memout_use_alias_cb(GF_Filter* filter,
509 : const char* url,
510 : const char* mime)
511 : {
512 8 : return GF_TRUE; // Always allow alias usage
513 : }
514 :
515 : static GF_FilterProbeScore
516 8 : gpac_default_memout_probe_url_cb(const char* url, const char* mime)
517 : {
518 8 : return GF_FPROBE_SUPPORTED; // Support everything
519 : }
520 :
521 : static GF_Err
522 19 : gpac_default_memout_configure_pid_cb(GF_Filter* filter,
523 : GF_FilterPid* pid,
524 : Bool is_remove)
525 : {
526 19 : GPAC_MemIoContext* ctx = (GPAC_MemIoContext*)gf_filter_get_rt_udta(filter);
527 19 : GPAC_MemOutPIDContext* pctx = gf_filter_pid_get_udta(pid);
528 19 : GPAC_MemOutPIDFlags udta = gf_filter_pid_get_udta_flags(pid);
529 :
530 19 : if (is_remove) {
531 0 : if (pctx) {
532 : // Free the post-process context if it exists
533 0 : if (pctx->entry)
534 0 : pctx->entry->ctx_free(pctx->private_ctx);
535 0 : g_free(pctx);
536 : }
537 0 : return GF_OK;
538 : }
539 :
540 19 : if (!udta) {
541 : GF_FilterEvent evt;
542 19 : gf_filter_pid_init_play_event(pid, &evt, 0, 1, "MemOut");
543 19 : gf_filter_pid_send_event(pid, &evt);
544 19 : gf_filter_pid_set_udta_flags(pid, GPAC_MEMOUT_PID_FLAG_INITIALIZED);
545 : } else {
546 : // If the PID is already initialized, we can skip the configuration
547 0 : if (udta & GPAC_MEMOUT_PID_FLAG_INITIALIZED) {
548 0 : GST_DEBUG_OBJECT(ctx->sess->element,
549 : "PID %s is already initialized, proceeding with "
550 : "configure_pid callback only",
551 : gf_filter_pid_get_name(pid));
552 0 : return pctx->entry->configure_pid(filter, pid);
553 : }
554 : }
555 :
556 : // Allocate the post-process context
557 19 : pctx = g_new0(GPAC_MemOutPIDContext, 1);
558 19 : gf_filter_pid_set_udta(pid, pctx);
559 :
560 : // Check if there is a "dasher" upstream filter
561 19 : gboolean has_dasher = FALSE;
562 19 : GF_Filter* uf = gf_filter_pid_get_source_filter(pid);
563 36 : while (uf) {
564 36 : if (g_strcmp0(gf_filter_get_name(uf), "dasher") == 0) {
565 10 : has_dasher = TRUE;
566 10 : break;
567 : }
568 26 : GF_FilterPid* upid = gf_filter_get_ipid(uf, 0);
569 26 : if (!upid)
570 9 : break; // No upstream PID, stop checking
571 17 : uf = gf_filter_pid_get_source_filter(upid);
572 : }
573 :
574 : // Decide which post-process context to use
575 19 : if (has_dasher) {
576 : // If the upstream chain has dasher, we use the DASH post-process context,
577 : // regardless of whether it's connected to dasher directly or mp4mx
578 10 : pctx->entry = gpac_filter_get_post_process_registry_entry("dasher");
579 : } else {
580 : // Otherwise, use the post-process context based on connected filter name
581 9 : const gchar* source_name = gf_filter_pid_get_filter_name(pid);
582 9 : pctx->entry = gpac_filter_get_post_process_registry_entry(source_name);
583 : }
584 :
585 : // Create a new post-process context
586 19 : pctx->entry->ctx_init(&pctx->private_ctx);
587 :
588 : // Configure the PID with the post-process context
589 19 : return pctx->entry->configure_pid(filter, pid);
590 : }
|