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_VOBSUB
33 :
34 : #include <gpac/internal/vobsub.h>
35 :
36 :
37 : typedef struct
38 : {
39 : //opts
40 : Bool blankframe;
41 :
42 : GF_FilterPid *idx_pid, *sub_pid;
43 : GF_Filter *sub_filter;
44 : GF_List *opids;
45 : Bool first;
46 :
47 : u32 idx_file_crc;
48 : FILE *mdia;
49 :
50 : Double start_range;
51 : u64 first_dts;
52 :
53 : u32 nb_playing;
54 : GF_Fraction64 duration;
55 : Bool in_seek;
56 :
57 : Bool initial_play_done;
58 : Bool idx_parsed;
59 :
60 : u32 timescale;
61 :
62 : vobsub_file *vobsub;
63 : } GF_VOBSubDmxCtx;
64 :
65 :
66 4 : GF_Err vobsubdmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
67 : {
68 : u32 crc;
69 : const GF_PropertyValue *p;
70 4 : GF_VOBSubDmxCtx *ctx = gf_filter_get_udta(filter);
71 :
72 4 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_URL);
73 4 : if (!p) {
74 0 : gf_filter_setup_failure(filter, GF_URL_ERROR);
75 0 : return GF_EOS;
76 : }
77 :
78 4 : if (is_remove) {
79 : u32 i, count;
80 0 : ctx->idx_pid = NULL;
81 0 : ctx->sub_pid = NULL;
82 0 : count = gf_filter_get_opid_count(filter);
83 0 : for (i=0; i<count; i++) {
84 0 : gf_filter_pid_remove( gf_filter_get_opid(filter, i) );
85 : }
86 : return GF_OK;
87 : }
88 4 : if (! gf_filter_pid_check_caps(pid))
89 : return GF_NOT_SUPPORTED;
90 :
91 4 : if (!ctx->idx_pid) {
92 2 : ctx->idx_pid = pid;
93 2 : } else if (ctx->idx_pid != pid) {
94 2 : ctx->sub_pid = pid;
95 : }
96 :
97 4 : gf_filter_pid_set_framing_mode(pid, GF_TRUE);
98 :
99 4 : if (ctx->idx_pid==pid) {
100 : GF_Err e;
101 : Bool use_gfio = GF_FALSE;
102 : char sURL[GF_MAX_PATH], *ext;
103 2 : crc = gf_crc_32(p->value.string, (u32) strlen(p->value.string));
104 2 : if (ctx->idx_file_crc == crc) return GF_OK;
105 2 : ctx->idx_file_crc = crc;
106 :
107 2 : if (ctx->sub_filter) {
108 0 : gf_filter_remove_src(filter, ctx->sub_filter);
109 0 : ctx->sub_filter = NULL;
110 : }
111 2 : if (!strncmp(p->value.string, "gfio://", 7)) {
112 : use_gfio = GF_TRUE;
113 0 : strcpy(sURL, gf_fileio_translate_url(p->value.string) );
114 : } else {
115 : strcpy(sURL, p->value.string);
116 : }
117 2 : ext = gf_file_ext_start(sURL);
118 2 : if (ext) ext[0] = 0;
119 : strcat(sURL, ".sub");
120 2 : if (use_gfio) {
121 0 : GF_FileIO *gfio = gf_fileio_from_url(p->value.string);
122 0 : char *base = gf_file_basename(sURL);
123 0 : const char *new_url = gf_fileio_factory(gfio, base ? base : sURL);
124 0 : if (new_url) {
125 : strcpy(sURL, new_url);
126 : }
127 : }
128 :
129 2 : ctx->sub_filter = gf_filter_connect_source(filter, sURL, NULL, GF_TRUE, &e);
130 2 : if (e) return e;
131 2 : if (ctx->mdia) gf_fclose(ctx->mdia);
132 2 : ctx->mdia = NULL;
133 2 : gf_filter_disable_probe(ctx->sub_filter);
134 :
135 2 : ctx->first = GF_TRUE;
136 : }
137 :
138 : return GF_OK;
139 : }
140 :
141 36 : static Bool vobsubdmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt)
142 : {
143 36 : GF_VOBSubDmxCtx *ctx = gf_filter_get_udta(filter);
144 :
145 36 : switch (evt->base.type) {
146 2 : case GF_FEVT_PLAY:
147 2 : if (ctx->nb_playing) return GF_TRUE;
148 2 : if (ctx->vobsub && (ctx->start_range != evt->play.start_range)) {
149 : u32 i;
150 0 : for (i=0; i<ctx->vobsub->num_langs; i++) {
151 0 : ctx->vobsub->langs[i].last_dts = 0;
152 0 : ctx->vobsub->langs[i].current = 0;
153 0 : ctx->vobsub->langs[i].is_seek = GF_TRUE;
154 : }
155 : }
156 2 : ctx->start_range = evt->play.start_range;
157 2 : ctx->nb_playing++;
158 :
159 : //cancel event
160 2 : return GF_TRUE;
161 :
162 0 : case GF_FEVT_STOP:
163 0 : ctx->nb_playing--;
164 : //don't cancel event
165 0 : return GF_FALSE;
166 :
167 : case GF_FEVT_SET_SPEED:
168 : //cancel event
169 : return GF_TRUE;
170 : default:
171 : break;
172 : }
173 : //by default don't cancel event - to rework once we have downloading in place
174 34 : return GF_FALSE;
175 : }
176 :
177 2 : GF_Err vobsubdmx_parse_idx(GF_Filter *filter, GF_VOBSubDmxCtx *ctx)
178 : {
179 : FILE *file = NULL;
180 : int version;
181 : GF_Err err = GF_OK;
182 : const GF_PropertyValue *p;
183 :
184 2 : if (!ctx->idx_pid) return GF_SERVICE_ERROR;
185 :
186 2 : p = gf_filter_pid_get_property(ctx->idx_pid, GF_PROP_PID_FILEPATH);
187 2 : if (!p) {
188 0 : gf_filter_setup_failure(filter, GF_URL_ERROR);
189 0 : return GF_EOS;
190 : }
191 :
192 2 : file = gf_fopen(p->value.string, "rb");
193 2 : if (!file) {
194 0 : gf_filter_setup_failure(filter, GF_URL_ERROR);
195 0 : return GF_EOS;
196 : }
197 :
198 2 : GF_SAFEALLOC(ctx->vobsub, vobsub_file);
199 2 : if (!ctx->vobsub) {
200 0 : gf_fclose(file);
201 0 : gf_filter_setup_failure(filter, GF_URL_ERROR);
202 0 : return GF_EOS;
203 : }
204 :
205 2 : err = vobsub_read_idx(file, ctx->vobsub, &version);
206 2 : gf_fclose(file);
207 2 : if (err) {
208 0 : gf_filter_setup_failure(filter, GF_URL_ERROR);
209 0 : return GF_EOS;
210 : }
211 2 : if (!gf_filter_get_opid_count(filter) ) {
212 : u32 i;
213 2 : ctx->duration.num = 0;
214 :
215 4 : for (i=0; i<ctx->vobsub->num_langs; i++) {
216 2 : vobsub_pos *pos = (vobsub_pos*)gf_list_last(ctx->vobsub->langs[i].subpos);
217 2 : if ((u64) ctx->duration.num < pos->start*90)
218 2 : ctx->duration.num = (s64) (pos->start*90);
219 : }
220 2 : ctx->duration.den = 90000;
221 :
222 6 : for (i=0; i<ctx->vobsub->num_langs; i++) {
223 2 : GF_FilterPid *opid = gf_filter_pid_new(filter);
224 :
225 : //copy properties from idx pid
226 2 : gf_filter_pid_copy_properties(opid, ctx->idx_pid);
227 :
228 2 : gf_filter_pid_set_property(opid, GF_PROP_PID_ID, &PROP_UINT(i+1) );
229 2 : gf_filter_pid_set_property(opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_TEXT) );
230 2 : gf_filter_pid_set_property(opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_SUBPIC) );
231 2 : gf_filter_pid_set_property(opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(90000) );
232 2 : gf_filter_pid_set_property(opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA((char*)&ctx->vobsub->palette[0][0], sizeof(ctx->vobsub->palette)) );
233 :
234 :
235 2 : gf_filter_pid_set_property(opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->vobsub->width) );
236 2 : gf_filter_pid_set_property(opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->vobsub->height) );
237 2 : gf_filter_pid_set_property(opid, GF_PROP_PID_LANGUAGE, &PROP_STRING(ctx->vobsub->langs[i].name) );
238 2 : gf_filter_pid_set_property(opid, GF_PROP_PID_DURATION, &PROP_FRAC64(ctx->duration) );
239 2 : gf_filter_pid_set_property(opid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_FASTFORWARD ) );
240 :
241 2 : gf_filter_pid_set_udta(opid, &ctx->vobsub->langs[i]);
242 : }
243 : }
244 : return GF_OK;
245 : }
246 :
247 32 : static GF_Err vobsubdmx_send_stream(GF_VOBSubDmxCtx *ctx, GF_FilterPid *pid)
248 : {
249 : static const u8 null_subpic[] = { 0x00, 0x09, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0xFF };
250 : GF_List *subpic;
251 : vobsub_lang *vslang;
252 : u32 c, count;
253 : GF_FilterPacket *dst_pck;
254 :
255 : unsigned char buf[0x800];
256 :
257 32 : vslang = gf_filter_pid_get_udta(pid);
258 32 : subpic = vslang->subpos;
259 :
260 32 : count = gf_list_count(subpic);
261 :
262 32 : c = vslang->current;
263 :
264 32 : for (; c<count; c++) {
265 : u32 i, left, size, psize, dsize, hsize, duration;
266 : u8 *packet;
267 30 : vobsub_pos *pos = (vobsub_pos*)gf_list_get(subpic, c);
268 :
269 30 : if (vslang->is_seek) {
270 0 : if (pos->start*90 < ctx->start_range * 90000) {
271 0 : continue;
272 : }
273 0 : vslang->is_seek = GF_FALSE;
274 : }
275 :
276 30 : gf_fseek(ctx->mdia, pos->filepos, SEEK_SET);
277 30 : if (gf_ftell(ctx->mdia) != pos->filepos) {
278 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[VobSub] Could not seek in file\n"));
279 30 : return GF_IO_ERR;
280 : }
281 :
282 30 : if (!gf_fread(buf, sizeof(buf), ctx->mdia)) {
283 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[VobSub] Could not read from file\n"));
284 : return GF_IO_ERR;
285 : }
286 :
287 60 : if (*(u32*)&buf[0x00] != 0xba010000 ||
288 90 : (buf[14] || buf[15] || (buf[16]!=0x01) || (buf[17]!=0xbd)) ||
289 60 : !(buf[0x15] & 0x80) ||
290 60 : (buf[0x17] & 0xf0) != 0x20 ||
291 30 : (buf[buf[0x16] + 0x17] & 0xe0) != 0x20)
292 : {
293 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[VobSub] Corrupted data found in file (1)\n"));
294 : return GF_CORRUPTED_DATA;
295 : }
296 :
297 30 : psize = (buf[buf[0x16] + 0x18] << 8) + buf[buf[0x16] + 0x19];
298 30 : dsize = (buf[buf[0x16] + 0x1a] << 8) + buf[buf[0x16] + 0x1b];
299 :
300 30 : if (ctx->blankframe && !c && (pos->start>0)) {
301 0 : dst_pck = gf_filter_pck_new_alloc(pid, sizeof(null_subpic), &packet);
302 0 : if (!dst_pck) return GF_OUT_OF_MEM;
303 :
304 0 : memcpy(packet, null_subpic, sizeof(null_subpic));
305 0 : gf_filter_pck_set_cts(dst_pck, 0);
306 0 : gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1);
307 0 : gf_filter_pck_set_duration(dst_pck, (u32) (pos->start * 90) );
308 0 : gf_filter_pck_send(dst_pck);
309 : }
310 :
311 30 : dst_pck = gf_filter_pck_new_alloc(pid, psize, &packet);
312 30 : if (!dst_pck) return GF_OUT_OF_MEM;
313 :
314 48 : for (i = 0, left = psize; i < psize; i += size, left -= size) {
315 48 : hsize = 0x18 + buf[0x16];
316 48 : size = MIN(left, 0x800 - hsize);
317 48 : memcpy(packet + i, buf + hsize, size);
318 :
319 48 : if (size != left) {
320 18 : while (gf_fread(buf, sizeof(buf), ctx->mdia)) {
321 18 : if (buf[buf[0x16] + 0x17] == (vslang->idx | 0x20)) {
322 : break;
323 : }
324 : }
325 : }
326 : }
327 :
328 30 : if (i != psize || left > 0) {
329 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[VobSub] Corrupted data found in file (2)\n"));
330 : return GF_CORRUPTED_DATA;
331 : }
332 :
333 30 : if (vobsub_get_subpic_duration(packet, psize, dsize, &duration) != GF_OK) {
334 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[VobSub] Corrupted data found in file (3)\n"));
335 : return GF_CORRUPTED_DATA;
336 : }
337 :
338 30 : gf_filter_pck_set_cts(dst_pck, pos->start * 90);
339 30 : gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1);
340 30 : gf_filter_pck_set_duration(dst_pck, duration);
341 :
342 30 : if (vslang->last_dts && (vslang->last_dts >= pos->start * 90)) {
343 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[VobSub] Out of order timestamps in vobsub file\n"));
344 : }
345 30 : gf_filter_pck_send(dst_pck);
346 30 : vslang->last_dts = pos->start * 90;
347 :
348 30 : vslang->current++;
349 30 : if (gf_filter_pid_would_block(pid)) return GF_OK;
350 : }
351 : return GF_EOS;
352 : }
353 :
354 52 : GF_Err vobsubdmx_process(GF_Filter *filter)
355 : {
356 52 : GF_VOBSubDmxCtx *ctx = gf_filter_get_udta(filter);
357 : GF_FilterPacket *pck;
358 : u32 pkt_size, i, count, nb_eos;
359 : Bool start, end;
360 :
361 52 : if (!ctx->idx_parsed) {
362 : GF_Err e;
363 2 : pck = gf_filter_pid_get_packet(ctx->idx_pid);
364 2 : if (!pck) return GF_OK;
365 2 : gf_filter_pck_get_framing(pck, &start, &end);
366 : //for now we only work with complete files
367 : assert(end);
368 :
369 2 : e = vobsubdmx_parse_idx(filter, ctx);
370 2 : ctx->idx_parsed = GF_TRUE;
371 2 : gf_filter_pid_drop_packet(ctx->idx_pid);
372 2 : if (e) {
373 0 : gf_filter_setup_failure(filter, e);
374 0 : return e;
375 : }
376 : }
377 :
378 52 : if (!ctx->nb_playing) return GF_OK;
379 :
380 48 : if (!ctx->mdia) {
381 : const GF_PropertyValue *p;
382 2 : if (!ctx->sub_pid) return GF_OK;
383 2 : p = gf_filter_pid_get_property(ctx->sub_pid, GF_PROP_PID_FILEPATH);
384 2 : if (!p) {
385 0 : gf_filter_setup_failure(filter, GF_URL_ERROR);
386 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[VobSub] Cannot open sub file\n"));
387 : return GF_EOS;
388 : }
389 2 : ctx->mdia = gf_fopen(p->value.string, "rb");
390 : }
391 :
392 48 : pck = gf_filter_pid_get_packet(ctx->sub_pid);
393 48 : if (!pck) {
394 16 : if (gf_filter_pid_is_eos(ctx->sub_pid)) return GF_EOS;
395 16 : return GF_OK;
396 : }
397 :
398 32 : /*data =*/ gf_filter_pck_get_data(pck, &pkt_size);
399 32 : gf_filter_pck_get_framing(pck, &start, &end);
400 : //for now we only work with complete files
401 : assert(end);
402 :
403 : nb_eos = 0;
404 32 : count = gf_filter_get_opid_count(filter);
405 64 : for (i=0; i<count; i++) {
406 32 : GF_FilterPid *opid = gf_filter_get_opid(filter, i);
407 32 : GF_Err e = vobsubdmx_send_stream(ctx, opid);
408 32 : if (e==GF_EOS) {
409 2 : nb_eos++;
410 2 : gf_filter_pid_set_eos(opid);
411 : }
412 : }
413 :
414 32 : if (nb_eos==count) {
415 : //only drop packet once we are done
416 2 : gf_filter_pid_drop_packet(ctx->sub_pid);
417 2 : return GF_EOS;
418 : }
419 : return GF_OK;
420 : }
421 :
422 2 : static void vobsubdmx_finalize(GF_Filter *filter)
423 : {
424 2 : GF_VOBSubDmxCtx *ctx = gf_filter_get_udta(filter);
425 2 : if (ctx->vobsub) vobsub_free(ctx->vobsub);
426 2 : if (ctx->mdia) gf_fclose(ctx->mdia);
427 2 : }
428 :
429 3065 : static const char * vobsubdmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score)
430 : {
431 3065 : if (!strncmp(data, "# VobSub", 8)) {
432 2 : *score = GF_FPROBE_SUPPORTED;
433 2 : return "text/vobsub";
434 : }
435 : return NULL;
436 : }
437 :
438 : #define OFFS(_n) #_n, offsetof(GF_VOBSubDmxCtx, _n)
439 : static const GF_FilterArgs GF_VOBSubDmxArgs[] =
440 : {
441 : { OFFS(blankframe), "force inserting a blank frame if first subpic is not at 0", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED},
442 : {0}
443 : };
444 :
445 :
446 : static const GF_FilterCapability VOBSubDmxCaps[] =
447 : {
448 : CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
449 : CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "idx|sub"),
450 : CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "text/vobsub"),
451 : CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT),
452 : CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_SUBPIC),
453 : };
454 :
455 :
456 : GF_FilterRegister VOBSubDmxRegister = {
457 : .name = "vobsubdmx",
458 : GF_FS_SET_DESCRIPTION("VobSub demuxer")
459 : GF_FS_SET_HELP("This filter demultiplexes VobSub files/data to produce media PIDs and frames.")
460 : .private_size = sizeof(GF_VOBSubDmxCtx),
461 : .max_extra_pids = 1,
462 : .args = GF_VOBSubDmxArgs,
463 : .finalize = vobsubdmx_finalize,
464 : SETCAPS(VOBSubDmxCaps),
465 : .configure_pid = vobsubdmx_configure_pid,
466 : .process = vobsubdmx_process,
467 : .probe_data = vobsubdmx_probe_data,
468 : .process_event = vobsubdmx_process_event
469 : };
470 :
471 : #endif
472 :
473 2877 : const GF_FilterRegister *vobsubdmx_register(GF_FilterSession *session)
474 : {
475 : #ifndef GPAC_DISABLE_VOBSUB
476 2877 : return &VOBSubDmxRegister;
477 : #else
478 : return NULL;
479 : #endif
480 :
481 : }
482 :
|