/* * Java port of Xine AVI demultiplexer. * Contains Xine code (GPL) * * Copyright (c) 2003-2005 Jonathan Hueber. * * This AVI demultiplexer is only used for debugging. * * 1a39e335700bec46ae31a38e2156a898 */ package net.sourceforge.jffmpeg.demux.avi; 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 javax.media.Track; import javax.media.TrackListener; import javax.media.format.VideoFormat; import javax.media.format.AudioFormat; import javax.media.Format; import java.awt.Dimension; import java.util.Iterator; import net.sourceforge.jffmpeg.GPLLicense; /** * AVI 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 AviDemux implements Demultiplexer, Positionable, GPLLicense { private final int MAX_AUDIO_STREAMS = 1; private int numberOfAudioChannels = 0; private AviTrack[] track = new AviTrack[ 1 + MAX_AUDIO_STREAMS ]; private byte[] idx = null; /** Empty constructor */ public AviDemux() { } /** Required methods to be a Demultiplexer */ public void close() { } public Object getControl(String str) { return null; } public Object[] getControls() { return new Object[0]; } public Time getDuration() { return new Time( 5000000 ); } public Time getMediaTime() { return new Time( 5000000 ); } public String getName() { return "AVI demux"; } public ContentDescriptor[] getSupportedInputContentDescriptors() { return new ContentDescriptor[] { new FileTypeDescriptor( "video.avit" ) }; } public Track[] getTracks() throws IOException, BadHeaderException { return track; } 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) { 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; } return; } throw new javax.media.IncompatibleSourceException(); } /** Parse headers */ public void start() throws java.io.IOException { /** * Check this is an AVI file */ String id = new String( readBuffer( 4 ), "ASCII" ); readBuffer( 4 ); String type = new String( readBuffer( 4 ), "ASCII" ); if ( !"RIFF".equalsIgnoreCase( id ) || !"AVI ".equalsIgnoreCase( type ) ) throw new IOException( "Not AVI file" ); /** * Extract header data */ byte[] hdrl = null; while ( true ) { String command = new String( readBuffer( 4 ), "ASCII" ); int length = (readBytes(4) + 1) &~1; if ( "LIST".equalsIgnoreCase( command ) ) { command = new String( readBuffer( 4 ), "ASCII" ); length -= 4; if ( "movi".equalsIgnoreCase( command ) ) { break; } if ( "hdrl".equalsIgnoreCase( command ) ) { hdrl = readBuffer( length ); } if ( "idx1".equalsIgnoreCase( command ) ) { idx = readBuffer( length ); } if ( "iddx".equalsIgnoreCase( command ) ) { idx = readBuffer( length ); } } else { readBuffer( length ); } } /** * Parse hdrl */ int streamNumber = 0; int lastTagID = 0; for ( int i = 0; i < hdrl.length; ) { String command = new String( hdrl, i, 4 ); int size = str2ulong( hdrl, i + 4 ); if ( "LIST".equalsIgnoreCase( command ) ) { i += 12; continue; } String command2 = new String( hdrl, i+8, 4 ); if ( "strh".equalsIgnoreCase( command ) ) { lastTagID = 0; if ( "vids".equalsIgnoreCase( command2 ) ) { String compressor = new String( hdrl, i+12, 4); int scale = str2ulong( hdrl, i+28 ); int rate = str2ulong( hdrl, i+32 ); track[0] = new Video( this, streamNumber++, compressor, scale, rate ); streamVideoTag = ((Video)track[0]).getVideoTag(); lastTagID = 1; } if ( "auds".equalsIgnoreCase( command2 ) ) { int scale = str2ulong( hdrl, i+28); int rate = str2ulong( hdrl, i+32); int sampleSize = str2ulong( hdrl, i+52); track[1+numberOfAudioChannels++] = new Audio( this, streamNumber++, scale, rate, sampleSize ); lastTagID = 2; } } if ( "strf".equalsIgnoreCase( command ) ) { if ( lastTagID == 1 ) { /** * Video information */ byte[] information = new byte[ size - 4 ]; System.arraycopy( hdrl, i + 4, information, 0, information.length ); track[0].setBih( information ); } if ( lastTagID == 2 ) { /** * Audio information */ byte[] information = new byte[ size - 4 ]; System.arraycopy( hdrl, i + 4, information, 0, information.length ); track[ 1 + numberOfAudioChannels - 1].setBih( information ); } } i += size + 8; } endOfHeader = seekSource.tell(); } long endOfHeader; public synchronized long readFrame( Buffer buffer, boolean video, long position ) throws IOException { boolean isVideo; if ( position < endOfHeader ) position = endOfHeader; seekSource.seek( position ); do { isVideo = getChunk( buffer ); /** * Skip padding */ if ( (buffer.getLength() & 1) == 1 ) readBuffer( 1 ); } while ( isVideo != video ); return seekSource.tell(); } private String streamVideoTag; private boolean getChunk( Buffer output ) throws IOException { String command = new String( readBuffer( 4 ), "ASCII" ).toUpperCase(); int size = readBytes(4); /** * Skip LIST and RIFF */ while ( "LIST".equals( command ) || "RIFF".equals( command ) ) { readBuffer( 4 ); command = new String( readBuffer( 4 ), "ASCII" ).toUpperCase(); size = readBytes(4); } /** * Look for ##db ##dc ##wb [video] */ String videoTag = streamVideoTag.substring(0, 3); if ( command.substring(0, 3).equalsIgnoreCase( videoTag ) && (command.charAt(3) == 'B' || command.charAt(3) == 'C') ) { /** * Video */ output.setData( readBuffer(size) ); output.setLength( size ); return true; } /** * Match Audio strings */ for ( int i = 0; i < numberOfAudioChannels; i++ ) { // if ( command.equalsIgnoreCase( audio[i].getAudioTag() ) ) { /** * Audio */ output.setData( readBuffer(size) ); output.setLength( size ); return false; // } } throw new IOException( "Not header " + command ); } /** * str2ulong */ public static final int str2ulong( byte[] data, int i ) { return (data[ i ] & 0xff) | ((data[ i + 1 ] & 0xff) << 8 ) | ((data[ i + 2 ] & 0xff) << 16 ) | ((data[ i + 3 ] & 0xff) << 24 ); } /** * Read a byte array */ private final byte[] readBuffer( int size ) throws IOException { byte[] buffer = new byte[ size ]; int read = 0; while ( read < size ) { int next = dataSource.read( buffer, read, size - read ); if ( next < 0 ) throw new IOException( "End of Stream" ); read += next; } return buffer; } /** * Read unsigned byte */ private final int readByte() throws IOException { byte[] data = new byte[ 1 ]; dataSource.read( data, 0, 1 ); return data[0]; } /** * Read up to 4 bytes */ private final int readBytes( int number ) throws IOException { byte[] buffer = new byte[ number ]; int read = dataSource.read( buffer, 0, number ); if ( read != buffer.length ) { if ( read < 0 ) throw new IOException( "End of Stream" ); for ( int i = read; i < buffer.length; i++ ) buffer[ i ] = (byte)readByte(); } /** * Create integer */ switch ( number ) { case 1: return (buffer[ 0 ] & 0xff); case 2: return (buffer[ 0 ] & 0xff) | ((buffer[ 1 ] & 0xff) << 8); case 3: return (buffer[ 0 ] & 0xff) | ((buffer[ 1 ] & 0xff) << 8) | ((buffer[ 2 ] & 0xff) << 16); case 4: return (buffer[ 0 ] & 0xff) | ((buffer[ 1 ] & 0xff) << 8) | ((buffer[ 2 ] & 0xff) << 16) | ((buffer[ 3 ] & 0xff) << 24); default: throw new IOException( "Illegal Read quantity" ); } } public void stop() { } } abstract class AviTrack implements Track, GPLLicense { protected AviDemux demux; public AviTrack( AviDemux demux ) { this.demux = demux; } public Time getDuration() { return new Time( 1000 ); } public Time getStartTime() { return new Time( 2000 ); } public boolean isEnabled() { return true; } public Time mapFrameToTime(int param) { return new Time( param ); } public int mapTimeToFrame(javax.media.Time time) { return 0; } private long pos; /** * Supply a frame of data to codec */ public void readFrame(Buffer outputBuffer) { // System.out.println( "Read Frame" ); try { pos = demux.readFrame( outputBuffer, isVideo(), pos ); } catch (IOException e) {} } public void setEnabled(boolean enabled) { } public void setTrackListener(TrackListener trackListener) { } protected byte[] bih; /** * See buffer.h/xine_bmiheader */ public void setBih( byte[] bih ) { this.bih = bih; } public abstract boolean isVideo(); } class Video extends AviTrack implements GPLLicense { private int streamNumber; private String compressor; private int scale; private int rate; public Video( AviDemux demux, int streamNumber, String compressor, int scale, int rate ) { super( demux ); this.streamNumber = streamNumber; this.compressor = compressor; this.scale = scale; this.rate = rate; } public boolean isVideo() { return true; } public String toString() { return "Stream: " + streamNumber + " Compressor " + compressor + " Scale " + scale + " Rate " + rate; } public int getWidth() { return AviDemux.str2ulong( bih, 8 ); } public int getHeight() { return AviDemux.str2ulong( bih, 12 ); } public String getVideoTag() { return new String( new char[] { (char)((streamNumber / 10) + '0'), (char)((streamNumber % 10) + '0'), 'd', 'b' } ); } public Format getFormat() { // System.out.println( toString() ); return new VideoFormat( compressor, new Dimension(getWidth(), getHeight()), 10000, (new byte[0]).getClass(), ((float)rate)/100 ); } /** * Also known as biClrUsed */ public int getPaletteCount() { return AviDemux.str2ulong( bih, 32 ); } } class Audio extends AviTrack implements GPLLicense { private int streamNumber; private int scale; private int rate; private int sampleSize; public Audio( AviDemux demux, int streamNumber, int scale, int rate, int sampleSize ) { super( demux ); this.streamNumber = streamNumber; this.scale = scale; this.rate = rate; this.sampleSize = sampleSize; } public boolean isVideo() { return false; } public String getAudioTag() { return new String( new char[] { (char)((streamNumber / 10) + '0'), (char)((streamNumber % 10) + '0'), 'w', 'b' } ); } public String toString() { return "Stream: " + streamNumber + " Audio Rate " + rate + " Scale " + scale; } public Format getFormat() { // System.out.println( toString() ); return new AudioFormat( "mpeglayer3", 24000, 16, 2, 0, 1); // endian, int signed } }