File: vbi_decoder.c

package info (click to toggle)
zapping 0.10~cvs6-2
  • links: PTS
  • area: main
  • in suites: lenny
  • size: 9,856 kB
  • ctags: 11,841
  • sloc: ansic: 111,154; asm: 11,770; sh: 9,812; xml: 2,742; makefile: 1,283; perl: 488
file content (862 lines) | stat: -rw-r--r-- 22,117 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
/*
 *  libzvbi - VBI decoding library
 *
 *  Copyright (C) 2000, 2001, 2002 Michael H. Schimek
 *  Copyright (C) 2000, 2001 I�aki Garc�a Etxebarria
 *
 *  Based on AleVT 1.5.1
 *  Copyright (C) 1998, 1999 Edgar Toernig <froese@gmx.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* $Id: vbi_decoder.c,v 1.21 2005/10/22 15:47:08 mschimek Exp $ */

#include <assert.h>
#include <stdlib.h>		/* malloc() */
#include "vbi_decoder-priv.h"
#include "misc.h"
#include "vps.h"
#ifndef ZAPPING8
#  include "wss.h"
#endif

/**
 * @param vbi VBI decoder allocated with vbi3_decoder_new().
 * @param nk Identifies the network transmitting the page.
 * @param pgno Teletext page number or Closed Caption channel.
 * @param subno Teletext subpage number, can be @c VBI3_ANY_SUBNO
 *   (most recently received subpage, if any).
 * @param format_options Array of pairs of a vbi3_format_option and value,
 *   terminated by a @c 0 option. See vbi3_decoder_get_page().
 *
 * Allocates a new vbi3_page, formatted from a cached Teletext page.
 *
 * @returns
 * vbi3_page which must be freed with vbi3_page_delete() when done.
 * @c NULL on error.
 */
vbi3_page *
vbi3_decoder_get_page_va_list	(vbi3_decoder *		vbi,
				 const vbi3_network *	nk,
				 vbi3_pgno		pgno,
				 vbi3_subno		subno,
				 va_list		format_options)
{
	if (pgno < 0x100)
		return vbi3_caption_decoder_get_page_va_list
			(&vbi->cc, pgno, format_options);
	else
		return vbi3_teletext_decoder_get_page_va_list
			(&vbi->vt, nk, pgno, subno, format_options);
}

/**
 * @param vbi VBI decoder allocated with vbi3_decoder_new().
 * @param nk Identifies the network transmitting the page.
 * @param pgno Teletext page number or Closed Caption channel.
 * @param subno Teletext subpage number, can be @c VBI3_ANY_SUBNO
 *   (most recently received subpage, if any).
 * @param ... Array of pairs of a vbi3_format_option and value,
 *   terminated by a @c 0 option.
 *
 * Allocates a new vbi3_page, formatted from a cached Teletext
 * or Closed Caption page.
 *
 * Example:
 *
 * @code
 * vbi3_page *pg;
 *
 * pg = vbi3_decoder_get_page (vbi, NULL, 0x100, VBI3_ANY_SUBNO,
 *			      VBI3_NAVIGATION, TRUE,
 *			      VBI3_WST_LEVEL, VBI3_LEVEL_2p5,
 *			      0);
 * @endcode
 *
 * @returns
 * vbi3_page which must be freed with vbi3_page_delete() when done.
 * @c NULL on error.
 */
vbi3_page *
vbi3_decoder_get_page		(vbi3_decoder *		vbi,
				 const vbi3_network *	nk,
				 vbi3_pgno		pgno,
				 vbi3_subno		subno,
				 ...)
{
	vbi3_page *pg;
	va_list format_options;

	va_start (format_options, subno);

	if (pgno < 0x100)
		pg = vbi3_caption_decoder_get_page_va_list
			(&vbi->cc, pgno, format_options);
	else
		pg = vbi3_teletext_decoder_get_page_va_list
			(&vbi->vt, nk, pgno, subno, format_options);

	va_end (format_options);

	return pg;
}

#ifndef ZAPPING8

