FileDocCategorySizeDatePackage
AVIMux.javaAPI DocJMF 2.1.1e32709Mon May 12 12:20:58 BST 2003com.sun.media.multiplexer.video

AVIMux

public class AVIMux extends BasicMux

Fields Summary
private int[]
suggestedBufferSizes
private int[]
suggestedBufferSizeOffsets
private int[]
scaleOffsets
private boolean[]
endOfMediaStatus
private int
numberOfEoms
private int
width
private int
height
private static final int
MAX_FRAMES_STORED
private static final int
AVIH_HEADER_LENGTH
private static final int
STRH_HEADER_LENGTH
private static final int
STRF_VIDEO_HEADER_LENGTH
private static final int
STRF_AUDIO_HEADER_LENGTH
static final String
AUDIO
static final String
VIDEO
static final int
AVIF_HASINDEX
static final int
AVIF_MUSTUSEINDEX
static final int
AVIF_ISINTERLEAVED
static final int
AVIF_WASCAPTUREFILE
static final int
AVIF_COPYRIGHTED
static final int
AVIF_KEYFRAME
private int
usecPerFrame
private float
frameRate
private int
maxBytesPerSecond
private int
paddingGranularity
private long
avgFrameTime
private int
flags
private int
totalDataLength
private int
totalFrames
private int
totalVideoFrames
private int
initialFrames
private int[]
reserved
private Vector
chunkList
private final int
BUF_SIZE
private ByteBuffer
bbuf
private int
chunkOffset
private int
moviOffset
private int
avihOffset
private int
hdrlSizeOffset
private int
totalStrlLength
private int
blockAlign
private int
samplesPerBlock
private double
sampleRate
private double
audioDuration
private int
averageBytesPerSecond
private int
mp3BitRate
private long
cumulativeInterFrameTimeVideo
private long
previousTimeStampVideo
static final String
LISTRECORDCHUNK
static final String
VIDEO_MAGIC
static final String
VIDEO_MAGIC_JPEG
static final String
VIDEO_MAGIC_IV32a
static final String
VIDEO_MAGIC_IV32b
static final String
VIDEO_MAGIC_IV31
static final String
VIDEO_MAGIC_CVID
static final String
AUDIO_MAGIC
Format
littleEndian
Format
signed
Format
unsigned
Constructors Summary
public AVIMux()

 // Audio

    //    private long nanoSecPerFrame = 0;


      
	supportedInputs = new Format[2];
	supportedInputs[0] = new AudioFormat(null);
	supportedInputs[1] = new VideoFormat(null);

	supportedOutputs = new ContentDescriptor[1];
	supportedOutputs[0] = new FileTypeDescriptor(FileTypeDescriptor.MSVIDEO);

	chunkList.addElement(bbuf);
    
Methods Summary
private javax.media.Format[]createRGBFormats(java.awt.Dimension size)

	int NS = Format.NOT_SPECIFIED;
	Format [] rgbFormats = new Format[] {
	    new RGBFormat(size, size.width * size.height * 2,
			  Format.byteArray, NS, 16,
			  0x1F << 10, 0x1F << 5, 0x1F,
			  2, size.width * 2, RGBFormat.TRUE, RGBFormat.LITTLE_ENDIAN),
	    new RGBFormat(size, size.width * size.height * 3,
			  Format.byteArray, NS, 24,
			  3, 2, 1, 3, size.width * 3, RGBFormat.TRUE, NS),
	    new RGBFormat(size, size.width * size.height * 4,
			  Format.byteArray, NS, 32,
			  3, 2, 1, 4, size.width * 4, RGBFormat.TRUE, NS)
	};
	return rgbFormats;
    
private javax.media.Format[]createYUVFormats(java.awt.Dimension size)

	int NS = Format.NOT_SPECIFIED;
	Format [] yuvFormats = new Format[] {
	    // UYVY
	    new YUVFormat(size, size.width * size.height * 2, Format.byteArray, NS,
			  YUVFormat.YUV_YUYV,
			  size.width * 2, size.width * 2, 1, 0, 2),
	    // YUY2
	    new YUVFormat(size, size.width * size.height * 2, Format.byteArray, NS,
			  YUVFormat.YUV_YUYV,
			  size.width * 2, size.width * 2, 0, 1, 3),
	    // 4:2:0 planar
	    new YUVFormat(size, (size.width * size.height * 3) / 2,
			  Format.byteArray, NS,
			  YUVFormat.YUV_420,
			  size.width, size.width / 2,
			  0, size.width * size.height, (size.width * size.height * 5) / 4),
	    // 4:2:0 planar
	    new YUVFormat(size, (size.width * size.height * 3) / 2,
			  Format.byteArray, NS,
			  YUVFormat.YUV_420,
			  size.width, size.width / 2,
			  0, (size.width * size.height * 5) / 4,size.width * size.height)
	};
	return yuvFormats;
    
