Path: blob/main/src/lwjgl/java/de/cuina/fireandfuel/CodecJLayerMP3.java
8649 views
package de.cuina.fireandfuel;12/*3* CodecJLayerMP3 - an ICodec interface for Paulscode Sound System4* Copyright (C) 2012 by fireandfuel from Cuina Team (http://www.cuina.byethost12.com/)5*6* This program is free software; you can redistribute it and/or7* modify it under the terms of the GNU Lesser General Public License8* as published by the Free Software Foundation; either version 39* of the License, or (at your option) any later version.10*11* This program is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY12* KIND, either express or implied. See the GNU Lesser General Public License for more details.13*14* You should have received a copy of the GNU Lesser General Public15* License along with this program; if not, see http://www.gnu.org/licenses/lgpl.txt16*/1718import java.io.BufferedInputStream;19import java.io.IOException;20import java.net.URL;2122import javax.sound.sampled.AudioFormat;23import javax.sound.sampled.AudioInputStream;2425import javazoom.jl.decoder.Bitstream;26import javazoom.jl.decoder.Decoder;27import javazoom.jl.decoder.Header;28import javazoom.jl.decoder.Obuffer;29import javazoom.mp3spi.DecodedMpegAudioInputStream;3031import paulscode.sound.ICodec;32import paulscode.sound.SoundBuffer;33import paulscode.sound.SoundSystemConfig;34import paulscode.sound.SoundSystemLogger;3536/**37* The CodecJLayer class provides an ICodec interface to the external JLayer38* library.39*40* <b><br>41* <br>42* This software is based on or using the JLayer and mp3spi library from43* http://www.javazoom.net/javalayer/javalayer.html and Tritonus library from44* http://www.tritonus.org/.45*46* JLayer, mp3spi and Tritonus library are released under the conditions of47* GNU Library General Public License version 2 or (at your option)48* any later version of the License.49* </b><br>50*/5152public class CodecJLayerMP3 implements ICodec53{54/**55* Used to return a current value from one of the synchronized56* boolean-interface methods.57*/58private static final boolean GET = false;5960/**61* Used to set the value in one of the synchronized boolean-interface62* methods.63*/64private static final boolean SET = true;6566/**67* Used when a parameter for one of the synchronized boolean-interface68* methods is not applicable.69*/70private static final boolean XXX = false;7172/**73* True if there is no more data to read in.74*/75private boolean endOfStream = false;7677/**78* True if the stream has finished initializing.79*/80private boolean initialized = false;8182private Decoder decoder;83private Bitstream bitstream;84private DMAISObuffer buffer;8586private Header mainHeader;8788/**89* Audio format to use when playing back the wave data.90*/91private AudioFormat myAudioFormat = null;9293/**94* Input stream to use for reading in pcm data.95*/96private DecodedMpegAudioInputStream myAudioInputStream = null;9798/**99* Processes status messages, warnings, and error messages.100*/101private SoundSystemLogger logger;102103public CodecJLayerMP3()104{105logger = SoundSystemConfig.getLogger();106}107108@Override109public void reverseByteOrder(boolean b)110{111}112113@Override114public boolean initialize(URL url)115{116initialized(SET, false);117cleanup();118if(url == null)119{120errorMessage("url null in method 'initialize'");121cleanup();122return false;123}124125try126{127bitstream = new Bitstream(new BufferedInputStream(url.openStream()));128decoder = new Decoder();129130mainHeader = bitstream.readFrame();131132buffer = new DMAISObuffer(2);133decoder.setOutputBuffer(buffer);134135int channels;136if(mainHeader.mode() < 3)137channels = 2;138else channels = 1;139140bitstream.closeFrame();141bitstream.close();142143myAudioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,144mainHeader.frequency(), 16, channels, channels * 2, mainHeader.frequency(),145false);146147AudioFormat mpegAudioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, -1.0f,14816, channels, channels * 2, -1.0f, false);149150myAudioInputStream = new DecodedMpegAudioInputStream(myAudioFormat,151new AudioInputStream(new BufferedInputStream(url.openStream()),152mpegAudioFormat, -1));153myAudioInputStream.skip((int)(myAudioInputStream.getFormat().getFrameRate() * 0.018f) * myAudioInputStream.getFormat().getFrameSize());154} catch (Exception e)155{156errorMessage("Unable to set up input streams in method " + "'initialize'");157printStackTrace(e);158cleanup();159return false;160}161162if(myAudioInputStream == null)163{164errorMessage("Unable to set up audio input stream in method " + "'initialize'");165cleanup();166return false;167}168169endOfStream(SET, false);170initialized(SET, true);171return true;172}173174@Override175public boolean initialized()176{177return initialized(GET, XXX);178}179180@Override181public SoundBuffer read()182{183if(myAudioInputStream == null)184{185endOfStream(SET, true);186return null;187}188189// Get the format for the audio data:190AudioFormat audioFormat = myAudioInputStream.getFormat();191192// Check to make sure there is an audio format:193if(audioFormat == null)194{195errorMessage("Audio Format null in method 'read'");196endOfStream(SET, true);197return null;198}199200// Variables used when reading from the audio input stream:201int bytesRead = 0, cnt = 0;202203// Allocate memory for the audio data:204byte[] streamBuffer = new byte[SoundSystemConfig.getStreamingBufferSize()];205206try207{208// Read until buffer is full or end of stream is reached:209while((!endOfStream(GET, XXX)) && (bytesRead < streamBuffer.length))210{211myAudioInputStream.execute();212if((cnt = myAudioInputStream.read(streamBuffer, bytesRead, streamBuffer.length213- bytesRead)) < 0)214{215endOfStream(SET, true);216break;217}218// keep track of how many bytes were read:219bytesRead += cnt;220}221} catch (IOException ioe)222{223224/*225* errorMessage( "Exception thrown while reading from the " +226* "AudioInputStream (location #3)." ); printStackTrace( e ); return227* null;228*/// TODO: Figure out why this exceptions is being thrown at end of229// MP3 files!230endOfStream(SET, true);231return null;232} catch (ArrayIndexOutOfBoundsException e)233{234//this exception is thrown at the end of the mp3's235endOfStream(SET, true);236return null;237}238239// Return null if no data was read:240if(bytesRead <= 0)241{242endOfStream(SET, true);243return null;244}245246// Insert the converted data into a ByteBuffer:247// byte[] data = convertAudioBytes(streamBuffer,248// audioFormat.getSampleSizeInBits() == 16);249250// Wrap the data into a SoundBuffer:251SoundBuffer buffer = new SoundBuffer(streamBuffer, audioFormat);252253// Return the result:254return buffer;255}256257@Override258public SoundBuffer readAll()259{260// Check to make sure there is an audio format:261if(myAudioFormat == null)262{263errorMessage("Audio Format null in method 'readAll'");264return null;265}266267// Array to contain the audio data:268byte[] fullBuffer = null;269270// Determine how much data will be read in:271int fileSize = myAudioFormat.getChannels() * (int) myAudioInputStream.getFrameLength()272* myAudioFormat.getSampleSizeInBits() / 8;273if(fileSize > 0)274{275// Allocate memory for the audio data:276fullBuffer = new byte[myAudioFormat.getChannels()277* (int) myAudioInputStream.getFrameLength()278* myAudioFormat.getSampleSizeInBits() / 8];279int read = 0, total = 0;280try281{282// Read until the end of the stream is reached:283while((read = myAudioInputStream.read(fullBuffer, total, fullBuffer.length - total)) != -1284&& total < fullBuffer.length)285{286total += read;287}288} catch (IOException e)289{290errorMessage("Exception thrown while reading from the "291+ "AudioInputStream (location #1).");292printStackTrace(e);293return null;294}295} else296{297// Total file size unknown.298299// Variables used when reading from the audio input stream:300int totalBytes = 0, bytesRead = 0, cnt = 0;301byte[] smallBuffer = null;302303// Allocate memory for a chunk of data:304smallBuffer = new byte[SoundSystemConfig.getFileChunkSize()];305306// Read until end of file or maximum file size is reached:307while((!endOfStream(GET, XXX)) && (totalBytes < SoundSystemConfig.getMaxFileSize()))308{309bytesRead = 0;310cnt = 0;311312try313{314// Read until small buffer is filled or end of file reached:315while(bytesRead < smallBuffer.length)316{317myAudioInputStream.execute();318if((cnt = myAudioInputStream.read(smallBuffer, bytesRead,319smallBuffer.length - bytesRead)) < 0)320{321endOfStream(SET, true);322break;323}324bytesRead += cnt;325}326} catch (IOException e)327{328errorMessage("Exception thrown while reading from the "329+ "AudioInputStream (location #2).");330printStackTrace(e);331return null;332}333334// Reverse byte order if necessary:335// if( reverseBytes )336// reverseBytes( smallBuffer, 0, bytesRead );337338// Keep track of the total number of bytes read:339totalBytes += bytesRead;340341// Append the small buffer to the full buffer:342fullBuffer = appendByteArrays(fullBuffer, smallBuffer, bytesRead);343}344}345346// Insert the converted data into a ByteBuffer347// byte[] data = convertAudioBytes( fullBuffer,348// myAudioFormat.getSampleSizeInBits() == 16 );349350// Wrap the data into an SoundBuffer:351SoundBuffer soundBuffer = new SoundBuffer(fullBuffer, myAudioFormat);352353// Close the audio input stream354try355{356myAudioInputStream.close();357} catch (IOException e)358{359}360361// Return the result:362return soundBuffer;363}364365@Override366public boolean endOfStream()367{368return endOfStream(GET, XXX);369}370371@Override372public void cleanup()373{374if(myAudioInputStream != null)375try376{377myAudioInputStream.close();378} catch (Exception e)379{380}381}382383@Override384public AudioFormat getAudioFormat()385{386return myAudioFormat;387}388389/**390* Internal method for synchronizing access to the boolean 'initialized'.391*392* @param action393* GET or SET.394* @param value395* New value if action == SET, or XXX if action == GET.396* @return True if steam is initialized.397*/398private synchronized boolean initialized(boolean action, boolean value)399{400if(action == SET)401initialized = value;402return initialized;403}404405/**406* Internal method for synchronizing access to the boolean 'endOfStream'.407*408* @param action409* GET or SET.410* @param value411* New value if action == SET, or XXX if action == GET.412* @return True if end of stream was reached.413*/414private synchronized boolean endOfStream(boolean action, boolean value)415{416if(action == SET)417endOfStream = value;418return endOfStream;419}420421/**422* Reverse-orders all bytes contained in the specified array.423*424* @param buffer425* Array containing audio data.426*/427public static void reverseBytes(byte[] buffer)428{429reverseBytes(buffer, 0, buffer.length);430}431432/**433* Reverse-orders the specified range of bytes contained in the specified434* array.435*436* @param buffer437* Array containing audio data.438* @param offset439* Array index to begin.440* @param size441* number of bytes to reverse-order.442*/443public static void reverseBytes(byte[] buffer, int offset, int size)444{445446byte b;447for(int i = offset; i < (offset + size); i += 2)448{449b = buffer[i];450buffer[i] = buffer[i + 1];451buffer[i + 1] = b;452}453}454455/**456* Prints an error message.457*458* @param message459* Message to print.460*/461private void errorMessage(String message)462{463logger.errorMessage("CodecJLayerMP3", message, 0);464}465466/**467* Prints an exception's error message followed by the stack trace.468*469* @param e470* Exception containing the information to print.471*/472private void printStackTrace(Exception e)473{474logger.printStackTrace(e, 1);475}476477/**478* Creates a new array with the second array appended to the end of the479* first array.480*481* @param arrayOne482* The first array.483* @param arrayTwo484* The second array.485* @param length486* How many bytes to append from the second array.487* @return Byte array containing information from both arrays.488*/489private static byte[] appendByteArrays(byte[] arrayOne, byte[] arrayTwo, int length)490{491byte[] newArray;492if(arrayOne == null && arrayTwo == null)493{494// no data, just return495return null;496} else if(arrayOne == null)497{498// create the new array, same length as arrayTwo:499newArray = new byte[length];500// fill the new array with the contents of arrayTwo:501System.arraycopy(arrayTwo, 0, newArray, 0, length);502arrayTwo = null;503} else if(arrayTwo == null)504{505// create the new array, same length as arrayOne:506newArray = new byte[arrayOne.length];507// fill the new array with the contents of arrayOne:508System.arraycopy(arrayOne, 0, newArray, 0, arrayOne.length);509arrayOne = null;510} else511{512// create the new array large enough to hold both arrays:513newArray = new byte[arrayOne.length + length];514System.arraycopy(arrayOne, 0, newArray, 0, arrayOne.length);515// fill the new array with the contents of both arrays:516System.arraycopy(arrayTwo, 0, newArray, arrayOne.length, length);517arrayOne = null;518arrayTwo = null;519}520521return newArray;522}523524private static class DMAISObuffer extends Obuffer525{526private int m_nChannels;527private byte[] m_abBuffer;528private int[] m_anBufferPointers;529private boolean m_bIsBigEndian;530531public DMAISObuffer(int nChannels)532{533m_nChannels = nChannels;534m_abBuffer = new byte[OBUFFERSIZE * nChannels];535m_anBufferPointers = new int[nChannels];536reset();537}538539public void append(int nChannel, short sValue)540{541byte bFirstByte;542byte bSecondByte;543if(m_bIsBigEndian)544{545bFirstByte = (byte) ((sValue >>> 8) & 0xFF);546bSecondByte = (byte) (sValue & 0xFF);547} else548// little endian549{550bFirstByte = (byte) (sValue & 0xFF);551bSecondByte = (byte) ((sValue >>> 8) & 0xFF);552}553m_abBuffer[m_anBufferPointers[nChannel]] = bFirstByte;554m_abBuffer[m_anBufferPointers[nChannel] + 1] = bSecondByte;555m_anBufferPointers[nChannel] += m_nChannels * 2;556}557558public void set_stop_flag()559{560}561562public void close()563{564}565566public void write_buffer(int nValue)567{568}569570public void clear_buffer()571{572}573574public void reset()575{576for(int i = 0; i < m_nChannels; i++)577{578/*579* Points to byte location, implicitly assuming 16 bit samples.580*/581m_anBufferPointers[i] = i * 2;582}583}584}585}586587588