/**
 * @param vbi VBI decoder allocated with vbi3_decoder_new().
 * @param pid Program ID will be stored here.
 * @param channel Logical channel transmitting the ID.
 *
 * Returns the most recently on a logical channel received program ID.
 *
 * Sources can be VBI3_SLICED_TELETEXT_B and VBI3_SLICED_VPS (EN 300 231 PDC),
 * and VBI3_SLICED_CAPTION_525 (EIA 608 XDS) data.
 */
void
vbi3_decoder_get_program_id	(vbi3_decoder *		vbi,
				 vbi3_program_id *	pid,
				 vbi3_pid_channel	channel)
{
	cache_network *cn;

	assert (NULL != vbi);
	assert (NULL != pid);

	cn = vbi->vt.network;

	assert ((unsigned int) channel < N_ELEMENTS (cn->program_id));

	*pid = cn->program_id[channel];
}

/**
 * @param vbi VBI decoder allocated with vbi3_decoder_new().
 * @param ar Aspect ratio information will be stored here.
 *
 * Returns the most recently received aspect ratio information.
 *
 * Sources can be VBI3_SLICED_WSS_625 (EN 300 294), VBI3_SLICED_WSS_CPR1204
 * (EIA-J CPR-1204) and VBI3_SLICED_CAPTION_525 (EIA 608 XDS) data.
 */
void
vbi3_decoder_get_aspect_ratio	(vbi3_decoder *		vbi,
				 vbi3_aspect_ratio *	ar)
{
	assert (NULL != vbi);
	assert (NULL != ar);

	*ar = vbi->vt.network->aspect_ratio;
}

#endif /* !ZAPPING8 */

vbi3_teletext_decoder *
vbi3_decoder_cast_to_teletext_decoder
				(vbi3_decoder *		vbi)
{
	assert (NULL != vbi);

	return &vbi->vt;
}

vbi3_caption_decoder *
vbi3_decoder_cast_to_caption_decoder
				(vbi3_decoder *		vbi)
{
	assert (NULL != vbi);

	return &vbi->cc;
}

/**
 * @internal
 * @param vbi VBI decoder allocated with vbi3_decoder_new().
 * @param cn New network, can be @c NULL if 0.0 != time.
 * @param time Deferred reset when time is greater than
 *   vbi3_decoder_feed() timestamp. Pass a negative number to
 *   cancel a deferred reset, 0.0 to reset immediately.
 *
 * Internal reset function.
 */
static void
internal_reset			(vbi3_decoder *		vbi,
				 cache_network *	cn,
				 double			time)
{
	vbi3_event e;

	if (time <= 0.0 /* reset now or cancel deferred reset */
	    || time > vbi->reset_time)
		vbi->reset_time = time;

	vbi->teletext_reset (&vbi->vt, cn, time);
	vbi->caption_reset (&vbi->cc, cn, time);

	if (0.0 == time /* reset now */) {
		e.type		= VBI3_EVENT_RESET;
		e.network	= &vbi->vt.network->network;
		e.timestamp	= vbi->timestamp;

		_vbi3_event_handler_list_send (&vbi->handlers, &e);
	}
}