public synchronized intdoProcess(javax.media.Buffer buffer, int trackID)

	boolean isVideoFormat;
	
	if (buffer.isEOM()) {
	    numberOfEoms++;
	    if (numberOfEoms >= numTracks)
		return super.doProcess(buffer, trackID);
	    else
		return BUFFER_PROCESSED_OK;
	}

	
	if (buffer.getData() == null)
	    return BUFFER_PROCESSED_OK;
	
	isVideoFormat = buffer.getFormat() instanceof VideoFormat;

	if (isVideoFormat) {
	    long timeStamp = buffer.getTimeStamp();
	    if (timeStamp - previousTimeStampVideo > avgFrameTime * 1.9) {
		Buffer blankBuffer;
		int blankFrames = (int) ((timeStamp-previousTimeStampVideo) /
		                         avgFrameTime);
		for (int i = 0; i < blankFrames; i++) {
		    blankBuffer = new Buffer();
		    blankBuffer.setTimeStamp(previousTimeStampVideo +
					     i * avgFrameTime);
		    blankBuffer.setFormat(buffer.getFormat());
		    blankBuffer.setDuration(avgFrameTime);
		    blankBuffer.setSequenceNumber(buffer.getSequenceNumber());
		    blankBuffer.setFlags(buffer.getFlags() &
					 ~Buffer.FLAG_KEY_FRAME);
		    int result = writeFrame(blankBuffer, trackID);
		    if (result != BUFFER_PROCESSED_OK)
			return result;
		    //System.err.println("Inserted blank");
		}
	    }
	    //System.err.println("writing valid frame");
	}

	return writeFrame(buffer, trackID);
    
private java.lang.StringgetAviEncodingMagic(int streamNumber, boolean isVideoFormat)

	String encoding = inputs[streamNumber].getEncoding().toLowerCase();
	String magic;

	if (isVideoFormat) {
	    // $$$ NOT COMPLETE. USE Hashtable $$$ TODO
	    if (encoding.equalsIgnoreCase(VideoFormat.CINEPAK))
		magic = VIDEO_MAGIC_CVID;
	    else if (encoding.startsWith("iv32"))  // ??
		magic = VIDEO_MAGIC_IV32b;
	    else if (encoding.startsWith("iv31")) // ??
		magic = VIDEO_MAGIC_IV31;
	    else if (encoding.startsWith("iv")) // ??
		magic = VIDEO_MAGIC_IV32a;
	    else
		magic = VIDEO_MAGIC;
	} else
	    magic = AUDIO_MAGIC;
	
	String streamPrefix = null;
	// TODO: use equivalent of sprintf to generate streamPrefix from streamNumber
	// TEMPORARY HACK
	if (streamNumber == 0) {
	    streamPrefix = "00";
	} else if (streamNumber == 1) {
	    streamPrefix = "01";
	} else if (streamNumber == 2) {
	    streamPrefix = "02";
	} else if (streamNumber == 3) {
	    streamPrefix = "03";
	} else if (streamNumber == 4) {
	    streamPrefix = "04";
	}

	return (streamPrefix + magic);
    
public java.lang.StringgetName()

	return "AVI Multiplexer";
    
