#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct { int channel; int freq; } CHANLIST; static CHANLIST ntsc_cable[] = { { 1, 73250 }, { 2, 55250 }, { 3, 61250 }, { 4, 67250 }, { 5, 77250 }, { 6, 83250 }, { 7, 175250 }, { 8, 181250 }, { 9, 187250 }, { 10, 193250 }, { 11, 199250 }, { 12, 205250 }, { 13, 211250 }, { 14, 121250 }, { 15, 127250 }, { 16, 133250 }, { 17, 139250 }, { 18, 145250 }, { 19, 151250 }, { 20, 157250 }, { 21, 163250 }, { 22, 169250 }, { 23, 217250 }, { 24, 223250 }, { 25, 229250 }, { 26, 235250 }, { 27, 241250 }, { 28, 247250 }, { 29, 253250 }, { 30, 259250 }, { 31, 265250 }, { 32, 271250 }, { 33, 277250 }, { 34, 283250 }, { 35, 289250 }, { 36, 295250 }, { 37, 301250 }, { 38, 307250 }, { 39, 313250 }, { 40, 319250 }, { 41, 325250 }, { 42, 331250 }, { 43, 337250 }, { 44, 343250 }, { 45, 349250 }, { 46, 355250 }, { 47, 361250 }, { 48, 367250 }, { 49, 373250 }, { 50, 379250 }, { 51, 385250 }, { 52, 391250 }, { 53, 397250 }, { 54, 403250 }, { 55, 409250 }, { 56, 415250 }, { 57, 421250 }, { 58, 427250 }, { 59, 433250 }, { 60, 439250 }, { 61, 445250 }, { 62, 451250 }, { 63, 457250 }, { 64, 463250 }, { 65, 469250 }, { 66, 475250 }, { 67, 481250 }, { 68, 487250 }, { 69, 493250 }, { 70, 499250 }, { 71, 505250 }, { 72, 511250 }, { 73, 517250 }, { 74, 523250 }, { 75, 529250 }, { 76, 535250 }, { 77, 541250 }, { 78, 547250 }, { 79, 553250 }, { 80, 559250 }, { 81, 565250 }, { 82, 571250 }, { 83, 577250 }, { 84, 583250 }, { 85, 589250 }, { 86, 595250 }, { 87, 601250 }, { 88, 607250 }, { 89, 613250 }, { 90, 619250 }, { 91, 625250 }, { 92, 631250 }, { 93, 637250 }, { 94, 643250 }, { 95, 91250 }, { 96, 97250 }, { 97, 103250 }, { 98, 109250 }, { 99, 115250 } }; #define INPUT 0 // Desired color model for X to convert in hardware #define FOURCC_YV12 0x32315659 /* YV12 YUV420P */ #define FOURCC_YUV2 0x32595559 /* YUV2 YUV422 */ static int x_color_model = FOURCC_YUV2; //static int x_color_model = FOURCC_YV12; static int v4l_color_model = VIDEO_PALETTE_YUV422; //static int v4l_color_model = VIDEO_PALETTE_YUV420P; static int fd = -1; static int current_channel = 15; #define WIDTH 720 #define HEIGHT 480 #define OUTPUT_FRAMES 1 #define COLOUR 0xffff #define HUE 38000 // Specify use of progressive conversion or bus mastering transfer static int use_progressive = 1; static int x_error_handler(Display *display, XErrorEvent *event) { char string[1024]; string[0] = 0; XGetErrorText(event->display, event->error_code, string, 1024); printf("BC_Resources::x_error_handler 1: %s\n", string); return 0; } static int grab_port_id(Display* display, int screen) { int numFormats, i, j, k; unsigned int ver, rev, numAdapt, reqBase, eventBase, errorBase; int port_id = -1; XvAdaptorInfo *info; XvImageFormatValues *formats; int result = 0; //printf("grab_port_id 1\n"); // XV extension is available if(Success != XvQueryExtension(display, &ver, &rev, &reqBase, &eventBase, &errorBase)) { return -1; } //printf("grab_port_id 10\n"); // XV adaptors are available result = XvQueryAdaptors(display, DefaultRootWindow(display), &numAdapt, &info); //printf("grab_port_id 20 %d %d\n", result, numAdapt); if(!numAdapt) { return -1; } //printf("grab_port_id 30\n"); // Get adaptor with desired color model for(i = 0; i < numAdapt && port_id == -1; i++) { //printf("grab_port_id 40\n"); /* adaptor supports XvImages */ if(info[i].type & XvImageMask) { //printf("grab_port_id 50\n"); formats = XvListImageFormats(display, info[i].base_id, &numFormats); /* * for(j = 0; j < numFormats; j++) * printf("%08x\n", formats[j].id); */ for(j = 0; j < numFormats && port_id < 0; j++) { //printf("grab_port_id 60\n"); /* this adaptor supports the desired format */ if(formats[j].id == x_color_model) { /* Try to grab a port */ for(k = 0; k < info[i].num_ports; k++) { //printf("grab_port_id 70\n"); /* Got a port */ if(Success == XvGrabPort(display, info[i].base_id + k, CurrentTime)) { //printf("BC_WindowBase::grab_port_id %llx\n", info[i].base_id); port_id = info[i].base_id + k; break; } } } } if(formats) XFree(formats); } } XvFreeAdaptorInfo(info); return port_id; } static void capture_frame(int frame_number) { struct video_mmap params; params.frame = frame_number; params.width = WIDTH; params.height = HEIGHT; // Required to actually set the palette. params.format = v4l_color_model; // Tell the driver the buffer is available for writing if(ioctl(fd, VIDIOCMCAPTURE, ¶ms) < 0) perror("capture_frame VIDIOCMCAPTURE"); } void transfer_field(unsigned char *input, unsigned char *output, int field) { int i, j; /* * if(field == 1) * { * bzero(output, WIDTH * HEIGHT * 3 / 2); * return; * } */ for(i = 0; i < HEIGHT; i++) { unsigned char *in_row = input + i * WIDTH * 2; unsigned char *out_row = output + i * WIDTH * 2; // Top and bottom row // Matching rows if(i == 0 || i == HEIGHT - 1 || (!field && !(i % 2)) || (field && (i % 2))) { memcpy(out_row, in_row, WIDTH * 2); } else // Interpolated rows { unsigned char *in_row1 = in_row - WIDTH * 2; unsigned char *in_row2 = in_row + WIDTH * 2; for(j = 0; j < WIDTH * 2; j++) { *out_row++ = ((unsigned short)*in_row1++ + (unsigned short)*in_row2++) >> 1; } } } // U, V /* * memcpy(output + WIDTH * HEIGHT, * input + WIDTH * HEIGHT + WIDTH * HEIGHT / 4, * WIDTH * HEIGHT / 4); * memcpy(output + WIDTH * HEIGHT + WIDTH * HEIGHT / 4, * input + WIDTH * HEIGHT, * WIDTH * HEIGHT / 4); */ } static int get_freq(int channel) { int i; if(channel < 1) channel = 1; if(channel > 99) channel = 99; for(i = 0; i < sizeof(ntsc_cable) / sizeof(CHANLIST); i++) { if(ntsc_cable[i].channel == channel) return ntsc_cable[i].freq; } return ntsc_cable[3].freq; } static void set_channel(int channel) { int freq; printf("Setting channel %d\n", channel); freq = (int)(get_freq(channel) * 0.016); if(ioctl(fd, VIDIOCSFREQ, &freq) < 0) printf("set_channel: line %d: %s\n", __LINE__, strerror(errno)); current_channel = channel; } int main(int argc, char *argv[]) { XvImage *xv_image; unsigned char *frame_buffer; unsigned char *hardware_frame; Display* display = XOpenDisplay(":0"); int screen = DefaultScreen(display); Window rootwin = RootWindow(display, screen); Visual *vis = DefaultVisual(display, screen); int default_depth = DefaultDepth(display, screen); int mask = CWOverrideRedirect; XSetWindowAttributes attr; int w = WIDTH; int h = HEIGHT; int i, j; XSizeHints size_hints; int64_t start_time; struct timeval new_time; struct sched_param params; XShmSegmentInfo shm_info; if(argc > 1) use_progressive = atoi(argv[1]); // enable realtime mode params.sched_priority = 1; if(sched_setscheduler(0, SCHED_FIFO, ¶ms)) perror("sched_setscheduler"); attr.override_redirect = True; // Create surface to bind cursor to Window win = XCreateWindow(display, rootwin, 0, 0, w, h, 0, default_depth, InputOutput, vis, mask, &attr); // Create graphics context unsigned long gcmask; gcmask = GCGraphicsExposures; XGCValues gcvalues; // prevent expose events for every redraw gcvalues.graphics_exposures = 0; GC gc = XCreateGC(display, rootwin, gcmask, &gcvalues); XGetNormalHints(display, win, &size_hints); size_hints.flags = PPosition | PSize | PMinSize | PMaxSize | PAspect; size_hints.x = 0; size_hints.y = 0; size_hints.width = w; size_hints.height = h; size_hints.min_width = w; size_hints.max_width = w; size_hints.min_height = h; size_hints.max_height = h; size_hints.min_aspect.x = 4; size_hints.min_aspect.y = 3; size_hints.max_aspect.x = 4; size_hints.max_aspect.y = 3; XSetStandardProperties(display, win, "TV", "TV", None, 0, 0, &size_hints); // Hide cursor unsigned char cursor_data[] = { 0,0,0,0, 0,0,0,0 }; Colormap colormap = DefaultColormap(display, screen); Pixmap pixmap_bottom = XCreateBitmapFromData(display, win, cursor_data, 8, 8); XColor black, dummy; XAllocNamedColor(display, colormap, "black", &black, &dummy); Cursor cursor = XCreatePixmapCursor(display, pixmap_bottom, pixmap_bottom, &black, &black, 0, 0); XDefineCursor(display, win, cursor); XMapWindow(display, win); XFlush(display); // Initialize common capturing fd = open("/dev/video0", O_RDWR); if(fd < 0) { perror("open: "); exit(1); } struct video_channel channel; struct video_tuner tuner; struct video_window window; struct video_picture picture; struct video_buffer buffer; struct video_mbuf capture_params; int freq; // Set channel and picture tuner.tuner = 0; if(ioctl(fd, VIDIOCGTUNER, &tuner) < 0) printf("main: line %d: %s\n", __LINE__, strerror(errno)); tuner.mode = VIDEO_MODE_NTSC; if(ioctl(fd, VIDIOCSTUNER, &tuner) < 0) printf("main: line %d: %s\n", __LINE__, strerror(errno)); channel.channel = 0; channel.norm = VIDEO_MODE_NTSC; if(ioctl(fd, VIDIOCSCHAN, &channel) < 0) printf("main: line %d: %s\n", __LINE__, strerror(errno)); set_channel(current_channel); // Set capture clipping if(ioctl(fd, VIDIOCGWIN, &window) < 0) printf("main: line %d: %s\n", __LINE__, strerror(errno)); window.x = 0; window.y = 0; window.width = w; window.height = h; window.flags = 0; window.clipcount = 0; window.chromakey = 0; if(ioctl(fd, VIDIOCSWIN, &window) < 0) printf("main: line %d: %s\n", __LINE__, strerror(errno)); printf("Starting tv\n"); if(!use_progressive) { // Get framebuffer offset int base; int bytes_per_line; int bar, fred; XF86DGAGetVideoLL(display, XDefaultScreen(display), &base, &bytes_per_line, &bar, &fred); // Set colorspace if(ioctl(fd, VIDIOCGPICT, &picture) < 0) printf("main: line %d: %s\n", __LINE__, strerror(errno)); picture.colour = COLOUR; picture.hue = HUE; picture.depth = 4; picture.palette = 0; if(ioctl(fd, VIDIOCSPICT, &picture) < 0) printf("main: line %d: %s\n", __LINE__, strerror(errno)); // Set destination address for DMA if(ioctl(fd, VIDIOCGFBUF, &buffer) < 0) printf("main: line %d: %s\n", __LINE__, strerror(errno)); buffer.base = (char*)base; buffer.width = w; buffer.height = h; buffer.depth = 32; buffer.bytesperline = bytes_per_line * 4; if(ioctl(fd, VIDIOCSFBUF, &buffer) < 0) printf("main: line %d: %s\n", __LINE__, strerror(errno)); // Start video capture directly to framebuffer int one = 1; if(ioctl(fd, VIDIOCCAPTURE, &one) < 0) printf("main: line %d: %s\n", __LINE__, strerror(errno)); // Wait for a keypress that never happens getchar(); } else { int input_frame = 0; // Create XVideo bitmap XSetErrorHandler(x_error_handler); int xv_portid = grab_port_id(display, screen); if(xv_portid < 0) { printf("main: failed to get port id.\n"); exit(1); } for(i = 0; i < OUTPUT_FRAMES; i++) { xv_image = XvShmCreateImage(display, xv_portid, x_color_model, 0, w, h, &shm_info); shm_info.shmid = shmget(IPC_PRIVATE, xv_image->data_size, IPC_CREAT | 0777); if(shm_info.shmid < 0) perror("main: shmget"); frame_buffer = (unsigned char *)shmat(shm_info.shmid, NULL, 0); shmctl(shm_info.shmid, IPC_RMID, 0); xv_image->data = shm_info.shmaddr = frame_buffer; shm_info.readOnly = 0; XShmAttach(display, &shm_info); } w = xv_image->width; h = xv_image->height; // Get address of overlay in hardware // Generate random code to detect overlay in graphics memory. // This prevents detection of a previous execution's data. srand(time(0)); for(i = 0; i < 0x100; i++) { frame_buffer[i] = rand() % 0xff; } for(i = 0x100; i <= w * h * 2 - 0x100; i += 0x100) memcpy(&frame_buffer[i], frame_buffer, 0x100); // Put in graphics memory XvShmPutImage(display, xv_portid, win, gc, xv_image, 0, 0, w, h, 0, 0, w, h, False); XFlush(display); // Wait for server sleep(1); char* dga_base = 0; int dga_bytes_per_line = 0; int dga_bank_size = 0; int dga_ram_size = 0; XF86DGAGetVideo(display, screen, &dga_base, &dga_bytes_per_line, &dga_bank_size, &dga_ram_size); dga_ram_size *= 1024; /* * // Dump hardware memory * char *buffer = calloc(1, dga_ram_size); * for(i = 0; i < dga_ram_size; i += 1024 * 1024) * { * memcpy(buffer + i, dga_base + i, 1024 * 1024); * printf("%d ", i); * fflush(stdout); * } * FILE *test = fopen("/test", "w"); * fwrite(buffer, 1, dga_ram_size, test); * fclose(test); * printf("wrote frame buffer\n"); */ // Search for pattern int step = 0x10000; unsigned char *temp = calloc(1, step); int offset = 0; int got_it = 0; for(i = 0; i < dga_ram_size - 0x100 * 2 && !got_it; i += step) { memcpy(temp, dga_base + i, 0x100 * 2); for(j = 0; j < 0x100; j++) { if(!memcmp(temp + j, frame_buffer, 0x100)) { got_it = 1; offset = j + i; break; } } } if(!got_it) { printf("Overlay memory search 1 failed\n"); exit(1); } if(got_it) { got_it = 0; memcpy(temp, dga_base + offset - step + 0x100, step); for(i = 0; i < step - 0x100 * 2; i++) { if(!memcmp(temp + i, frame_buffer, 0x100)) { offset = offset - step + 0x100 + i; got_it = 1; break; } } } if(!got_it) { printf("Overlay memory search 2 failed\n"); exit(1); } free(temp); hardware_frame = dga_base + offset; printf("Got overlay hardware at offset=%x\n", offset); // Set color if(ioctl(fd, VIDIOCGPICT, &picture) < 0) printf("main: line %d: %s\n", __LINE__, strerror(errno)); picture.colour = COLOUR; picture.hue = HUE; if(ioctl(fd, VIDIOCSPICT, &picture) < 0) printf("main: line %d: %s\n", __LINE__, strerror(errno)); // Get DMA memory if(ioctl(fd, VIDIOCGMBUF, &capture_params) < 0) perror("VIDIOCGMBUF"); unsigned char *capture_buffer = (char*)mmap(0, capture_params.size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); // Start capturing for(i = 0; i < capture_params.frames; i++) { capture_frame(i); } // capture loop gettimeofday(&new_time, 0); start_time = (int64_t)new_time.tv_usec + (int64_t)new_time.tv_sec * 1000000; int64_t difference; int64_t proper_time; int64_t total_frames = 0; int output_frame = 0; while(1) { // Test keyboard input fd_set keyboard; struct timeval keyboard_timeout; FD_ZERO(&keyboard); FD_SET(0, &keyboard); keyboard_timeout.tv_sec = 0; keyboard_timeout.tv_usec = 0; int select_result = select(1, &keyboard, 0, 0, &keyboard_timeout); if(select_result > 0) { char line[1024]; char *ptr = line; while((*ptr++ = fgetc(stdin)) != '\n') ; *ptr = 0; int channel; sscanf(line, "%d", &channel); if(line[0] == 0x1b && line[1] == 0x5b && line[2] == 0x41) set_channel(current_channel + 1); else if(line[0] == 0x1b && line[1] == 0x5b && line[2] == 0x42) set_channel(current_channel - 1); else set_channel(channel); } // Wait for new frame if(ioctl(fd, VIDIOCSYNC, &input_frame)) perror("VIDIOCSYNC: try removing and reinserting the card"); // Transfer field 1 to output buffer unsigned char *input = capture_buffer + capture_params.offsets[input_frame]; transfer_field(input, hardware_frame, 0); // Delay if necessary /* * gettimeofday(&new_time, 0); * difference = ((int64_t)new_time.tv_sec * 1000000 + * (int64_t)new_time.tv_usec) - * start_time; * proper_time = (int64_t)total_frames * * (int64_t)1001 * * (int64_t)1000000 / * (int64_t)60000; * if(proper_time > difference) * { * printf("1\n"); * usleep(proper_time - difference); * } */ /* * XvShmPutImage(display, * xv_portid, * win, * gc, * xv_image, * 0, * 0, * w, * h, * 0, * 0, * w, * h, * False); * XFlush(display); */ total_frames++; output_frame++; if(output_frame >= OUTPUT_FRAMES) output_frame = 0; // Transfer field 2 transfer_field(input, hardware_frame, 1); gettimeofday(&new_time, 0); difference = ((int64_t)new_time.tv_sec * 1000000 + (int64_t)new_time.tv_usec) - start_time; proper_time = (int64_t)total_frames * (int64_t)1001 * (int64_t)1000000 / (int64_t)60000; if(proper_time > difference) { usleep(proper_time - difference); } /* * XvShmPutImage(display, * xv_portid, * win, * gc, * xv_image, * 0, * 0, * w, * h, * 0, * 0, * w, * h, * False); * XFlush(display); */ total_frames++; // Release buffer capture_frame(input_frame); input_frame++; if(input_frame >= capture_params.frames) input_frame = 0; output_frame++; if(output_frame >= OUTPUT_FRAMES) output_frame = 0; } } }