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