public javax.media.FormatsetInputFormat(javax.media.Format input, int trackID)


          

	String reason = null;
	//System.err.println("Got input format " + input + " #" + trackID);
	if (input instanceof AudioFormat) {
	    AudioFormat af = (AudioFormat) input;
	    WavAudioFormat wavAudioFormat = null;

	    if (input instanceof WavAudioFormat) {
		wavAudioFormat = (WavAudioFormat) input;
	    }

	    String encoding = af.getEncoding();
	    if (encoding == null)
		return null;

// 	    if (encoding.equalsIgnoreCase(AudioFormat.LINEAR) &&
// 		af.getSampleSizeInBits() > 8) {
// 		if (af.getEndian() == AudioFormat.BIG_ENDIAN)
// 		    return null;
// 		if (af.getEndian() == AudioFormat.NOT_SPECIFIED)
// 		    input = af.intersects(littleEndian);
// 	    }

// 	    encoding = encoding.toLowerCase();
	    if (encoding.equalsIgnoreCase(AudioFormat.LINEAR)) {
		if ( af.getSampleSizeInBits() > 8 ) {
		    if (af.getEndian() == AudioFormat.BIG_ENDIAN)
		    return null;

		if (af.getSigned() == AudioFormat.UNSIGNED)
		    return null;

		// Data has to be little endian and signed.
	        if (af.getEndian() == AudioFormat.NOT_SPECIFIED ||
		    af.getSigned() == AudioFormat.NOT_SPECIFIED) {
		    input = (AudioFormat)af.intersects(signed);
		}
		} else {
		    if (af.getSigned() == AudioFormat.SIGNED)
			return null;

		    // Data has to be unsigned.
		    if (af.getEndian() == AudioFormat.NOT_SPECIFIED ||
			af.getSigned() == AudioFormat.NOT_SPECIFIED) {
			input = (AudioFormat)af.intersects(unsigned);
		    }
		}
	    }
	
	    Integer formatTag = (Integer)
		WavAudioFormat.reverseFormatMapper.get(encoding.toLowerCase());
	    if ( (formatTag == null) ||
		 (af.getEncoding().equalsIgnoreCase(AudioFormat.TRUESPEECH)) ||
		 (af.getEncoding().toLowerCase().startsWith("voxware")) ) {

		reason = "Cannot handle format";
		return null;
	    }

	    // For certain encodings additional encoding-specific information
	    // is required which can only be obtained thru WavAudioFormat
	    short wFormatTag = formatTag.shortValue();
	    switch (wFormatTag) {
	    case WavAudioFormat.WAVE_FORMAT_ADPCM:
	    case WavAudioFormat.WAVE_FORMAT_DVI_ADPCM:
	    case WavAudioFormat.WAVE_FORMAT_GSM610:
		if (wavAudioFormat == null) {
		    reason = "A WavAudioFormat is required " +
			" to provide encoding specific information for this " +
			"encoding " + wFormatTag;
		    return null;
	        }
	    }
	} else if (input instanceof VideoFormat) {
	    VideoFormat vf = (VideoFormat) input;
	    // TODO check video formats for known raw
	    String encoding = vf.getEncoding();
	    Dimension size = vf.getSize();
	    if (size == null)
		size = new Dimension(320, 240);
	    if (encoding == null)
		return null;
	    // encoding = encoding.toLowerCase();
	    if (encoding.equalsIgnoreCase(VideoFormat.RGB)) {
		if (matches(vf, createRGBFormats(size)) == null)
		    return null;
	    } else if (encoding.equalsIgnoreCase(VideoFormat.YUV)) {
		if (matches(vf, createYUVFormats(size)) == null)
		    return null;
	    } else if (encoding.equalsIgnoreCase("jpeg")) {
		return null;
	    } else if (encoding.length() > 4)
 		return null; // Allow fourcc
	    
	    frameRate = vf.getFrameRate();
	    if (frameRate > 0)
		usecPerFrame = (int) ((1/frameRate) * 1000000);
	    avgFrameTime = usecPerFrame * 1000;
	} else {
	    reason = "Can only support Audio and Video formats";
	}

	if (reason != null) {
	    return null;
	} else {
	    inputs[trackID] = input;

	    return input;
	}
    
public intsetNumTracks(int nTracks)

	//System.err.println("Got numTracks = " + nTracks);
	if (nTracks > 2)
	    return 2;
	else {
	    suggestedBufferSizeOffsets = new int[nTracks];
	    suggestedBufferSizes = new int[nTracks];
	    endOfMediaStatus = new boolean[nTracks];
	    for (int i = 0; i < nTracks; i++) {
		suggestedBufferSizes[i] = -1;
		suggestedBufferSizeOffsets[i] = -1;
	    }

	    return super.setNumTracks(nTracks);
	}
    
