opsu-dance/src/org/newdawn/slick/openal/Mp3InputStream.java
Jeffrey Han 20d40dd520 Better handling of misnamed MP3/OGG files. (part of #120)
This also catches more MP3 loading errors that could occur and properly cleans up resources.

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
2015-08-26 09:29:28 -05:00

217 lines
5.9 KiB
Java

/*
* Copyright (c) 2013, Slick2D
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the Slick2D nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.newdawn.slick.openal;
import java.io.IOException;
import java.io.InputStream;
import javazoom.jl.decoder.Bitstream;
import javazoom.jl.decoder.BitstreamException;
import javazoom.jl.decoder.Decoder;
import javazoom.jl.decoder.DecoderException;
import javazoom.jl.decoder.Header;
import javazoom.jl.decoder.SampleBuffer;
import org.newdawn.slick.util.Log;
/**
* An input stream that can extract MP3 data.
*
* @author fluddokt (https://github.com/fluddokt)
*/
public class Mp3InputStream extends InputStream implements AudioInputStream {
/** The MPEG audio bitstream. */
private Bitstream bitstream;
/** The MPEG decoder. */
private Decoder decoder;
/** The frame header extractor. */
private Header header;
/** The buffer. */
private SampleBuffer buf;
/** The number of channels. */
private int channels;
/** The sample rate. */
private int sampleRate;
/** The buffer length. */
private int bufLen = 0;
/** True if we've reached the end of the available data. */
private boolean endOfStream = false;
/** The byte position. */
private int bpos;
/**
* Create a new stream to decode MP3 data.
* @param input the input stream from which to read the MP3 file
* @throws IOException failure to read the header from the input stream
*/
public Mp3InputStream(InputStream input) throws IOException {
decoder = new Decoder();
bitstream = new Bitstream(input);
try {
header = bitstream.readFrame();
} catch (BitstreamException e) {
Log.error(e);
}
if (header == null) {
close();
throw new IOException("Failed to read header from MP3 input stream.");
}
channels = (header.mode() == Header.SINGLE_CHANNEL) ? 1 : 2;
sampleRate = header.frequency();
buf = new SampleBuffer(sampleRate, channels);
decoder.setOutputBuffer(buf);
try {
decoder.decodeFrame(header, bitstream);
} catch (DecoderException e) {
Log.error(e);
}
bufLen = buf.getBufferLength();
bitstream.closeFrame();
}
@Override
public int read() throws IOException {
if (atEnd())
return -1;
while (bpos / 2 >= bufLen) {
try {
header = bitstream.readFrame();
if (header == null) {
buf.clear_buffer();
endOfStream = true;
return -1;
}
buf.clear_buffer();
decoder.decodeFrame(header, bitstream);
bufLen = buf.getBufferLength();
bitstream.closeFrame();
} catch (DecoderException | BitstreamException e) {
Log.error(e);
}
bpos = 0;
}
int npos = bpos / 2;
bpos++;
if (bpos % 2 == 0)
return (buf.getBuffer()[npos] >> 8) & 0xff;
else
return (buf.getBuffer()[npos]) & 0xff;
}
@Override
public boolean atEnd() { return endOfStream; }
@Override
public int getChannels() { return channels; }
@Override
public int getRate() { return sampleRate; }
@Override
public int read(byte[] b, int off, int len) throws IOException {
for (int i = 0; i < len; i++) {
try {
int value = read();
if (value >= 0)
b[i] = (byte) value;
else
return (i == 0) ? -1 : i;
} catch (IOException e) {
Log.error(e);
return i;
}
}
return len;
}
@Override
public int read(byte[] b) throws IOException { return read(b, 0, b.length); }
@Override
public long skip(long length) {
if (bufLen <= 0)
Log.warn("Mp3InputStream: skip: bufLen not yet determined.");
int skipped = 0;
while (skipped + bufLen * 2 < length) {
try {
header = bitstream.readFrame();
if (header == null) {
// Log.warn("Mp3InputStream: skip: header is null.");
endOfStream = true;
return -1;
}
// last frame that won't be skipped so better read it
if (skipped + bufLen * 2 * 4 >= length || bufLen <= 0) {
buf.clear_buffer();
decoder.decodeFrame(header, bitstream);
bufLen = buf.getBufferLength();
}
skipped += bufLen * 2 - bpos;
bitstream.closeFrame();
bpos = 0;
} catch (BitstreamException | DecoderException e) {
Log.error(e);
}
}
if (bufLen * 2 - bpos > length - skipped) {
bpos += length - skipped;
skipped += length - skipped;
}
return skipped;
}
@Override
public void close() throws IOException {
try {
bitstream.close();
} catch (BitstreamException e) {
e.printStackTrace();
}
}
}