static void
cni_change			(vbi3_decoder *		vbi,
				 vbi3_cni_type		type,
				 unsigned int		cni)
{
	cache_network *cn;
	double timeout;

	cn = vbi->vt.network;

	/* Timeout is variable because VPS and 8/30 repeat at
	   different rates. */
	timeout = 0.0;

	if (VBI3_CNI_TYPE_VPS != type
	    && 0 != cn->network.cni_vps
	    && (cn->network.cni_vps
		!= vbi3_convert_cni (VBI3_CNI_TYPE_VPS, type, cni))) {
		/* We cannot say with certainty if cni and cni_vps belong
		   to the same network. If 0 == vbi3_convert_cni() we cannot
		   convert, otherwise CNIs mismatch because the channel
		   changed, the network transmits wrong CNIs or the
		   conversion is incorrect.

		   After n seconds, if we cannot confirm the cni_vps or
		   receive a new one, we assume there was a channel change
		   and the new network does not transmit a cni_vps. */
		cn->confirm_cni_vps = cn->network.cni_vps;
		timeout = vbi->vt.cni_vps_timeout;
	}

	if (VBI3_CNI_TYPE_8301 != type
	    && 0 != cn->network.cni_8301
	    && (cn->network.cni_8301
		!= vbi3_convert_cni (VBI3_CNI_TYPE_8301, type, cni))) {
		cn->confirm_cni_8301 = cn->network.cni_8301;
		timeout = vbi->vt.cni_830_timeout;
	}

	if (VBI3_CNI_TYPE_8302 != type
	    && 0 != cn->network.cni_8302
	    && (cn->network.cni_8302
		!= vbi3_convert_cni (VBI3_CNI_TYPE_8302, type, cni))) {
		cn->confirm_cni_8302 = cn->network.cni_8302;
		timeout = vbi->vt.cni_830_timeout;
	}

	if (timeout > 0.0) {
		/* Arrange for a reset in timeout seconds
		   and discard all data received in the meantime. */
		internal_reset (vbi, cn, vbi->timestamp + timeout);
	}
}

static vbi3_bool
decode_vps			(vbi3_decoder *		vbi,
				 const uint8_t		buffer[13])
{
	unsigned int cni;
	cache_network *cn;

	if (!vbi3_decode_vps_cni (&cni, buffer))
		return FALSE;

	cn = vbi->vt.network;

	if (0 == cni) {
		/* Should probably ignore this. */
	} else if (cni == cn->network.cni_vps) {
		/* No CNI change, no channel change. */

		cn->confirm_cni_vps = 0;

		if (0 == cn->confirm_cni_8301
		    && 0 == cn->confirm_cni_8302) {
			/* All CNIs valid, cancel reset requests. */
			internal_reset (vbi, cn, -1);
		}
	} else if (cni == cn->confirm_cni_vps) {
		vbi3_event e;

		/* The CNI is correct. */

		if (0 == cn->network.cni_vps) {
			/* First time CNI, assume no channel change. */

			vbi3_network_set_cni (&cn->network,
					     VBI3_CNI_TYPE_VPS, cni);

			cn->confirm_cni_vps = 0;

			if (0 == cn->confirm_cni_8301
			    && 0 == cn->confirm_cni_8302) {
				/* All CNIs valid, cancel reset requests. */
				internal_reset (vbi, cn, -1);
			}
		} else {
			vbi3_network nk;
			cache_network *cn;

			/* Different CNI, channel change detected. */

			vbi3_network_init (&nk);
			vbi3_network_set_cni (&nk, VBI3_CNI_TYPE_VPS, cni);

			cn = _vbi3_cache_add_network
				(vbi->vt.cache, &nk, vbi->vt.videostd_set);

			internal_reset (vbi, cn, 0.0 /* now */);

			cache_network_unref (cn);

			vbi3_network_destroy (&nk);
		}

		/* internal_reset() may have changed this. */
		cn = vbi->vt.network;

		e.type = VBI3_EVENT_NETWORK;
		e.network = &cn->network;
		e.timestamp = vbi->timestamp;

		_vbi3_event_handler_list_send (&vbi->handlers, &e);
	} else {
		/* VPS is poorly error protected but repeats once per frame.
		   We accept this CNI after receiving it twice in a row. */
		cn->confirm_cni_vps = cni;

		if (0 == cn->network.cni_vps) {
			/* First time CNI, channel change possible. */
			cni_change (vbi, VBI3_CNI_TYPE_VPS, cni);
		} else {
			/* Assume a channel change with unknown CNI if we
			   cannot confirm the new CNI or receive the old
			   CNI again within timeout seconds. */
			internal_reset (vbi, cn, vbi->timestamp
					+ vbi->vt.cni_vps_timeout);
		}

		/* Discard PDC data until we identified the network. */

		return TRUE;
	}

#ifndef ZAPPING8

	if ((vbi->handlers.event_mask & VBI3_EVENT_PROG_ID)
	    && vbi->reset_time <= 0.0 /* not suspended */) {
		vbi3_program_id pid;
		vbi3_program_id *p;
		cache_network *cn;

		if (!vbi3_decode_vps_pdc (&pid, buffer))
			return FALSE;

		cn = vbi->vt.network;

		p = &cn->program_id[VBI3_PID_CHANNEL_VPS];

		if (p->cni != pid.cni
		    || p->pil != pid.pil
		    || p->pcs_audio != pid.pcs_audio
		    || p->pty != pid.pty) {
			vbi3_event e;

			/* Program ID changed. */

			*p = pid;

			e.type = VBI3_EVENT_PROG_ID;
			e.network = &cn->network;
			e.timestamp = vbi->timestamp;

			e.ev.prog_id = p;

			_vbi3_event_handler_list_send (&vbi->handlers, &e);
		}
	}

#endif /* !ZAPPING8 */

	return TRUE;
}