private voidwriteAVIH()


	// TODO: Need to modify this logic if we support more
	// than 2 tracks, e.g 1 video and 4 audio tracks.
	int audioFrames = 0;
	if (totalVideoFrames <= 0) {
	    // has no video track
	    usecPerFrame = 1000;
	    audioFrames = (int) ((audioDuration * 1000000.) / usecPerFrame);
	} else {
  	    // System.out.println("AVIMux: writeAVIH: usecPerFrame from format " + usecPerFrame);
  	    // System.out.println("AVIMux: cumulativeInterFrameTimeVideo is " +
	    //  			       cumulativeInterFrameTimeVideo);
	    // System.out.println("AVIMux: totalVideoFrames is " + totalVideoFrames);
	    int computedUsecPerFrame =
		(int) (cumulativeInterFrameTimeVideo / (1000. * (totalVideoFrames - 1)));
 	    // System.out.println("AVIMux: computedUsecPerFrame is " + computedUsecPerFrame);

	    // Note: always using computedUsecPerFrame
	    usecPerFrame = computedUsecPerFrame;
	}
	seek(avihOffset);
	bufClear();
	bufWriteIntLittleEndian(usecPerFrame);
	bufWriteIntLittleEndian(maxBytesPerSecond);
	bufWriteIntLittleEndian(paddingGranularity);
	bufWriteIntLittleEndian(flags);
	// TODO: Need to modify this logic if we support more
	// than 2 tracks, e.g 1 video and 4 audio tracks.
	if (totalVideoFrames > 0)
	    bufWriteIntLittleEndian(totalVideoFrames);
	else {
	    bufWriteIntLittleEndian(audioFrames); // bufWriteIntLittleEndian(totalFrames);
	}
	bufWriteIntLittleEndian(initialFrames);
	bufWriteIntLittleEndian(numTracks);
	// I see 32768 as suggested buffer size in the avi header of many
	// avi files.
	// There is also a suggestedBufferSize field in the strh chuck of
	// each track. This will contain the max buffer size for each track


	//	bufWriteIntLittleEndian(32768); // $$$$$$$$$ ====> UNCOMMENT $$$$
	bufWriteIntLittleEndian(0); // $$$$$$$$$ ====> REMOVE $$$$

	// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
	// For width and height fields what do you set
	// for an audio only avi file?? What do you set
	// if you have multiple video tracks
	// Can you have an audio only avi file?
	// Can you have multiple video track in an avi file;
	// I don't think so.
	// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
	bufWriteIntLittleEndian(width);
	bufWriteIntLittleEndian(height);
	bufWriteIntLittleEndian(0); // padding 1
	bufWriteIntLittleEndian(0); // padding 2
	bufWriteIntLittleEndian(0); // padding 3
	bufWriteIntLittleEndian(0); // padding 4
	bufFlush();
    
protected voidwriteFooter()

	writeIDX1Chunk();
	writeAVIH();
	seek(moviOffset);
	// for each frame you neeed 4 bytes for the chunk identifier (eg 01wb)
	// and 4 four bytes for the chunk length.
	// You need 4 bytes for the "movi" string
	bufClear();
	bufWriteIntLittleEndian(4 + totalDataLength + (totalFrames * 8)); // movi size
	bufFlush();
	
	for (int i = 0; i < numTracks; i++) {
	    int offset = suggestedBufferSizeOffsets[i];
	    if (offset > 0) {
		seek(offset);
		bufClear();
		bufWriteIntLittleEndian(suggestedBufferSizes[i]);
		bufFlush();
	    }
	    seek(scaleOffsets[i]);
	    if (inputs[i] instanceof VideoFormat) { // Video stream
		int rateVal = 10000;
		bufClear();
		bufWriteIntLittleEndian((int) (usecPerFrame / 100)); // scale
		bufWriteIntLittleEndian(rateVal);

		bufWriteIntLittleEndian(0); // Start
		bufWriteIntLittleEndian(totalVideoFrames);

		bufFlush();

		// sample size field not modified for video
	    } else { // audio stream
		AudioFormat audioFormat = (AudioFormat) inputs[i];
		// scale is frameSizeInBytes is blockAlign

		if (mp3BitRate > 0) {
		    bufClear();
		    bufWriteIntLittleEndian(8); // scale
		    bufFlush();

		    bufClear();
		    bufWriteIntLittleEndian(mp3BitRate); // rate is bit rate
		    bufFlush();
		    // for mp3 encoding set sample size to 1
		    blockAlign = 1;
		} else {
		    bufClear();
		    bufWriteIntLittleEndian(blockAlign);
		    bufFlush();
		    
		    int factor = 1;
		    if (samplesPerBlock > 0)
			factor = samplesPerBlock;
		    int rate = (int) ( (audioFormat.getSampleRate()
					/ factor) * blockAlign);
		    bufClear();
		    bufWriteIntLittleEndian(rate);
		    bufFlush();
		}


		// Update sampleSize field. sampleSize is blockAlign
		seek(filePointer + 16); // skip to the sampleSize field
		bufClear();
		bufWriteIntLittleEndian(blockAlign); // sampleSize
		bufFlush();
	    }
	}
	// Write file length "RIFF <length> AVI "
	seek(4);
	bufClear();
	bufWriteIntLittleEndian(fileSize-8);
	bufFlush();
	//System.err.println("Done pushing out AVI file");
    
