/* * vidfs.c, started 10 July 2009 by tss * * A userland filesystem using FUSE that allows programs to peer into video * files. OpenCV's video interface is used to get access to frames. */ #include #include #include #include #include #include #include #include #include "cv.h" #include "highgui.h" #define _FILE_OFFSET_BITS 64 #define FUSE_USE_VERSION 26 #include ///////////////// //// GLOBALS //// ///////////////// // First, video information CvCapture *vid; // OpenCV video capture object unsigned int vid_next_frame; // Frame that cvQueryFrame would get next unsigned int vid_num_frames; // Number of frames in the video unsigned int vid_width; // Video frame width unsigned int vid_height; // Video frame height unsigned int vid_num_channels; // Number of image channels per pixel char vid_depth_str[8]; // (see below) unsigned int vid_row_step; // Bytes per row (see below) time_t vid_mtime; // Second-resolution mod time // Now for string versions of most of the above information (except for // vid_depth_str, already a string, and vid_next_frame and vid_mtime, // internal values). Also, one for the filename of the movie we're reading. char vid_num_frames_str[32]; // /INFO_num_frames char vid_width_str[32]; // /INFO_width char vid_height_str[32]; // /INFO_height char vid_num_channels_str[32]; // /INFO_num_channels char vid_row_step_str[32]; // /INFO_row_step char *vid_filename_str; // /INFO_movie_file // The vid_depth_str string, accessible from the mount point in the file // /INFO_depth, describes the type used to represent a single pixel in // a single channel. It has the format %c%u, where the character is either // 'U', 'S', or 'F' (unsigned, signed, or floating point value) and the // number is the number of bits used to represent the pixel. This number // can be 8, 16, 32, or 64, but in all likelihood will be 8. // The vid_row_step value (accessible in /INFO_row_step) is the number of // bytes used to represent each row. Sometimes there are gaps between rows, // that is, K bytes are allocated for each row when only J < K bytes are // needed. The first J bytes of the row are then row data, with the // remaining K-J bytes ignorable. // Here is the header we will use for each ppm file we output char ppm_header[128]; unsigned int ppm_header_size; // Here are the sizes of the "files" we return for video frames. size_t size_file_raw; size_t size_file_ppm; size_t size_file_matlab; // Now names for all the filesystem entities static const char fn_num_frames[] = "/INFO_num_frames"; static const char fn_width[] = "/INFO_width"; static const char fn_height[] = "/INFO_height"; static const char fn_num_channels[] = "/INFO_num_channels"; static const char fn_row_step[] = "/INFO_row_step"; static const char fn_depth[] = "/INFO_depth"; static const char fn_filename[] = "/INFO_movie_file"; static const char fn_dir_raw[] = "/RAWRAW"; // For dirt-easy parsing, static const char fn_dir_ppm[] = "/PPMPPM"; // these dirnames are all static const char fn_dir_matlab[] = "/MATLAB"; // six characters long, not static const unsigned int VIDFS_DIR_NAME_LEN = 7; // counting the slash. // Finally, a cache for computed PPM and MATLAB representations of the data uint8_t *cache_ppm; unsigned int cache_ppm_frameno; uint8_t *cache_matlab; unsigned int cache_matlab_frameno; // Extra finally, a CvMat and a set of CvMat headers that will help with // the OpenCV -> MATLAB conversion. CvMat *cvm_scratch_matlab; CvMat *cvm_cache_matlab_hdrs; /////////////////////////// //// UTILITY FUNCTIONS //// /////////////////////////// // Returns true iff the string is composed entirely of digits and starts // with any digit but 0---unless it is just 0. inline bool checkdigits(const char *s) { if((*s == '0') && (*(s+1) != '\0')) return false; for(; *s; ++s) if(!isdigit(*s)) return false; return true; } // Retrieve a frame from the video stream with minimum effort. Note that the // data referred to by a pointer returned by this function can change whenever // this function is called. IplImage *get_frame(const unsigned int &frameno) { // We may not have a video object if we have just daemonized, so in that // case we reopen the video. Error check, pshaw... if(vid == NULL) { // To reopen, we need to lop the \n off the filename... sigh. char *my_vid_filename_str = (char*) malloc(strlen(vid_filename_str)*sizeof(char)); strcpy(my_vid_filename_str, vid_filename_str); my_vid_filename_str[strlen(vid_filename_str)-1] = '\0'; vid = cvCreateFileCapture(my_vid_filename_str); vid_next_frame = 0; free(my_vid_filename_str); } // OK, now get the frame IplImage *result; if(vid_next_frame == frameno) { result = cvQueryFrame(vid); vid_next_frame = cvGetCaptureProperty(vid, CV_CAP_PROP_POS_FRAMES); } else if(vid_next_frame == (frameno+1)) { result = cvRetrieveFrame(vid); } else { cvSetCaptureProperty(vid, CV_CAP_PROP_POS_FRAMES, frameno); result = cvQueryFrame(vid); } return result; } // Create a PPM version of a frame and stuff it into its cache, if it isn't // there already. This is handled by the next four functions---templates make // it easier to handle disparate types---though we have special functions // for chars, which we copy directly or shift rather than scale. template void compute_ppm_inner(IplImage *img, const T&) { T *row_ptr = (T*) img->imageData; T *col_ptr = (T*) img->imageData; uint8_t *ppm_ptr = cache_ppm + ppm_header_size; const double input_range = ((double) std::numeric_limits::max()) - ((double) std::numeric_limits::min()); const double scale_factor = ((double) std::numeric_limits::max()) / input_range; const double shift = -((double) std::numeric_limits::min()) * scale_factor; // How to stride from pixel to pixel const size_t col_step = sizeof(T)*vid_num_channels; // Compute the ppm image if(vid_num_channels > 2) // handling color images for(unsigned int r_ind=0; r_indRGB ordering *ppm_ptr++ = (uint8_t) round((*(col_ptr+2))*scale_factor + shift); *ppm_ptr++ = (uint8_t) round((*(col_ptr+1))*scale_factor + shift); *ppm_ptr++ = (uint8_t) round((*(col_ptr ))*scale_factor + shift); col_ptr += col_step; } col_ptr = (row_ptr += vid_row_step); } else // handling grayscale (or two-channel images?) for(unsigned int r_ind=0; r_indimageData; int8_t *col_ptr = (int8_t*) img->imageData; uint8_t *ppm_ptr = cache_ppm + ppm_header_size; // How to stride from pixel to pixel const size_t col_step = sizeof(int8_t)*vid_num_channels; // Compute the ppm image if(vid_num_channels > 2) // handling color images for(unsigned int r_ind=0; r_indRGB ordering *ppm_ptr++ = (uint8_t) ((unsigned int) *(col_ptr+2) + 128U); *ppm_ptr++ = (uint8_t) ((unsigned int) *(col_ptr+1) + 128U); *ppm_ptr++ = (uint8_t) ((unsigned int) *(col_ptr ) + 128U); col_ptr += col_step; } col_ptr = (row_ptr += vid_row_step); } else // handling grayscale (or two-channel images?) for(unsigned int r_ind=0; r_indimageData; uint8_t *col_ptr = (uint8_t*) img->imageData; uint8_t *ppm_ptr = cache_ppm + ppm_header_size; // How to stride from pixel to pixel const size_t col_step = sizeof(uint8_t)*vid_num_channels; // Compute the ppm image if(vid_num_channels > 2) // handling color images for(unsigned int r_ind=0; r_indRGB ordering *ppm_ptr++ = *(col_ptr+2); *ppm_ptr++ = *(col_ptr+1); *ppm_ptr++ = *(col_ptr ); col_ptr += col_step; } col_ptr = (row_ptr += vid_row_step); } else // handling grayscale (or two-channel images?) for(unsigned int r_ind=0; r_inddepth) { case IPL_DEPTH_8U: compute_ppm_inner(img, (uint8_t) 0); break; case IPL_DEPTH_8S: compute_ppm_inner(img, (int8_t) 0); break; case IPL_DEPTH_16U: compute_ppm_inner(img, (uint16_t) 0); break; case IPL_DEPTH_16S: compute_ppm_inner(img, (int16_t) 0); break; case IPL_DEPTH_32S: compute_ppm_inner(img, (int32_t) 0); break; case IPL_DEPTH_32F: compute_ppm_inner(img, (float) 0); break; case IPL_DEPTH_64F: compute_ppm_inner(img, (double) 0); break; default: break; // should never happen } // Mark the cache as containing the desired image cache_ppm_frameno = frameno; } // Create a MATLAB version of a frame and stuff it into its cache void compute_matlab(const unsigned int &frameno) { if(frameno == cache_matlab_frameno) return; // Not in the cache; gotta compute it. For this we need a copy of the // image header. IplImage my_img = *get_frame(frameno); // We copy the channels out one by one and transpose them so that they are // in column-major order for MATLAB. for(unsigned int c_ind=0; c_ind= size_srcbuf) return 0; if(offset + size_read > size_srcbuf) size_read = size_srcbuf - offset; memcpy(dstbuf, srcbuf+offset, size_read); return size_read; } ////////////////////////////// //// FILESYSTEM FUNCTIONS //// ////////////////////////////// // Returns attributes of files and directories int vidfs_getattr(const char *path, struct stat *stbuf) { // Blank the stat structure, but set the mtime to match the video file. memset(stbuf, 0, sizeof(struct stat)); stbuf->st_mtime = vid_mtime; // Just so we don't have to do these comparisons more than once bool is_query_raw = false; bool is_query_ppm = false; bool is_query_matlab = false; if(!strncmp(path, fn_dir_raw, VIDFS_DIR_NAME_LEN)) is_query_raw = true; else if(!strncmp(path, fn_dir_ppm, VIDFS_DIR_NAME_LEN)) is_query_ppm = true; else if(!strncmp(path, fn_dir_matlab, VIDFS_DIR_NAME_LEN)) is_query_matlab = true; // The root directory if(!strcmp(path, "/")) { stbuf->st_mode = S_IFDIR | 0555; // read-only directory stbuf->st_nlink = 3; // it's in hellofs... stbuf->st_size = 4096; // typical dir size... } // One of, or something in, a subdirectory else if(is_query_raw || is_query_ppm || is_query_matlab) { // It's the subdirectory itself if((path[VIDFS_DIR_NAME_LEN] == '\0') || ((path[VIDFS_DIR_NAME_LEN] == '/') && (path[VIDFS_DIR_NAME_LEN] == '\0'))) { stbuf->st_mode = S_IFDIR | 0555; stbuf->st_nlink = 3; stbuf->st_size = 4096; } // No, it's a file in the subdirectory. Filenames are exclusively made // up of digits---frame numbers. else if(checkdigits(path+VIDFS_DIR_NAME_LEN+1)) { const unsigned int frameno = strtoul(path+VIDFS_DIR_NAME_LEN+1, NULL, 10); // If frame out of bounds, return no such entity if(frameno >= vid_num_frames) return -ENOENT; // It's a regular file, so... stbuf->st_mode = S_IFREG | 0644; stbuf->st_nlink = 1; // We set the file size depending on the directory of choice if(is_query_raw) stbuf->st_size = size_file_raw; else if(is_query_ppm) stbuf->st_size = size_file_ppm; else if(is_query_matlab) stbuf->st_size = size_file_matlab; } // If we get to this, it's gibberish else return -ENOENT; } // It's one of the info files else { stbuf->st_mode = S_IFREG | 0644; stbuf->st_nlink = 1; // But which one? That sets the size... if(!strcmp(path, fn_num_frames)) stbuf->st_size = strlen(vid_num_frames_str)/sizeof(char); else if(!strcmp(path, fn_width)) stbuf->st_size = strlen(vid_width_str)/sizeof(char); else if(!strcmp(path, fn_height)) stbuf->st_size = strlen(vid_height_str)/sizeof(char); else if(!strcmp(path, fn_num_channels)) stbuf->st_size = strlen(vid_num_channels_str)/sizeof(char); else if(!strcmp(path, fn_row_step)) stbuf->st_size = strlen(vid_row_step_str)/sizeof(char); else if(!strcmp(path, fn_depth)) stbuf->st_size = strlen(vid_depth_str)/sizeof(char); else if(!strcmp(path, fn_filename)) stbuf->st_size = strlen(vid_filename_str)/sizeof(char); // If we get down here, it's not a file we know about else return -ENOENT; } return 0; // Success! } // "Opens" a file---really, it just makes sure attempts to write fail. int vidfs_open(const char *path, struct fuse_file_info *fi) { // Make sure we're opening for read only if((fi->flags & O_ACCMODE) != O_RDONLY) return -EACCES; return 0; } // Not gonna bother with opendir---let's see how that goes. // Get list of files in the directory int vidfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { // The root directory if(!strcmp(path, "/")) { filler(buf, "." , NULL, 0); filler(buf, "..", NULL, 0); filler(buf, fn_dir_raw+1, NULL, 0); filler(buf, fn_dir_ppm+1, NULL, 0); filler(buf, fn_dir_matlab+1, NULL, 0); filler(buf, fn_num_frames+1, NULL, 0); filler(buf, fn_width+1, NULL, 0); filler(buf, fn_height+1, NULL, 0); filler(buf, fn_num_channels+1, NULL, 0); filler(buf, fn_row_step+1, NULL, 0); filler(buf, fn_depth+1, NULL, 0); filler(buf, fn_filename+1, NULL, 0); return 0; } // Any of the subdirectories---the filenames are all the same. This time // it pays to use the offset argument, since the number of files can be // enormous. else { if(offset < 1) if(filler(buf, ".", NULL, 1)) return 0; if(offset < 2) if(filler(buf, ".", NULL, 2)) return 0; unsigned int runner = (offset >= 2) ? offset - 2 : 0; for(; runnerimageData, size_file_raw, (uint8_t*) buf, size, offset); } else if(!strncmp(path, fn_dir_ppm, VIDFS_DIR_NAME_LEN)) { const unsigned int frameno = strtoul(path+VIDFS_DIR_NAME_LEN+1, NULL, 10); compute_ppm(frameno); return copy_core((uint8_t*) cache_ppm, size_file_ppm, (uint8_t*) buf, size, offset); } else if(!strncmp(path, fn_dir_matlab, VIDFS_DIR_NAME_LEN)) { const unsigned int frameno = strtoul(path+VIDFS_DIR_NAME_LEN+1, NULL, 10); compute_matlab(frameno); return copy_core((uint8_t*) cache_matlab, size_file_matlab, (uint8_t*) buf, size, offset); } else if(!strcmp(path, fn_num_frames)) { return copy_core((uint8_t*) vid_num_frames_str, strlen(vid_num_frames_str)/sizeof(char), (uint8_t*) buf, size, offset); } else if(!strcmp(path, fn_width)) { return copy_core((uint8_t*) vid_width_str, strlen(vid_width_str)/sizeof(char), (uint8_t*) buf, size, offset); } else if(!strcmp(path, fn_height)) { return copy_core((uint8_t*) vid_height_str, strlen(vid_height_str)/sizeof(char), (uint8_t*) buf, size, offset); } else if(!strcmp(path, fn_num_channels)) { return copy_core((uint8_t*) vid_num_channels_str, strlen(vid_num_channels_str)/sizeof(char), (uint8_t*) buf, size, offset); } else if(!strcmp(path, fn_row_step)) { return copy_core((uint8_t*) vid_row_step_str, strlen(vid_row_step_str)/sizeof(char), (uint8_t*) buf, size, offset); } else if(!strcmp(path, fn_depth)) { return copy_core((uint8_t*) vid_depth_str, strlen(vid_depth_str)/sizeof(char), (uint8_t*) buf, size, offset); } else if(!strcmp(path, fn_filename)) { return copy_core((uint8_t*) vid_filename_str, strlen(vid_filename_str)/sizeof(char), (uint8_t*) buf, size, offset); } else return -ENOENT; } //////////////////////// //// INITIALIZATION //// //////////////////////// // Open the video file, do some checks, and get info about it. void init_video(int argc, char **argv) { // Before getting started with FUSE, let's try opening the video and // getting some information about it. vid = cvCreateFileCapture(argv[1]); if(vid == NULL) { fprintf(stderr, "%s: failed to open video file %s\n", argv[0], argv[1]); exit(-1); } vid_num_frames = (unsigned int) cvGetCaptureProperty(vid, CV_CAP_PROP_FRAME_COUNT); vid_width = (unsigned int) cvGetCaptureProperty(vid, CV_CAP_PROP_FRAME_WIDTH); vid_height = (unsigned int) cvGetCaptureProperty(vid, CV_CAP_PROP_FRAME_HEIGHT); // Get creation time of the movie file struct stat movie_file_stat; if(stat(argv[1], &movie_file_stat)) { fprintf(stderr, "%s: failed to stat video file %s\n", argv[0], argv[1]); exit(-1); } vid_mtime = movie_file_stat.st_mtime; // Some sanity checks, and then the next step is to load a frame to get // information about the pixel format. if((vid_num_frames <= 0) || (vid_width <= 0) || (vid_height <= 0)) { fprintf(stderr, "%s: video file %s empty or malformed\n", argv[0], argv[1]); exit(-1); } IplImage *img = cvQueryFrame(vid); vid_num_channels = img->nChannels; unsigned int num_bytes; // used in computing file sizes switch(img->depth) { case IPL_DEPTH_8U: strcpy(vid_depth_str, "U8\n"); num_bytes = sizeof(uint8_t); break; case IPL_DEPTH_8S: strcpy(vid_depth_str, "S8\n"); num_bytes = sizeof(int8_t); break; case IPL_DEPTH_16U: strcpy(vid_depth_str, "U16\n"); num_bytes = sizeof(uint16_t); break; case IPL_DEPTH_16S: strcpy(vid_depth_str, "S16\n"); num_bytes = sizeof(int16_t); break; case IPL_DEPTH_32S: strcpy(vid_depth_str, "S32\n"); num_bytes = sizeof(int32_t); break; case IPL_DEPTH_32F: strcpy(vid_depth_str, "F32\n"); num_bytes = sizeof(float); break; case IPL_DEPTH_64F: strcpy(vid_depth_str, "F64\n"); num_bytes = sizeof(double); break; default: fprintf(stderr, "%s: video file %s has unrecognized or unsupported pixel type\n", argv[0], argv[1]); cvReleaseCapture(&vid); exit(-1); } vid_row_step = img->widthStep; if(img->dataOrder != 0) { fprintf(stderr, "%s: planar pixel format in %s is unsupported, sorry!\n", argv[0], argv[1]); exit(-1); } // We can now create the PPM header ppm_header_size = snprintf(ppm_header, 128, "P6\n%u\n%u\n255\n", vid_width, vid_height)*sizeof(char); // yes, * // Compute the sizes we can expect for the various files that // this filesystem will serve. Note that PPM files are always 3 bytes // per pixel color. size_file_raw = vid_height*vid_row_step; size_file_matlab = vid_height*vid_width *vid_num_channels*num_bytes; size_file_ppm = vid_height*vid_width *3 *1 + ppm_header_size; // Allocate the caches for PPM and MATLAB data and indicate that they // contain invalid values. cache_ppm = (uint8_t*) malloc(size_file_ppm); cache_matlab = (uint8_t*) malloc(size_file_matlab); cache_ppm_frameno = std::numeric_limits::max(); cache_matlab_frameno = std::numeric_limits::max(); // Allocate scratch space and headers for the OpenCV->MATLAB conversion int cv_mat_type = 0; switch(img->depth) { // love this so much! opencv so awesome!@!!!! case IPL_DEPTH_8U: cv_mat_type = CV_8UC1; break; case IPL_DEPTH_8S: cv_mat_type = CV_8SC1; break; case IPL_DEPTH_16U: cv_mat_type = CV_16UC1; break; case IPL_DEPTH_16S: cv_mat_type = CV_16SC1; break; case IPL_DEPTH_32S: cv_mat_type = CV_32SC1; break; case IPL_DEPTH_32F: cv_mat_type = CV_32FC1; break; case IPL_DEPTH_64F: cv_mat_type = CV_64FC1; break; default: break; // should not happen } cvm_scratch_matlab = cvCreateMat(vid_height, vid_width, cv_mat_type); cvm_cache_matlab_hdrs = (CvMat*) malloc(vid_num_channels*sizeof(CvMat)); // note reversal of height/width arguments, since we are creating // column-major representations for MATLAB. for(unsigned int c_ind=0; c_ind