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