/*
* Java port of ffmpeg MPEG demultiplexer.
* Copyright (c) 2003 Jonathan Hueber.
*
* Copyright (c) 2000, 2001 Fabrice Bellard.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* See Credits file and Readme for details
* 1a39e335700bec46ae31a38e2156a898
*/
package net.sourceforge.jffmpeg.demux.mpg;
import javax.media.Demultiplexer;
import javax.media.protocol.Positionable;
import javax.media.protocol.Seekable;
import javax.media.Time;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.FileTypeDescriptor;
import javax.media.Track;
import javax.media.protocol.DataSource;
import javax.media.protocol.PullDataSource;
import javax.media.protocol.PullSourceStream;
import javax.media.MediaLocator;
import javax.media.BadHeaderException;
import javax.media.Buffer;
import javax.media.Format;
import javax.media.format.AudioFormat;
import javax.media.format.VideoFormat;
import javax.media.TrackListener;
import java.io.IOException;
import java.util.HashMap;
import java.util.Enumeration;
import java.awt.Dimension;
/**
* Mpeg file demultiplexer.
*/
public class MpegDemux implements Demultiplexer, Positionable {
/**
* Stream packets
*/
public static final int PACKET_START_CODE_MASK = 0xffffff00;
public static final int PACKET_START_CODE_PREFIX = 0x00000100;
public static final int SEQUENCE_START_CODE = 0x000001b3;
public static final int EXT_START_CODE = 0x000001b5;
public static final int SEQUENCE_END_CODE = 0x000001b7;
public static final int GOP_START_CODE = 0x000001b8;
public static final int ISO_11172_END_CODE = 0x000001b9;
public static final int PACK_START_CODE = 0x000001ba;
public static final int SYSTEM_HEADER_START_CODE = 0x000001bb;
public static final int PROGRAM_STREAM_MAP = 0x000001bc;
public static final int PRIVATE_STREAM_1 = 0x000001bd;
public static final int PADDING_STREAM = 0x000001be;
public static final int PRIVATE_STREAM_2 = 0x000001bf;
/**
* Input and output streams
*/
private PullSourceStream in;
private Seekable seek;
private HashMap tracks = new HashMap();
public void open() throws javax.media.ResourceUnavailableException {
}
public void reset() {
}
public void close() {
}
public synchronized void start() throws IOException {
}
public void stop() {
}
public Object getControl( String control ) {
return null;
}
public Object[] getControls() {
return new Object[0];
}
public Time getDuration() {
return new Time( 0 );
}
public Time getMediaTime() {
return new Time( 0 );
}
public String getName() {
return "Mpeg Demultiplexer";
}
public ContentDescriptor[] getSupportedInputContentDescriptors() {
return new ContentDescriptor[] {
new FileTypeDescriptor( "video.mpegt" )
};
}
public Track[] getTracks() throws IOException, BadHeaderException {
if ( tracks.size() == 0 ) {
MpegVideoTrack test = new MpegVideoTrack( this, 0x1e0 );
test.readFrame( new Buffer() );
test.readFrame( new Buffer() );
test.readFrame( new Buffer() );
}
return (Track[])tracks.values().toArray( new Track[0] );
}
public boolean isPositionable() {
return true;
}
public boolean isRandomAccess() {
return true;
}
public Time setPosition( javax.media.Time newTime, int parameter ) {
return newTime;
}
public void setSource( DataSource inputDataSource ) throws java.io.IOException, javax.media.IncompatibleSourceException {
if ( inputDataSource instanceof PullDataSource ) {
in = ((PullDataSource)inputDataSource).getStreams()[0];
if ( in instanceof Seekable
&& in.getContentLength() != in.LENGTH_UNKNOWN) {
seek = (Seekable)in;
}
return;
}
throw new javax.media.IncompatibleSourceException();
}
/**
* Handle MPEG Packets
*/
private byte[] scratch = new byte[ 4 ];
/**
* Look at the next packet and retrieve ID
*/
protected synchronized int peekPacket( long pos ) throws IOException {
pos = seek.seek( pos );
in.read( scratch, 0, 4 );
int startcode = ((scratch[0]&0xff)<<24)|((scratch[1]&0xff)<<16)
|((scratch[2]&0xff)<<8) | (scratch[3]&0xff);
seek.seek( pos );
return startcode;
}
/**
* Seek to the next packet
*/
protected synchronized long skipPacket( long pos ) throws IOException {
pos = seek.seek( pos + 1 );
int i = 0xffffffff;
while ( ((i|0xff) != 0x1ff) && (i != 0) ) {
in.read( scratch, 0, 1 );
i = ((i << 8) | (0xff & scratch[0])) & 0xffffffff;
pos++;
}
return pos - 4;
}
/**
* Read packet
*/
private static final int PACKET_READ_SIZE = 1024;
protected synchronized long readPacket( Buffer output,
int id,
long pos ) throws IOException {
/**
* Set buffer size
*/
byte[] buffer = (byte[])output.getData();
if ( buffer == null || buffer.length < 100000 ) {
buffer = new byte[ 1000000 ];
output.setData( buffer );
}
/**
* Seek to start of packet
*/
pos = seek.seek( pos );
int j = output.getLength();
/**
* Read packet header
*/
in.read( buffer, j, 4 ); j += 4; pos += 4;
/**
* Read until next packet header
*/
int p = 0;
int i = 0xffffffff;
while ( (i|0xff) != 0x1ff ) {
if ( (p % PACKET_READ_SIZE) == 0 ) {
in.read( buffer, j, PACKET_READ_SIZE );
}
j++; pos++; p++;
i = ((i << 8) | (0xff & buffer[j-1])) & 0xffffffff;
}
pos -= 4;
/**
* Set to output
*/
output.setData( buffer );
output.setLength( j - 4 );
return pos;
}
/**
* Skip past DTS packet
* - Check if this packet should be added as a new stream
*/
protected synchronized long skipDTSPacket( long pos ) throws IOException {
readDTSHeader( null, pos);
pos = seek.seek( pos + 4 );
in.read( scratch, 0, 2 ); pos += 2;
int len = ((scratch[0]&0xff)<<8) | (scratch[1]&0xff);
return pos + len;
}
/**
* Read Presentation Timestamp
*/
private long get_pts( int c ) throws IOException {
long pts;
int val;
if (c < 0) {
in.read( scratch, 0, 1 );
c = scratch[0]&0xff;
}
in.read( scratch, 0, 4 );
pts = ((long)((c >> 1) & 0x07)) << 30;
val = ((scratch[0]&0xff)<<8) | (scratch[1]&0xff);
pts |= ((long)(val >> 1)) << 15;
val = ((scratch[2]&0xff)<<8) | (scratch[3]&0xff);
pts |= ((long)(val >> 1));
return pts;
}
/**
* Read a DTS packet header
*/
protected synchronized long readDTSHeader( Buffer output,
long pos ) throws IOException {
return readDTSPacket( output, 0, pos, true );
}
/**
* Read a DTS packet
*/
protected synchronized long readDTSPacket( Buffer output,
int streamId,
long pos ) throws IOException {
return readDTSPacket( output, streamId, pos, false );
}
/**
* Read a DTS packet
*/
private synchronized long readDTSPacket( Buffer output,
int streamId,
long pos,
boolean onlyHeader )
throws IOException {
/**
* Check buffer size
*/
byte[] buffer = null;
if ( output != null ) {
buffer = (byte[])output.getData();
if ( buffer == null || buffer.length < 100000 ) {
buffer = new byte[ 1000000 ];
}
}
/**
* Seek to start of packet
*/
pos = seek.seek( pos );
/**
* Read packet header
*/
int startcode = peekPacket( pos );
in.read( scratch, 0, 4 ); pos += 4;
/**
* Read packet length
*/
in.read( scratch, 0, 2 ); pos += 2;
int len = ((scratch[0]&0xff)<<8) | (scratch[1]&0xff);
/* Set up Display and Presentation Timestamp */
long dts = -1;
long pts = -1;
/**
* Skip padding
*/
int c;
do {
in.read( scratch, 0, 1 ); pos++; len--;
c = scratch[0] & 0xff;
} while ( c == 0xff );
/**
* Parse header bits
*/
if ((c & 0xc0) == 0x40) {
in.read( scratch, 0, 2 ); pos += 2; len -= 2;
c = scratch[1] & 0xff;
}
if ((c & 0xf0) == 0x20) {
/* Display and Presentation Timestamps are the same */
dts = get_pts(c);
pts = dts;
len -= 4; pos += 4;
} else if ((c & 0xf0) == 0x30) {
/* Display and Presentation Timestamps are different */
pts = get_pts(c);
dts = get_pts(-1);
len -= 9; pos += 4;
} else if ((c & 0xc0) == 0x80) {
/* Mpeg 2 Header extension */
if ((c & 0x30) != 0) {
throw new IOException( "Encrypted streams are not handled" );
}
in.read( scratch, 0, 2 ); pos += 2; len -= 2;
int flags = scratch[0]&0xff;
int header_len = scratch[1]&0xff;
if (header_len > len) {
throw new IOException( "Invalid Header" );
}
if ((flags & 0xc0) == 0x80) {
dts = get_pts(-1);
pts = dts;
if (header_len < 5) {
throw new IOException( "Invalid Header" );
}
header_len -= 5;
len -= 5; pos += 5;
}
if ((flags & 0xc0) == 0xc0) {
pts = get_pts(-1);
dts = get_pts(-1);
if (header_len < 10) {
throw new IOException( "Invalid Header" );
}
header_len -= 10;
len -= 10; pos += 10;
}
len -= header_len; pos += header_len;
while (header_len > 0) {
in.read( scratch, 0, 1 );
header_len--;
}
} else if( c != 0x0f ) {
throw new IOException( "Invalid Header" );
}
/* Special case for 0x1bd */
if ( startcode == PRIVATE_STREAM_1 ) {
if (len < 1) {
throw new IOException( "Invalid Header" );
}
in.read( scratch, 0, 1 );
len--; pos += 1;
startcode = scratch[0] & 0xff;
if (startcode >= 0x80 && startcode <= 0xbf) {
/* audio: skip header */
if (len < 3) {
throw new IOException( "Invalid Header" );
}
in.read( scratch, 0, 3 ); len -= 3; pos += 3;
}
/* Fudge "extended" startcode */
if (streamId != startcode && !onlyHeader ) {
onlyHeader = true;
pos += len;
}
}
/* Handle Timestamp */
if ( pts >= 0 ) {
// System.out.println( dts + " -- " + pts );
// output.setTimestamp( pts * 1000000 );
}
if ( !onlyHeader ) {
/**
* Read until next packet header
*/
int j = output.getLength();
in.read( buffer, j, len ); j += len; pos += len;
output.setData( buffer );
output.setLength( j );
} else {
/**
* We are skipping this packet
* -- Should we create a track to handle it?
*/
if ( !tracks.containsKey( new Integer( startcode ) ) ) {
System.out.println( "Add track " + startcode );
if (startcode >= 0x1e0 && startcode <= 0x1ef) {
/* Mpeg2 video */
tracks.put( new Integer( startcode ),
new MpegVideoTrack( this, startcode ) );
} else if (startcode >= 0x1c0 && startcode <= 0x1df) {
/* Mpeg Layer 2 Audio */
tracks.put( new Integer( startcode ),
new MpegAudioTrack( this, startcode ) );
} else if (startcode >= 0x80 && startcode <= 0x9f) {
/* AC3 */
tracks.put( new Integer( startcode ),
new MpegAC3AudioTrack( this, PRIVATE_STREAM_1,
startcode ) );
} else if (startcode >= 0xa0 && startcode <= 0xbf) {
/* PCM */
}
}
}
return pos;
}
}