private intwriteFrame(javax.media.Buffer buffer, int trackID)

	boolean isVideoFormat;
	int     pad;
	int     length;
	int     flag;
	
	isVideoFormat = buffer.getFormat() instanceof VideoFormat;

	length = buffer.getLength();
	if ((length & 1) > 0)
	    pad = 1;
	else
	    pad = 0;

	String aviEncodingMagic = getAviEncodingMagic(trackID, isVideoFormat);
	bufClear();
	bufWriteBytes(aviEncodingMagic);
	bufWriteIntLittleEndian(length + pad);
	bufFlush();
	
	if (length > 0)
	    write((byte[])buffer.getData(), buffer.getOffset(), length);
	if (pad > 0) {
	    bufClear();
	    bufWriteByte((byte)0);
	    bufFlush();
	}

	totalDataLength += length + pad;
	
	if (length > suggestedBufferSizes[trackID])
	    suggestedBufferSizes[trackID] = length;
	
	if (bbuf.length == BUF_SIZE) {
	    bbuf = new ByteBuffer(BUF_SIZE);
	    chunkList.addElement(bbuf);
	}

	bbuf.writeBytes(aviEncodingMagic);
	flag = (buffer.getFlags() & Buffer.FLAG_KEY_FRAME) != 0
	       ? AVIF_KEYFRAME
	       : 0;
	bbuf.writeIntLittleEndian(flag);
	bbuf.writeIntLittleEndian(chunkOffset);
	bbuf.writeIntLittleEndian(length);
	chunkOffset += (length + pad + 8);
	if (isVideoFormat) {
	    long timeStamp = buffer.getTimeStamp();
	    //System.out.println("AVIMux: timeStamp is " + timeStamp);
	    if (totalVideoFrames > 0)
		cumulativeInterFrameTimeVideo += (timeStamp - previousTimeStampVideo);
	    // System.out.println("AVIMux: cumulativeInterFrameTimeVideo is " + cumulativeInterFrameTimeVideo);
	    previousTimeStampVideo = timeStamp;
	    totalVideoFrames++;
	} else {
	    if (samplesPerBlock != -1) {
		int numBlocks = (length / blockAlign);
		int numSamples = numBlocks * samplesPerBlock;
		audioDuration  += (numSamples / sampleRate);
	    } else {
		if (averageBytesPerSecond > 0) {
		    audioDuration += ((double)length / averageBytesPerSecond);
		}
	    }
	}


	totalFrames++;
	return BUFFER_PROCESSED_OK;
    
