Line data Source code
1 : /*
2 : * GPAC - Multimedia Framework C SDK
3 : *
4 : * Authors: Jean Le Feuvre
5 : * Copyright (c) Telecom ParisTech 2017-2021
6 : * All rights reserved
7 : *
8 : * This file is part of GPAC / NHNT stream to file 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/bitstream.h>
29 :
30 : #include <gpac/internal/isomedia_dev.h>
31 :
32 :
33 : typedef struct
34 : {
35 : //opts
36 : Bool exporter, large;
37 :
38 : //only one input pid declared
39 : GF_FilterPid *ipid;
40 : //only one output pid declared
41 : GF_FilterPid *opid_nhnt, *opid_mdia, *opid_info;
42 :
43 : u32 codecid;
44 : u32 streamtype;
45 : u32 oti;
46 :
47 : const char *dcfg;
48 : u32 dcfg_size;
49 :
50 : GF_Fraction64 duration;
51 : Bool first;
52 :
53 : GF_BitStream *bs;
54 : u64 mdia_pos;
55 : Bool configure_side_streams;
56 : } GF_NHNTDumpCtx;
57 :
58 1 : GF_Err nhntdump_config_side_streams(GF_Filter *filter, GF_NHNTDumpCtx *ctx)
59 : {
60 : const GF_PropertyValue *p;
61 : char *name, *url, *res_name;
62 : char fileName[GF_MAX_PATH+1];
63 : GF_FileIO *gfio = NULL;
64 : GF_Err e;
65 :
66 1 : url = gf_filter_pid_get_destination(ctx->opid_nhnt);
67 1 : if (url) {
68 0 : if (!strncmp(url, "gfio://", 7)) {
69 0 : gfio = gf_fileio_from_url(url);
70 0 : strncpy(fileName, gf_fileio_translate_url(url), GF_MAX_PATH);
71 : } else {
72 : strncpy(fileName, url, GF_MAX_PATH);
73 : }
74 0 : fileName[GF_MAX_PATH] = 0;
75 0 : gf_free(url);
76 : } else {
77 : strcpy(fileName, "dump");
78 : }
79 :
80 1 : name = gf_file_ext_start(fileName);
81 1 : if (name) {
82 0 : name[0] = 0;
83 : }
84 1 : if (!ctx->opid_mdia)
85 1 : ctx->opid_mdia = gf_filter_pid_new(filter);
86 :
87 1 : p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_DECODER_CONFIG);
88 1 : if (p) {
89 1 : ctx->dcfg = p->value.data.ptr;
90 1 : ctx->dcfg_size = p->value.data.size;
91 :
92 1 : if (!ctx->opid_info)
93 1 : ctx->opid_info = gf_filter_pid_new(filter);
94 :
95 0 : } else if (ctx->opid_info) {
96 0 : gf_filter_pid_remove(ctx->opid_info);
97 0 : ctx->opid_info = NULL;
98 : }
99 :
100 1 : gf_filter_pid_set_property(ctx->opid_mdia, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) );
101 1 : gf_filter_pid_set_property(ctx->opid_mdia, GF_PROP_PID_FILE_EXT, &PROP_STRING("media") );
102 1 : gf_filter_pid_set_property(ctx->opid_mdia, GF_PROP_PID_MIME, &PROP_STRING("application/x-nhnt") );
103 :
104 1 : if (!ctx->exporter) {
105 0 : name = gf_file_ext_start(fileName);
106 0 : if (name) name[0] = 0;
107 : strcat(fileName, ".media");
108 0 : if (gfio) {
109 0 : res_name = (char *) gf_fileio_factory(gfio, gf_file_basename(fileName) );
110 : } else {
111 : res_name = fileName;
112 : }
113 0 : gf_filter_pid_set_property(ctx->opid_mdia, GF_PROP_PID_OUTPATH, &PROP_STRING(res_name) );
114 :
115 0 : GF_Filter *o_media = gf_filter_connect_destination(filter, res_name, &e);
116 0 : if (o_media) gf_filter_set_source(o_media, filter, NULL);
117 : }
118 :
119 :
120 1 : if (ctx->opid_info) {
121 1 : gf_filter_pid_set_property(ctx->opid_info, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) );
122 1 : gf_filter_pid_set_property(ctx->opid_info, GF_PROP_PID_FILE_EXT, &PROP_STRING("info") );
123 1 : gf_filter_pid_set_property(ctx->opid_info, GF_PROP_PID_MIME, &PROP_STRING("application/x-nhnt") );
124 :
125 1 : if (!ctx->exporter) {
126 0 : name = gf_file_ext_start(fileName);
127 0 : if (name) name[0] = 0;
128 : strcat(fileName, ".info");
129 0 : if (gfio) {
130 0 : res_name = (char *) gf_fileio_factory(gfio, gf_file_basename(fileName) );
131 : } else {
132 : res_name = fileName;
133 : }
134 0 : gf_filter_pid_set_property(ctx->opid_info, GF_PROP_PID_OUTPATH, &PROP_STRING(res_name) );
135 :
136 0 : GF_Filter *o_info = gf_filter_connect_destination(filter, res_name, &e);
137 0 : if (o_info) gf_filter_set_source(o_info, filter, NULL);
138 : }
139 :
140 : }
141 1 : ctx->configure_side_streams = GF_FALSE;
142 1 : return GF_OK;
143 : }
144 :
145 1 : GF_Err nhntdump_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
146 : {
147 : u32 cid, chan, sr, bps, w, h;
148 : const char *name;
149 : const GF_PropertyValue *p;
150 1 : GF_NHNTDumpCtx *ctx = gf_filter_get_udta(filter);
151 :
152 1 : if (is_remove) {
153 0 : ctx->ipid = NULL;
154 0 : if (ctx->opid_nhnt) {
155 0 : gf_filter_pid_remove(ctx->opid_nhnt);
156 0 : ctx->opid_nhnt = NULL;
157 : }
158 0 : if (ctx->opid_mdia) {
159 0 : gf_filter_pid_remove(ctx->opid_mdia);
160 0 : ctx->opid_mdia = NULL;
161 : }
162 0 : if (ctx->opid_info) {
163 0 : gf_filter_pid_remove(ctx->opid_info);
164 0 : ctx->opid_info = NULL;
165 : }
166 : return GF_OK;
167 : }
168 1 : if (! gf_filter_pid_check_caps(pid))
169 : return GF_NOT_SUPPORTED;
170 :
171 1 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID);
172 1 : if (!p) return GF_NOT_SUPPORTED;
173 1 : cid = p->value.uint;
174 :
175 1 : if (ctx->codecid == cid) {
176 : return GF_OK;
177 : }
178 1 : ctx->codecid = cid;
179 1 : ctx->configure_side_streams = GF_TRUE;
180 :
181 1 : if (ctx->codecid<GF_CODECID_LAST_MPEG4_MAPPING) ctx->oti = ctx->codecid;
182 : else {
183 0 : ctx->oti = gf_codecid_oti(ctx->codecid);
184 : }
185 1 : if (!ctx->oti) {
186 0 : GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("CodecID %s has no mapping to MPEG-4 systems, cannot use NHNT. Use NHML instead\n", gf_4cc_to_str(cid) ));
187 : return GF_NOT_SUPPORTED;
188 : }
189 :
190 1 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE);
191 1 : ctx->streamtype = p ? p->value.uint : GF_STREAM_UNKNOWN;
192 :
193 1 : if (!ctx->opid_nhnt)
194 1 : ctx->opid_nhnt = gf_filter_pid_new(filter);
195 :
196 1 : ctx->ipid = pid;
197 :
198 :
199 1 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE);
200 1 : sr = p ? p->value.uint : 0;
201 1 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS);
202 1 : chan = p ? p->value.uint : 0;
203 1 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_FORMAT);
204 1 : bps = p ? gf_audio_fmt_bit_depth(p->value.uint) : 16;
205 1 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH);
206 1 : w = p ? p->value.uint : 0;
207 1 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT);
208 1 : h = p ? p->value.uint : 0;
209 :
210 :
211 1 : name = gf_codecid_name(ctx->codecid);
212 1 : if (ctx->exporter) {
213 1 : if (w && h) {
214 1 : GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Exporting %s - Size %dx%d\n", name, w, h));
215 0 : } else if (sr && chan) {
216 0 : GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Exporting %s - SampleRate %d %d channels %d bits per sample\n", name, sr, chan, bps));
217 : } else {
218 0 : GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Exporting %s\n", name));
219 : }
220 : }
221 :
222 1 : gf_filter_pid_set_property(ctx->opid_nhnt, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) );
223 1 : gf_filter_pid_set_property(ctx->opid_nhnt, GF_PROP_PID_FILE_EXT, &PROP_STRING("nhnt") );
224 1 : gf_filter_pid_set_property(ctx->opid_nhnt, GF_PROP_PID_MIME, &PROP_STRING("application/x-nhnt") );
225 :
226 :
227 1 : ctx->first = GF_TRUE;
228 :
229 1 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_DURATION);
230 1 : if (p) ctx->duration = p->value.lfrac;
231 1 : gf_filter_pid_set_framing_mode(pid, GF_TRUE);
232 1 : return GF_OK;
233 : }
234 :
235 175 : GF_Err nhntdump_process(GF_Filter *filter)
236 : {
237 175 : GF_NHNTDumpCtx *ctx = gf_filter_get_udta(filter);
238 : GF_FilterPacket *pck, *dst_pck;
239 : u8 *output;
240 : u32 size, pck_size;
241 : u64 dts, cts;
242 :
243 175 : if (ctx->configure_side_streams) {
244 1 : return nhntdump_config_side_streams(filter, ctx);
245 : }
246 :
247 174 : pck = gf_filter_pid_get_packet(ctx->ipid);
248 174 : if (!pck) {
249 1 : if (gf_filter_pid_is_eos(ctx->ipid)) {
250 1 : gf_filter_pid_set_eos(ctx->opid_nhnt);
251 1 : gf_filter_pid_set_eos(ctx->opid_mdia);
252 1 : if (ctx->opid_info) gf_filter_pid_set_eos(ctx->opid_info);
253 : return GF_EOS;
254 : }
255 : return GF_OK;
256 : }
257 :
258 173 : if (ctx->first) {
259 : u32 nhnt_hdr_size = 4+1+1+1+2+3+4+4+4;
260 : const GF_PropertyValue *p;
261 :
262 1 : dst_pck = gf_filter_pck_new_alloc(ctx->opid_nhnt, nhnt_hdr_size, &output);
263 1 : if (!dst_pck) return GF_OUT_OF_MEM;
264 :
265 1 : if (!ctx->bs) ctx->bs = gf_bs_new(output, nhnt_hdr_size, GF_BITSTREAM_WRITE);
266 0 : else gf_bs_reassign_buffer(ctx->bs, output, nhnt_hdr_size);
267 :
268 : /*write header*/
269 : /*'NHnt' format*/
270 1 : gf_bs_write_data(ctx->bs, ctx->large ? "NHnl" : "NHnt", 4);
271 : /*version 1*/
272 1 : gf_bs_write_u8(ctx->bs, 0);
273 : /*streamType*/
274 1 : gf_bs_write_u8(ctx->bs, ctx->streamtype);
275 : /*OTI*/
276 1 : gf_bs_write_u8(ctx->bs, ctx->oti);
277 : /*reserved*/
278 1 : gf_bs_write_u16(ctx->bs, 0);
279 : /*bufferDB*/
280 : //p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_DB_SIZE);
281 1 : gf_bs_write_u24(ctx->bs, 0);
282 : /*avg BitRate*/
283 1 : p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_BITRATE);
284 1 : gf_bs_write_u32(ctx->bs, p ? p->value.uint : 0);
285 : /*max bitrate*/
286 : //p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_MAX_RATE);
287 1 : gf_bs_write_u32(ctx->bs, 0);
288 : /*timescale*/
289 1 : p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_TIMESCALE);
290 1 : gf_bs_write_u32(ctx->bs, p ? p->value.uint : 1000);
291 :
292 1 : gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_FALSE);
293 1 : gf_filter_pck_send(dst_pck);
294 :
295 1 : if (ctx->opid_info) {
296 1 : dst_pck = gf_filter_pck_new_shared(ctx->opid_info, ctx->dcfg, ctx->dcfg_size, NULL);
297 1 : if (dst_pck) {
298 1 : gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE);
299 1 : gf_filter_pck_set_readonly(dst_pck);
300 1 : gf_filter_pck_send(dst_pck);
301 : }
302 : }
303 : }
304 :
305 : //get media data
306 173 : gf_filter_pck_get_data(pck, &pck_size);
307 :
308 : //nhnt data size
309 173 : size = 3 + 1 + 3*(ctx->large ? 8 : 4);
310 173 : dst_pck = gf_filter_pck_new_alloc(ctx->opid_nhnt, size, &output);
311 173 : if (!dst_pck) return GF_OUT_OF_MEM;
312 :
313 : //send nhnt data
314 173 : gf_bs_reassign_buffer(ctx->bs, output, size);
315 :
316 : /*dump nhnt info*/
317 173 : gf_bs_write_u24(ctx->bs, pck_size);
318 173 : gf_bs_write_int(ctx->bs, gf_filter_pck_get_sap(pck) ? 1 : 0, 1);
319 : /*AU start & end flag always true*/
320 173 : gf_bs_write_int(ctx->bs, 1, 1);
321 173 : gf_bs_write_int(ctx->bs, 1, 1);
322 : /*reserved*/
323 173 : gf_bs_write_int(ctx->bs, 0, 3);
324 173 : gf_bs_write_int(ctx->bs, gf_filter_pck_get_sap(pck) ? 0 : 1, 2);
325 :
326 173 : dts = gf_filter_pck_get_dts(pck);
327 173 : cts = gf_filter_pck_get_cts(pck);
328 173 : if (ctx->large) {
329 0 : gf_bs_write_u64(ctx->bs, ctx->mdia_pos);
330 0 : gf_bs_write_u64(ctx->bs, cts);
331 0 : gf_bs_write_u64(ctx->bs, dts);
332 : } else {
333 173 : gf_bs_write_u32(ctx->bs, (u32) ctx->mdia_pos);
334 173 : gf_bs_write_u32(ctx->bs, (u32) cts);
335 173 : gf_bs_write_u32(ctx->bs, (u32) dts);
336 : }
337 173 : ctx->mdia_pos += pck_size;
338 173 : gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_FALSE);
339 173 : gf_filter_pck_send(dst_pck);
340 :
341 : //send the complete data packet
342 173 : dst_pck = gf_filter_pck_new_ref(ctx->opid_mdia, 0, pck_size, pck);
343 173 : if (!dst_pck) return GF_OUT_OF_MEM;
344 :
345 173 : gf_filter_pck_merge_properties(pck, dst_pck);
346 : //keep byte offset ?
347 : // gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO);
348 :
349 173 : gf_filter_pck_set_framing(dst_pck, ctx->first, GF_FALSE);
350 173 : gf_filter_pck_send(dst_pck);
351 :
352 173 : ctx->first = GF_FALSE;
353 :
354 173 : if (ctx->exporter) {
355 173 : u32 timescale = gf_filter_pck_get_timescale(pck);
356 173 : u64 ts = gf_filter_pck_get_cts(pck);
357 173 : gf_set_progress("Exporting", ts*ctx->duration.den, ctx->duration.num*timescale);
358 : }
359 :
360 173 : gf_filter_pid_drop_packet(ctx->ipid);
361 :
362 173 : return GF_OK;
363 : }
364 :
365 1 : static void nhntdump_finalize(GF_Filter *filter)
366 : {
367 1 : GF_NHNTDumpCtx *ctx = gf_filter_get_udta(filter);
368 1 : if (ctx->bs) gf_bs_del(ctx->bs);
369 1 : }
370 :
371 : static const GF_FilterCapability NHNTDumpCaps[] =
372 : {
373 : CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
374 : CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE),
375 : CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE),
376 : CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
377 : CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "nhnt"),
378 : CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "application/x-nhnt"),
379 : };
380 :
381 : #define OFFS(_n) #_n, offsetof(GF_NHNTDumpCtx, _n)
382 : static const GF_FilterArgs NHNTDumpArgs[] =
383 : {
384 : { OFFS(exporter), "compatibility with old exporter, displays export results", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
385 : { OFFS(large), "use large file mode", GF_PROP_BOOL, "false", NULL, 0},
386 : {0}
387 : };
388 :
389 :
390 : GF_FilterRegister NHNTDumpRegister = {
391 : .name = "nhntw",
392 : GF_FS_SET_DESCRIPTION("NHNT writer")
393 : GF_FS_SET_HELP("This filter converts a single stream to an NHNT output file.\n"
394 : "NHNT documentation is available at https://wiki.gpac.io/NHNT-Format\n")
395 : .private_size = sizeof(GF_NHNTDumpCtx),
396 : .args = NHNTDumpArgs,
397 : .finalize = nhntdump_finalize,
398 : SETCAPS(NHNTDumpCaps),
399 : .configure_pid = nhntdump_configure_pid,
400 : .process = nhntdump_process
401 : };
402 :
403 2877 : const GF_FilterRegister *nhntdump_register(GF_FilterSession *session)
404 : {
405 2877 : return &NHNTDumpRegister;
406 : }
407 :
|