/* * Java port of ffmpeg mp3 decoder. * 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.codecs.audio.mpeg.mp3; import java.awt.Dimension; import javax.media.Codec; import javax.media.Format; import javax.media.format.AudioFormat; import javax.media.Buffer; import net.sourceforge.jffmpeg.JMFCodec; import net.sourceforge.jffmpeg.codecs.utils.BitStream; import net.sourceforge.jffmpeg.codecs.utils.FFMpegException; import net.sourceforge.jffmpeg.codecs.audio.mpeg.mp3.data.Table; import java.io.*; /** * Mp3 Codec */ public class MP3 implements Codec, JMFCodec { public static final boolean debug = false; public final static int MPA_STEREO = 0; public final static int MPA_JSTEREO = 1; public final static int MPA_DUAL = 2; public final static int MPA_MONO = 3; public final static int MODE_EXT_I_STEREO = 1; public final static int MODE_EXT_MS_STEREO = 2; protected BitStream in = new BitStream(); protected BitStream granuleIn = new BitStream(); /** * Expected header */ protected int headerMask = 0; protected int streamHeader = 0; /** * MP3 Packet header */ protected boolean gotHeader = false; protected boolean lsf; protected int layer; protected boolean mpeg25; protected boolean error_protection; protected int bitrate_index; protected int sample_rate_index; protected boolean padding; protected boolean extension; protected int mode; protected int mode_ext; protected boolean copyright; protected boolean original; protected int emphasis; protected int frame_size; protected int bitRate; /** * ID3 headers */ public static int ID3 = 0x494433; public static int LAME = 0x4c414d45; private boolean lameFile; /** * Internal variables */ protected int nb_channels; protected int[][][] mpa_bitrate_tab = Table.getBitrateTable(); protected int[] mpa_freq_tab = new int[] { 44100, 48000, 32000 }; protected Granule[][] granules; /** * IMDCT **/ private int[][][] sb_samples = new int[2][2][ 18 * Granule.SBLIMIT ]; private int[][] mdct_buffer = new int[2][ Granule.SBLIMIT * 18 ]; private SoundOutput soundOutput = new SoundOutput(); /** * Search for header */ private final boolean isHeader( int header ) { return ( (header & 0xffe00000) == 0xffe00000 ) && ( (header & (3 << 17)) != 0 ) && ( (header & (15 << 12)) != 15 ) && ( (header & (3 << 10)) != 3 ); } public MP3() { granules = new Granule[ 2 ][ 2 ]; granules[ 0 ][ 0 ] = new Granule(); granules[ 0 ][ 1 ] = new Granule(); granules[ 1 ][ 0 ] = new Granule(); granules[ 1 ][ 1 ] = new Granule(); } private int decodeHeader() { int header = in.getBits( 16 ) << 16 | in.getBits( 16 ); // System.out.println( "Header " + Integer.toHexString( header ) ); /** * Header */ int sync = (header >> 21) & 0x7ff; // bits 21-31 if ( (header & (1 << 20)) != 0 ) { // bit 20 lsf = (header & (1 << 19)) == 0; // bit 19 mpeg25 = false; } else { lsf = true; mpeg25 = true; } layer = 4 - ((header >> 17) & 3); // bit 17-18 error_protection = ((header >> 16 ) & 1) == 0; // bit 16 bitrate_index = (header >> 12) & 0xf; // bit 12-15 sample_rate_index = (header >> 10) & 3; // bit 10-11 padding = ((header >> 9) & 1) != 0; // bit 9 extension = ((header >> 8) & 1) != 0; // bit 8 mode = ((header >> 6) & 3); // bit 6-7 mode_ext = ((header >> 4) & 3); // bit 4-5 copyright = ((header >> 3) & 1) != 0; // bit 3 original = ((header >> 2) & 1) != 0; // bit 2 emphasis = header & 3; // bit 0-1 /** * Calculate internal variables */ nb_channels = (mode == MPA_MONO) ? 1 : 2; if ( (((((0xffe00000 | (3 << 17) | (0xf << 12) | (3 << 10) | (3 << 19))) & currentHeader) != streamHeader ) && streamHeader != 0) || (sync != 0x7ff) || (layer == 4) || (bitrate_index == 0xf) || (sample_rate_index == 3) ) { //Should code this :) in.seek( (in.getPos() - 24) & ~0x07 ); while ( in.availableBits() > 32 ) { if (in.showBits( 11 ) ==0x7ff ) { break; } in.getBits( 8 ); } return -1; /* if ( (header & 0xffffff00) == ID3 || lameFile ) { /* Skip into file * in.seek( in.getPos() + 0x500 ); } /* lameFile = true; /* Find LAME * while ( header != LAME || ((header & 0xff000000)==0x55000000)||((header & 0xff000000)==0x00000000)) { if ( in.availableBits() < 32 ) return -1; in.seek( (in.getPos() - 24) & ~0x07 ); header = in.getBits( 16 ) << 16 | in.getBits( 16 ); } } */ // throw new Error( "Not suitable header" ); } if ( debug ) { System.out.println( "Decoded header..." ); System.out.println( "layer: " + layer ); System.out.println( "nb_channels: " + nb_channels ); System.out.println( "lsf: " + (lsf? 1:0) ); } streamHeader = currentHeader & (0xffe00000 | (3 << 17) | (0xf << 12) | (3 << 10) | (3 << 19)); /* Calculate frame size */ if ( bitrate_index != 0 ) { int sample_rate = mpa_freq_tab[ sample_rate_index ] >> ((lsf?1:0) + (mpeg25?1:0)); sample_rate_index += 3*((lsf?1:0) + (mpeg25?1:0)); frame_size = mpa_bitrate_tab[ lsf ? 1:0 ][ layer - 1 ][ bitrate_index ]; bitRate = frame_size * 1000; switch( layer ) { case 1: { frame_size = (frame_size * 12000) / sample_rate; frame_size = (frame_size + (padding ? 1:0) ) * 4; break; } case 2: { frame_size = (frame_size * 144000) / sample_rate; frame_size += padding ? 1:0; break; } default: case 3: { frame_size = (frame_size * 144000) / ( sample_rate << (lsf?1:0)); frame_size += padding ? 1:0; break; } } } else { throw new Error( "Free format frame size" ); } if ( debug ) System.out.println( "Frame size " + frame_size ); return header; } private void decodeMP3( Buffer outputBuffer, int frameStart ) throws FFMpegException { if ( error_protection ) { in.getBits( 16 ); //Error handling } int main_data_begin; int private_bits; int nb_granules; if ( lsf ) { main_data_begin = in.getBits( 8 ); private_bits = in.getBits( (nb_channels == 2) ? 2 : 1 ); nb_granules = 1; } else { main_data_begin = in.getBits( 9 ); private_bits = in.getBits( (nb_channels == 2) ? 3 : 5 ); nb_granules = 2; for ( int channel = 0; channel < nb_channels; channel++ ) { granules[ channel ][ 0 ].setScfsi( 0 ); granules[ channel ][ 1 ].setScfsi( in.getBits( 4 ) ); } } if ( debug ) { System.out.println( "main_data_begin " + main_data_begin ); System.out.println( "private_bits " + private_bits ); } for ( int granuleNumber = 0; granuleNumber < nb_granules; granuleNumber++ ) { for ( int channel = 0; channel < nb_channels; channel++ ) { granules[ channel ][ granuleNumber ].read( in, lsf, mode_ext ); if ( debug ) System.out.println( granules[ channel ][ granuleNumber ] ); } } /* SEEK TO MAINDATA */ // System.out.println( "seekback: " + main_data_begin ); int seekPos = granuleIn.getPos() + granuleIn.availableBits() - main_data_begin * 8; granuleIn.seek( seekPos ); /* Add new data */ byte[] newGranuleData = in.getDataArray(); int startNewData = (in.getPos() + 7)/8; granuleIn.addData( newGranuleData, startNewData, frame_size - (startNewData - frameStart/8) ); in.seek( frameStart + frame_size * 8 ); /* Don't read data we don't yet have */ if ( seekPos < 0 ) { return; } // System.out.println( "read granules" ); for ( int granuleNumber = 0; granuleNumber < nb_granules; granuleNumber++ ) { for ( int channel = 0; channel < nb_channels; channel++ ) { granules[ channel ][ granuleNumber ].readScaleFactors( granuleIn, lsf, granules[ channel ][ 0 ], channel, mode_ext ); if (debug) granules[ channel ][ granuleNumber ].dumpScaleFactors(); if ( debug ) System.out.println( "exponents from scale factors" ); granules[ channel ][ granuleNumber ].exponents_from_scale_factors( sample_rate_index ); if ( debug ) System.out.println( "Decode Huffman" ); granules[ channel ][ granuleNumber ].huffman_decode( granuleIn, sample_rate_index ); } /* Compute Stereo */ if ( nb_channels == 2 ) { if ( debug ) System.out.println( "Compute Stereo" ); granules[ 1 ][ granuleNumber ].computeStereo( this, granules[ 0 ][ granuleNumber ] ); } for ( int channel = 0; channel < nb_channels; channel++ ) { /* Reorder block */ if ( debug ) System.out.println( "Reorder Block" ); granules[ channel ][ granuleNumber ].reorderBlock( this ); /* Compute antialias */ if ( debug ) System.out.println( "Antialias" ); granules[ channel ][ granuleNumber ].antialias( this ); if ( debug ) granules[ channel ][ granuleNumber ].dumpHybrid(); /* Compute imdct */ soundOutput.computeImdct( granules[ channel ][ granuleNumber ], sb_samples[ channel ][ granuleNumber ], mdct_buffer[ channel ] ); } } /* Synth_finter */ byte[] out = (byte[])outputBuffer.getData(); int outputPointer = outputBuffer.getLength(); if ( out == null ) { out = new byte[ 0 ]; outputPointer = 0; } if ( out.length - outputPointer < nb_channels * nb_granules * 18 * 32 * 2 ) { byte[] tmp = out; out = new byte[ 4 * ( out.length + nb_channels * nb_granules * 18 * 32 ) ]; System.arraycopy( tmp, 0, out, 0, outputPointer ); outputBuffer.setData( out ); } for ( int channel = 0; channel < nb_channels; channel++ ) { int samplePointer = outputPointer + channel * 2; for ( int frameNumber = 0; frameNumber < nb_granules * 18; frameNumber++ ) { soundOutput.synth_filter( channel, nb_channels, out, samplePointer, sb_samples[ channel ][ frameNumber / 18 ], (frameNumber % 18) * Granule.SBLIMIT ); samplePointer += 32 * nb_channels * 2; } } outputBuffer.setLength( outputPointer + nb_channels * nb_granules * 18 * 32 * 2); /* System.out.println( "Output " + (18 * nb_granules) + " " + nb_channels ); for ( int i = 0; i < nb_channels * nb_granules * 18 * 32; i++ ) { System.out.print( out[ i ] + " " ); if ( (i % 18) == 17 ) System.out.println(); } System.out.println( "end"); */ } /** * Caluclate from header */ /** * Codec management */ public Format[] getSupportedInputFormats() { if ( debug ) System.out.println( "getSupportedInputFormats" ); return new Format[] { new AudioFormat( "mpeglayer3" ) }; } public Format[] getSupportedOutputFormats(Format format) { if ( debug ) System.out.println( "getSupportedOutputFormats" ); return new Format[] { new AudioFormat( "LINEAR" ) }; } private AudioFormat inputFormat; public Format setInputFormat( Format format ) { if ( debug ) System.out.println( "Input Format: " + format ); inputFormat = (AudioFormat)format; return format; } public Format setOutputFormat( Format format ) { if ( debug ) System.out.println( "Output Format: " + format ); return new AudioFormat("LINEAR", inputFormat.getSampleRate(), inputFormat.getSampleSizeInBits() > 0 ? inputFormat.getSampleSizeInBits() : 16, inputFormat.getChannels(), 0, 1); // endian, int signed } // private OutputStream outtemp; private int currentHeader = -1; public int process( Buffer input, Buffer output ) { /* Flush buffer */ if ( (input.getFlags() & Buffer.FLAG_FLUSH) != 0 ) reset(); output.setFlags( input.getFlags() ); output.setTimeStamp( input.getTimeStamp() ); output.setDuration( input.getDuration() ); try { byte[] data = (byte[])input.getData(); //in.getLength int length = input.getLength(); /* try { outtemp.write( data, 0, length ); } catch ( IOException e ) { } */ /* System.out.println( "Parsing packet" ); for ( int i = 0; i < length; i++ ) { System.out.print( Integer.toHexString( data[i] & 0xff ) + " " ); } */ /** * Parse data */ in.addData( data, 0, length ); do { if ( currentHeader == -1 ) { /* * Read header (need 32 bits) */ if ( in.availableBits() < 32 ) break; currentHeader = decodeHeader( ); if ( currentHeader == -1 ) continue; } /* * Do we have a complete packet? */ if ( frame_size * 8 < in.availableBits() - 32 ) { decodeMP3( output, in.getPos() - 32 ); currentHeader = -1; } } while ( currentHeader == -1 && in.availableBits() >= 128); /* int[] out = (int[])output.getData(); int outLength = output.getLength(); byte[] a = new byte[ outLength * 2 ]; for ( int k = 0; k < outLength; k++) { a[ k * 2 ] = (byte)(out[k] & 0xff); a[ k * 2 + 1 ] = (byte)((out[k] & 0xff00) >> 8); } output.setData( a ); output.setLength( outLength * 2 ); */ /* outStream.write( data, 0, length ); System.out.println(); */ } catch ( Exception e ) { reset(); e.printStackTrace(); return BUFFER_PROCESSED_FAILED; } catch( Error e ) { reset(); e.printStackTrace(); currentHeader = -1; } return BUFFER_PROCESSED_OK; } public void open() { /* try { outtemp = new FileOutputStream( "MpegData.mp3" ); } catch (IOException e ) { } */ } public void close() { /* try { outtemp.close(); } catch (IOException e ) { } **/ } public void reset() { sb_samples = new int[2][2][ 18 * Granule.SBLIMIT ]; mdct_buffer = new int[2][ Granule.SBLIMIT * 18 ]; soundOutput = new SoundOutput(); in = new BitStream(); granuleIn = new BitStream(); granules[ 0 ][ 0 ] = new Granule(); granules[ 0 ][ 1 ] = new Granule(); granules[ 1 ][ 0 ] = new Granule(); granules[ 1 ][ 1 ] = new Granule(); } public String getName() { return "mpeglayer3"; } public Object[] getControls() { return new Object[ 0 ]; } public Object getControl( String type ) { return null; } /** * Implement the Jffmpeg codec interface */ public boolean isCodecAvailable() { return true; } /** * Outofbands video size */ public void setVideoSize( Dimension size ) { } public void setEncoding( String encoding ) { } public void setIsRtp( boolean isRtp ) { } public void setIsTruncated( boolean isTruncated ) { } }