fg_rect fg_get_capture_window(fg_grabber *fg) { fg_rect rect = { 0, 0, 0, 0 }; struct v4l2_crop crop; crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (v4l2_ioctl(fg->fd, VIDIOC_S_CROP, &crop) == -1) { if (errno == EINVAL) { fg_debug_error("fg_get_capture_window(): " "device does not support cropping"); return rect; } else { fg_debug_error("fg_get_capture_window(): " "getting cropping window failed"); return rect; } } rect.left = crop.c.left; rect.top = crop.c.top; rect.width = crop.c.width; rect.height = crop.c.height; return rect; }
//-------------------------------------------------------------------------- // TODO: fg_*_channel() functions not tested at all yet. int fg_set_channel( fg_grabber* fg, float freq ) { int val, scale; struct v4l2_frequency frq; if ( !(fg->inputs[fg->input].type & V4L2_INPUT_TYPE_TUNER) ) { fg_debug_error("fg_set_channel(): current source is not a tuner"); return -1; } // TODO: is this still correct? // The LOW flag means freq in 1/16 MHz, not 1/16 kHz if ( fg->tuners[fg->tuner].capability & V4L2_TUNER_CAP_LOW ) scale = 16000; else scale = 16; val = (int)( freq * scale ); frq.tuner = fg->inputs[fg->input].tuner; frq.type = fg->tuners[fg->tuner].type; frq.frequency = val; FG_CLEAR(frq.reserved); if ( v4l2_ioctl( fg->fd, VIDIOC_S_FREQUENCY, &frq ) < 0 ) { fg_debug_error( "fg_set_channel(): failed to tune channel" ); return -1; } return 0; }
int fg_grab_frame(fg_grabber *fg, fg_frame *fr) { for (;;) { fd_set fds; struct timeval tv; int r; FD_ZERO(&fds); FD_SET(fg->fd, &fds); tv.tv_sec = FG_READ_TIMEOUT; tv.tv_usec = 0; r = select(fg->fd + 1, &fds, NULL, NULL, &tv); if ( r == -1 ) { if (EINTR == errno) continue; fg_debug_error("fg_grab_frame(): grabbing frame failed"); return -1; } if (0 == r) { fg_debug_error("fg_grab_frame(): frame grabbing timeout reached"); return -1; } if (v4l2_read(fg->fd, fr->data, fr->length) == -1) { if (errno == EAGAIN) continue; else { fg_debug_error( "fg_grab_frame(): error reading from device"); return -1; } } else { gettimeofday(&(fr->timestamp), NULL); return 0; } } return -1; }
int fg_frame_save( fg_frame* fr, const char* filename ) { if (fr->format != FG_FORMAT_RGB24) { fg_debug_error("fg_frame_save(): failed because format is not RGB24"); return -1; } FILE *outfile; if ( (outfile = fopen(filename, "wb")) == NULL) { fg_debug_error("fg_frame_save(): unable to open output file"); return -1; } struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; JSAMPROW row_pointer[1]; cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); jpeg_stdio_dest(&cinfo, outfile); cinfo.in_color_space = JCS_RGB; jpeg_set_defaults(&cinfo); cinfo.image_width = fr->size.width; cinfo.image_height = fr->size.height; cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, 80, FALSE); jpeg_start_compress(&cinfo, TRUE); while (cinfo.next_scanline < cinfo.image_height) { row_pointer[0] = &(fr->data[cinfo.next_scanline*fr->rowstride]); jpeg_write_scanlines(&cinfo, row_pointer, 1); } jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); return 0; }
int fg_set_input(fg_grabber* fg, int index) { struct v4l2_format fmt; if (index >= fg->num_inputs) { fg_debug_error("fg_set_input(): invalid input number"); return -1; } FG_CLEAR(fmt); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (v4l2_ioctl(fg->fd, VIDIOC_G_FMT, &fmt) == -1) { fg_debug_error("fg_set_input(): failed saving current format"); return -1; } while (v4l2_ioctl(fg->fd, VIDIOC_S_INPUT, &index) == -1) { switch(errno) { case EBUSY: continue; case EINVAL: default: fg_debug_error( "fg_set_input(): set input failed" ); return -1; } } // Reset the video format fg->format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fg->format.fmt.pix.width = fmt.fmt.pix.width; fg->format.fmt.pix.height = fmt.fmt.pix.height; fg->format.fmt.pix.pixelformat = fmt.fmt.pix.pixelformat; fg->format.fmt.pix.field = fmt.fmt.pix.field; if (v4l2_ioctl(fg->fd, VIDIOC_S_FMT, &(fg->format)) == -1) { fg_debug_error("fg_set_input(): resetting video format failed"); return -1; } fg->input = index; return 0; }
int fg_get_format(fg_grabber *fg) { if (v4l2_ioctl(fg->fd, VIDIOC_G_FMT, &(fg->format)) == -1) { fg_debug_error("fg_get_format(): getting video format failed"); return -1; } return fg->format.fmt.pix.pixelformat; }
int fg_get_input_type( fg_grabber* fg, int index ) { if (index > fg->num_inputs - 1) { fg_debug_error("fg_get_input_type(): invalid input number" ); return -1; } return fg->inputs[index].type; }
char *fg_get_input_name(fg_grabber *fg, int index) { if (index > fg->num_inputs - 1) { fg_debug_error("fg_get_input_name(): invalid input number" ); return NULL; } return (char *)fg->inputs[index].name; }
int fg_get_input(fg_grabber *fg) { int current_input; if (v4l2_ioctl(fg->fd, VIDIOC_G_INPUT, ¤t_input) == -1) { fg_debug_error("fg_get_input(): unable to get current input index."); return -1; } return current_input; }
void fg_unref(fg_grabber *fg) { // Make sure we free all memory (backwards!) if (v4l2_close(fg->fd) != 0) fg_debug_error("fg_close(): warning: failed closing device file"); free(fg->device); free(fg->inputs); free(fg->tuners); free(fg->controls); free(fg); }
fg_size fg_get_capture_size(fg_grabber *fg) { fg_size size = { 0, 0 }; if (v4l2_ioctl(fg->fd, VIDIOC_G_FMT, &(fg->format)) == -1) { fg_debug_error("fg_get_capture_size(): getting capture size failed"); return size; } size.width = fg->format.fmt.pix.width; size.height = fg->format.fmt.pix.height; return size; }
int fg_set_format(fg_grabber *fg, int fmt) { fg->format.fmt.pix.pixelformat = fmt; if (v4l2_ioctl(fg->fd, VIDIOC_S_FMT, &(fg->format)) == -1) { fg_debug_error("fg_set_format(): setting video format failed"); return -1; } return 0; }
int fg_set_capture_size(fg_grabber *fg, fg_size size) { fg->format.fmt.pix.width = size.width; fg->format.fmt.pix.height = size.height; if (v4l2_ioctl(fg->fd, VIDIOC_S_FMT, &(fg->format)) == -1) { fg_debug_error("fg_set_capture_size(): setting capture size to " "'%dx%d failed", size.width, size.height); return -1; } return 0; }
fg_frame *fg_grab(fg_grabber *fg) { fg_frame *fr = fg_frame_new(fg); if (fr == NULL) { fg_debug_error("fg_grab(): ran out of memory allocating frame"); return NULL; } if (fg_grab_frame(fg, fr) == -1) { free(fr); return NULL; } return fr; }
fg_frame *fg_frame_new(fg_grabber *fg) { fg_frame* fr = malloc( sizeof( fg_frame ) ); fr->size = fg_get_capture_size(fg); fr->format = fg->format.fmt.pix.pixelformat; fr->rowstride = fg->format.fmt.pix.bytesperline; fr->length = fg->format.fmt.pix.sizeimage; fr->data = malloc( fr->length ); if (fr->data == NULL) { fg_debug_error("frame_new(): ran out of memory allocating new frame"); free(fr); return NULL; } return fr; }
float fg_get_channel( fg_grabber* fg ) { int scale; struct v4l2_frequency freq; // TODO: is this correct? (original lib was backwards from set func) // The LOW flag means freq in 1/16 MHz, not 1/16 kHz if (fg->tuners[fg->tuner].capability & V4L2_TUNER_CAP_LOW) scale = 16000; else scale = 16; freq.tuner = fg->tuner; FG_CLEAR(freq.reserved); if ( v4l2_ioctl( fg->fd, VIDIOC_G_FREQUENCY, &freq ) == -1 ) { fg_debug_error( "fg_get_channel(): failed to query channel" ); return -1; } return ( freq.frequency / scale ); }
fg_grabber *fg_open(const char *dev) { fg_grabber* fg; int i; struct stat st; struct v4l2_crop crop; fg = malloc(sizeof(fg_grabber)); if (fg == NULL) { fg_debug_error( "fg_open(): ran out of memory allocating frame grabber."); return NULL; } // Make these safe to free() fg->device = NULL; fg->inputs = NULL; fg->tuners = NULL; fg->controls = NULL; // Use default device if none specified if (dev != NULL) fg->device = strdup(dev); else fg->device = strdup(FG_DEFAULT_DEVICE); // Verify the device exists if (stat(fg->device, &st) == -1) { fg_debug_error("fg_open(): video device '%s' does not exist", fg->device); goto error_exit; } // Verify the device is a character device if (!S_ISCHR(st.st_mode)) { fg_debug_error("fg_open(): video device '%s' is not a " "character device", fg->device); goto error_exit; } // Open the video device fg->fd = v4l2_open(fg->device, O_RDWR | O_NONBLOCK, 0); if ( fg->fd == -1 ) { fg_debug_error( "fg_open(): open video device failed" ); goto error_exit; } // Make sure child processes don't inherit video (close on exec) fcntl(fg->fd, F_SETFD, FD_CLOEXEC); // Get the device capabilities if (v4l2_ioctl(fg->fd, VIDIOC_QUERYCAP, &(fg->caps)) == -1) { fg_debug_error( "fg_open(): query capabilities failed" ); goto error_exit; } // Make sure video capture is supported if (!(fg->caps.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { fg_debug_error("fg_open(): video device does not support video capture"); goto error_exit; } // Determine the number of inputs if ( (fg->num_inputs = fg_get_input_count(fg)) < 1 ) { fg_debug_error("fg_open(): no video inputs were found on video device"); goto error_exit; } // Read info for all input sources fg->input = 0; fg->inputs = malloc(sizeof(struct v4l2_input) * fg->num_inputs); if (fg->inputs == NULL) { fg_debug_error("fg_open(): ran out of memory allocating inputs."); goto error_exit; } for (i=0; i < fg->num_inputs; i++) { fg->inputs[i].index = i; if (v4l2_ioctl(fg->fd, VIDIOC_ENUMINPUT, &(fg->inputs[i])) == -1) { fprintf(stderr, "%d\n", i); fg_debug_error("fg_open(): error getting input information"); goto error_exit; } } if (fg_set_input(fg, fg->input) == -1) { fg_debug_error("fg_open(): error setting default input"); goto error_exit; } // Determine the number of tuners fg->tuner = 0; if ((fg->num_tuners = count_tuners(fg->fd)) > 0) { // Read info for all tuners fg->tuners = malloc(sizeof(struct v4l2_tuner) * fg->num_tuners); if (fg->tuners == NULL) { fg_debug_error("fg_open(): ran out of memory allocating tuners"); goto error_exit; } for (i=0; i < fg->num_tuners; i++) { fg->tuners[i].index = 0; if (v4l2_ioctl(fg->fd, VIDIOC_G_TUNER, &(fg->tuners[i])) == -1) { fg_debug_error("fg_open(): error getting tuner information"); goto error_exit; } } } // Reset cropping to default (if supported) FG_CLEAR(fg->cropcap); fg->cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (v4l2_ioctl(fg->fd, VIDIOC_CROPCAP, &(fg->cropcap)) == 0) { crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; crop.c = fg->cropcap.defrect; if (v4l2_ioctl(fg->fd, VIDIOC_S_CROP, &crop) == -1) { if (errno == EINVAL) { fg_debug("fg_open(): warning: cropping not supported"); } else { fg_debug_error( "fg_open(): error setting crop window on video device"); goto error_exit; } } } else { // should be fatal because the documentation says the VIDIOC_CROPCAP // is mandatory on capture devices, but several of my devices disagree fg_debug_error("fg_open(): warning: error getting cropping info"); // goto error_exit; } // Set the video format FG_CLEAR(fg->format); fg->format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fg->format.fmt.pix.width = FG_DEFAULT_WIDTH; fg->format.fmt.pix.height = FG_DEFAULT_HEIGHT; // libv4l should intervene here to support this format even if the // device does not support it directly (phew!) fg->format.fmt.pix.pixelformat = FG_FORMAT_RGB24; fg->format.fmt.pix.field = V4L2_FIELD_INTERLACED; if (v4l2_ioctl(fg->fd, VIDIOC_S_FMT, &(fg->format)) == -1) { fg_debug_error("fg_open(): setting video format failed"); goto error_exit; } // Reset all supported controls to defaults if (fg_default_controls(fg) == -1) { fg_debug_error("fg_open(): failed setting controls to default."); goto error_exit; } // List available frame rates /* struct v4l2_frmivalenum fi; FG_CLEAR(fi); fi.index = 0; fi.pixel_format = fg->format.fmt.pix.pixelformat; fi.width = fg->format.fmt.pix.width; fi.height = fg->format.fmt.pix.height; if (v4l2_ioctl(fg->fd, VIDIOC_ENUM_FRAMEINTERVALS, &fi) == -1) { fg_debug_error("fg_open(): error enumerating frame intervals"); goto error_exit; } switch(fi.type) { case V4L2_FRMIVAL_TYPE_DISCRETE: fprintf(stderr, "DISCRETE: %d/%d\n", fi.discrete.numerator, fi.discrete.denominator); break; case V4L2_FRMIVAL_TYPE_STEPWISE: fprintf(stderr, "STEPWISE\n"); break; case V4L2_FRMIVAL_TYPE_CONTINUOUS: fprintf(stderr, "CONTINUOUS\n"); break; default: fprintf(stderr, "ERR\n"); } */ return fg; // Free memory allocated in this function and return NULL error_exit: fg_unref(fg); return NULL; }