#ifndef ZAPPING8

static void
aspect_event			(vbi3_decoder *		vbi,
				 const vbi3_aspect_ratio *ar)
{
	cache_network *cn;
	vbi3_event e;

	cn = vbi->vt.network;

	if (0 == memcmp (&cn->aspect_ratio, ar, sizeof (cn->aspect_ratio))) {
		/* No change. */
		return;
	}

	/* WSS is poorly error protected but repeats once per frame.
	   We accept this aspect ratio after receiving it twice. */
	if (0 != memcmp (&vbi->confirm_aspect_ratio, ar,
			 sizeof (vbi->confirm_aspect_ratio))) {
		vbi->confirm_aspect_ratio = *ar;
		return;
	}

	cn->aspect_ratio = *ar;

	e.type = VBI3_EVENT_ASPECT;
	e.network = &cn->network;
	e.timestamp = vbi->timestamp;

	e.ev.aspect = &cn->aspect_ratio;

	_vbi3_event_handler_list_send (&vbi->handlers, &e);
}

#endif /* !ZAPPING8 */

/**
 */
void
vbi3_decoder_feed		(vbi3_decoder *		vbi,
				 vbi3_sliced *		sliced,
				 unsigned int		n_lines,
				 double			timestamp)
{
	vbi3_bool all_success;
	double dt;

	dt = timestamp - vbi->timestamp;

	if (vbi->timestamp > 0.0 && (dt < 0.025 || dt > 0.050)) {
		if (0)
			fprintf (stderr, "vbi frame/s dropped at %f, dt=%f\n",
				 timestamp, dt);

		if (0 != vbi->vt.handlers.event_mask
		    || (VBI3_EVENT_NETWORK & vbi->handlers.event_mask)) {
			_vbi3_teletext_decoder_resync (&vbi->vt);
		}

		if (0 != vbi->cc.handlers.event_mask
		    || (VBI3_EVENT_NETWORK & vbi->handlers.event_mask)) {
			_vbi3_caption_decoder_resync (&vbi->cc);
		}

		/* Set to all failed. */
		vbi->error_history_vps = 0;
		vbi->error_history_wss_625 = 0;
		vbi->error_history_wss_cpr1204 = 0;

		vbi->timestamp = timestamp;

		/* Assuming a channel change, arrange for a reset in 1.5
		   seconds if we don't receive the old or a new network ID,
		   and discard all data received in the meantime. */
		internal_reset (vbi, /* cn */ NULL, vbi->timestamp + 1.5);
	} else {
// FIXME deferred reset here
	}

	if (timestamp > vbi->timestamp) {
		vbi->timestamp = timestamp;

		if (vbi->handlers.event_mask & VBI3_EVENT_TIMER) {
			vbi3_event e;

			e.type = VBI3_EVENT_TIMER;
			e.network = &vbi->vt.network->network;
			e.timestamp = timestamp;

			_vbi3_event_handler_list_send (&vbi->handlers, &e);
		}
	}

	all_success = TRUE;

	while (n_lines > 0) {
		if (sliced->id & VBI3_SLICED_TELETEXT_B_625) {
			vbi->timestamp_teletext = vbi->timestamp;
			all_success &= vbi3_teletext_decoder_feed
				(&vbi->vt, sliced->data, vbi->timestamp);
		} else if (sliced->id & VBI3_SLICED_CAPTION_525) {
			vbi->timestamp_caption = vbi->timestamp;
			all_success &= vbi3_caption_decoder_feed
				(&vbi->cc, sliced->data,
				 sliced->line, vbi->timestamp);
		} else if ((sliced->id & VBI3_SLICED_VPS)
			   && (0 == sliced->line || 16 == sliced->line)) {
			vbi->timestamp_vps = vbi->timestamp;
			all_success &= decode_vps (vbi, sliced->data);
#ifndef ZAPPING8
		} else if ((sliced->id & VBI3_SLICED_WSS_625)
			   && (0 == sliced->line || 23 == sliced->line)) {
			vbi3_aspect_ratio aspect;
			vbi3_bool success;

			vbi->timestamp_wss_625 = vbi->timestamp;

			/* Make sure memcmp() won't see random bits in
			   gaps in the structure. */
			CLEAR (aspect);

			success = vbi3_decode_wss_625 (&aspect, sliced->data);
			vbi->error_history_wss_625 =
				vbi->error_history_wss_625 * 2 + success;
			all_success &= success;

			if (success) {
				aspect_event (vbi, &aspect);
			}
		} else if (sliced->id & VBI3_SLICED_WSS_CPR1204) {
			vbi3_aspect_ratio aspect;
			vbi3_bool success;

			vbi->timestamp_wss_cpr1204 = vbi->timestamp;

			CLEAR (aspect);

			success = vbi3_decode_wss_cpr1204 (&aspect,
							   sliced->data);
			vbi->error_history_wss_cpr1204 =
				vbi->error_history_wss_cpr1204 * 2 + success;
			all_success &= success;

			if (success) {
				aspect_event (vbi, &aspect);
			}
#endif /* !ZAPPING8 */
		}

		++sliced;
		--n_lines;
	}

#if 0 /* TODO */
	if (vbi->handlers.event_mask & VBI3_EVENT_TRIGGER)
		vbi3_deferred_trigger(vbi);

	if (0 && (rand() % 511) == 0)
		vbi3_eacem_trigger
		  (vbi, (unsigned char *) /* Latin-1 */
		   "<http://zapping.sourceforge.net>[n:Zapping][5450]");
#endif
}

