/************************************************************************* * * PROGRAM: mj2j.c (Motion JPEG to JFIF) * * AUTHOR: Tom Bouril (August 1997) * * DESCRIPTION: Extracts frames of JPEG data from a motion-JPEG movie * file (created with Parallax's MovieTool program) and * stores those JPEG frames into individual JFIF files. * (JFIF files are JPEG data with a header attached.) * * INSTRUCTIONS: There are two options you have for running this program * from the command line. The two options are: * * 1) > mj2j filename * * This will display the struct fields of the motion-JPEG * movie file's "jpheader" so you can determine information * about the file. * * 2) > mj2j filename_1 X Y filename_2 * * filename_1 = The name of the Motion-JPEG file from which * you want to strip JFIF images from. * * X = The frame number of the first image you want * to save. (The first image is Frame No. 0) * * Y = The frame number of the last image you want * to save. (Y >= X) * * filename_2 = The prefix name of the files to which images * will be saved. See example below. * * EXAMPLES: * * > mj2j DemoMovie 134 259 DemoFrame. * * This command will store frame numbers 134 through 259 of the DemoMovie * file into 126 separate JFIF files named DemoFrame134, DemoFrame.135, * DemoFrame.136, ..., DemoFrame.258, DemoFrame.259. The DemoMovie file * will NOT be altered. * * The first frame of a movie file is frame number ZERO. If you want to * create a JPEG image of ONLY the first frame of a movie file, enter a * command like this: * * > mj2j DemoMovie 0 0 DemoFrame * * If you want to create individual JPEG files for all 200 * frames of a 200-frame movie file, enter a command like this: * * > mj2j DemoMovie 0 199 DemoFrame * * WARNING: When recording a movie, it is not uncommon for a small * percentage of frames to be dropped. In other words, * even though the "frames" variable of the "jpheader" struct * may equal 100, it is possible that only 98 frames were * recorded to the file. (This occurs because sometimes the * system perfomance cannot keep up with frames-per-second * demand.) If, for example, frames 0 and 83 were dropped while * recording a 100-frame movie, and you tried saving all 100 * of these frames to JFIF files, you would NOT get a JFIF * file for frames 0 and 83. * *************************************************************************/ #include #include #include #include #include #include #include #include "jfif.h" // Used by SaveJFIF() function. /************************************************************************* * * FUNCTION PROTOTYPES * *************************************************************************/ int CheckForFrameIndexErrors(int, int, jpheader *); void ProcessFile(FILE *, jpheader *, char **argv); int SaveJFIF(XPlxCImage *, char *, unsigned char *, int); char *ParseJFIF(unsigned char *); /************************************************************************* * * FUNCTION: main() * * DESCRIPTION: Read in-line comments below for details. * *************************************************************************/ void main(int argc, char **argv) { FILE *MJFile; // MJPEG file jpheader MJHdr; // MJPEG file header if (argc >= 2) { // Open file specified on the first command-line argument. if ((MJFile = fopen(argv[1], "rb")) == NULL) { fprintf(stderr, "Cannot access file: %s\n", argv[1]); fprintf(stderr, "Program ABORTED.\n"); exit(0); } // Read file header and confirm it's a MJPEG file. fread(&MJHdr, sizeof(jpheader), 1, MJFile); if (strcmp(MJHdr.magic, "j_movie")) { fprintf(stderr, "File \"%s\" is NOT a MJPEG file--ABORT!\n", argv[1]); fclose(MJFile); exit(0); } if (argc == 2) { // Print out contents of MJPEG Header. printf("Contents of MJPEG file header is:\n\n"); printf("version: = %d (Version No.)\n", MJHdr.version); printf("fps = %d (Frames Per Second)\n", MJHdr.fps); printf("frames = %d (Total No. of frames)\n", MJHdr.frames); printf("width = %d (Video width in pixels)\n", MJHdr.width); printf("height = %d (Video height in pixels)\n", MJHdr.height); printf("bandwidth = %d (Bandwidth in KBytes/sec.)\n", MJHdr.bandwidth); printf("qfactor = %d (Compression factor)\n", MJHdr.qfactor); printf("mapsize = %d (No. of colors in colormap)\n", MJHdr.mapsize); printf("indexbuf = %d (File offset of frame indexes)\n", MJHdr.indexbuf); printf("tracks = %d (No. of audio tracks)\n", MJHdr.tracks); printf("unused = Unused field\n"); printf("audioslice = %d (No. of audio bytes per frame)\n", MJHdr.audioslice); } else if (argc == 5) { // Create JFIF image(s) from Motion-JPEG file. ProcessFile(MJFile, &MJHdr, argv); } else { fprintf(stderr, "Incorrect number of arguments entered on command line.\n"); fprintf(stderr, "Program ABORTED\n"); } } else { fprintf(stderr, "No filename was specified on command line.\n"); fprintf(stderr, "Program ABORTED\n"); exit(0); } fclose(MJFile); return; } /************************************************************************* * * FUNCTION: ProcessFile() * * DESCRIPTION: Parse the motion JPEG file and save the desired frames * to individual JFIF files. * *************************************************************************/ void ProcessFile(FILE *MJFile, jpheader *MJHdr, char **argv) { char *Filename, *FrameContents, Postfix[6]; Display *Disp; int *FrameOffset, FrameSize, LoFrameIndex = atoi(argv[2]), HiFrameIndex = atoi(argv[3]), i; unsigned char *pQTable, Operand; // MUST be unsigned! XPlxCImage *Image; // Check validity of frame index numbers entered on command line. if (CheckForFrameIndexErrors(LoFrameIndex, HiFrameIndex, MJHdr)) { return; // Frame index numbers NOT valid. Abort program. } // Open XDisplay Disp = XOpenDisplay(getenv("DISPLAY")); if (Disp == NULL) { fprintf(stderr, "Can't connect to X Server--ABORT\n"); exit(0); } // Allocate memory to store Filename. Filename = (char *) malloc(strlen(argv[4]) + 6); // Allocate a FrameOffset[] array. FrameOffset = (int *) malloc(MJHdr->frames * sizeof(int)); // Obtain the frame offsets. FrameOffset[X] will then // contain the file offset of Frame No. X. fseek(MJFile, MJHdr->indexbuf, SEEK_SET); fread(FrameOffset, sizeof(int), MJHdr->frames, MJFile); // Create a single JPEG file for each frame requested. for (i = LoFrameIndex; i <= HiFrameIndex; i++) { // Create filename for the new JPEG file. strcpy(Filename, argv[4]); // Filename from command line. sprintf(Postfix, "%d", i); // Convert int to string. strcat(Filename, Postfix); // Append frame No. to filename. // Go to start of frame. fseek(MJFile, FrameOffset[i], SEEK_SET); Operand = CLEAR_FRAME; // Init. to nonsense to enter while() loop. while (Operand != END_FRAME) { // Read 1-byte Movie File Operand. fread(&Operand, 1, 1, MJFile); switch(Operand) { case LOAD_AUDIO_0: // Skip past audio data. fseek(MJFile, MJHdr->audioslice, SEEK_CUR); break; case LOAD_JPEG: // Read 4-byte FrameSize, then allocate an array to // store the frame and read the frame from file. fread(&FrameSize, sizeof(int), 1, MJFile); FrameContents = (char *) malloc(FrameSize); fread(FrameContents, 1, FrameSize, MJFile); MakeQTables(MJHdr->qfactor, &pQTable); Image = XPlxCreateCImage(Disp, FrameContents, FrameSize, (unsigned int) MJHdr->width, (unsigned int) MJHdr->height); // Check to see if FrameContents contains a JFIF header. // If the movie consists of images with JFIF headers, // the data must be saved without appending another header. if ((int) ParseJFIF(FrameContents) > 0) { // Image already contains a JFIF header. Write only the // contents of "Image" to the file. SaveJFIF(Image, Filename, pQTable, 1); } else { // Append a JFIF header to the Image data and save it to file. SaveJFIF(Image, Filename, pQTable, 0); } XPlxDestroyCImage(Image); free(pQTable); free(FrameContents); break; default: // Skip past all other frame types. break; } // End: switch(Operand) } // End: while() loop } // End: for() loop. free(FrameOffset); free(Filename); XCloseDisplay(Disp); return; } /************************************************************************* * * FUNCTION: CheckForFrameIndexErrors() * * DESCRIPTION: Read in-line comments below. * *************************************************************************/ int CheckForFrameIndexErrors(int Lo, int Hi, jpheader *MJHdr) { // Check for negative frame index numbers. if (Hi < 0 || Lo < 0) { fprintf(stderr, "Negative frame index numbers NOT accepted.\n"); fprintf(stderr, "Program ABORTED\n"); return(1); } // Check to see if HiFrameIndex < LoFrameIndex. if (Hi < Lo) { fprintf(stderr, "Last frame index is less than first frame index.\n"); fprintf(stderr, "Program ABORTED\n"); return(2); } // Make certain frame range entered on command line does not exceed // the number of frames in the file. if (Hi >= MJHdr->frames) { fprintf(stderr, "Frame range on command line exceeds frames in the file.\n"); fprintf(stderr, "Program ABORTED\n"); return(3); } return(0); // NO errors! } /************************************************************************** * * FUNCTION: SaveJFIF() * * DESCRIPTION: If "Header" parameter is FALSE, attach a JFIF header to the * image and save it to a file. If the "Header" parameter is * TRUE, do NOT attach a JFIF header to the image data--save * only the image data to a file. * **************************************************************************/ int SaveJFIF(XPlxCImage *image, char *file, unsigned char *table, int Header) { int fd, i, offset; if (image == NULL) { // JPEG image passed to this function was NULL--ABORT. return(-1); } if ((fd = open(file, O_CREAT | O_WRONLY | O_TRUNC, 0644)) == -1) { fprintf(stderr, "Could not create a file for JFIF file.\n"); return(-1); } if (Header) { // Image data already contains JFIF header. Do NOT append another // header. Save ONLY the contents of variable "image" to the file. if (write(fd, image->data, image->size) < 0) { fprintf(stderr, "write() failed!\n"); close(fd); unlink(file); return(-1); } close(fd); return(0); } // Update width and height of compressed image. jfif_header[25] = (image->height >> 8) & 0xff; jfif_header[26] = (image->height) & 0xff; jfif_header[27] = (image->width >> 8) & 0xff; jfif_header[28] = (image->width) & 0xff; // Load the quantization tables specified by the user. for (i = 0; i < 64; i++) { jfif_header[i+ 44] = table[i + 5]; jfif_header[i+109] = table[i + 70]; } // Output the JFIF header info. if (write(fd, jfif_header, sizeof(jfif_header)) < 0) { fprintf(stderr, "JFIF header write failed!\n"); close(fd); unlink(file); return(-1); } if (image->size >= 4) { // On the HP platform, the first 4 bytes of the image are garbage. // This situation is fixed by the driver, which inserts 0xffffffd0 // into this space (which the CCube chip will ignore. Software // decoders will not parse this however, so when we write JFIF // files, we must remove the first 4 bytes. #if 0 unsigned int *firstDWord = image->data; if (*firstDWord == 0xffffffd0) { #else if (0xffffffd0 == *(unsigned int*) image->data) { #endif offset = 4; } else { offset = 0; } } // Save the compressed image data. if (write(fd, image->data + offset, image->size - offset) < 0) { fprintf(stderr, "JFIF data write failed!\n"); close(fd); unlink(file); return(-1); } close(fd); return(0); } /************************************************************************** * * FUNCTION: ParseJFIF() * * DESCRIPTION: Inspects a memory buffer to see if it contains a JFIF * header. * * RETURN: If the return value is NEGATIVE, the buffer contains no * JFIF header. If the return value is POSITIVE, the value * returned is the start of the JPEG data. If the return * value is ZERO, I do NOT know what that means!!! * **************************************************************************/ char *ParseJFIF(unsigned char *buffer) { unsigned char *buf, c, *p, qtab[128], htab[1024]; int count, done, haveq, haveh, height, i, j, k, l, m, n, rsti = 0, width; buf = buffer; done = count = haveh = haveq = 0; while (!done) { c = *buf++; count++; if (c != 0xff) { return((char*)-1); } c = *buf++; count++; switch (c) { case 0xd8: // break; case 0xfe: // COM p = buf; i = ((int) *p++) << 8; i += ((int) *p++); count += i; buf += i; break; case 0xdd: // RST p = buf; i = ((int) *p++) << 8; i += ((int) *p++); rsti = ((int) *p++) << 8; rsti += ((int) *p++); count += i; buf += i; break; case 0xe0: // APP0 p = buf; i = ((int) *p++) << 8; i += ((int) *p++); count += i; if (*p++ != 'J') return((char*)-2); if (*p++ != 'F') return((char*)-2); if (*p++ != 'I') return((char*)-2); if (*p++ != 'F') return((char*)-2); buf += i; break; case 0xdb: // DQT // Create the Q-Table p = buf; i = ((int) *p++) << 8; i += ((int) *p++); count += i; buf += i; break; case 0xc0: // SOF p = buf; i = ((int) *p++) << 8; i += ((int) *p++); count += i; if (*p++ != 8) { return((char*)-4); } height = ((int) *p++) << 8; height += ((int) *p++); width = ((int) *p++) << 8; width += ((int) *p++); if ((j = *p++) != 3) { return((char*)-5); } for (k=0; k> 4) & 0xf; n = n & 0xf; switch (l) { case 1: if ((m != 2) || (n != 1)) { return((char*)-5); } break; case 2: case 3: if ((m != 1) || (n != 1)) { return((char*)-5); } break; default: return((char*)0); } } // End: for (k = 0...) buf += i; break; // End: case 0xc0: case 0xc4: // DHT p = buf; i = ((int) *p++) << 8; i += ((int) *p++); count += i; j = i - 2; while (j) { htab[haveh++] = *p++; j--; l = 0; for (k=0; k<16; k++) { htab[haveh] = *p++; l += htab[haveh++]; } j -= 16; for (k=0; k> 4) & 0xf; n = n & 0xf; switch (l) { case 1: if ((m != 0) || (n != 0)) { return((char*)-5); } break; case 2: case 3: if ((m != 1) || (n != 1)) { return((char*)-5); } break; default: return((char*)0); } } done = 1; buf += i; break; default: return((char*)-1); } // End: switch(c) } // End: while(!done) #if 0 if (!haveh || !haveq) { // Make sure the file defines qtables and htables return((char*)-6); } #endif return(buf); }