/* * @(#)MpegParser.java 1.50 01/02/27 * * Licensed Materials - Property of IBM * "Restricted Materials of IBM" * 5648-B81 * (c) Copyright IBM Corporation 1998,1999 All Rights Reserved * US Government Users Restricted Rights - Use, duplication or * disclosure restricted by GSA ADP Schedule Contract with * IBM Corporation. * */ package com.ibm.media.parser.video; import java.security.*; import java.lang.reflect.Method; import java.lang.reflect.Constructor; import java.io.OutputStream; //EE temporary! import java.io.FileOutputStream; //EE temporary! import java.io.IOException; import java.awt.Dimension; import javax.media.*; import javax.media.protocol.DataSource; import javax.media.protocol.SourceStream; import javax.media.protocol.PullSourceStream; import javax.media.protocol.Seekable; import javax.media.protocol.Positionable; import javax.media.Format; import javax.media.protocol.ContentDescriptor; import javax.media.protocol.FileTypeDescriptor; import javax.media.format.AudioFormat; import javax.media.format.VideoFormat; import com.sun.media.format.WavAudioFormat; import com.sun.media.parser.BasicPullParser; import com.sun.media.parser.BasicTrack; import com.sun.media.util.jdk12; import com.sun.media.util.LoopThread; import com.sun.media.JMFSecurity; import com.sun.media.JMFSecurityManager; import com.sun.media.CircularBuffer; import com.sun.media.Log; import com.ms.security.PermissionID; import com.ms.security.PolicyEngine; public class MpegParser extends BasicPullParser { /* * Temporary fields */ /* temporary for saving in output files */ boolean saveOutputFlag = false; //true; String AoutName = "Audio.mpg"; String VoutName = "Video.mpg"; FileOutputStream aout; FileOutputStream vout; /* temporary for throwing all data away */ boolean throwOutputFlag = false; /// true; /* As long as MPEG audio decoder doesn't exist, the audio track should be * hidden inside the parser. That's mean that only video tracks are output * of getTracks, and once in a while the audio buffer should be just flushed. * There is a similar option to ignore the video data. */ boolean hideAudioTracks = false; //true; boolean hideVideoTracks = false; /* * Constants definitions for the MPEG-1 system layer splitter */ static final long NO_PTS_VAL = -3333333; /* an arbitray value */ private static final float EPSILON_PTS = 45000; /* which is 0.5 sec in PTS units */ private static final float EPSILON_NS = 500000000; /* which is 0.5 sec in nanoseconds */ /* The nature of MPEG video stream is that I frames usually apears every * 15 frames. In setPosition, we want to have an I frame before the time * being setted. This is why we will try to look for time stamp of 0.5 seconds * earlier (in common frame rates of 24-30 frames per second, 0.5 second will * probably include an I frame). */ private static final long PRE_ROLLING_DELTA_NS = 500000000; /* which is 0.5 sec in nano-seconds */ /* packet/track/stream type */ private static final byte UNKNOWN_TYPE = 0; private static final byte AUDIO_TYPE = 1; private static final byte VIDEO_TYPE = 2; private static final byte SYS11172_TYPE = 3; /* streams buffer size */ private static final int AUDIO_TRACK_BUF_SIZE = 100000; private static final int VIDEO_TRACK_BUF_SIZE = 200000; /* codes definition */ private static final int PACK_START_CODE = 0x000001BA; private static final int SYSTEM_HEADER_START_CODE = 0x000001BB; private static final int PACKET_START_CODE_24 = 0x000001; /* 24 bits of 0x000001 */ private static final int END_CODE = 0x000001B9; private static final int MIN_STREAM_CODE = 0x00BC; private static final int MAX_STREAM_CODE = 0x00FF; private static final int PRIVATE_STREAM2_CODE = 0x00BF; private static final int VIDEO_PICTURE_START_CODE = 0x00000100; private static final int VIDEO_SEQUENCE_HEADER_CODE = 0x000001B3; private static final int VIDEO_GROUP_START_CODE = 0x000001B8; /* streams IDs */ private static final int MAX_AUDIO_STREAMS = 32; private static final int MAX_VIDEO_STREAMS = 16; private static final int MAX_NUM_STREAMS = 48; private static final int MIN_AUDIO_ID = 0; private static final int MAX_AUDIO_ID = 31; private static final int MIN_VIDEO_ID = 32; private static final int MAX_VIDEO_ID = 47; private static int MAX_TRACKS_SUPPORTED = MAX_NUM_STREAMS; /* * Fields */ private static ContentDescriptor[] supportedFormat = new ContentDescriptor[] { new ContentDescriptor("audio.mpeg"), new ContentDescriptor(FileTypeDescriptor.MPEG), new ContentDescriptor(FileTypeDescriptor.MPEG_AUDIO)}; private PullSourceStream stream = null; private TrackList[] trackList = new TrackList[MAX_TRACKS_SUPPORTED]; private Track[] tracks = null; private Track[] videoTracks = null; /// temporary, for hiding audio tracks private Track[] audioTracks = null; /// temporary, for hiding video tracks private int videoCount = 0; /// temporary, for hiding audio tracks private int audioCount = 0; /// temporary, for hiding video tracks private int numSupportedTracks = 0; private int numTracks = 0; private int numPackets = 0; private int initTmpBufLen; // a buffer used in the initation phase private byte[] initTmpStreamBuf; private byte streamType = UNKNOWN_TYPE; // stream can be of: Unknown/Audio/Video/System11172 type private long streamContentLength = 0L; private SystemHeader sysHeader = new SystemHeader(); private boolean sysHeaderSeen = false; boolean EOMflag = false; boolean parserErrorFlag = false; private boolean durationInitialized = false; private boolean sysPausedFlag = false; private boolean seekableStreamFlag = false; // can be seeked at least to the beginning private boolean randomAccessStreamFlag = true; // can be seeked to any location /* some JMFSecurity stuff */ private static JMFSecurity jmfSecurity = null; private static boolean securityPrivelege=false; private Method mSecurity[] = new Method[1]; private Class clSecurity[] = new Class[1]; private Object argsSecurity[][] = new Object[1][0]; private long startLocation = 0; static { try { jmfSecurity = JMFSecurityManager.getJMFSecurity(); securityPrivelege = true; } catch (SecurityException e) { } } /* Time is managed in few sets of variables (for different types of media), * each has it's own units: * durationNs - in nanoseconds * PTS - is 33 bits of the presentation time stamp value. * Upon the MPEG standard, it probably should be unsigned, * but since there are movies with negetive values, we will * treat the time stamp as SIGNED !! * AV...Ns - time for Audio/Video only streams, in nanoseconds * Important: every public method who return time, calculate the time * relative to the startPTS ! */ /** content duration in NS **/ private Time durationNs = Duration.DURATION_UNKNOWN; /** last seek time **/ private Time lastSetPositionTime = new Time(0L); /** first content PTS **/ private long startPTS = NO_PTS_VAL; /** last encountered content PTS **/ long currentPTS = NO_PTS_VAL; /** end of content PTS **/ long endPTS = NO_PTS_VAL; private long AVstartTimeNs = 0L; private long AVcurrentTimeNs = 0L; private long AVlastTimeNs = 0L; private long lastAudioNs = 0L; /// private long AVtotalBytesRead = 0L; /* counter, in byte units */ /* Sub-thread for filling the tracks inner buffers with data * (only for system-type bitstream) */ private MpegBufferThread mpThread = null; static int[][][] bitrates = { { // MPEG-2.5 { -1 }, // reserved { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, // Layer III { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, // Layer II { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1 } // Layer I }, { // reserved { -1 } }, { // MPEG-2 { -1 }, // reserved { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, // Layer III { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, // Layer II { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1 } // Layer I }, { // MPEG-1 { -1 }, // reserved { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1 }, // Layer III { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1 }, // Layer II { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1 } // Layer I } }; static int[][] samplerates = { { 11025, 12000, 8000, -1 }, // MPEG-2.5 { -1 }, // reserved { 22050, 24000, 16000, -1 }, // MPEG-2 { 44100, 48000, 32000, -1 } // MPEG-1 }; public void setSource(DataSource source) throws IOException, IncompatibleSourceException { super.setSource(source); stream = (PullSourceStream) streams[0]; streamContentLength = stream.getContentLength(); // can be LENGTH_UNKNOWN seekableStreamFlag = (streams[0] instanceof Seekable); if (!seekableStreamFlag) throw new IncompatibleSourceException("Mpeg Stream is not Seekable"); randomAccessStreamFlag = seekableStreamFlag && ((Seekable) streams[0]).isRandomAccess(); } public ContentDescriptor [] getSupportedInputContentDescriptors() { return supportedFormat; } public void start() throws IOException { super.start(); sysPausedFlag = false; if (mpThread != null) mpThread.start(); } public void stop() { super.stop(); sysPausedFlag = true; if (mpThread != null) mpThread.pause(); // Release any blocking readFrame. TrackList info; for (int i = 0; i < numTracks ; i++) { if (tracks[i] != null && tracks[i].isEnabled()) { info = ((MediaTrack)tracks[i]).getTrackInfo(); info.releaseReadFrame(); } } } public void close() { stop(); flushInnerBuffers(); super.close(); if (mpThread != null) mpThread.kill(); } /* * getTracks - read the MPEG system header, fill the inner buffers, * configure the track layout, activate the inner thread */ public Track[] getTracks() throws IOException, BadHeaderException { /* check if the tracks are already initialized */ if (streamType == SYS11172_TYPE) { if ((hideAudioTracks) && (videoTracks != null)) { return videoTracks; } if ((hideVideoTracks) && (audioTracks != null)) { return audioTracks; } } if (tracks != null) { return tracks; } try { initTmpBufLen = (AUDIO_TRACK_BUF_SIZE < VIDEO_TRACK_BUF_SIZE) ? AUDIO_TRACK_BUF_SIZE : VIDEO_TRACK_BUF_SIZE; initTmpStreamBuf = new byte[initTmpBufLen]; /* detect stream type: Audio only / Video only / interleaved (system) */ initTmpBufLen = detectStreamType(initTmpStreamBuf); /* extract the tracks information */ switch (streamType) { case AUDIO_TYPE : case VIDEO_TYPE : initTrackAudioVideoOnly(); break; case SYS11172_TYPE : initTrackSystemStream(); break; case UNKNOWN_TYPE : default : throw new BadHeaderException("Couldn't detect stream type"); } // System.out.println("Number of tracks: " + numTracks); initDuration(); if (saveOutputFlag) { aout = new FileOutputStream(AoutName); vout = new FileOutputStream(VoutName); } /* activate the inner thread for filling the inner buffers */ if (streamType == SYS11172_TYPE) { if ( /*securityPrivelege && */ (jmfSecurity != null) ) { String permission = null; try { if (jmfSecurity.getName().startsWith("jmf-security")) { permission = "thread"; jmfSecurity.requestPermission(mSecurity, clSecurity, argsSecurity, JMFSecurity.THREAD); mSecurity[0].invoke(clSecurity[0], argsSecurity[0]); permission = "thread group"; jmfSecurity.requestPermission(mSecurity, clSecurity, argsSecurity, JMFSecurity.THREAD_GROUP); mSecurity[0].invoke(clSecurity[0], argsSecurity[0]); } else if (jmfSecurity.getName().startsWith("internet")) { PolicyEngine.checkPermission(PermissionID.THREAD); PolicyEngine.assertPermission(PermissionID.THREAD); } } catch (Throwable e) { if (JMFSecurityManager.DEBUG) { System.err.println( "Unable to get " + permission + " privilege " + e); } securityPrivelege = false; // TODO: Do the right thing if permissions cannot be obtained. // User should be notified via an event } } if ( (jmfSecurity != null) && (jmfSecurity.getName().startsWith("jdk12"))) { try { Constructor cons = jdk12CreateThreadAction.cons; mpThread = (MpegBufferThread) jdk12.doPrivM.invoke( jdk12.ac, new Object[] { cons.newInstance( new Object[] { MpegBufferThread.class, })}); } catch (Exception e) { System.err.println("MpegParser: Caught Exception " + e); } } else { mpThread = new MpegBufferThread(); } if (mpThread != null) { mpThread.setParser(this); mpThread.start(); // I don't think you need permission for start } if (saveOutputFlag || throwOutputFlag) { try { Thread.sleep(30000); } catch (InterruptedException e) {} } } /* return the resulting tracks */ if (streamType == SYS11172_TYPE) { if (hideAudioTracks) { return videoTracks; } if (hideVideoTracks) { return audioTracks; } } return tracks; } catch (BadDataException e) { parserErrorFlag = true; throw new BadHeaderException("Bad data"); } catch (BadHeaderException e) { parserErrorFlag = true; throw e; } catch (IOException e) { updateEOMState(); EOMflag = true; throw e; } } private boolean isValidMp3Header(int code) { return (((code >>> 21) & 0x7ff) == 0x7ff && // sync ((code >>> 19) & 0x3) != 1 && // version ((code >>> 17) & 0x3) != 0 && // layer ((code >>> 12) & 0xf) != 0 && // bit rate ((code >>> 12) & 0xf) != 0xf && // bit rate ((code >>> 10) & 0x3) != 0x3 && // sample rate (code & 0x3) != 0x2); // emphasis } /* * Try to detect whether the stream is MPEG 11172-1 system bitstream, * or MPEG video only stream, or MPEg audio only stream */ private int detectStreamType(byte[] streamBuf) throws IOException { int i=0, code, videoCount=0, audioCount=0; boolean found=false; /* Copy each byte from the bitsream into a temporary buffer. * If the stream is system, continue from where we got to in the * stream and don't use the bytes in the temporary buffer. * If the stream is raw MPEG audio/video - just copy the temporary * buffer into the single track buffer. */ if (streamType != UNKNOWN_TYPE) { return 0; } try { /* try to look for generic codes */ readBytes(stream, streamBuf, 4); while ((!found) && (i < streamBuf.length-5)) { code = ((streamBuf[i] & 0xFF) << 24) | ((streamBuf[i+1] & 0xFF) << 16) | ((streamBuf[i+2] & 0xFF) << 8) | (streamBuf[i+3] & 0xFF); switch (code) { case PACK_START_CODE : /* check what happen right after the pack header end */ /* byte 0010XXX1 */ i++; readBytes(stream, streamBuf, i+3, 1); /* read the next byte */ if ((streamBuf[i+3] & (byte)0xF1) == (byte)0x21) { streamType = SYS11172_TYPE; found = true; } continue; /* not a "real" pack code - skip on reading next byte */ case VIDEO_SEQUENCE_HEADER_CODE : if (i == 0) { /* first code on the bitstream */ streamType = VIDEO_TYPE; found = true; } case VIDEO_PICTURE_START_CODE : case VIDEO_GROUP_START_CODE : videoCount++; break; default : /* check if audio frame sync word and legal layer code */ if ( ((code & 0xFFF00000) == 0xFFF00000) && ((code & 0x00060000) != 0x00000000) && isValidMp3Header(code) ) { audioCount++; // if (i == 0) { /* first code on the bitstream */ streamType = AUDIO_TYPE; found = true; // } startLocation = i; } /* otherwise, do nothing */ break; } i++; readBytes(stream, streamBuf, i+3, 1); /* read the next byte */ } } catch (IOException e) { /* apply some very very simple logic */ if (streamType == UNKNOWN_TYPE) { if (videoCount > 0) { streamType = VIDEO_TYPE; } else if (audioCount > 0) { streamType = AUDIO_TYPE; } } updateEOMState(); EOMflag = true; throw e; } /* apply some simple logic */ if (streamType == UNKNOWN_TYPE) { if (videoCount > 4) { streamType = VIDEO_TYPE; } else if (audioCount > 20) { streamType = AUDIO_TYPE; } } // I think this applies only to Audio if (seekableStreamFlag && (streamType == AUDIO_TYPE)) { int duration = -1; Seekable s = (Seekable) stream; long currentPos = s.tell(); // s.seek(0); s.seek(startLocation); int frameHeader = readInt(stream); int h_id = (frameHeader>>>19) & 0x03; // MPEG version int h_layer = (frameHeader>>>17) & 0x03; // Audio Layer int h_bitrate = (frameHeader>>>12) & 0x0f; int h_samplerate = (frameHeader>>>10) & 0x03; int h_padding = (frameHeader>>> 9) & 0x01; int h_mode = (frameHeader>>> 6) & 0x03; // Channel mode int bitrate = bitrates[h_id][h_layer][h_bitrate]; // TODO: check if streamContentLength is not unknown/unbounded // duration = (int)(streamContentLength/(bitrate * 125)); // Look for Xing VBR header int offset = (((h_id & 1) == 1) ? ((h_mode != 3) ? (32+4) : (17+4)) : ((h_mode != 3) ? (17+4) : ( 9+4))); s.seek(offset); String hdr = readString(stream); if (hdr.equals("Xing")) { int flags = readInt(stream); int frames = readInt(stream); int bytes = readInt(stream); int samplerate = samplerates[h_id][h_samplerate]; int frameSize = 144000 * bitrate / samplerate + h_padding; duration = (frameSize * frames) / (bitrate * 125); // Fixed time per frame if (duration > 0) { durationInitialized = true; durationNs = new Time((double) duration); } } s.seek(currentPos); } return (i+4); } private void initTrackAudioVideoOnly() throws IOException, BadHeaderException, BadDataException { TrackList trackInfo; int possibleLen, itmp=0; numTracks = 1; tracks = new Track[1]; trackList[0] = new TrackList(); /* fill the whole buffer with data */ possibleLen = (streamType == AUDIO_TYPE) ? AUDIO_TRACK_BUF_SIZE : VIDEO_TRACK_BUF_SIZE; if (initTmpBufLen < possibleLen) { if (possibleLen > initTmpStreamBuf.length) { /* enlarge buffer if needed */ byte[] tmpBuf2 = new byte[possibleLen]; System.arraycopy (initTmpStreamBuf, 0, tmpBuf2, 0, initTmpBufLen); initTmpStreamBuf = tmpBuf2; } try { itmp = readBytes(stream, initTmpStreamBuf, initTmpBufLen, (possibleLen - initTmpBufLen)); } catch (IOException e) { updateEOMState(); EOMflag = true; } initTmpBufLen += itmp; } trackInfo = trackList[0]; do { /* look for track's embeded info */ extractStreamInfo(initTmpStreamBuf, 0, initTmpBufLen, true); if (trackInfo.infoFlag) { break; /* the info was found */ } /* else - read more data, throw the existing data */ try { itmp = readBytes(stream, initTmpStreamBuf, possibleLen); } catch (IOException e) { updateEOMState(); EOMflag = true; break; } initTmpBufLen = itmp; } while (trackInfo.infoFlag == false) ; /* it's a real problem if we didn't detect any valid info till now */ if (trackInfo.infoFlag == false) { /* not a legal stream */ numTracks = 0; tracks = null; throw new BadHeaderException("Sorry, No tracks found"); } /* now, if seekable, move to the beginning of the file */ /// if (seekable....) { ((Seekable)stream).seek(0L); initTmpBufLen = 0; EOMflag = false; /// } else { /* cannot jump to file beginning, just 'remember' the data exist in the buffer * (maybe we lose some, if we had more than one loop) */ /// ............ /// } } private void initTrackSystemStream() throws IOException, BadHeaderException, BadDataException { int i; tracks = new Track[MAX_TRACKS_SUPPORTED]; /* temporary allocation */ for (i = 0 ; i < tracks.length ; i++) { tracks[i] = null; } for (i = 0 ; i < trackList.length ; i++) { trackList[i] = null; } /* read first chunks of data */ mpegSystemParseBitstream(false, 0L, true, NO_PTS_VAL); /* it's a real problem if we didn't detect any existing track till now */ if (numTracks == 0) { throw new BadHeaderException("Sorry, No tracks found"); } /* now create a correct length array of tracks */ { Track[] tmpTracks = new Track[numTracks]; for (i = 0 ; i < numTracks ; i++) { tmpTracks[i] = tracks[i]; /* copy pointer */ } tracks = tmpTracks; } /* reorgenize the order of the tracks in the tracks array */ if (hideAudioTracks) { TrackList trackInfo; int v; /* count video tracks first */ for (i = 0 ; i < numTracks ; i++) { if (tracks[i] != null) { trackInfo = ((MediaTrack)tracks[i]).getTrackInfo(); if (trackInfo.trackType == VIDEO_TYPE) { videoCount++; } } } if (videoCount == 0) { /* no video tracks */ throw new BadHeaderException("Sorry, No video tracks found"); } videoTracks = new Track[videoCount]; /* copy pointers to video tracks only */ for (i=0, v=0 ; i < numTracks ; i++) { if (tracks[i] != null) { trackInfo = ((MediaTrack)tracks[i]).getTrackInfo(); if (trackInfo.trackType == VIDEO_TYPE) { videoTracks[v] = tracks[i]; } } } } if (hideVideoTracks) { TrackList trackInfo; int v; /* count video tracks first */ for (i = 0 ; i < numTracks ; i++) { if (tracks[i] != null) { trackInfo = ((MediaTrack)tracks[i]).getTrackInfo(); if (trackInfo.trackType == AUDIO_TYPE) { audioCount++; } } } if (audioCount == 0) { /* no audio tracks */ throw new BadHeaderException("Sorry, No video tracks found"); } audioTracks = new Track[audioCount]; /* copy pointers to audio tracks only */ for (i=0, v=0 ; i < numTracks ; i++) { if (tracks[i] != null) { trackInfo = ((MediaTrack)tracks[i]).getTrackInfo(); if (trackInfo.trackType == AUDIO_TYPE) { audioTracks[v] = tracks[i]; } } } } } /** * Returns a descriptive name for the plug-in. * This is a user readable string. */ public String getName() { return "Parser for MPEG-1 file format"; } /* * some time 'macros' */ private long convPTStoNanoseconds (long val) { return (val * 100000 / 9L); } private long convNanosecondsToPTS (long val) { return (val * 9 / 100000L); } private long convBytesToTimeAV(long bytes) { long time; if (trackList[0] == null) return 0; if (streamType == AUDIO_TYPE) { if (((Audio)(trackList[0].media)).bitRate == 0) { time = 0L; } else { time = (bytes << 3) / ((Audio)(trackList[0].media)).bitRate; time *= 1000000L; // for nanoseconds } } else { /* VIDEO_TYPE */ if (((Video)(trackList[0].media)).bitRate == 0) { time = 0L; } else { time = (bytes << 3) / ((Video)(trackList[0].media)).bitRate; time *= 1000000000L; // for nanoseconds } } return time; } private long convTimeToBytesAV(long time) { long bytes; if (streamType == AUDIO_TYPE) { bytes = (time >> 3) * ((Audio)(trackList[0].media)).bitRate; bytes /= 1000000L; // because of nanoseconds } else { /* VIDEO_TYPE */ bytes = (time >> 3) * ((Video)(trackList[0].media)).bitRate; bytes /= 1000000000L; // because of nanoseconds } return bytes; } /* * Get the duration of the stream. * If the stream length is unknown - need to update this "on the fly" * (during the first run only) */ public Time getDuration() { if (durationInitialized) { return durationNs; } else { // try to update the duration if (EOMflag) { // updateEOMState() is already called when EOM was detected durationInitialized = true ; } return durationNs; } } private void initDuration() { if (streamContentLength != SourceStream.LENGTH_UNKNOWN) { if (streamType == SYS11172_TYPE) { if (randomAccessStreamFlag) { initDurationSystemSeekableRA(); } } else { updateDurationAudioVideoOnly(); } } } /* track information was extracted for sure before calling to this method */ private void updateDurationAudioVideoOnly() { if (durationInitialized) // NEW return; AVstartTimeNs = 0L; AVcurrentTimeNs = 0L; AVlastTimeNs = convBytesToTimeAV(streamContentLength); durationNs = new Time(AVlastTimeNs - AVstartTimeNs); durationInitialized = true ; } private void initDurationSystemSeekableRA() { long baseLocation=0L, ltmp; int saveNumPackets = numPackets; boolean saveEOMflag = EOMflag; baseLocation = ((Seekable)stream).tell(); /* look for the base time */ if (startPTS == NO_PTS_VAL) { EOMflag = false; ((Seekable)stream).seek(0L); try { mpegSystemParseBitstream(true, 64*1024L, false, NO_PTS_VAL); } catch (Exception e) { } } if (startPTS == NO_PTS_VAL) { startPTS = 0L; } /* look for the EOM time */ if (endPTS == NO_PTS_VAL) { EOMflag = false; currentPTS = NO_PTS_VAL; ltmp = streamContentLength - 128*1024; if (ltmp < 0) { ltmp = 0; } ((Seekable)stream).seek(ltmp); try { mpegSystemParseBitstream(true, 128*1024L, false, NO_PTS_VAL); } catch (Exception e) { } endPTS = currentPTS; } if (endPTS == NO_PTS_VAL) { endPTS = startPTS; } /* calc the duration */ ltmp = endPTS - startPTS; if (ltmp < 0) { /* wrong values */ ltmp = 0; parserErrorFlag = true; } durationNs = new Time(convPTStoNanoseconds(ltmp)); lastSetPositionTime = new Time(convPTStoNanoseconds(startPTS)); ((Seekable)stream).seek(baseLocation); EOMflag = saveEOMflag; numPackets = saveNumPackets; durationInitialized = true ; } /** * Generate the EOM buffer and add to the buffer Q. */ void updateTrackEOM () { for (int i = 0 ; i < trackList.length ; i++) { if (trackList[i] != null) trackList[i].generateEOM(); } } /* * updateEOMState - update some veriables and flags in case of EOM detected */ void updateEOMState () { /* there is a problem with using the getLocation(stream) method: * in case of non-Seekable stream, the BasicPullParser just * count the number of bytes, without considering skipping * (treatment there is mistaken!!), no reseting at EOM, etc... * That's why the following calculation will work properly for * non-Seekable streams IF AND ONLY IF there was no scroll-movement * on the first pass till the first EOM!! * For Seekable streams there is no problem. */ if (! durationInitialized) { if (streamContentLength == SourceStream.LENGTH_UNKNOWN) { streamContentLength = getLocation(stream); } /* for System - both cases of not random-accessible or stream length unknown */ if (streamType == SYS11172_TYPE) { if (startPTS == NO_PTS_VAL) { startPTS = 0L; } if (endPTS == NO_PTS_VAL) { endPTS = currentPTS; } if (endPTS == NO_PTS_VAL) { endPTS = startPTS; } long ltmp = endPTS - startPTS; if (ltmp < 0) { /* wrong values */ ltmp = 0; parserErrorFlag = true; } durationNs = new Time(convPTStoNanoseconds(ltmp)); durationInitialized = true ; } else { /* for Audio/Video only */ updateDurationAudioVideoOnly(); } /* update the "global" duration */ ////???? sendEvent (new DurationUpdateEvent(this, durationNs)); } } /* * Get the current processed media time * * For System stream, it is NOT so simple: this is the time of the data * processed by the inner thread, but probably NOT the time of the data * inputed to the decoders by readFrame() !!! * maybe we will try to calculate what's the situation in the inner buffers... * For Audio/Video only, it is the time of the last data that was read * in readFrame() * For both, it is not known what is the situation in the codec's-plugin buffers... */ public Time getMediaTime() { Time mtime; if (streamType == SYS11172_TYPE) { if (currentPTS == NO_PTS_VAL) { mtime = new Time(0L); } else { mtime = new Time(convPTStoNanoseconds(currentPTS - startPTS)); } } else { /* Audio / Viseo only */ /// AVcurrentTimeNs = convBytesToTimeAV(AVtotalBytesRead); AVcurrentTimeNs = convBytesToTimeAV(getLocation(stream)); mtime = new Time(AVcurrentTimeNs); } return mtime; } /* * Seek for a specific time (or nearest) in the stream, and reset * the parser's buffers */ public Time setPosition(Time where, int rounding) { Time newTime = null, preWhere; if ((! durationInitialized) || (durationNs == Duration.DURATION_UNKNOWN)) { return new Time(0L); } preWhere = new Time(where.getNanoseconds() - PRE_ROLLING_DELTA_NS); long newTimeNs; if (streamType == SYS11172_TYPE) { flushInnerBuffers(); long preWherePTS, wherePTS, newPTS; preWherePTS = convNanosecondsToPTS(preWhere.getNanoseconds()); preWherePTS += startPTS; /* 'convert' to our PTS values */ wherePTS = convNanosecondsToPTS(where.getNanoseconds()); wherePTS += startPTS; /* 'convert' to our PTS values */ newPTS = setPositionSystemSeekableRA(preWherePTS, wherePTS); /* newPTS is already in the 'outside world' values */ newTimeNs = convPTStoNanoseconds(newPTS); lastAudioNs = newTimeNs; } else { /* Audio/Video only */ newTimeNs = setPositionAudioVideoOnly(preWhere.getNanoseconds(), where.getNanoseconds()); lastAudioNs = newTimeNs; } newTime = new Time(newTimeNs); // To guarantee that the position time is never the same. if (lastSetPositionTime.getNanoseconds() == newTimeNs) newTimeNs++; lastSetPositionTime = new Time(newTimeNs); EOMflag = false; parserErrorFlag = false; // System.out.println("Set position to: "+(float)where.getSeconds()+" --> "+(float)preWhere.getSeconds()+" --> "+(float)newTime.getSeconds()); // System.out.flush(); return newTime; } private long setPositionAudioVideoOnly(long where, long origWhere) { long newTime, pos; if (origWhere <= AVstartTimeNs + EPSILON_NS) { newTime = AVstartTimeNs; ((Seekable)stream).seek(0L); /// AVtotalBytesRead = 0L; } else if (origWhere >= AVlastTimeNs - EPSILON_NS) { newTime = AVlastTimeNs - AVstartTimeNs; ((Seekable)stream).seek(streamContentLength); /// AVtotalBytesRead = streamContentLength; } else { newTime = where; pos = convTimeToBytesAV(where); ((Seekable)stream).seek(pos); /// AVtotalBytesRead = pos; } return newTime; } private long setPositionSystemSeekableRA(long wherePTS, long origWherePTS) { long newTime = NO_PTS_VAL; long lres = -1; long range, step, pos; long saveStartPTS = startPTS; boolean saveEOMflag = EOMflag; boolean zeroPosFlag = false; if ((endPTS == NO_PTS_VAL) || (startPTS == NO_PTS_VAL)) { newTime = 0L; ((Seekable)stream).seek(0L); } else if (origWherePTS <= startPTS + EPSILON_PTS) { newTime = 0L; ((Seekable)stream).seek(0L); } else if (origWherePTS >= endPTS - EPSILON_PTS) { newTime = endPTS - startPTS; ((Seekable)stream).seek(streamContentLength); } else if (endPTS - startPTS < EPSILON_PTS) { newTime = 0L; ((Seekable)stream).seek(0L); } else { /* try to guess the location */ pos = (long)(streamContentLength * ((wherePTS - startPTS) / ((float)(endPTS - startPTS)))); step = 20 * 1024L; /* arbitrary */ pos -= step; if (pos < 0) { pos = 0; } range = streamContentLength - pos; // first range is till the end of media while (true) { ((Seekable)stream).seek(pos); currentPTS = NO_PTS_VAL; startPTS = NO_PTS_VAL; EOMflag = false; try { lres = mpegSystemParseBitstream(true, range, false, wherePTS); } catch (IOException e) { lres = -2; saveEOMflag = true; } catch (Exception e) { lres = -1; } if (lres >= 0) { /* PTS found */ newTime = currentPTS - saveStartPTS; ((Seekable)stream).seek(lres); break; } else if (lres == -2) { newTime = endPTS - saveStartPTS; ((Seekable)stream).seek(streamContentLength); break; } else { /* lres == -1 */ pos -= step; if (pos <= 0) { if (zeroPosFlag) { /* couldn't find any. decide on 0L */ newTime = 0L; ((Seekable)stream).seek(0L); break; } pos = 0; zeroPosFlag = true; /* a flag to prevent loop forever */ } range = 3 * step; } } /* end of while() */ startPTS = saveStartPTS; EOMflag = saveEOMflag; // redandant, actually } return newTime; } /* * parse the bitstream into the tracks inner buffers * justLooking == false --> regular parsing into the tracks buffers * justLooking == true --> only parse and update PTS params * (don't save in the inner buffers!) * newPTS == NO_PTS_VAL --> do not look for specific time stamp * newPTS == xxx --> look for the time stamp which is close to xxx * * return value: meaningful only if (newPTS != NO_PTS_VAL). * return the start location of the current pack in the stream * or -1 if should look before. (-2: problem or eom) * new fix: return the PTS prior to the newPTS, and not the first one after it. */ long mpegSystemParseBitstream (boolean justLooking, long range, boolean justEnough, long newPTS) throws IOException, BadHeaderException, BadDataException { byte bval; byte[] buf1 = new byte[1]; int code = 0; boolean read4 = true, packFound = false; long baseLocation = getLocation(stream); long lastPacketLocation = baseLocation; long lastLastPacketLocation = baseLocation; long loc = baseLocation+4; long lastCurrentPTS = NO_PTS_VAL; long savePTS = NO_PTS_VAL; while ((!sysPausedFlag && !EOMflag) || justLooking || justEnough) { if (justEnough && !needingMore()) { break; /* stop if we've gotten enough data */ } if (justLooking) { if (getLocation(stream) - baseLocation > range) { break; /* stop if parsed more than range bytes */ } if (newPTS != NO_PTS_VAL) { /* check if PTS was found */ if (newPTS < startPTS) { return (-1L); /* should seek before this point */ } if (newPTS <= currentPTS) { if (newPTS == currentPTS) { return lastPacketLocation; } else { /* < */ currentPTS = lastCurrentPTS; return lastLastPacketLocation; } } } } if (read4) { /* read 4 bytes of code */ code = readInt(stream, true); } else { /* read only the next byte */ readBytes(stream, buf1, 1); code = ((code << 8) & 0xFFFFFF00) | (buf1[0] & 0x00FF); } switch (code) { case PACK_START_CODE : parsePackHeader(); read4 = true; packFound = true; break; case SYSTEM_HEADER_START_CODE : parseSystemHeader(); read4 = true; break; case END_CODE : EOMflag = true; /// ???? if ((lastPTS == NO_PTS_VAL) && (newPTS == NO_PTS_VAL)) { if (endPTS == NO_PTS_VAL) { /// maybe update always if lastPTS wasn't accurate enough.... endPTS = currentPTS; } if ((!justLooking) || (newPTS != NO_PTS_VAL)) { updateEOMState(); } break; default : /* packet start code (it's only 24 bits) or default (error) */ if ( ((code >> 8) == PACKET_START_CODE_24) && ((!justLooking) || (packFound & justLooking)) ) { if (justLooking && (newPTS != NO_PTS_VAL)) { loc = getLocation(stream); savePTS = currentPTS; } bval = (byte)(code & 0x000000FF); parsePacket(bval, justLooking); read4 = true; /* update for setPosition call */ if (justLooking && (newPTS != NO_PTS_VAL)) { /* it seems there is no special need to initialize * the 'lastCurrentPTS' and the 'lastLastPacketLocation' */ if (savePTS != currentPTS) { /* new PTS here */ lastCurrentPTS = savePTS; lastLastPacketLocation = lastPacketLocation; lastPacketLocation = loc - 4; } } break; } else { /* another code - shouldn't be */ read4 = false; break; } } } /* in general, can catch here BadDataException & BadHeaderException, * that may thrown because wrong start code (if read4 was false....) */ return ((EOMflag) ? (-2L) : (-1L)); } /* * parse the pack header */ private void parsePackHeader () throws IOException, BadDataException { byte[] buf1 = new byte[1]; readBytes(stream, buf1, 1); if ((buf1[0] & (byte)0xF0) != (byte)0x20) { /* check 0010xxxx */ throw new BadDataException("invalid pack header"); } if ((buf1[0] & (byte)0x01) != (byte)0x01) { /* check marker bit #0 */ throw new BadDataException("illegal marker bit"); } /* skip mux_rate */ skip(stream, 7); /* we decide that there is no point to extract the value of the SCR * (Systen Clock Reference) here, because there are movies in which * there isn't any match between the SCR time and the PTS time. * If we would extract it here, the code would be: * long scr = ((long)(buf1[0] & 0x000E)) << 29; * scr = ((scr << 31) >> 31); << make it signed num !? >> * int itmp = readInt(stream, true); * if ((itmp & 0x00010001) != 0x00010001) { << check 2 marker bits on #16 and #0 >> * throw new BadDataException("illegal marker bit"); * } * int itmp2 = (itmp & 0xFFFE0000) >> 2; << bits 29..15 >> * scr |= ((long) (itmp2 & 0x3fffffff)); * scr |= (long)((itmp & 0x0000FFFE) >> 1); << bits 14..0 >> * if (startSCR == NO_PTS_VAL) { * startSCR = scr; * } * currentSCR = scr; * skip(stream, 3); */ } /* * parse the MPEG system header * * According to the 11172-1 standard, all system headers in a specific * bitstream, should be identical. The reality is different. That's why * there is no point of really parsing the next system headers and comparing * it (by equal()) to the first one... */ private void parseSystemHeader () throws IOException, BadHeaderException { byte bval; byte[] buf1 = new byte[1]; int itmp, size, scale, streamID, i, len; short stmp; /* read header length */ len = readShort(stream, true); if (sysHeaderSeen) { /* not the first system header */ skip(stream, len); } else { /* first one - parse it */ sysHeader.resetSystemHeader(); sysHeader.headerLen = len; /* ...could check if there are enough bytes in the input... */ itmp = readInt(stream, true); len -= 4; if ((itmp & 0x80000100) != 0x80000100) { // marker bits on #31 and #8 throw new BadHeaderException("illegal marker bits in system header"); } sysHeader.rateBound = (itmp & 0x7FFFFE00) >> 9; sysHeader.audioBound = (itmp & 0x000000FC) >> 2; sysHeader.fixedFlag = (itmp & 0x00000002) >> 1; sysHeader.CSPSFlag = itmp & 0x00000001; readBytes(stream, buf1, 1); bval = buf1[0]; len -= 1; if ((bval & (byte)0x20) != (byte)0x20) { // check marker bits #5 throw new BadHeaderException("illegal marker bits in system header"); } sysHeader.audioLockFlag = (bval & 0x0080) >> 7; sysHeader.videoLockFlag = (bval & 0x0040) >> 6; sysHeader.videoBound = bval & 0x001F; readBytes(stream, buf1, 1); len -= 1; sysHeader.reserved = buf1[0]; /* read streams STD info */ while (len > 1) { readBytes(stream, buf1, 1); bval = buf1[0]; len -= 1; if ((bval & (byte)0x80) != (byte)0x80) // end of STD info break; /* check if STD refers to all audio streams */ if (bval == (byte)0xb8) { stmp = readShort(stream, true); len -= 2; if ((stmp & 0x0000C000) != 0x0000C000) { throw new BadHeaderException("illegal marker bits in system header"); } size = stmp & 0x00001FFF; /* in 128 byte units */ sysHeader.allAudioSTDFlag = true; for (i = MIN_AUDIO_ID ; i <= MAX_AUDIO_ID ; i++) { /* do not set the stream_flags[i] field, because * info isn't track specific */ sysHeader.STDBufBoundScale[i] = 0; sysHeader.STDBufSizeBound[i] = size; } } /* check if STD refers to all video streams */ else if (bval == (byte)0xb9) { stmp = readShort(stream, true); len -= 2; if ((stmp & 0x0000C000) != 0x0000C000) { throw new BadHeaderException("illegal marker bits in system header"); } size = stmp & 0x00001FFF; /* in 1024 byte units */ sysHeader.allVideoSTDFlag = true; for (i = MIN_VIDEO_ID ; i <= MAX_VIDEO_ID ; i++) { /* do not set the stream_flags[i] field, because * info isn't track specific */ sysHeader.STDBufBoundScale[i] = 1; sysHeader.STDBufSizeBound[i] = size; } } /* STD information of specific stream/track */ else { if (((bval & 0x00FF) < MIN_STREAM_CODE) || ((bval & 0x00FF) > MAX_STREAM_CODE)) { throw new BadHeaderException("illegal track number in system header"); } streamID = getStreamID(bval); if ((streamID >= 0) && (streamID < MAX_NUM_STREAMS)) { stmp = readShort(stream, true); len -= 2; if ((stmp & 0x0000C000) != 0x0000C000) { throw new BadHeaderException("illegal marker bits in system header"); } scale = (stmp & 0x00002000) >> 13; size = stmp & 0x00001FFF; /* in 1024 byte units */ sysHeader.streamFlags[streamID] = true; sysHeader.STDBufBoundScale[streamID] = scale; sysHeader.STDBufSizeBound[streamID] = size; } } } if (len < 0) { throw new BadHeaderException("illegal system header"); } if (len > 0) { skip(stream, len); } sysHeaderSeen = true; ///// } } /* * parse a packet */ private void parsePacket (byte bval, boolean justLooking) throws IOException, BadDataException { int streamID, itmp, itmp2; int packetLen, count=0, dataSize; int STDBufSize=0; int STDBufScale=0; int numWrittenToTmpBuf = 0; byte[] tmpBuf = null; byte[] buf1 = new byte[1]; long pts; TrackList trackInfo; /* identify the stream ID */ if (((bval & 0x00FF) < MIN_STREAM_CODE) || ((bval & 0x00FF) > MAX_STREAM_CODE)) { throw new BadDataException("invalid stream(track) number"); } streamID = getStreamID(bval); /* read packet length */ packetLen = readShort(stream, true); buf1[0] = bval; /* could check here if there are are enough bytes in the input */ if ((buf1[0] & 0x00FF) != PRIVATE_STREAM2_CODE) { /* skip stuffing bytes */ do { readBytes(stream, buf1, 1); count++; } while (buf1[0] == (byte)0xFF); /* STD buf details (meanwhile do nothing with this info) */ if ((buf1[0] & (byte)0xC0) == (byte)0x40) { STDBufScale = ((buf1[0] & 0x0020) >> 5); STDBufSize = (((int)buf1[0]) & 0x001F) << 8; readBytes(stream, buf1, 1); STDBufSize |= (int)buf1[0]; readBytes(stream, buf1, 1); count += 2; } /* PTS - presentation time stamp (for now, do not try to attach to a spesific frame header) */ if ((buf1[0] & (byte)0xE0) == (byte)0x20) { pts = ((long)(buf1[0] & 0x000E)) << 29; pts = ((pts << 31) >> 31); /* make it signed num! (and lose the 33 bit) */ if ((buf1[0] & (byte)0x01) != (byte)0x01) { /* check marker bit #0 */ throw new BadDataException("illegal marker bit"); } itmp = readInt(stream, true); count += 4; if ((itmp & 0x00010001) != 0x00010001) { /* check 2 marker bits on #16 and #0 */ throw new BadDataException("illegal marker bit"); } // for bits 29..15 - problem if msb==1: pts |= (long)((itmp & 0xFFFE0000) >> 2); itmp2 = (itmp & 0xFFFE0000) >> 2; /* bits 29..15 */ pts |= ((long) (itmp2 & 0x3fffffff)); /* bits 29..15 */ pts |= (long)((itmp & 0x0000FFFE) >> 1); /* bits 14..0 */ currentPTS = pts; if (startPTS == NO_PTS_VAL) { startPTS = currentPTS; if ((startPTS > 0) && (startPTS <= EPSILON_PTS)) { /* actually zero time */ startPTS = 0L; } } /* if there is a DTS - decoding time stamp - skip it */ if ((buf1[0] & (byte)0xF0) == (byte)0x30) { skip(stream, 5); count += 5; } } else if (buf1[0] != (byte)0x0F) { /* else - just validate the 8 bit code */ throw new BadDataException("invalid packet"); } } /* end of not PRIVATE_STREAM2_CODE */ /* handle the packet data */ dataSize = packetLen - count; if (justLooking) { skip(stream, dataSize); return; } /* check if it is reserved or private stream */ if ((streamID < 0) || (streamID >= MAX_NUM_STREAMS)) { skip(stream, dataSize); } else { /* (regular) audio/video stream */ if (trackList[streamID] == null) { /* in the first packet of the track */ trackList[streamID] = new TrackList(); } trackInfo = trackList[streamID]; /* if stream not initialized yet, and/or couldn't extract stream info yet */ if (trackInfo.infoFlag == false) { tmpBuf = new byte[dataSize]; numWrittenToTmpBuf = extractStreamInfo(tmpBuf, streamID, dataSize, false); } if (trackInfo.infoFlag == false) { /* no header found in this packet */ trackList[streamID] = null; if (numWrittenToTmpBuf < dataSize) { skip(stream, (dataSize-numWrittenToTmpBuf)); /* skip the rest of the packet */ } } else { /* update PTS if needed */ if (startPTS == NO_PTS_VAL) { trackInfo.startPTS = currentPTS; } /* now copy data */ trackInfo.copyStreamDataToInnerBuffer(tmpBuf, numWrittenToTmpBuf, dataSize - numWrittenToTmpBuf, currentPTS); trackInfo.numPackets++; if (dataSize > trackInfo.maxPacketSize) { trackInfo.maxPacketSize = dataSize; } } } numPackets++; } /* * parse the packet and extract specific information */ private int extractStreamInfo (byte[] tmpBuf, int streamID, int dataLen, boolean AVOnlyState) throws IOException, BadDataException { byte stype = UNKNOWN_TYPE; TrackList trackInfo = trackList[streamID]; int numBytes; /* check if need to initialize the buffer and the stream structure */ if (trackInfo.trackType == UNKNOWN_TYPE) { /* update fields */ stype = (AVOnlyState) ? streamType : ((streamID < MIN_VIDEO_ID) ? AUDIO_TYPE : VIDEO_TYPE); trackInfo.init(stype); sysHeader.streamFlags[streamID] = true; trackInfo.startPTS = currentPTS; } /* extract specific audio / video info */ if (stype == AUDIO_TYPE) { numBytes = extractAudioInfo(tmpBuf, trackInfo, dataLen, AVOnlyState); } else { /* VIDEO_TYPE */ numBytes = extractVideoInfo(tmpBuf, trackInfo, dataLen, AVOnlyState); } if (trackInfo.infoFlag == true) { if (AVOnlyState) { tracks[0] = new MediaTrack(trackInfo); } else { tracks[numTracks] = new MediaTrack(trackInfo); numTracks++; } } return numBytes; } /* * parse the audio packet and extract specific audio information * * Remark: audio.ID bit is set to '1' for standard MPEG-1 audio * (ISO/IEC 11172-3). If this bit is set to '0', MPEG-2 * backwards compatible audio (ISO/IEC 13818-3) extension for * lower sampling frequencies is used. */ private int extractAudioInfo (byte[] tmpBuf, TrackList trackInfo, int dataLen, boolean AVOnlyState) throws IOException, BadDataException { Audio audio = new Audio(); int br, sr, numBytes; /* tables for standard MPEG-1 audio */ int samplingFrequencyTable[] = {44100, 48000, 32000}; final short bitrateIndexTableL2[] = {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384}; /* bitrate table for layer 3: * {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320}; */ /* tables for MPEG-2 audio extension */ /* int samplingFrequencyTable[] = {22050, 24000, 16000}; */ final short bitrateIndexTableL23Ext[] = {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}; /* bitrate table for layer 1 extension: {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}; */ numBytes = (AVOnlyState) ? dataLen : (readBytes(stream, tmpBuf, dataLen)); for (int i = (int) startLocation ; i < numBytes-3 ; i++) { // doesn't handle header start code which is splitted on end-of-packet if ( (tmpBuf[i] == (byte)0xFF) && ( (tmpBuf[i+1] &(byte)0xF0) == (byte)0xF0) ) { audio.ID = (tmpBuf[i+1] & 0x0008) >> 3; audio.layer = 4 - ((tmpBuf[i+1] & 0x0006) >> 1); audio.protection = (tmpBuf[i+1] & 0x0001); br = (tmpBuf[i+2] & 0x00F0) >> 4; sr = (tmpBuf[i+2] & 0x000C) >> 2; { if ( (sr < 0) || (sr >= samplingFrequencyTable.length) ) { throw new BadDataException("Non Standard sample rates not supported"); } } audio.mode = (tmpBuf[i+3] & 0x00C0) >> 6; audio.modeExt = (tmpBuf[i+3] & 0x0030) >> 4; audio.channels = (audio.mode == 3) ? 1 : 2; audio.copyright = (tmpBuf[i+3] & 0x0008) >> 3; audio.original = (tmpBuf[i+3] & 0x0004)>> 2; audio.emphasis = (tmpBuf[i+3] & 0x0003); audio.valid = (br != 0x000F); /* calculate sampling frequency and bitrate values */ if (audio.ID == 1) { /* standard MPEG-1 */ audio.sampleRate = samplingFrequencyTable[sr]; if (audio.layer == 3) { if (br < 2) { audio.bitRate = bitrateIndexTableL2[br]; } else if (br == 2) { audio.bitRate = 40; } else { audio.bitRate = bitrateIndexTableL2[br-1]; } } else if (audio.layer == 2) { audio.bitRate = bitrateIndexTableL2[br]; } else { /* layer 1 */ audio.bitRate = br << 5; } } else { /* extension MPEG-2 */ audio.sampleRate = samplingFrequencyTable[sr]>>1; if ((audio.layer == 3) || (audio.layer == 2)) { audio.bitRate = bitrateIndexTableL23Ext[br]; } else { /* layer 1 */ if (br < 9) { audio.bitRate = bitrateIndexTableL2[br]; } else if (br == 9) { audio.bitRate = 144; } else if (br == 10) { audio.bitRate = bitrateIndexTableL2[br-1]; } else if (br == 11) { audio.bitRate = 176; } else { audio.bitRate = bitrateIndexTableL2[br-2]; } } } /* this is a calculation for one decoded frame length, in bytes */ // if (audio.bitRate < 30) { // decodeFrameLen = 10 * 1024; // } else { // br = (audio.layer == 1) ? 48 : 144; // decodeFrameLen = (int)(8 * (float)(audio.bitRate * 1000 * br) / audio.sampleRate); // } /* num of bytes for a duration of 1 second */ trackInfo.readFrameSize = (audio.bitRate * 1000) >> 3; trackInfo.infoFlag = true; trackInfo.media = audio; break; } } //trackInfo.media.toString(); return numBytes; } /* * parse the video packet and extract specific video information */ private int extractVideoInfo (byte[] tmpBuf, TrackList trackInfo, int dataLen, boolean AVOnlyState) throws IOException, BadDataException { Video video = new Video(); int i, code, numBytes, pr; float aspectRatioTable[] = {0.0f, 1.0f, 0.6735f, 0.7031f, 0.7615f, 0.8055f, 0.8437f, 0.8935f, 0.9375f, 0.9815f, 1.0255f, 1.0695f, 1.1250f, 1.1575f, 1.2015f, 1.0f}; float pictureRateTable[] = {0.0f, 23.976f, 24.0f, 25.0f, 29.97f, 30.0f, 50.0f, 59.94f, 60.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f}; numBytes = (AVOnlyState) ? dataLen : (readBytes(stream, tmpBuf, dataLen)); for (i = 0 ; i < numBytes-10 ; i++) { // doesn't handle header start code which is splitted on end-of-packet // not working in Win, because of padding with the sign bit: // code = data[i] << 24 | data[i+1] << 16 | data[i+2] << 8 | data [i+3]; code = ((tmpBuf[i] << 24) & 0xFF000000) | ((tmpBuf[i+1] << 16) & 0x00FF0000) | ((tmpBuf[i+2] << 8) & 0x0000FF00) | (tmpBuf[i+3] & 0x000000FF); if (code == VIDEO_SEQUENCE_HEADER_CODE) { video.width = (tmpBuf[i+4+0] & 0x00FF) << 4; video.width |= (((int)tmpBuf[i+4+1]) >> 4) & 0x000F; video.height = (tmpBuf[i+4+1] & 0x000F) << 8; video.height |= (tmpBuf[i+4+2] & 0x00FF); pr = (tmpBuf[i+4+3] & 0x00F0) >> 4; video.pelAspectRatio = aspectRatioTable[pr]; pr = tmpBuf[i+4+3] & 0x000F; video.pictureRate = pictureRateTable[pr]; pr = ( (tmpBuf[i+4+4] & 0x00FF) << 10 ) | ( (tmpBuf[i+4+5] & 0x00FF) << 2 ) | ( (tmpBuf[i+4+6] & 0x00C0) >> 6); video.bitRate = pr * 400; // bitrate in units of 400 bps if ( (video.pelAspectRatio == 0.0) || (video.pictureRate == 0.0) ) { throw new BadDataException("video header corrupted"); } if (video.pictureRate < 23.0) { trackInfo.readFrameSize = 64 * 1024; } else { /* readFrameSize should be 1 second, but limited to not more than half the video track buf size (arbitrary) */ trackInfo.readFrameSize = (int)(video.bitRate >> 3); if (trackInfo.readFrameSize > (VIDEO_TRACK_BUF_SIZE>>1)) { trackInfo.readFrameSize = VIDEO_TRACK_BUF_SIZE>>1 ; } } trackInfo.infoFlag = true; trackInfo.media = video; //trackInfo.media.toString(); break; } } return numBytes; } /* * getStreamID - match value into an index */ private int getStreamID (byte bval) { return ((bval & 0xFF) - 0xC0); } /* * The inner class can use this method as they cannot use the * getLocation(stream) method. */ private long getLocation() { return getLocation(stream); } /* * check if all the inner buffers have 'enough' space for another packet * (in the future, may check it only for the next relevant track....) */ boolean needingMore() { TrackList trackInfo; for (int i = 0; i < numTracks ; i++) { if (tracks[i] != null) { trackInfo = ((MediaTrack)tracks[i]).getTrackInfo(); if (trackInfo.bufQ.canRead()) { return false; } } } return true; } void flushInnerBuffers() { TrackList trackInfo; for (int i = 0; i < numTracks ; i++) { if (tracks[i] != null) { trackInfo = ((MediaTrack)tracks[i]).getTrackInfo(); // Release the wait in copyStreamDataToInnerBuffer. synchronized (trackInfo.bufQ) { trackInfo.flushFlag = true; trackInfo.bufQ.notifyAll(); } trackInfo.flushBuffer(); } } } /// a temporary utility for debugging ! void saveInnerBuffersToFiles() { TrackList trackInfo; for (int i = 0; i < numTracks ; i++) { if (tracks[i] != null) { trackInfo = ((MediaTrack)tracks[i]).getTrackInfo(); trackInfo.saveBufToFile(); } } } void throwInnerBuffersContents() { TrackList trackInfo; for (int i = 0; i < numTracks ; i++) { if (tracks[i] != null) { trackInfo = ((MediaTrack)tracks[i]).getTrackInfo(); trackInfo.flushBuffer(); } } } /// temporary !! /* ===================================================================== */ /* * Media class */ private abstract class Media { abstract Format createFormat(); } /* ===================================================================== */ /* * Audio specific information class */ private class Audio extends Media { boolean valid = false; int ID = 0; int layer = 0; int protection = 0; int bitRate = 0; /* in Kbits/sec == bits/msec */ int sampleRate = 0; int mode = 0; int modeExt = 0; int copyright= 0; int original = 0; int emphasis= 0; int channels=0; AudioFormat format = null; Format createFormat() { if (format != null) return format; String encodingString; if (layer == 3) encodingString = AudioFormat.MPEGLAYER3; else encodingString = AudioFormat.MPEG; //System.out.println("Audio layer="+layer+" encodingString="+encodingString); int bitsPerSample = 16; int frameSizeInBits = ((layer == 1) ? 352 : 1024) * channels * bitsPerSample; // format = new AudioFormat(encodingString, // (double) sampleRate, // bitsPerSample, // channels, // AudioFormat.LITTLE_ENDIAN, // AudioFormat.SIGNED, // frameSizeInBits, // Format.NOT_SPECIFIED, // No FRAME_RATE specified // byte[].class); int bytesPerSecond = (bitRate * 1000) >> 3; format = new WavAudioFormat(encodingString, (double) sampleRate, bitsPerSample, channels, frameSizeInBits, bytesPerSecond, AudioFormat.LITTLE_ENDIAN, AudioFormat.SIGNED, Format.NOT_SPECIFIED, // No FRAME_RATE specified byte[].class, null // Codec Specific Header ); return format; } public String toString() { System.out.println("Audio Media: " + format); System.out.println("Number of channels " + channels); System.out.println("valid " + valid); System.out.println("ID " + ID); System.out.println("layer " + layer); System.out.println("protection " + protection); System.out.println("bitrate " + bitRate); System.out.println("sample rate " + sampleRate); System.out.println("Mode " + mode + " ext "+ modeExt); System.out.println("copyright " + copyright); System.out.println("original " + original); System.out.println("emphasis " + emphasis); System.out.println("channels " + channels); return super.toString(); } } /* ===================================================================== */ /* * Video specific information class */ private class Video extends Media { int width=0; int height=0; float pelAspectRatio = 0; float pictureRate = 0; int bitRate = 0; VideoFormat format = null; Format createFormat() { int size = (int)(width * height * 1.5); //// *3 ?? *4 ?? if (format != null) return format; format = new VideoFormat(VideoFormat.MPEG, new java.awt.Dimension(width, height), size, byte[].class, (float)pictureRate); return format; } public String toString() { System.out.println("Video Media: " + format); System.out.println("width " + width); System.out.println("height " + height); System.out.println("pixel aspect ratio " + pelAspectRatio); System.out.println("picture rate " + pictureRate); System.out.println("bitrate " + bitRate); return super.toString(); } } /* ===================================================================== */ /* * Track information class */ private class TrackList { byte trackType = UNKNOWN_TYPE; /* Chunk identifier: Unknown/Audio/Video */ Time duration = Duration.DURATION_UNKNOWN; // TODO: NEED THIS? long startPTS = NO_PTS_VAL; // the stream reset time ("zero") boolean infoFlag = false; // indicates wether audio/video info was extracted int numPackets = 0; int maxPacketSize = 0; int readFrameSize = 0; // size of 'chunk' in readFrame (~1 frame for video, ~8 frames for audio) Media media; // Info specific to each track type boolean supported = false; // Is this track type supported boolean flushFlag = false; CircularBuffer bufQ = null; Buffer current = null; MpegParser parser = MpegParser.this; void init (byte stype) { supported = true; trackType = stype; // We buffer up to 15 frames of compressed MPEG video and // 5 secs worth of compressed audio. if (trackType == VIDEO_TYPE) bufQ = new CircularBuffer(15); else bufQ = new CircularBuffer(10); } /* * return the number of data bytes in the inner buffer */ int readyDataBytes () { return 1; } /* * fill the inner buffer from the input stream */ void copyStreamDataToInnerBuffer (byte in[], int inSize, int size, long pts) throws IOException { int total = size; int len = 0; if (inSize > 0) total += inSize; else inSize = 0; synchronized (bufQ) { if (current != null) { len = current.getLength(); if (len != 0 && len + total >= readFrameSize) { // We are done reading one frame. bufQ.writeReport(); bufQ.notify(); current = null; } } byte data[]; flushFlag = false; if (current == null) { while (!bufQ.canWrite() && !flushFlag) { try { bufQ.wait(); } catch (InterruptedException e) {} } if (flushFlag) return; current = bufQ.getEmptyBuffer(); current.setFlags(0); current.setOffset(0); current.setLength(0); current.setTimeStamp(convPTStoNanoseconds(pts)); // Check the buffer for the enough space. int bsize = (total > readFrameSize ? total : readFrameSize); data = (byte[])current.getData(); if (data == null || data.length < bsize) { data = new byte[bsize]; current.setData(data); } } else data = (byte[])current.getData(); len = current.getLength(); // First copy the spilled over temp buffer. if (inSize > 0) System.arraycopy(in, 0, data, len, inSize); // Read data from the stream into the buffer. parser.readBytes(stream, data, len + inSize, size); current.setLength(len + total); } // synchronized bufQ } /* * fill the output buffer from the inner buffer */ void copyFromInnerBuffer (Buffer out) { Buffer buf; synchronized (bufQ) { while (!bufQ.canRead() && !sysPausedFlag && !parserErrorFlag) { try { bufQ.wait(); } catch (InterruptedException e) {} } if (sysPausedFlag || parserErrorFlag) { out.setLength(0); out.setDiscard(true); return; } buf = bufQ.read(); byte saved[] = (byte[])out.getData(); out.copy(buf); buf.setData(saved); bufQ.readReport(); bufQ.notify(); } } /** * Release any blocking readFrame. */ void releaseReadFrame() { synchronized (bufQ) { bufQ.notifyAll(); } } /** * Generate the EOM buffer and add to the buffer Q. */ void generateEOM () { Buffer buf; synchronized (bufQ) { // We'll need to push the last bit of data left in the buffer Q. if (current != null) { bufQ.writeReport(); bufQ.notify(); current = null; } // Now grab an empty buffer and fill that with the EOM flag. while (!bufQ.canWrite()) { try { bufQ.wait(); } catch (InterruptedException e) {} } buf = bufQ.getEmptyBuffer(); buf.setFlags(buf.FLAG_EOM); buf.setLength(0); bufQ.writeReport(); bufQ.notify(); } } /* * flush the inner buffer */ void flushBuffer() { synchronized (bufQ) { if (current != null) { current.setDiscard(true); bufQ.writeReport(); current = null; } while (bufQ.canRead()) { bufQ.read(); bufQ.readReport(); } bufQ.notifyAll(); } } public String toString() { System.out.println("track type " + trackType + "(0 ?, 1 audio, 2 video)"); System.out.println("start PTS " + startPTS); System.out.println("info flag " + infoFlag); System.out.println("number of packets " + numPackets); System.out.println("maximum packet size " + maxPacketSize); System.out.println("supported " + supported); System.out.println("duration (?) " + duration); return media.toString(); } /// a temporary utility for debugging ! void saveBufToFile(){ /* FileOutputStream fout; int size = readyDataBytes(); int itmp = bufLen - readPtr; fout = (trackType == AUDIO_TYPE) ? aout : vout; if (size == 0) return; try { if (size > itmp) { fout.write (buf, readPtr, itmp); fout.write (buf, 0, (size-itmp)); } else { fout.write (buf, readPtr, size); } fout.flush(); readPtr = 0; writePtr = 0; } catch (java.io.IOException e) { System.out.println (" EE>> problem in writing to files"); } */ } } /* ===================================================================== */ /* * Implemantation of the Track interface */ private class MediaTrack implements Track { private TrackList trackInfo; private boolean enabled; private long sequenceNumber = 0; private Format format; private TrackListener listener; MpegParser parser = MpegParser.this; MediaTrack (TrackList trackInfo) { this.trackInfo = trackInfo; enabled = true; // EE?? format = trackInfo.media.createFormat(); } public void setTrackListener(TrackListener l) { listener = l; } public Format getFormat () { return format; } public void setEnabled (boolean t) { enabled = t; } public boolean isEnabled () { return enabled; } public Time getDuration () { return trackInfo.duration; } public Time getStartTime () { if (streamType == SYS11172_TYPE) { return new Time(((double)startPTS) / 90000.0); } else { return new Time(AVstartTimeNs); } } public void readFrame (Buffer buffer) { /* check first */ if (buffer == null) { return; } if (!enabled) { buffer.setDiscard(true); return; } /* now read frame into the buffer */ if (streamType == SYS11172_TYPE) { systemStreamReadFrame(buffer); } else { AudioVideoOnlyReadFrame(buffer); } buffer.setFormat(format); buffer.setSequenceNumber(++sequenceNumber); if (format instanceof AudioFormat) { long tmp = buffer.getTimeStamp(); buffer.setTimeStamp(lastAudioNs); lastAudioNs = tmp; } } private void AudioVideoOnlyReadFrame (Buffer buffer) { if (sysPausedFlag || parserErrorFlag) { buffer.setLength(0); buffer.setDiscard(true); } int size = trackInfo.readFrameSize; Object obj = buffer.getData(); byte[] data; if ( (obj == null) || (! (obj instanceof byte[]) ) || ( ((byte[])obj).length < size) ) { data = new byte[size]; buffer.setData(data); } else { data = (byte[]) obj; } int read1=0, read2=size; int actualBytesRead=0, counter=0; /* first, need to check if there is some data left in the initiation buffer */ if (initTmpBufLen > 0) { read1 = (initTmpBufLen > size) ? size : initTmpBufLen; System.arraycopy (initTmpStreamBuf, 0, data, 0, read1); initTmpBufLen -= read1; read2 -= read1; counter = read1; /// AVtotalBytesRead += read1; } if (trackInfo.trackType == AUDIO_TYPE) { buffer.setTimeStamp(convBytesToTimeAV(getLocation() - read1)); } /* now, read from the input stream */ if ((read2 > 0) && !EOMflag) { try { actualBytesRead = parser.readBytes (stream, data, read1, read2); if (actualBytesRead == com.sun.media.protocol.BasicSourceStream.LENGTH_DISCARD) { if (read1 == 0) { buffer.setDiscard(true); return; } } else { counter += actualBytesRead; /// AVtotalBytesRead += actualBytesRead; } } catch (IOException e) { updateEOMState(); EOMflag = true; if (AVlastTimeNs == 0) { /// AVcurrentTimeNs = convBytesToTimeAV(AVtotalBytesRead); AVcurrentTimeNs = convBytesToTimeAV(getLocation()); AVlastTimeNs = AVcurrentTimeNs; } } } if (EOMflag) { if (read1 > 0) { buffer.setLength(read1); buffer.setOffset(0); } else { buffer.setLength(0); buffer.setEOM(true); } } buffer.setOffset(0); buffer.setLength(counter); //buffer.setTimeStamp(lastSetPositionTimeNs); } private void systemStreamReadFrame(Buffer buffer) { /* copy from inner buffer to the output */ trackInfo.copyFromInnerBuffer(buffer); if (sysPausedFlag || parserErrorFlag) return; /* check for disabled tracks */ for (int i = 0; i < numTracks ; i++) { if (tracks[i] != null) { if (!tracks[i].isEnabled() ) { TrackList AtrackInfo = ((MediaTrack)tracks[i]).getTrackInfo(); AtrackInfo.flushBuffer(); } } } /* debugging: it's an opportunity to 'flush' the audio inner buffers */ if (hideAudioTracks) { TrackList AtrackInfo; for (int i = 0; i < numTracks ; i++) { if (tracks[i] != null) { AtrackInfo = ((MediaTrack)tracks[i]).getTrackInfo(); if (AtrackInfo.trackType == AUDIO_TYPE) { AtrackInfo.flushBuffer(); } } } } if (hideVideoTracks) { TrackList AtrackInfo; for (int i = 0; i < numTracks ; i++) { if (tracks[i] != null) { AtrackInfo = ((MediaTrack)tracks[i]).getTrackInfo(); if (AtrackInfo.trackType == VIDEO_TYPE) { AtrackInfo.flushBuffer(); } } } } } public int mapTimeToFrame (Time t) { return 0; // TODO } public Time mapFrameToTime (int frameNumber) { return null; // TODO } private TrackList getTrackInfo () { return trackInfo; } } /* ===================================================================== */ /* * A class for holding and manipulating the system data * of the MPEg bitstream */ private class SystemHeader { int headerLen = 0; /* 16 */ int rateBound = 0; /* (1+)22(+1) */ int audioBound = 0; /* 6 */ int fixedFlag = 0; /* 1 */ int CSPSFlag = 0; /* 1 */ int audioLockFlag = 0; /* 1 */ int videoLockFlag = 0; /* 1 */ int videoBound = 0; /* (1+)5 */ int reserved = 0; /* 8 */ boolean allAudioSTDFlag = false; /* STD is for all audio streams */ boolean allVideoSTDFlag = false; /* STD is for all video streams */ boolean streamFlags[] = new boolean [MAX_NUM_STREAMS]; /* 8xN */ int STDBufBoundScale[] = new int [MAX_NUM_STREAMS]; /* (2+)1xN */ int STDBufSizeBound[] = new int [MAX_NUM_STREAMS]; /* 13xN */ SystemHeader () { for (int i = 0; i < MAX_NUM_STREAMS; i++) { streamFlags[i] = false; STDBufBoundScale[i] = 0; STDBufSizeBound[i] = 0; } } /* reset system header fields */ void resetSystemHeader () { headerLen = 0; rateBound = 0; audioBound = 0; fixedFlag = 0; CSPSFlag = 0; audioLockFlag = 0; videoLockFlag = 0; videoBound = 0; reserved = 0; allAudioSTDFlag = false; allVideoSTDFlag = false; for (int i = 0; i < MAX_NUM_STREAMS; i++) { streamFlags[i] = false; STDBufBoundScale[i] = 0; STDBufSizeBound[i] = 0; } } void printFields () { System.out.println("headerLen "+headerLen); System.out.println("rateBound "+rateBound); System.out.println("audioBound "+audioBound); System.out.println("fixedFlag "+fixedFlag); System.out.println("CSPSFlag "+CSPSFlag); System.out.println("audioLockFlag "+audioLockFlag); System.out.println("videoLockFlag "+videoLockFlag); System.out.println("videoBound "+videoBound); System.out.println("reserved "+reserved); System.out.println("allAudioSTDFlag "+allAudioSTDFlag); System.out.println("allVideoSTDFlag "+allVideoSTDFlag); for (int i = 0; i < MAX_NUM_STREAMS; i++) { if (streamFlags[i]) System.out.println("["+i+"] STDBufBoundScale "+ STDBufBoundScale[i]+ " STDBufSizeBound "+ STDBufSizeBound[i]); } } } } /** this exception is thrown when mpeg data is not valid **/ class BadDataException extends MediaException { /* Creates a simple exception object. */ BadDataException() { super(); } /* Creates an exception object with a specific reason. */ BadDataException(String reason) { super(reason); } } /** * This class used to be an inner class, which is the correct thing to do. * Changed it to a package private class because of jdk1.2 security. * For jdk1.2 and above applets, MpegBufferThread is created in a * privileged block using jdk12CreateThreadAction. jdk12CreateThreadAction * class is unable to create and instantiate an inner class * in MpegParser class */ /** * A class for managing the thread which fill the inner buffers of * the tracks. */ class MpegBufferThread extends LoopThread { private MpegParser parser; MpegBufferThread() { this.setName(this.getName() + " (MpegBufferThread)"); useVideoPriority(); } void setParser(MpegParser p) { parser = p; } public boolean process() { // If EOM is set, then we'll generate the EOM marker. if (parser.EOMflag) { parser.updateTrackEOM(); pause(); return true; } try { /* fill buffers */ parser.mpegSystemParseBitstream(false, 0L, false, MpegParser.NO_PTS_VAL); } catch (BadDataException e) { parser.parserErrorFlag = true; } catch (BadHeaderException e) { parser.parserErrorFlag = true; } catch (IOException e) { parser.updateEOMState(); parser.EOMflag = true; if (parser.endPTS == MpegParser.NO_PTS_VAL) parser.endPTS = parser.currentPTS; } if (parser.parserErrorFlag) { Log.error("MPEG parser error: possibly with a corrupted bitstream."); pause(); } return true; /* for loop thread to continue */ } }