/**
 * @param vbi VBI decoder allocated with vbi3_decoder_new().
 * @param callback Function to be called on events.
 * @param user_data User pointer passed through to the @a callback function.
 * 
 * Removes all event handlers from the list with this @a callback and
 * @a user_data. You can safely call this function from a handler removing
 * itself or another handler.
 */
void
vbi3_decoder_remove_event_handler
				(vbi3_decoder *		vbi,
				 vbi3_event_cb *		callback,
				 void *			user_data)
{
	assert (NULL != vbi);

	vbi3_teletext_decoder_remove_event_handler
		(&vbi->vt, callback, user_data);

	vbi3_caption_decoder_remove_event_handler
		(&vbi->cc, callback, user_data);

	_vbi3_event_handler_list_remove_by_callback
		(&vbi->handlers, callback, user_data);
}

/**
 * @param vbi VBI decoder allocated with vbi3_decoder_new().
 * @param event_mask Set of events (@c VBI3_EVENT_) the handler is waiting
 *   for, can be -1 for all and 0 for none.
 * @param callback Function to be called on events by
 *   vbi3_decoder_feed() and other vbi3_decoder methods as noted.
 * @param user_data User pointer passed through to the @a callback function.
 * 
 * Adds a new event handler to the VBI decoder. When the @a callback
 * with @a user_data is already registered the function merely changes the
 * set of events it will receive in the future. When the @a event_mask is
 * empty the function does nothing or removes a registered event
 * handler. You can safely call this function from an event handler.
 *
 * Any number of handlers can be added, also different handlers for the
 * same event which will be called in registration order.
 *
 * @returns
 * @c FALSE on failure (out of memory), removing the handler.
 */
