/* * libzvbi test * * Copyright (C) 2000, 2001 Michael H. Schimek * * 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: caption.c,v 1.14 2006/05/22 08:57:05 mschimek Exp $ */ #undef NDEBUG #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #ifndef X_DISPLAY_MISSING #include #include #include #include "src/vbi.h" #include "src/exp-gfx.h" #include "src/hamm.h" #include "src/dvb_demux.h" #include "sliced.h" vbi_decoder * vbi; vbi_pgno pgno = -1; vbi_dvb_demux * dx; /* * Rudimentary render code for CC test. * Attention: RGB 5:6:5 little endian only. */ #define DISP_WIDTH 640 #define DISP_HEIGHT 480 #define CELL_WIDTH 16 #define CELL_HEIGHT 26 Display * display; int screen; Colormap cmap; Window window; GC gc; XEvent event; XImage * ximage; ushort * ximgdata; int shift = 0, step = 3; int sh_first, sh_last; vbi_rgba row_buffer[64 * CELL_WIDTH * CELL_HEIGHT]; #define COLORKEY 0x80FF80 /* where video looks through */ #define RGB565(rgba) \ (((((rgba) >> 16) & 0xF8) << 8) | ((((rgba) >> 8) & 0xFC) << 3) \ | (((rgba) & 0xF8) >> 3)) static void draw_video (int x0, int y0, int w, int h) { ushort *canvas = ximgdata + x0 + y0 * DISP_WIDTH; int x, y; for (y = 0; y < h; y++) { for (x = 0; x < w; x++) canvas[x] = RGB565(COLORKEY); canvas += DISP_WIDTH; } } static void draw_blank (int column, int width) { vbi_rgba *canvas = row_buffer + column * CELL_WIDTH; int x, y; for (y = 0; y < CELL_HEIGHT; y++) { for (x = 0; x < CELL_WIDTH * width; x++) canvas[x] = COLORKEY; canvas += sizeof(row_buffer) / sizeof(row_buffer[0]) / CELL_HEIGHT; } } /* Not exactly efficient, but this is only a test */ static void draw_row (ushort * canvas, vbi_page * pg, int row) { int i, j, num_tspaces = 0; vbi_rgba *s = row_buffer; for (i = 0; i < pg->columns; ++i) { if (pg->text[row * pg->columns + i].opacity == VBI_TRANSPARENT_SPACE) { num_tspaces++; continue; } if (num_tspaces > 0) { draw_blank(i - num_tspaces, num_tspaces); num_tspaces = 0; } vbi_draw_cc_page_region (pg, VBI_PIXFMT_RGBA32_LE, row_buffer + i * CELL_WIDTH, sizeof(row_buffer) / CELL_HEIGHT, i, row, 1, 1); } if (num_tspaces > 0) draw_blank(i - num_tspaces, num_tspaces); for (i = 0; i < CELL_HEIGHT; i++) { for (j = 0; j < pg->columns * CELL_WIDTH; j++) canvas[j] = RGB565(s[j]); s += sizeof(row_buffer) / sizeof(row_buffer[0]) / CELL_HEIGHT; canvas += DISP_WIDTH; } } static void bump (int n, vbi_bool draw) { ushort *canvas = ximgdata + 45 * DISP_WIDTH; if (shift < n) n = shift; if (shift <= 0 || n <= 0) return; memmove (canvas + (sh_first * CELL_HEIGHT) * DISP_WIDTH, canvas + (sh_first * CELL_HEIGHT + n) * DISP_WIDTH, ((sh_last - sh_first + 1) * CELL_HEIGHT - n) * DISP_WIDTH * 2); if (draw) XPutImage (display, window, gc, ximage, 0, 0, 0, 0, DISP_WIDTH, DISP_HEIGHT); shift -= n; } static void render (vbi_page * pg, int row) { /* ushort *canvas = ximgdata + 48 + 45 * DISP_WIDTH; */ if (shift > 0) { bump(shift, FALSE); draw_video (48, 45 + sh_last * CELL_HEIGHT, DISP_WIDTH - 48, CELL_HEIGHT); } draw_row (ximgdata + 48 + (45 + row * CELL_HEIGHT) * DISP_WIDTH, pg, row); XPutImage (display, window, gc, ximage, 0, 0, 0, 0, DISP_WIDTH, DISP_HEIGHT); } static void clear (vbi_page * pg) { pg = pg; draw_video (0, 0, DISP_WIDTH, DISP_HEIGHT); XPutImage (display, window, gc, ximage, 0, 0, 0, 0, DISP_WIDTH, DISP_HEIGHT); } static void roll_up (vbi_page * pg, int first_row, int last_row) { ushort scol, *canvas = ximgdata + 45 * DISP_WIDTH; vbi_rgba col; int i, j; #if 1 /* soft */ sh_first = first_row; sh_last = last_row; shift = 26; bump(step, FALSE); canvas += 48 + (((last_row * CELL_HEIGHT) + CELL_HEIGHT - step) * DISP_WIDTH); col = pg->color_map[pg->text[last_row * pg->columns].background]; scol = RGB565 (col); for (j = 0; j < step; ++j) { if (pg->text[last_row * pg->columns].opacity == VBI_TRANSPARENT_SPACE) { for (i = 0; i < CELL_WIDTH * pg->columns; ++i) canvas[i] = RGB565 (COLORKEY); } else { for (i = 0; i < CELL_WIDTH * pg->columns; ++i) canvas[i] = scol; } canvas += DISP_WIDTH; } #else /* at once */ memmove (canvas + first_row * CELL_HEIGHT * DISP_WIDTH, canvas + (first_row + 1) * CELL_HEIGHT * DISP_WIDTH, (last_row - first_row) * CELL_HEIGHT * DISP_WIDTH * 2); draw_video (48, 45 + last_row * CELL_HEIGHT, DISP_WIDTH - 48, CELL_HEIGHT); #endif XPutImage (display, window, gc, ximage, 0, 0, 0, 0, DISP_WIDTH, DISP_HEIGHT); } static void xevent (int nap_usec); static void cc_handler (vbi_event * ev, void * user_data) { vbi_page page; vbi_bool success; int row; user_data = user_data; if (pgno != -1 && ev->ev.caption.pgno != pgno) return; /* Fetching & rendering in the handler is a bad idea, but this is only a test */ success = vbi_fetch_cc_page (vbi, &page, ev->ev.caption.pgno, TRUE); assert (success); #if 1 /* optional */ if (abs (page.dirty.roll) > page.rows) { clear (&page); } else if (page.dirty.roll == -1) { roll_up (&page, page.dirty.y0, page.dirty.y1); } else { #endif for (row = page.dirty.y0; row <= page.dirty.y1; ++row) render (&page, row); } vbi_unref_page (&page); } static void reset (void) { vbi_page page; vbi_bool success; int row; success = vbi_fetch_cc_page (vbi, &page, pgno, TRUE); assert (success); for (row = 0; row <= page.rows; ++row) render (&page, row); vbi_unref_page (&page); } /* * X11 stuff */ static void xevent (int nap_usec) { while (XPending (display)) { XNextEvent (display, &event); switch (event.type) { case KeyPress: { int c = XLookupKeysym (&event.xkey, 0); switch (c) { case 'q': case 'c': exit (EXIT_SUCCESS); case '1' ... '8': pgno = c - '1' + 1; reset (); return; case XK_F1 ... XK_F8: pgno = c - XK_F1 + 1; reset (); return; } break; } case Expose: XPutImage (display, window, gc, ximage, 0, 0, 0, 0, DISP_WIDTH, DISP_HEIGHT); break; case ClientMessage: exit (EXIT_SUCCESS); } } bump (step, TRUE); usleep (nap_usec / 4); } static vbi_bool init_window (int ac, char ** av) { Atom delete_window_atom; XWindowAttributes wa; int i; ac = ac; av = av; if (!(display = XOpenDisplay (NULL))) { return FALSE; } screen = DefaultScreen (display); cmap = DefaultColormap (display, screen); window = XCreateSimpleWindow (display, RootWindow (display, screen), /* x, y */ 0, 0, DISP_WIDTH, DISP_HEIGHT, /* borderwidth */ 2, /* foreground */ 0xffffffff, /* background */ 0x00000000); if (!window) { return FALSE; } XGetWindowAttributes (display, window, &wa); if (16 != wa.depth) { fprintf (stderr, "Can only run at " "color depth 16 (5:6:5) LE\n"); return FALSE; } if (!(ximgdata = malloc (DISP_WIDTH * DISP_HEIGHT * 2))) { return FALSE; } for (i = 0; i < DISP_WIDTH * DISP_HEIGHT; ++i) ximgdata[i] = RGB565 (COLORKEY); ximage = XCreateImage(display, DefaultVisual (display, screen), DefaultDepth (display, screen), ZPixmap, 0, (char *) ximgdata, DISP_WIDTH, DISP_HEIGHT, 8, 0); if (!ximage) { return FALSE; } delete_window_atom = XInternAtom (display, "WM_DELETE_WINDOW", False); XSelectInput (display, window, KeyPressMask | ExposureMask | StructureNotifyMask); XSetWMProtocols (display, window, &delete_window_atom, 1); XStoreName (display, window, "Caption Test - [Q], [F1]..[F8]"); gc = XCreateGC (display, window, 0, NULL); XMapWindow (display, window); XSync (display, False); XPutImage (display, window, gc, ximage, 0, 0, 0, 0, DISP_WIDTH, DISP_HEIGHT); return TRUE; } /* * Feed caption from a sample stream */ static void pes_mainloop (void) { uint8_t buffer[2048]; while (1 == fread (buffer, sizeof (buffer), 1, stdin)) { const uint8_t *bp; unsigned int bytes_left; bp = buffer; bytes_left = sizeof (buffer); while (bytes_left > 0) { vbi_sliced sliced[64]; unsigned int n_lines; int64_t pts; n_lines = vbi_dvb_demux_cor (dx, sliced, 64, &pts, &bp, &bytes_left); if (n_lines > 0) { vbi_decode (vbi, sliced, n_lines, pts / 90000.0); } /* xevent (1e6 / 30); */ } } fprintf (stderr, "\rEnd of stream\n"); } static void old_mainloop (void) { for (;;) { vbi_sliced sliced[40]; double timestamp; int n_lines; n_lines = read_sliced (sliced, ×tamp, /* max_lines */ 40); if (n_lines < 0) break; vbi_decode (vbi, sliced, n_lines, timestamp); /* xevent (1e6 / 30); */ } fprintf (stderr, "\rEnd of stream\n"); } /* * Feed artificial caption */ static void cmd (unsigned int n) { vbi_sliced sliced; static double time = 0.0; sliced.id = VBI_SLICED_CAPTION_525; sliced.line = 21; sliced.data[0] = vbi_par8 (n >> 8); sliced.data[1] = vbi_par8 (n & 0x7F); vbi_decode (vbi, &sliced, 1, time); xevent (33333); time += 1 / 29.97; } static void printc (int c) { cmd (c * 256 + 0x80); xevent (33333); } static void prints (const char * s) { for (; s[0] && s[1]; s += 2) cmd (s[0] * 256 + s[1]); if (s[0]) cmd (s[0] * 256 + 0x80); xevent (33333); } enum { white, green, red, yellow, blue, cyan, magenta, black }; static int mapping_row [] = { 2, 3, 4, 5, 10, 11, 12, 13, 14, 15, 0, 6, 7, 8, 9, -1 }; #define italic 7 #define underline 1 #define opaque 0 #define semi_transp 1 #define BACKG(bg, t) \ (cmd (0x2000), cmd (0x1020 + ((ch & 1) << 11) + (bg << 1) + t)) #define PREAMBLE(r, fg, u) \ cmd (0x1040 + ((ch & 1) << 11) + ((mapping_row[r] & 14) << 7) \ + ((mapping_row[r] & 1) << 5) + (fg << 1) + u) #define INDENT(r, fg, u) \ cmd (0x1050 + ((ch & 1) << 11) + ((mapping_row[r] & 14) << 7) \ + ((mapping_row[r] & 1) << 5) + ((fg / 4) << 1) + u) #define MIDROW(fg, u) cmd (0x1120 + ((ch & 1) << 11) + (fg << 1) + u) #define SPECIAL_CHAR(n) cmd (0x1130 + ((ch & 1) << 11) + n) #define CCODE(code, ch) (code + ((ch & 1) << 11) + ((ch & 2) << 7)) #define RESUME_CAPTION cmd (CCODE (0x1420, ch)) #define BACKSPACE cmd (CCODE (0x1421, ch)) #define DELETE_EOR cmd (CCODE (0x1424, ch)) #define ROLL_UP(rows) cmd (CCODE (0x1425, ch) + rows - 2) #define FLASH_ON cmd (CCODE (0x1428, ch)) #define RESUME_DIRECT cmd (CCODE (0x1429, ch)) #define TEXT_RESTART cmd (CCODE (0x142A, ch)) #define RESUME_TEXT cmd (CCODE (0x142B, ch)) #define END_OF_CAPTION cmd (CCODE (0x142F, ch)) #define ERASE_DISPLAY cmd (CCODE (0x142C, ch)) #define CR cmd (CCODE (0x142D, ch)) #define ERASE_HIDDEN cmd (CCODE (0x142E, ch)) #define TAB(t) cmd (CCODE (0x1720, ch) + t) #define TRANSP (cmd (0x2000), cmd (0x172D + ((ch & 1) << 11))) #define BLACK(u) (cmd (0x2000), cmd (0x172E + ((ch & 1) << 11) + u)) static void PAUSE (unsigned int n_frames) { while (n_frames-- > 0) xevent (33333); } static void hello_world (void) { int ch = 0; int i; pgno = -1; prints (" HELLO WORLD! "); PAUSE (30); ch = 4; TEXT_RESTART; prints ("Character set - Text 1"); CR; CR; for (i = 32; i <= 127; i++) { printc (i); if ((i & 15) == 15) CR; } MIDROW (italic, 0); for (i = 32; i <= 127; i++) { printc (i); if ((i & 15) == 15) CR; } MIDROW (white, underline); for (i = 32; i <= 127; i++) { printc (i); if ((i & 15) == 15) CR; } MIDROW (white, 0); prints ("Special: "); for (i = 0; i <= 15; i++) { SPECIAL_CHAR (i); } CR; prints ("DONE - Text 1 "); PAUSE (50); ch = 5; TEXT_RESTART; prints ("Styles - Text 2"); CR; CR; MIDROW (white, 0); prints ("WHITE"); CR; MIDROW (red, 0); prints ("RED"); CR; MIDROW (green, 0); prints ("GREEN"); CR; MIDROW (blue, 0); prints ("BLUE"); CR; MIDROW (yellow, 0); prints ("YELLOW"); CR; MIDROW (cyan, 0); prints ("CYAN"); CR; MIDROW (magenta, 0); prints ("MAGENTA"); BLACK (0); CR; BACKG (white, opaque); prints ("WHITE"); BACKG (black, opaque); CR; BACKG (red, opaque); prints ("RED"); BACKG (black, opaque); CR; BACKG (green, opaque); prints ("GREEN"); BACKG (black, opaque); CR; BACKG (blue, opaque); prints ("BLUE"); BACKG (black, opaque); CR; BACKG (yellow, opaque); prints ("YELLOW"); BACKG (black, opaque); CR; BACKG (cyan, opaque); prints ("CYAN"); BACKG (black, opaque); CR; BACKG (magenta, opaque); prints ("MAGENTA"); BACKG (black, opaque); CR; TRANSP; prints (" TRANSPARENT BACKGROUND "); BACKG (black, opaque); CR; MIDROW (white, 0); FLASH_ON; prints (" Flashing Text (if implemented) "); CR; MIDROW (white, 0); prints ("DONE - Text 2 "); PAUSE (50); ch = 0; ROLL_UP (2); ERASE_DISPLAY; prints (" ROLL-UP TEST "); CR; PAUSE (20); prints (">> A young Jedi named Darth"); CR; PAUSE (20); prints ("Vader, who was a pupil of"); CR; PAUSE (20); prints ("mine until he turned to evil,"); CR; PAUSE (20); prints ("helped the Empire hunt down"); CR; PAUSE (20); prints ("and destroy the Jedi Knights."); CR; PAUSE (20); prints ("He betrayed and murdered your"); CR; PAUSE (20); prints ("father. Now the Jedi are all"); CR; PAUSE (20); prints ("but extinct. Vader was seduced"); CR; PAUSE (20); prints ("by the dark side of the Force."); CR; PAUSE (20); prints (">> The Force?"); CR; PAUSE (20); prints (">> Well, the Force is what gives"); CR; PAUSE (20); prints ("a Jedi his power. It's an energy"); CR; PAUSE (20); prints ("field created by all living"); CR; PAUSE (20); prints ("things."); CR; PAUSE (20); prints ("It surrounds us and penetrates"); CR; PAUSE (20); prints ("us."); CR; PAUSE (20); prints ("It binds the galaxy together."); CR; PAUSE (20); CR; PAUSE (30); prints (" DONE - Caption 1 "); PAUSE (30); ch = 1; RESUME_DIRECT; ERASE_DISPLAY; MIDROW (yellow, 0); INDENT (2, 10, 0); prints (" FOO "); CR; INDENT (3, 10, 0); prints (" MIKE WAS HERE "); CR; PAUSE (20); MIDROW (red, 0); INDENT (6, 13, 0); prints (" AND NOW... "); CR; INDENT (8, 13, 0); prints (" HE'S HERE "); CR; PAUSE (20); PREAMBLE (12, cyan, 0); prints ("01234567890123456789012345678901234567890123456789"); CR; MIDROW (white, 0); prints (" DONE - Caption 2 "); CR; PAUSE (30); } int main (int argc, char ** argv) { vbi_bool success; if (!init_window (argc, argv)) exit (EXIT_FAILURE); vbi = vbi_decoder_new (); assert (NULL != vbi); success = vbi_event_handler_add (vbi, VBI_EVENT_CAPTION, cc_handler, /* used_data */ NULL); assert (success); if (isatty (STDIN_FILENO)) { hello_world (); } else { int c; pgno = 1; c = getchar (); ungetc (c, stdin); if (0 == c) { dx = vbi_dvb_pes_demux_new (/* callback */ NULL, /* user_data */ NULL); assert (NULL != dx); pes_mainloop (); vbi_dvb_demux_delete (dx); } else { open_sliced_read (stdin); old_mainloop (); } } printf ("Done.\n"); for (;;) xevent (33333); vbi_decoder_delete (vbi); exit (EXIT_SUCCESS); } #else /* X_DISPLAY_MISSING */ int main (int argc, char ** argv) { printf ("Could not find X11 or has been disabled " "at configuration time\n"); exit(EXIT_FAILURE); } #endif