1 /*
2 * GPAC - Multimedia Framework C SDK
3 *
4 * Authors: Jean Le Feuvre
5 * Copyright (c) Telecom ParisTech 2000-2020
6 * All rights reserved
7 *
8 * This file is part of GPAC / AMR&EVRC&SMV reframer filter
9 *
10 * GPAC is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2, or (at your option)
13 * any later version.
14 *
15 * GPAC is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; see the file COPYING. If not, write to
22 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 */
25
26 #include <gpac/filters.h>
27 #include <gpac/constants.h>
28
29 typedef struct
30 {
31 u64 pos;
32 Double duration;
33 } AMRIdx;
34
35 typedef struct
36 {
37 //filter args
38 Double index;
39
40 //only one input pid declared
41 GF_FilterPid *ipid;
42 //only one output pid declared
43 GF_FilterPid *opid;
44
45 u32 start_offset;
46 u32 codecid, sample_rate, block_size;
47
48
49 u64 file_pos, cts;
50
51 u16 amr_mode_set;
52
53 GF_Fraction64 duration;
54 Double start_range;
55 Bool in_seek;
56 u32 timescale;
57 Bool is_playing;
58 Bool is_file;
59 Bool initial_play_done, file_loaded;
60 Bool skip_magic;
61
62 u32 hdr;
63 u32 resume_from;
64 u32 remaining;
65
66 AMRIdx *indexes;
67 u32 index_alloc_size, index_size;
68 } GF_AMRDmxCtx;
69
70
71
72
amrdmx_configure_pid(GF_Filter * filter,GF_FilterPid * pid,Bool is_remove)73 GF_Err amrdmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
74 {
75 const GF_PropertyValue *p;
76 GF_AMRDmxCtx *ctx = gf_filter_get_udta(filter);
77
78 if (is_remove) {
79 ctx->ipid = NULL;
80 gf_filter_pid_remove(ctx->opid);
81 return GF_OK;
82 }
83 if (! gf_filter_pid_check_caps(pid))
84 return GF_NOT_SUPPORTED;
85
86 ctx->ipid = pid;
87 p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE);
88 if (p) ctx->timescale = p->value.uint;
89
90 ctx->start_offset = 6;
91 ctx->sample_rate = 8000;
92 ctx->block_size = 160;
93
94 p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID);
95 if (p) {
96 if (ctx->codecid && (ctx->codecid != p->value.uint)) {
97 return GF_NOT_SUPPORTED;
98 }
99 ctx->codecid = p->value.uint;
100 if (ctx->codecid == GF_CODECID_AMR_WB) {
101 ctx->sample_rate = 16000;
102 ctx->block_size = 320;
103 }
104 ctx->skip_magic = GF_FALSE;
105 if (!ctx->opid) {
106 ctx->opid = gf_filter_pid_new(filter);
107 gf_filter_pid_copy_properties(ctx->opid, ctx->ipid);
108 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL);
109 }
110 }
111 return GF_OK;
112 }
113
amrdmx_check_dur(GF_Filter * filter,GF_AMRDmxCtx * ctx)114 static void amrdmx_check_dur(GF_Filter *filter, GF_AMRDmxCtx *ctx)
115 {
116 FILE *stream;
117 u32 i;
118 u64 duration, cur_dur;
119 char magic[20];
120 const GF_PropertyValue *p;
121 if (!ctx->opid || ctx->timescale || ctx->file_loaded) return;
122
123 if (ctx->index<=0) {
124 ctx->file_loaded = GF_TRUE;
125 return;
126 }
127
128 p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH);
129 if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) {
130 ctx->is_file = GF_FALSE;
131 ctx->file_loaded = GF_TRUE;
132 return;
133 }
134 ctx->is_file = GF_TRUE;
135
136 stream = gf_fopen(p->value.string, "rb");
137 if (!stream) return;
138
139 ctx->codecid = GF_CODECID_NONE;
140 ctx->start_offset = 6;
141 ctx->sample_rate = 8000;
142 ctx->block_size = 160;
143 i = (u32) gf_fread(magic, 20, stream);
144 if (i != 20) return;
145
146 if (!strnicmp(magic, "#!AMR\n", 6)) {
147 gf_fseek(stream, 6, SEEK_SET);
148 ctx->codecid = GF_CODECID_AMR;
149 }
150 else if (!strnicmp(magic, "#!EVRC\n", 7)) {
151 gf_fseek(stream, 7, SEEK_SET);
152 ctx->start_offset = 7;
153 ctx->codecid = GF_CODECID_EVRC;
154 }
155 else if (!strnicmp(magic, "#!SMV\n", 6)) {
156 gf_fseek(stream, 6, SEEK_SET);
157 ctx->codecid = GF_CODECID_SMV;
158 }
159 else if (!strnicmp(magic, "#!AMR-WB\n", 9)) {
160 ctx->codecid = GF_CODECID_AMR_WB;
161 ctx->start_offset = 9;
162 ctx->sample_rate = 16000;
163 ctx->block_size = 320;
164 gf_fseek(stream, 9, SEEK_SET);
165 }
166 else if (!strnicmp(magic, "#!AMR_MC1.0\n", 12)) return;
167 else if (!strnicmp(magic, "#!AMR-WB_MC1.0\n", 15)) return;
168 else return;
169
170 ctx->index_size = 0;
171
172 cur_dur = 0;
173 duration = 0;
174 while (!gf_feof(stream)) {
175 u32 size=0;
176 u64 pos;
177 u8 toc, ft;
178 toc = gf_fgetc(stream);
179
180 switch (ctx->codecid) {
181 case GF_CODECID_AMR:
182 ft = (toc >> 3) & 0x0F;
183 size = (u32)GF_AMR_FRAME_SIZE[ft];
184 break;
185 case GF_CODECID_AMR_WB:
186 ft = (toc >> 3) & 0x0F;
187 size = (u32)GF_AMR_WB_FRAME_SIZE[ft];
188 break;
189 default:
190 for (i=0; i<GF_SMV_EVRC_RATE_TO_SIZE_NB; i++) {
191 if (GF_SMV_EVRC_RATE_TO_SIZE[2*i]==toc) {
192 /*remove rate_type byte*/
193 size = (u32)GF_SMV_EVRC_RATE_TO_SIZE[2*i+1] - 1;
194 break;
195 }
196 }
197 break;
198 }
199 duration += ctx->block_size;
200 cur_dur += ctx->block_size;
201 pos = gf_ftell(stream);
202 if (cur_dur > ctx->index * ctx->sample_rate) {
203 if (!ctx->index_alloc_size) ctx->index_alloc_size = 10;
204 else if (ctx->index_alloc_size == ctx->index_size) ctx->index_alloc_size *= 2;
205 ctx->indexes = gf_realloc(ctx->indexes, sizeof(AMRIdx)*ctx->index_alloc_size);
206 ctx->indexes[ctx->index_size].pos = pos - 1;
207 ctx->indexes[ctx->index_size].duration = (Double) duration;
208 ctx->indexes[ctx->index_size].duration /= ctx->sample_rate;
209 ctx->index_size ++;
210 cur_dur = 0;
211 }
212 if (size) gf_fseek(stream, size, SEEK_CUR);
213 }
214 gf_fclose(stream);
215
216 if (!ctx->duration.num || (ctx->duration.num * ctx->sample_rate != duration * ctx->duration.den)) {
217 ctx->duration.num = (u32) duration;
218 ctx->duration.den = ctx->sample_rate;
219
220 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration));
221 }
222
223 p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED);
224 if (p && p->value.boolean) ctx->file_loaded = GF_TRUE;
225 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CAN_DATAREF, & PROP_BOOL(GF_TRUE ) );
226 }
227
amrdmx_check_pid(GF_Filter * filter,GF_AMRDmxCtx * ctx,u16 amr_mode_set)228 static void amrdmx_check_pid(GF_Filter *filter, GF_AMRDmxCtx *ctx, u16 amr_mode_set)
229 {
230 if (ctx->opid) {
231 if (ctx->amr_mode_set != amr_mode_set) {
232 ctx->amr_mode_set = amr_mode_set;
233 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_AMR_MODE_SET, & PROP_UINT( amr_mode_set));
234 }
235 return;
236 }
237
238 ctx->opid = gf_filter_pid_new(filter);
239 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT( GF_STREAM_AUDIO));
240
241 amrdmx_check_dur(filter, ctx);
242
243 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, & PROP_UINT(ctx->sample_rate));
244 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, & PROP_UINT(ctx->sample_rate));
245 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, & PROP_UINT(1) );
246 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(ctx->codecid ) );
247 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLES_PER_FRAME, & PROP_UINT(ctx->block_size ) );
248 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_AMR_MODE_SET, & PROP_UINT(ctx->amr_mode_set));
249
250 if (ctx->is_file && ctx->index) {
251 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PLAYBACK_MODE, & PROP_UINT(GF_PLAYBACK_MODE_FASTFORWARD) );
252 }
253 }
254
amrdmx_process_event(GF_Filter * filter,const GF_FilterEvent * evt)255 static Bool amrdmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt)
256 {
257 u32 i;
258 GF_FilterEvent fevt;
259 GF_AMRDmxCtx *ctx = gf_filter_get_udta(filter);
260
261 switch (evt->base.type) {
262 case GF_FEVT_PLAY:
263 if (!ctx->is_playing) {
264 ctx->is_playing = GF_TRUE;
265 ctx->cts = 0;
266 ctx->remaining = 0;
267 }
268 if (! ctx->is_file) {
269 return GF_FALSE;
270 }
271 amrdmx_check_dur(filter, ctx);
272
273 ctx->start_range = evt->play.start_range;
274 ctx->in_seek = GF_TRUE;
275 ctx->file_pos = 0;
276 if (ctx->start_range) {
277 for (i=1; i<ctx->index_size; i++) {
278 if (ctx->indexes[i].duration>ctx->start_range) {
279 ctx->cts = (u64) (ctx->indexes[i-1].duration * ctx->sample_rate);
280 ctx->file_pos = ctx->indexes[i-1].pos;
281 break;
282 }
283 }
284 }
285 if (!ctx->initial_play_done) {
286 ctx->initial_play_done = GF_TRUE;
287 //seek will not change the current source state, don't send a seek
288 if (!ctx->file_pos) {
289 ctx->skip_magic = GF_TRUE;
290 return GF_TRUE;
291 }
292 }
293 //post a seek
294 GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid);
295 if (!ctx->file_pos)
296 ctx->skip_magic = GF_TRUE;
297 fevt.seek.start_offset = ctx->file_pos;
298 gf_filter_pid_send_event(ctx->ipid, &fevt);
299
300 //cancel event
301 return GF_TRUE;
302
303 case GF_FEVT_STOP:
304 ctx->is_playing = GF_FALSE;
305 //don't cancel event
306 return GF_FALSE;
307
308 case GF_FEVT_SET_SPEED:
309 //cancel event
310 return GF_TRUE;
311 default:
312 break;
313 }
314 //by default don't cancel event - to rework once we have downloading in place
315 return GF_FALSE;
316 }
317
amrdmx_update_cts(GF_AMRDmxCtx * ctx)318 static GFINLINE void amrdmx_update_cts(GF_AMRDmxCtx *ctx)
319 {
320 if (ctx->timescale) {
321 u64 inc = ctx->block_size;
322 inc *= ctx->timescale;
323 inc /= ctx->sample_rate;
324 ctx->cts += inc;
325 } else {
326 ctx->cts += ctx->block_size;
327 }
328 }
329
amrdmx_process(GF_Filter * filter)330 GF_Err amrdmx_process(GF_Filter *filter)
331 {
332 GF_AMRDmxCtx *ctx = gf_filter_get_udta(filter);
333 GF_FilterPacket *pck, *dst_pck;
334 u64 byte_offset;
335 u8 *data, *output;
336 u8 *start;
337 u32 pck_size, remain;
338
339 //update duration
340 amrdmx_check_dur(filter, ctx);
341
342 if (ctx->opid && !ctx->is_playing)
343 return GF_OK;
344
345 pck = gf_filter_pid_get_packet(ctx->ipid);
346 if (!pck) {
347 if (gf_filter_pid_is_eos(ctx->ipid)) {
348 if (ctx->opid)
349 gf_filter_pid_set_eos(ctx->opid);
350 assert(ctx->remaining == 0);
351 return GF_EOS;
352 }
353 return GF_OK;
354 }
355
356 data = (char *) gf_filter_pck_get_data(pck, &pck_size);
357 byte_offset = gf_filter_pck_get_byte_offset(pck);
358
359 start = data;
360 remain = pck_size;
361
362 //flush not previously dispatched data
363 if (ctx->remaining) {
364 u32 to_send = ctx->remaining;
365 if (ctx->remaining > pck_size) {
366 to_send = pck_size;
367 ctx->remaining -= pck_size;
368 } else {
369 ctx->remaining = 0;
370 }
371 if (! ctx->in_seek) {
372 dst_pck = gf_filter_pck_new_alloc(ctx->opid, to_send, &output);
373 memcpy(output, data, to_send);
374
375 gf_filter_pck_set_cts(dst_pck, ctx->cts);
376 gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1);
377 gf_filter_pck_set_framing(dst_pck, GF_FALSE, ctx->remaining ? GF_FALSE : GF_TRUE);
378 if (byte_offset != GF_FILTER_NO_BO) {
379 gf_filter_pck_set_byte_offset(dst_pck, byte_offset);
380 }
381 gf_filter_pck_send(dst_pck);
382 }
383
384 if (ctx->remaining) {
385 gf_filter_pid_drop_packet(ctx->ipid);
386 return GF_OK;
387 }
388 amrdmx_update_cts(ctx);
389 start += to_send;
390 remain -= to_send;
391 }
392
393 //input pid sets some timescale - we flushed pending data , update cts
394 if (ctx->timescale) {
395 u64 cts = gf_filter_pck_get_cts(pck);
396 if (cts != GF_FILTER_NO_TS)
397 ctx->cts = cts;
398 }
399 if (ctx->skip_magic) {
400
401 if (!strnicmp(start, "#!AMR\n", 6)) {
402 ctx->start_offset = 6;
403 ctx->codecid = GF_CODECID_AMR;
404 }
405 else if (!strnicmp(start, "#!EVRC\n", 7)) {
406 ctx->start_offset = 7;
407 ctx->codecid = GF_CODECID_EVRC;
408 }
409 else if (!strnicmp(start, "#!SMV\n", 6)) {
410 ctx->start_offset = 6;
411 ctx->codecid = GF_CODECID_SMV;
412 }
413 else if (!strnicmp(start, "#!AMR-WB\n", 9)) {
414 ctx->codecid = GF_CODECID_AMR_WB;
415 ctx->start_offset = 9;
416 ctx->sample_rate = 16000;
417 ctx->block_size = 320;
418 }
419 start += ctx->start_offset;
420 remain -= ctx->start_offset;
421 }
422 if (ctx->resume_from) {
423 start += ctx->resume_from;
424 remain -= ctx->resume_from;
425 ctx->resume_from = 0;
426 }
427
428
429 while (remain) {
430 u8 toc, ft;
431 u16 amr_mode_set = ctx->amr_mode_set;
432 u32 size=0, i;
433
434 toc = start[0];
435 if (!toc) {
436 GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[AMRDmx] Could not find TOC word in packet, droping\n"));
437 break;
438 }
439 switch (ctx->codecid) {
440 case GF_CODECID_AMR:
441 ft = (toc >> 3) & 0x0F;
442
443 /*update mode set (same mechanism for both AMR and AMR-WB*/
444 amr_mode_set |= (1<<ft);
445 size = (u32)GF_AMR_FRAME_SIZE[ft];
446 break;
447 case GF_CODECID_AMR_WB:
448 ft = (toc >> 3) & 0x0F;
449 size = (u32)GF_AMR_WB_FRAME_SIZE[ft];
450
451 /*update mode set (same mechanism for both AMR and AMR-WB*/
452 amr_mode_set |= (1<<ft);
453 break;
454 case GF_CODECID_NONE:
455 size=0;
456 break;
457 default:
458 for (i=0; i<GF_SMV_EVRC_RATE_TO_SIZE_NB; i++) {
459 if (GF_SMV_EVRC_RATE_TO_SIZE[2*i]==toc) {
460 /*remove rate_type byte*/
461 size = (u32)GF_SMV_EVRC_RATE_TO_SIZE[2*i+1] - 1;
462 break;
463 }
464 }
465 break;
466 }
467
468 if (!size) {
469 GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[AMRDmx] Broken TOC, trying resync\n"));
470 start++;
471 remain--;
472 continue;
473 }
474 //ready to send packet
475 amrdmx_check_pid(filter, ctx, amr_mode_set);
476
477 if (!ctx->is_playing) return GF_OK;
478 size++;
479 if (size > remain) {
480 ctx->remaining = size - remain;
481 size = remain;
482 }
483
484 if (ctx->in_seek) {
485 u64 nb_samples_at_seek = (u64) (ctx->start_range * ctx->sample_rate);
486 if (ctx->cts + ctx->block_size >= nb_samples_at_seek) {
487 //u32 samples_to_discard = (ctx->cts + ctx->block_size ) - nb_samples_at_seek;
488 ctx->in_seek = GF_FALSE;
489 }
490 }
491 if (!ctx->in_seek) {
492 dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output);
493
494 memcpy(output, start, size);
495
496 gf_filter_pck_set_cts(dst_pck, ctx->cts);
497 gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1);
498 gf_filter_pck_set_duration(dst_pck, ctx->block_size);
499 gf_filter_pck_set_framing(dst_pck, GF_TRUE, ctx->remaining ? GF_FALSE : GF_TRUE);
500
501 if (byte_offset != GF_FILTER_NO_BO) {
502 u64 boffset = byte_offset;
503 boffset += start - data;
504 gf_filter_pck_set_byte_offset(dst_pck, boffset);
505 }
506
507 gf_filter_pck_send(dst_pck);
508 }
509 start += size;
510 remain -= size;
511
512 ctx->skip_magic = 0;
513 if (ctx->remaining) break;
514 amrdmx_update_cts(ctx);
515
516 //don't demux too much of input, abort when we would block. This avoid dispatching
517 //a huge number of frames in a single call
518 if (gf_filter_pid_would_block(ctx->opid)) {
519 ctx->resume_from = (u32) ( (char *)start - (char *)data);
520 return GF_OK;
521 }
522 }
523 gf_filter_pid_drop_packet(ctx->ipid);
524
525 return GF_OK;
526 }
527
amrdmx_initialize(GF_Filter * filter)528 static GF_Err amrdmx_initialize(GF_Filter *filter)
529 {
530 GF_AMRDmxCtx *ctx = gf_filter_get_udta(filter);
531 ctx->skip_magic = GF_TRUE;
532 return GF_OK;
533 }
534
amrdmx_finalize(GF_Filter * filter)535 static void amrdmx_finalize(GF_Filter *filter)
536 {
537 GF_AMRDmxCtx *ctx = gf_filter_get_udta(filter);
538 if (ctx->indexes) gf_free(ctx->indexes);
539 }
540
amrdmx_probe_data(const u8 * data,u32 size,GF_FilterProbeScore * score)541 static const char * amrdmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score)
542 {
543 if (!strnicmp(data, "#!AMR\n", 6)) {
544 *score = GF_FPROBE_SUPPORTED;
545 return "audio/amr";
546 }
547 else if (!strnicmp(data, "#!AMR-WB\n", 9)) {
548 *score = GF_FPROBE_SUPPORTED;
549 return "audio/amr";
550 }
551 else if (!strnicmp(data, "#!EVRC\n", 7)) {
552 *score = GF_FPROBE_SUPPORTED;
553 return "audio/evrc";
554 }
555 else if (!strnicmp(data, "#!SMV\n", 6)) {
556 *score = GF_FPROBE_SUPPORTED;
557 return "audio/smv";
558 }
559 return NULL;
560 }
561
562 static const GF_FilterCapability AMRDmxCaps[] =
563 {
564 CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
565 CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "amr|awb|evc|smv"),
566 CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "audio/amr|audio/evrc|audio/smv"),
567 CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO),
568 CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_AMR),
569 CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_AMR_WB),
570 CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_SMV),
571 CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_EVRC),
572 CAP_BOOL(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE),
573 };
574
575 #define OFFS(_n) #_n, offsetof(GF_AMRDmxCtx, _n)
576 static const GF_FilterArgs AMRDmxArgs[] =
577 {
578 { OFFS(index), "indexing window length", GF_PROP_DOUBLE, "1.0", NULL, 0},
579 {0}
580 };
581
582
583 GF_FilterRegister AMRDmxRegister = {
584 .name = "rfamr",
585 GF_FS_SET_DESCRIPTION("AMR/EVRC reframer")
586 GF_FS_SET_HELP("This filter parses AMR, AMR Wideband, EVRC and SMV files/data and outputs corresponding audio PID and frames.")
587 .private_size = sizeof(GF_AMRDmxCtx),
588 .args = AMRDmxArgs,
589 .initialize = amrdmx_initialize,
590 .finalize = amrdmx_finalize,
591 SETCAPS(AMRDmxCaps),
592 .configure_pid = amrdmx_configure_pid,
593 .process = amrdmx_process,
594 .probe_data = amrdmx_probe_data,
595 .process_event = amrdmx_process_event
596 };
597
598
amrdmx_register(GF_FilterSession * session)599 const GF_FilterRegister *amrdmx_register(GF_FilterSession *session)
600 {
601 return &AMRDmxRegister;
602 }
603
604