Line data Source code
1 : /*
2 : * GPAC - Multimedia Framework C SDK
3 : *
4 : * Authors: Jean Le Feuvre
5 : * Copyright (c) Telecom ParisTech 2005-2021
6 : * All rights reserved
7 : *
8 : * This file is part of GPAC / NHNT demuxer 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 : #include <gpac/thread.h>
29 : #include <gpac/list.h>
30 : #include <gpac/bitstream.h>
31 :
32 : #ifndef GPAC_DISABLE_AV_PARSERS
33 : #include <gpac/avparse.h>
34 : #endif
35 :
36 :
37 : enum
38 : {
39 : GF_MEDIA_TYPE_NHNL = GF_4CC('N','H','n','l'),
40 : GF_MEDIA_TYPE_NHNT = GF_4CC('N','H','n','t')
41 : };
42 :
43 : typedef struct
44 : {
45 : u64 pos;
46 : Double duration;
47 : } NHNTIdx;
48 :
49 : typedef struct
50 : {
51 : //opts
52 : Bool reframe;
53 : Double index;
54 :
55 : GF_FilterPid *ipid;
56 : GF_FilterPid *opid;
57 :
58 : Double start_range;
59 : u64 first_dts;
60 :
61 : Bool is_playing;
62 : GF_Fraction64 duration;
63 : Bool need_reassign, in_seek;
64 :
65 : Bool initial_play_done;
66 : Bool header_parsed;
67 : u32 sig;
68 : u32 timescale;
69 :
70 : FILE *mdia;
71 : GF_BitStream *bs;
72 :
73 : NHNTIdx *indexes;
74 : u32 index_alloc_size, index_size;
75 : GF_Err in_error;
76 : } GF_NHNTDmxCtx;
77 :
78 :
79 :
80 0 : static void nhntdmx_check_dur(GF_NHNTDmxCtx *ctx)
81 : {
82 : GF_Fraction64 dur;
83 : FILE *stream;
84 : GF_BitStream *bs;
85 : u32 sig, timescale;
86 : u64 dts_prev;
87 : u32 prev_dur;
88 : u64 cur_dur=0;
89 : const GF_PropertyValue *p;
90 :
91 0 : if (ctx->duration.num) return;
92 :
93 0 : p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH);
94 0 : if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) {
95 0 : ctx->duration.num = 1;
96 0 : return;
97 : }
98 0 : if (ctx->duration.num) return;
99 :
100 0 : stream = gf_fopen(p->value.string, "rb");
101 0 : if (!stream) return;
102 :
103 : // ctx->index_size = 0;
104 :
105 0 : bs = gf_bs_from_file(stream, GF_BITSTREAM_READ);
106 :
107 :
108 0 : sig = GF_4CC(gf_bs_read_u8(bs), gf_bs_read_u8(bs), gf_bs_read_u8(bs), gf_bs_read_u8(bs));
109 0 : if (sig == GF_MEDIA_TYPE_NHNT) sig = 0;
110 0 : else if (sig == GF_MEDIA_TYPE_NHNL) sig = 1;
111 : else {
112 0 : gf_bs_del(bs);
113 0 : gf_fclose(stream);
114 0 : return;
115 : }
116 0 : gf_bs_read_u8(bs); /*version*/
117 0 : gf_bs_read_u8(bs); //streamType
118 0 : gf_bs_read_u8(bs); //oti
119 0 : gf_bs_read_u16(bs);
120 0 : gf_bs_read_u24(bs); //bufferSizeDB
121 0 : gf_bs_read_u32(bs); //avgBitrate
122 0 : gf_bs_read_u32(bs); //maxBitrate
123 0 : timescale = gf_bs_read_u32(bs);
124 :
125 : dur.num = 0;
126 : dts_prev = 0;
127 : prev_dur = 0;
128 0 : while (gf_bs_available(bs)) {
129 : u64 dts;
130 0 : u64 pos =gf_bs_get_position(bs);
131 0 : /*u32 len = */gf_bs_read_u24(bs);
132 0 : Bool is_rap = gf_bs_read_int(bs, 1);
133 0 : /*Bool is_start = (Bool)*/gf_bs_read_int(bs, 1);
134 0 : /*Bool is_end = (Bool)*/gf_bs_read_int(bs, 1);
135 : /*3 reserved + AU type (2)*/
136 0 : gf_bs_read_int(bs, 5);
137 :
138 0 : if (sig) {
139 0 : /*offset = */gf_bs_read_u64(bs);
140 0 : /*cts = */gf_bs_read_u64(bs);
141 0 : dts = gf_bs_read_u64(bs);
142 : } else {
143 0 : /*offset = */gf_bs_read_u32(bs);
144 0 : /*cts = */gf_bs_read_u32(bs);
145 0 : dts = gf_bs_read_u32(bs);
146 : }
147 0 : if (!ctx->first_dts)
148 0 : ctx->first_dts = 1 + dts;
149 0 : prev_dur = (u32) (dts - dts_prev);
150 0 : dur.num += prev_dur;
151 : dts_prev = dts;
152 :
153 0 : cur_dur += prev_dur;
154 : //only index at I-frame start
155 0 : if (is_rap && (cur_dur >= ctx->index * ctx->timescale) ) {
156 0 : if (!ctx->index_alloc_size) ctx->index_alloc_size = 10;
157 0 : else if (ctx->index_alloc_size == ctx->index_size) ctx->index_alloc_size *= 2;
158 0 : ctx->indexes = gf_realloc(ctx->indexes, sizeof(NHNTIdx)*ctx->index_alloc_size);
159 0 : ctx->indexes[ctx->index_size].pos = pos;
160 0 : ctx->indexes[ctx->index_size].duration = (Double) dur.num;
161 0 : ctx->indexes[ctx->index_size].duration /= ctx->timescale;
162 0 : ctx->index_size ++;
163 : cur_dur = 0;
164 : }
165 :
166 : }
167 0 : gf_bs_del(bs);
168 0 : gf_fclose(stream);
169 :
170 0 : dur.num += prev_dur;
171 0 : dur.den = timescale;
172 0 : if (!ctx->duration.num || (ctx->duration.num * dur.den != dur.num * ctx->duration.den)) {
173 0 : ctx->duration = dur;
174 0 : if (ctx->opid) {
175 0 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration));
176 0 : if (ctx->duration.num)
177 0 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_FASTFORWARD ) );
178 : }
179 : }
180 : }
181 :
182 :
183 0 : GF_Err nhntdmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
184 : {
185 0 : GF_NHNTDmxCtx *ctx = gf_filter_get_udta(filter);
186 :
187 0 : if (is_remove) {
188 0 : ctx->ipid = NULL;
189 : //gf_filter_pid_remove(st->opid);
190 :
191 0 : return GF_OK;
192 : }
193 0 : if (! gf_filter_pid_check_caps(pid))
194 : return GF_NOT_SUPPORTED;
195 :
196 0 : ctx->ipid = pid;
197 0 : gf_filter_pid_set_framing_mode(pid, GF_TRUE);
198 :
199 0 : return GF_OK;
200 : }
201 :
202 0 : static Bool nhntdmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt)
203 : {
204 : GF_FilterEvent fevt;
205 : u64 file_pos = 0;
206 : u32 i;
207 0 : GF_NHNTDmxCtx *ctx = gf_filter_get_udta(filter);
208 :
209 0 : switch (evt->base.type) {
210 0 : case GF_FEVT_PLAY:
211 0 : if (ctx->is_playing && (ctx->start_range == evt->play.start_range)) {
212 : return GF_TRUE;
213 : }
214 0 : nhntdmx_check_dur(ctx);
215 0 : ctx->start_range = evt->play.start_range;
216 0 : ctx->is_playing = GF_TRUE;
217 :
218 0 : if (ctx->start_range) {
219 0 : for (i=1; i<ctx->index_size; i++) {
220 0 : if (ctx->indexes[i].duration>ctx->start_range) {
221 0 : file_pos = ctx->indexes[i-1].pos;
222 0 : break;
223 : }
224 : }
225 : }
226 :
227 0 : if (!ctx->initial_play_done) {
228 0 : ctx->initial_play_done = GF_TRUE;
229 : //seek will not change the current source state, don't send a seek
230 0 : if (!file_pos)
231 : return GF_TRUE;
232 : }
233 : //post a seek
234 0 : ctx->need_reassign = GF_TRUE;
235 0 : ctx->in_seek = GF_TRUE;
236 0 : ctx->header_parsed = file_pos ? GF_TRUE : GF_FALSE;
237 0 : GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid);
238 0 : fevt.seek.start_offset = file_pos;
239 0 : gf_filter_pid_send_event(ctx->ipid, &fevt);
240 :
241 : //cancel event
242 0 : return GF_TRUE;
243 :
244 0 : case GF_FEVT_STOP:
245 0 : ctx->is_playing = GF_FALSE;
246 : //don't cancel event
247 0 : return GF_FALSE;
248 :
249 : case GF_FEVT_SET_SPEED:
250 : //cancel event
251 : return GF_TRUE;
252 : default:
253 : break;
254 : }
255 : //by default don't cancel event - to rework once we have downloading in place
256 0 : return GF_FALSE;
257 : }
258 :
259 0 : GF_Err nhntdmx_process(GF_Filter *filter)
260 : {
261 0 : GF_NHNTDmxCtx *ctx = gf_filter_get_udta(filter);
262 : const GF_PropertyValue *p;
263 : GF_FilterPacket *pck;
264 : u32 pkt_size;
265 : Bool start, end;
266 : u8 *data;
267 0 : if (ctx->in_error)
268 : return ctx->in_error;
269 :
270 0 : pck = gf_filter_pid_get_packet(ctx->ipid);
271 0 : if (!pck) {
272 0 : if (gf_filter_pid_is_eos(ctx->ipid)) gf_filter_pid_set_eos(ctx->opid);
273 : return GF_OK;
274 : }
275 0 : data = (u8 *)gf_filter_pck_get_data(pck, &pkt_size);
276 0 : gf_filter_pck_get_framing(pck, &start, &end);
277 : //for now we only work with complete files
278 : assert(end);
279 :
280 0 : if (!ctx->bs) {
281 0 : ctx->bs = gf_bs_new(data, pkt_size, GF_BITSTREAM_READ);
282 0 : } else if (ctx->need_reassign) {
283 0 : ctx->need_reassign = GF_FALSE;
284 0 : gf_bs_reassign_buffer(ctx->bs, data, pkt_size);
285 : }
286 :
287 0 : p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH);
288 0 : if (!p) {
289 0 : gf_filter_pid_drop_packet(ctx->ipid);
290 0 : return GF_NOT_SUPPORTED;
291 : }
292 :
293 0 : if (!ctx->header_parsed) {
294 0 : if (!ctx->opid) {
295 : char *ext;
296 : char szMedia[1000];
297 : char *dsi;
298 : u32 dsi_size;
299 : u32 val, oti;
300 : u64 media_size;
301 : Bool use_gfio = GF_FALSE;
302 : FILE *finfo;
303 :
304 :
305 0 : if (!strncmp(p->value.string, "gfio://", 7)) {
306 : use_gfio = GF_TRUE;
307 0 : strcpy(szMedia, gf_fileio_translate_url(p->value.string) );
308 : } else {
309 : strcpy(szMedia, p->value.string);
310 : }
311 :
312 0 : ext = strrchr(szMedia, '.');
313 0 : if (ext) ext[0] = 0;
314 : strcat(szMedia, ".media");
315 0 : ctx->mdia = gf_fopen_ex(szMedia, p->value.string, "rb");
316 :
317 0 : if (!ctx->mdia) {
318 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[NHNT] Cannot find MEDIA file %s\n", szMedia));
319 0 : gf_filter_pid_drop_packet(ctx->ipid);
320 0 : gf_filter_pid_set_discard(ctx->ipid, GF_TRUE);
321 0 : ctx->in_error = GF_URL_ERROR;
322 0 : return GF_URL_ERROR;
323 : }
324 :
325 0 : ctx->sig = GF_4CC(gf_bs_read_u8(ctx->bs), gf_bs_read_u8(ctx->bs), gf_bs_read_u8(ctx->bs), gf_bs_read_u8(ctx->bs));
326 0 : if (ctx->sig == GF_MEDIA_TYPE_NHNT) ctx->sig = 0;
327 0 : else if (ctx->sig == GF_MEDIA_TYPE_NHNL) ctx->sig = 1;
328 : else {
329 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[NHNT] Invalid NHNT signature %s\n", gf_4cc_to_str(ctx->sig) ));
330 0 : gf_filter_pid_drop_packet(ctx->ipid);
331 0 : gf_filter_pid_set_discard(ctx->ipid, GF_TRUE);
332 0 : ctx->in_error = GF_NON_COMPLIANT_BITSTREAM;
333 0 : return GF_NON_COMPLIANT_BITSTREAM;
334 : }
335 0 : ctx->opid = gf_filter_pid_new(filter);
336 :
337 : //we change the file path of the pid to point to the media stream, not the nhnt stream
338 0 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILEPATH, & PROP_STRING(szMedia));
339 :
340 0 : media_size = gf_fsize(ctx->mdia);
341 0 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DOWN_SIZE, & PROP_LONGUINT(media_size) );
342 :
343 : /*version*/
344 0 : gf_bs_read_u8(ctx->bs);
345 :
346 0 : val = gf_bs_read_u8(ctx->bs);
347 0 : if (val == GF_STREAM_OD) {
348 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[NHNT] OD stream detected, might result in broken import\n"));
349 : }
350 0 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(val));
351 :
352 0 : oti = gf_bs_read_u8(ctx->bs);
353 0 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(oti));
354 :
355 0 : gf_bs_read_u16(ctx->bs);
356 0 : /*val = */gf_bs_read_u24(ctx->bs); //bufferSizeDB
357 0 : val = gf_bs_read_u32(ctx->bs); //avgBitrate
358 0 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_BITRATE, &PROP_UINT(val));
359 0 : /*val = */gf_bs_read_u32(ctx->bs); //maxBitrate
360 0 : ctx->timescale = gf_bs_read_u32(ctx->bs);
361 0 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->timescale));
362 :
363 0 : if (use_gfio) {
364 0 : strcpy(szMedia, gf_fileio_translate_url(p->value.string) );
365 : } else {
366 0 : strcpy(szMedia, p->value.string);
367 : }
368 0 : ext = gf_file_ext_start(szMedia);
369 0 : if (ext) ext[0] = 0;
370 : strcat(szMedia, ".info");
371 :
372 0 : finfo = gf_fopen_ex(szMedia, p->value.string, "rb");
373 0 : dsi = NULL;
374 0 : if (finfo) {
375 0 : if ( gf_file_load_data_filep(finfo, (u8 **) &dsi, &dsi_size) != GF_OK) {
376 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[NHNT] Failed to read decoder config\n"));
377 : } else {
378 :
379 : #ifndef GPAC_DISABLE_AV_PARSERS
380 0 : if (oti==GF_CODECID_MPEG4_PART2) {
381 : GF_M4VDecSpecInfo cfg;
382 0 : GF_Err e = gf_m4v_get_config(dsi, dsi_size, &cfg);
383 0 : if (e>=0) {
384 0 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(cfg.width) );
385 0 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(cfg.height) );
386 0 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PROFILE_LEVEL, &PROP_UINT(cfg.VideoPL) );
387 0 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, &PROP_FRAC_INT(cfg.par_num, cfg.par_den) );
388 : }
389 : }
390 : #endif
391 :
392 0 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(dsi, dsi_size) );
393 : }
394 0 : gf_fclose(finfo);
395 : }
396 :
397 0 : if (ctx->reframe)
398 0 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, &PROP_BOOL(ctx->reframe) );
399 :
400 0 : nhntdmx_check_dur(ctx);
401 0 : ctx->header_parsed = GF_TRUE;
402 : } else {
403 : /*version*/
404 0 : gf_bs_skip_bytes(ctx->bs, 4+1+1+1+2+3+4+4+4);
405 0 : ctx->header_parsed = GF_TRUE;
406 : }
407 : }
408 :
409 0 : if (!ctx->is_playing) return GF_OK;
410 :
411 0 : while (gf_bs_available(ctx->bs) >= 16 ) {
412 : GF_FilterPacket *dst_pck;
413 : u8 *output;
414 : u64 dts, cts, offset;
415 0 : u32 res, len = gf_bs_read_u24(ctx->bs);
416 0 : Bool is_rap = gf_bs_read_int(ctx->bs, 1);
417 0 : Bool is_start = (Bool)gf_bs_read_int(ctx->bs, 1);
418 0 : Bool is_end = (Bool)gf_bs_read_int(ctx->bs, 1);
419 : /*3 reserved + AU type (2)*/
420 0 : gf_bs_read_int(ctx->bs, 5);
421 :
422 0 : if (ctx->sig) {
423 0 : offset = gf_bs_read_u64(ctx->bs);
424 0 : cts = gf_bs_read_u64(ctx->bs);
425 0 : dts = gf_bs_read_u64(ctx->bs);
426 : } else {
427 0 : offset = gf_bs_read_u32(ctx->bs);
428 0 : cts = gf_bs_read_u32(ctx->bs);
429 0 : dts = gf_bs_read_u32(ctx->bs);
430 : }
431 :
432 0 : if (!ctx->first_dts)
433 0 : ctx->first_dts = 1 + dts;
434 :
435 0 : if (ctx->in_seek) {
436 0 : Double now = (Double) (dts - (ctx->first_dts-1) );
437 0 : now /= ctx->timescale;
438 0 : if (now >= ctx->start_range) ctx->in_seek = GF_FALSE;
439 : }
440 0 : gf_fseek(ctx->mdia, offset, SEEK_SET);
441 :
442 0 : dst_pck = gf_filter_pck_new_alloc(ctx->opid, len, &output);
443 0 : if (!dst_pck) return GF_OUT_OF_MEM;
444 :
445 0 : res = (u32) gf_fread(output, len, ctx->mdia);
446 0 : if (res != len) {
447 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[NHNT] Read failure, expecting %d bytes got %d", len, res));
448 : }
449 0 : gf_filter_pck_set_framing(dst_pck, is_start, is_end);
450 0 : if (is_rap)
451 0 : gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1);
452 0 : gf_filter_pck_set_dts(dst_pck, dts);
453 0 : gf_filter_pck_set_cts(dst_pck, cts);
454 0 : gf_filter_pck_set_byte_offset(dst_pck, offset);
455 0 : if (ctx->in_seek) gf_filter_pck_set_seek_flag(dst_pck, GF_TRUE);
456 0 : gf_filter_pck_send(dst_pck);
457 :
458 0 : if (gf_filter_pid_would_block(ctx->opid))
459 : return GF_OK;
460 : }
461 0 : gf_filter_pid_drop_packet(ctx->ipid);
462 0 : return GF_OK;
463 : }
464 :
465 0 : GF_Err nhntdmx_initialize(GF_Filter *filter)
466 : {
467 : // GF_NHNTDmxCtx *ctx = gf_filter_get_udta(filter);
468 0 : return GF_OK;
469 : }
470 :
471 0 : void nhntdmx_finalize(GF_Filter *filter)
472 : {
473 0 : GF_NHNTDmxCtx *ctx = gf_filter_get_udta(filter);
474 0 : if (ctx->mdia) gf_fclose(ctx->mdia);
475 0 : if (ctx->bs) gf_bs_del(ctx->bs);
476 0 : if (ctx->indexes) gf_free(ctx->indexes);
477 0 : }
478 :
479 :
480 : #define OFFS(_n) #_n, offsetof(GF_NHNTDmxCtx, _n)
481 : static const GF_FilterArgs GF_NHNTDmxArgs[] =
482 : {
483 : { OFFS(reframe), "force reparsing of referenced content", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
484 : { OFFS(index), "indexing window length", GF_PROP_DOUBLE, "1.0", NULL, 0},
485 : {0}
486 : };
487 :
488 :
489 : static const GF_FilterCapability NHNTDmxCaps[] =
490 : {
491 : CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
492 : CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "nhnt"),
493 : CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "application/x-nhnt"),
494 : CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO),
495 : CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL),
496 : CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE),
497 : };
498 :
499 :
500 : GF_FilterRegister NHNTDmxRegister = {
501 : .name = "nhntr",
502 : GF_FS_SET_DESCRIPTION("NHNT reader")
503 : GF_FS_SET_HELP("This filter reads NHNT files/data to produce a media PID and frames.\n"
504 : "NHNT documentation is available at https://wiki.gpac.io/NHNT-Format\n")
505 : .private_size = sizeof(GF_NHNTDmxCtx),
506 : .args = GF_NHNTDmxArgs,
507 : .initialize = nhntdmx_initialize,
508 : .finalize = nhntdmx_finalize,
509 : SETCAPS(NHNTDmxCaps),
510 : .configure_pid = nhntdmx_configure_pid,
511 : .process = nhntdmx_process,
512 : .process_event = nhntdmx_process_event
513 : };
514 :
515 2877 : const GF_FilterRegister *nhntdmx_register(GF_FilterSession *session)
516 : {
517 2877 : return &NHNTDmxRegister;
518 : }
519 :
|