diff --git a/AudioProcessing/lib/jogg-0.0.7.jar b/AudioProcessing/lib/jogg-0.0.7.jar new file mode 100644 index 0000000..1cbd1ad Binary files /dev/null and b/AudioProcessing/lib/jogg-0.0.7.jar differ diff --git a/AudioProcessing/lib/jorbis-0.0.15.jar b/AudioProcessing/lib/jorbis-0.0.15.jar new file mode 100644 index 0000000..4cf51f9 Binary files /dev/null and b/AudioProcessing/lib/jorbis-0.0.15.jar differ diff --git a/AudioProcessing/lib/tritonus_jorbis-0.3.6.jar b/AudioProcessing/lib/tritonus_jorbis-0.3.6.jar new file mode 100644 index 0000000..45b8fc1 Binary files /dev/null and b/AudioProcessing/lib/tritonus_jorbis-0.3.6.jar differ diff --git a/AudioProcessing/pom.xml b/AudioProcessing/pom.xml index a04339e..b864175 100644 --- a/AudioProcessing/pom.xml +++ b/AudioProcessing/pom.xml @@ -34,6 +34,27 @@ system ${basedir}/lib/mp3spi1.9.5.jar + + org.tritonus + jorbis + 0.3.6 + system + ${basedir}/lib/tritonus_jorbis-0.3.6.jar + + + com.jcraft + jogg + 0.0.7 + system + ${basedir}/lib/jogg-0.0.7.jar + + + com.jcraft + jorbis + 0.0.15 + system + ${basedir}/lib/jorbis-0.0.15.jar + org.tritonus share diff --git a/AudioProcessing/src/main/java/org/wyrez/audio/decoder/DecoderFactory.java b/AudioProcessing/src/main/java/org/wyrez/audio/decoder/DecoderFactory.java index e401c60..a023020 100644 --- a/AudioProcessing/src/main/java/org/wyrez/audio/decoder/DecoderFactory.java +++ b/AudioProcessing/src/main/java/org/wyrez/audio/decoder/DecoderFactory.java @@ -26,7 +26,7 @@ import java.io.InputStream; */ public enum DecoderFactory { - WAVE, MP3; + WAVE, MP3, OGG; /** * Creates a decoder for the selected codec. @@ -36,10 +36,12 @@ public enum DecoderFactory { */ public Decoder create(InputStream is) throws Exception { switch (this) { - case WAVE: - return new WaveDecoder(is); case MP3: return new MP3Decoder(is); + case OGG: + return new VorbisDecoder(is); + case WAVE: + return new WaveDecoder(is); default: //impossible return null; } @@ -63,6 +65,8 @@ public enum DecoderFactory { switch (extension.toUpperCase()) { case "MP3": return MP3.create(new FileInputStream(file)); + case "OGG": + return OGG.create(new FileInputStream(file)); case "WAV": case "WAVE": return WAVE.create(new FileInputStream(file)); diff --git a/AudioProcessing/src/main/java/org/wyrez/audio/decoder/MP3Decoder.java b/AudioProcessing/src/main/java/org/wyrez/audio/decoder/MP3Decoder.java index 76367e0..6948fa2 100644 --- a/AudioProcessing/src/main/java/org/wyrez/audio/decoder/MP3Decoder.java +++ b/AudioProcessing/src/main/java/org/wyrez/audio/decoder/MP3Decoder.java @@ -25,22 +25,11 @@ package org.wyrez.audio.decoder; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.PushbackInputStream; -import java.util.HashMap; -import javax.sound.sampled.AudioFileFormat; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.UnsupportedAudioFileException; -import javazoom.jl.decoder.Bitstream; -import javazoom.jl.decoder.Header; -import javazoom.spi.mpeg.sampled.file.MpegAudioFileFormat; -import javazoom.spi.mpeg.sampled.file.MpegAudioFormat; -import javazoom.spi.mpeg.sampled.file.MpegEncoding; -import javazoom.spi.mpeg.sampled.file.MpegFileFormatType; -import org.tritonus.share.TDebug; +import javazoom.spi.mpeg.sampled.file.MpegAudioFileReader; import org.tritonus.share.sampled.FloatSampleBuffer; -import org.tritonus.share.sampled.file.TAudioFileReader; /** * A decoder for MP3 files with a maximum of 2 channels. @@ -64,7 +53,7 @@ public class MP3Decoder implements Decoder { */ public MP3Decoder(InputStream stream) throws Exception { InputStream in = new BufferedInputStream(stream, 1024 * 1024); - this.in = new MP3AudioFileReader().getAudioInputStream(in); + this.in = new MpegAudioFileReader().getAudioInputStream(in); AudioFormat baseFormat = this.in.getFormat(); this.channels = baseFormat.getChannels(); if (channels > 2) { @@ -195,171 +184,4 @@ public class MP3Decoder implements Decoder { public float getSamplingRate() { return samplingRate; } - - class MP3AudioFileReader extends TAudioFileReader { - - public static final int INITAL_READ_LENGTH = 128000; - private static final int MARK_LIMIT = INITAL_READ_LENGTH + 1; - private final AudioFormat.Encoding[][] sm_aEncodings = { - {MpegEncoding.MPEG2L1, MpegEncoding.MPEG2L2, MpegEncoding.MPEG2L3}, - {MpegEncoding.MPEG1L1, MpegEncoding.MPEG1L2, MpegEncoding.MPEG1L3}, - {MpegEncoding.MPEG2DOT5L1, MpegEncoding.MPEG2DOT5L2, - MpegEncoding.MPEG2DOT5L3},}; - - protected MP3AudioFileReader() { - super(MARK_LIMIT, true); - } - - @Override - protected AudioFileFormat getAudioFileFormat(InputStream inputStream, long mediaLength) - throws UnsupportedAudioFileException, IOException { - HashMap aff_properties = new HashMap(); - HashMap af_properties = new HashMap(); - int mLength = (int) mediaLength; - int size = inputStream.available(); - PushbackInputStream pis = new PushbackInputStream(inputStream, MARK_LIMIT); - byte head[] = new byte[22]; - pis.read(head); - - // Check for WAV, AU, and AIFF, Ogg Vorbis, Flac, MAC file formats. - // Next check for Shoutcast (supported) and OGG (unsupported) streams. - if ((head[0] == 'R') && (head[1] == 'I') && (head[2] == 'F') - && (head[3] == 'F') && (head[8] == 'W') && (head[9] == 'A') - && (head[10] == 'V') && (head[11] == 'E')) { - int isPCM = ((head[21] << 8) & 0x0000FF00) | ((head[20]) & 0x00000FF); - throw new UnsupportedAudioFileException("WAV PCM stream found"); - - } else if ((head[0] == '.') && (head[1] == 's') && (head[2] == 'n') - && (head[3] == 'd')) { - throw new UnsupportedAudioFileException("AU stream found"); - } else if ((head[0] == 'F') && (head[1] == 'O') && (head[2] == 'R') - && (head[3] == 'M') && (head[8] == 'A') && (head[9] == 'I') - && (head[10] == 'F') && (head[11] == 'F')) { - throw new UnsupportedAudioFileException("AIFF stream found"); - } else if (((head[0] == 'M') | (head[0] == 'm')) - && ((head[1] == 'A') | (head[1] == 'a')) - && ((head[2] == 'C') | (head[2] == 'c'))) { - throw new UnsupportedAudioFileException("APE stream found"); - } else if (((head[0] == 'F') | (head[0] == 'f')) - && ((head[1] == 'L') | (head[1] == 'l')) - && ((head[2] == 'A') | (head[2] == 'a')) - && ((head[3] == 'C') | (head[3] == 'c'))) { - throw new UnsupportedAudioFileException("FLAC stream found"); - } // Shoutcast stream ? - else if (((head[0] == 'I') | (head[0] == 'i')) - && ((head[1] == 'C') | (head[1] == 'c')) - && ((head[2] == 'Y') | (head[2] == 'y'))) { - pis.unread(head); - // Load shoutcast meta data. - } // Ogg stream ? - else if (((head[0] == 'O') | (head[0] == 'o')) - && ((head[1] == 'G') | (head[1] == 'g')) - && ((head[2] == 'G') | (head[2] == 'g'))) { - throw new UnsupportedAudioFileException("Ogg stream found"); - } // No, so pushback. - else { - pis.unread(head); - } - // MPEG header info. - int nVersion = AudioSystem.NOT_SPECIFIED; - int nLayer = AudioSystem.NOT_SPECIFIED; - // int nSFIndex = AudioSystem.NOT_SPECIFIED; - int nMode = AudioSystem.NOT_SPECIFIED; - int FrameSize = AudioSystem.NOT_SPECIFIED; - // int nFrameSize = AudioSystem.NOT_SPECIFIED; - int nFrequency = AudioSystem.NOT_SPECIFIED; - int nTotalFrames = AudioSystem.NOT_SPECIFIED; - float FrameRate = AudioSystem.NOT_SPECIFIED; - int BitRate = AudioSystem.NOT_SPECIFIED; - int nChannels = AudioSystem.NOT_SPECIFIED; - int nHeader = AudioSystem.NOT_SPECIFIED; - int nTotalMS = AudioSystem.NOT_SPECIFIED; - boolean nVBR = false; - AudioFormat.Encoding encoding = null; - try { - Bitstream m_bitstream = new Bitstream(pis); - aff_properties.put("mp3.header.pos", - new Integer(m_bitstream.header_pos())); - Header m_header = m_bitstream.readFrame(); - // nVersion = 0 => MPEG2-LSF (Including MPEG2.5), nVersion = 1 => MPEG1 - nVersion = m_header.version(); - if (nVersion == 2) { - aff_properties.put("mp3.version.mpeg", Float.toString(2.5f)); - } else { - aff_properties.put("mp3.version.mpeg", - Integer.toString(2 - nVersion)); - } - // nLayer = 1,2,3 - nLayer = m_header.layer(); - aff_properties.put("mp3.version.layer", Integer.toString(nLayer)); - // nSFIndex = m_header.sample_frequency(); - nMode = m_header.mode(); - aff_properties.put("mp3.mode", new Integer(nMode)); - nChannels = nMode == 3 ? 1 : 2; - aff_properties.put("mp3.channels", new Integer(nChannels)); - nVBR = m_header.vbr(); - af_properties.put("vbr", new Boolean(nVBR)); - aff_properties.put("mp3.vbr", new Boolean(nVBR)); - aff_properties.put("mp3.vbr.scale", new Integer(m_header.vbr_scale())); - FrameSize = m_header.calculate_framesize(); - aff_properties.put("mp3.framesize.bytes", new Integer(FrameSize)); - if (FrameSize < 0) { - throw new UnsupportedAudioFileException("Invalid FrameSize : " + FrameSize); - } - nFrequency = m_header.frequency(); - aff_properties.put("mp3.frequency.hz", new Integer(nFrequency)); - FrameRate = (float) ((1.0 / (m_header.ms_per_frame())) * 1000.0); - aff_properties.put("mp3.framerate.fps", new Float(FrameRate)); - if (FrameRate < 0) { - throw new UnsupportedAudioFileException("Invalid FrameRate : " + FrameRate); - } - if (mLength != AudioSystem.NOT_SPECIFIED) { - aff_properties.put("mp3.length.bytes", new Integer(mLength)); - nTotalFrames = m_header.max_number_of_frames(mLength); - aff_properties.put("mp3.length.frames", new Integer(nTotalFrames)); - } - BitRate = m_header.bitrate(); - af_properties.put("bitrate", new Integer(BitRate)); - aff_properties.put("mp3.bitrate.nominal.bps", new Integer(BitRate)); - nHeader = m_header.getSyncHeader(); - encoding = sm_aEncodings[nVersion][nLayer - 1]; - aff_properties.put("mp3.version.encoding", encoding.toString()); - if (mLength != AudioSystem.NOT_SPECIFIED) { - nTotalMS = Math.round(m_header.total_ms(mLength)); - aff_properties.put("duration", new Long((long) nTotalMS * 1000L)); - } - aff_properties.put("mp3.copyright", new Boolean(m_header.copyright())); - aff_properties.put("mp3.original", new Boolean(m_header.original())); - aff_properties.put("mp3.crc", new Boolean(m_header.checksums())); - aff_properties.put("mp3.padding", new Boolean(m_header.padding())); - InputStream id3v2 = m_bitstream.getRawID3v2(); - if (id3v2 != null) { - aff_properties.put("mp3.id3tag.v2", id3v2); - } - if (TDebug.TraceAudioFileReader) { - TDebug.out(m_header.toString()); - } - } catch (Exception e) { - throw new UnsupportedAudioFileException("not a MPEG stream:" - + e.getMessage()); - } - // Deeper checks ? - int cVersion = (nHeader >> 19) & 0x3; - if (cVersion == 1) { - throw new UnsupportedAudioFileException( - "not a MPEG stream: wrong version"); - } - int cSFIndex = (nHeader >> 10) & 0x3; - if (cSFIndex == 3) { - - throw new UnsupportedAudioFileException( - "not a MPEG stream: wrong sampling rate"); - } - - AudioFormat format = new MpegAudioFormat(encoding, (float) nFrequency, - AudioSystem.NOT_SPECIFIED, nChannels, -1, FrameRate, true, af_properties); - return new MpegAudioFileFormat(MpegFileFormatType.MP3, format, - nTotalFrames, mLength, aff_properties); - } - } } diff --git a/AudioProcessing/src/main/java/org/wyrez/audio/decoder/VorbisDecoder.java b/AudioProcessing/src/main/java/org/wyrez/audio/decoder/VorbisDecoder.java new file mode 100644 index 0000000..18b7009 --- /dev/null +++ b/AudioProcessing/src/main/java/org/wyrez/audio/decoder/VorbisDecoder.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2013 Darth Affe and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * + * This Code is based on the source provided with a tutorial by + * http://www.badlogicgames.com (as of 16.05.2013). + * The original source can be found here: + * http://code.google.com/p/audio-analysis/source/browse/trunk/src/com/badlogic/audio/io/MP3Decoder.java + */ +package org.wyrez.audio.decoder; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import org.tritonus.sampled.file.jorbis.JorbisAudioFileReader; +import org.tritonus.share.sampled.FloatSampleBuffer; + +/** + * A decoder for MP3 files with a maximum of 2 channels. + * + * @author Darth Affe + */ +public class VorbisDecoder implements Decoder { + + private AudioInputStream in; + private FloatSampleBuffer buffer; + private byte[] bytes; + private int channels; + private float samplingRate; + + /** + * Constructor, sets the input stream to read the MP3 file from. Audio files + * with more than 2 channels aren't supported! + * + * @param stream The input stream. + * @throws Exception in case the input stream couldn't be read properly + */ + public VorbisDecoder(InputStream stream) throws Exception { + InputStream in = new BufferedInputStream(stream, 1024 * 1024); + this.in = new JorbisAudioFileReader().getAudioInputStream(in); + AudioFormat baseFormat = this.in.getFormat(); + this.channels = baseFormat.getChannels(); + if (channels > 2) { + throw new IllegalArgumentException("more than 2 channels aren't supported"); + } + this.samplingRate = baseFormat.getSampleRate(); + AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, + baseFormat.getSampleRate(), 16, + baseFormat.getChannels(), + baseFormat.getChannels() * 2, + baseFormat.getSampleRate(), false); + this.in = AudioSystem.getAudioInputStream(format, this.in); + } + + /** + * Tries to read in samples.length stereo samples. Returns the number of + * samples actually read. Guarantees that samples.length samples are read in + * if there was enough data in the stream. + * + * @param samples The samples array to write the samples to + * @return The number of samples actually read + */ + @Override + public int readSamplesStereo(float[] samples) { + if (buffer == null || buffer.getSampleCount() < samples.length / 2) { + buffer = new FloatSampleBuffer(in.getFormat().getChannels(), samples.length / 2, in.getFormat().getSampleRate()); + bytes = new byte[buffer.getByteArrayBufferSize(in.getFormat())]; + } + + int read = 0; + int readBytes = 0; + try { + readBytes = in.read(bytes, read, bytes.length - read); + } catch (IOException e) { + return 0; + } + if (readBytes == -1) { + return 0; + } + + read += readBytes; + while (readBytes != -1 && read != bytes.length) { + try { + readBytes = in.read(bytes, read, bytes.length - read); + } catch (IOException e) { + return 0; + } + read += readBytes; + } + + int frameCount = bytes.length / in.getFormat().getFrameSize(); + buffer.setSamplesFromBytes(bytes, 0, in.getFormat(), 0, frameCount); + + for (int i = 0, j = 0; i < buffer.getSampleCount(); i++, j += 2) { + samples[j] = buffer.getChannel(0)[i]; + samples[j + 1] = buffer.getChannel(buffer.getChannelCount() == 2 ? 1 : 0)[i]; + } + return buffer.getSampleCount() * 2; + } + + /** + * Tries to read in samples.length samples, merging stereo to a mono channel + * by averaging. Returns the number of samples actually read. Guarantees + * that samples.length samples are read in if there was enough data in the + * stream. + * + * @param samples The samples array to write the samples to + * @return The number of samples actually read + */ + @Override + public int readSamplesMergedMono(float[] samples) { + if (buffer == null || buffer.getSampleCount() < samples.length) { + buffer = new FloatSampleBuffer(in.getFormat().getChannels(), samples.length, in.getFormat().getSampleRate()); + bytes = new byte[buffer.getByteArrayBufferSize(in.getFormat())]; + } + + int read = 0; + int readBytes = 0; + try { + readBytes = in.read(bytes, read, bytes.length - read); + } catch (IOException e) { + return 0; + } + if (readBytes == -1) { + return 0; + } + + read += readBytes; + while (readBytes != -1 && read != bytes.length) { + try { + readBytes = in.read(bytes, read, bytes.length - read); + } catch (IOException e) { + return 0; + } + read += readBytes; + } + + int frameCount = bytes.length / in.getFormat().getFrameSize(); + buffer.setSamplesFromBytes(bytes, 0, in.getFormat(), 0, frameCount); + + for (int i = 0; i < buffer.getSampleCount(); i++) { + if (buffer.getChannelCount() == 2) { + samples[i] = (buffer.getChannel(0)[i] + buffer.getChannel(1)[i]) / 2; + } else { + samples[i] = buffer.getChannel(0)[i]; + } + } + return buffer.getSampleCount(); + } + + /** + * Returns the number of channels in the audio source. (This value can't be + * greater than 2) + * + * @returns The channel count + */ + @Override + public int getChannelCount() { + return channels; + } + + /** + * Returns the sampling rate in Hz of the audio source. + * + * @returns the sampling rate as float + */ + @Override + public float getSamplingRate() { + return samplingRate; + } +}