1 /*
2     Sliced vbi demonstration utility
3     Copyright (C) 2004  Hans Verkuil  <hverkuil@xs4all.nl>
4 
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335  USA
18  */
19 
20 /* This test tool is used to test the sliced VBI implementation. It reads
21    from /dev/vbi0 by default (or the device name that is specified as the
22    first argument) and shows which packets arrive and what the contents
23    is. It also serves as example code on how to use the sliced VBI API.
24  */
25 
26 #include <unistd.h>
27 #include <features.h>		/* Uses _GNU_SOURCE to define getsubopt in stdlib.h */
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <inttypes.h>
32 #include <getopt.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <fcntl.h>
36 #include <errno.h>
37 #include <sys/ioctl.h>
38 #include <sys/time.h>
39 #include <math.h>
40 
41 #include <linux/videodev2.h>
42 
43 #define printable(c) ((((c) & 0x7F) < 0x20 || ((c) & 0x7F) > 0x7E) ? '.' : ((c) & 0x7F))
44 
45 int valid_char(char c);
46 
valid_char(char c)47 int valid_char(char c)
48 {
49 	/* Invalid Character */
50 	if (((c) & 0x7F) < 0x20 || ((c) & 0x7F) > 0x7E)
51 		return 0;
52 	else			/* Valid Character */
53 		return 1;
54 }
55 
56 int frames = 0;
57 int lines = 0;
58 int space_needed = 0;
59 int text_off = 0;
60 
61 static const char *formats[] = {
62 	"Full format 4:3, 576 lines",
63 	"Letterbox 14:9 centre, 504 lines",
64 	"Letterbox 14:9 top, 504 lines",
65 	"Letterbox 16:9 centre, 430 lines",
66 	"Letterbox 16:9 top, 430 lines",
67 	"Letterbox > 16:9 centre",
68 	"Full format 14:9 centre, 576 lines",
69 	"Anamorphic 16:9, 576 lines"
70 };
71 static const char *subtitles[] = {
72 	"none",
73 	"in active image area",
74 	"out of active image area",
75 	"?"
76 };
77 
decode_wss(struct v4l2_sliced_vbi_data * s)78 static void decode_wss(struct v4l2_sliced_vbi_data *s)
79 {
80 	unsigned char parity;
81 	int wss;
82 
83 	wss = s->data[0] | (s->data[1] << 8);
84 
85 	parity = wss & 15;
86 	parity ^= parity >> 2;
87 	parity ^= parity >> 1;
88 
89 	if (!(parity & 1))
90 		return;
91 
92 	printf("WSS: %s; %s mode; %s color coding;\n"
93 	       "      %s helper; reserved b7=%d; %s\n"
94 	       "      open subtitles: %s; %scopyright %s; copying %s\n",
95 	       formats[wss & 7],
96 	       (wss & 0x10) ? "film" : "camera",
97 	       (wss & 0x20) ? "MA/CP" : "standard",
98 	       (wss & 0x40) ? "modulated" : "no",
99 	       !!(wss & 0x80),
100 	       (wss & 0x0100) ? "have TTX subtitles; " : "",
101 	       subtitles[(wss >> 9) & 3],
102 	       (wss & 0x0800) ? "surround sound; " : "",
103 	       (wss & 0x1000) ? "asserted" : "unknown",
104 	       (wss & 0x2000) ? "restricted" : "not restricted");
105 }
106 
odd_parity(uint8_t c)107 static int odd_parity(uint8_t c)
108 {
109 	c ^= (c >> 4);
110 	c ^= (c >> 2);
111 	c ^= (c >> 1);
112 
113 	return c & 1;
114 }
115 
decode_xds(struct v4l2_sliced_vbi_data * s)116 static void decode_xds(struct v4l2_sliced_vbi_data *s)
117 {
118 	char c;
119 
120 	//printf("XDS: %02x %02x: ", s->data[0], s->data[1]);
121 	c = odd_parity(s->data[0]) ? s->data[0] & 0x7F : '?';
122 	c = printable(c);
123 	//putchar(c);
124 	c = odd_parity(s->data[1]) ? s->data[1] & 0x7F : '?';
125 	c = printable(c);
126 	//putchar(c);
127 	//putchar('\n');
128 }
129 
130 #define CC_SIZE 64
131 
decode_cc(struct v4l2_sliced_vbi_data * s)132 static void decode_cc(struct v4l2_sliced_vbi_data *s)
133 {
134 	static int xds_transport = 0;
135 	char c = s->data[0] & 0x7F;
136 	static char cc[CC_SIZE + 1];
137 	static char cc_last[2 + 1] = { 0, 0 };
138 	char cc_disp[CC_SIZE + 1];
139 	static int cc_idx;
140 
141 	if (s->field) {	/* field 2 */
142 		/* 0x01xx..0x0Exx ASCII_or_NUL[0..32] 0x0Fchks */
143 		if (odd_parity(s->data[0]) && (c >= 0x01 && c <= 0x0F)) {
144 			decode_xds(s);
145 			xds_transport = (c != 0x0F);
146 		} else if (xds_transport) {
147 			decode_xds(s);
148 		}
149 		return;
150 	}
151 
152 	if (s->data[0] == 0x10 ||
153 	    s->data[0] == 0x13 ||
154 	    s->data[0] == 0x15 ||
155 	    s->data[0] == 0x16 ||
156 	    s->data[0] == 0x91 ||
157 	    s->data[0] == 0x92 ||
158 	    s->data[0] == 0x94 || s->data[0] == 0x97 || s->data[0] == 0x1c) {
159 		if (text_off) {
160 			if (s->data[0] == 0x94 &&
161 			    (s->data[1] == 0xad || s->data[1] == 0x25)) {
162 				text_off = 0;
163 			}
164 		} else {
165 			if (s->data[0] == 0x1c &&
166 			    (s->data[1] == 0x2a || s->data[1] == 0xab)) {
167 				text_off = 1;
168 			}
169 		}
170 	}
171 
172 	if (text_off == 0) {
173 		c = odd_parity(s->data[0]) ? s->data[0] & 0x7F : '?';
174 
175 		if (cc_idx >= CC_SIZE) {
176 			cc_idx = CC_SIZE - 2;
177 			memmove(cc, cc + 2, cc_idx);
178 		}
179 		cc[cc_idx++] = c;
180 
181 		c = odd_parity(s->data[1]) ? s->data[1] & 0x7F : '?';
182 
183 		cc[cc_idx++] = c;
184 
185 		cc[cc_idx] = 0;
186 	}
187 
188 	if (cc_idx == CC_SIZE) {
189 		int x = 0, y = 0;
190 		int debug = 0;
191 
192 		memset(cc_disp, 0, CC_SIZE);
193 
194 		if (debug)
195 			fprintf(stderr, "\n");
196 		for (y = 0, x = 0; y < cc_idx;) {
197 
198 			/* Control Code or Valid Character */
199 			if (valid_char(cc[y]) == 0) {
200 				if (debug) {
201 					if (cc[y] == 0x00)
202 						fprintf(stderr, "()");
203 					else
204 						fprintf(stderr, "(0x%02x)",
205 							cc[y]);
206 				}
207 
208 				/* skip over control code */
209 				if (cc[y] >= 0x11 && cc[y] <= 0x1f) {
210 					if (debug) {
211 						if (cc[y + 1] == 0x00)
212 							fprintf(stderr, "()");
213 						else
214 							fprintf(stderr,
215 								"(0x%02x)",
216 								cc[y + 1]);
217 					}
218 
219 					if (space_needed == 1) {
220 						space_needed = 0;
221 						cc_disp[x++] = ' ';
222 						lines++;
223 					} else if (cc[y] == 0x14
224 						   && cc[y + 1] == 0x14) {
225 						space_needed = 0;
226 						cc_disp[x++] = ' ';
227 						lines++;
228 					}
229 
230 					cc_last[0] = cc[y];
231 					cc_last[1] = cc[y + 1];
232 					y += 2;
233 				} else {
234 					cc_last[0] = cc_last[1];
235 					cc_last[1] = cc[y];
236 					y++;
237 				}
238 			} else {
239 				if (debug)
240 					fprintf(stderr, "(%c)", cc[y] & 0x7F);
241 
242 				/* Record character */
243 				if ((cc[y] & 0x7F) == '\n') {
244 					cc_disp[x] = ' ';
245 					lines++;
246 				} else if (cc_last[1] == 0x2B
247 					   && cc_last[0] == 0x14
248 					   && (cc[y] & 0x7F) == '@') {
249 					/* Do Nothing */
250 					cc_last[0] = cc_last[1];
251 					cc_last[1] = cc[y];
252 					y++;
253 					continue;
254 				} else if ((cc[y] & 0x7F) != '\n') {
255 					cc_disp[x] = cc[y] & 0x7F;
256 					lines++;
257 				} else {
258 					printf("\nOdd Character (%c)\n",
259 					       cc[y] & 0x7F);
260 				}
261 
262 				space_needed = 1;
263 				x++;
264 				cc_last[0] = cc_last[1];
265 				cc_last[1] = cc[y];
266 				y++;
267 			}
268 
269 			/* Insert CC_SIZE char Line Break */
270 			if (lines >= CC_SIZE && cc_disp[x - 1] == ' ') {
271 				cc_disp[x++] = '\n';
272 				lines = 0;
273 				space_needed = 0;
274 			}
275 		}
276 		if (debug)
277 			fprintf(stderr, "\n");
278 		printf("%s", cc_disp);
279 		memset(cc_disp, 0, CC_SIZE);
280 		//memset(cc, 0, CC_SIZE);
281 
282 		cc_idx = 0;
283 	}
284 }
285 
286 const uint8_t vbi_bit_reverse[256] = {
287 	0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
288 	0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
289 	0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
290 	0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
291 	0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
292 	0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
293 	0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
294 	0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
295 	0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
296 	0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
297 	0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
298 	0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
299 	0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
300 	0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
301 	0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
302 	0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
303 	0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
304 	0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
305 	0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
306 	0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
307 	0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
308 	0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
309 	0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
310 	0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
311 	0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
312 	0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
313 	0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
314 	0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
315 	0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
316 	0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
317 	0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
318 	0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
319 };
320 
321 #define printable(c) ((((c) & 0x7F) < 0x20 || ((c) & 0x7F) > 0x7E) ? '.' : ((c) & 0x7F))
322 
323 #define PIL(day, mon, hour, min) \
324 	(((day) << 15) + ((mon) << 11) + ((hour) << 6) + ((min) << 0))
325 
dump_pil(int pil)326 static void dump_pil(int pil)
327 {
328 	int day, mon, hour, min;
329 
330 	day = pil >> 15;
331 	mon = (pil >> 11) & 0xF;
332 	hour = (pil >> 6) & 0x1F;
333 	min = pil & 0x3F;
334 
335 	if (pil == PIL(0, 15, 31, 63))
336 		printf(" PDC: Timer-control (no PDC)\n");
337 	else if (pil == PIL(0, 15, 30, 63))
338 		printf(" PDC: Recording inhibit/terminate\n");
339 	else if (pil == PIL(0, 15, 29, 63))
340 		printf(" PDC: Interruption\n");
341 	else if (pil == PIL(0, 15, 28, 63))
342 		printf(" PDC: Continue\n");
343 	else if (pil == PIL(31, 15, 31, 63))
344 		printf(" PDC: No time\n");
345 	else
346 		printf(" PDC: %05x, 200X-%02d-%02d %02d:%02d\n",
347 		       pil, mon, day, hour, min);
348 }
349 
decode_vps(struct v4l2_sliced_vbi_data * s)350 static void decode_vps(struct v4l2_sliced_vbi_data *s)
351 {
352 	static char pr_label[20];
353 	static char label[20];
354 	static int l = 0;
355 	int cni, pcs, pty, pil;
356 	int c;
357 	unsigned char *buf = s->data;
358 
359 	c = vbi_bit_reverse[buf[1]];
360 
361 	if ((int8_t) c < 0) {
362 		label[l] = 0;
363 		memcpy(pr_label, label, sizeof(pr_label));
364 		l = 0;
365 	}
366 
367 	c &= 0x7F;
368 
369 	label[l] = printable(c);
370 
371 	l = (l + 1) % 16;
372 
373 	printf("VPS: 3-10: %02x %02x %02x %02x %02x %02x %02x %02x (\"%s\")\n",
374 	       buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
375 	       pr_label);
376 
377 	pcs = buf[2] >> 6;
378 
379 	cni = +((buf[10] & 3) << 10)
380 	    + ((buf[11] & 0xC0) << 2)
381 	    + ((buf[8] & 0xC0) << 0)
382 	    + (buf[11] & 0x3F);
383 
384 	pil = ((buf[8] & 0x3F) << 14) + (buf[9] << 6) + (buf[10] >> 2);
385 
386 	pty = buf[12];
387 
388 	printf("      CNI: %04x PCS: %d PTY: %d ", cni, pcs, pty);
389 
390 	dump_pil(pil);
391 }
392 
process(struct v4l2_sliced_vbi_data * s)393 static void process(struct v4l2_sliced_vbi_data *s)
394 {
395 	if (s->id == 0)
396 		return;
397 
398 	//printf("%04d: line %02u field %d type %x\n", frames, s->line, s->field, s->id);
399 	switch (s->id) {
400 	case V4L2_SLICED_TELETEXT_B:
401 		printf("teletext\n");
402 		break;
403 	case V4L2_SLICED_VPS:
404 		if (s->line != 16 || s->field)
405 			break;
406 		decode_vps(s);
407 		break;
408 	case V4L2_SLICED_WSS_625:
409 		if (s->line != 23 || s->field)
410 			break;
411 		decode_wss(s);
412 		break;
413 	case V4L2_SLICED_CAPTION_525:
414 		if (s->line != 21)
415 			break;
416 		decode_cc(s);
417 		break;
418 	default:
419 		printf("unknown\n");
420 		break;
421 	}
422 }
423 
main(int argc,char ** argv)424 int main(int argc, char **argv)
425 {
426 	char *device = "/dev/vbi0";
427 	struct v4l2_format fmt;
428 	v4l2_std_id std;
429 	struct v4l2_sliced_vbi_data *buf;
430 	int fh;
431 
432 	if (argc == 2)
433 		device = argv[1];
434 	fh = open(device, O_RDONLY);
435 
436 	if (fh == -1) {
437 		fprintf(stderr, "cannot open %s\n", device);
438 		return 1;
439 	}
440 
441 	setbuf(stdout, NULL);
442 
443 	ioctl(fh, VIDIOC_G_STD, &std);
444 	fmt.type = V4L2_BUF_TYPE_SLICED_VBI_CAPTURE;
445 	fmt.fmt.sliced.service_set = (std & V4L2_STD_NTSC) ? V4L2_SLICED_VBI_525 : V4L2_SLICED_VBI_625;
446 	fmt.fmt.sliced.reserved[0] = 0;
447 	fmt.fmt.sliced.reserved[1] = 0;
448 	if (ioctl(fh, VIDIOC_S_FMT, &fmt) < 0) {
449 		perror("vbi");
450 		close(fh);
451 		return 1;
452 	}
453 
454 	fprintf(stderr, "%08x, %d\n", fmt.fmt.sliced.service_set, fmt.fmt.sliced.io_size);
455 	buf = malloc(fmt.fmt.sliced.io_size);
456 	for (;;) {
457 		int size = read(fh, buf, fmt.fmt.sliced.io_size);
458 		unsigned i;
459 
460 		if (size <= 0)
461 			break;
462 		frames++;
463 		for (i = 0; i < size / sizeof(struct v4l2_sliced_vbi_data); i++) {
464 			process(&buf[i]);
465 		}
466 	}
467 	close(fh);
468 	return 0;
469 }
470