QuicktimeMuxpublic class QuicktimeMux extends BasicMux
Fields Summary |
---|
private int | removeCount | private boolean | sourceConnected | private boolean | sinkConnected | private boolean | closed | private boolean | opened | private int | debugCounter | private Hashtable | streamNumberHash | private TrakInfo[] | trakInfoArray | private int | dataSize | private Format[] | rgbFormats | private Format[] | yuvFormats | private int[] | scaleOffsets | private boolean[] | endOfMediaStatus | private int | numberOfEoms | private int | numberOfTracks | private int | numberOfSupportedTracks | private static final String | VIDEO | private static final String | AUDIO | private long | mdatOffset | private long | moovOffset | private int | mdatLength | private int | moovLength | private long | mvhdDurationOffset | private static Hashtable | audioFourccMapper | private static Hashtable | videoFourccMapper | private final int | movieTimeScale | private final int | DEFAULT_FRAME_RATE | private final int | DEFAULT_FRAME_DURATION | private final int | TRAK_ENABLED | private final int | TRAK_IN_MOVIE | private static final int | DATA_SELF_REFERENCE_FLAG | private static final boolean | ALWAYS_USE_ONE_ENTRY_FOR_STTS | private static final int | EPSILON_DURATION | private static final int | MVHD_ATOM_SIZE | private static final int | TKHD_ATOM_SIZE | private static final int | MDHD_ATOM_SIZE | private boolean | requireTwoPass | Format | bigEndian |
Constructors Summary |
---|
public QuicktimeMux()
// Note AudioFormat.LINEAR is a special case because, the
// fourcc code could be 'twos' or 'raw '
audioFourccMapper.put(AudioFormat.ALAW, AudioFormat.ALAW);
// audioFourccMapper.put(AudioFormat.ULAW, AudioFormat.ULAW);
// audioFourccMapper.put(AudioFormat.ULAW, "ulaw");
// For some reason, in AudioFormat, the ULAW string is in all
// upper case. Need to investigate
audioFourccMapper.put("ulaw", "ulaw");
audioFourccMapper.put(AudioFormat.IMA4, AudioFormat.IMA4);
audioFourccMapper.put(AudioFormat.GSM, "agsm");
audioFourccMapper.put(AudioFormat.MAC3, AudioFormat.MAC3);
audioFourccMapper.put(AudioFormat.MAC6, AudioFormat.MAC6);
videoFourccMapper.put(VideoFormat.RGB, VideoFormat.RGB);
videoFourccMapper.put(VideoFormat.CINEPAK, VideoFormat.CINEPAK);
videoFourccMapper.put(VideoFormat.JPEG, VideoFormat.JPEG);
videoFourccMapper.put(VideoFormat.H261, VideoFormat.H261);
videoFourccMapper.put(VideoFormat.H263, VideoFormat.H263);
videoFourccMapper.put(VideoFormat.INDEO32, VideoFormat.INDEO32);
videoFourccMapper.put(VideoFormat.INDEO41, VideoFormat.INDEO41);
videoFourccMapper.put(VideoFormat.INDEO50, VideoFormat.INDEO50);
videoFourccMapper.put(VideoFormat.MJPG, VideoFormat.MJPG);
videoFourccMapper.put(VideoFormat.MJPEGA, VideoFormat.MJPEGA);
videoFourccMapper.put(VideoFormat.MJPEGB, VideoFormat. MJPEGB);
videoFourccMapper.put(VideoFormat.MPEG, VideoFormat. MPEG);
videoFourccMapper.put(VideoFormat.RPZA, VideoFormat.RPZA); // Apple Video
videoFourccMapper.put(VideoFormat.YUV, "yuv2"); // Component Video
// TODO: ADD DV - NTSC
// TODO: Add Animation ['rle '], BMP, Graphics ['smc '], Sorenson, TGA, TIFF, PNG
// TODO: Add Kodak Photo CD ['kpcd']
supportedInputs = new Format[2];
supportedInputs[0] = new AudioFormat(null);
supportedInputs[1] = new VideoFormat(null);
supportedOutputs = new ContentDescriptor[1];
supportedOutputs[0] = new FileTypeDescriptor(FileTypeDescriptor.QUICKTIME);
int NS = Format.NOT_SPECIFIED;
rgbFormats = new Format[] {
new RGBFormat(null, NS, Format.byteArray, NS, 16,
0x1F << 10, 0x1F << 5, 0x1F,
2, NS, RGBFormat.FALSE, RGBFormat.BIG_ENDIAN),
new RGBFormat(null, NS, Format.byteArray, NS, 24,
1, 2, 3, 3, NS, RGBFormat.FALSE, NS),
new RGBFormat(null, NS, Format.byteArray, NS, 32,
2, 3, 4, 4, NS, RGBFormat.FALSE, NS)
};
yuvFormats = new Format[] {
new YUVFormat(null, NS, Format.byteArray, NS,
YUVFormat.YUV_YUYV | YUVFormat.YUV_SIGNED,
NS, NS, 0, 1, 3)
};
|
Methods Summary |
---|
public synchronized int | doProcess(javax.media.Buffer buffer, int trackID)
byte [] data;
int length;
if (buffer.isEOM()) {
if (!endOfMediaStatus[trackID]) {
endOfMediaStatus[trackID] = true;
numberOfEoms++;
if (numberOfEoms == numTracks)
return super.doProcess(buffer, trackID);
else
return BUFFER_PROCESSED_OK;
}
}
if (!trakInfoArray[trackID].initFormat) {
if ( trakInfoArray[trackID] instanceof VideoTrakInfo ) {
VideoTrakInfo vti = (VideoTrakInfo)trakInfoArray[trackID];
vti.videoFormat = (VideoFormat) buffer.getFormat();
vti.frameRate = vti.videoFormat.getFrameRate();
if (vti.frameRate > 0) { // that is it is not 0 or NOT_SPECIFIED
vti.frameDuration =
(int) ( ((1 / vti.frameRate) * movieTimeScale) + 0.5);
} else {
vti.frameRate = DEFAULT_FRAME_RATE;
vti.frameDuration = DEFAULT_FRAME_DURATION;
}
}
trakInfoArray[trackID].initFormat = true;
}
Object obj = buffer.getData();
if (obj == null)
return BUFFER_PROCESSED_FAILED;
data = (byte[]) obj;
if (data == null)
return BUFFER_PROCESSED_FAILED;
length = buffer.getLength();
dataSize += length;
TrakInfo trakInfo = trakInfoArray[trackID];
write(data, 0, length);
int chunkOffset = (int) (filePointer - length);
// For all tracks
int chunkOffsetsIndex = trakInfo.chunkOffsetsIndex++;
int numChunkOffsetsArraysUsed = trakInfo.numChunkOffsetsArraysUsed;
trakInfo.chunkOffsetsArray[numChunkOffsetsArraysUsed-1][chunkOffsetsIndex] =
chunkOffset;
if (++chunkOffsetsIndex >= trakInfo.MAX_CHUNKOFFSETS_ARRAYSIZE) {
trakInfo.chunkOffsetsIndex = 0;
trakInfo.chunkOffsetsArray[numChunkOffsetsArraysUsed] =
new int[trakInfo.MAX_CHUNKOFFSETS_ARRAYSIZE];
trakInfo.numChunkOffsetsArraysUsed++;
if (++numChunkOffsetsArraysUsed >= trakInfo.MAX_CHUNKOFFSETS_NUMARRAYS) {
System.err.println("Cannot create quicktime file with more than " +
(trakInfo.MAX_CHUNKOFFSETS_NUMARRAYS *
trakInfo.MAX_CHUNKOFFSETS_ARRAYSIZE) + " chunks ");
return BUFFER_PROCESSED_FAILED;
}
}
String type = trakInfo.type;
VideoTrakInfo vti = null;
AudioTrakInfo ati = null;
if (type.equals(VIDEO)) {
vti = (VideoTrakInfo) trakInfo;
/// For STSZ (sample size atom): START
int sampleSizeIndex = vti.sampleSizeIndex++;
int numSampleSizeArraysUsed = vti.numSampleSizeArraysUsed;
vti.sampleSize[numSampleSizeArraysUsed-1][sampleSizeIndex] = length;
if ( vti.constantSampleSize && (length != vti.sampleSize[0][0]) ) {
vti.constantSampleSize = false;
}
// store durations based on (timestamp - previous timestamp).
// The timeStamps will be used later to create the stts chunk
// for the videotrack.
if (vti.minDuration >= 0 ) { // $$ should this be > 0?
long timeStamp = buffer.getTimeStamp();
if (timeStamp <= Buffer.TIME_UNKNOWN) { // $$ should this be <= 0?
// If you get any timestamp with TIME_UNKNOWN (-1) then
// don't bother storing any more time stamps in the timestamps array.
// In this case we will have a stts chunk with 1 entry which will
// be the frame duration obtained from the frame rate
// System.out.println("Got neg timestamp. stop collecting timestamps");
vti.minDuration = -1;
} else if (vti.totalFrames > 0) {
long durationOfBufferData = (timeStamp - vti.previousTimeStamp);
if (durationOfBufferData < vti.minDuration)
vti.minDuration = durationOfBufferData;
else if (durationOfBufferData > vti.maxDuration)
vti.maxDuration = durationOfBufferData;
int timeStampIndex = vti.timeStampIndex++;
int numTimeStampArraysUsed = vti.numTimeStampArraysUsed;
// System.out.println((numTimeStampArraysUsed-1) + " : " +
// timeStampIndex + " : " + durationOfBufferData);
vti.timeStamps[numTimeStampArraysUsed-1][timeStampIndex] =
durationOfBufferData;
if (++timeStampIndex >= vti.MAX_TIMESTAMP_ARRAYSIZE) {
vti.timeStampIndex = 0;
// System.out.println("Creating new timeStamps array");
vti.timeStamps[numTimeStampArraysUsed] =
new long[vti.MAX_TIMESTAMP_ARRAYSIZE];
vti.numTimeStampArraysUsed++;
if (++numTimeStampArraysUsed >= vti.MAX_TIMESTAMP_NUMARRAYS) {
System.err.println("Cannot create quicktime file with more than " +
(vti.MAX_TIMESTAMP_NUMARRAYS *
vti.MAX_TIMESTAMP_ARRAYSIZE) + " frames ");
return BUFFER_PROCESSED_FAILED;
}
}
}
vti.previousTimeStamp = timeStamp;
}
if (++sampleSizeIndex >= vti.MAX_SAMPLE_SIZE_ARRAYSIZE) {
vti.sampleSizeIndex = 0;
vti.sampleSize[numSampleSizeArraysUsed] =
new int[vti.MAX_SAMPLE_SIZE_ARRAYSIZE];
vti.numSampleSizeArraysUsed++;
if (++numSampleSizeArraysUsed >= vti.MAX_SAMPLE_SIZE_NUMARRAYS) {
System.err.println("Cannot create quicktime file with more than " +
(vti.MAX_SAMPLE_SIZE_NUMARRAYS *
vti.MAX_SAMPLE_SIZE_ARRAYSIZE) + " samples ");
return BUFFER_PROCESSED_FAILED;
}
}
/// For STSZ (sample size atom): END
/// For STSS (sync sample atom): START
boolean keyframe = ((buffer.getFlags() & Buffer.FLAG_KEY_FRAME) > 0);
if (keyframe) {
int keyFrameIndex = vti.keyFrameIndex++;
int numKeyFrameArraysUsed = vti.numKeyFrameArraysUsed;
vti.keyFrames[numKeyFrameArraysUsed-1][keyFrameIndex] =
vti.totalFrames+1; // Frame Numbering starts from 1, not 0
if (++keyFrameIndex >= vti.MAX_KEYFRAME_ARRAYSIZE) {
vti.keyFrameIndex = 0;
// System.out.println("Create new keyframe array for index " +
// numKeyFrameArraysUsed);
vti.keyFrames[numKeyFrameArraysUsed] =
new int[vti.MAX_KEYFRAME_ARRAYSIZE];
vti.numKeyFrameArraysUsed++;
if (++numKeyFrameArraysUsed >= vti.MAX_KEYFRAME_NUMARRAYS) {
System.err.println("Cannot create quicktime file with more than " +
(vti.MAX_KEYFRAME_NUMARRAYS *
vti.MAX_KEYFRAME_ARRAYSIZE) + " keyframes ");
//close();
return BUFFER_PROCESSED_FAILED;
}
}
}
/// For STSS (sync sample atom): END
} else {
// TODO: for now else means audio
// But this won't be the case when we support
// tracks like MUSI (ie midi) etc.
ati = (AudioTrakInfo) trakInfo;
// System.out.println("audio: chunk length is " + length);
/// For STSC (sample per chunk): START
int samplesPerChunk = (length / ati.frameSizeInBytes) * ati.samplesPerBlock;
// System.out.println("samplesPerChunk is " + samplesPerChunk);
ati.numSamples += samplesPerChunk;
if (ati.previousSamplesPerChunk != samplesPerChunk) {
// Another samplesPerChunk entry
int samplesPerChunkIndex = ati.samplesPerChunkIndex;
int numSamplesPerChunkArraysUsed = ati.numSamplesPerChunkArraysUsed;
ati.samplesPerChunkArray[
numSamplesPerChunkArraysUsed-1
][samplesPerChunkIndex] =
trakInfo.totalFrames + 1; // Chunk numbers start from 1
samplesPerChunkIndex++;
ati.samplesPerChunkArray[
numSamplesPerChunkArraysUsed-1
][samplesPerChunkIndex] =
samplesPerChunk;
samplesPerChunkIndex++;
ati.samplesPerChunkIndex = samplesPerChunkIndex;
ati.previousSamplesPerChunk = samplesPerChunk;
if (++samplesPerChunkIndex >= ati.MAX_SAMPLESPERCHUNK_ARRAYSIZE) {
ati.samplesPerChunkIndex = 0;
ati.samplesPerChunkArray[numSamplesPerChunkArraysUsed] =
new int[ati.MAX_SAMPLESPERCHUNK_ARRAYSIZE];
ati.numSamplesPerChunkArraysUsed++;
if (++numSamplesPerChunkArraysUsed >= ati.MAX_SAMPLESPERCHUNK_NUMARRAYS) {
System.err.println("Cannot create quicktime file with more than " +
(ati.MAX_SAMPLESPERCHUNK_NUMARRAYS *
ati.MAX_SAMPLESPERCHUNK_ARRAYSIZE) + " chunks ");
//close();
return BUFFER_PROCESSED_FAILED;
}
}
}
/// For STSC (sample per chunk): END
}
// if (length > suggestedBufferSizes[streamNumber])
// suggestedBufferSizes[streamNumber] = length;
// //}
trakInfo.totalFrames++;
// if (isVideoFormat)
// totalVideoFrames++;
// totalFrames++;
// // Handle this case properly. Internal error?
// if ( totalFrames >= MAX_FRAMES_STORED ) {
// System.err.println("Cannot store more than " + MAX_FRAMES_STORED +
// " frames");
// close();
// return;
// }
// // System.out.println("Frame " + totalFrames);
return BUFFER_PROCESSED_OK;
| public java.lang.String | getName()
return "Quicktime Multiplexer";
| public boolean | requireTwoPass()
return requireTwoPass;
| public javax.media.Format | setInputFormat(javax.media.Format input, int trackID)
if (trakInfoArray == null) {
trakInfoArray = new TrakInfo[numTracks];
endOfMediaStatus = new boolean[numTracks];
}
if (! (input instanceof VideoFormat ||
input instanceof AudioFormat) ) {
trakInfoArray[trackID] = new TrakInfo();
trakInfoArray[trackID].format = input;
trakInfoArray[trackID].supported = false;
return input;
}
String encoding = input.getEncoding();
if (input instanceof VideoFormat) {
if (videoFourccMapper.get(encoding.toLowerCase()) == null) {
return null;
}
if ( encoding.equalsIgnoreCase(VideoFormat.RGB) ) {
if (matches(input, rgbFormats) == null)
return null;
}
if ( encoding.equalsIgnoreCase(VideoFormat.YUV) ) {
if (matches(input, yuvFormats) == null)
return null;
}
VideoTrakInfo vti = new VideoTrakInfo();
trakInfoArray[trackID] = vti;
vti.supported = true;
vti.type = VIDEO;
vti.encoding = encoding;
vti.format = input;
// vti.videoFormat = (VideoFormat) input;
vti.videoFormat = (VideoFormat) null; // filled in later in doProcess
// float frameRate = ((VideoFormat)input).getFrameRate();
// vti.frameRate = frameRate;
// if (frameRate > 0) { // that is it is not 0 or NOT_SPECIFIED
// vti.frameDuration = (int) ((1 / frameRate) * movieTimeScale);
// } else {
// vti.frameDuration = DEFAULT_FRAME_DURATION;
// }
// System.out.println("frameDuration is " + vti.frameDuration);
} else if (input instanceof AudioFormat) {
if (encoding.equalsIgnoreCase(AudioFormat.LINEAR)) {
AudioFormat af = (AudioFormat) input;
if (af.getSampleSizeInBits() > 8) {
if (af.getSigned() == AudioFormat.UNSIGNED)
return null;
if (af.getEndian() == AudioFormat.LITTLE_ENDIAN)
return null;
else if (af.getEndian() == AudioFormat.NOT_SPECIFIED)
input = af.intersects(bigEndian);
}
} else {
if (audioFourccMapper.get(encoding.toLowerCase()) == null)
return null;
}
AudioTrakInfo ati = new AudioTrakInfo();
trakInfoArray[trackID] = ati;
ati.supported = true;
ati.type = AUDIO;
ati.encoding = encoding;
ati.format = input;
ati.audioFormat = (AudioFormat) input;
ati.frameSizeInBytes = ati.audioFormat.getFrameSizeInBits()/8;
if (ati.frameSizeInBytes <= 0)
ati.frameSizeInBytes = ati.audioFormat.getSampleSizeInBits() *
ati.audioFormat.getChannels() / 8;
//System.out.println("frameSizeInBytes is " + ati.frameSizeInBytes + " " + ati.audioFormat.getFrameSizeInBits());
if (encoding.equalsIgnoreCase(AudioFormat.IMA4)) {
ati.samplesPerBlock = ati.IMA4_SAMPLES_PER_BLOCK;
} else if (encoding.equalsIgnoreCase(AudioFormat.GSM)) {
ati.samplesPerBlock = ati.GSM_SAMPLES_PER_BLOCK;
} else if (encoding.equalsIgnoreCase(AudioFormat.MAC3)) {
ati.samplesPerBlock = ati.MAC3_SAMPLES_PER_BLOCK;
} else if (encoding.equalsIgnoreCase(AudioFormat.MAC6)) {
ati.samplesPerBlock = ati.MAC6_SAMPLES_PER_BLOCK;
}
}
//streamNumberHash = new Hashtable(numStreams);
// suggestedBufferSizes = new int[numStreams];
// suggestedBufferSizeOffsets = new int[numStreams];
//endOfMediaStatus = new boolean[num]; // java initializes them to false, right ??
if (trakInfoArray[trackID].supported) {
numberOfSupportedTracks++;
// suggestedBufferSizes[i] = -1; // TODO: need this??? what is this
// suggestedBufferSizeOffsets[i] = -1; // TODO: need this??? what is this
}
inputs[trackID] = input;
return input;
| private void | updateSTCO()
for (int streamNumber = 0; streamNumber < trakInfoArray.length; streamNumber++) {
TrakInfo trakInfo = trakInfoArray[streamNumber];
int numChunkOffsetsArraysUsed = trakInfo.numChunkOffsetsArraysUsed;
int[][] chunkOffsetsArray = trakInfo.chunkOffsetsArray;
int chunkOffsetOffset = trakInfo.chunkOffsetOffset;
seek(chunkOffsetOffset);
bufClear();
int numEntries = trakInfo.totalFrames;
int bytesPerLoop = 1 * 4;
int actualBufSize = ( (maxBufSize - 200) / bytesPerLoop ) * bytesPerLoop;
int requiredSize = numEntries * bytesPerLoop;
int numLoops;
int entriesPerLoop;
if (requiredSize <= actualBufSize) {
numLoops = 1;
entriesPerLoop = numEntries;
} else {
numLoops = requiredSize / actualBufSize;
if ( ( (float) requiredSize / actualBufSize ) > numLoops )
numLoops++;
entriesPerLoop = actualBufSize / bytesPerLoop;
}
int indexi = 0;
int indexj = 0;
int off;
for (int ii = 0; ii < numLoops; ii++) {
for (int jj = 0; jj < entriesPerLoop; jj++) {
off = (int) (chunkOffsetsArray[indexi][indexj++] + moovLength);
bufWriteInt(off);
if (indexj >= trakInfo.MAX_CHUNKOFFSETS_ARRAYSIZE) {
indexi++;
indexj = 0;
}
}
bufFlush();
bufClear();
if (ii == numLoops -2) {
entriesPerLoop = numEntries - (numLoops -1) * entriesPerLoop;
}
}
}
| private int | writeAudioSampleDescription(int streamNumber, java.lang.String type)
AudioTrakInfo audioTrakInfo = (AudioTrakInfo) trakInfoArray[streamNumber];
AudioFormat audioFormat = audioTrakInfo.audioFormat;
int channels = audioFormat.getChannels();
int sampleSizeInBits = audioFormat.getSampleSizeInBits();
int sampleRate = (int) audioFormat.getSampleRate();
long offset = filePointer;
// Sample Description Table
bufClear();
bufWriteInt(0); // Size
int size = 4;
String encoding = audioTrakInfo.encoding;
String fourcc;
if (encoding.equalsIgnoreCase(AudioFormat.LINEAR)) {
// for 16 bit fourcc will always be 'twos'
// for 8 bit signed fourcc is 'twos'
// for 8 bit unsigned fourcc is 'raw '
if ( (sampleSizeInBits == 8) &&
(audioFormat.getSigned() == AudioFormat.UNSIGNED) ) {
fourcc = "raw ";
} else {
fourcc = "twos";
}
} else {
fourcc = (String) audioFourccMapper.get(encoding.toLowerCase());
}
bufWriteBytes(fourcc);
size += 4;
// 6 [4 + 2] reserved bytes
bufWriteInt(0); // reserved
bufWriteShort((short)0); // reserved
size += 6;
/** Data Reference Index:
* A 16-bit integer that contains the index of the data
* reference to use to retrieve data associated with samples
* that use this sample description. Data references are
* stored in data reference atoms.
*/
bufWriteShort((short)1); // Data Reference index.
size += 2;
// This section takes up 20 bytes
bufWriteShort((short)0); // Version: Must be set to 0
bufWriteShort((short)0); // Revision Level: Must be set to 0
bufWriteInt(0); // Vendor: Must be set to 0
bufWriteShort((short)channels);
bufWriteShort((short)sampleSizeInBits);
bufWriteShort((short)0); // Compression ID: Must be set to 0
bufWriteShort((short)0); // Packet Size: Must be set to 0
bufWriteInt(sampleRate * 65536); // Unsigned Fixed Point
bufFlush();
size += 20;
return writeSize(offset, size);
| private int | writeDHlrHdlr(int streamNumber, java.lang.String type)
bufClear();
bufWriteInt(8 + 28);
bufWriteBytes("hdlr");
bufWriteInt(0); // Version + Flags
bufWriteBytes("dhlr");
bufWriteBytes("alis");
bufWriteBytes(" "); // Component Manufacturer ??
bufWriteInt(0); // flags ??
bufWriteInt(0); // flags mask ??
bufWriteBytes(" "); // Component Name ??
bufFlush();
return (8 + 28);
| private int | writeDINF(int streamNumber, java.lang.String type)Structure of dinf
size: 36 [4]
dinf [4]
size: 28 [4]
dref [4]
version+flags [4]
num_entries [4]
size 12 [4]
type alis [4]
version+flags 1 [4]
data 0 [0]
bufClear();
bufWriteInt(36);
bufWriteBytes("dinf");
bufWriteInt(28);
bufWriteBytes("dref");
bufWriteInt(0); // Version + Flags
bufWriteInt(1); // Number of entries
bufWriteInt(12);
bufWriteBytes("alis");
bufWriteInt(DATA_SELF_REFERENCE_FLAG); // Version + Flags
bufFlush();
return 36;
| protected void | writeFooter()
moovOffset = filePointer;
seek((int)mdatOffset);
bufClear();
mdatLength = 8 + dataSize;
bufWriteInt(mdatLength);
bufFlush();
seek((int)moovOffset);
writeMOOV();
int maxTrackDuration = -1;
for (int i = 0; i < numTracks; i++) {
if (!trakInfoArray[i].supported)
continue;
// System.out.println("Duration of track 1 " +
// trakInfoArray[i].duration);
writeSize(trakInfoArray[i].tkhdDurationOffset, trakInfoArray[i].duration);
if (trakInfoArray[i].type.equals(VIDEO)) {
writeSize(trakInfoArray[i].mdhdDurationOffset, trakInfoArray[i].duration);
}
if (trakInfoArray[i].duration > maxTrackDuration) {
maxTrackDuration = trakInfoArray[i].duration;
}
}
// update duartion at mvhdDurationOffset
writeSize(mvhdDurationOffset, maxTrackDuration);
RandomAccess st;
if (requireTwoPass && (sth != null) &&
(sth instanceof RandomAccess) ) {
// See if there is space to write the streamable quicktime file.
if ( (st = (RandomAccess) sth).write(-1, moovLength + mdatLength)) {
updateSTCO();
// At this point the non-streamable file cannot be played as the
// updateSTCO updates the offsets so as to work with the streamable
// file. But if the 2 st.write calls don't succeed then you will
// have neither streamable nor non-streamable file.
// But this shouldn't happen due to lack of disk space because
// the 'if' statement succeeds only if a dummy file of the
// required size could be created.
write(null, 0, -1); // EOS. I don't think this is necessary as it is nop $$$
// TODO: $$$ If these 2 writes don't succeed, then throw appropriate
// Exception. "Error creating Quicktime file.
st.write(moovOffset, moovLength);
st.write(mdatOffset, mdatLength);
// Done writing moov and mdat chunks to streamable file.
} else {
System.err.println("No space to write streamable file");
}
st.write(-1, -1);
}
| protected void | writeHeader()
mdatOffset = filePointer;
bufClear();
bufWriteInt(0);
bufWriteBytes("mdat");
bufFlush();
dataSize = 0;
| private int | writeMDHD(int streamNumber, java.lang.String type)
int timeScale = 0;
int duration = 0;
if (type.equals(VIDEO)) {
// For video, the timescale is typically the movie time
// scale. So we can have the same duration value that
// we had in TKHD for this track
timeScale = movieTimeScale;
VideoTrakInfo videoTrakInfo = (VideoTrakInfo) trakInfoArray[streamNumber];
duration = videoTrakInfo.duration; // Computed by writeTKHD earlier
// System.out.println("duration of track " + (streamNumber+1) +
// " is " + videoTrakInfo.duration);
} else {
// For audio, the timeScale is typically the sampleRate
// audio duration should be computed based on this timeScale
// Due to this different timeScale, the duration recorded in
// MDHD will have a different value from that in the
// corresponding TKHD
AudioTrakInfo audioTrakInfo = (AudioTrakInfo) trakInfoArray[streamNumber];
timeScale = (int) audioTrakInfo.audioFormat.getSampleRate();
duration = audioTrakInfo.numSamples; //? TODO CHECK
}
bufClear();
bufWriteInt(MDHD_ATOM_SIZE + 8);
bufWriteBytes("mdhd");
bufWriteInt(1); // version + flag [track is enabled]
bufWriteInt(0); // create time
bufWriteInt(0); // mod time
bufWriteInt(timeScale);
trakInfoArray[streamNumber].mdhdDurationOffset = (int) filePointer;
bufWriteInt(duration); // duration. Will be updated later for video with computed duration
bufWriteShort((short)0); // language
bufWriteShort((short)0); // quality
bufFlush();
return MDHD_ATOM_SIZE + 8;
| private int | writeMDIA(int streamNumber, java.lang.String type)Required atoms are mdhd
long offset = filePointer;
bufClear();
bufWriteInt(0);
bufWriteBytes("mdia");
bufFlush();
int size = 8;
size += writeMDHD(streamNumber, type);
size += writeMhlrHdlr(streamNumber, type);
size += writeMINF(streamNumber, type);
return writeSize(offset, size);
| private int | writeMINF(int streamNumber, java.lang.String type)Required atoms: vmhd or smhd and hdlr
long offset = filePointer;
bufClear();
bufWriteInt(0); // Size
bufWriteBytes("minf");
bufFlush();
int size = 8;
if (type.equals(VIDEO)) {
size += writeVMHD(streamNumber, type);
} else {
size += writeSMHD(streamNumber, type);
}
size += writeDHlrHdlr(streamNumber, type); ///??? Data Handler
size += writeDINF(streamNumber, type);
size += writeSTBL(streamNumber, type);
return writeSize(offset, size);
| private int | writeMOOV()
long offset = filePointer;
bufClear();
bufWriteInt(0); // This is filled in later
bufWriteBytes("moov");
bufFlush();
int size = 8;
size += writeMVHD();
for (int i = 0; i < numTracks; i++) {
if (!trakInfoArray[i].supported)
continue;
size += writeTRAK(i, trakInfoArray[i].type);
}
moovLength = size;
return writeSize(offset, size);
| private int | writeMVHD()
bufClear();
bufWriteInt(MVHD_ATOM_SIZE + 8);
bufWriteBytes("mvhd");
bufWriteInt(0); // Skip version(1), flags(3)
bufWriteInt(0); // create time
bufWriteInt(0); // mod time
bufWriteInt(movieTimeScale); // time scale
mvhdDurationOffset = filePointer;
bufWriteInt(0); // duration. Will be filled at the end
bufWriteInt(65536); // preferredRate
bufWriteShort((short)0xff); // preferredVolume
// 10 reserved bytes
bufWriteInt(0); // reserved
bufWriteInt(0); // reserved
bufWriteShort((short)0); // reserved
/////////////////////////
bufFlush();
writeMatrix();
bufClear();
bufWriteInt(0); // previewTime
bufWriteInt(0); // previewDuration
bufWriteInt(0); // posterTime
bufWriteInt(0); // selectionTime
bufWriteInt(0); // selectionDuration
bufWriteInt(0); // currentTime
bufWriteInt(numberOfSupportedTracks + 1); // nextTrackID
bufFlush();
return MVHD_ATOM_SIZE + 8;
| private void | writeMatrix()
// Matrix
bufClear();
bufWriteInt(65536);
bufWriteInt(0);
bufWriteInt(0);
bufWriteInt(0);
bufWriteInt(65536);
bufWriteInt(0);
bufWriteInt(0);
bufWriteInt(0);
bufWriteInt(1073741824);
bufFlush();
| private int | writeMhlrHdlr(int streamNumber, java.lang.String type)
bufClear();
bufWriteInt(8 + 28);
bufWriteBytes("hdlr");
bufWriteInt(0); // Version + Flags
bufWriteBytes("mhlr");
bufWriteBytes(type);
bufWriteBytes(" "); // Component Manufacturer ??
bufWriteInt(0); // flags ??
bufWriteInt(0); // flags mask ??
bufWriteBytes(" "); // Component Name ??
bufFlush();
return (8 + 28);
| private int | writeSMHD(int streamNumber, java.lang.String type)
bufClear();
bufWriteInt(8 + 8);
bufWriteBytes("smhd");
bufWriteInt(0); // Version + Flags
bufWriteShort((short)0); // Balance
bufWriteShort((short)0); // Reserved
bufFlush();
return (8 + 8);
| private int | writeSTBL(int streamNumber, java.lang.String type)
long offset = filePointer;
bufClear();
bufWriteInt(0);
bufWriteBytes("stbl");
bufFlush();
int size = 8;
size += writeSTSD(streamNumber, type);
size += writeSTTS(streamNumber, type);
size += writeSTSS(streamNumber, type);
size += writeSTSC(streamNumber, type);
size += writeSTSZ(streamNumber, type);
size += writeSTCO(streamNumber, type);
return writeSize(offset, size);
| private int | writeSTCO(int streamNumber, java.lang.String type)
long offset = filePointer;
bufClear();
bufWriteInt(0);
bufWriteBytes("stco");
int size = 8;
bufWriteInt(0); // Version + Flags
size += 4;
TrakInfo trakInfo = trakInfoArray[streamNumber];
int numChunkOffsetsArraysUsed = trakInfo.numChunkOffsetsArraysUsed;
int[][] chunkOffsetsArray = trakInfo.chunkOffsetsArray;
bufWriteInt(trakInfo.totalFrames); // Number of Entries
size += 4;
int numEntries = trakInfo.totalFrames;
int bytesPerLoop = 1 * 4;
int actualBufSize = ( (maxBufSize - 200) / bytesPerLoop ) * bytesPerLoop;
int requiredSize = numEntries * bytesPerLoop;
int numLoops;
int entriesPerLoop;
if (requiredSize <= actualBufSize) {
numLoops = 1;
entriesPerLoop = numEntries;
} else {
numLoops = requiredSize / actualBufSize;
if ( ( (float) requiredSize / actualBufSize ) > numLoops )
numLoops++;
entriesPerLoop = actualBufSize / bytesPerLoop;
}
int indexi = 0;
int indexj = 0;
trakInfo.chunkOffsetOffset = filePointer;
int off;
for (int ii = 0; ii < numLoops; ii++) {
for (int jj = 0; jj < entriesPerLoop; jj++) {
off = (int) (chunkOffsetsArray[indexi][indexj++] - mdatOffset);
bufWriteInt(off);
if (indexj >= trakInfo.MAX_CHUNKOFFSETS_ARRAYSIZE) {
indexi++;
indexj = 0;
}
}
bufFlush();
bufClear();
if (ii == numLoops -2) {
entriesPerLoop = numEntries - (numLoops -1) * entriesPerLoop;
}
}
size += (trakInfo.totalFrames * 4);
return writeSize(offset, size);
| private int | writeSTSC(int streamNumber, java.lang.String type)
long offset = filePointer;
bufClear();
bufWriteInt(0);
bufWriteBytes("stsc");
int size = 8;
bufWriteInt(0); // Version + Flags
size += 4;
if (type.equals(VIDEO)) {
// All chunks have only one sample, ie video frame
VideoTrakInfo vti = (VideoTrakInfo) trakInfoArray[streamNumber];
bufWriteInt(1); // Number of entries
size += 4;
bufWriteInt(1); // First Chunk
bufWriteInt(1); // Samples/Chunk
bufWriteInt(1); // Sample Description
size += 12;
} else {
// Will be Audio
AudioTrakInfo ati = (AudioTrakInfo) trakInfoArray[streamNumber];
int numberOfEntries =
( (ati.numSamplesPerChunkArraysUsed - 1) * ati.MAX_SAMPLESPERCHUNK_ARRAYSIZE +
ati.samplesPerChunkIndex) / 2;
bufWriteInt(numberOfEntries); // Number of entries
size += 4;
int numEntries = numberOfEntries;
int bytesPerLoop = 3 * 4;
int actualBufSize = ( (maxBufSize - 200) / bytesPerLoop ) * bytesPerLoop;
int requiredSize = numEntries * bytesPerLoop;
int numLoops;
int entriesPerLoop;
if (requiredSize <= actualBufSize) {
numLoops = 1;
entriesPerLoop = numEntries;
} else {
numLoops = requiredSize / actualBufSize;
if ( ( (float) requiredSize / actualBufSize ) > numLoops )
numLoops++;
entriesPerLoop = actualBufSize / bytesPerLoop;
}
int indexi = 0;
int indexj = 0;
int samplesPerChunkArray[][] = ati.samplesPerChunkArray;
for (int ii = 0; ii < numLoops; ii++) {
for (int jj = 0; jj < entriesPerLoop; jj++) {
bufWriteInt(samplesPerChunkArray[indexi][indexj++]); // First Chunk
bufWriteInt(samplesPerChunkArray[indexi][indexj++]); // Samples/Chunk
bufWriteInt(1); // Sample Description ID
if (indexj >= ati.MAX_SAMPLESPERCHUNK_ARRAYSIZE) {
indexi++;
indexj = 0;
}
}
bufFlush();
bufClear();
if (ii == numLoops -2) {
entriesPerLoop = numEntries - (numLoops -1) * entriesPerLoop;
}
}
size += (numberOfEntries * 12);
}
if (bufLength > 0) {
bufFlush();
}
return writeSize(offset, size);
| private int | writeSTSD(int streamNumber, java.lang.String type)
long offset = filePointer;
bufClear();
bufWriteInt(0);
bufWriteBytes("stsd");
int size = 8;
bufWriteInt(0); // Version + Flags
bufWriteInt(1); // Number of entries
bufFlush();
size += 8;
if (type.equals(VIDEO)) {
size += writeVideoSampleDescription(streamNumber, type);
} else {
// Will be Audio
size += writeAudioSampleDescription(streamNumber, type);
}
return writeSize(offset, size);
| private int | writeSTSS(int streamNumber, java.lang.String type)Sync Sample Atom
if ( !type.equals(VIDEO) ) { // sync sample atom applies only to video tracks
return 0;
}
VideoTrakInfo vti = (VideoTrakInfo) trakInfoArray[streamNumber];
int numKeyFrameArraysUsed = vti.numKeyFrameArraysUsed;
int numKeyFrames = (numKeyFrameArraysUsed - 1) * vti.MAX_KEYFRAME_ARRAYSIZE +
vti.keyFrameIndex;
// System.out.println("stts:numKeyFrames is " + numKeyFrames);
if ( numKeyFrames == 0) {
com.sun.media.Log.warning("Error: There should be atleast 1 keyframe in the track. All frames are now treated as keyframes");
return 0;
}
if (numKeyFrames == vti.totalFrames) {
// No sync sample atom as all frames are key frames
for (int i = 0; i < numKeyFrameArraysUsed; i++) {
vti.keyFrames[i] = null; // Can be garbage collected
}
return 0;
}
long offset = filePointer;
bufClear();
bufWriteInt(0);
bufWriteBytes("stss");
int size = 8;
bufWriteInt(0); // Version + Flags
size += 4;
int[][] keyFrames = vti.keyFrames;
bufWriteInt(numKeyFrames); // Number of entries
size += 4;
int numEntries = numKeyFrames;
int bytesPerLoop = 1 * 4;
int actualBufSize = ( (maxBufSize - 200) / bytesPerLoop ) * bytesPerLoop;
int requiredSize = numEntries * bytesPerLoop;
int numLoops;
int entriesPerLoop;
if (requiredSize <= actualBufSize) {
numLoops = 1;
entriesPerLoop = numEntries;
} else {
numLoops = requiredSize / actualBufSize;
if ( ( (float) requiredSize / actualBufSize ) > numLoops )
numLoops++;
entriesPerLoop = actualBufSize / bytesPerLoop;
}
int indexi = 0;
int indexj = 0;
for (int ii = 0; ii < numLoops; ii++) {
for (int jj = 0; jj < entriesPerLoop; jj++) {
bufWriteInt(keyFrames[indexi][indexj++]);
if (indexj >= vti.MAX_KEYFRAME_ARRAYSIZE) {
indexi++;
indexj = 0;
}
}
bufFlush();
bufClear();
if (ii == numLoops -2) {
entriesPerLoop = numEntries - (numLoops -1) * entriesPerLoop;
}
}
size += (numKeyFrames * 4);
return writeSize(offset, size);
| private int | writeSTSZ(int streamNumber, java.lang.String type)
long offset = filePointer;
bufClear();
bufWriteInt(0);
bufWriteBytes("stsz");
int size = 8;
bufWriteInt(0); // Version + Flags
size += 4;
TrakInfo trakInfo = trakInfoArray[streamNumber];
if (type.equals(AUDIO)) {
// All Samples have the same sample size
bufWriteInt( 1 ); // sampleSize
bufWriteInt( ((AudioTrakInfo)trakInfo).numSamples);
size += 8;
} else if (type.equals(VIDEO)) {
VideoTrakInfo vti = (VideoTrakInfo) trakInfo;
int numSampleSizeArraysUsed = vti.numSampleSizeArraysUsed;
int numberOfEntries = trakInfo.totalFrames;
if (trakInfo.constantSampleSize) {
// All Samples have the same sample size
// This is always the case for audio but sometimes for
// video, for e.g uncompressed video.
int sampleSize = vti.sampleSize[0][0];
bufWriteInt(sampleSize); // sampleSize
bufWriteInt(numberOfEntries);
size += 8;
} else {
int[][] sampleSize = vti.sampleSize;
bufWriteInt(0); // implies that sampleSize is not constant
bufWriteInt(numberOfEntries); // Number of Entries
size += 8;
int numEntries = numberOfEntries;
int bytesPerLoop = 1 * 4;
int actualBufSize = ( (maxBufSize - 200) / bytesPerLoop ) * bytesPerLoop;
int requiredSize = numEntries * bytesPerLoop;
int numLoops;
int entriesPerLoop;
if (requiredSize <= actualBufSize) {
numLoops = 1;
entriesPerLoop = numEntries;
} else {
numLoops = requiredSize / actualBufSize;
if ( ( (float) requiredSize / actualBufSize ) > numLoops )
numLoops++;
entriesPerLoop = actualBufSize / bytesPerLoop;
}
int indexi = 0;
int indexj = 0;
for (int ii = 0; ii < numLoops; ii++) {
for (int jj = 0; jj < entriesPerLoop; jj++) {
bufWriteInt(sampleSize[indexi][indexj++]);
if (indexj >= vti.MAX_SAMPLE_SIZE_ARRAYSIZE) {
indexi++;
indexj = 0;
}
}
bufFlush();
bufClear();
if (ii == numLoops -2) {
entriesPerLoop = numEntries - (numLoops -1) * entriesPerLoop;
}
}
size += (numberOfEntries * 4);
}
// Is this garbage collection necessary?
for (int i = 0; i < numSampleSizeArraysUsed; i++) {
vti.sampleSize[i] = null; // Can be garbage collected.
}
}
if (bufLength > 0) {
bufFlush();
}
return writeSize(offset, size);
| private int | writeSTTS(int streamNumber, java.lang.String type)
long offset = filePointer;
bufClear();
bufWriteInt(0);
bufWriteBytes("stts");
int size = 8;
bufWriteInt(0); // Version + Flags
size += 4;
if (type.equals(VIDEO)) {
VideoTrakInfo vti = (VideoTrakInfo) trakInfoArray[streamNumber];
if (ALWAYS_USE_ONE_ENTRY_FOR_STTS ||
(vti.minDuration <= -1) /* $$ should it be <= 0? */ ||
( (vti.maxDuration - vti.minDuration) < EPSILON_DURATION ) ) {
bufWriteInt(1); // Number of entries
size += 4;
bufWriteInt(vti.totalFrames);
bufWriteInt(vti.frameDuration);
vti.duration = vti.totalFrames * vti.frameDuration;
size += 8;
} else {
// TODO: Collapse entries with the same duration so that
// to make this table small
// System.out.println("numFrames is " + vti.totalFrames);
bufWriteInt(vti.totalFrames); // Number of entries
size += 4;
vti.duration = 0;
long[][] timeStamps = vti.timeStamps;
int indexi = 0;
int indexj = 0;
long timeStamp = 0;
int numEntries = vti.totalFrames-1;
int bytesPerLoop = 2* 4;
int actualBufSize = ( (maxBufSize - 200) / bytesPerLoop ) * bytesPerLoop;
int requiredSize = numEntries * bytesPerLoop;
int numLoops;
int entriesPerLoop;
if (requiredSize <= actualBufSize) {
numLoops = 1;
entriesPerLoop = numEntries;
} else {
numLoops = requiredSize / actualBufSize;
if ( ( (float) requiredSize / actualBufSize ) > numLoops )
numLoops++;
entriesPerLoop = actualBufSize / bytesPerLoop;
}
for (int ii = 0; ii < numLoops; ii++) {
for (int jj = 0; jj < entriesPerLoop; jj++) {
bufWriteInt(1); // Num frames with this duration
timeStamp = timeStamps[indexi][indexj++];
// duration
int dur = (int) ( 0.5 + (timeStamp / 1000000000.) * movieTimeScale);
bufWriteInt(dur);
vti.duration += dur;
size += 8;
if (indexj >= vti.MAX_TIMESTAMP_ARRAYSIZE) {
indexi++;
indexj = 0;
}
}
bufFlush();
bufClear();
if (ii == numLoops -2) {
entriesPerLoop = numEntries - (numLoops -1) * entriesPerLoop;
}
}
// Last frame gets same duration as previous frame
if (vti.totalFrames > 1) {
bufWriteInt(1); // Num frames with this duration
int dur = (int) ( (timeStamp / 1000000000.) * movieTimeScale);
bufWriteInt(dur);
size += 8;
vti.duration += dur;
// System.out.println("vti.duration final is " + vti.duration);
}
}
for (int i = 0; i < vti.numTimeStampArraysUsed; i++) {
vti.timeStamps[i] = null; // Can be garbage collected
}
} else {
// Will be Audio
AudioTrakInfo ati = (AudioTrakInfo) trakInfoArray[streamNumber];
bufWriteInt(1); // Number of entries
size += 4;
bufWriteInt(ati.numSamples);
bufWriteInt(1); // Sample duration
size += 8;
}
if (bufLength > 0) {
bufFlush();
}
return writeSize(offset, size);
| private int | writeSize(long offset, int size)
long currentOffset = filePointer;
seek((int)offset);
bufClear();
bufWriteInt(size);
bufFlush();
seek((int)currentOffset);
return size;
| private int | writeTKHD(int streamNumber, java.lang.String type)
int width = 0;
int height = 0;
int duration = 0;
int volume = 0;
// For TKHD, the duration for all the tracks is computed
// based on movieTimeScale
if (type.equals(VIDEO)) {
VideoTrakInfo videoTrakInfo = (VideoTrakInfo) trakInfoArray[streamNumber];
Dimension size = null;
VideoFormat vf = videoTrakInfo.videoFormat;
if (vf != null) {
size = vf.getSize();
}
if (size != null) {
width = size.width;
height = size.height;
} else {
// TODO: throw internal error
}
// System.out.println("tkhd: width, height " + width + " : " + height);
// videoTrakInfo.duration = videoTrakInfo.frameDuration * videoTrakInfo.totalFrames;
duration = videoTrakInfo.duration;
} else {
AudioTrakInfo audioTrakInfo = (AudioTrakInfo) trakInfoArray[streamNumber];
float sampleRate = (int) audioTrakInfo.audioFormat.getSampleRate();
float epsilon = 0.01F; // TODO: CHECK TO SEE IF THIS EPSILON MAKES SENSE
duration = (int) ( (audioTrakInfo.numSamples / sampleRate) * movieTimeScale
+ epsilon );
audioTrakInfo.duration = duration;
// System.out.println("duration of audio track " + (streamNumber+1) +
// " is " + audioTrakInfo.duration);
volume = 255;
}
bufClear();
bufWriteInt(TKHD_ATOM_SIZE + 8);
bufWriteBytes("tkhd");
bufWriteInt(TRAK_ENABLED | TRAK_IN_MOVIE); // version + flag [track is enabled]
bufWriteInt(0); // create time
bufWriteInt(0); // mod time
bufWriteInt(streamNumber+1);
bufWriteInt(0); // reserved
trakInfoArray[streamNumber].tkhdDurationOffset = filePointer;
bufWriteInt(duration); // duration. Will be updated later with computed duration
bufWriteInt(0); // reserved
bufWriteInt(0); // reserved
bufWriteShort((short)0); // layer TODO
bufWriteShort((short)0); // alternate group TODO
bufWriteShort((short)volume);
bufWriteShort((short)0); // reserved
bufFlush();
writeMatrix();
bufClear();
bufWriteInt(width * 65536); // Track Width, fixed point
bufWriteInt(height * 65536); // Track Height, fixed point
bufFlush();
return TKHD_ATOM_SIZE + 8;
| private int | writeTRAK(int streamNumber, java.lang.String type)Required atoms are tkhd and mdia
long offset = filePointer;
bufClear();
bufWriteInt(0);
bufWriteBytes("trak");
bufFlush();
int size = 8;
size += writeTKHD(streamNumber, type);
size += writeMDIA(streamNumber, type);
return writeSize(offset, size);
| private int | writeVMHD(int streamNumber, java.lang.String type)
bufClear();
bufWriteInt(8 + 12);
bufWriteBytes("vmhd");
bufWriteInt(1); // Version + Flags
bufWriteShort((short)64); // Graphics Mode
bufWriteShort((short)32768); // Opcolor: Red
bufWriteShort((short)32768); // Opcolor: Green
bufWriteShort((short)32768); // Opcolor: Blue
bufFlush();
return (8 + 12);
| private int | writeVideoSampleDescription(int streamNumber, java.lang.String type)
VideoTrakInfo videoTrakInfo = (VideoTrakInfo) trakInfoArray[streamNumber];
int width = videoTrakInfo.videoFormat.getSize().width;
int height = videoTrakInfo.videoFormat.getSize().height;
long offset = filePointer;
// Sample Description Table
bufClear();
bufWriteInt(0); // Size
int size = 4;
String encoding = videoTrakInfo.encoding;
String fourcc = null;
int bitsPerPixel;
if (encoding.equalsIgnoreCase(VideoFormat.RGB)) {
RGBFormat rgbFormat = (RGBFormat) videoTrakInfo.format;
bitsPerPixel = rgbFormat.getBitsPerPixel();
fourcc = "raw ";
} else {
fourcc = (String) videoFourccMapper.get(encoding.toLowerCase());
// Check: For all non-RGB formats, set bitsPerPixel, ie depth
// to 24. The stsd chunk has a depth field that is set to this value
bitsPerPixel = 24;
}
bufWriteBytes(fourcc);
size += 4;
// 6 [4 + 2] reserved bytes
bufWriteInt(0); // reserved
bufWriteShort((short)0); // reserved
size += 6;
/** Data Reference Index:
* A 16-bit integer that contains the index of the data
* reference to use to retrieve data associated with samples
* that use this sample description. Data references are
* stored in data reference atoms.
*/
bufWriteShort((short)1); // Data Reference index.
size += 2;
// This section takes up 70 bytes
bufWriteShort((short)0); // Version # of compressed data
bufWriteShort((short)0); // Revision level
bufWriteBytes("appl"); // Vendor
bufWriteInt(1023); // $$ TODO: ASK: Temporal Quality [No info to set this value]
bufWriteInt(1023); // $$ TODO: ASK Spatial Quality [No info to set this value]
bufWriteShort((short)width);
bufWriteShort((short)height);
// $$ Horizontal Resolution in pixels/inch [No info to set this value]
bufWriteInt(4718592); // 72 pixels/inch * 65536
// $$ Vertical Resolution in pixels/inch [No info to set this value]
bufWriteInt(4718592); // 72 pixels/inch * 65536
bufWriteInt(0); // Data size [always set to 0]
// A 16-bit integer that indicates how many frames of compressed data
// are stored in each sample. Usually set to 1.
bufWriteShort((short)1);
// Compressor name takes up 32 bytes, fill the rest with space
bufWriteBytes(fourcc); // TODO: use full name, e.g Cinepak instead of cvid
bufWriteBytes(" "); // 28 spaces
bufWriteShort((short)bitsPerPixel);
bufWriteShort((short)-1); // Use Default color table.
bufFlush();
size += 70;
return writeSize(offset, size);
|
|