import ij.plugin.*;
import ij.*;
import ij.process.*;
import ij.gui.*;
import ij.io.*;
import ij.plugin.Animator;
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;
import javax.imageio.ImageIO;
/**
* ImageJ Plugin for reading an AVI file into an image stack
* (one slice per video frame)
*
*
* Restrictions and Notes:
* - Only few formats supported:
* - uncompressed 8 bit with palette (=LUT)
* - uncompressed 8 & 16 bit grayscale
* - uncompressed 24 & 32 bit RGB (alpha channel ignored)
* - uncompressed 32 bit AYUV (alpha channel ignored)
* - various YUV 4:2:2 compressed formats
* - png or jpeg-encoded individual frames.
* Note that most MJPG (motion-JPEG) formats are not read correctly.
* - Does not read avi formats with more than one frame per chunk
* - Palette changes during the video not supported
* - Out-of-sequence frames (sequence given by index) not supported
* - Different frame sizes in one file (rcFrame) not supported
* - Conversion of (A)YUV formats to grayscale is non-standard:
* All 255 levels are kept as in the input (i.e. the full dynamic
* range of data from a frame grabber is preserved).
* For standard behavior, use "Brightness&Contrast", Press "Set",
* enter "Min." 16, "Max." 235, and press "Apply".
*
* - Note: As a last frame, one can enter '0' (= last frame),
* '-1' (last frame -1), etc.
*
* Version History:
* 2008-04-29
* based on a plugin by Daniel Marsh and Wayne Rasband;
* modifications by Michael Schmid
* - Support for several other formats added, especially some YUV
* (also named YCbCr) formats
* - Uneven chunk sizes fixed
* - Negative biHeight fixed
* - Audio or second video stream don't cause a problem
* - Can read part of a file (specify start & end frame numbers)
* - Can convert YUV and RGB to grayscale (does not convert 8-bit with palette)
* - Can flip vertically
* - Can create a virtual stack
* - Added slice label: time of the frame in the movie
* - Added a public method 'getStack' that does not create an image window
* - More compact code, especially for reading the header (rewritten)
* - In the code, bitmapinfo items have their canonical names.
* 2008-06-08
* - Support for png and jpeg/mjpg encoded files added
* - Retrieves animation speed from image frame rate
* - Exception handling without multiple error messages
* 2008-07-03
* - Support for 16bit AVIs coded by MIL (Matrox Imaging Library)
* 2009-03-06
* - Jesper Soendergaard Pedersen added support for extended (large) AVI files,
* also known as 'AVI 2.0' or 'OpenDML 1.02 AVI file format extension'
* For Virtual stacks, it reads the 'AVI 2.0' index (indx and ix00 tags).
* This results in a dramatic speed increase in loading of virtual stacks.
* If indx and ix00 are not found or bIndexType is unsupported, as well as for
* non-virtual stacks it finds the frames 'the old way', by scanning the whole file.
* - Fixes a bug where it read too many frames.
* This version was published as external plugin.
* 2011-12-03
* - Minor updates & cleanup for integration into ImageJ again.
* - Multithread-compliant.
* 2011-12-10
* - Based on a plugin by Jesper Soendergaard Pedersen, also reads the 'idx1' index of
* AVI 1 files, speeding up initial reading of virtual stacks also for smaller files.
* - When the first frame to read is > 1, uses the index to quickly skip the initial frames.
* - Creates a unique window name.
* - Opens MJPG files also if they do not contain Huffman tables
*
* The AVI format looks like this:
* RIFF AVI RIFF HEADER, AVI CHUNK
* | LIST hdrl MAIN AVI HEADER
* | | avih AVI HEADER
* | | LIST strl STREAM LIST(s) (One per stream)
* | | | strh STREAM HEADER (Required after above; fourcc type is 'vids' for video stream)
* | | | strf STREAM FORMAT (for video: BitMapInfo; may also contain palette)
* | | | strd OPTIONAL -- STREAM DATA (ignored in this plugin)
* | | | strn OPTIONAL -- STREAM NAME (ignored in this plugin)
* | | | indx OPTIONAL -- MAIN 'AVI 2.0' INDEX
* | LIST movi MOVIE DATA
* | | ix00 partial video index of 'AVI 2.0', usually missing in AVI 1 (ix01 would be for audio)
* | | [rec] RECORD DATA (one record per frame for interleaved video; optional, unsupported in this plugin)
* | | |-dataSubchunks RAW DATA: '??wb' for audio, '??db' and '??dc' for uncompressed and
* | | compressed video, respectively. "??" denotes stream number, usually "00" or "01"
* | idx1 AVI 1 INDEX ('old-style'); may be missing in very old formats
* RIFF AVIX 'AVI 2.0' only: further chunks
* | LIST movi more movie data, as above, usually with ix00 index
* Any number of further chunks (RIFF tags) may follow
*
* Items ('chunks') with one fourcc (four-character code such as 'strh') start with two 4-byte words:
* the fourcc and the size of the data area.
* Items with two fourcc (e.g. 'LIST hdrl') have three 4-byte words: the first fourcc, the size and the
* second fourcc. Note that the size includes the 4 bytes needed for the second fourcc.
*
* Chunks with fourcc 'JUNK' can appear anywhere and should be ignored.
*
*
*/
public class AVI_Reader extends VirtualStack implements PlugIn {
//four-character codes for avi chunk types
//NOTE: byte sequence is reversed - ints in Intel (little endian) byte order!
private final static int FOURCC_RIFF = 0x46464952; //'RIFF'
private final static int FOURCC_AVI = 0x20495641; //'AVI '
private final static int FOURCC_AVIX = 0x58495641; //'AVIX' // extended AVI
private final static int FOURCC_ix00 = 0x30307869; //'ix00' // index within
private final static int FOURCC_indx = 0x78646e69; //'indx' // main index
private final static int FOURCC_idx1 = 0x31786469; //'idx1' // index of single 'movi' block
private final static int FOURCC_LIST = 0x5453494c; //'LIST'
private final static int FOURCC_hdrl = 0x6c726468; //'hdrl'
private final static int FOURCC_avih = 0x68697661; //'avih'
private final static int FOURCC_strl = 0x6c727473; //'strl'
private final static int FOURCC_strh = 0x68727473; //'strh'
private final static int FOURCC_strf = 0x66727473; //'strf'
private final static int FOURCC_movi = 0x69766f6d; //'movi'
private final static int FOURCC_rec = 0x20636572; //'rec '
private final static int FOURCC_JUNK = 0x4b4e554a; //'JUNK'
private final static int FOURCC_vids = 0x73646976; //'vids'
private final static int FOURCC_00db = 0x62643030; //'00db'
private final static int FOURCC_00dc = 0x63643030; //'00dc'
//four-character codes for supported compression formats; see fourcc.org
private final static int NO_COMPRESSION = 0; //uncompressed, also 'RGB ', 'RAW '
private final static int NO_COMPRESSION_RGB= 0x20424752; //'RGB ' -a name for uncompressed
private final static int NO_COMPRESSION_RAW= 0x20574152; //'RAW ' -a name for uncompressed
private final static int NO_COMPRESSION_Y800=0x30303859;//'Y800' -a name for 8-bit grayscale
private final static int NO_COMPRESSION_Y8 = 0x20203859; //'Y8 ' -another name for Y800
private final static int NO_COMPRESSION_GREY=0x59455247;//'GREY' -another name for Y800
private final static int NO_COMPRESSION_Y16= 0x20363159; //'Y16 ' -a name for 16-bit uncompressed grayscale
private final static int NO_COMPRESSION_MIL= 0x204c494d; //'MIL ' - Matrox Imaging Library
private final static int AYUV_COMPRESSION = 0x56555941; //'AYUV' -uncompressed, but alpha, Y, U, V bytes
private final static int UYVY_COMPRESSION = 0x59565955; //'UYVY' - 4:2:2 with byte order u y0 v y1
private final static int Y422_COMPRESSION = 0x564E5955; //'Y422' -another name for of UYVY
private final static int UYNV_COMPRESSION = 0x32323459; //'UYNV' -another name for of UYVY
private final static int CYUV_COMPRESSION = 0x76757963; //'cyuv' -as UYVY but not top-down
private final static int V422_COMPRESSION = 0x32323456; //'V422' -as UYVY but not top-down
private final static int YUY2_COMPRESSION = 0x32595559; //'YUY2' - 4:2:2 with byte order y0 u y1 v
private final static int YUNV_COMPRESSION = 0x564E5559; //'YUNV' -another name for YUY2
private final static int YUYV_COMPRESSION = 0x56595559; //'YUYV' -another name for YUY2
private final static int YVYU_COMPRESSION = 0x55595659; //'YVYU' - 4:2:2 with byte order y0 u y1 v
private final static int JPEG_COMPRESSION = 0x6765706a; //'jpeg' JPEG compression of individual frames
private final static int JPEG_COMPRESSION2 = 0x4745504a; //'JPEG' JPEG compression of individual frames
private final static int JPEG_COMPRESSION3 = 0x04; //BI_JPEG: JPEG compression of individual frames
private final static int MJPG_COMPRESSION = 0x47504a4d; //'MJPG' Motion JPEG, also reads compression of individual frames
private final static int PNG_COMPRESSION = 0x20676e70; //'png ' PNG compression of individual frames
private final static int PNG_COMPRESSION2 = 0x20474e50; //'PNG ' PNG compression of individual frames
private final static int PNG_COMPRESSION3 = 0x05; //BI_PNG PNG compression of individual frames
private final static int BITMASK24 = 0x10000; //for 24-bit (in contrast to 8, 16,... not a bitmask)
private final static long SIZE_MASK = 0xffffffffL; //for conversion of sizes from unsigned int to long
// flags from AVI chunk header
private final static int AVIF_HASINDEX = 0x00000010; // Index at end of file?
private final static int AVIF_MUSTUSEINDEX = 0x00000020; // ignored by this plugin
private final static int AVIF_ISINTERLEAVED= 0x00000100; // ignored by this plugin
// constants used to read 'AVI 2' index chunks (others than those defined here are not supported)
private final static byte AVI_INDEX_OF_CHUNKS=0x01; //index of frames
private final static byte AVI_INDEX_OF_INDEXES=0x00; //main indx pointing to ix00 etc subindices
//static versions of dialog parameters that will be remembered
private static boolean staticConvertToGray;
private static boolean staticFlipVertical;
private static boolean staticIsVirtual;
//dialog parameters
private int firstFrame = 1; //the first frame to read
private int lastFrame = 0; //the last frame to read; 0 means 'read all'
private boolean convertToGray; //whether to convert color video to grayscale
private boolean flipVertical; //whether to flip image vertical
private boolean isVirtual; //whether to open as virtual stack
//the input file
private RandomAccessFile raFile;
private String raFilePath;
private boolean headerOK = false; //whether header has been read
//more avi file properties etc
private int streamNumber; //number of the (first) video stream
private int type0xdb, type0xdc; //video stream chunks must have one of these two types (e.g. '00db' for straem 0)
private long fileSize; //file size
private long aviSize; //size of 'AVI' chunk
private long headerPositionEnd; //'movi' will start somewhere here
private long indexPosition; //position of the main index (indx)
private long indexPositionEnd; //indx seek end
private long moviPosition; //position of 'movi' list
private int paddingGranularity = 2; //tags start at even address
private int frameNumber = 1; //frame currently read; global because distributed over 1st AVi and further RIFF AVIX chunks
private int lastFrameToRead = Integer.MAX_VALUE;
private int totalFramesFromIndex;//number of frames from 'AVI 2.0' indices
private boolean indexForCountingOnly;//don't read the index, only count int totalFramesFromIndex how many entries
//derived from BitMapInfo
private int dataCompression; //data compression type used
private int scanLineSize;
private boolean dataTopDown; //whether data start at top of image
private ColorModel cm;
private boolean variableLength; //compressed (PNG, JPEG) frames have variable length
//for conversion to ImageJ stack
private Vector frameInfos; //for virtual stack: long[] with frame pos&size in file, time(usec)
private ImageStack stack;
private ImagePlus imp;
//for debug messages and error handling
private boolean verbose = IJ.debugMode;
private long startTime;
private boolean aborting;
//From AVI Header Chunk
private int dwMicroSecPerFrame;
private int dwMaxBytesPerSec;
private int dwReserved1;
private int dwFlags;
private int dwTotalFrames; //AVI 2.0: will be replaced by number of frames from index
private int dwInitialFrames;
private int dwStreams;
private int dwSuggestedBufferSize;
private int dwWidth;
private int dwHeight;
//From Stream Header Chunk
private int fccStreamHandler;
private int dwStreamFlags;
private int dwPriorityLanguage; //actually 2 16-bit words: wPriority and wLanguage
private int dwStreamInitialFrames;
private int dwStreamScale;
private int dwStreamRate;
private int dwStreamStart;
private int dwStreamLength;
private int dwStreamSuggestedBufferSize;
private int dwStreamQuality;
private int dwStreamSampleSize;
//From Stream Format Chunk: BITMAPINFO contents (40 bytes)
private int biSize; // size of this header in bytes (40)
private int biWidth;
private int biHeight;
private short biPlanes; // no. of color planes: for the formats decoded; here always 1
private short biBitCount; // Bits per Pixel
private int biCompression;
private int biSizeImage; // size of image in bytes (may be 0: if so, calculate)
private int biXPelsPerMeter; // horizontal resolution, pixels/meter (may be 0)
private int biYPelsPerMeter; // vertical resolution, pixels/meter (may be 0)
private int biClrUsed; // no. of colors in palette (if 0, calculate)
private int biClrImportant; // no. of important colors (appear first in palette) (0 means all are important)
/** The plugin is invoked by ImageJ using this method.
* String 'arg' may be used to select the path.
*/
public void run (String arg) {
OpenDialog od = new OpenDialog("Select AVI File", arg); //file dialog
String fileName = od.getFileName();
if (fileName == null) return;
String fileDir = od.getDirectory();
String path = fileDir + fileName;
try {
openAndReadHeader(path); //open and read header
} catch (Exception e) {
error(exceptionMessage(e));
return;
}
if (!showDialog(fileName)) return; //ask for parameters
try {
ImageStack stack = makeStack(path, firstFrame, lastFrame, isVirtual, convertToGray, flipVertical); //read data
} catch (Exception e) {
error(exceptionMessage(e));
return;
}
if (stack==null || aborting || (stack.isVirtual()&&stack.getProcessor(1)==null))
return;
if (stack.getSize() == 0) {
String rangeText = "";
if (firstFrame>1 || lastFrame!=0)
rangeText = "\nin Range "+firstFrame+
(lastFrame>0 ? " - "+lastFrame : " - end");
error("Error: No Frames Found"+rangeText);
return;
}
imp = new ImagePlus(WindowManager.getUniqueName(fileName), stack);
if (imp.getBitDepth()==16)
imp.getProcessor().resetMinAndMax();
setFramesPerSecond(imp);
FileInfo fi = new FileInfo();
fi.fileName = fileName;
fi.directory = fileDir;
imp.setFileInfo(fi);
if (arg.equals(""))
imp.show();
IJ.showTime(imp, startTime, "Read AVI in ", stack.getSize());
}
/** Returns the ImagePlus opened by run(). */
public ImagePlus getImagePlus() {
return imp;
}
/** Create an ImageStack from an avi file with given path.
* @param path Directoy+filename of the avi file
* @param firstFrame Number of first frame to read (first frame of the file is 1)
* @param lastFrame Number of last frame to read or 0 for reading all, -1 for all but last...
* @param isVirtual Whether to return a virtual stack
* @param convertToGray Whether to convert color images to grayscale
* @return Returns the stack; null on failure.
* The stack returned may be non-null, but have a length of zero if no suitable frames were found
*/
public ImageStack makeStack (String path, int firstFrame, int lastFrame,
boolean isVirtual, boolean convertToGray, boolean flipVertical) {
this.firstFrame = firstFrame;
this.lastFrame = lastFrame;
this.isVirtual = isVirtual;
this.convertToGray = convertToGray;
this.flipVertical = flipVertical;
String exceptionMessage = null;
IJ.showProgress(.001);
try {
readAVI(path);
} catch (OutOfMemoryError e) {
stack.trim();
IJ.showMessage("AVI Reader", "Out of memory. " + stack.getSize() + " of " + dwTotalFrames + " frames will be opened.");
} catch (Exception e) {
exceptionMessage = exceptionMessage(e);
} finally {
try {
raFile.close();
if (verbose)
IJ.log("File closed.");
} catch (Exception e) {}
IJ.showProgress(1.0);
}
if (exceptionMessage != null) {
error(exceptionMessage);
return null;
}
if (isVirtual && frameInfos != null)
stack = this;
if (stack!=null && cm!=null)
stack.setColorModel(cm);
return stack;
}
/** Returns an ImageProcessor for the specified slice of this virtual stack (if it is one)
where 1<=n<=nslices. Returns null if no virtual stack or no slices.
*/
public synchronized ImageProcessor getProcessor(int n) {
if (frameInfos==null || frameInfos.size()==0 || raFilePath==null)
return null;
if (n<1 || n>frameInfos.size())
throw new IllegalArgumentException("Argument out of range: "+n);
Object pixels = null;
RandomAccessFile rFile = null;
String exceptionMessage = null;
try {
rFile = new RandomAccessFile(new File(raFilePath), "r");
long[] frameInfo = (long[])(frameInfos.get(n-1));
pixels = readFrame(rFile, frameInfo[0], (int)frameInfo[1]);
} catch (Exception e) {
exceptionMessage = exceptionMessage(e);
} finally {
try {
rFile.close();
} catch (Exception e) {}
}
if (exceptionMessage != null) {
error(exceptionMessage);
return null;
}
if (pixels == null) return null; //failed
if (pixels instanceof byte[])
return new ByteProcessor(dwWidth, biHeight, (byte[])pixels, cm);
else if (pixels instanceof short[])
return new ShortProcessor(dwWidth, biHeight, (short[])pixels, cm);
else
return new ColorProcessor(dwWidth, biHeight, (int[])pixels);
}
/** Returns the image width of the virtual stack */
public int getWidth() {
return dwWidth;
}
/** Returns the image height of the virtual stack */
public int getHeight() {
return biHeight;
}
/** Returns the number of images in this virtual stack (if it is one) */
public int getSize() {
if (frameInfos == null) return 0;
else return frameInfos.size();
}
/** Returns the label of the specified slice in this virtual stack (if it is one). */
public String getSliceLabel(int n) {
if (frameInfos==null || n<1 || n>frameInfos.size())
throw new IllegalArgumentException("No Virtual Stack or argument out of range: "+n);
return frameLabel(((long[])(frameInfos.get(n-1)))[2]);
}
/** Deletes the specified image from this virtual stack (if it is one),
* where 1<=n<=nslices. */
public void deleteSlice(int n) {
if (frameInfos==null || frameInfos.size()==0) return;
if (n<1 || n>frameInfos.size())
throw new IllegalArgumentException("Argument out of range: "+n);
frameInfos.removeElementAt(n-1);
}
/** Parameters dialog, returns false on cancel */
private boolean showDialog (String fileName) {
if (lastFrame!=-1)
lastFrame = dwTotalFrames;
if (!IJ.isMacro()) {
convertToGray = staticConvertToGray;
flipVertical = staticFlipVertical;
isVirtual = staticIsVirtual;
}
GenericDialog gd = new GenericDialog("AVI Reader");
gd.addNumericField("First Frame: ", firstFrame, 0);
gd.addNumericField("Last Frame: ", lastFrame, 0, 6, "");
gd.addCheckbox("Use Virtual Stack", isVirtual);
gd.addCheckbox("Convert to Grayscale", convertToGray);
gd.addCheckbox("Flip Vertical", flipVertical);
gd.showDialog();
if (gd.wasCanceled()) return false;
firstFrame = (int)gd.getNextNumber();
lastFrame = (int)gd.getNextNumber();
isVirtual = gd.getNextBoolean();
convertToGray = gd.getNextBoolean();
flipVertical = gd.getNextBoolean();
if (!IJ.isMacro()) {
staticConvertToGray = convertToGray;
staticFlipVertical = flipVertical;
staticIsVirtual = isVirtual;
}
IJ.register(this.getClass());
return true;
}
/** Read into a (virtual) stack */
private void readAVI(String path) throws Exception, IOException {
if (!headerOK) // we have not read the header yet?
openAndReadHeader(path);
startTime += System.currentTimeMillis();// taking previously elapsed time into account
/** MOVED UP HERE BY JSP*/
if (lastFrame > 0) // last frame number to read: from Dialog
lastFrameToRead = lastFrame;
if (lastFrame < 0 && dwTotalFrames > 0) // negative means "end frame minus ..."
lastFrameToRead = dwTotalFrames+lastFrame;
if (lastFrameToRead < firstFrame) // no frames to read
return;
boolean hasIndex = (dwFlags & AVIF_HASINDEX) != 0;
if (isVirtual || firstFrame>1) { // avoid scanning frame-by-frame where we only need the positions
frameInfos = new Vector(100); // holds frame positions, sizes and time since start
long nextPosition = -1;
if (indexPosition > 0) { // attempt to get AVI2.0 index instead of scanning for all frames
raFile.seek(indexPosition);
nextPosition = findFourccAndRead(FOURCC_indx, false, indexPositionEnd, false);
}
if (hasIndex && frameInfos.size()==0) { // got nothing from indx, attempt to read AVI 1 index 'idx1'
raFile.seek(headerPositionEnd);
moviPosition = findFourccAndSkip(FOURCC_movi, true, fileSize); // go behind the 'movi' list
if (moviPosition>0)
nextPosition = findFourccAndRead(FOURCC_idx1, false, fileSize, false);
}
if (verbose)
IJ.log("'frameInfos' has "+frameInfos.size()+" entries");
}
if (isVirtual && frameInfos.size()>0) // Virtual Stack only needs reading the index
return;
// Read AVI (movie data) frame by frame - if no index tag is present the pointers
// for the virtual AVI stack will be read here
raFile.seek(headerPositionEnd);
if (firstFrame>1 && frameInfos.size()>0) {
long[] frameInfo = (long[])frameInfos.get(0);
raFile.seek(frameInfo[0]-8); // chunk starts 8 bytes before frame data
frameNumber = firstFrame;
if (verbose)
IJ.log("directly go to frame "+firstFrame+" @ 0x"+Long.toHexString(frameInfo[0]-8));
readMovieData(fileSize);
} else {
frameNumber = 1;
findFourccAndRead(FOURCC_movi, true, fileSize, true);
}
long pos = raFile.getFilePointer();
//IJ.log("at 0x"+Long.toHexString(pos)+" filesize=0x"+Long.toHexString(fileSize));
// extended AVI: try to find further 'RIFF' chunks, where we expect AVIX tags
while (pos>0 && pos 0 && totalFramesFromIndex > dwTotalFrames)
dwTotalFrames = totalFramesFromIndex;
indexForCountingOnly = false;
return true;
case FOURCC_strh:
int streamType = readInt();
if (streamType != FOURCC_vids) {
if (verbose)
IJ.log("Non-video Stream '"+fourccString(streamType)+" skipped");
streamNumber++;
return false;
}
readStreamHeader();
return true;
case FOURCC_strf:
readBitMapInfo(endPosition);
return true;
case FOURCC_indx:
case FOURCC_ix00:
readAvi2Index(endPosition);
return true;
case FOURCC_idx1:
readOldFrameIndex(endPosition);
return true;
case FOURCC_RIFF:
readAVIX(endPosition);
return true;
case FOURCC_movi:
readMovieData(endPosition);
return true;
}
throw new Exception("Program error, type "+fourccString(fourcc));
}
void readAviHeader() throws Exception, IOException { //'avih'
dwMicroSecPerFrame = readInt();
dwMaxBytesPerSec = readInt();
dwReserved1 = readInt(); //in newer avi formats, this is dwPaddingGranularity?
dwFlags = readInt();
dwTotalFrames = readInt();
dwInitialFrames = readInt();
dwStreams = readInt();
dwSuggestedBufferSize = readInt();
dwWidth = readInt();
dwHeight = readInt();
// dwReserved[4] follows, ignored
if (verbose) {
IJ.log("AVI HEADER (avih):"+timeString());
IJ.log(" dwMicroSecPerFrame=" + dwMicroSecPerFrame);
IJ.log(" dwMaxBytesPerSec=" + dwMaxBytesPerSec);
IJ.log(" dwReserved1=" + dwReserved1);
IJ.log(" dwFlags=" + dwFlags);
IJ.log(" dwTotalFrames=" + dwTotalFrames);
IJ.log(" dwInitialFrames=" + dwInitialFrames);
IJ.log(" dwStreams=" + dwStreams);
IJ.log(" dwSuggestedBufferSize=" + dwSuggestedBufferSize);
IJ.log(" dwWidth=" + dwWidth);
IJ.log(" dwHeight=" + dwHeight);
}
}
void readStreamHeader() throws Exception, IOException { //'strh'
fccStreamHandler = readInt();
dwStreamFlags = readInt();
dwPriorityLanguage = readInt();
dwStreamInitialFrames = readInt();
dwStreamScale = readInt();
dwStreamRate = readInt();
dwStreamStart = readInt();
dwStreamLength = readInt();
dwStreamSuggestedBufferSize = readInt();
dwStreamQuality = readInt();
dwStreamSampleSize = readInt();
//rcFrame rectangle follows, ignored
if (verbose) {
IJ.log("VIDEO STREAM HEADER (strh):");
IJ.log(" fccStreamHandler='" + fourccString(fccStreamHandler)+"'");
IJ.log(" dwStreamFlags=" + dwStreamFlags);
IJ.log(" wPriority,wLanguage=" + dwPriorityLanguage);
IJ.log(" dwStreamInitialFrames=" + dwStreamInitialFrames);
IJ.log(" dwStreamScale=" + dwStreamScale);
IJ.log(" dwStreamRate=" + dwStreamRate);
IJ.log(" dwStreamStart=" + dwStreamStart);
IJ.log(" dwStreamLength=" + dwStreamLength);
IJ.log(" dwStreamSuggestedBufferSize=" + dwStreamSuggestedBufferSize);
IJ.log(" dwStreamQuality=" + dwStreamQuality);
IJ.log(" dwStreamSampleSize=" + dwStreamSampleSize);
}
if (dwStreamSampleSize > 1)
throw new Exception("Video stream with "+dwStreamSampleSize+" (more than 1) frames/chunk not supported");
// what the chunks in that stream will be named (we have two possibilites: uncompressed & compressed)
type0xdb = FOURCC_00db + (streamNumber<<8); //'01db' for stream 1, etc. (inverse byte order!)
type0xdc = FOURCC_00dc + (streamNumber<<8); //'01dc' for stream 1, etc.
}
/** Read 'AVI 2'-type main index 'indx' or an 'ix00' index to frames
* (only the types AVI_INDEX_OF_INDEXES and AVI_INDEX_OF_CHUNKS are supported) */
private void readAvi2Index(long endPosition) throws Exception, IOException {
short wLongsPerEntry = readShort();
byte bIndexSubType = raFile.readByte();
byte bIndexType = raFile.readByte();
int nEntriesInUse = readInt();
int dwChunkId = readInt();
long qwBaseOffset = readLong();
readInt(); // dwReserved3
if (verbose) {
String bIndexString = bIndexType == AVI_INDEX_OF_CHUNKS ? ": AVI_INDEX_OF_CHUNKS" :
bIndexType == AVI_INDEX_OF_INDEXES ? ": AVI_INDEX_OF_INDEXES" : ": UNSUPPOERTED";
IJ.log("AVI 2 INDEX:");
IJ.log(" wLongsPerEntry=" + wLongsPerEntry);
IJ.log(" bIndexSubType=" + bIndexSubType);
IJ.log(" bIndexType=" + bIndexType + bIndexString);
IJ.log(" nEntriesInUse=" + nEntriesInUse);
IJ.log(" dwChunkId='" + fourccString(dwChunkId)+"'");
if (bIndexType == AVI_INDEX_OF_CHUNKS)
IJ.log(" qwBaseOffset=" + "0x"+Long.toHexString(qwBaseOffset));
}
if (bIndexType == AVI_INDEX_OF_INDEXES) { // 'indx' points to other indices
if (wLongsPerEntry != 4) return; //badly formed index, ignore it
for (int i=0;ilastFrameToRead) break;
}
} else if (bIndexType == AVI_INDEX_OF_CHUNKS) {
if (wLongsPerEntry != 2) return; //badly formed index, ignore it
if (dwChunkId != type0xdb && dwChunkId != type0xdc) { //not the stream we search for? (should not happen)
if (verbose)
IJ.log("INDEX ERROR: SKIPPED ix00, wrong stream number or type, should be "+
fourccString(type0xdb)+" or "+fourccString(type0xdc));
return;
}
if (indexForCountingOnly) { //only count number of entries, don't put into table
totalFramesFromIndex += nEntriesInUse;
return;
}
for (int i=0;i= firstFrame) {
frameInfos.add(new long[] {pos, dwSize, (long) frameNumber*dwMicroSecPerFrame});
//if (verbose)
// IJ.log("movie data "+frameNumber+" '"+fourccString(dwChunkId)+"' "+posSizeString(pos,dwSize)+timeString());
}
frameNumber++;
if (frameNumber>lastFrameToRead) break;
}
if (verbose)
IJ.log("Index read up to frame "+(frameNumber-1));
}
}
private void readOldFrameIndex(long endPosition) throws Exception, IOException {
int offset = -1; //difference between absolute frame address and address given in idx1
int[] offsetsToTry = new int[] {0, (int)moviPosition}; // dwOffset may be w.r.t. file start or w.r.t. 'movi' list.
while (true) {
if ((raFile.getFilePointer()+16) >endPosition) break;
int dwChunkId = readInt();
int dwFlags = readInt();
int dwOffset = readInt();
int dwSize = readInt();
//IJ.log("idx1: dwOffset=0x"+Long.toHexString(dwOffset));
//IJ.log("moviPosition=0x"+Long.toHexString(moviPosition));
if ((dwChunkId==type0xdb || dwChunkId==type0xdc) && dwSize>0) {
if (offset < 0) { // find out what the offset refers to
long temp = raFile.getFilePointer();
for (int i=0; i= firstFrame) {
long framepos = (dwOffset+offset) & SIZE_MASK;
frameInfos.add(new long[]{framepos+8, dwSize, (long)frameNumber*dwMicroSecPerFrame});
//if (verbose)
//IJ.log("idx1 movie data '"+fourccString(dwChunkId)+"' "+posSizeString(framepos,dwSize)+timeString());
}
frameNumber++;
if (frameNumber>lastFrameToRead) break;
} //if(dwChunkId...)
} //while(true)
if (verbose)
IJ.log("Index read up to frame "+(frameNumber-1));
}
/**Read stream format chunk: starts with BitMapInfo, may contain palette
*/
void readBitMapInfo(long endPosition) throws Exception, IOException {
biSize = readInt();
biWidth = readInt();
biHeight = readInt();
biPlanes = readShort();
biBitCount = readShort();
biCompression = readInt();
biSizeImage = readInt();
biXPelsPerMeter = readInt();
biYPelsPerMeter = readInt();
biClrUsed = readInt();
biClrImportant = readInt();
if (verbose) {
IJ.log(" biSize=" + biSize);
IJ.log(" biWidth=" + biWidth);
IJ.log(" biHeight=" + biHeight);
IJ.log(" biPlanes=" + biPlanes);
IJ.log(" biBitCount=" + biBitCount);
IJ.log(" biCompression=0x" + Integer.toHexString(biCompression)+" '"+fourccString(biCompression)+"'");
IJ.log(" biSizeImage=" + biSizeImage);
IJ.log(" biXPelsPerMeter=" + biXPelsPerMeter);
IJ.log(" biYPelsPerMeter=" + biYPelsPerMeter);
IJ.log(" biClrUsed=" + biClrUsed);
IJ.log(" biClrImportant=" + biClrImportant);
}
int allowedBitCount = 0;
boolean readPalette = false;
switch (biCompression) {
case NO_COMPRESSION:
case NO_COMPRESSION_RGB:
case NO_COMPRESSION_RAW:
dataCompression = NO_COMPRESSION;
dataTopDown = biHeight<0; //RGB mode is usually bottom-up, negative height signals top-down
allowedBitCount = 8 | BITMASK24 | 32; //we don't support 1, 2 and 4 byte data
readPalette = biBitCount <= 8;
break;
case NO_COMPRESSION_Y8:
case NO_COMPRESSION_GREY:
case NO_COMPRESSION_Y800:
dataTopDown = true;
dataCompression = NO_COMPRESSION;
allowedBitCount = 8;
break;
case NO_COMPRESSION_Y16:
case NO_COMPRESSION_MIL:
dataCompression = NO_COMPRESSION;
allowedBitCount = 16;
break;
case AYUV_COMPRESSION:
dataCompression = AYUV_COMPRESSION;
allowedBitCount = 32;
break;
case UYVY_COMPRESSION:
case UYNV_COMPRESSION:
dataTopDown = true;
case CYUV_COMPRESSION: //same, not top-down
case V422_COMPRESSION:
dataCompression = UYVY_COMPRESSION;
allowedBitCount = 16;
break;
case YUY2_COMPRESSION:
case YUNV_COMPRESSION:
case YUYV_COMPRESSION:
dataTopDown = true;
dataCompression = YUY2_COMPRESSION;
allowedBitCount = 16;
break;
case YVYU_COMPRESSION:
dataTopDown = true;
dataCompression = YVYU_COMPRESSION;
allowedBitCount = 16;
break;
case JPEG_COMPRESSION:
case JPEG_COMPRESSION2:
case JPEG_COMPRESSION3:
case MJPG_COMPRESSION:
dataCompression = JPEG_COMPRESSION;
variableLength = true;
break;
case PNG_COMPRESSION:
case PNG_COMPRESSION2:
case PNG_COMPRESSION3:
variableLength = true;
dataCompression = PNG_COMPRESSION;
break;
default:
throw new Exception("Unsupported compression: "+Integer.toHexString(biCompression)+
(biCompression>=0x20202020 ? " '" + fourccString(biCompression)+"'" : ""));
}
int bitCountTest = biBitCount==24 ? BITMASK24 : biBitCount; //convert "24" to a flag
if (allowedBitCount!=0 && (bitCountTest & allowedBitCount)==0)
throw new Exception("Unsupported: "+biBitCount+" bits/pixel for compression '"+
fourccString(biCompression)+"'");
if (biHeight < 0) //negative height was for top-down data in RGB mode
biHeight = -biHeight;
// Scan line is padded with zeroes to be a multiple of four bytes
scanLineSize = ((biWidth * biBitCount + 31) / 32) * 4;
// a value of biClrUsed=0 means we determine this based on the bits per pixel
if (readPalette && biClrUsed==0)
biClrUsed = 1 << biBitCount;
if (verbose) {
IJ.log(" > data compression=0x" + Integer.toHexString(dataCompression)+" '"+fourccString(dataCompression)+"'");
IJ.log(" > palette colors=" + biClrUsed);
IJ.log(" > scan line size=" + scanLineSize);
IJ.log(" > data top down=" + dataTopDown);
}
//read color palette
if (readPalette) {
long spaceForPalette = endPosition-raFile.getFilePointer();
if (verbose)
IJ.log(" Reading "+biClrUsed+" Palette colors: " + posSizeString(spaceForPalette));
if (spaceForPalette < biClrUsed*4)
throw new Exception("Not enough data ("+spaceForPalette+") for palette of size "+(biClrUsed*4));
byte[] pr = new byte[biClrUsed];
byte[] pg = new byte[biClrUsed];
byte[] pb = new byte[biClrUsed];
for (int i = 0; i < biClrUsed; i++) {
pb[i] = raFile.readByte();
pg[i] = raFile.readByte();
pr[i] = raFile.readByte();
raFile.readByte();
}
cm = new IndexColorModel(biBitCount, biClrUsed, pr, pg, pb);
}
}
/**Read from the 'movi' chunk. Skips audio ('..wb', etc.), 'LIST' 'rec' etc, only reads '..db' or '..dc'*/
void readMovieData(long endPosition) throws Exception, IOException {
if (verbose)
IJ.log("MOVIE DATA "+posSizeString(endPosition-raFile.getFilePointer())+timeString());
if (verbose)
IJ.log("Searching for stream "+streamNumber+": '"+
fourccString(type0xdb)+"' or '"+fourccString(type0xdc)+"' chunks");
if (isVirtual) {
if (frameInfos==null) // we might have it already from reading the first chunk
frameInfos = new Vector(100); // holds frame positions in file (for non-constant frame sizes, should hold long[] with pos and size)
} else if (stack==null)
stack = new ImageStack(dwWidth, biHeight);
while (true) { //loop over all chunks
int type = readType(endPosition);
if (type==0) break; //end of 'movi' reached?
long size = readInt() & SIZE_MASK;
long pos = raFile.getFilePointer();
long nextPos = pos + size;
if ((type==type0xdb || type==type0xdc) && size>0) {
IJ.showProgress((double)frameNumber /lastFrameToRead);
if (verbose)
IJ.log(frameNumber+" movie data '"+fourccString(type)+"' "+posSizeString(size)+timeString());
if (frameNumber >= firstFrame) {
if (isVirtual)
frameInfos.add(new long[]{pos, size, frameNumber*dwMicroSecPerFrame});
else { //read the frame
Object pixels = readFrame(raFile, pos, (int)size);
String label = frameLabel(frameNumber*dwMicroSecPerFrame);
stack.addSlice(label, pixels);
}
}
frameNumber++;
if (frameNumber>lastFrameToRead) break;
} else if (verbose)
IJ.log("skipped '"+fourccString(type)+"' "+posSizeString(size));
if (nextPos > endPosition) break;
raFile.seek(nextPos);
}
}
/** Reads a frame at a given position in the file, returns pixels array */
private Object readFrame (RandomAccessFile rFile, long filePos, int size)
throws Exception, IOException {
rFile.seek(filePos);
//if (verbose)
// IJ.log("virtual AVI: readFrame @"+posSizeString(filePos, size));
if (variableLength) //JPEG or PNG-compressed frames
return readCompressedFrame(rFile, size);
else
return readFixedLengthFrame(rFile, size);
}
/** Reads a JPEG or PNG-compressed frame from a RandomAccessFile and
* returns the pixels array of the resulting image abd sets the
* ColorModel cm (if appropriate) */
private Object readCompressedFrame (RandomAccessFile rFile, int size)
throws Exception, IOException {
InputStream inputStream = new raInputStream(rFile, size, biCompression==MJPG_COMPRESSION);
BufferedImage bi = ImageIO.read(inputStream);
if (bi==null) throw new Exception("can't read frame, ImageIO returns null");
int type = bi.getType();
ImageProcessor ip = null;
//IJ.log("BufferedImage Type="+type);
if (type==BufferedImage.TYPE_BYTE_GRAY) {
ip = new ByteProcessor(bi);
} else if (type==bi.TYPE_BYTE_INDEXED) {
cm = bi.getColorModel();
ip = new ByteProcessor((Image)bi);
} else
ip = new ColorProcessor(bi);
if (convertToGray)
ip = ip.convertToByte(false);
if (flipVertical)
ip.flipVertical();
return ip.getPixels();
}
/** Read a fixed-length frame (RandomAccessFile rFile, long filePos, int size)
* return the pixels array of the resulting image
*/
private Object readFixedLengthFrame (RandomAccessFile rFile, int size) throws Exception, IOException {
if (size < scanLineSize*biHeight) //check minimum size (fixed frame length format)
throw new Exception("Data chunk size "+size+" too short ("+(scanLineSize*biHeight)+" required)");
byte[] rawData = new byte[size];
int n = rFile.read(rawData, 0, size);
if (n < rawData.length)
throw new Exception("Frame ended prematurely after " + n + " bytes");
boolean topDown = flipVertical ? !dataTopDown : dataTopDown;
Object pixels = null;
byte[] bPixels = null;
int[] cPixels = null;
short[] sPixels = null;
if (biBitCount <=8 || convertToGray) {
bPixels = new byte[dwWidth * biHeight];
pixels = bPixels;
} else if (biBitCount == 16 && dataCompression == NO_COMPRESSION) {
sPixels = new short[dwWidth * biHeight];
pixels = sPixels;
} else {
cPixels = new int[dwWidth * biHeight];
pixels = cPixels;
}
int offset = topDown ? 0 : (biHeight-1)*dwWidth;
int rawOffset = 0;
for (int i = biHeight - 1; i >= 0; i--) { //for all lines
if (biBitCount <=8)
unpack8bit(rawData, rawOffset, bPixels, offset, dwWidth);
else if (convertToGray)
unpackGray(rawData, rawOffset, bPixels, offset, dwWidth);
else if (biBitCount==16 && dataCompression == NO_COMPRESSION)
unpackShort(rawData, rawOffset, sPixels, offset, dwWidth);
else
unpack(rawData, rawOffset, cPixels, offset, dwWidth);
rawOffset += scanLineSize;
offset += topDown ? dwWidth : -dwWidth;
}
return pixels;
}
/** For one line: copy byte data into the byte array for creating a ByteProcessor */
void unpack8bit(byte[] rawData, int rawOffset, byte[] pixels, int byteOffset, int w) {
for (int i = 0; i < w; i++)
pixels[byteOffset + i] = rawData[rawOffset + i];
}
/** For one line: Unpack and convert YUV or RGB video data to grayscale (byte array for ByteProcessor) */
void unpackGray(byte[] rawData, int rawOffset, byte[] pixels, int byteOffset, int w) {
int j = byteOffset;
int k = rawOffset;
if (dataCompression == 0) {
for (int i = 0; i < w; i++) {
int b0 = (((int) (rawData[k++])) & 0xff);
int b1 = (((int) (rawData[k++])) & 0xff);
int b2 = (((int) (rawData[k++])) & 0xff);
if (biBitCount==32) k++; // ignore 4th byte (alpha value)
pixels[j++] = (byte)((b0*934 + b1*4809 + b2*2449 + 4096)>>13); //0.299*R+0.587*G+0.114*B
}
} else {
if (dataCompression==UYVY_COMPRESSION || dataCompression==AYUV_COMPRESSION)
k++; //skip first byte in these formats (chroma)
int step = dataCompression==AYUV_COMPRESSION ? 4 : 2;
for (int i = 0; i < w; i++) {
pixels[j++] = rawData[k]; //Non-standard: no scaling from 16-235 to 0-255 here
k+=step;
}
}
}
/** For one line: Unpack 16bit grayscale data and convert to short array for ShortProcessor */
void unpackShort(byte[] rawData, int rawOffset, short[] pixels, int shortOffset, int w) {
int j = shortOffset;
int k = rawOffset;
for (int i = 0; i < w; i++) {
pixels[j++] = (short) ((int)(rawData[k++] & 0xFF)| (((int)(rawData[k++] & 0xFF))<<8));
}
}
/** For one line: Read YUV, RGB or RGB+alpha data and writes RGB int array for ColorProcessor */
void unpack(byte[] rawData, int rawOffset, int[] pixels, int intOffset, int w) {
int j = intOffset;
int k = rawOffset;
switch (dataCompression) {
case NO_COMPRESSION:
for (int i = 0; i < w; i++) {
int b0 = (((int) (rawData[k++])) & 0xff);
int b1 = (((int) (rawData[k++])) & 0xff) << 8;
int b2 = (((int) (rawData[k++])) & 0xff) << 16;
if (biBitCount==32) k++; // ignore 4th byte (alpha value)
pixels[j++] = 0xff000000 | b0 | b1 | b2;
}
break;
case YUY2_COMPRESSION:
for (int i = 0; i < w/2; i++) {
int y0 = rawData[k++] & 0xff;
int u = rawData[k++] ^ 0xffffff80; //converts byte range 0...ff to -128 ... 127
int y1 = rawData[k++] & 0xff;
int v = rawData[k++] ^ 0xffffff80;
writeRGBfromYUV(y0, u, v, pixels, j++);
writeRGBfromYUV(y1, u, v, pixels, j++);
}
break;
case UYVY_COMPRESSION:
for (int i = 0; i < w/2; i++) {
int u = rawData[k++] ^ 0xffffff80;
int y0 = rawData[k++] & 0xff;
int v = rawData[k++] ^ 0xffffff80;
int y1 = rawData[k++] & 0xff;
writeRGBfromYUV(y0, u, v, pixels, j++);
writeRGBfromYUV(y1, u, v, pixels, j++);
}
break;
case YVYU_COMPRESSION:
for (int i = 0; i < w/2; i++) {
int y0 = rawData[k++] & 0xff;
int v = rawData[k++] ^ 0xffffff80;
int y1 = rawData[k++] & 0xff;
int u = rawData[k++] ^ 0xffffff80;
writeRGBfromYUV(y0, u, v, pixels, j++);
writeRGBfromYUV(y1, u, v, pixels, j++);
}
break;
case AYUV_COMPRESSION:
for (int i = 0; i < w; i++) {
k++; //ignore alpha channel
int y = rawData[k++] & 0xff;
int v = rawData[k++] ^ 0xffffff80;
int u = rawData[k++] ^ 0xffffff80;
writeRGBfromYUV(y, u, v, pixels, j++);
}
break;
}
}
/** Write an intData RGB value converted from YUV,
* The y range between 16 and 235 becomes 0...255
* u, v should be between -112 and +112
*/
final void writeRGBfromYUV(int y, int u, int v, int[]pixels, int intArrayIndex) {
//int r = (int)(1.164*(y-16)+1.596*v+0.5);
//int g = (int)(1.164*(y-16)-0.391*u-0.813*v+0.5);
//int b = (int)(1.164*(y-16)+2.018*u+0.5);
int r = (9535*y + 13074*v -148464) >> 13;
int g = (9535*y - 6660*v - 3203*u -148464) >> 13;
int b = (9535*y + 16531*u -148464) >> 13;
if (r>255) r=255; if (r<0) r=0;
if (g>255) g=255; if (g<0) g=0;
if (b>255) b=255; if (b<0) b=0;
pixels[intArrayIndex] = 0xff000000 | (r<<16) | (g<<8) | b;
}
/** Read 8-byte int with Intel (little-endian) byte order
* (note: RandomAccessFile.readLong has other byte order than AVI) */
final long readLong() throws IOException {
long low = readInt() & 0x00000000FFFFFFFFL;
long high = readInt() & 0x00000000FFFFFFFFL;
long result = high <<32 | low;
return (long) result; //(high << 32 | low);
}
/** Read 4-byte int with Intel (little-endian) byte order
* (note: RandomAccessFile.readInt has other byte order than AVI) */
final int readInt() throws IOException {
int result = 0;
for (int shiftBy = 0; shiftBy < 32; shiftBy += 8)
result |= (raFile.readByte() & 0xff) << shiftBy;
return result;
}
/** Read 2-byte short with Intel (little-endian) byte order
* (note: RandomAccessFile.readShort has other byte order than AVI) */
final short readShort() throws IOException {
int low = raFile.readByte() & 0xff;
int high = raFile.readByte() & 0xff;
return (short) (high << 8 | low);
}
/** Read type of next chunk that is not JUNK.
* Returns type (or 0 if no non-JUNK chunk until endPosition) */
private int readType(long endPosition) throws IOException {
while (true) {
long pos = raFile.getFilePointer();
if (pos%paddingGranularity!=0) {
pos = (pos/paddingGranularity+1)*paddingGranularity;
raFile.seek(pos); //pad to even address
}
if (pos >= endPosition) return 0;
int type = readInt();
if (type != FOURCC_JUNK)
return type;
long size = readInt()&SIZE_MASK;
if (verbose)
IJ.log("Skip JUNK: "+posSizeString(size));
raFile.seek(raFile.getFilePointer()+size); //skip junk
}
}
private void setFramesPerSecond (ImagePlus imp) {
if (dwMicroSecPerFrame<1000 && dwStreamRate>0) //if no reasonable frame time, get it from rate
dwMicroSecPerFrame = (int)(dwStreamScale*1e6/dwStreamRate);
if (dwMicroSecPerFrame>=1000)
imp.getCalibration().fps = 1e6 / dwMicroSecPerFrame;
}
private String frameLabel(long timeMicroSec) {
return IJ.d2s(timeMicroSec/1.e6)+" s";
}
private String posSizeString(long size) throws IOException {
return posSizeString(raFile.getFilePointer(), size);
}
private String posSizeString(long pos, long size) throws IOException {
return "0x"+Long.toHexString(pos)+"-0x"+Long.toHexString(pos+size-1)+" ("+size+" Bytes)";
}
private String timeString() {
return " (t="+(System.currentTimeMillis()-startTime)+" ms)";
}
/** returns a string of a four-cc code corresponding to an int (Intel byte order) */
private String fourccString(int fourcc) {
String s = "";
for (int i=0; i<4; i++) {
int c = fourcc&0xff;
s += Character.toString((char)c);
fourcc >>= 8;
}
return s;
}
private void error(String msg) {
aborting = true;
IJ.error("AVI Reader", msg);
}
private String exceptionMessage (Exception e) {
String msg;
if (e.getClass() == Exception.class) //for "home-built" exceptions: message only
msg = e.getMessage();
else
msg = e + "\n" + e.getStackTrace()[0]+"\n"+e.getStackTrace()[1];
return "An error occurred reading the AVI file.\n \n" + msg;
}
/** An input stream reading from a RandomAccessFile (starting at the current position).
* This class also adds 'Define Huffman Table' (DHT) segments to convert MJPG to JPEG.
*/
final private static int BUFFERSIZE = 4096; //should be large enough to hold the full JFIF header
// up to beginning of the image data and the Huffman tables
final private static byte[] HUFFMAN_TABLES = new byte[] { //the 'DHT' segment
(byte)0xFF,(byte)0xC4,0x01,(byte)0xA2, //these 4 bytes are tag & length; data follow
0x00,0x00,0x01,0x05,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x01,0x00,0x03,0x01,0x01,0x01,0x01,
0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
0x08,0x09,0x0A,0x0B,0x10,0x00,0x02,0x01,0x03,0x03,0x02,0x04,0x03,0x05,0x05,0x04,0x04,0x00,
0x00,0x01,0x7D,0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,
0x07,0x22,0x71,0x14,0x32,(byte)0x81,(byte)0x91,(byte)0xA1,0x08,0x23,0x42,
(byte)0xB1,(byte)0xC1,0x15,0x52,(byte)0xD1,(byte)0xF0,0x24,
0x33,0x62,0x72,(byte)0x82,0x09,0x0A,0x16,0x17,0x18,0x19,0x1A,0x25,0x26,0x27,0x28,0x29,0x2A,0x34,
0x35,0x36,0x37,0x38,0x39,0x3A,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x53,0x54,0x55,0x56,
0x57,0x58,0x59,0x5A,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x73,0x74,0x75,0x76,0x77,0x78,
0x79,0x7A,(byte)0x83,(byte)0x84,(byte)0x85,(byte)0x86,(byte)0x87,(byte)0x88,(byte)0x89,
(byte)0x8A,(byte)0x92,(byte)0x93,(byte)0x94,(byte)0x95,(byte)0x96,(byte)0x97,(byte)0x98,(byte)0x99,
(byte)0x9A,(byte)0xA2,(byte)0xA3,(byte)0xA4,(byte)0xA5,(byte)0xA6,(byte)0xA7,(byte)0xA8,(byte)0xA9,
(byte)0xAA,(byte)0xB2,(byte)0xB3,(byte)0xB4,(byte)0xB5,(byte)0xB6,(byte)0xB7,(byte)0xB8,(byte)0xB9,
(byte)0xBA,(byte)0xC2,(byte)0xC3,(byte)0xC4,(byte)0xC5,(byte)0xC6,(byte)0xC7,(byte)0xC8,(byte)0xC9,
(byte)0xCA,(byte)0xD2,(byte)0xD3,(byte)0xD4,(byte)0xD5,(byte)0xD6,(byte)0xD7,(byte)0xD8,(byte)0xD9,
(byte)0xDA,(byte)0xE1,(byte)0xE2,(byte)0xE3,(byte)0xE4,(byte)0xE5,(byte)0xE6,(byte)0xE7,(byte)0xE8,
(byte)0xE9,(byte)0xEA,(byte)0xF1,(byte)0xF2,(byte)0xF3,(byte)0xF4,(byte)0xF5,(byte)0xF6,(byte)0xF7,
(byte)0xF8,(byte)0xF9,(byte)0xFA,0x11,0x00,0x02,0x01,0x02,0x04,0x04,0x03,0x04,0x07,0x05,0x04,0x04,0x00,0x01,
0x02,0x77,0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,
0x13,0x22,0x32,(byte)0x81,0x08,0x14,0x42,(byte)0x91,(byte)0xA1,(byte)0xB1,(byte)0xC1,0x09,0x23,0x33,
0x52,(byte)0xF0,0x15,0x62,
0x72,(byte)0xD1,0x0A,0x16,0x24,0x34,(byte)0xE1,0x25,(byte)0xF1,0x17,0x18,0x19,0x1A,0x26,0x27,0x28,0x29,0x2A,
0x35,0x36,0x37,0x38,0x39,0x3A,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x53,0x54,0x55,0x56,
0x57,0x58,0x59,0x5A,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x73,0x74,0x75,0x76,0x77,0x78,
0x79,0x7A,(byte)0x82,(byte)0x83,(byte)0x84,(byte)0x85,(byte)0x86,(byte)0x87,(byte)0x88,(byte)0x89,
(byte)0x8A,(byte)0x92,(byte)0x93,(byte)0x94,(byte)0x95,(byte)0x96,(byte)0x97,(byte)0x98,
(byte)0x99,(byte)0x9A,(byte)0xA2,(byte)0xA3,(byte)0xA4,(byte)0xA5,(byte)0xA6,(byte)0xA7,(byte)0xA8,
(byte)0xA9,(byte)0xAA,(byte)0xB2,(byte)0xB3,(byte)0xB4,(byte)0xB5,(byte)0xB6,(byte)0xB7,(byte)0xB8,
(byte)0xB9,(byte)0xBA,(byte)0xC2,(byte)0xC3,(byte)0xC4,(byte)0xC5,(byte)0xC6,(byte)0xC7,(byte)0xC8,
(byte)0xC9,(byte)0xCA,(byte)0xD2,(byte)0xD3,(byte)0xD4,(byte)0xD5,(byte)0xD6,(byte)0xD7,(byte)0xD8,
(byte)0xD9,(byte)0xDA,(byte)0xE2,(byte)0xE3,(byte)0xE4,(byte)0xE5,(byte)0xE6,(byte)0xE7,(byte)0xE8,
(byte)0xE9,(byte)0xEA,(byte)0xF2,(byte)0xF3,(byte)0xF4,(byte)0xF5,(byte)0xF6,(byte)0xF7,(byte)0xF8,
(byte)0xF9,(byte)0xFA };
final private static int HUFFMAN_LENGTH = 420;
class raInputStream extends InputStream {
RandomAccessFile rFile; //where to read the data from
int readableSize; //number of bytes that one should expect to be readable
boolean fixMJPG; //whether to use an ugly hack to convert MJPG frames to JPEG
byte[] buffer; //holds beginning of data for fixing Huffman tables
int bufferPointer; //next position in buffer to read
int bufferLength; //bytes allocated in buffer
/** Constructor */
raInputStream (RandomAccessFile rFile, int readableSize, boolean fixMJPG) throws IOException {
this.rFile = rFile;
this.readableSize = readableSize;
this.fixMJPG = fixMJPG;
if (fixMJPG) {
buffer = new byte[BUFFERSIZE];
bufferLength = Math.min(BUFFERSIZE-HUFFMAN_LENGTH, readableSize);
bufferLength = rFile.read(buffer, 0, bufferLength);
addHuffmanTables();
}
}
public int available () {
return readableSize;
}
// Read methods:
// There is no check against reading beyond the allowed range, which is
// start position + readableSize
// (i.e., reading beyond the frame in the avi file would be possible).
/** Read a single byte */
public int read () throws IOException {
readableSize--;
if (fixMJPG) {
int result = buffer[bufferPointer] & 0xff;
bufferPointer++;
if (bufferPointer >= bufferLength) fixMJPG = false; //buffer exhausted, no more attempt to fix it
return result;
} else
return rFile.read();
}
/** Read bytes into an array */
public int read (byte[] b, int off, int len) throws IOException {
//IJ.log("read "+len+" bytes, fix="+fixMJPG);
int nBytes;
if (fixMJPG) {
nBytes = Math.min(len, bufferLength-bufferPointer);
System.arraycopy(buffer, bufferPointer, b, off, nBytes);
bufferPointer += nBytes;
if (bufferPointer >= bufferLength) {
fixMJPG = false;
if (len-nBytes > 0)
nBytes += rFile.read(b, off+nBytes, len-nBytes);
}
} else
nBytes = rFile.read(b, off, len);
readableSize -= nBytes;
return nBytes;
}
// Add Huffman table if not present yet
private void addHuffmanTables() {
if (readShort(0)!=0xffd8 || bufferLength<6) return; //not a start of JPEG-like data
int offset = 2;
int segmentLength = 0;
do {
int code = readShort(offset); //read segment type
//IJ.log("code=0x"+Long.toHexString(code));
if (code==0xffc4) //Huffman table found, nothing to do
return;
else if (code==0xffda || code==0xffd9) { //start of image data or end of image?
insertHuffmanTables(offset);
return; //finished
}
offset += 2;
segmentLength = readShort(offset); //read length of this segment
offset += segmentLength; //and skip the segment contents
} while (offset=0);
}
// read a short from the buffer
private int readShort(int offset) {
return ((buffer[offset]&0xff)<<8) | (buffer[offset+1]&0xff);
}
// insert Huffman tables at the given position
private void insertHuffmanTables(int position) {
//IJ.log("inserting Huffman tables");
System.arraycopy(buffer, position, buffer, position+HUFFMAN_LENGTH, bufferLength-position);
System.arraycopy(HUFFMAN_TABLES, 0, buffer, position, HUFFMAN_LENGTH);
bufferLength += HUFFMAN_LENGTH;
readableSize += HUFFMAN_LENGTH;
}
}
}