Methods Summary |
---|
private javax.media.protocol.CachedStream | getCacheStream()
return cacheStream;
|
public javax.media.Time | getDuration()
return movieHeader.duration;
|
public javax.media.Time | getMediaTime()
return null; // TODO
|
public java.lang.String | getName()Returns a descriptive name for the plug-in.
This is a user readable string.
return "Parser for quicktime file format";
|
public javax.media.protocol.ContentDescriptor[] | getSupportedInputContentDescriptors()
return supportedFormat;
|
public javax.media.Track[] | getTracks()
if (tracks != null)
return tracks;
if (seekableStream == null) {
return new Track[0];
}
if (cacheStream != null) {
// Disable jitter buffer during parsing of the header
cacheStream.setEnabledBuffering(false);
}
readHeader();
if (cacheStream != null) {
cacheStream.setEnabledBuffering(true);
}
tracks = new Track[numSupportedTracks];
// System.out.println("numTracks is " + numTracks);
// System.out.println("numSupportedTracks is " + numSupportedTracks);
int index = 0;
for (int i = 0; i < numSupportedTracks; i++) {
TrakList trakInfo = trakList[i];
if (trakInfo.trackType.equals(AUDIO)) {
tracks[i] = new AudioTrack(trakInfo);
// System.out.println("track id " + (index-1) + " : " +
// tracks[index-1]);
} else if (trakInfo.trackType.equals(VIDEO)) {
tracks[i] = new VideoTrack(trakInfo);
// System.out.println("track id " + (index-1) + " : " +
// tracks[index-1]);
}
}
for (int i = 0; i < numSupportedTracks; i++) {
TrakList trakInfo = trakList[i];
if (trakInfo.trackType.equals(HINT)) {
int trackBeingHinted = trakInfo.trackIdOfTrackBeingHinted;
for (int j = 0; j < numTracks; j++) {
if (trackBeingHinted == trakList[j].id) {
trakInfo.indexOfTrackBeingHinted = j;
String hintedTrackType = trakList[j].trackType;
String encodingOfHintedTrack =
trakList[trakInfo.indexOfTrackBeingHinted].media.encoding;
if (encodingOfHintedTrack.equals("agsm"))
encodingOfHintedTrack = "gsm";
String rtpEncoding = encodingOfHintedTrack + "/rtp";
if (hintedTrackType.equals(AUDIO)) {
int channels;
String encoding;
int frameSizeInBytes;
int samplesPerBlock;
int sampleRate;
Audio audio = (Audio) (trakList[j].media);
hintAudioTrackNum = i;
channels = audio.channels;
frameSizeInBytes = audio.frameSizeInBits / 8;
samplesPerBlock = audio.samplesPerBlock;
sampleRate = audio.sampleRate;
((Hint) trakInfo.media).format =
new AudioFormat(rtpEncoding,
(double) sampleRate,
8, // sampleSizeInBits [$$$ hardcoded]
channels);
tracks[i] = new HintAudioTrack(trakInfo,
channels,
rtpEncoding,
frameSizeInBytes,
samplesPerBlock,
sampleRate);
// System.out.println("track id " + (index-1) + " : " +
// tracks[index-1]);
} else if (hintedTrackType.equals(VIDEO)) {
int indexOfTrackBeingHinted = trakInfo.indexOfTrackBeingHinted;
TrakList sampleTrakInfo = null;
if (indexOfTrackBeingHinted >= 0) {
sampleTrakInfo = trakList[indexOfTrackBeingHinted];
}
int width = 0;
int height = 0;
if (sampleTrakInfo != null) {
Video sampleTrakVideo = (Video) sampleTrakInfo.media;
width = sampleTrakVideo.width;
height = sampleTrakVideo.height;
}
if ( (width > 0) && (height > 0) ) {
((Hint) trakInfo.media).format =
new VideoFormat(rtpEncoding,
new Dimension(width, height),
Format.NOT_SPECIFIED,
null, Format.NOT_SPECIFIED);
// System.out.println("VIDEO HINT TRACK FORMAT is " +
// ((Hint) trakInfo.media).format);
}
HintVideoTrack hintVideoTrack =
new HintVideoTrack(trakInfo);
tracks[i] = hintVideoTrack;
// System.out.println("track id " + (index-1) + " : " +
// tracks[index-1]);
}
break;
}
}
}
}
return tracks;
|
private boolean | isSupported(java.lang.String trackType)
if (enableHintTrackSupport) {
return ( trackType.equals(VIDEO) ||
trackType.equals(AUDIO) ||
trackType.equals(HINT)
);
} else {
return ( trackType.equals(VIDEO) ||
trackType.equals(AUDIO)
);
}
|
private boolean | parseAtom()
boolean readSizeField = false;
try {
int atomSize = readInt(stream);
// System.out.println("atomSize is " + atomSize);
readSizeField = true;
String atom = readString(stream);
// System.out.println("atom is " + atom);
if ( atomSize < 8 )
throw new BadHeaderException(atom + ": Bad Atom size " + atomSize);
if ( atom.equals("moov") )
return parseMOOV(atomSize - 8);
if ( atom.equals("mdat") )
return parseMDAT(atomSize - 8);
skipAtom(atom + " [not implemented]", atomSize - 8);
return true;
} catch (IOException e) {
// System.err.println("parseAtom: IOException " + e);
if (!readSizeField) {
// System.out.println("EOM");
return false; // EOM. Parsing done
}
throw new BadHeaderException("Unexpected End of Media");
}
|
private com.sun.media.parser.video.QuicktimeParser$Audio | parseAudioSampleData(java.lang.String encoding, int dataSize)
skip(stream, 2); // data reference index
// TODO: check for dataSize >= MIN_AUDIO_SAMPLE_DATA_SIZE
/**
* Skip versiom(2), Revision Level (2), Vendor(4),
*/
skip(stream, 8);
Audio audio = new Audio();
audio.encoding = encoding;
audio.channels = readShort(stream);
audio.bitsPerSample = readShort(stream);
/**
* Skip compression id (2),
* Skip packset size (2),
*/
skip(stream, 4);
int sampleRate = readInt(stream);
/**
* The media timeScale (foound in the mdhd atom) seems to
* represent the sampleRate (because it represents units/sec)
* This sampleRate field for some reason contains the
* timeScale shifted left by 16 bits. In other words sampleRate
* is media timeScale times 65536.
* Instead of dividing this by 65536, I am just using the
* media timeScale as sampleRate
* CHECK
*/
// audio.sampleRate = sampleRate >> 16; // Also works
audio.sampleRate = currentTrack.mediaTimeScale;
// System.out.println("mediaTimeScale is " + currentTrack.mediaTimeScale);
skip(stream, dataSize -2 -MIN_AUDIO_SAMPLE_DATA_SIZE); // 2 for data ref. index
return audio;
|
private void | parseCTAB(int ctabSize) // TODO
try {
// System.out.println("ctab not handled yet");
skip(stream, ctabSize); // DUMMY
} catch (IOException e) {
//TODO
throw new BadHeaderException("....");
}
|
private void | parseDINF(int dinfSize)
try {
int remainingSize = dinfSize;
// System.out.println("dinfSize is " + dinfSize);
while (remainingSize > 0) {
int atomSize = readInt(stream);
String atom = readString(stream);
// System.out.println("dinf: atomSize is " + atomSize);
// System.out.println("dinf: atom is " + atom);
if (atom.equals("dref")) {
parseDREF(atomSize - 8);
} else {
skipAtom(atom + " [Unknown atom in dinf]", atomSize - 8);
}
remainingSize -= atomSize;
}
} catch (IOException e) {
throw new BadHeaderException("Got IOException when seeking past DIMF atom");
}
|
private void | parseDREF(int drefSize)
try {
// TODO: add size check
// if (drefSize < MIN_DREF_ATOM_SIZE) {
// throw new BadHeaderException("dref atom: header size is incorrect");
// }
skip(stream, 4); // skip version and flags
int numEntries = readInt(stream);
// System.out.println("dref: number of entries is " + numEntries);
for (int i = 0; i < numEntries; i++) {
int drefEntrySize = readInt(stream);
// System.out.println("drefEntrySize is " + drefSize);
int type = readInt(stream);
// System.out.println("dref entry type is " + type);
/**
* Version: A 1-byte specification of the version of
* these data references.
* Flags: A 3-byte space for data reference flags.
* There is one defined flag. Self reference This flag
* indicates that the media's data is in the same file
* as the movie atom. On the Macintosh, and other file
* systems with multifork files, set this flag to 1
* even if the data resides in a different fork
* from the movie atom. This flag's value is 0x0001.
*/
int versionPlusFlag = readInt(stream);
// System.out.println("versionPlusFlag is " + versionPlusFlag);
skip(stream, drefEntrySize -(4+4+4));
if ( (versionPlusFlag & DATA_SELF_REFERENCE_FLAG) <= 0 ) {
throw new BadHeaderException("Only self contained Quicktime movies are supported");
}
}
} catch (IOException e) {
throw new BadHeaderException("Got IOException when seeking past DREF atom");
}
|
private void | parseHDLR(int hdlrSize)moov/trak/mdia/hdlr
try {
if (hdlrSize < MIN_HDLR_ATOM_SIZE) {
throw new BadHeaderException("hdlr atom: header size is incorrect");
}
// Skip version(1), flags(3), component type(4)
skip(stream, 8);
currentTrack.trackType = readString(stream);
// System.out.println("track type is " + currentTrack.trackType);
currentTrack.supported = isSupported(currentTrack.trackType);
// Skip the rest of the fields including the variable component
// name field
skip(stream, hdlrSize -8 -4);
} catch (IOException e) {
throw new BadHeaderException("Got IOException when seeking past HDLR atom");
}
|
private com.sun.media.parser.video.QuicktimeParser$Hint | parseHintSampleData(java.lang.String encoding, int dataSize)
// TODO: check for dataSize >= MIN_HINT_SAMPLE_DATA_SIZE
if (!encoding.equals("rtp ")) {
System.err.println("Hint track Data Format is not rtp");
}
// System.out.println("parseHintSampleData: dataSize is " + dataSize);
Hint hint = new Hint();
int dataReferenceIndex = readShort(stream);
int hintTrackVersion = readShort(stream);
if (hintTrackVersion == 0) {
System.err.println("Hint Track version #0 is not supported");
System.err.println("Use QuickTimePro to convert it to version #1");
currentTrack.supported = false;
if ((dataSize - 2 - 2) > 0)
skip(stream, (dataSize -2 -2));
return hint;
}
int lastCompatibleHintTrackVersion = readShort(stream);
int maxPacketSize = readInt(stream);
currentTrack.maxPacketSize = maxPacketSize;
int remaining = dataSize -2 -2 -2 -4;
if (debug1) {
System.out.println("dataReferenceIndex is " + dataReferenceIndex);
System.out.println("hintTrackVersion is " + hintTrackVersion);
System.out.println("lastCompatibleHintTrackVersion is " + lastCompatibleHintTrackVersion);
System.out.println("maxPacketSize is " + maxPacketSize);
System.out.println("remaining is " + remaining);
}
while (remaining > 8) {
// Additional data is present;
int entryLength = readInt(stream);
remaining -= 4;
if ( entryLength > 8) {
if (debug2)
System.out.println("entryLength is " + entryLength);
// entryLength -= 4;
String dataTag = readString(stream);
if (debug2)
System.out.println("dataTag is " + dataTag);
remaining -= 4;
// entryLength -= 4;
// TODO: assuming that the data tag is 'tims'. It can be tsro,snro,rely
if (dataTag.equals("tims")) {
// 32-bit integer specifying the RTP timescale. This entry is
// required for RTP data.
int rtpTimeScale = readInt(stream);
// System.out.println(" rtpTimeScale is " + rtpTimeScale);
// currentTrack.rtpTimeScale = dataValue;
// entryLength -= 4;
remaining -= 4;
} else if (dataTag.equals("tsro")) {
// 32-bit integer specifying the offset to add to the stored
// timestamp when sending RTP packets. If this entry is not
// present, a random offset should be used, as specified by the
// IETF. If this entry is 0, use an offset of 0 (no offset).
System.out.println("QuicktimeParser: rtp: tsro dataTag not supported");
int rtpTimeStampOffset = readInt(stream);
remaining -= 4;
} else if (dataTag.equals("snro")) {
// 32-bit integer specifying the offset to add to the sequence
// number when sending RTP packets. If this entry is not present, a
// random offset should be used, as specified by the IETF. If this
// entry is 0, use an offset of 0 (no offset).
System.out.println("QuicktimeParser: rtp: snro dataTag not supported");
int rtpSequenceNumberOffset = readInt(stream);
// System.out.println("rtpSequenceNumberOffset is " + rtpSequenceNumberOffset);
remaining -= 4;
} else if (dataTag.equals("rely")) {
// 8-bit flag indicating whether this track should or must be sent
// over a reliable transport, such as TCP/IP. If this entry is not
// present, unreliable transport should be used, such as RTP/UDP.
// The current client software for QuickTime streaming will only
// receive streaming tracks sent using RTP/UDP.
System.out.println("QuicktimeParser: rtp: rely dataTag not supported");
int rtpReliableTransportFlag = readByte(stream);
// System.out.println("rtpReliableTransportFlag is " + rtpReliableTransportFlag);
remaining--;
} else {
// Unknown flag: Error.
// TODO: handle this without skipping if possible
// May not be possible because we don't know how many bytes
// to skip before the next tag.
skip(stream, remaining);
remaining = 0;
}
} else {
skip(stream, remaining);
remaining = 0;
break;
}
}
if (remaining > 0)
skip(stream, remaining);
return hint;
|
private boolean | parseMDAT(int size)
try {
mdatAtomPresent = true;
movieHeader.mdatStart = getLocation( stream ); // Need this ??? TODO
movieHeader.mdatSize = size; // Need this ??? TODO
/** Seek past the MDAT atom only if the MOOV atom
* hasn't been seen yet.
* The only reason to seek past the MDAT atom even if the
* MOOV atom has been seen is to handle top level atoms
* like PNOT (Movie Preview data). We currently don't support
* PNOT atom.
* Also, We don't know how fast the
* seek is. If it is based on RandomAccessFile like
* Sun's file datasource, then it is pretty fast.
* But if it a cached http datasource over a slow
* internet connection, then the seek will take a long
* time. So seeking past the MDAT atom is not done unless
* the MOOV atom hasn't been seen yet.
*/
if (!moovAtomPresent) {
skip(stream, size);
return true; // Parsing continues as MOOV atom hasn't been seen
}
return false; // Parsing done
} catch (IOException e) {
throw new BadHeaderException("Got IOException when seeking past MDAT atom");
}
|
private void | parseMDHD(int mdhdSize)moov/trak/mdia/mdhd
try {
if (mdhdSize != MDHD_ATOM_SIZE) {
throw new BadHeaderException("mdhd atom: header size is incorrect");
}
// Skip version(1), flags(3), creation time(4), modification time(4)
skip(stream, 12);
int timeScale = readInt(stream);
int duration = readInt(stream);
currentTrack.mediaDuration = new Time((double) duration / timeScale);
currentTrack.mediaTimeScale = timeScale;
skip(stream, 4); // Skip language(2) and quality(2) fields
} catch (IOException e) {
throw new BadHeaderException("Got IOException when seeking past MDHD atom");
}
|
private boolean | parseMDIA(int mdiaSize)Required atoms are mdhd, hdlr and minf
Doesn't say in the spec. that hdlr or minf is a required atom,
but can we play a qt file without a hdlr or minf atoms
hdlr atom should come before minf atom
Return true if the trackType is supported
moov/trak/mdia
boolean hdlrAtomPresent = false;
boolean minfAtomPresent = false;;
try {
currentTrack.trackType = null;
int remainingSize = mdiaSize;
int atomSize = readInt(stream);
String atom = readString(stream);
if ( atomSize < 8 )
throw new BadHeaderException(atom + ": Bad Atom size " + atomSize);
if ( ! atom.equals("mdhd") ) {
throw new BadHeaderException("Expected mdhd atom but got " + atom);
}
parseMDHD(atomSize - 8);
remainingSize -= atomSize;
// TODO: before calling parseXXX, should check if
// (atomSize - 8) >= remainingSize
while (remainingSize > 0) {
atomSize = readInt(stream);
atom = readString(stream);
if (atom.equals("hdlr")) {
parseHDLR(atomSize - 8); // Updates trackType in currentTrack
hdlrAtomPresent = true;
} else if (atom.equals("minf")) {
if (currentTrack.trackType == null) {
throw new BadHeaderException("In MDIA atom container minf atom appears before hdlr");
}
if (currentTrack.supported) {
parseMINF(atomSize - 8);
} else {
skipAtom(atom + " [atom in mdia] as trackType " +
currentTrack.trackType + " is not supported",
atomSize - 8);
}
minfAtomPresent = true;
} else {
skipAtom(atom + " [atom in mdia: not implemented]", atomSize - 8);
}
remainingSize -= atomSize;
}
if (!hdlrAtomPresent)
throw new BadHeaderException("hdlr atom not present in mdia atom container");
if (!minfAtomPresent)
throw new BadHeaderException("minf atom not present in mdia atom container");
return (currentTrack.supported);
} catch (IOException e) {
throw new BadHeaderException("Got IOException when seeking past MDIA atom");
}
|
private void | parseMINF(int minfSize)Required atoms are [vsg]mhd, hdlr
Optional atoms are dinf and stbl
Currently we skip [vsg]mhd, hdlr and dinf atoms and only
handle stbl
boolean hdlrAtomPresent = false;
try {
int remainingSize = minfSize;
int atomSize = readInt(stream);
String atom = readString(stream);
if ( atomSize < 8 )
throw new BadHeaderException(atom + ": Bad Atom size " + atomSize);
if ( ! atom.endsWith("hd") ) {
throw new BadHeaderException("Expected media information header atom but got " + atom);
}
skipAtom(atom + " [atom in minf: not implemented]", atomSize - 8);
remainingSize -= atomSize;
// TODO: before calling parseXXX, should check if
// (atomSize - 8) >= remainingSize
while (remainingSize > 0) {
atomSize = readInt(stream);
atom = readString(stream);
if (atom.equals("hdlr")) {
skipAtom(atom + " [atom in minf: not implemented]", atomSize - 8);
hdlrAtomPresent = true;
} else if (atom.equals("dinf")) {
parseDINF(atomSize - 8);
} else if (atom.equals("stbl")) {
parseSTBL(atomSize - 8);
} else {
skipAtom(atom + " [atom in minf: not implemented]", atomSize - 8);
}
remainingSize -= atomSize;
}
if (!hdlrAtomPresent)
throw new BadHeaderException("hdlr atom not present in minf atom container");
} catch (IOException e) {
throw new BadHeaderException("Got IOException when seeking past MINF atom");
}
|
private boolean | parseMOOV(int moovSize)Required atoms are mvhd and trak
Doesn't say in the spec. that trak is a required atom,
but can we play a qt file without a trak atom??
boolean trakAtomPresent = false;
try {
moovAtomPresent = true;
long moovMax = getLocation(stream) + moovSize;
int remainingSize = moovSize;
int atomSize = readInt(stream);
String atom = readString(stream);
if ( atomSize < 8 )
throw new BadHeaderException(atom + ": Bad Atom size " + atomSize);
if ( ! atom.equals("mvhd") ) {
if (atom.equals("cmov"))
throw new BadHeaderException("Compressed movie headers are not supported");
else
throw new BadHeaderException("Expected mvhd atom but got " + atom);
}
parseMVHD(atomSize - 8);
// System.out.println("Duration of movie is " +
// movieHeader.duration.getSeconds());
remainingSize -= atomSize;
// TODO: before calling parseXXX, should check if
// (atomSize - 8) >= remainingSize
while (remainingSize > 0) {
atomSize = readInt(stream);
atom = readString(stream);
if (atom.equals("trak")) {
if (trakList[numSupportedTracks] == null) {
trakList[numSupportedTracks] = currentTrack = new TrakList();
}
if (parseTRAK(atomSize - 8)) {
numSupportedTracks++;
}
trakAtomPresent = true;
numTracks++;
} else if (atom.equals("ctab")) {
parseCTAB(atomSize - 8);
} else {
skipAtom(atom + " [atom in moov: not implemented]", atomSize - 8);
}
remainingSize -= atomSize;
}
if (!trakAtomPresent)
throw new BadHeaderException("trak atom not present in trak atom container");
// Parsing is done if the MDAT atom has also been seen.
return !mdatAtomPresent;
} catch (IOException e) {
throw new BadHeaderException("IOException when parsing the header");
}
|
private void | parseMVHD(int size)MVHD is a leaf atom of size MVHD_ATOM_SIZE (100)
try {
if (size != MVHD_ATOM_SIZE) {
throw new BadHeaderException("mvhd atom: header size is incorrect");
}
// Skip version(1), flags(3), create time (4), mod time (4)
skip(stream, 12);
movieHeader.timeScale = readInt(stream);
int duration = readInt(stream);
movieHeader.duration = new Time((double) duration /
movieHeader.timeScale);
int preferredRate = readInt(stream);
int preferredVolume = readShort(stream);
skip(stream, 10); // Reserved
skip(stream, 36); // MATRIX
int previewTime = readInt(stream);
int previewDuration = readInt(stream);
int posterTime = readInt(stream);
int selectionTime = readInt(stream);
int selectionDuration = readInt(stream);
int currentTime = readInt(stream);
int nextTrackID = readInt(stream);
} catch (IOException e) {
throw new BadHeaderException("Got IOException when seeking past MVHD atom");
}
|
private void | parseSTBL(int stblSize)Required atoms are none.
try {
int remainingSize = stblSize;
while (remainingSize > 0) {
int atomSize = readInt(stream);
String atom = readString(stream);
if (atom.equals("stsd")) {
parseSTSD(atomSize - 8);
} else if (atom.equals("stts")) {
parseSTTS(atomSize - 8);
} else if (atom.equals("stss")) {
parseSTSS(atomSize - 8);
} else if (atom.equals("stsc")) {
parseSTSC(atomSize - 8);
} else if (atom.equals("stsz")) {
parseSTSZ(atomSize - 8);
} else if (atom.equals("stco")) {
parseSTCO(atomSize - 8);
} else if (atom.equals("stsh")) {
// parseSTSH(atomSize - 8);
skipAtom(atom + " [not implemented]", atomSize - 8);
} else {
skipAtom(atom + " [UNKNOWN atom in stbl: ignored]", atomSize - 8);
}
remainingSize -= atomSize;
}
} catch (IOException e) {
throw new BadHeaderException("Got IOException when seeking past STBL atom");
}
|
private void | parseSTCO(int stcoSize)Chunk offset atom
STCO is a leaf atom of minimum size MIN_STCO_ATOM_SIZE (8)
if (debug2)
System.out.println("rtp:parseSTCO: " + stcoSize);
try {
if (stcoSize < MIN_STCO_ATOM_SIZE) {
throw new BadHeaderException("stco atom: header size is incorrect");
}
/**
* Skip versiom(1), Flags(3)
*/
skip(stream, 4);
// numEntries should be equal to number of Chunks
int numEntries = readInt(stream);
currentTrack.numberOfChunks = numEntries;
int[] chunkOffsets = new int[numEntries];
int requiredSize = (stcoSize - MIN_STCO_ATOM_SIZE - numEntries*4);
if ( requiredSize < 0) {
throw new BadHeaderException("stco atom: inconsistent number_of_entries field");
}
int remaining = numEntries;
// 1 int is written in each loop
int numIntsWrittenPerLoop = 1;
int maxEntriesPerLoop = tmpIntBufferSize / numIntsWrittenPerLoop;
int i = 0;
while (remaining > 0) {
int numEntriesPerLoop =
(remaining > maxEntriesPerLoop) ? maxEntriesPerLoop : remaining;
readBytes(stream, tmpBuffer,
numEntriesPerLoop * numIntsWrittenPerLoop * 4);
int offset = 0;
for (int ii = 1; ii <= numEntriesPerLoop; ii++, i++) {
chunkOffsets[i] = parseIntFromArray(tmpBuffer, offset, true);
offset += 4;
}
remaining -= numEntriesPerLoop;
}
currentTrack.chunkOffsets = chunkOffsets;
skip(stream, requiredSize);
} catch (IOException e) {
throw new BadHeaderException("Got IOException when seeking past STCO atom");
}
|
private void | parseSTSC(int stscSize)
try {
if (stscSize < MIN_STSC_ATOM_SIZE) {
throw new BadHeaderException("stsc atom: header size is incorrect");
}
/**
* Skip versiom(1), Flags(3)
*/
skip(stream, 4);
int numEntries = readInt(stream);
int requiredSize = (stscSize - MIN_STSC_ATOM_SIZE - numEntries*12);
if ( requiredSize < 0) {
throw new BadHeaderException("stsc atom: inconsistent number_of_entries field");
}
/**
* At this point we don't know how many chunks there are
* and so we cannot compute the samplePerChunk array
* TODO: make use of the sampleDescriptionId field
*/
int compactSamplesChunkNum[] = new int[numEntries];
int compactSamplesPerChunk[] = new int[numEntries];
byte[] tmpBuf = new byte[numEntries*4*3];
readBytes(stream, tmpBuf, numEntries*4*3);
int offset = 0;
for (int i = 0; i < numEntries; i++) {
compactSamplesChunkNum[i] = parseIntFromArray(tmpBuf, offset, true);
offset += 4;
compactSamplesPerChunk[i] = parseIntFromArray(tmpBuf, offset, true);
offset += 4;
// int sampleDescriptionId = readInt(stream);
offset += 4; // skip next 4 bytes
}
tmpBuf = null;
currentTrack.compactSamplesChunkNum = compactSamplesChunkNum;
currentTrack.compactSamplesPerChunk = compactSamplesPerChunk;
skip(stream, requiredSize);
} catch (IOException e) {
throw new BadHeaderException("Got IOException when seeking past STSC atom");
}
|
private void | parseSTSD(int stsdSize)STSD is a leaf atom of minimum size MIN_STSD_ATOM_SIZE (8)
// System.out.println("stsd size is " + stsdSize);
try {
if (stsdSize < MIN_STSD_ATOM_SIZE) {
throw new BadHeaderException("stsd atom: header size is incorrect");
}
// Note: if the trackType is not
// supported the minf atom is skipped and so you will not
// come here.
skip(stream, 4); // skip version and flags
int numEntries = readInt(stream);
//$$ System.out.println("stsd: numEntries is " + numEntries);
if ( numEntries > 1) {
// System.err.println("Multiple formats in a track not supported");
}
for (int i = 0; i < numEntries; i++) {
int sampleDescriptionSize = readInt(stream);
//$$ System.out.println("stsd: sampleDescriptionSize is " + sampleDescriptionSize);
// CHECK ?? spec. says int but is it a 4 letter String????
String encoding = readString(stream);
// System.out.println("stsd: encoding is " + encoding);
if (i != 0) {
skip(stream, sampleDescriptionSize - 8);
continue;
}
// skip(stream, 8); // 6 reserved bytes + 2 for data reference index
skip(stream, 6); // 6 reserved bytes
// TODO: check of sampleDescriptionSize is atleast 16 bytes
if (currentTrack.trackType.equals(VIDEO)) {
currentTrack.media =
parseVideoSampleData(encoding,
sampleDescriptionSize -4 -4 -6);
} else if (currentTrack.trackType.equals(AUDIO)) {
currentTrack.media =
parseAudioSampleData(encoding,
sampleDescriptionSize -4 -4 -6);
} else if (currentTrack.trackType.equals(HINT)) {
numberOfHintTracks++;
currentTrack.media =
parseHintSampleData(encoding,
sampleDescriptionSize -4 -4 -6);
} else {
// Note: you will never come into this else block.
// If the trackType is not supported, the minf atom is skipped
// and so you will not come here.
skip(stream,
sampleDescriptionSize - 4 - 4 -6);
}
}
} catch (IOException e) {
throw new BadHeaderException("Got IOException when seeking past STSD atom");
}
|
private void | parseSTSS(int stssSize)Sync sample atom
STSS is a leaf atom of minimum size MIN_STSS_ATOM_SIZE (8)
try {
if (stssSize < MIN_STSS_ATOM_SIZE) {
throw new BadHeaderException("stss atom: header size is incorrect");
}
/**
* Skip versiom(1), Flags(3)
*/
skip(stream, 4);
int numEntries = readInt(stream);
int requiredSize = (stssSize - MIN_STSS_ATOM_SIZE - numEntries*4);
if ( requiredSize < 0) {
throw new BadHeaderException("stss atom: inconsistent number_of_entries field");
}
if (numEntries < 1) {
skip(stream, requiredSize);
return;
}
int[] syncSamples = new int[numEntries];
int remaining = numEntries;
// 1 int is written in each loop
int numIntsWrittenPerLoop = 1;
int maxEntriesPerLoop = tmpIntBufferSize / numIntsWrittenPerLoop;
int i = 0;
while (remaining > 0) {
int numEntriesPerLoop =
(remaining > maxEntriesPerLoop) ? maxEntriesPerLoop : remaining;
readBytes(stream, tmpBuffer,
numEntriesPerLoop * numIntsWrittenPerLoop * 4);
int offset = 0;
for (int ii = 1; ii <= numEntriesPerLoop; ii++, i++) {
syncSamples[i] = parseIntFromArray(tmpBuffer, offset, true);
offset += 4;
}
remaining -= numEntriesPerLoop;
}
currentTrack.syncSamples = syncSamples;
skip(stream, requiredSize);
} catch (IOException e) {
throw new BadHeaderException("Got IOException when seeking past STSS atom");
}
|
private void | parseSTSZ(int stszSize)Sample Size Atom
STSZ is a leaf atom of minimum size MIN_STSZ_ATOM_SIZE (8)
if (debug2)
System.out.println("parseSTSZ: " + stszSize);
try {
if (stszSize < MIN_STSZ_ATOM_SIZE) {
throw new BadHeaderException("stsz atom: header size is incorrect");
}
/**
* Skip versiom(1), Flags(3)
*/
skip(stream, 4);
currentTrack.sampleSize = readInt(stream);
if (currentTrack.sampleSize != 0) {
// All samples are of same sample size
skip(stream, stszSize - MIN_STSZ_ATOM_SIZE);
currentTrack.media.maxSampleSize = currentTrack.sampleSize;
return;
}
// All samples are not of same size
if ( (stszSize - MIN_STSZ_ATOM_SIZE) < 4) { // for numEntries
throw new BadHeaderException("stsz atom: incorrect atom size");
}
int numEntries = readInt(stream);
if (currentTrack.numberOfSamples == 0) {
currentTrack.numberOfSamples = numEntries; // TODO: ????
} else {
// TODO: if not they are inconsistent: should throw BadHeaderException
}
int requiredSize = (stszSize - MIN_STSZ_ATOM_SIZE
- 4 // for numEntries
- numEntries*4);
if ( requiredSize < 0) {
throw new BadHeaderException("stsz atom: inconsistent number_of_entries field");
}
int[] sampleSizeArray = new int[numEntries];
int maxSampleSize = Integer.MIN_VALUE;
int value;
int remaining = numEntries;
// 1 int is written in each loop
int numIntsWrittenPerLoop = 1;
int maxEntriesPerLoop = tmpIntBufferSize / numIntsWrittenPerLoop;
int i = 0;
while (remaining > 0) {
int numEntriesPerLoop =
(remaining > maxEntriesPerLoop) ? maxEntriesPerLoop : remaining;
readBytes(stream, tmpBuffer,
numEntriesPerLoop * numIntsWrittenPerLoop * 4);
int offset = 0;
for (int ii = 1; ii <= numEntriesPerLoop; ii++, i++) {
value = parseIntFromArray(tmpBuffer, offset, true);
offset += 4;
if (value > maxSampleSize)
maxSampleSize = value;
sampleSizeArray[i] = value;
}
remaining -= numEntriesPerLoop;
}
currentTrack.sampleSizeArray = sampleSizeArray;
currentTrack.media.maxSampleSize = maxSampleSize;
skip(stream, requiredSize);
} catch (IOException e) {
throw new BadHeaderException("Got IOException when seeking past STSZ atom");
}
|
private void | parseSTTS(int sttsSize)Time to Sample atom
STTS is a leaf atom of minimum size MIN_STTS_ATOM_SIZE (8)
if (debug2)
System.out.println("parseSTTS: " + sttsSize);
try {
if (sttsSize < MIN_STTS_ATOM_SIZE) {
throw new BadHeaderException("stts atom: header size is incorrect");
}
/**
* Skip versiom(1), Flags(3)
*/
skip(stream, 4);
int numEntries = readInt(stream);
if (debug2)
System.out.println("numEntries is " + numEntries);
int requiredSize = (sttsSize - MIN_STTS_ATOM_SIZE - numEntries*8);
if ( requiredSize < 0) {
throw new BadHeaderException("stts atom: inconsistent number_of_entries field");
}
int totalNumSamples = 0;
double timeScaleFactor = (1.0 / currentTrack.mediaTimeScale);
if ( numEntries == 1) {
totalNumSamples = readInt(stream);
currentTrack.durationOfSamples = readInt(stream) * timeScaleFactor;
} else {
int[] timeToSampleIndices = new int[numEntries];
double[] durations = new double[numEntries];
timeToSampleIndices[0] = readInt(stream);
totalNumSamples += timeToSampleIndices[0];
durations[0] = readInt(stream) * timeScaleFactor *
timeToSampleIndices[0];
int remaining = numEntries - 1; // As first 2 entries is already read.
// 2 ints are written in each loop
int numIntsWrittenPerLoop = 2;
// integer division
int maxEntriesPerLoop = tmpIntBufferSize / numIntsWrittenPerLoop;
int i = 1;
while (remaining > 0) {
int numEntriesPerLoop =
(remaining > maxEntriesPerLoop) ? maxEntriesPerLoop : remaining;
readBytes(stream, tmpBuffer,
numEntriesPerLoop * numIntsWrittenPerLoop * 4);
int offset = 0;
for (int ii = 1; ii <= numEntriesPerLoop; ii++, i++) {
timeToSampleIndices[i] =
parseIntFromArray(tmpBuffer, offset, true);
offset += 4;
int value = parseIntFromArray(tmpBuffer, offset, true);
offset += 4;
durations[i] += ( value * timeScaleFactor *
timeToSampleIndices[i] +
durations[i-1] );
totalNumSamples += timeToSampleIndices[i];
timeToSampleIndices[i] = totalNumSamples;
}
remaining -= numEntriesPerLoop;
}
currentTrack.timeToSampleIndices = timeToSampleIndices;
currentTrack.cumulativeDurationOfSamples = durations;
}
if (currentTrack.numberOfSamples == 0) {
currentTrack.numberOfSamples = totalNumSamples;
} else {
// TODO: if not they are inconsistent: should throw BadHeaderException
}
skip(stream, requiredSize);
} catch (IOException e) {
throw new BadHeaderException("Got IOException when seeking past STTS atom");
}
|
private void | parseTKHD(int tkhdSize)TKHD is a leaf atom of size TKHD_ATOM_SIZE (84)
try {
if (tkhdSize != TKHD_ATOM_SIZE) {
throw new BadHeaderException("mvhd atom: header size is incorrect");
}
int iVersionPlusFlag = readInt(stream);
currentTrack.flag = iVersionPlusFlag & 0xFFFFFF;
skip(stream, 8); // Skip creation time and modification time
currentTrack.id = readInt(stream);
// System.out.println("<<<<<<<< id is >>>>>> " + currentTrack.id);
// System.out.println("<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>");
// System.out.println("<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>");
// System.out.println("<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>");
// System.out.println("<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>");
// System.out.println("<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>");
// System.out.println("<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>");
skip(stream, 4); // Skip reserved field
int duration = readInt(stream);
currentTrack.duration = new Time((double) duration /
movieHeader.timeScale);
skip(stream, tkhdSize -4 -8 -4 -4 -4); // Skip the rest of the fields
} catch (IOException e) {
throw new BadHeaderException("Got IOException when seeking past TKHD atom");
}
|
private boolean | parseTRAK(int trakSize)Required atoms are tkhd and mdia
boolean mdiaAtomPresent = false;
boolean supported = false; // is trackType supported
try {
int remainingSize = trakSize;
int atomSize = readInt(stream);
String atom = readString(stream);
if ( atomSize < 8 )
throw new BadHeaderException(atom + ": Bad Atom size " + atomSize);
if ( ! atom.equals("tkhd") ) {
throw new BadHeaderException("Expected tkhd atom but got " + atom);
}
parseTKHD(atomSize - 8);
remainingSize -= atomSize;
// TODO: before calling parseXXX, should check if
// (atomSize - 8) >= remainingSize
while (remainingSize > 0) {
atomSize = readInt(stream);
atom = readString(stream);
if (atom.equals("mdia")) {
supported = parseMDIA(atomSize - 8);
mdiaAtomPresent = true;
} else if (atom.equals("tref")) {
parseTREF(atomSize - 8);
} else {
skipAtom(atom + " [atom in trak: not implemented]", atomSize - 8);
}
remainingSize -= atomSize;
}
} catch (IOException e) {
throw new BadHeaderException("Got IOException when seeking past TRAK atom");
}
if (!mdiaAtomPresent)
throw new BadHeaderException("mdia atom not present in trak atom container");
// FYI$$: Some files like vinton.mov have an audio track but
// do not have a stsd chunk. Apple's movie player plays only
// the video in this case. This case is now handled.
// gracefully. But there may be other cases like this.
// We need to update the if statement accordingly.
if ( supported &&
(currentTrack.media == null) ) {
supported = false;
}
return supported;
|
private void | parseTREF(int size)
try {
int childAtomSize = readInt(stream);
size -= 4;
// System.out.println("parseTREF: childAtomSize is " + childAtomSize);
String atom = readString(stream);
size -= 4;
if (atom.equalsIgnoreCase("hint")) {
currentTrack.trackIdOfTrackBeingHinted = readInt(stream);
size -= 4;
// System.out.println("trackBeingHinted is " + currentTrack.trackIdOfTrackBeingHinted);
}
skip(stream, size);
} catch (IOException e) {
throw new BadHeaderException("Got IOException when seeking past HDLR atom");
}
|
private com.sun.media.parser.video.QuicktimeParser$Video | parseVideoSampleData(java.lang.String encoding, int dataSize)
// TODO: check for dataSize >= MIN_VIDEO_SAMPLE_DATA_SIZE
skip(stream, 2); // data reference index
/**
* Skip versiom(2), Revision Level (2), Vendor(4),
* Temporal Quality (4), Spatial Quality (4);
*/
skip(stream, 16);
Video video = new Video();
video.encoding = encoding;
video.width = readShort(stream);
video.height = readShort(stream);
/**
* Skip Horizontal resolution (4),
* Skip Vertical resolution (4),
* Skip data size (4),
* Skip frame count (2),
*/
skip(stream, 14);
/* Skip compressor name */
skip(stream, 32);
video.pixelDepth = readShort(stream);
video.colorTableID = readShort(stream);
int colorTableSize = 0;
if (video.colorTableID == 0) {
// Color table follows colorTableID
colorTableSize = readInt(stream);
skip(stream, colorTableSize -4); // TODO: DUMMY
}
skip(stream, dataSize - 2 - MIN_VIDEO_SAMPLE_DATA_SIZE -
- colorTableSize); // 2 for data ref. index
return video;
|
private void | readHeader()
while ( parseAtom() );
if ( !moovAtomPresent )
throw new BadHeaderException("moov atom not present");
if ( !mdatAtomPresent )
throw new BadHeaderException("mdat atom not present");
// System.out.println("Number of tracks is " + numTracks);
// System.out.println("Number of supported/valid tracks is " + numSupportedTracks);
for (int i = 0; i < numSupportedTracks; i++) {
TrakList trak = trakList[i];
// System.out.println("track index " + i + " encoding " +
// trak.media.encoding);
// System.out.println("Number of frames in track " +
// trak.trackType + " : " +
// + i +
// " is " + trak.numberOfSamples);
// System.out.println("Duration of track " + i +
// trak.duration.getSeconds());
if (trak.buildSyncTable()) {
keyFrameTrack = i;
}
// System.out.println("$$$$ Call buildSamplePerChunkTable for track id " + trak.id);
trak.buildSamplePerChunkTable();
// Table is built for VIDEO and hint tracks but not
// for audio tracks.
if ( !trak.trackType.equals(AUDIO) ) {
trak.buildSampleOffsetTable();
// System.out.println("Creating buildSampleOffsetTable for track " +
// trak.trackType + " : " +
// trak.sampleOffsetTable);
trak.buildStartTimeAndDurationTable();
float frameRate = (float) (trak.numberOfSamples /
trak.duration.getSeconds());
//$$$$$ ((Video) trak.media).frameRate = frameRate;
trak.media.frameRate = frameRate;
}
// NOTE: The next method should be called after buildSampleOffsetTable()
trak.buildCumulativeSamplePerChunkTable();
trak.media.createFormat();
// System.out.println("track " + (i+1) + " info: ");
// System.out.println("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
// System.out.println(trak);
// System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n");
}
|
public javax.media.Time | setPosition(javax.media.Time where, int rounding)
double time = where.getSeconds();
if (time < 0)
time = 0;
// if ( (keyFrameTrack != -1) && (tracks[keyFrameTrack].isEnabled()) ) {
int keyT;
if ( (((keyT = keyFrameTrack) != -1) && (tracks[keyFrameTrack].isEnabled())) ||
(((keyT = hintAudioTrackNum) != -1) && (tracks[hintAudioTrackNum].isEnabled())) ) {
TrakList trakInfo = trakList[keyT];
int index = trakInfo.time2Index(time);
if (index < 0) {
((MediaTrack)tracks[keyT]).setSampleIndex(trakInfo.numberOfSamples + 1); // past eom
} else {
int syncIndex;
if (keyT == keyFrameTrack) {
if (index >= trakInfo.syncSampleMapping.length) {
index = trakInfo.syncSampleMapping.length - 1;
}
if (trakInfo.syncSampleMapping != null) {
syncIndex = trakInfo.syncSampleMapping[index];
double newtime = trakInfo.index2TimeAndDuration(syncIndex).startTime;
time = newtime;
} else {
// Note: you won't come here because syncSampleMapping wont
// be null in this case.
syncIndex = index;
}
} else { // hint audio track
syncIndex = index;
double newtime = trakInfo.index2TimeAndDuration(syncIndex).startTime;
time = newtime;
}
((MediaTrack)tracks[keyT]).setSampleIndex(syncIndex);
}
}
for (int i = 0; i < numSupportedTracks; i++) {
if (i == keyT)
continue;
if (!tracks[i].isEnabled())
continue;
// TODO: See if you can just call a setPosition or
// setIndex method for each media type, instead of
// using if statement
TrakList trakInfo = trakList[i];
// Note that the time here may not be the same as the
// the "Time where" parameter passed into this method.
// The time may be changed if it doesn't map to a keyFrame
// in the Video track.
int index = trakInfo.time2Index(time);
// if ( trakInfo.trackType.equals(VIDEO) ||
// trakInfo.trackType.equals(HINT) ) {
if ( trakInfo.trackType.equals(VIDEO) ||
( trakInfo.trackType.equals(HINT) &&
(tracks[i] instanceof HintVideoTrack)) ) {
if (index < 0) {
((MediaTrack)tracks[i]).setSampleIndex(trakInfo.numberOfSamples + 1); // past eom
} else {
int syncIndex;
if (trakInfo.syncSampleMapping != null) {
syncIndex = trakInfo.syncSampleMapping[index];
} else
syncIndex = index;
((MediaTrack)tracks[i]).setSampleIndex(syncIndex);
}
} else { // TODO: if you have other track types, then check for AUDIO here
if (index < 0) {
((MediaTrack)tracks[i]).setChunkNumber(trakInfo.numberOfChunks + 1); // past eom
} else {
int sampleOffsetInChunk;
((MediaTrack)tracks[i]).setSampleIndex(index);
// $$$$$ IMPORTANT TODO: fix this as the index2Chunk method
// takes index starting from 1, not 0
int chunkNumber = trakInfo.index2Chunk(index);
if (chunkNumber != 0) {
if ( trakInfo.constantSamplesPerChunk == -1) {
// Note samplesPerChunk array contains cumulative
// samples per chunk
sampleOffsetInChunk = index -
trakInfo.samplesPerChunk[chunkNumber-1];
} else {
// TODO: need to test this case
sampleOffsetInChunk = index -
chunkNumber *
trakInfo.constantSamplesPerChunk;
}
} else {
sampleOffsetInChunk = index;
}
((AudioTrack)tracks[i]).setChunkNumberAndSampleOffset(chunkNumber,
sampleOffsetInChunk);
}
}
}
if (cacheStream != null) {
synchronized(this) {
cacheStream.abortRead();
}
}
synchronized(mediaTime) {
mediaTime.set(time);
}
return mediaTime;
|
public void | setSource(javax.media.protocol.DataSource source)
super.setSource(source);
stream = (PullSourceStream) streams[0];
seekableStream = (Seekable) streams[0];
|
private void | skipAtom(java.lang.String atom, int size)
if (debug2)
System.out.println("skip unsupported atom " + atom);
skip(stream, size);
|
protected boolean | supports(javax.media.protocol.SourceStream[] s)Quicktime format requires that the stream be seekable and
random accessible.
return seekable;
|