vbi3_bool
vbi3_decoder_add_event_handler	(vbi3_decoder *		vbi,
				 vbi3_event_mask	event_mask,
				 vbi3_event_cb *	callback,
				 void *			user_data)
{
	vbi3_event_mask child_events;

	assert (NULL != vbi);

	child_events = event_mask & ~(VBI3_EVENT_CLOSE |
				      VBI3_EVENT_RESET |
				      VBI3_EVENT_TIMER);

	if (vbi3_teletext_decoder_add_event_handler
	    (&vbi->vt, child_events, callback, user_data)) {

		if (vbi3_caption_decoder_add_event_handler
		    (&vbi->cc, child_events, callback, user_data)) {

			event_mask &= (VBI3_EVENT_CLOSE |
				       VBI3_EVENT_RESET |
				       VBI3_EVENT_NETWORK |
				       VBI3_EVENT_PROG_ID |
				       VBI3_EVENT_ASPECT |
				       VBI3_EVENT_TIMER);

			if (0 == event_mask) {
				return TRUE;
			}

			if (_vbi3_event_handler_list_add
			    (&vbi->handlers, event_mask,
			     callback, user_data)) {
				return TRUE;
			}

			vbi3_caption_decoder_remove_event_handler
				(&vbi->cc, callback, user_data);
		}

		vbi3_teletext_decoder_remove_event_handler
			(&vbi->vt, callback, user_data);
	}

	return FALSE;
}

/**
 * @param vbi VBI decoder allocated with vbi3_decoder_new().
 * @param nk Identifies the new network, can be @c NULL.
 * @param videostd_set The new video standard.
 *
 * Resets the VBI decoder, useful for example after a channel change.
 *
 * This function sends a @c VBI3_EVENT_RESET.
 */
void
vbi3_decoder_reset		(vbi3_decoder *		vbi,
				 const vbi3_network *	nk,
				 vbi3_videostd_set	videostd_set)
{
	cache_network *cn;

	cn = _vbi3_cache_add_network (vbi->vt.cache, nk, videostd_set);

	vbi->vt.videostd_set = videostd_set;
	vbi->cc.videostd_set = videostd_set;

	internal_reset (vbi, cn, 0.0);

	cache_network_unref (cn);
}

static void
teletext_reset_trampoline	(vbi3_teletext_decoder *	td,
				 cache_network *	cn,
				 double			time)
{
	internal_reset (PARENT (td, vbi3_decoder, vt), cn, time);
}

static void
caption_reset_trampoline	(vbi3_caption_decoder *	cd,
				 cache_network *	cn,
				 double			time)
{
	internal_reset (PARENT (cd, vbi3_decoder, cc), cn, time);
}

/**
 * @param vbi VBI decoder structure to be destroyed.
 *
 * Frees all resources associated with @a vbi, except the structure itself.
 * This function sends a @c VBI3_EVENT_CLOSE.
 */
void
_vbi3_decoder_destroy		(vbi3_decoder *		vbi)
{
	vbi3_event e;

	assert (NULL != vbi);

	e.type		= VBI3_EVENT_CLOSE;
	e.network	= &vbi->vt.network->network;
	e.timestamp	= vbi->timestamp;

	_vbi3_event_handler_list_send (&vbi->handlers, &e);

	_vbi3_caption_decoder_destroy (&vbi->cc);
	_vbi3_teletext_decoder_destroy (&vbi->vt);

	_vbi3_event_handler_list_destroy (&vbi->handlers);

	CLEAR (*vbi);
}

/**
 * @internal
 * @param vbi VBI decoder structure to be initialized.
 * @param ca Cache to be used by this decoder, can be @c NULL.
 *   To allocate a cache call vbi3_cache_new(). Caches have a reference
 *   counter, you can vbi3_cache_unref() after calling this function.
 * @param nk Current network, can be @c NULL.
 * @param videostd_set The current video standard.
 *
 * Initialize a VBI decoder structure.
 *
 * @returns
 * @c FALSE on failure (out of memory).
 */
