From: Mike L. <mli...@us...> - 2002-09-16 20:45:36
|
Update of /cvsroot/bitcollider/bitcollider/video In directory usw-pr-cvs1:/tmp/cvs-serv31002/video Added Files: Makefile.am avi.c mpeg.c quicktime.c video.c video.h Log Message: Delirium's video plugin --- NEW FILE: Makefile.am --- # (PD) 2001 The Bitzi Corporation # Please see file COPYING or http://bitzi.com/publicdomain # for more info. # # $Id: Makefile.am,v 1.1 2002/09/16 20:45:33 mlinksva Exp $ # AUTOMAKE_OPTIONS = foreign INCLUDES = -I$(top_srcdir)/.. -I$(top_srcdir)/ver -I$(top_srcdir)/include lib_LTLIBRARIES = libvideo.la libvideo_la_SOURCES = video.c mpeg.c quicktime.c avi.c libvideo_la_LDFLAGS = -module -avoid-version all: mkdir -p $(top_srcdir)/plugins rm -f $(top_srcdir)/plugins/video.bcp ln -s $(top_srcdir)/video/.libs/libvideo.so $(top_srcdir)/plugins/video.bcp clean distclean: rm -f $(top_srcdir)/plugins/video.bcp --- NEW FILE: avi.c --- /* AVI parsing module for the Bitzi Bitcollider video plugin * * (PD) 2002 Mark Nelson [delirium] <del...@ru...> * Please see file COPYING or http://bitzi.com/publicdomain for more * information. * * The primary reference used for the AVI file format was the file format * section of John McGowan's AVI Overview: * http://www.jmcgowan.com/avitech.html#Format * The full AVI Overview is available at: * http://www.jmcgowan.com/avi.html */ #include "video.h" /* AVI uses little-endian ordering, and block sizes count only bytes after * the block size integer. */ void parse_avi(FILE *file, Data *data) { char fourcc[5]; /* Buffer in which to store fourccs */ unsigned blockLen; /* Length of the current block */ fseek(file, 12L, SEEK_SET); /* We've already checked signature */ /* Verify existence of and read length of AVI header: * "LIST____hdrlavih____" * where the first ____ is the length of the LIST block */ fread(fourcc, sizeof(char), 4, file); if(memcmp(fourcc, "LIST", 4)!=0) return; fseek(file, 4L, SEEK_CUR); fread(fourcc, sizeof(char), 4, file); if(memcmp(fourcc, "hdrl", 4)!=0) return; fread(fourcc, sizeof(char), 4, file); if(memcmp(fourcc, "avih", 4)!=0) return; blockLen = fread_le(file, 4); /* Now we're at the start of the AVI header */ /* 0: microseconds per frame (4 bytes) */ data->fps = (unsigned int) round_double((double) 1.0e6 / fread_le(file, 4)); fseek(file, 12L, SEEK_CUR); /* 16: total frames (4 bytes) */ data->duration = (unsigned int) round_double((double) fread_le(file, 4) * 1000 / data->fps); fseek(file, 12L, SEEK_CUR); /* 32: width (4 bytes) */ data->width = (unsigned int) fread_le(file, 4); /* 36: height (4 bytes) */ data->height = (unsigned int) fread_le(file, 4); /* Skip rest of avi header */ fseek(file, (long) blockLen - 40, SEEK_CUR); /* Verify existence of and read length of video stream header: * "LIST____strlstrh____vids" */ fread(fourcc, sizeof(char), 4, file); if(memcmp(fourcc, "LIST", 4)!=0) return; blockLen = fread_le(file, 4); fread(fourcc, sizeof(char), 4, file); if(memcmp(fourcc, "strl", 4)!=0) return; fread(fourcc, sizeof(char), 4, file); if(memcmp(fourcc, "strh", 4)!=0) return; fseek(file, 4L, SEEK_CUR); fread(fourcc, sizeof(char), 4, file); if(memcmp(fourcc, "vids", 4)!=0) return; /* Now we're in the video stream header */ /* 16: FOURCC of video codec (4 bytes)*/ fread(fourcc, sizeof(char), 4, file); fourcc[4] = '\0'; data->codec = strdup(fourcc); /* Skip rest of video stream header */ fseek(file, (long) blockLen - 20, SEEK_CUR); /* Verify existence of audio stream header: * "LIST____strlstrh____auds" * Note: audio stream header is optional */ /* This is commented out since we're not reading audio data anyway (yet) */ /* fread(fourcc, sizeof(char), 4, file); if(memcmp(fourcc, "LIST", 4)!=0) return; fseek(file, 4L, SEEK_CUR); fread(fourcc, sizeof(char), 4, file); if(memcmp(fourcc, "strl", 4)!=0) return; fread(fourcc, sizeof(char), 4, file); if(memcmp(fourcc, "strh", 4)!=0) return; fseek(file, 4L, SEEK_CUR); fread(fourcc, sizeof(char), 4, file); if(memcmp(fourcc, "auds", 4)!=0) return; */ /* Now we're in the audio stream header */ /* TODO: extract some information about audio stream encoding */ } --- NEW FILE: mpeg.c --- /* MPEG-1/MPEG-2 parsing module for the Bitzi Bitcollider video plugin * * (PD) 2002 Mark Nelson [delirium] <del...@ru...> * Please see file COPYING or http://bitzi.com/publicdomain for more * information. * * The primary references used for the MPEG-1/2 stream formats were: * http://www.andrewduncan.ws/MPEG/MPEG-1_Picts.html and * http://www.andrewduncan.ws/MPEG/MPEG-2_Picts.html */ #include "video.h" /* Returns 1 or 2 to indicate MPEG-1 or MPEG-2 * * Most MPEG data is stored in bits not necessarily aligned on byte boundaries; * bits are ordered most-significant first, so big-endian of a sort. * Block sizes only count bytes after the block size integer. */ int parse_mpeg(FILE *file, Data *data) { int version = 0; /* MPEG-1/2; our return value */ uint32 temp; /* First check if this is a Program stream (multiplexed audio/video), * and handle Pack header if so */ temp = fread_be(file, 4); if(temp == 0x000001BA) { /* Figure out if this is an MPEG-1 or MPEG-2 program */ temp = (uint32) fgetc(file); if((temp & 0xF0) == 0x20) /* binary 0010 xxxx */ version = 1; else if((temp & 0xC0) == 0x40) /* binary 01xx xxxx */ version = 2; else return 0; if(version == 1) { fseek(file, 4L, SEEK_CUR); data->bitrate = (unsigned int) round_double((double)((fread_be(file, 3) & 0x7FFFFE) >> 1) * 0.4); } else { fseek(file, 5L, SEEK_CUR); data->bitrate = (unsigned int) round_double((double)((fread_be(file, 3) & 0xFFFFFC) >> 2) * 0.4); temp = fgetc(file) & 0x07; /* stuffing bytes */ if(temp != 0) fseek(file, (long) temp, SEEK_CUR); } /* Skip any other blocks we find until we get to a video stream, which * might be within a 2nd PACK */ temp = fread_be(file, 4); while(temp != 0x000001BA && temp != 0x000001E0) { if(feof(file)) /* shouldn't happen */ return version; if(temp == 0x00000000) /* Skip past zero padding */ { while((temp & 0xFFFFFF00) != 0x00000100) { if(feof(file)) return version; /* shouldn't happen here either */ temp <<= 8; temp |= fgetc(file); } } else { temp = fread_be(file, 2); fseek(file, (long) temp, SEEK_CUR); temp = fread_be(file, 4); } } /* Now read byte by byte until we find the 0x000001B3 instead of actually * parsing (due to too many variations). Theoretically this could mean * we find 0x000001B3 as data inside another packet, but that's extremely * unlikely, especially since the sequence header should not be far */ temp = fread_be(file, 4); while(temp != 0x000001B3) { if(feof(file)) /* No seq. header; shouldn't happen */ return version; temp <<= 8; temp |= fgetc(file); } } else /* video stream only */ fseek(file, 4L, SEEK_SET); /* Now we're just past the video sequence header start code */ temp = fread_be(file, 3); data->width = (temp & 0xFFF000) >> 12; data->height = temp & 0x000FFF; switch(fgetc(file) & 0x0F) { case 1: /* 23.976 fps */ case 2: /* 24 fps */ data->fps = 24; break; case 3: /* 25 fps */ data->fps = 25; break; case 4: /* 29.97 fps */ case 5: /* 30 fps */ data->fps = 30; break; case 6: /* 50 fps */ data->fps = 50; break; case 7: /* 59.94 fps */ case 8: /* 60 fps */ data->fps = 60; break; } if(data->bitrate == 0) /* if this is a video-only stream, */ { /* get bitrate from here */ temp = (fread_be(file, 3) & 0xFFFFC0) >> 6; if(temp != 0x3FFFF) /* variable bitrate */ data->bitrate = (unsigned int) round_double((double) temp * 0.4); } else fseek(file, 3L, SEEK_CUR); /* If MPEG-2 or don't know yet, look for the sequence header extension */ if(version != 1) { /* Skip past rest of sequence header and 64-byte matrices (if any) */ temp = fgetc(file); if(temp & 0x02) { fseek(file, 63L, SEEK_CUR); temp = fgetc(file); } if(temp & 0x01) fseek(file, 64L, SEEK_CUR); temp = fread_be(file, 4); if(temp == 0x000001B5) { if(version == 0) version = 2; fseek(file, 1L, SEEK_CUR); /* extensions specify MSBs of width/height */ temp = fread_be(file, 2); data->width |= (temp & 0x0180) << 5; data->height |= (temp & 0x0060) << 7; fseek(file, 2L, SEEK_CUR); /* and a numerator/denominator multiplier for fps */ temp = fgetc(file); if((temp & 0x60) && (temp & 0x1F)) data->fps = (unsigned int) round_double((double)data->fps * (temp & 0x60)/(temp & 0x1F)); } else if(version == 0) version = 1; } return version; } --- NEW FILE: quicktime.c --- /* QuickTime parsing module for the Bitzi Bitcollider video plugin * * (PD) 2002 Mark Nelson [delirium] <del...@ru...> * Please see file COPYING or http://bitzi.com/publicdomain for more * information. * * The primary reference for the QuickTime file format is: * http://developer.apple.com/techpubs/quicktime/qtdevdocs/QTFF/qtff.html */ #include "video.h" /* QuickTime uses big-endian ordering, and block ("atom") lengths include the * entire atom, including the fourcc specifying atom type and the length * integer itself. */ void parse_quicktime(FILE *file, Data *data) { char fourcc[5]; unsigned blockLen; unsigned subBlockLen; unsigned subSubBlockLen; unsigned timescale; long blockStart; long subBlockStart; long subSubBlockStart; fseek(file, 4L, SEEK_SET); fread(fourcc, sizeof(char), 4, file); /* If data is first, header's at end of file, so skip to it */ if(memcmp(fourcc, "mdat", 4)==0) { fseek(file, 0L, SEEK_SET); blockLen = fread_be(file, 4); fseek(file, (long) (blockLen + 4), SEEK_SET); fread(fourcc, sizeof(char), 4, file); } if(memcmp(fourcc, "moov", 4)!=0) return; blockStart = ftell(file); blockLen = fread_be(file, 4); /* mvhd length */ fread(fourcc, sizeof(char), 4, file); if(memcmp(fourcc, "mvhd", 4)!=0) return; /* Now we're at the start of the movie header */ /* 20: time scale (time units per second) (4 bytes) */ fseek(file, blockStart + 20, SEEK_SET); timescale = fread_be(file, 4); /* 24: duration in time units (4 bytes) */ data->duration = (unsigned int) round_double((double) fread_be(file, 4) / timescale * 1000); /* Skip the rest of the mvhd */ fseek(file, blockStart + blockLen, SEEK_SET); /* Find and parse trak atoms */ while(!feof(file)) { unsigned int width, height; /* Find the next trak atom */ blockStart = ftell(file); blockLen = fread_be(file, 4); /* trak (or other atom) length */ fread(fourcc, sizeof(char), 4, file); if(memcmp(fourcc, "trak", 4)!=0) /* If it's not a trak atom, skip it */ { if(!feof(file)) fseek(file, blockStart + blockLen, SEEK_SET); continue; } subBlockStart = ftell(file); subBlockLen = fread_be(file, 4); /* tkhd length */ fread(fourcc, sizeof(char), 4, file); if(memcmp(fourcc, "tkhd", 4)!=0) return; /* Now in the track header */ /* 84: width (2 bytes) */ fseek(file, subBlockStart + 84, SEEK_SET); width = fread_be(file, 2); /* 88: height (2 bytes) */ fseek(file, subBlockStart + 88, SEEK_SET); height = fread_be(file, 2); /* Note on above: Apple's docs say that width/height are 4-byte integers, * but all files I've seen have the data stored in the high-order two * bytes, with the low-order two being 0x0000. Interpreting it the * "official" way would make width/height be thousands of pixels each. */ /* Skip rest of tkhd */ fseek(file, subBlockStart + subBlockLen, SEEK_SET); /* Find mdia atom for this trak */ subBlockStart = ftell(file); subBlockLen = fread_be(file, 4); fread(fourcc, sizeof(char), 4, file); while(memcmp(fourcc, "mdia", 4)!=0) { fseek(file, subBlockStart + subBlockLen, SEEK_SET); subBlockStart = ftell(file); subBlockLen = fread_be(file, 4); fread(fourcc, sizeof(char), 4, file); } /* Now we're in the mdia atom; first sub-atom should be mdhd */ subSubBlockStart = ftell(file); subSubBlockLen = fread_be(file, 4); fread(fourcc, sizeof(char), 4, file); if(memcmp(fourcc, "mdhd", 4)!=0) return; /* TODO: extract language from the mdhd? For now skip to hdlr. */ fseek(file, subSubBlockStart + subSubBlockLen, SEEK_SET); subSubBlockStart = ftell(file); subSubBlockLen = fread_be(file, 4); fread(fourcc, sizeof(char), 4, file); if(memcmp(fourcc, "hdlr", 4)!=0) return; /* 12: Component type: "mhlr" or "dhlr"; we only care about mhlr, * which should (?) appear first */ fseek(file, subSubBlockStart + 12, SEEK_SET); fread(fourcc, sizeof(char), 4, file); if(memcmp(fourcc, "mhlr", 4)!=0) return; fread(fourcc, sizeof(char), 4, file); if(memcmp(fourcc, "vide", 4)==0) /* This is a video trak */ { data->height = height; data->width = width; } /* Skip rest of the trak */ fseek(file, blockStart + blockLen, SEEK_SET); } } --- NEW FILE: video.c --- /* Bitzi Bitcollider video plugin * * (PD) 2002 Mark Nelson [delirium] <del...@ru...> * Please see file COPYING or http://bitzi.com/publicdomain * for more info. * * Currently supported: AVI, QuickTime, MPEG-1, MPEG-2 * (types are detected by headers, not by extension, since many files on * the internet have the wrong extension) * * Revision history: * v0.0.1 - 12 Jan 2002 - Initial version; preliminary AVI support. * v0.1.0 - 19 Aug 2002 - Complete rewrite, now supports AVI/QT/MPEG-1/MPEG-2 */ #include "video.h" /* --- Prototypes for externally-called plugin functions --- */ PluginMethods *init_plugin(void); static void video_shutdown_plugin(void); static void video_free_attributes(Attribute *attrList); static SupportedFormat *video_get_supported_formats(void); static const char *video_get_name(void); static const char *video_get_version(void); static char *video_get_error(void); static Attribute *video_file_analyze(const char *fileName); /* --- Initialize plugin information --- */ static SupportedFormat formats[] = { { ".AVI", "Microsoft AVI" }, { ".MOV", "Apple QuickTime" }, { ".MPG", "MPEG-1" }, { ".MPEG", "MPEG-1" }, { ".M2V", "MPEG-2" }, { NULL, NULL } }; static char *errorString = NULL; static PluginMethods methods = { video_shutdown_plugin, video_get_version, video_get_name, video_get_supported_formats, video_file_analyze, NULL, /* no init/update/final */ NULL, NULL, video_free_attributes, video_get_error }; /* --- Externally-called Plugin Functions --- */ PluginMethods *init_plugin(void) { return &methods; } static void video_shutdown_plugin(void) { if (errorString) free(errorString); } static const char *video_get_version(void) { return PLUGIN_VERSION; } static const char *video_get_name(void) { return PLUGIN_NAME; } static SupportedFormat *video_get_supported_formats(void) { return formats; } static Attribute *video_file_analyze(const char *fileName) { FILE *file; Attribute *attrList; int numAttrs; int attr; int version; char temp[100]; /* Used for sprintf()'ing values to */ char fmt[10] = ""; Format format; Data data; data.width = 0; data.height = 0; data.fps = 0; data.duration = 0; data.bitrate = 0; data.codec = NULL; file = fopen(fileName, "rb"); if(file == NULL) return NULL; format = find_format(file); switch(format) { case AVI: parse_avi(file, &data); strcpy(fmt, "AVI"); break; case QUICKTIME: parse_quicktime(file, &data); strcpy(fmt, "QuickTime"); break; case MPEG: version = parse_mpeg(file, &data); if(version == 1) strcpy(fmt, "MPEG-1"); else if(version == 2) strcpy(fmt, "MPEG-2"); break; case UNKNOWN: /* this is only here to quiet compiler warnings */ } /* If necessary, use filesize to estimate bitrate from duration * or vice versa */ if(data.bitrate == 0 && data.duration != 0) { fseek(file, 0L, SEEK_END); data.bitrate = (unsigned int) round_double((double) ftell(file) / data.duration * 8); } else if(data.duration == 0 && data.bitrate != 0) { fseek(file, 0L, SEEK_END); data.duration = (unsigned int) round_double((double) ftell(file) / data.bitrate * 8); } fclose(file); /* Figure out how many attributes we collected (everything not 0/NULL/"" */ numAttrs = (strcmp(fmt, "") != 0) + (data.width != 0) + (data.height != 0) + (data.fps != 0) + (data.duration != 0) + (data.bitrate != 0) + (data.codec != NULL); if(numAttrs == 0) return NULL; /* Allocate space for all the attributes plus a NULL/NULL sentinel pair */ attrList = malloc(sizeof(Attribute) * (numAttrs + 1)); /* Return the attributes we collected */ attr = 0; if(strcmp(fmt, "") != 0) { attrList[attr].key = strdup("tag.video.format"); attrList[attr].value = strdup(fmt); attr++; } if(data.width != 0) { sprintf(temp, "%u", data.width); attrList[attr].key = strdup("tag.video.width"); attrList[attr].value = strdup(temp); attr++; } if(data.height != 0) { sprintf(temp, "%u", data.height); attrList[attr].key = strdup("tag.video.height"); attrList[attr].value = strdup(temp); attr++; } if(data.fps != 0) { sprintf(temp, "%u", data.fps); attrList[attr].key = strdup("tag.video.fps"); attrList[attr].value = strdup(temp); attr++; } if(data.duration != 0) { sprintf(temp, "%u", data.duration); attrList[attr].key = strdup("tag.video.duration"); attrList[attr].value = strdup(temp); attr++; } if(data.bitrate != 0) { sprintf(temp, "%u", data.bitrate); attrList[attr].key = strdup("tag.video.bitrate"); attrList[attr].value = strdup(temp); attr++; } if(data.codec != NULL) { attrList[attr].key = strdup("tag.video.codec"); attrList[attr].value = data.codec; attr++; } attrList[attr].key = NULL; attrList[attr].value = NULL; return attrList; } static void video_free_attributes(Attribute *attrList) { int i = 0; while(attrList[i].key != NULL) { free(attrList[i].key); free(attrList[i].value); i++; } free(attrList); } static char *video_get_error(void) { return errorString; } /* --- Classify file format --- */ Format find_format(FILE *file) { unsigned char buffer[HEAD_BUFFER]; /* Read start of the file into a buffer, so we don't have to * keep re-reading for each file-format check. */ if(fread(buffer, sizeof(char), HEAD_BUFFER, file) != HEAD_BUFFER) return UNKNOWN; rewind(file); /* AVI signature: "RIFF____AVI " */ if(memcmp(buffer, "RIFF", 4)==0 && memcmp(buffer+8, "AVI ", 4)==0) return AVI; /* QuickTime signature: "____moov" or "____mdat" */ else if(memcmp(buffer+4, "moov", 4)==0 || memcmp(buffer+4, "mdat", 4)==0) return QUICKTIME; /* MPEG signature: 0x000001B3 or 0x000001BA */ else if(buffer[0]==0x00 && buffer[1]==0x00 && buffer[2]==0x01 && (buffer[3]==0xB3 || buffer[3]==0xBA)) return MPEG; return UNKNOWN; } /* --- Utility functions --- */ /* We implement our own rounding function, because the availability of * C99's round(), nearbyint(), rint(), etc. seems to be spotty, whereas * floor() is available in math.h on all C compilers. */ double round_double(double num) { return floor(num + 0.5); } /* Read the specified number of bytes as a little-endian (least * significant byte first) integer. * Note: bytes must be less than the byte width of "unsigned long int" * on your platform (e.g. 8 for 32-bit systems). */ unsigned long int fread_le(FILE *file, int bytes) { int x; unsigned long int result = 0; for(x=0; x < bytes; x++) result |= getc(file) << (x*8); return result; } /* Same as above, but big-endian (most significant byte first) ordering */ unsigned long int fread_be(FILE *file, int bytes) { int x; unsigned long int result = 0; for(x=bytes-1; x >= 0; x--) result |= getc(file) << (x*8); return result; } --- NEW FILE: video.h --- /* Header file for the Bitzi Bitcollider video plugin * * (PD) 2002 Mark Nelson [delirium] <del...@ru...> * Please see file COPYING or http://bitzi.com/publicdomain for more * information. */ #ifndef VIDEO_H #define VIDEO_H #include <stdio.h> #include <string.h> #include <stdlib.h> #include <assert.h> #include <errno.h> #include <math.h> #include "plugin.h" #define PLUGIN_VERSION "0.1.0" #define PLUGIN_NAME "Video metadata (AVI, QuickTime, MPEG-1, MPEG-2)" #define HEAD_BUFFER 12 /* We must be able to determine * file format using this many bytes * from the beginning of the file */ /* 32-bit-specific definitions of portable data types */ typedef unsigned int uint32; /* The various formats we support. */ typedef enum { UNKNOWN, AVI, /* Microsoft AVI */ QUICKTIME, /* Apple QuickTime (MOV) */ MPEG /* MPEG 1 or 2 */ } Format; /* Wrap the metadata we're collecting into a struct for easy passing */ typedef struct { unsigned int width; /* width in pixels */ unsigned int height; /* height in pixels */ unsigned int fps; /* frames per second */ unsigned int duration; /* duration in milliseconds */ unsigned int bitrate; /* bitrate in kbps */ char *codec; /* video compression codec */ } Data; /* local prototypes */ Format find_format(FILE *file); void parse_avi(FILE *file, Data *data); void parse_quicktime(FILE *file, Data *data); int parse_mpeg(FILE *file, Data *data); double round_double(double num); unsigned long int fread_le(FILE *file, int bytes); unsigned long int fread_be(FILE *file, int bytes); #endif |