package sound.paulscode.codecs; /* * CodecJLayerMP3 - an ICodec interface for Paulscode Sound System * Copyright (C) 2012 by fireandfuel from Cuina Team (http://www.cuina.byethost12.com/) * * This program 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 3 * of the License, or (at your option) any later version. * * This program is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. 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 program; if not, see http://www.gnu.org/licenses/lgpl.txt */ import java.io.BufferedInputStream; import java.io.IOException; import java.net.URL; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import sound.javazoom.decoder.Bitstream; import sound.javazoom.decoder.Decoder; import sound.javazoom.decoder.Header; import sound.javazoom.decoder.Obuffer; import sound.javazoom.mp3spi.DecodedMpegAudioInputStream; import sound.paulscode.ICodec; import sound.paulscode.SoundBuffer; import sound.paulscode.SoundSystemConfig; import sound.paulscode.SoundSystemLogger; /** * The CodecJLayer class provides an ICodec interface to the external JLayer * library. * * <b><br> * <br> * This software is based on or using the JLayer and mp3spi library from * http://www.javazoom.net/javalayer/javalayer.html and Tritonus library from * http://www.tritonus.org/. * * JLayer, mp3spi and Tritonus library are released under the conditions of * GNU Library General Public License version 2 or (at your option) * any later version of the License. * </b><br> * * @author fireandfuel * * This Class was moved from package de.cuina.firefuel for organization reasons */ public class CodecJLayerMP3 implements ICodec { /** * Used to return a current value from one of the synchronized * boolean-interface methods. */ private static final boolean GET = false; /** * Used to set the value in one of the synchronized boolean-interface * methods. */ private static final boolean SET = true; /** * Used when a parameter for one of the synchronized boolean-interface * methods is not applicable. */ private static final boolean XXX = false; /** * True if there is no more data to read in. */ private boolean endOfStream = false; /** * True if the stream has finished initializing. */ private boolean initialized = false; private Decoder decoder; private Bitstream bitstream; private DMAISObuffer buffer; private Header mainHeader; /** * Audio format to use when playing back the wave data. */ private AudioFormat myAudioFormat = null; /** * Input stream to use for reading in pcm data. */ private DecodedMpegAudioInputStream myAudioInputStream = null; /** * Processes status messages, warnings, and error messages. */ private SoundSystemLogger logger; public CodecJLayerMP3() { logger = SoundSystemConfig.getLogger(); } @Override public void reverseByteOrder(boolean b) { } @Override public boolean initialize(URL url) { initialized(SET, false); cleanup(); if(url == null) { errorMessage("url null in method 'initialize'"); cleanup(); return false; } try { bitstream = new Bitstream(new BufferedInputStream(url.openStream())); decoder = new Decoder(); mainHeader = bitstream.readFrame(); buffer = new DMAISObuffer(2); decoder.setOutputBuffer(buffer); int channels; if(mainHeader.mode() < 3) channels = 2; else channels = 1; bitstream.closeFrame(); bitstream.close(); myAudioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, mainHeader.frequency(), 16, channels, channels * 2, mainHeader.frequency(), false); AudioFormat mpegAudioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, -1.0f, 16, channels, channels * 2, -1.0f, false); myAudioInputStream = new DecodedMpegAudioInputStream(myAudioFormat, new AudioInputStream(new BufferedInputStream(url.openStream()), mpegAudioFormat, -1)); } catch (Exception e) { errorMessage("Unable to set up input streams in method " + "'initialize'"); printStackTrace(e); cleanup(); return false; } if(myAudioInputStream == null) { errorMessage("Unable to set up audio input stream in method " + "'initialize'"); cleanup(); return false; } endOfStream(SET, false); initialized(SET, true); return true; } @Override public boolean initialized() { return initialized(GET, XXX); } @Override public SoundBuffer read() { if(myAudioInputStream == null) { endOfStream(SET, true); return null; } // Get the format for the audio data: AudioFormat audioFormat = myAudioInputStream.getFormat(); // Check to make sure there is an audio format: if(audioFormat == null) { errorMessage("Audio Format null in method 'read'"); endOfStream(SET, true); return null; } // Variables used when reading from the audio input stream: int bytesRead = 0, cnt = 0; // Allocate memory for the audio data: byte[] streamBuffer = new byte[SoundSystemConfig.getStreamingBufferSize()]; try { // Read until buffer is full or end of stream is reached: while((!endOfStream(GET, XXX)) && (bytesRead < streamBuffer.length)) { myAudioInputStream.execute(); if((cnt = myAudioInputStream.read(streamBuffer, bytesRead, streamBuffer.length - bytesRead)) <= 0) { endOfStream(SET, true); break; } // keep track of how many bytes were read: bytesRead += cnt; } } catch (IOException ioe) { /* * errorMessage( "Exception thrown while reading from the " + * "AudioInputStream (location #3)." ); printStackTrace( e ); return * null; */// TODO: Figure out why this exception is being thrown at end of // MP3 files! endOfStream(SET, true); return null; } catch (ArrayIndexOutOfBoundsException e) { //this exception is thrown at the end of the mp3's e.printStackTrace(); endOfStream(SET, true); return null; } // Return null if no data was read: if(bytesRead <= 0) { endOfStream(SET, true); return null; } // Insert the converted data into a ByteBuffer: // byte[] data = convertAudioBytes(streamBuffer, // audioFormat.getSampleSizeInBits() == 16); // Wrap the data into a SoundBuffer: SoundBuffer buffer = new SoundBuffer(streamBuffer, audioFormat); // Return the result: return buffer; } @Override public SoundBuffer readAll() { // Check to make sure there is an audio format: if(myAudioFormat == null) { errorMessage("Audio Format null in method 'readAll'"); return null; } // Array to contain the audio data: byte[] fullBuffer = null; // Determine how much data will be read in: int fileSize = myAudioFormat.getChannels() * (int) myAudioInputStream.getFrameLength() * myAudioFormat.getSampleSizeInBits() / 8; if(fileSize > 0) { // Allocate memory for the audio data: fullBuffer = new byte[myAudioFormat.getChannels() * (int) myAudioInputStream.getFrameLength() * myAudioFormat.getSampleSizeInBits() / 8]; int read = 0, total = 0; try { // Read until the end of the stream is reached: while((read = myAudioInputStream.read(fullBuffer, total, fullBuffer.length - total)) != -1 && total < fullBuffer.length) { total += read; } } catch (IOException e) { errorMessage("Exception thrown while reading from the " + "AudioInputStream (location #1)."); printStackTrace(e); return null; } } else { // Total file size unknown. // Variables used when reading from the audio input stream: int totalBytes = 0, bytesRead = 0, cnt = 0; byte[] smallBuffer = null; // Allocate memory for a chunk of data: smallBuffer = new byte[SoundSystemConfig.getFileChunkSize()]; // Read until end of file or maximum file size is reached: while((!endOfStream(GET, XXX)) && (totalBytes < SoundSystemConfig.getMaxFileSize())) { bytesRead = 0; cnt = 0; try { // Read until small buffer is filled or end of file reached: while(bytesRead < smallBuffer.length) { myAudioInputStream.execute(); if((cnt = myAudioInputStream.read(smallBuffer, bytesRead, smallBuffer.length - bytesRead)) <= 0) { endOfStream(SET, true); break; } bytesRead += cnt; } } catch (IOException e) { errorMessage("Exception thrown while reading from the " + "AudioInputStream (location #2)."); printStackTrace(e); return null; } // Reverse byte order if necessary: // if( reverseBytes ) // reverseBytes( smallBuffer, 0, bytesRead ); // Keep track of the total number of bytes read: totalBytes += bytesRead; // Append the small buffer to the full buffer: fullBuffer = appendByteArrays(fullBuffer, smallBuffer, bytesRead); } } // Insert the converted data into a ByteBuffer // byte[] data = convertAudioBytes( fullBuffer, // myAudioFormat.getSampleSizeInBits() == 16 ); // Wrap the data into an SoundBuffer: SoundBuffer soundBuffer = new SoundBuffer(fullBuffer, myAudioFormat); // Close the audio input stream try { myAudioInputStream.close(); } catch (IOException e) { } // Return the result: return soundBuffer; } @Override public boolean endOfStream() { return endOfStream(GET, XXX); } @Override public void cleanup() { if(myAudioInputStream != null) try { myAudioInputStream.close(); } catch (Exception e) { } } @Override public AudioFormat getAudioFormat() { return myAudioFormat; } /** * Internal method for synchronizing access to the boolean 'initialized'. * * @param action * GET or SET. * @param value * New value if action == SET, or XXX if action == GET. * @return True if steam is initialized. */ private synchronized boolean initialized(boolean action, boolean value) { if(action == SET) initialized = value; return initialized; } /** * Internal method for synchronizing access to the boolean 'endOfStream'. * * @param action * GET or SET. * @param value * New value if action == SET, or XXX if action == GET. * @return True if end of stream was reached. */ private synchronized boolean endOfStream(boolean action, boolean value) { if(action == SET) endOfStream = value; return endOfStream; } /** * Reverse-orders all bytes contained in the specified array. * * @param buffer * Array containing audio data. */ public static void reverseBytes(byte[] buffer) { reverseBytes(buffer, 0, buffer.length); } /** * Reverse-orders the specified range of bytes contained in the specified * array. * * @param buffer * Array containing audio data. * @param offset * Array index to begin. * @param size * number of bytes to reverse-order. */ public static void reverseBytes(byte[] buffer, int offset, int size) { byte b; for(int i = offset; i < (offset + size); i += 2) { b = buffer[i]; buffer[i] = buffer[i + 1]; buffer[i + 1] = b; } } /** * Prints an error message. * * @param message * Message to print. */ private void errorMessage(String message) { logger.errorMessage("CodecJLayerMP3", message, 0); } /** * Prints an exception's error message followed by the stack trace. * * @param e * Exception containing the information to print. */ private void printStackTrace(Exception e) { logger.printStackTrace(e, 1); } /** * Creates a new array with the second array appended to the end of the * first array. * * @param arrayOne * The first array. * @param arrayTwo * The second array. * @param length * How many bytes to append from the second array. * @return Byte array containing information from both arrays. */ private static byte[] appendByteArrays(byte[] arrayOne, byte[] arrayTwo, int length) { byte[] newArray; if(arrayOne == null && arrayTwo == null) { // no data, just return return null; } else if(arrayOne == null) { // create the new array, same length as arrayTwo: newArray = new byte[length]; // fill the new array with the contents of arrayTwo: System.arraycopy(arrayTwo, 0, newArray, 0, length); arrayTwo = null; } else if(arrayTwo == null) { // create the new array, same length as arrayOne: newArray = new byte[arrayOne.length]; // fill the new array with the contents of arrayOne: System.arraycopy(arrayOne, 0, newArray, 0, arrayOne.length); arrayOne = null; } else { // create the new array large enough to hold both arrays: newArray = new byte[arrayOne.length + length]; System.arraycopy(arrayOne, 0, newArray, 0, arrayOne.length); // fill the new array with the contents of both arrays: System.arraycopy(arrayTwo, 0, newArray, arrayOne.length, length); arrayOne = null; arrayTwo = null; } return newArray; } private static class DMAISObuffer extends Obuffer { private int m_nChannels; private byte[] m_abBuffer; private int[] m_anBufferPointers; private boolean m_bIsBigEndian; public DMAISObuffer(int nChannels) { m_nChannels = nChannels; m_abBuffer = new byte[OBUFFERSIZE * nChannels]; m_anBufferPointers = new int[nChannels]; reset(); } public void append(int nChannel, short sValue) { byte bFirstByte; byte bSecondByte; if(m_bIsBigEndian) { bFirstByte = (byte) ((sValue >>> 8) & 0xFF); bSecondByte = (byte) (sValue & 0xFF); } else // little endian { bFirstByte = (byte) (sValue & 0xFF); bSecondByte = (byte) ((sValue >>> 8) & 0xFF); } m_abBuffer[m_anBufferPointers[nChannel]] = bFirstByte; m_abBuffer[m_anBufferPointers[nChannel] + 1] = bSecondByte; m_anBufferPointers[nChannel] += m_nChannels * 2; } public void set_stop_flag() { } public void close() { } public void write_buffer(int nValue) { } public void clear_buffer() { } public void reset() { for(int i = 0; i < m_nChannels; i++) { /* * Points to byte location, implicitly assuming 16 bit samples. */ m_anBufferPointers[i] = i * 2; } } } }