vbi3_bool
_vbi3_decoder_init		(vbi3_decoder *		vbi,
				 vbi3_cache *		ca,
				 const vbi3_network *	nk,
				 vbi3_videostd_set	videostd_set)
{
	vbi3_cache *cache;

	assert (NULL != vbi);

	CLEAR (*vbi);

	/* Most recent VBI data timestamp. */
	vbi->timestamp		= 0.0;

	/* Ditto for each service, to detect activity. */
	vbi->timestamp_teletext		= -1000000.0;
	vbi->timestamp_caption		= -1000000.0;
	vbi->timestamp_vps		= -1000000.0;
	vbi->timestamp_wss_625		= -1000000.0;
	vbi->timestamp_wss_cpr1204	= -1000000.0;

	if (ca) {
		cache = ca;
	} else {
		if (!(cache = vbi3_cache_new ()))
			return FALSE;
	}

	_vbi3_event_handler_list_init (&vbi->handlers);

	_vbi3_teletext_decoder_init (&vbi->vt, cache, nk, videostd_set);
	_vbi3_caption_decoder_init (&vbi->cc, cache, nk, videostd_set);

	if (!ca) {
		/* Drop our reference. */
		vbi3_cache_unref (cache);
	}

	vbi->reset_time = 0.0; /* no deferred reset */

	/* Redirect reset requests to parent vbi3_decoder. */

	vbi->teletext_reset = vbi->vt.virtual_reset;
	vbi->vt.virtual_reset = teletext_reset_trampoline;

	vbi->caption_reset = vbi->cc.virtual_reset;
	vbi->cc.virtual_reset = caption_reset_trampoline;

	return TRUE;
}

/**
 * @param vbi VBI decoder allocated with vbi3_decoder_new(), can be @c NULL.
 *
 * Frees all resources associated with @a vbi. This function sends a
 * @c VBI3_EVENT_CLOSE.
 */
void
vbi3_decoder_delete		(vbi3_decoder *		vbi)
{
	if (NULL == vbi)
		return;

	_vbi3_decoder_destroy (vbi);

	vbi3_free (vbi);
}

static void
teletext_delete_trampoline	(vbi3_teletext_decoder *td)
{
	vbi3_decoder_delete (PARENT (td, vbi3_decoder, vt));
}

static void
caption_delete_trampoline	(vbi3_caption_decoder *	cd)
{
	vbi3_decoder_delete (PARENT (cd, vbi3_decoder, cc));
}

/**
 * @param ca Cache to be used by this decoder, can be @c NULL.
 *   To allocate a cache call vbi3_cache_new(). Caches have a reference
 *   counter, you can vbi3_cache_unref() after calling this function.
 * @param nk Current network, can be @c NULL.
 * @param videostd_set The current video standard.
 *
 * Allocates a new VBI (Teletext, Closed Caption, VPS, WSS, etc) decoder.
 *
 * @returns
 * Pointer to newly allocated VBI decoder which must be freed with
 * vbi3_decoder_delete() when done. @c NULL on failure (out of memory).
 */
vbi3_decoder *
vbi3_decoder_new		(vbi3_cache *		ca,
				 const vbi3_network *	nk,
				 vbi3_videostd_set	videostd_set)
{
	vbi3_decoder *vbi;

	if (!(vbi = vbi3_malloc (sizeof (*vbi)))) {
		error ("Out of memory (%u bytes)", sizeof (*vbi));
		return NULL;
	}

        if (!_vbi3_decoder_init (vbi, ca, nk, videostd_set)) {
		vbi3_free (vbi);
		vbi = NULL;
	}

	/* Make it safe to delete a vbi3_teletext/caption_decoder
	   cast from a vbi3_decoder. */
	vbi->vt.virtual_delete = teletext_delete_trampoline;
	vbi->cc.virtual_delete = caption_delete_trampoline;

	return vbi;
}