diff --git a/README.md b/README.md
index eb4cae5..9e1f95f 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,9 @@
-
-
-
# JavaMP3
+This is a fork of [delthas' Java MP3 decoding library](https://github.com/delthas/JavaMP3), incorporating fixes by [josephx86](https://github.com/josephx86/JavaMP3), [GlaDOSik](https://github.com/GlaDOSik/JavaMP3) as well as myself.
+
+The build from this repository is the basis for the MP3 decoding dependency that is shipped with the [Processing Sound library](https://github.com/processing/processing-sound).
+
**Currently supports MPEG-1 Layer I/II/III (that is, most MP1, MP2, and MP3 files)**
## Introduction
@@ -16,18 +17,11 @@ This API lets you:
## Install
-JavaMP3 requires Java >= 8 to run. You can get this library using Maven by adding this to your ```pom.xml```:
+JavaMP3 requires Java >= 8 to run.
-```xml
-
-
- fr.delthas
- javamp3
- 1.0.1
-
-
-```
+* For the latest built jars of this fork, check this repository's [releases](https://github.com/kevinstadler/JavaMP3/releases) page.
+* delthas' original version of the library (v 1.0.1) is also available from Maven, see the instructions [here](https://github.com/delthas/JavaMP3#install).
## Quick example
diff --git a/pom.xml b/pom.xml
index d62ccae..e5ff861 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,16 +3,16 @@
4.0.0
fr.delthas
javamp3
- 1.0.1
+ 1.0.4
JavaMP3
- https://github.com/delthas/javamp3
- A fast and lightweight MP3 decoding library
+ https://github.com/kevinstadler/JavaMP3
+ A fork of delthas' Java MP3 decoding library
2017
GitHub
- https://github.com/delthas/javamp3/issues
+ https://github.com/kevinstadler/JavaMP3/issues
@@ -35,23 +35,6 @@
-
- scm:git:git@github.com:delthas/javamp3.git
- scm:git:git@github.com:delthas/javamp3.git
- git@github.com:delthas/javamp3.git
-
-
-
-
- ossrh
- https://oss.sonatype.org/content/repositories/snapshots
-
-
- ossrh
- https://oss.sonatype.org/service/local/staging/deploy/maven2/
-
-
-
junit
diff --git a/src/main/java/fr/delthas/javamp3/Decoder.java b/src/main/java/fr/delthas/javamp3/Decoder.java
index 4c8815e..0d6f5e1 100644
--- a/src/main/java/fr/delthas/javamp3/Decoder.java
+++ b/src/main/java/fr/delthas/javamp3/Decoder.java
@@ -9,7 +9,7 @@ final class Decoder {
private static final int[] BITRATE_LAYER_II = {0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000};
private static final int[] BITRATE_LAYER_III = {0, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000};
private static final int[] SAMPLING_FREQUENCY = {44100, 48000, 32000};
- private static final float[] SCALEFACTORS = {2.00000000000000f, 1.58740105196820f, 1.25992104989487f, 1.00000000000000f, 0.79370052598410f, 0.62996052494744f, 0.50000000000000f, 0.39685026299205f, 0.31498026247372f, 0.25000000000000f, 0.19842513149602f, 0.15749013123686f, 0.12500000000000f, 0.09921256574801f, 0.07874506561843f, 0.06250000000000f, 0.04960628287401f, 0.03937253280921f, 0.03125000000000f, 0.02480314143700f, 0.01968626640461f, 0.01562500000000f, 0.01240157071850f, 0.00984313320230f, 0.00781250000000f, 0.00620078535925f, 0.00492156660115f, 0.00390625000000f, 0.00310039267963f, 0.00246078330058f, 0.00195312500000f, 0.00155019633981f, 0.00123039165029f, 0.00097656250000f, 0.00077509816991f, 0.00061519582514f, 0.00048828125000f, 0.00038754908495f, 0.00030759791257f, 0.00024414062500f, 0.00019377454248f, 0.00015379895629f, 0.00012207031250f, 0.00009688727124f, 0.00007689947814f, 0.00006103515625f, 0.00004844363562f, 0.00003844973907f, 0.00003051757813f, 0.00002422181781f, 0.00001922486954f, 0.00001525878906f, 0.00001211090890f, 0.00000961243477f, 0.00000762939453f, 0.00000605545445f, 0.00000480621738f, 0.00000381469727f, 0.00000302772723f, 0.00000240310869f, 0.00000190734863f, 0.00000151386361f, 0.00000120155435f};
+ private static final float[] SCALEFACTORS = {2.00000000000000f, 1.58740105196820f, 1.25992104989487f, 1.00000000000000f, 0.79370052598410f, 0.62996052494744f, 0.50000000000000f, 0.39685026299205f, 0.31498026247372f, 0.25000000000000f, 0.19842513149602f, 0.15749013123686f, 0.12500000000000f, 0.09921256574801f, 0.07874506561843f, 0.06250000000000f, 0.04960628287401f, 0.03937253280921f, 0.03125000000000f, 0.02480314143700f, 0.01968626640461f, 0.01562500000000f, 0.01240157071850f, 0.00984313320230f, 0.00781250000000f, 0.00620078535925f, 0.00492156660115f, 0.00390625000000f, 0.00310039267963f, 0.00246078330058f, 0.00195312500000f, 0.00155019633981f, 0.00123039165029f, 0.00097656250000f, 0.00077509816991f, 0.00061519582514f, 0.00048828125000f, 0.00038754908495f, 0.00030759791257f, 0.00024414062500f, 0.00019377454248f, 0.00015379895629f, 0.00012207031250f, 0.00009688727124f, 0.00007689947814f, 0.00006103515625f, 0.00004844363562f, 0.00003844973907f, 0.00003051757813f, 0.00002422181781f, 0.00001922486954f, 0.00001525878906f, 0.00001211090890f, 0.00000961243477f, 0.00000762939453f, 0.00000605545445f, 0.00000480621738f, 0.00000381469727f, 0.00000302772723f, 0.00000240310869f, 0.00000190734863f, 0.00000151386361f, 0.00000120155435f, 0.0f};
private static final int[] SCALEFACTOR_SIZES_LAYER_III = {
0, 0, 0, 1, 0, 2, 0, 3,
3, 0, 1, 1, 1, 2, 1, 3,
@@ -425,63 +425,92 @@ private Decoder() {
}
public static SoundData init(InputStream in) throws IOException {
- SoundData soundData = new SoundData();
- soundData.buffer = new Buffer(in);
- soundData.buffer.lastByte = soundData.buffer.in.read();
- if(!decodeFrame(soundData))
+ Buffer buffer = new Buffer(in);
+
+ while (buffer.lastByte != -1) {
+ SoundData soundData = new SoundData();
+ soundData.buffer = buffer;
+ try {
+ if (Decoder.decodeFrame(soundData)) {
+ // require directly adjacent second frame (actually allow up to two bytes
+ // away because of some quirks with Layer III decoding)
+ FrameHeader adjacentHeader = Decoder.findNextHeader(soundData, 1);
+ if (adjacentHeader != null) {
+ // found valid adjacent header: unwind inputstream to before the second header
+ adjacentHeader.unRead(soundData);
+ return soundData;
+ }
+ }
+ // exception during decoding: just try again from wherever the last
+ // decoder read blew up (on the next header read attempt the FrameHeader
+ // class will reset the pointer to the beginning of the byte)
+ // TODO it's possible that the failed frame decoding actually read past
+ // the true frame header, thus skipping the frame altogether. to counter
+ // such cases we'd need to implement another mark()/reset() pair before
+ // the main body of decodeFrame()
+ } catch (NegativeArraySizeException e) {
+ } catch (NullPointerException e) {
+ } catch (ArrayIndexOutOfBoundsException e) {
+ }
+ }
+ return null;
+ }
+
+ private static FrameHeader findNextHeader(SoundData soundData) {
+ return Decoder.findNextHeader(soundData, Integer.MAX_VALUE);
+ }
+
+ private static FrameHeader findNextHeader(SoundData soundData, int maxBytesSkipped) {
+ // read header
+ try {
+ FrameHeader header = new FrameHeader(soundData);
+
+ int skipped = 0;
+ while (!header.isValid()) {
+ if (soundData.buffer.lastByte == -1 || skipped >= maxBytesSkipped) {
+ return null;
+ }
+ skipped++;
+ soundData.buffer.in.reset();
+ // skip to next byte
+ soundData.buffer.lastByte = soundData.buffer.in.read();
+ header.set(soundData);
+ }
+ return header;
+
+ } catch (IOException e) {
+ // read error or EOF
return null;
- return soundData;
+ }
}
+
public static boolean decodeFrame(SoundData soundData) throws IOException {
if (soundData.buffer.lastByte == -1) {
return false;
}
- while (true) {
- int read;
- do {
- read = soundData.buffer.lastByte;
- soundData.buffer.lastByte = soundData.buffer.in.read();
- if (soundData.buffer.lastByte == -1) {
- return false;
- }
- } while (read != 0b11111111);
- if ((soundData.buffer.lastByte >>> 4) != 0b1111) {
- soundData.buffer.lastByte = soundData.buffer.in.read();
- if (soundData.buffer.lastByte == -1) {
- return false;
- }
- } else {
- break;
- }
+ FrameHeader header = Decoder.findNextHeader(soundData);
+ if (header == null) {
+ return false;
}
-
- soundData.buffer.current = 4;
-
- int id = read(soundData.buffer, 1);
- int layer = read(soundData.buffer, 2);
- int protectionBit = read(soundData.buffer, 1);
- int bitrateIndex = read(soundData.buffer, 4);
- int samplingFrequency = read(soundData.buffer, 2);
- int paddingBit = read(soundData.buffer, 1);
- int privateBit = read(soundData.buffer, 1);
- int mode = read(soundData.buffer, 2);
- int modeExtension = read(soundData.buffer, 2);
- read(soundData.buffer, 4);
+
+// if (header.bitrateIndex == 0) {
+// System.err.println("MP3 decoder warning: files with free bitrate not supported");
+// }
if (soundData.frequency == -1) {
- soundData.frequency = SAMPLING_FREQUENCY[samplingFrequency];
+ soundData.frequency = SAMPLING_FREQUENCY[header.samplingFrequency];
}
if (soundData.stereo == -1) {
- if (mode == 0b11 /* single_channel */) {
+ if (header.mode == 0b11 /* single_channel */) {
soundData.stereo = 0;
} else {
soundData.stereo = 1;
}
- if (layer == 0b01 /* layer III */) {
- if (mode == 0b11 /* single_channel */) {
+ if (header.layer == 0b01 /* layer III */) {
+ if (header.mode == 0b11 /* single_channel */) {
soundData.mainData = new byte[1024];
soundData.store = new float[32 * 18];
soundData.v = new float[1024];
@@ -492,7 +521,7 @@ public static boolean decodeFrame(SoundData soundData) throws IOException {
}
soundData.mainDataReader = new MainDataReader(soundData.mainData);
} else {
- if (mode == 0b11 /* single_channel */) {
+ if (header.mode == 0b11 /* single_channel */) {
soundData.synthOffset = new int[]{64};
soundData.synthBuffer = new float[1024];
} else {
@@ -502,54 +531,53 @@ public static boolean decodeFrame(SoundData soundData) throws IOException {
}
}
- int bound = modeExtension == 0b0 ? 4 : modeExtension == 0b01 ? 8 : modeExtension == 0b10 ? 12 : modeExtension == 0b11 ? 16 : -1;
+ int bound = header.modeExtension == 0b0 ? 4 : header.modeExtension == 0b01 ? 8 : header.modeExtension == 0b10 ? 12 : header.modeExtension == 0b11 ? 16 : -1;
- if (protectionBit == 0) {
+ if (header.protectionBit == 0) {
// TODO CRC CHECK
read(soundData.buffer, 16);
}
- if (layer == 0b11 /* layer I */) {
+ if (header.layer == 0b11 /* layer I */) {
float[] sampleDecoded = null;
- if (mode == 0b11 /* single_channel */) {
+ if (header.mode == 0b11 /* single_channel */) {
sampleDecoded = samples_I(soundData.buffer, 1, -1);
- } else if (mode == 0b0 /* stereo */ || mode == 0b10 /* dual_channel */) {
+ } else if (header.mode == 0b0 /* stereo */ || header.mode == 0b10 /* dual_channel */) {
sampleDecoded = samples_I(soundData.buffer, 2, -1);
- } else if (mode == 0b01 /* intensity_stereo */) {
+ } else if (header.mode == 0b01 /* intensity_stereo */) {
sampleDecoded = samples_I(soundData.buffer, 2, bound);
}
- if (mode == 0b11 /* single_channel */) {
+ if (header.mode == 0b11 /* single_channel */) {
synth(soundData, sampleDecoded, soundData.synthOffset, soundData.synthBuffer, 1);
} else {
synth(soundData, sampleDecoded, soundData.synthOffset, soundData.synthBuffer, 2);
}
- } else if (layer == 0b10 /* layer II */) {
+ } else if (header.layer == 0b10 /* layer II */) {
float[] sampleDecoded = null;
- int bitrate = BITRATE_LAYER_II[bitrateIndex];
- if (mode == 0b11 /* single_channel */) {
+ int bitrate = BITRATE_LAYER_II[header.bitrateIndex];
+ if (header.mode == 0b11 /* single_channel */) {
sampleDecoded = samples_II(soundData.buffer, 1, -1, bitrate, soundData.frequency);
- } else if (mode == 0b0 /* stereo */ || mode == 0b10 /* dual_channel */) {
+ } else if (header.mode == 0b0 /* stereo */ || header.mode == 0b10 /* dual_channel */) {
sampleDecoded = samples_II(soundData.buffer, 2, -1, bitrate, soundData.frequency);
- } else if (mode == 0b01 /* intensity_stereo */) {
+ } else if (header.mode == 0b01 /* intensity_stereo */) {
sampleDecoded = samples_II(soundData.buffer, 2, bound, bitrate, soundData.frequency);
}
- if (mode == 0b11 /* single_channel */) {
+ if (header.mode == 0b11 /* single_channel */) {
synth(soundData, sampleDecoded, soundData.synthOffset, soundData.synthBuffer, 1);
} else {
synth(soundData, sampleDecoded, soundData.synthOffset, soundData.synthBuffer, 2);
}
- } else if (layer == 0b01 /* layer III */) {
- int frameSize = (144 * BITRATE_LAYER_III[bitrateIndex]) / SAMPLING_FREQUENCY[samplingFrequency] + paddingBit;
+ } else if (header.layer == 0b01 /* layer III */) {
+ int frameSize = (144 * BITRATE_LAYER_III[header.bitrateIndex]) / SAMPLING_FREQUENCY[header.samplingFrequency] + header.paddingBit;
if (frameSize > 2000) {
System.err.println("Frame too large! " + frameSize);
}
- samples_III(soundData.buffer, soundData.stereo == 1 ? 2 : 1, soundData.mainDataReader, frameSize, samplingFrequency, mode, modeExtension, soundData.store, soundData.v, soundData);
+ samples_III(soundData.buffer, soundData.stereo == 1 ? 2 : 1, soundData.mainDataReader, frameSize, header.samplingFrequency, header.mode, header.modeExtension, soundData.store, soundData.v, soundData);
}
if (soundData.buffer.current != 0) {
read(soundData.buffer, 8 - soundData.buffer.current);
}
-
return true;
}
@@ -1737,7 +1765,7 @@ private static void synth(SoundData soundData, float[] samples, int[] synthOffse
private static int read(MainDataReader reader, int bits) {
int number = 0;
while (bits > 0) {
- int advance = Integer.min(bits, 8 - reader.current);
+ int advance = Math.min(bits, 8 - reader.current);
bits -= advance;
reader.current += advance;
number |= ((reader.array[reader.index] >>> (8 - reader.current)) & (0xFF >>> (8 - advance))) << bits;
@@ -1752,7 +1780,7 @@ private static int read(MainDataReader reader, int bits) {
private static int read(Buffer buffer, int bits) throws IOException {
int number = 0;
while (bits > 0) {
- int advance = Integer.min(bits, 8 - buffer.current);
+ int advance = Math.min(bits, 8 - buffer.current);
bits -= advance;
buffer.current += advance;
if (bits != 0 && buffer.lastByte == -1) {
@@ -1786,6 +1814,62 @@ private static void readInto(Buffer buffer, byte[] array, int offset, int length
buffer.lastByte = buffer.in.read();
}
+ private static final class FrameHeader {
+
+ int sigBytes;
+ int version;
+ int layer;
+ int protectionBit;
+ int bitrateIndex;
+ int samplingFrequency;
+ int paddingBit;
+ int privateBit;
+ int mode;
+ int modeExtension;
+
+ private FrameHeader(SoundData soundData) throws IOException {
+ this.set(soundData);
+ }
+
+ private void set(SoundData soundData) throws IOException {
+ // previously aborted data reads might have left the Buffer off a byte
+ // boundary, so reset back to reading from the beginning of the byte
+ soundData.buffer.current = 0;
+ // read 4 byte header with possibility of rollback
+ soundData.buffer.in.mark(4);
+ try {
+ this.sigBytes = read(soundData.buffer, 12);
+ this.version = read(soundData.buffer, 1);
+ this.layer = read(soundData.buffer, 2);
+ this.protectionBit = read(soundData.buffer, 1);
+ this.bitrateIndex = read(soundData.buffer, 4);
+ this.samplingFrequency = read(soundData.buffer, 2);
+ this.paddingBit = read(soundData.buffer, 1);
+ this.privateBit = read(soundData.buffer, 1);
+ this.mode = read(soundData.buffer, 2);
+ this.modeExtension = read(soundData.buffer, 2);
+ // last 4 bits ignored
+ read(soundData.buffer, 4);
+ } catch (EOFException e) {
+ // not enough data for a full header, so just mark header as invalid
+ this.sigBytes = 0;
+ }
+ }
+
+ private void unRead(SoundData soundData) throws IOException {
+ soundData.buffer.in.reset();
+ soundData.buffer.lastByte = this.sigBytes >>> 4;
+ }
+
+ private boolean isValid() {
+ return this.sigBytes == 0b111111111111 &&
+ // version currently ignored, even though decoder only supports MPEG V1 (version == 1)
+ this.layer != 0b0 &&
+ this.bitrateIndex != 0b1111 &&
+ this.samplingFrequency != 0b11;
+ }
+ }
+
private static final class MainDataReader {
public final byte[] array;
public int top = 0;
@@ -1802,8 +1886,9 @@ private static final class Buffer {
public int current = 0;
public int lastByte = -1;
- public Buffer(InputStream inputStream) {
- in = inputStream;
+ public Buffer(InputStream inputStream) throws IOException {
+ this.in = inputStream;
+ this.lastByte = this.in.read();
}
}
diff --git a/src/main/java/fr/delthas/javamp3/Sound.java b/src/main/java/fr/delthas/javamp3/Sound.java
index e0983f1..49083a2 100644
--- a/src/main/java/fr/delthas/javamp3/Sound.java
+++ b/src/main/java/fr/delthas/javamp3/Sound.java
@@ -225,7 +225,7 @@ public int decodeFullyInto(OutputStream os) throws IOException {
os.write(soundData.samplesBuffer, index, remaining);
}
int read = remaining;
- while(!Decoder.decodeFrame(soundData)) {
+ while(Decoder.decodeFrame(soundData)) {
os.write(soundData.samplesBuffer);
read += soundData.samplesBuffer.length;
}