protected voidwriteHeader()


	for (int i = 0; i < inputs.length; i++) {

	    // TODO: Handle multiple audio tracks
	    if (inputs[i] instanceof AudioFormat) {
		AudioFormat af = (AudioFormat) inputs[i];
		WavAudioFormat wavAudioFormat = null;
		
		sampleRate = af.getSampleRate();
		if (af.getEncoding().equalsIgnoreCase(AudioFormat.LINEAR)) {
		    samplesPerBlock = 1;
		}

		if (inputs[i] instanceof WavAudioFormat) {
		    wavAudioFormat = (WavAudioFormat) inputs[i];
		    
		    byte[] codecSpecificHeader = wavAudioFormat.getCodecSpecificHeader();
		    if ( (!af.getEncoding().equalsIgnoreCase(AudioFormat.MPEGLAYER3)) &&
			 (codecSpecificHeader != null) &&
			 (codecSpecificHeader.length >= 2) ) {
			try {
			    samplesPerBlock =
				com.sun.media.parser.BasicPullParser.parseShortFromArray(
											 codecSpecificHeader, /*bigEndian*/ false);

			} catch (IOException e) {
			    System.err.println("Unable to parse codecSpecificHeader");
			    // samplesPerBlock = 1;
			}
		    }
		}
	    }
	}


	bufClear();
	bufWriteBytes("RIFF");
	bufSkip(4);
	bufWriteBytes("AVI ");
	bufWriteBytes("LIST");
	hdrlSizeOffset = filePointer;
	bufSkip(4);
	bufWriteBytes("hdrl");
	bufWriteBytes("avih");
	bufWriteIntLittleEndian(AVIH_HEADER_LENGTH);
	avihOffset = filePointer;
	bufSkip(AVIH_HEADER_LENGTH);

	scaleOffsets = new int[numTracks];
	for (int i = 0; i < numTracks; i++) {
	    Format format = inputs[i];
	    boolean isVideo = (format instanceof VideoFormat);
	    bufWriteBytes("LIST");

	    byte [] codecSpecificHeader = null;
	    int extraByteLength = 0; // size of codecSpecificHeader
	    RGBFormat rgbFormat;
	    AviVideoFormat aviVideoFormat = null;
	    WavAudioFormat wavAudioFormat = null;
	    int planes = 1; // Default value
	    int depth = 24; // Default value: Is this correct? $$$
	    String yuvEncoding = null;

	    String encoding = format.getEncoding();
	    int wFormatTag = -1;

	    if (isVideo) {
		int bytesInBitmap = 40;
		rgbFormat = null;
		
		if (format instanceof RGBFormat)
		    rgbFormat = (RGBFormat) format;
		else if (format instanceof YUVFormat) {
		    YUVFormat yuv = (YUVFormat) format;
		    if (  yuv.getYuvType() == YUVFormat.YUV_YUYV &&
			  yuv.getStrideY() == yuv.getSize().width * 2 &&
			  yuv.getOffsetY() == 0 && yuv.getOffsetU() == 1 &&
			  yuv.getOffsetV() == 3 )
			yuvEncoding = "YUY2";
		    else if (  yuv.getYuvType() == YUVFormat.YUV_YUYV &&
			       yuv.getStrideY() == yuv.getSize().width * 2  &&
			       yuv.getOffsetY() == 1 && yuv.getOffsetU() == 0 &&
			       yuv.getOffsetV() == 2 )
			yuvEncoding = "UYVY";
		    else if (  yuv.getYuvType() == YUVFormat.YUV_YUYV &&
			       yuv.getStrideY() == yuv.getSize().width * 2  &&
			       yuv.getOffsetY() == 0 && yuv.getOffsetU() == 3 &&
			       yuv.getOffsetV() == 1 )
			yuvEncoding = "YVYU";
		    else if (  yuv.getYuvType() == YUVFormat.YUV_420 ) {
			if ( yuv.getStrideY() == yuv.getSize().width &&
			     yuv.getStrideUV() == yuv.getSize().width / 2 ) {
			    if ( yuv.getOffsetU() < yuv.getOffsetV() )
				yuvEncoding = "I420";
			    else
				yuvEncoding = "YV12";
			}
		    }
		}
		if (format instanceof AviVideoFormat)
		    aviVideoFormat = (AviVideoFormat) format;
		
		if (aviVideoFormat != null) {
		    planes = aviVideoFormat.getPlanes();
		    depth = aviVideoFormat.getBitsPerPixel();
		    codecSpecificHeader = aviVideoFormat.getCodecSpecificHeader();
		} else if (rgbFormat != null) {
		    depth = rgbFormat.getBitsPerPixel();
		}
	    } else {
		if (format instanceof WavAudioFormat) {
		    wavAudioFormat = (WavAudioFormat) format;
		    codecSpecificHeader = wavAudioFormat.getCodecSpecificHeader();
		}
		if (codecSpecificHeader == null) {
		    Integer formatTag =
			(Integer)
			WavAudioFormat.reverseFormatMapper.get(encoding.toLowerCase());
		    if (formatTag != null) {
			wFormatTag = formatTag.shortValue();
			if (wFormatTag == WavAudioFormat.WAVE_FORMAT_MPEG_LAYER3) {
			    extraByteLength = 12;
			}
		    }
		}
	    }

	    if ((extraByteLength <= 0) && codecSpecificHeader != null) {
		extraByteLength = codecSpecificHeader.length;
	    }
	    

	    int strlLength = 0;
	    if (isVideo) {
		strlLength =  STRH_HEADER_LENGTH +
		    STRF_VIDEO_HEADER_LENGTH + 20 + extraByteLength;
		bufWriteIntLittleEndian(strlLength);
	    } else {
		if (extraByteLength > 0) {
		    strlLength = STRH_HEADER_LENGTH +
			STRF_AUDIO_HEADER_LENGTH + 20 + extraByteLength + 2;
		} else {
		    strlLength = STRH_HEADER_LENGTH +
			STRF_AUDIO_HEADER_LENGTH + 20;
		}
		bufWriteIntLittleEndian(strlLength);
	    }
	    totalStrlLength += strlLength;
	    bufWriteBytes("strl");
	    bufWriteBytes("strh");
	    bufWriteIntLittleEndian(STRH_HEADER_LENGTH);

	    
	    /// STRH
	    if (isVideo) {
		bufWriteBytes(VIDEO);
		{
		    // TODO: cleanup
		    if (encoding.startsWith("rgb"))
			encoding = "DIB ";
		    else if (yuvEncoding != null) {
			encoding = yuvEncoding;
		    }
		    bufWriteBytes(encoding);
		}
	    } else {
		bufWriteBytes(AUDIO);
		bufWriteIntLittleEndian(0); // encoding
	    }
	    bufWriteIntLittleEndian(0); // flags
	    bufWriteIntLittleEndian(0); // Priority
	    bufWriteIntLittleEndian(0); // Initial Frames
	    scaleOffsets[i] = filePointer;

	    bufWriteIntLittleEndian(1); // Scale: will be updated
	    bufWriteIntLittleEndian(15); // Rate: will be updated
	    bufWriteIntLittleEndian(0); // Start
	    bufWriteIntLittleEndian(0); // Length ??
	    suggestedBufferSizeOffsets[i] = filePointer;
	    bufWriteIntLittleEndian(0); // Suggested buffer size ??
	    bufWriteIntLittleEndian(10000); // quality ??
	    bufWriteIntLittleEndian(0); // sample size: will be updated for audio
	    bufWriteIntLittleEndian(0); // Padding 1
	    bufWriteIntLittleEndian(0); // Padding 2

	    /// STRF
	    // Number of bytes required by bitmap structure
	    bufWriteBytes("strf");
	    if (isVideo) {
		// Chunk length
		bufWriteIntLittleEndian(STRF_VIDEO_HEADER_LENGTH + extraByteLength);
		// Number of bytes required by bitmap structure
		bufWriteIntLittleEndian(STRF_VIDEO_HEADER_LENGTH + extraByteLength);
		width = ((VideoFormat)format).getSize().width;
		height = ((VideoFormat)format).getSize().height;
		bufWriteIntLittleEndian(width);
		bufWriteIntLittleEndian(height);
		bufWriteShortLittleEndian((short)planes); // video.planes ???
		bufWriteShortLittleEndian((short)depth); // video.depth   ???
		
		if (encoding.startsWith("DIB"))
		    bufWriteIntLittleEndian(0);
		else
		    bufWriteBytes(encoding); // ???
		
		int biSizeImage = 0;
		int biXPelsPerMeter = 0;
		int biYPelsPerMeter = 0;
		int biClrUsed = 0;
		int biClrImportant = 0;

		if (aviVideoFormat != null) {
		    if (aviVideoFormat.getImageSize() != Format.NOT_SPECIFIED)
			biSizeImage = aviVideoFormat.getImageSize();
		    if (aviVideoFormat.getXPelsPerMeter() != Format.NOT_SPECIFIED)
			biXPelsPerMeter = aviVideoFormat.getXPelsPerMeter();
		    if (aviVideoFormat.getYPelsPerMeter() != Format.NOT_SPECIFIED)
			biYPelsPerMeter = aviVideoFormat.getYPelsPerMeter();
		    if (aviVideoFormat.getClrUsed() != Format.NOT_SPECIFIED)
			biClrUsed = aviVideoFormat.getClrUsed();
		    if (aviVideoFormat.getClrImportant() != Format.NOT_SPECIFIED)
			biClrImportant = aviVideoFormat.getClrImportant();
		}

		bufWriteIntLittleEndian(biSizeImage); // bmi biSizeImage
		bufWriteIntLittleEndian(biXPelsPerMeter); // bmi biXPelsPerMeter
		bufWriteIntLittleEndian(biYPelsPerMeter); // bmi biYPelsPerMeter
		bufWriteIntLittleEndian(biClrUsed); // bmi biClrUsed
		bufWriteIntLittleEndian(biClrImportant); // bmi biClrImportant


	    } else { // Audio
		AudioFormat audioFormat = (AudioFormat) format;
		if (extraByteLength > 0) {
		    bufWriteIntLittleEndian(STRF_AUDIO_HEADER_LENGTH +
					    extraByteLength + 2);
		} else {
		    bufWriteIntLittleEndian(STRF_AUDIO_HEADER_LENGTH);
		}

		{ // MAY REMOVE BLOCK
		    if (encoding.equals("unknown")) {
			encoding = AudioFormat.LINEAR; // try WAVE_FORMAT_PCM
		    }
		}

		Integer formatTag = (Integer)
		    WavAudioFormat.reverseFormatMapper.get(encoding.toLowerCase());
		if (formatTag == null) {
		    // TODO : Make this check in setInputFormat
		} else {
		    bufWriteShortLittleEndian(formatTag.shortValue()); // encoding
		    short numChannels = (short) audioFormat.getChannels();
		    // System.out.println("AVIMux: numChannels is " + numChannels);
		    bufWriteShortLittleEndian(numChannels);
		    bufWriteIntLittleEndian((int) audioFormat.getSampleRate());
		    short sampleSizeInBits = (short) (audioFormat.getSampleSizeInBits());

		    if (wavAudioFormat != null) {
			averageBytesPerSecond = wavAudioFormat.getAverageBytesPerSecond(); //TODO: per audio track

			if (formatTag.shortValue() == WavAudioFormat.WAVE_FORMAT_MPEG_LAYER3) {
			    mp3BitRate = averageBytesPerSecond * 8;
			}
		    } else if (formatTag.shortValue() == WavAudioFormat.WAVE_FORMAT_MPEG_LAYER3) {
			
			int frameRate = (int) audioFormat.getFrameRate();

			if (frameRate != Format.NOT_SPECIFIED) {
			    averageBytesPerSecond = frameRate;
			    mp3BitRate = averageBytesPerSecond * 8;
			} else {
			    averageBytesPerSecond = 
				(int) audioFormat.getSampleRate() * numChannels *
				(sampleSizeInBits/8);
			}

		    } else {
			averageBytesPerSecond = 
			    (int) audioFormat.getSampleRate() * numChannels *
			          (sampleSizeInBits/8);
		    }
		    bufWriteIntLittleEndian(averageBytesPerSecond);
		    blockAlign = audioFormat.getFrameSizeInBits()/8;
		    if (blockAlign < 1) {
			blockAlign = (sampleSizeInBits * numChannels)/8;
		    }
		    if (blockAlign == 0)
			blockAlign = 1;
		    
		    if (mp3BitRate > 0) {
			// For mp3, set blockAlign to 1
			blockAlign = 1;
		    }
		    bufWriteShortLittleEndian((short)(blockAlign));
		    bufWriteShortLittleEndian(sampleSizeInBits);
		}
	    }
	    if (extraByteLength > 0) {
		if (!isVideo) {
		    if (codecSpecificHeader != null) {
			bufWriteShortLittleEndian((short) codecSpecificHeader.length);
			bufWriteBytes(codecSpecificHeader);
		    } else {
			Integer formatTag =
			    (Integer)
			    WavAudioFormat.reverseFormatMapper.get(encoding.toLowerCase());
			if (formatTag != null) {
			    wFormatTag = formatTag.shortValue();
			    if (wFormatTag == WavAudioFormat.WAVE_FORMAT_MPEG_LAYER3) {
				AudioFormat af = (AudioFormat) inputs[i];
				int frameRate = (int) af.getFrameRate();

				int blockSize;
				if (frameRate > 0) {
				    float temp = (72F * frameRate * 8) / 8000F;
				    temp *= (8000F / af.getSampleRate());
				    blockSize = (int) temp;
				} else {
				    blockSize = 417; // ???
				}


				bufWriteShortLittleEndian((short) 12); // Length of extra data
				bufWriteShortLittleEndian((short)1); // wID
				bufWriteIntLittleEndian(2); // fdwFlags
				bufWriteShortLittleEndian((short) blockSize); // nBlockSize
				bufWriteShortLittleEndian((short)1); // nFramesPerBlock
				bufWriteShortLittleEndian((short)1393); // nCodecDelay
			    }
			}
		    }
		} else {
		    bufWriteBytes(codecSpecificHeader); // VIDEO
		}
	    }
	}
	bufWriteBytes("LIST");
	moviOffset = filePointer;
	bufSkip(4); // List Length: To be filled in later
	bufWriteBytes("movi");
	bufFlush();
	/*fileSize = filePointer // header size
	    +  8; // 4 for the idx1 string and 4 for the idx1 length
	*/
	seek(hdrlSizeOffset);
	int hdrlSize = totalStrlLength + AVIH_HEADER_LENGTH +
	    4 /* bytes */ *
	    (1 + // for "hdrl" string
             2 + // for "avih" and avih_size
             2 * (numTracks)); // "LIST" and LIST_SIZE for the strl in every track
	// System.out.println("hdrlSize is " + hdrlSize);
	bufClear();
	bufWriteIntLittleEndian(hdrlSize); // This is filled in later
	//System.err.println("$$$ Wrote hdrlSize = " + hdrlSize );
	bufFlush();
	seek(moviOffset + 8);
    
private voidwriteIDX1Chunk()

	bufClear();
	bufWriteBytes("idx1");
	bufWriteIntLittleEndian((totalFrames * 16)); // 16 is size of AVIIndexEntry
	bufFlush();
	
	for (int i = 0; i < chunkList.size(); i++) {
// 	    System.out.println("idx1 Frame # " + i + ": " +
// 			       aviIndexEntry[i].id + " : " +
// 			       (aviIndexEntry[i].flag) + " : " +
// 			       // (aviIndexEntry[i].chunkOffset + moviOffset /**+ 8*/) + " : " +
// 			       (aviIndexEntry[i].chunkOffset) + " : " +
// 			       (aviIndexEntry[i].chunkLength));
	    ByteBuffer bbuf = (ByteBuffer) chunkList.elementAt(i);
	    write(bbuf.buffer, 0, bbuf.length);
	}