/*
* Java port of ffmpeg VOB demultiplexer.
* Contains some liba52 and Xine code (GPL)
* Copyright (c) 2003 Jonathan Hueber.
*
* Copyright (c) 2000, 2001, 2002 Fabrice Bellard.
*
* vobdemux is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* vobdemux 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package net.sourceforge.jffmpeg.demux.vob;
import javax.media.Track;
import javax.media.Time;
import javax.media.Format;
import javax.media.Buffer;
import javax.media.TrackListener;
import javax.media.format.VideoFormat;
import java.awt.Dimension;
import java.io.IOException;
import java.io.InputStream;
import net.sourceforge.jffmpeg.GPLLicense;
/**
* This class handles video data read from a VOB file
*/
public class VideoTrack extends DataBuffer implements Track, GPLLicense {
/**
* Internal statistics
*/
private int totalNumberOfFrames = 0;
private int framesDelivered = 0;
public int videoFramesBufferTargetLow = 0;
public int videoFramesBufferTargetHigh = 0;
/* We only really get 1 frame every 20 reads */
private int packetsPerFrame = 0;
private static final int PACKETS_PER_FRAME = 10;
/**
* Time in milliseconds this frame was meant to start
*/
private int width, height;
private float frameRate = 30;
private long halfSeconds = 0;
VobDemux demux;
int streamNumber;
boolean enabled;
/**
* Creates a new instance of Video Track
*/
public VideoTrack( VobDemux demux, int streamNumber ) {
this.demux = demux;
this.streamNumber = streamNumber;
enabled = (streamNumber == 0x1e0);
/**
* Initialise frame buffers
*/
for ( int i = 0; i < frameBuffer.length; i++ ) {
frameBuffer[ i ] = new Buffer();
frameBuffer[ i ].setData( new byte[ 1000 ] );
}
partialFrame.setData( new byte[ 1000 ] );
partialFrame.setLength( 0 );
}
Time time = new Time( (long)(1000 / frameRate) );
public Time getDuration() {
// System.out.println( "Get Duration" );
return time;
}
/**
* Video format
*/
public Format getFormat() {
return new VideoFormat( "mpeg",
new Dimension(width, height),
10000,
(new byte[0]).getClass(),
frameRate );
}
public Time getStartTime() {
System.out.println( "Get Start time" );
return new Time( 2000 );
}
public boolean isEnabled() {
return enabled;
}
public Time mapFrameToTime(int param) {
System.out.println( "Map Frame to time" );
return new Time( param );
}
public int mapTimeToFrame(javax.media.Time time) {
System.out.println( "Map Time to Frame" );
return 0;
}
/**
* Append data to a buffer
*/
private static final void appendBuffer( Buffer buffer, byte[] data, int length ) {
byte[] bdata = (byte[])buffer.getData();
int blen = buffer.getLength();
/**
* Is the buffer large enough ?
*/
if ( bdata.length < blen + length ) {
byte[] temp = new byte[ (blen + length) * 2];
System.arraycopy( bdata, 0, temp, 0, blen );
buffer.setData( temp );
bdata = temp;
}
System.arraycopy( data, 0, bdata, blen, length );
buffer.setLength( blen + length );
}
/**
* Copy data to a buffer
*/
private static final void setBuffer( Buffer buffer, byte[] data, int offset, int length ) {
byte[] bdata = (byte[])buffer.getData();
/**
* Is the buffer large enough ?
*/
if ( bdata.length < length ) {
bdata = new byte[ length * 2];
buffer.setData( bdata );
}
System.arraycopy( data, offset, bdata, 0, length );
buffer.setLength( length );
}
public static final byte PICTURE_START_CODE = 0;
public static final byte SEQUENCE_START_CODE = (byte)0xb3;
public static final int I_TYPE = 1;
public static final int P_TYPE = 2;
public static final int B_TYPE = 3;
public static final String[] pictName = new String[] { "0", "I", "P", "B", "4", "5", "6", "7" };
/**
* Split the input data into frames of data
*/
private int currentFrameNumber = 0;
public static final int FRAME_BUFFER_MASK = 255;
private int[] frameType = new int[ FRAME_BUFFER_MASK + 1 ];
private long[] frameReference = new long[ FRAME_BUFFER_MASK + 1 ];
private Buffer[] frameBuffer = new Buffer[ FRAME_BUFFER_MASK + 1 ];
private Buffer partialFrame = new Buffer();
/**
* Temporary work areas
*/
private Buffer temp = new Buffer();
private int[] framePointer = new int[ FRAME_BUFFER_MASK + 1 ];
/**
* Supply a frame of data to codec
*/
public void readFrame(Buffer outputBuffer) {
outputBuffer.setFlags( Buffer.FLAG_NO_WAIT );
try {
/* Ignore data if this channel is disabled */
if ( !enabled ) {
demux.readVideo( streamNumber, temp );
outputBuffer.setLength( 0 );
return;
}
do {
/**
* Read data from VOB file
*/
while ( currentFrameNumber == getNumberOfFrames() ) {
demux.parse( streamNumber );
}
// System.out.println( "Frame number " + currentFrameNumber + " " + getNumberOfFrames() );
/* Supply next frame */
outputBuffer.setData( frameBuffer[ currentFrameNumber ].getData() );
outputBuffer.setLength( frameBuffer[ currentFrameNumber ].getLength() );
currentFrameNumber = (currentFrameNumber + 1) & FRAME_BUFFER_MASK;
/* Remove B-Frame if we are running slow */
} while ( frameType[ (currentFrameNumber - 1) & FRAME_BUFFER_MASK ] == B_TYPE
&& demux.isVideoSlow( frameReference[ currentFrameNumber ] ) );
framesDelivered++;
} catch (IOException e) {
}
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public void setTrackListener(TrackListener trackListener) {
}
public static final float[] frameRateTable = new float[] {
0, 24000, 24024, 25025, 30000, 30030, 50050,
60000, 60060, 15015, 5005, 10010, 12012, 15015
};
public static final double[] aspectRatioTable = new double[] {
0, 1.0, -3.0/4.0, -9.0/16.0, -1.0/2.21,
0, 1.0, -3.0/4.0, -9.0/16.0, -1.0/2.21,
0, 1.0, -3.0/4.0, -9.0/16.0, -1.0/2.21,
0, 1.0, -3.0/4.0, -9.0/16.0, -1.0/2.21,
};
private int numberOfFramesAvailable = 0;
/**
* We need to synchronize access to the number of frames available
*/
private synchronized int getNumberOfFrames() {
return numberOfFramesAvailable;
}
private synchronized void setNumberOfFrames( int nof) {
numberOfFramesAvailable = nof;
}
/**
* Extract frames into frame buffer
*/
public synchronized void readData( long timeStamp, InputStream in, int length ) throws IOException {
/* Read array of data */
if ( buffer.length < length ) {
buffer = new byte[ length * 2 ];
}
size = 0;
int read = 0;
while ( length > 0 ) {
read = in.read( buffer, size, length );
if ( read < 0 ) throw new IOException( "End of Stream" );
length -= read;
size += read;
}
/* Append to existing data */
appendBuffer( partialFrame, buffer, size );
/* We only really get 1 frame every 20 reads */
if ( packetsPerFrame-- > 0 ) return;
packetsPerFrame = PACKETS_PER_FRAME;
byte[] currentData = (byte[])partialFrame.getData();
int currentDataLength = partialFrame.getLength();
/**
* Extract frame data
*/
int numberOfFrames = getNumberOfFrames();
int oldNumberOfFrames = numberOfFrames;
// currentFrameNumber = 0;
boolean sequenceFrame = false;
// boolean dropThisFrame = false;
for ( int i = 0; i < currentDataLength - 6; i++ ) {
/**
* Extract pointers to frames
*/
if ( currentData[ i ] == 0
&& currentData[ i + 1 ] == 0
&& currentData[ i + 2 ] == 1 ) {
byte header = currentData[ i + 3 ];
if ( header == PICTURE_START_CODE ) {
/* Picture start code */
int reference = ((currentData[ i + 4 ] & 0xff) << 2) | ( (currentData[ i + 5 ] >> 6 ) & 0x03 );
int pict_code = (currentData[ i + 5 ] >>3) & 0x7;
// System.out.println( pictName[pict_code] + " " + reference );
if ( !sequenceFrame ) {
framePointer[ numberOfFrames ] = i;
numberOfFrames = (numberOfFrames + 1) & FRAME_BUFFER_MASK;
}
frameType[ (numberOfFrames - 1) & FRAME_BUFFER_MASK ] = pict_code;
frameReference[ (numberOfFrames - 1) & FRAME_BUFFER_MASK ] = timeStamp;
sequenceFrame = false;
}
if ( header == SEQUENCE_START_CODE ) {
sequenceFrame = true;
framePointer[ numberOfFrames ] = i;
numberOfFrames = (numberOfFrames + 1) & FRAME_BUFFER_MASK;
width = ((currentData[ i + 4 ] & 0xff)<<4)|((currentData[ i + 5 ] & 0xff)>>4);
height = ((currentData[ i + 5 ] & 0x0f)<<8)|((currentData[ i + 6 ] & 0xff));
double aspect = aspectRatioTable[ (currentData[ i + 7 ] >> 4) & 0x0f ];
frameRate = (float)frameRateTable[ currentData[ i + 7 ] & 0x0f ] / 1001;
// System.out.println( width + "," + height + " " + aspect + " " + frameRate + " " + currentData[ i + 7 ] );
}
}
}
/* Split data into frames */
if ( numberOfFrames != oldNumberOfFrames ) {
numberOfFrames = ( numberOfFrames - 1 ) & FRAME_BUFFER_MASK;
for ( int framePointerNumber = oldNumberOfFrames;
framePointerNumber != numberOfFrames;
framePointerNumber = (framePointerNumber+1) & FRAME_BUFFER_MASK ) {
setBuffer( frameBuffer[ framePointerNumber ], currentData, framePointer[ framePointerNumber ],
framePointer[ (framePointerNumber + 1)& FRAME_BUFFER_MASK ] - framePointer[ framePointerNumber ] );
totalNumberOfFrames++;
}
// System.out.println( "totalNumberOfFrames " + totalNumberOfFrames );
}
/**
* Leftover data goes into the partialFrame
*/
setBuffer( partialFrame, currentData, framePointer[ numberOfFrames ], currentDataLength - framePointer[ numberOfFrames ] );
setNumberOfFrames( numberOfFrames );
}
/**
* Dump all data pending seek. Note thread safety isn't the best
*/
public synchronized void drop() {
setNumberOfFrames( 1 );
currentFrameNumber = 0;
totalNumberOfFrames = 0;
super.drop();
/**
* Re-initialise buffers (should help reduce memory allocation)
*/
for ( int i = 0; i < frameBuffer.length; i++ ) {
frameBuffer[ i ] = new Buffer();
frameBuffer[ i ].setData( new byte[ 1000 ] );
}
partialFrame.setData( new byte[ 1000 ] );
partialFrame.setLength( 0 );
}
}