/* * 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 * 1a39e335700bec46ae31a38e2156a898 */ package net.sourceforge.jffmpeg.demux.vob; import java.io.*; import java.net.URL; import java.util.HashMap; 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 java.util.Iterator; import net.sourceforge.jffmpeg.GPLLicense; /** * VOB file demultiplexer. Effectively this simply maintains a HashMap * of data buffers representing the audio and video streams in the VOB file. * * Some timing information is tracked here */ public class VobDemux implements Demultiplexer, Positionable, GPLLicense { /** * Last time stamp read from Vob file */ private long timeStamp = 0; /** * Constants for maintaining Lip-sync */ public static final int SYNC_CHANNEL = 0x80; public static final long MAX_AUDIO_BUFFER = 4200; public static final long MIN_AUDIO_BUFFER = 100; public static final boolean debugLipSync = false; public static final boolean showAllFrames = false; /** * File size for fast-forward and rewind */ private long totalFileSize = 0; private long rateEstimateBase = 0; private long rateEstimateTime = 0; /** * Input and output streams */ private InputStream in; private DataBuffer[] streams = new DataBuffer[ 512 ]; /** Vob file constants */ public static final int PACK_START_CODE = 0x000001ba; public static final int SYSTEM_HEADER_START_CODE = 0x000001bb; public static final int SEQUENCE_END_CODE = 0x000001b7; public static final int PACKET_START_CODE_MASK = 0xffffff00; public static final int PACKET_START_CODE_PREFIX = 0x00000100; public static final int ISO_11172_END_CODE = 0x000001b9; /* mpeg2 constants */ 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; public static final int CODEC_TYPE_VIDEO = 0; public static final int CODEC_TYPE_AUDIO = 1; public static final int CODEC_ID_MPEG1VIDEO = 0; public static final int CODEC_ID_MP2 = 1; public static final int CODEC_ID_AC3 = 2; public static final int CODEC_ID_PCM_S16BE = 3; /** Empty constructor */ public VobDemux() { } /** Creates a new instance of VobDemux based on an InputStream */ public VobDemux( InputStream in ) { this.in = in; } /** * Read until we find a start code 0x000001xx */ private int findStartCode() throws IOException { int state = 0xff; do { state = ((state << 8) | in.read()) & 0xffffffff; } while ( (state & 0xffffff00) != 0x100 ); return state; } /** * Read a Presentation Time Stamp */ private long getPts( int c ) throws IOException { if (c < 0) c = in.read(); long pts = (((long)c >> 1) & 0x07) << 30; long val = (in.read() << 8) | in.read(); pts |= (val >> 1) << 15; val = (in.read() << 8) | in.read(); pts |= (val >> 1); return pts; } /** * This Find and parse packet ID "readPacket" */ protected synchronized void parse( int readPacket ) throws IOException { int startCode; do { /** Find packet ID */ startCode = findStartCode(); switch ( startCode ) { case PACK_START_CODE: { /* Read timestamp information */ int byte4 = in.read(); int byte5 = in.read(); int byte6 = in.read(); int byte7 = in.read(); int byte8 = in.read(); timeStamp = (( byte4 & 0x08 ) << 27) |(( byte4 & 0x03 ) << 28) |(( byte5 ) << 20) |(( byte6 & 0xf8 ) << 12 ) |(( byte6 & 0x03 ) << 13 ) |(( byte7 ) << 5 ) |(( byte8 & 0xfe ) >> 3 ); timeStamp *= 300; timeStamp /= 26900; // timeStamp /= 28000; break; } case SYSTEM_HEADER_START_CODE: { /** Ignore information packet */ // System.out.println( "SystemHeaderStartCode" ); break; } case PADDING_STREAM: case PRIVATE_STREAM_2: { /* Skip unsupported packets */ int length = (in.read() << 8) | in.read(); in.skip( length ); break; } default: { /* Ignore unrecognised packets */ if (!((startCode >= 0x1c0 && startCode <= 0x1df) || (startCode >= 0x1e0 && startCode <= 0x1ef) || (startCode == 0x1bd))) { // System.out.println( "Unrecognised " + startCode ); break; } /* packet length */ long pts = 0; long dts = 0; int length = (in.read() << 8) | in.read(); /* Read control flags */ int c; do { c = in.read(); length--; } while ( c == 0xff ); /* Escape code */ if ( (c & 0xc0) == 0x40 ) { in.read(); c = in.read(); length -=2; } /* Read Pts/Dts */ switch ( c & 0xf0 ) { case 0x20: { pts = getPts(c); length -= 4; break; } case 0x30: { pts = getPts(c); dts = getPts(-1); length -= 9; break; } default: { if ( (c & 0xc0) != 0x80 ) break; // if ( (c & 0x30) != 0) throw new IOException( "Encrypted streams not supported" ); /* Escaped header */ int flags = in.read(); int headerLength = in.read(); length -= 2; if ( headerLength > length ) continue; switch ( flags & 0xc0 ) { case 0x80: { pts = getPts(-1); headerLength -= 5; length -= 5; break; } case 0xc0: { pts = getPts(-1); dts = getPts(-1); headerLength -= 10; length -= 10; break; } } /* Skip remainder of header */ length -= headerLength; in.skip( headerLength ); break; } } // System.out.println( "pts " + pts + " dts " + dts ); /** Read Audio stream header 0x1bd */ if ( startCode == PRIVATE_STREAM_1 ) { startCode = in.read(); length--; if (startCode >= 0x80 && startCode <= 0xbf) { /* audio: skip header */ in.skip( 3 ); length -= 3; } } /* Stream ID is in startCode */ DataBuffer out = streams[ startCode ]; if ( out == null ) { // System.out.println( "Allocate " + startCode ); out = allocateStream( startCode ); if ( out == null ) { /* Unknown data */ in.skip( length ); break; } } out.readData( timeStamp, in, length ); break; } } } while ( readPacket != startCode ); } private DataBuffer allocateStream( int startCode ) { DataBuffer out; // int type; // int codec_id; if (startCode >= 0x1e0 && startCode <= 0x1ef) { // type = CODEC_TYPE_VIDEO; // codec_id = CODEC_ID_MPEG1VIDEO; out = new VideoTrack(this, startCode); } else if (startCode >= 0x1c0 && startCode <= 0x1df) { // type = CODEC_TYPE_AUDIO; // codec_id = CODEC_ID_MP2; out = new VideoTrack(this, startCode); } else if (startCode >= 0x80 && startCode <= 0x9f) { // type = CODEC_TYPE_AUDIO; // codec_id = CODEC_ID_AC3; out = new AudioTrack(this, startCode); } else if (startCode >= 0xa0 && startCode <= 0xbf) { // type = CODEC_TYPE_AUDIO; // codec_id = CODEC_ID_PCM_S16BE; out = new AudioTrack(this, startCode); } else { return null; } /* New stream */ streams[ startCode] = out; return out; } /** * Read video data into buffer */ protected synchronized void readVideo( int stream, Buffer buffer ) throws IOException { DataBuffer bsos; do { bsos = streams[ stream ]; if ( bsos == null || bsos.getCurrentSize() == 0 ) { parse( SYNC_CHANNEL ); bsos = null; } } while ( bsos == null ); byte[] data = bsos.getBuffer(); byte[] oldData = (byte[])buffer.getData(); buffer.setData( data ); buffer.setLength( bsos.getCurrentSize() ); if ( oldData != null ) { bsos.resetBuffer( oldData ); } else { bsos.resetBuffer( new byte[ data.length ] ); } } /** * Read audio data into buffer */ protected synchronized void readAudio( int stream, Buffer buffer ) throws IOException { // System.out.println( "byte rate " + estimateByteRate() ); DataBuffer bsos; do { bsos = streams[ stream ]; if ( bsos == null || (bsos.getCurrentSize() == 0)) { parse( SYNC_CHANNEL ); } } while (bsos == null); byte[] data = bsos.getBuffer(); byte[] oldData = (byte[])buffer.getData(); buffer.setData( data ); buffer.setLength( bsos.getCurrentSize() ); if ( oldData != null ) { bsos.resetBuffer( oldData ); } else { bsos.resetBuffer( new byte[ data.length ] ); } } /* Get Start time * private long startTime = 0; public long getStartTime() { return startTime; } */ /** Required methods to be a Demultiplexer */ public void close() { } public Object getControl(String str) { return null; } public Object[] getControls() { return new Object[0]; } Time estimatedDuration = new Time( 1000 ); public Time getDuration() { return estimatedDuration; } public Time getMediaTime() { System.out.println( "getMediaTime" ); return new Time( 5000000 ); } public String getName() { return "VOB demux"; } public ContentDescriptor[] getSupportedInputContentDescriptors() { // System.out.println( "Requesting content descriptor" ); return new ContentDescriptor[] { new FileTypeDescriptor( "video.vob" ) }; } public Track[] getTracks() throws IOException, BadHeaderException { int length = 0; for ( int i = 0; i < streams.length; i++ ) { if ( streams[ i ] != null ) length++; } Track[] tracks = new Track[ length ]; for ( int i = 0; i < streams.length; i++ ) { if ( streams[ i ] != null ) { --length; tracks[ length ] = (Track)streams[i]; } } return tracks; } public boolean isPositionable() { return true; } public boolean isRandomAccess() { return true; } public void open() throws javax.media.ResourceUnavailableException { } public void reset() { } public Time setPosition(javax.media.Time time, int param) { /** * Clear pending buffers **/ for ( int i = 0; i < streams.length; i++ ) { if ( streams[ i ] != null ) { DataBuffer dataBuffer = (DataBuffer)streams[ i ]; dataBuffer.drop(); } } try { seekSource.seek( (time.getNanoseconds() * ESTIMATED_BYTE_RATE)/1000000000 ); timeStamp = 0; start(); System.out.println( "Aim for position " + (time.getNanoseconds()/1000000) + " actual " + timeStamp ); } catch ( IOException e ) { e.printStackTrace(); } startTime = 0; //System.currentTimeMillis() - timeStamp - MAX_AUDIO_BUFFER; return time; } private PullSourceStream dataSource; private Seekable seekSource; public void setSource( DataSource inputDataSource ) throws java.io.IOException, javax.media.IncompatibleSourceException { if ( inputDataSource instanceof PullDataSource ) { PullSourceStream pullSource = ((PullDataSource)inputDataSource).getStreams()[0]; this.dataSource = pullSource; if ( pullSource instanceof Seekable ) { this.seekSource = (Seekable)pullSource; // System.out.println( "Data source " + this.dataSource + (this.dataSource instanceof Seekable) ); /* Find file size */ estimatedDuration = new Time( 1000 ); try { MediaLocator locator = inputDataSource.getLocator(); if ( locator != null ) { URL url = locator.getURL(); if ( url != null ) { File file = new File( url.getFile() ); totalFileSize = file.length(); // System.out.println( "File size " + (totalFileSize * 1000000/ ESTIMATED_BYTE_RATE) ); estimatedDuration = new Time( (totalFileSize * 1000000000 / ESTIMATED_BYTE_RATE) ); } } } catch ( IOException e ) { } } in = new PullSourceInputStream( pullSource ); return; } throw new javax.media.IncompatibleSourceException(); } public static final long ESTIMATED_BYTE_RATE = 540000; /* protected float estimateByteRate() { long time = System.currentTimeMillis() - rateEstimateTime; long bytes = (seekSource.tell() - rateEstimateBase) * 1000; if ( time == 0 || rateEstimateTime == 0 || time > 50000 ) { rateEstimateTime = System.currentTimeMillis(); rateEstimateBase = seekSource.tell(); if ( time <= 50000 ) return ESTIMATED_BYTE_RATE; } return bytes / time; } */ public synchronized void start() throws java.io.IOException { parse( SYNC_CHANNEL ); startTime = System.currentTimeMillis() - timeStamp - MAX_AUDIO_BUFFER; parse( SYNC_CHANNEL ); parse( SYNC_CHANNEL ); parse( SYNC_CHANNEL ); parse( SYNC_CHANNEL ); parse( SYNC_CHANNEL ); parse( SYNC_CHANNEL ); parse( SYNC_CHANNEL ); parse( SYNC_CHANNEL ); parse( SYNC_CHANNEL ); parse( SYNC_CHANNEL ); parse( SYNC_CHANNEL ); } public void stop() { } /** * The estmated startTime of the stream timer */ private long startTime = 0; /** * Note the Audio is buffered so this is not exact. * We fiddle the startTime so that the video is never more than * MAX_AUDIO_BUFFER behind */ public final void setAudioTimeStamp( long timeStamp ) { long time = System.currentTimeMillis() - startTime; /* Video is too far behind audio */ if ( time + MAX_AUDIO_BUFFER < timeStamp ) { if (debugLipSync) System.out.println( "Audio resync " + time + " " + timeStamp ); startTime -= timeStamp - time - MAX_AUDIO_BUFFER; } if ( time >= timeStamp && timeStamp > MAX_AUDIO_BUFFER ) { startTime -= timeStamp - time - MAX_AUDIO_BUFFER; return; } /* Video is too far behind audio */ if ( time + MIN_AUDIO_BUFFER > timeStamp && timeStamp > MAX_AUDIO_BUFFER ) { if (debugLipSync) System.out.println( "Audio behind " + time + " " + timeStamp ); startTime -= timeStamp - time - MAX_AUDIO_BUFFER; } } /** * Returns true if the video is behind target */ public final boolean isVideoSlow( long target ) { if ( showAllFrames ) return false; if ( startTime == 0 ) startTime = System.currentTimeMillis(); //demux.getStartTime(); long time = System.currentTimeMillis() - startTime; if ( target < 1000 && time - target > 5000 ) { /* change video section */ startTime = System.currentTimeMillis(); return false; } if (debugLipSync) System.out.print( "time " + time + " target " + target ); if (target <= time) { if (debugLipSync) System.out.println( " slow " + ( time - target ) ); return true; } if (debugLipSync) System.out.println(); if (target > time + 400) { synchronized( this ) { try { if ( target - time - 15 < 200 ) { if (debugLipSync) System.out.println( "Wait " + (target - time - 15 ) ); this.wait( target - time - 15 ); } else { this.wait( 100 ); } } catch ( Exception e ) {} } } return false; } }