From 9fee0ec4ca0c5b7a334cc29dbb58e76c7a4c736e Mon Sep 17 00:00:00 2001 From: Michiel Van Der Kolk Date: Mon, 11 Jul 2005 15:42:37 +0000 Subject: Songdb java version, source. only 1.5 compatible git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7101 a1c6a512-1295-4272-9138-f99709370657 --- songdbj/javazoom/spi/PropertiesContainer.java | 31 + .../convert/DecodedMpegAudioInputStream.java | 334 +++++++++ .../convert/MpegFormatConversionProvider.java | 120 ++++ .../spi/mpeg/sampled/file/IcyListener.java | 131 ++++ .../spi/mpeg/sampled/file/MpegAudioFileFormat.java | 103 +++ .../spi/mpeg/sampled/file/MpegAudioFileReader.java | 772 +++++++++++++++++++++ .../spi/mpeg/sampled/file/MpegAudioFormat.java | 67 ++ .../spi/mpeg/sampled/file/MpegEncoding.java | 47 ++ .../spi/mpeg/sampled/file/MpegFileFormatType.java | 40 ++ .../spi/mpeg/sampled/file/tag/IcyInputStream.java | 412 +++++++++++ .../javazoom/spi/mpeg/sampled/file/tag/IcyTag.java | 42 ++ .../mpeg/sampled/file/tag/MP3MetadataParser.java | 50 ++ .../javazoom/spi/mpeg/sampled/file/tag/MP3Tag.java | 52 ++ .../mpeg/sampled/file/tag/MP3TagParseSupport.java | 62 ++ .../spi/mpeg/sampled/file/tag/StringableTag.java | 36 + .../spi/mpeg/sampled/file/tag/TagParseEvent.java | 44 ++ .../mpeg/sampled/file/tag/TagParseListener.java | 37 + .../convert/DecodedVorbisAudioInputStream.java | 519 ++++++++++++++ .../convert/VorbisFormatConversionProvider.java | 244 +++++++ .../vorbis/sampled/file/VorbisAudioFileFormat.java | 85 +++ .../vorbis/sampled/file/VorbisAudioFileReader.java | 502 ++++++++++++++ .../spi/vorbis/sampled/file/VorbisAudioFormat.java | 66 ++ .../spi/vorbis/sampled/file/VorbisEncoding.java | 41 ++ .../vorbis/sampled/file/VorbisFileFormatType.java | 41 ++ 24 files changed, 3878 insertions(+) create mode 100644 songdbj/javazoom/spi/PropertiesContainer.java create mode 100644 songdbj/javazoom/spi/mpeg/sampled/convert/DecodedMpegAudioInputStream.java create mode 100644 songdbj/javazoom/spi/mpeg/sampled/convert/MpegFormatConversionProvider.java create mode 100644 songdbj/javazoom/spi/mpeg/sampled/file/IcyListener.java create mode 100644 songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileFormat.java create mode 100644 songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java create mode 100644 songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFormat.java create mode 100644 songdbj/javazoom/spi/mpeg/sampled/file/MpegEncoding.java create mode 100644 songdbj/javazoom/spi/mpeg/sampled/file/MpegFileFormatType.java create mode 100644 songdbj/javazoom/spi/mpeg/sampled/file/tag/IcyInputStream.java create mode 100644 songdbj/javazoom/spi/mpeg/sampled/file/tag/IcyTag.java create mode 100644 songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3MetadataParser.java create mode 100644 songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3Tag.java create mode 100644 songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3TagParseSupport.java create mode 100644 songdbj/javazoom/spi/mpeg/sampled/file/tag/StringableTag.java create mode 100644 songdbj/javazoom/spi/mpeg/sampled/file/tag/TagParseEvent.java create mode 100644 songdbj/javazoom/spi/mpeg/sampled/file/tag/TagParseListener.java create mode 100644 songdbj/javazoom/spi/vorbis/sampled/convert/DecodedVorbisAudioInputStream.java create mode 100644 songdbj/javazoom/spi/vorbis/sampled/convert/VorbisFormatConversionProvider.java create mode 100644 songdbj/javazoom/spi/vorbis/sampled/file/VorbisAudioFileFormat.java create mode 100644 songdbj/javazoom/spi/vorbis/sampled/file/VorbisAudioFileReader.java create mode 100644 songdbj/javazoom/spi/vorbis/sampled/file/VorbisAudioFormat.java create mode 100644 songdbj/javazoom/spi/vorbis/sampled/file/VorbisEncoding.java create mode 100644 songdbj/javazoom/spi/vorbis/sampled/file/VorbisFileFormatType.java (limited to 'songdbj/javazoom/spi') diff --git a/songdbj/javazoom/spi/PropertiesContainer.java b/songdbj/javazoom/spi/PropertiesContainer.java new file mode 100644 index 0000000000..27ed85f904 --- /dev/null +++ b/songdbj/javazoom/spi/PropertiesContainer.java @@ -0,0 +1,31 @@ +/* + * PropertiesContainer. + * + * JavaZOOM : mp3spi@javazoom.net + * http://www.javazoom.net + * + *----------------------------------------------------------------------- + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + *---------------------------------------------------------------------- + */ + +package javazoom.spi; + +import java.util.Map; + +public interface PropertiesContainer +{ + public Map properties(); +} diff --git a/songdbj/javazoom/spi/mpeg/sampled/convert/DecodedMpegAudioInputStream.java b/songdbj/javazoom/spi/mpeg/sampled/convert/DecodedMpegAudioInputStream.java new file mode 100644 index 0000000000..e4e6fed00a --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/convert/DecodedMpegAudioInputStream.java @@ -0,0 +1,334 @@ +/* + * DecodedMpegAudioInputStream. + * + * JavaZOOM : mp3spi@javazoom.net + * http://www.javazoom.net + * + *----------------------------------------------------------------------------- + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + *------------------------------------------------------------------------ + */ + +package javazoom.spi.mpeg.sampled.convert; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; + +import javazoom.jl.decoder.Bitstream; +import javazoom.jl.decoder.BitstreamException; +import javazoom.jl.decoder.Decoder; +import javazoom.jl.decoder.DecoderException; +import javazoom.jl.decoder.Equalizer; +import javazoom.jl.decoder.Header; +import javazoom.jl.decoder.Obuffer; +import javazoom.spi.PropertiesContainer; +import javazoom.spi.mpeg.sampled.file.IcyListener; +import javazoom.spi.mpeg.sampled.file.tag.TagParseEvent; +import javazoom.spi.mpeg.sampled.file.tag.TagParseListener; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.convert.TAsynchronousFilteredAudioInputStream; + +/** + * Main decoder. + */ +public class DecodedMpegAudioInputStream extends TAsynchronousFilteredAudioInputStream implements PropertiesContainer, TagParseListener +{ + private InputStream m_encodedStream; + private Bitstream m_bitstream; + private Decoder m_decoder; + private Equalizer m_equalizer; + private float[] m_equalizer_values; + private Header m_header; + private DMAISObuffer m_oBuffer; + + // Bytes info. + private long byteslength = -1; + private long currentByte = 0; + // Frame info. + private int frameslength = -1; + private long currentFrame = 0; + private int currentFramesize = 0; + private int currentBitrate = -1; + // Time info. + private long currentMicrosecond = 0; + // Shoutcast stream info + private IcyListener shoutlst = null; + + + private HashMap properties = null; + + public DecodedMpegAudioInputStream(AudioFormat outputFormat, AudioInputStream inputStream) + { + super(outputFormat, -1); + if (TDebug.TraceAudioConverter) + { + TDebug.out(">DecodedMpegAudioInputStream(AudioFormat outputFormat, AudioInputStream inputStream)"); + } + try + { + // Try to find out inputstream length to allow skip. + byteslength = inputStream.available(); + } + catch (IOException e) + { + TDebug.out("DecodedMpegAudioInputStream : Cannot run inputStream.available() : "+e.getMessage()); + byteslength = -1; + } + m_encodedStream = inputStream; + shoutlst = IcyListener.getInstance(); + shoutlst.reset(); + m_bitstream = new Bitstream(inputStream); + m_decoder = new Decoder(null); + m_equalizer = new Equalizer(); + m_equalizer_values = new float[32]; + for (int b=0;b 0)) frameslength = m_header.max_number_of_frames((int)byteslength); + } + catch (BitstreamException e) + { + TDebug.out("DecodedMpegAudioInputStream : Cannot read first frame : "+e.getMessage()); + byteslength = -1; + } + properties = new HashMap(); + } + + /** + * Return dynamic properties. + * + * + */ + public Map properties() + { + properties.put("mp3.frame",new Long(currentFrame)); + properties.put("mp3.frame.bitrate",new Integer(currentBitrate)); + properties.put("mp3.frame.size.bytes",new Integer(currentFramesize)); + properties.put("mp3.position.byte",new Long(currentByte)); + properties.put("mp3.position.microseconds",new Long(currentMicrosecond)); + properties.put("mp3.equalizer",m_equalizer_values); + // Optionnal shoutcast stream meta-data. + if (shoutlst != null) + { + String surl = shoutlst.getStreamUrl(); + String stitle = shoutlst.getStreamTitle(); + if ((stitle != null) && (stitle.trim().length()>0)) properties.put("mp3.shoutcast.metadata.StreamTitle",stitle); + if ((surl != null) && (surl.trim().length()>0)) properties.put("mp3.shoutcast.metadata.StreamUrl",surl); + } + return properties; + } + + public void execute() + { + if (TDebug.TraceAudioConverter) TDebug.out("execute() : begin"); + try + { + // Following line hangs when FrameSize is available in AudioFormat. + Header header = null; + if (m_header == null) header = m_bitstream.readFrame(); + else header = m_header; + if (TDebug.TraceAudioConverter) TDebug.out("execute() : header = "+header); + if (header == null) + { + if (TDebug.TraceAudioConverter) + { + TDebug.out("header is null (end of mpeg stream)"); + } + getCircularBuffer().close(); + return; + } + currentFrame++; + currentBitrate = header.bitrate_instant(); + currentFramesize = header.calculate_framesize(); + currentByte = currentByte + currentFramesize; + currentMicrosecond = (long) (currentFrame* header.ms_per_frame()*1000.0f); + for (int b=0;b 0) && (frameslength > 0)) + { + float ratio = bytes*1.0f/byteslength*1.0f; + long bytesread = skipFrames((long) (ratio*frameslength)); + currentByte = currentByte + bytesread; + return bytesread; + } + else return -1; + } + + /** + * Skip frames. + * You don't need to call it severals times, it will exactly skip given frames number. + * @param frames + * @return bytes length skipped matching to frames skipped. + */ + public long skipFrames(long frames) + { + if (TDebug.TraceAudioConverter) TDebug.out("skip(long frames) : begin"); + int framesRead = 0; + int bytesReads = 0; + try + { + for (int i=0;i>> 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 byte[] getBuffer() + { + return m_abBuffer; + } + public int getCurrentBufferSize() + { + return m_anBufferPointers[0]; + } + public void reset() + { + for (int i = 0; i < m_nChannels; i++) + { + /* Points to byte location, + * implicitely assuming 16 bit + * samples. + */ + m_anBufferPointers[i] = i * 2; + } + } + } + + public void tagParsed(TagParseEvent tpe) + { + System.out.println("TAG:"+tpe.getTag()); + } +} diff --git a/songdbj/javazoom/spi/mpeg/sampled/convert/MpegFormatConversionProvider.java b/songdbj/javazoom/spi/mpeg/sampled/convert/MpegFormatConversionProvider.java new file mode 100644 index 0000000000..1a3d51d9ad --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/convert/MpegFormatConversionProvider.java @@ -0,0 +1,120 @@ +/* + * MpegFormatConversionProvider. + * + * JavaZOOM : mp3spi@javazoom.net + * http://www.javazoom.net + * + * --------------------------------------------------------------------------- + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * -------------------------------------------------------------------------- + */ + + +package javazoom.spi.mpeg.sampled.convert; + + +import java.util.Arrays; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; + +import javazoom.spi.mpeg.sampled.file.MpegEncoding; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.Encodings; +import org.tritonus.share.sampled.convert.TEncodingFormatConversionProvider; + +/** + * ConversionProvider for MPEG files. + */ +public class MpegFormatConversionProvider extends TEncodingFormatConversionProvider +{ + private static final AudioFormat.Encoding MP3 = Encodings.getEncoding("MP3"); + private static final AudioFormat.Encoding PCM_SIGNED = Encodings.getEncoding("PCM_SIGNED"); + + private static final AudioFormat[] INPUT_FORMATS = + { + // mono + new AudioFormat(MP3, -1.0F, -1, 1, -1, -1.0F, false), + new AudioFormat(MP3, -1.0F, -1, 1, -1, -1.0F, true), + // stereo + new AudioFormat(MP3, -1.0F, -1, 2, -1, -1.0F, false), + new AudioFormat(MP3, -1.0F, -1, 2, -1, -1.0F, true), + }; + + + private static final AudioFormat[] OUTPUT_FORMATS = + { + // mono, 16 bit signed + new AudioFormat(PCM_SIGNED, -1.0F, 16, 1, 2, -1.0F, false), + new AudioFormat(PCM_SIGNED, -1.0F, 16, 1, 2, -1.0F, true), + // stereo, 16 bit signed + new AudioFormat(PCM_SIGNED, -1.0F, 16, 2, 4, -1.0F, false), + new AudioFormat(PCM_SIGNED, -1.0F, 16, 2, 4, -1.0F, true), + }; + + /** + * Constructor. + */ + public MpegFormatConversionProvider() + { + super(Arrays.asList(INPUT_FORMATS), Arrays.asList(OUTPUT_FORMATS)); + if (TDebug.TraceAudioConverter) + { + TDebug.out(">MpegFormatConversionProvider()"); + } + } + + public AudioInputStream getAudioInputStream(AudioFormat targetFormat, AudioInputStream audioInputStream) + { + if (TDebug.TraceAudioConverter) + { + TDebug.out(">MpegFormatConversionProvider.getAudioInputStream(AudioFormat targetFormat, AudioInputStream audioInputStream):"); + } + return new DecodedMpegAudioInputStream(targetFormat, audioInputStream); + } + + /** + * Add conversion support for any MpegEncoding source with FrameRate or FrameSize not empty. + * @param targetFormat + * @param sourceFormat + * @return + */ + public boolean isConversionSupported(AudioFormat targetFormat, AudioFormat sourceFormat) + { + if (TDebug.TraceAudioConverter) + { + TDebug.out(">MpegFormatConversionProvider.isConversionSupported(AudioFormat targetFormat, AudioFormat sourceFormat):"); + TDebug.out("checking if conversion possible"); + TDebug.out("from: " + sourceFormat); + TDebug.out("to: " + targetFormat); + } + + boolean conversion = super.isConversionSupported(targetFormat, sourceFormat); + if (conversion == false) + { + AudioFormat.Encoding enc = sourceFormat.getEncoding(); + if (enc instanceof MpegEncoding) + { + if ((sourceFormat.getFrameRate() != AudioSystem.NOT_SPECIFIED) || (sourceFormat.getFrameSize() != AudioSystem.NOT_SPECIFIED)) + { + conversion = true; + } + } + } + return conversion; + } +} diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/IcyListener.java b/songdbj/javazoom/spi/mpeg/sampled/file/IcyListener.java new file mode 100644 index 0000000000..8e49e6e2ef --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/IcyListener.java @@ -0,0 +1,131 @@ +/* + * IcyListener. + * + * JavaZOOM : mp3spi@javazoom.net + * http://www.javazoom.net + * + *----------------------------------------------------------------------- + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + *---------------------------------------------------------------------- + */ + +package javazoom.spi.mpeg.sampled.file; + +import javazoom.spi.mpeg.sampled.file.tag.MP3Tag; +import javazoom.spi.mpeg.sampled.file.tag.TagParseEvent; +import javazoom.spi.mpeg.sampled.file.tag.TagParseListener; + +/** + * This class (singleton) allow to be notified on shoutcast meta data + * while playing the stream (such as song title). + */ +public class IcyListener implements TagParseListener +{ + private static IcyListener instance = null; + private MP3Tag lastTag = null; + private String streamTitle = null; + private String streamUrl = null; + + + private IcyListener() + { + super(); + } + + public static synchronized IcyListener getInstance() + { + if (instance == null) + { + instance = new IcyListener(); + } + return instance; + } + + /* (non-Javadoc) + * @see javazoom.spi.mpeg.sampled.file.tag.TagParseListener#tagParsed(javazoom.spi.mpeg.sampled.file.tag.TagParseEvent) + */ + public void tagParsed(TagParseEvent tpe) + { + lastTag = tpe.getTag(); + String name = lastTag.getName(); + if ((name != null) && (name.equalsIgnoreCase("streamtitle"))) + { + streamTitle = (String) lastTag.getValue(); + } + else if ((name != null) && (name.equalsIgnoreCase("streamurl"))) + { + streamUrl = (String) lastTag.getValue(); + } + } + + /** + * @return + */ + public MP3Tag getLastTag() + { + return lastTag; + } + + /** + * @param tag + */ + public void setLastTag(MP3Tag tag) + { + lastTag = tag; + } + + /** + * @return + */ + public String getStreamTitle() + { + return streamTitle; + } + + /** + * @return + */ + public String getStreamUrl() + { + return streamUrl; + } + + /** + * @param string + */ + public void setStreamTitle(String string) + { + streamTitle = string; + } + + /** + * @param string + */ + public void setStreamUrl(String string) + { + streamUrl = string; + } + + /** + * Reset all properties. + */ + public void reset() + { + lastTag = null; + streamTitle = null; + streamUrl = null; + } + +} diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileFormat.java b/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileFormat.java new file mode 100644 index 0000000000..afdc4c5e9c --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileFormat.java @@ -0,0 +1,103 @@ +/* + * MpegAudioFileFormat. + * + * JavaZOOM : mp3spi@javazoom.net + * http://www.javazoom.net + * + *----------------------------------------------------------------------- + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + *---------------------------------------------------------------------- + */ +package javazoom.spi.mpeg.sampled.file; + +import java.util.Map; + +import javax.sound.sampled.AudioFormat; + +import org.tritonus.share.sampled.file.TAudioFileFormat; + +/** + * @author JavaZOOM + */ +public class MpegAudioFileFormat extends TAudioFileFormat +{ + /** + * Contructor. + * @param type + * @param audioFormat + * @param nLengthInFrames + * @param nLengthInBytes + */ + public MpegAudioFileFormat(Type type, AudioFormat audioFormat, int nLengthInFrames, int nLengthInBytes, Map properties) + { + super(type, audioFormat, nLengthInFrames, nLengthInBytes, properties); + } + + /** + * MP3 audio file format parameters. + * Some parameters might be unavailable. So availability test is required before reading any parameter. + * + *
AudioFileFormat parameters. + *
    + *
  • duration [Long], duration in microseconds. + *
  • title [String], Title of the stream. + *
  • author [String], Name of the artist of the stream. + *
  • album [String], Name of the album of the stream. + *
  • date [String], The date (year) of the recording or release of the stream. + *
  • copyright [String], Copyright message of the stream. + *
  • comment [String], Comment of the stream. + *
+ *
MP3 parameters. + *
    + *
  • mp3.version.mpeg [String], mpeg version : 1,2 or 2.5 + *
  • mp3.version.layer [String], layer version 1, 2 or 3 + *
  • mp3.version.encoding [String], mpeg encoding : MPEG1, MPEG2-LSF, MPEG2.5-LSF + *
  • mp3.channels [Integer], number of channels 1 : mono, 2 : stereo. + *
  • mp3.frequency.hz [Integer], sampling rate in hz. + *
  • mp3.bitrate.nominal.bps [Integer], nominal bitrate in bps. + *
  • mp3.length.bytes [Integer], length in bytes. + *
  • mp3.length.frames [Integer], length in frames. + *
  • mp3.framesize.bytes [Integer], framesize of the first frame. framesize is not constant for VBR streams. + *
  • mp3.framerate.fps [Float], framerate in frames per seconds. + *
  • mp3.header.pos [Integer], position of first audio header (or ID3v2 size). + *
  • mp3.vbr [Boolean], vbr flag. + *
  • mp3.vbr.scale [Integer], vbr scale. + *
  • mp3.crc [Boolean], crc flag. + *
  • mp3.original [Boolean], original flag. + *
  • mp3.copyright [Boolean], copyright flag. + *
  • mp3.padding [Boolean], padding flag. + *
  • mp3.mode [Integer], mode 0:STEREO 1:JOINT_STEREO 2:DUAL_CHANNEL 3:SINGLE_CHANNEL + *
  • mp3.id3tag.genre [String], ID3 tag (v1 or v2) genre. + *
  • mp3.id3tag.track [String], ID3 tag (v1 or v2) track info. + *
  • mp3.id3tag.encoded [String], ID3 tag v2 encoded by info. + *
  • mp3.id3tag.composer [String], ID3 tag v2 composer info. + *
  • mp3.id3tag.grouping [String], ID3 tag v2 grouping info. + *
  • mp3.id3tag.disc [String], ID3 tag v2 track info. + *
  • mp3.id3tag.v2 [InputStream], ID3v2 frames. + *
  • mp3.id3tag.v2.version [String], ID3v2 major version (2=v2.2.0, 3=v2.3.0, 4=v2.4.0). + *
  • mp3.shoutcast.metadata.key [String], Shoutcast meta key with matching value. + *
    For instance : + *
    mp3.shoutcast.metadata.icy-irc=#shoutcast + *
    mp3.shoutcast.metadata.icy-metaint=8192 + *
    mp3.shoutcast.metadata.icy-genre=Trance Techno Dance + *
    mp3.shoutcast.metadata.icy-url=http://www.di.fm + *
    and so on ... + *
+ */ + public Map properties() + { + return super.properties(); + } +} diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java b/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java new file mode 100644 index 0000000000..54440551a1 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java @@ -0,0 +1,772 @@ +/* + * MpegAudioFileReader. + * + * 12/31/04 : mp3spi.weak system property added to skip controls. + * + * 11/29/04 : ID3v2.2, v2.3 & v2.4 support improved. + * "mp3.id3tag.composer" (TCOM/TCM) added + * "mp3.id3tag.grouping" (TIT1/TT1) added + * "mp3.id3tag.disc" (TPA/TPOS) added + * "mp3.id3tag.encoded" (TEN/TENC) added + * "mp3.id3tag.v2.version" added + * + * 11/28/04 : String encoding bug fix in chopSubstring method. + * + * JavaZOOM : mp3spi@javazoom.net + * http://www.javazoom.net + * + *----------------------------------------------------------------------- + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + *---------------------------------------------------------------------- + */ + +package javazoom.spi.mpeg.sampled.file; + + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PushbackInputStream; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLConnection; +import java.security.AccessControlException; +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.tag.IcyInputStream; +import javazoom.spi.mpeg.sampled.file.tag.MP3Tag; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.file.TAudioFileReader; + +/** + * This class implements AudioFileReader for MP3 SPI. + */ +public class MpegAudioFileReader extends TAudioFileReader +{ + private final int SYNC = 0xFFE00000; + private String weak = null; + private final AudioFormat.Encoding[][] sm_aEncodings = + { + {MpegEncoding.MPEG2L1, MpegEncoding.MPEG2L2, MpegEncoding.MPEG2L3}, + {MpegEncoding.MPEG1L1, MpegEncoding.MPEG1L2, MpegEncoding.MPEG1L3}, + {MpegEncoding.MPEG2DOT5L1, MpegEncoding.MPEG2DOT5L2, MpegEncoding.MPEG2DOT5L3}, + + }; + + private static final int INITAL_READ_LENGTH = 64000; + private static final int MARK_LIMIT = INITAL_READ_LENGTH + 1; + + private static final String[] id3v1genres = { + "Blues" + , "Classic Rock" + , "Country" + , "Dance" + , "Disco" + , "Funk" + , "Grunge" + , "Hip-Hop" + , "Jazz" + , "Metal" + , "New Age" + , "Oldies" + , "Other" + , "Pop" + , "R&B" + , "Rap" + , "Reggae" + , "Rock" + , "Techno" + , "Industrial" + , "Alternative" + , "Ska" + , "Death Metal" + , "Pranks" + , "Soundtrack" + , "Euro-Techno" + , "Ambient" + , "Trip-Hop" + , "Vocal" + , "Jazz+Funk" + , "Fusion" + , "Trance" + , "Classical" + , "Instrumental" + , "Acid" + , "House" + , "Game" + , "Sound Clip" + , "Gospel" + , "Noise" + , "AlternRock" + , "Bass" + , "Soul" + , "Punk" + , "Space" + , "Meditative" + , "Instrumental Pop" + , "Instrumental Rock" + , "Ethnic" + , "Gothic" + , "Darkwave" + , "Techno-Industrial" + , "Electronic" + , "Pop-Folk" + , "Eurodance" + , "Dream" + , "Southern Rock" + , "Comedy" + , "Cult" + , "Gangsta" + , "Top 40" + , "Christian Rap" + , "Pop/Funk" + , "Jungle" + , "Native American" + , "Cabaret" + , "New Wave" + , "Psychadelic" + , "Rave" + , "Showtunes" + , "Trailer" + , "Lo-Fi" + , "Tribal" + , "Acid Punk" + , "Acid Jazz" + , "Polka" + , "Retro" + , "Musical" + , "Rock & Roll" + , "Hard Rock" + , "Folk" + , "Folk-Rock" + , "National Folk" + , "Swing" + , "Fast Fusion" + , "Bebob" + , "Latin" + , "Revival" + , "Celtic" + , "Bluegrass" + , "Avantgarde" + , "Gothic Rock" + , "Progressive Rock" + , "Psychedelic Rock" + , "Symphonic Rock" + , "Slow Rock" + , "Big Band" + , "Chorus" + , "Easy Listening" + , "Acoustic" + , "Humour" + , "Speech" + , "Chanson" + , "Opera" + , "Chamber Music" + , "Sonata" + , "Symphony" + , "Booty Brass" + , "Primus" + , "Porn Groove" + , "Satire" + , "Slow Jam" + , "Club" + , "Tango" + , "Samba" + , "Folklore" + , "Ballad" + , "Power Ballad" + , "Rhythmic Soul" + , "Freestyle" + , "Duet" + , "Punk Rock" + , "Drum Solo" + , "A Capela" + , "Euro-House" + , "Dance Hall" + , "Goa" + , "Drum & Bass" + , "Club-House" + , "Hardcore" + , "Terror" + , "Indie" + , "BritPop" + , "Negerpunk" + , "Polsk Punk" + , "Beat" + , "Christian Gangsta Rap" + , "Heavy Metal" + , "Black Metal" + , "Crossover" + , "Contemporary Christian" + , "Christian Rock" + , "Merengue" + , "Salsa" + , "Thrash Metal" + , "Anime" + , "JPop" + , "SynthPop" + }; + + public MpegAudioFileReader() + { + super(MARK_LIMIT, true); + if (TDebug.TraceAudioFileReader) TDebug.out(">MpegAudioFileReader(1.9.2-FINAL)"); + try + { + weak = System.getProperty("mp3spi.weak"); + } + catch(AccessControlException e) + {} + } + + /** + * Returns AudioFileFormat from File. + */ + public AudioFileFormat getAudioFileFormat(File file) throws UnsupportedAudioFileException, IOException + { + return super.getAudioFileFormat(file); + } + + /** + * Returns AudioFileFormat from URL. + */ + public AudioFileFormat getAudioFileFormat(URL url) throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) {TDebug.out("MpegAudioFileReader.getAudioFileFormat(URL): begin"); } + long lFileLengthInBytes = AudioSystem.NOT_SPECIFIED; + URLConnection conn = url.openConnection(); + // Tell shoucast server (if any) that SPI support shoutcast stream. + conn.setRequestProperty ("Icy-Metadata", "1"); + InputStream inputStream = conn.getInputStream(); + AudioFileFormat audioFileFormat = null; + try + { + audioFileFormat = getAudioFileFormat(inputStream, lFileLengthInBytes); + } + finally + { + inputStream.close(); + } + if (TDebug.TraceAudioFileReader) {TDebug.out("MpegAudioFileReader.getAudioFileFormat(URL): end"); } + return audioFileFormat; + } + + /** + * Returns AudioFileFormat from inputstream and medialength. + */ + public AudioFileFormat getAudioFileFormat(InputStream inputStream, long mediaLength) throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) TDebug.out(">MpegAudioFileReader.getAudioFileFormat(InputStream inputStream, long mediaLength): begin"); + 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[12]; + pis.read(head); + if (TDebug.TraceAudioFileReader) + { + TDebug.out("InputStream : "+inputStream + " =>" + new String(head)); + } + /* + * Check for WAV, AU, and AIFF file formats. + * + * Next check for Shoutcast (supported) and OGG (unsupported) streams. + * + * Note -- the check for WAV files will reject Broadcast WAV files. + * This may be incorrect as broadcast WAV files may contain MPEG data. + * Need to investigate. + * + */ + 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')) + { + if (TDebug.TraceAudioFileReader) TDebug.out("WAV stream found"); + if (weak == null) throw new UnsupportedAudioFileException("WAV stream found"); + } + else if ((head[0] == '.') && (head[1] == 's') && (head[2] == 'n') && (head[3] == 'd')) + { + if (TDebug.TraceAudioFileReader) TDebug.out("AU stream found"); + if (weak == null) 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')) + { + if (TDebug.TraceAudioFileReader) TDebug.out("AIFF stream found"); + if (weak == null) throw new UnsupportedAudioFileException("AIFF 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. + loadShoutcastInfo(pis, aff_properties); + } + // Ogg stream ? + else if (((head[0] == 'O') | (head[0] == 'o')) && ((head[1] == 'G') | (head[1] == 'g')) && ((head[2] == 'G') | (head[2] == 'g'))) + { + if (TDebug.TraceAudioFileReader) TDebug.out("Ogg stream found"); + if (weak == null) 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); + parseID3v2Frames(id3v2,aff_properties); + } + if (TDebug.TraceAudioFileReader) TDebug.out(m_header.toString()); + } + catch (Exception e) + { + if (TDebug.TraceAudioFileReader) TDebug.out("not a MPEG stream:" + e.getMessage()); + throw new UnsupportedAudioFileException("not a MPEG stream:" + e.getMessage()); + } + + // Deeper checks ? + int cVersion = (nHeader >> 19) & 0x3; + if (cVersion == 1) + { + if (TDebug.TraceAudioFileReader) TDebug.out("not a MPEG stream: wrong version"); + throw new UnsupportedAudioFileException("not a MPEG stream: wrong version"); + } + + int cSFIndex = (nHeader >> 10) & 0x3; + if (cSFIndex == 3) + { + if (TDebug.TraceAudioFileReader) TDebug.out("not a MPEG stream: wrong sampling rate"); + throw new UnsupportedAudioFileException("not a MPEG stream: wrong sampling rate"); + } + + // Look up for ID3v1 tag + if ((size == mediaLength) && (mediaLength != AudioSystem.NOT_SPECIFIED)) + { + FileInputStream fis = (FileInputStream) inputStream; + byte[] id3v1 = new byte[128]; + long bytesSkipped = fis.skip(inputStream.available()-id3v1.length); + int read = fis.read(id3v1,0,id3v1.length); + if ((id3v1[0]=='T') && (id3v1[1]=='A') && (id3v1[2]=='G')) + { + parseID3v1Frames(id3v1, aff_properties); + } + } + + AudioFormat format = new MpegAudioFormat(encoding, (float) nFrequency, AudioSystem.NOT_SPECIFIED // SampleSizeInBits - The size of a sample + , nChannels // Channels - The number of channels + , -1 // The number of bytes in each frame + , FrameRate // FrameRate - The number of frames played or recorded per second + , true + , af_properties); + return new MpegAudioFileFormat(MpegFileFormatType.MP3, format, nTotalFrames, mLength,aff_properties); + } + + /** + * Returns AudioInputStream from file. + */ + public AudioInputStream getAudioInputStream(File file) throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) TDebug.out("getAudioInputStream(File file)"); + InputStream inputStream = new FileInputStream(file); + try + { + return getAudioInputStream(inputStream); + } + catch (UnsupportedAudioFileException e) + { + if (inputStream != null) inputStream.close(); + throw e; + } + catch (IOException e) + { + if (inputStream != null) inputStream.close(); + throw e; + } + } + + /** + * Returns AudioInputStream from url. + */ + public AudioInputStream getAudioInputStream(URL url) + throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) {TDebug.out("MpegAudioFileReader.getAudioInputStream(URL): begin"); } + long lFileLengthInBytes = AudioSystem.NOT_SPECIFIED; + URLConnection conn = url.openConnection(); + // Tell shoucast server (if any) that SPI support shoutcast stream. + boolean isShout = false; + int toRead=4; + byte[] head = new byte[toRead]; + + conn.setRequestProperty ("Icy-Metadata", "1"); + BufferedInputStream bInputStream = new BufferedInputStream(conn.getInputStream()); + bInputStream.mark(toRead); + int read = bInputStream.read(head,0,toRead); + if ((read>2) && (((head[0] == 'I') | (head[0] == 'i')) && ((head[1] == 'C') | (head[1] == 'c')) && ((head[2] == 'Y') | (head[2] == 'y')))) isShout = true; + bInputStream.reset(); + InputStream inputStream = null; + // Is is a shoutcast server ? + if (isShout == true) + { + // Yes + IcyInputStream icyStream = new IcyInputStream(bInputStream); + icyStream.addTagParseListener(IcyListener.getInstance()); + inputStream = icyStream; + } + else + { + // No, is Icecast 2 ? + String metaint = conn.getHeaderField("icy-metaint"); + if (metaint != null) + { + // Yes, it might be icecast 2 mp3 stream. + IcyInputStream icyStream = new IcyInputStream(bInputStream,metaint); + icyStream.addTagParseListener(IcyListener.getInstance()); + inputStream = icyStream; + } + else + { + // No + inputStream = bInputStream; + } + } + AudioInputStream audioInputStream = null; + try + { + audioInputStream = getAudioInputStream(inputStream, lFileLengthInBytes); + } + catch (UnsupportedAudioFileException e) + { + inputStream.close(); + throw e; + } + catch (IOException e) + { + inputStream.close(); + throw e; + } + if (TDebug.TraceAudioFileReader) {TDebug.out("MpegAudioFileReader.getAudioInputStream(URL): end"); } + return audioInputStream; + } + + /** + * Return the AudioInputStream from the given InputStream. + */ + public AudioInputStream getAudioInputStream(InputStream inputStream) throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) TDebug.out("MpegAudioFileReader.getAudioInputStream(InputStream inputStream)"); + if (!inputStream.markSupported()) inputStream = new BufferedInputStream(inputStream); + return super.getAudioInputStream(inputStream); + } + + /** + * Parser ID3v1 frames + * @param frames + * @param props + */ + protected void parseID3v1Frames(byte[] frames, HashMap props) + { + if (TDebug.TraceAudioFileReader) TDebug.out("Parsing ID3v1"); + String tag = null; + try + { + tag = new String(frames, 0, frames.length, "ISO-8859-1"); + } + catch (UnsupportedEncodingException e) + { + tag = new String(frames, 0, frames.length); + if (TDebug.TraceAudioFileReader) TDebug.out("Cannot use ISO-8859-1"); + } + if (TDebug.TraceAudioFileReader) TDebug.out("ID3v1 frame dump='"+tag+"'"); + int start = 3; + String titlev1 = chopSubstring(tag, start, start += 30); + String titlev2 = (String) props.get("title"); + if (((titlev2==null) || (titlev2.length()==0)) && (titlev1 != null)) props.put("title",titlev1); + String artistv1 = chopSubstring(tag, start, start += 30); + String artistv2 = (String) props.get("author"); + if (((artistv2==null) || (artistv2.length()==0)) && (artistv1 != null)) props.put("author",artistv1); + String albumv1 = chopSubstring(tag, start, start += 30); + String albumv2 = (String) props.get("album"); + if (((albumv2==null) || (albumv2.length()==0)) && (albumv1 != null)) props.put("album",albumv1); + String yearv1 = chopSubstring(tag, start, start += 4); + String yearv2 = (String) props.get("year"); + if (((yearv2==null) || (yearv2.length()==0)) && (yearv1 != null)) props.put("date",yearv1); + String commentv1 = chopSubstring(tag, start, start += 28); + String commentv2 = (String) props.get("comment"); + if (((commentv2==null) || (commentv2.length()==0)) && (commentv1 != null)) props.put("comment",commentv1); + String trackv1 = ""+((int) (frames[126] & 0xff)); + String trackv2 = (String) props.get("mp3.id3tag.track"); + if (((trackv2==null) || (trackv2.length()==0)) && (trackv1 != null)) props.put("mp3.id3tag.track",trackv1); + + int genrev1 = (int) (frames[127] & 0xff); + if ((genrev1 >=0) && (genrev10)) + { + if (code.equals("TALB")) props.put("album",value); + else if (code.equals("TIT2")) props.put("title",value); + else if (code.equals("TYER")) props.put("date",value); + // ID3v2.4 date fix. + else if (code.equals("TDRC")) props.put("date",value); + else if (code.equals("TPE1")) props.put("author",value); + else if (code.equals("TCOP")) props.put("copyright",value); + else if (code.equals("COMM")) props.put("comment",value); + else if (code.equals("TCON")) props.put("mp3.id3tag.genre",value); + else if (code.equals("TRCK")) props.put("mp3.id3tag.track",value); + else if (code.equals("TPOS")) props.put("mp3.id3tag.disc",value); + else if (code.equals("TCOM")) props.put("mp3.id3tag.composer",value); + else if (code.equals("TIT1")) props.put("mp3.id3tag.grouping",value); + else if (code.equals("TENC")) props.put("mp3.id3tag.encoded",value); + } + i=i+size-1; + } + // ID3v2.2. + else if ((scode.equals("TAL")) || (scode.equals("TT2")) || (scode.equals("TP1")) || (scode.equals("TYE")) || (scode.equals("TRK")) || (scode.equals("TPA")) || (scode.equals("TCR")) || (scode.equals("TCO")) || (scode.equals("TCM")) || (scode.equals("COM")) || (scode.equals("TT1")) || (scode.equals("TEN"))) + { + i=i+6; + size = (int) (0x00000000) + (bframes[i-3] << 16) + (bframes[i-2] << 8) + (bframes[i-1]); + if (scode.equals("COM")) value = parseText(bframes, i, size, 5); + else value = parseText(bframes,i, size, 1); + if ((value != null) && (value.length()>0)) + { + if (scode.equals("TAL")) props.put("album",value); + else if (scode.equals("TT2")) props.put("title",value); + else if (scode.equals("TYE")) props.put("date",value); + else if (scode.equals("TP1")) props.put("author",value); + else if (scode.equals("TCR")) props.put("copyright",value); + else if (scode.equals("COM")) props.put("comment",value); + else if (scode.equals("TCO")) props.put("mp3.id3tag.genre",value); + else if (scode.equals("TRK")) props.put("mp3.id3tag.track",value); + else if (scode.equals("TPA")) props.put("mp3.id3tag.disc",value); + else if (scode.equals("TCM")) props.put("mp3.id3tag.composer",value); + else if (scode.equals("TT1")) props.put("mp3.id3tag.grouping",value); + else if (scode.equals("TEN")) props.put("mp3.id3tag.encoded",value); + } + i=i+size-1; + } + else if (code.startsWith("ID3")) + { + // ID3v2 Header. + int v2version = (int) (bframes[3] & 0xFF); + props.put("mp3.id3tag.v2.version",String.valueOf(v2version)); + i=i+4; + } + } + } + catch (RuntimeException e) + { + // Ignore all parsing errors. + if (TDebug.TraceAudioFileReader) TDebug.out("Cannot parse ID3v2 :"+e.getMessage()); + } + if (TDebug.TraceAudioFileReader) TDebug.out("ID3v2 parsed"); + } + + /** + * Parse Text Frames. + * @param bframes + * @param offset + * @param size + * @param skip + * @return + */ + protected String parseText(byte[] bframes, int offset, int size, int skip) + { + String value = null; + try + { + String[] ENC_TYPES = {"ISO-8859-1", "UTF16","UTF-16BE", "UTF-8"}; + value = new String(bframes,offset+skip,size-skip,ENC_TYPES[bframes[offset]]); + value = chopSubstring(value,0,value.length()); + } + catch (UnsupportedEncodingException e) + { + if (TDebug.TraceAudioFileReader) TDebug.out("ID3v2 Encoding error :"+e.getMessage()); + } + return value; + } + + /** + * Load shoutcast (ICY) info. + * @param input + * @param props + * @throws IOException + */ + protected void loadShoutcastInfo(InputStream input, HashMap props) throws IOException + { + IcyInputStream icy = new IcyInputStream(new BufferedInputStream(input)); + HashMap metadata = icy.getTagHash(); + MP3Tag titleMP3Tag = icy.getTag("icy-name"); + if (titleMP3Tag != null) props.put("title",((String) titleMP3Tag.getValue()).trim()); + MP3Tag[] meta = icy.getTags(); + if (meta != null) + { + StringBuffer metaStr = new StringBuffer(); + for (int i=0;iAudioFormat parameters. + *
    + *
  • bitrate [Integer], bitrate in bits per seconds, average bitrate for VBR enabled stream. + *
  • vbr [Boolean], VBR flag. + *
+ */ + public Map properties() + { + return super.properties(); + } +} diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/MpegEncoding.java b/songdbj/javazoom/spi/mpeg/sampled/file/MpegEncoding.java new file mode 100644 index 0000000000..6306d9ec80 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/MpegEncoding.java @@ -0,0 +1,47 @@ +/* + * MpegEncoding. + * + * JavaZOOM : mp3spi@javazoom.net + * http://www.javazoom.net + * + *----------------------------------------------------------------------- + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + *---------------------------------------------------------------------- + */ + +package javazoom.spi.mpeg.sampled.file; + +import javax.sound.sampled.AudioFormat; + +/** + * Encodings used by the MPEG audio decoder. + */ +public class MpegEncoding extends AudioFormat.Encoding +{ + public static final AudioFormat.Encoding MPEG1L1 = new MpegEncoding("MPEG1L1"); + public static final AudioFormat.Encoding MPEG1L2 = new MpegEncoding("MPEG1L2"); + public static final AudioFormat.Encoding MPEG1L3 = new MpegEncoding("MPEG1L3"); + public static final AudioFormat.Encoding MPEG2L1 = new MpegEncoding("MPEG2L1"); + public static final AudioFormat.Encoding MPEG2L2 = new MpegEncoding("MPEG2L2"); + public static final AudioFormat.Encoding MPEG2L3 = new MpegEncoding("MPEG2L3"); + public static final AudioFormat.Encoding MPEG2DOT5L1 = new MpegEncoding("MPEG2DOT5L1"); + public static final AudioFormat.Encoding MPEG2DOT5L2 = new MpegEncoding("MPEG2DOT5L2"); + public static final AudioFormat.Encoding MPEG2DOT5L3 = new MpegEncoding("MPEG2DOT5L3"); + + public MpegEncoding(String strName) + { + super(strName); + } +} diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/MpegFileFormatType.java b/songdbj/javazoom/spi/mpeg/sampled/file/MpegFileFormatType.java new file mode 100644 index 0000000000..2c59ad8621 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/MpegFileFormatType.java @@ -0,0 +1,40 @@ +/* + * MpegFileFormatType. + * + * JavaZOOM : mp3spi@javazoom.net + * http://www.javazoom.net + * + *----------------------------------------------------------------------- + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + *---------------------------------------------------------------------- + */ + +package javazoom.spi.mpeg.sampled.file; + +import javax.sound.sampled.AudioFileFormat; + +/** + * FileFormatTypes used by the MPEG audio decoder. + */ +public class MpegFileFormatType extends AudioFileFormat.Type +{ + public static final AudioFileFormat.Type MPEG = new MpegFileFormatType("MPEG", "mpeg"); + public static final AudioFileFormat.Type MP3 = new MpegFileFormatType("MP3", "mp3"); + + public MpegFileFormatType(String strName, String strExtension) + { + super(strName, strExtension); + } +} diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/tag/IcyInputStream.java b/songdbj/javazoom/spi/mpeg/sampled/file/tag/IcyInputStream.java new file mode 100644 index 0000000000..22aa4439fe --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/tag/IcyInputStream.java @@ -0,0 +1,412 @@ +/* + * IcyInputStream. + * + * jicyshout : http://sourceforge.net/projects/jicyshout/ + * + * JavaZOOM : mp3spi@javazoom.net + * http://www.javazoom.net + * + *----------------------------------------------------------------------- + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + *---------------------------------------------------------------------- + */ + +package javazoom.spi.mpeg.sampled.file.tag; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.HashMap; +import java.util.StringTokenizer; + +/** An BufferedInputStream that parses Shoutcast's "icy" metadata + from the stream. Gets headers at the beginning and if the + "icy-metaint" tag is found, it parses and strips in-stream + metadata. +

+ The deal with metaint: Icy streams don't try to put + tags between MP3 frames the way that ID3 does. Instead, it + requires the client to strip metadata from the stream before + it hits the decoder. You get an + icy-metaint name/val in the beginning of the + stream iff you sent "Icy-Metadata" with value "1" in the + request headers (SimpleMP3DataSource does this if the + "parseStreamMetadata" boolean is true). If this is the case + then the value of icy-metaint is the amount of real data + between metadata blocks. Each block begins with an int + indicating how much metadata there is -- the block is this + value times 16 (it can be, and often is, 0). +

+ Originally thought that "icy" implied Icecast, but this is + completely wrong -- real Icecast servers, found through + www.icecast.net and typified by URLs with a trailing directory + (like CalArts School of Music - http://65.165.174.100:8000/som) + do not have the "ICY 200 OK" magic string or any of the + CRLF-separated headers. Apparently, "icy" means "Shoutcast". + Yep, that's weird. + @author Chris Adamson, invalidname@mac.com + */ +public class IcyInputStream + extends BufferedInputStream + implements MP3MetadataParser { + + public static boolean DEBUG = false; + + MP3TagParseSupport tagParseSupport; + /** inline tags are delimited by ';', also filter out + null bytes + */ + protected static final String INLINE_TAG_SEPARATORS = ";\u0000"; + /* looks like icy streams start start with + ICY 200 OK\r\n + then the tags are like + icy-notice1:
This stream requires Winamp
\r\n + icy-notice2:SHOUTcast Distributed Network Audio Server/win32 v1.8.2
\r\n + icy-name:Core-upt Radio\r\n + icy-genre:Punk Ska Emo\r\n + icy-url:http://www.core-uptrecords.com\r\n + icy-pub:1\r\n + icy-metaint:8192\r\n + icy-br:56\r\n + \r\n (signifies end of headers) + we only get icy-metaint if the http request that created + this stream sent the header "icy-metadata:1" + // + in in-line metadata, we read a byte that tells us how + many 16-byte blocks there are (presumably, we still use + \r\n for the separator... the block is padded out with + 0x00's that we can ignore) + + // when server is full/down/etc, we get the following for + // one of the notice lines: + icy-notice2:This server has reached its user limit
+ or + icy-notice2:The resource requested is currently unavailable
+ */ + /** Tags that have been discovered in the stream. + */ + HashMap tags; + /** Buffer for readCRLF line... note this limits lines to + 1024 chars (I've read that WinAmp barfs at 128, so + this is generous) + */ + protected byte[] crlfBuffer = new byte[1024]; + /** value of the "metaint" tag, which tells us how many bytes + of real data are between the metadata tags. if -1, this stream + does not have metadata after the header. + */ + protected int metaint = -1; + /** how many bytes of real data remain before the next + block of metadata. Only meaningful if metaint != -1. + */ + protected int bytesUntilNextMetadata = -1; + // TODO: comment for constructor + /** Reads the initial headers of the stream and adds + tags appropriatly. Gets set up to find, read, + and strip blocks of in-line metadata if the + icy-metaint header is found. + */ + public IcyInputStream(InputStream in) throws IOException { + super(in); + tags = new HashMap(); + tagParseSupport = new MP3TagParseSupport(); + // read the initial tags here, including the metaint + // and set the counter for how far we read until + // the next metadata block (if any). + readInitialHeaders(); + IcyTag metaIntTag = (IcyTag) getTag("icy-metaint"); + if (DEBUG) System.out.println("METATAG:"+metaIntTag); + if (metaIntTag != null) { + String metaIntString = metaIntTag.getValueAsString(); + try { + metaint = Integer.parseInt(metaIntString.trim()); + if (DEBUG) System.out.println("METAINT:"+metaint); + bytesUntilNextMetadata = metaint; + } + catch (NumberFormatException nfe) { + } + } + } + + /** + * IcyInputStream constructor for know meta-interval (Icecast 2) + * @param in + * @param metaint + * @throws IOException + */ + public IcyInputStream(InputStream in, String metaIntString) throws IOException { + super(in); + tags = new HashMap(); + tagParseSupport = new MP3TagParseSupport(); + try + { + metaint = Integer.parseInt(metaIntString.trim()); + if (DEBUG) System.out.println("METAINT:"+metaint); + bytesUntilNextMetadata = metaint; + } + catch (NumberFormatException nfe) { + } + } + + /** Assuming we're at the top of the stream, read lines one + by one until we hit a completely blank \r\n. Parse the + data as IcyTags. + */ + protected void readInitialHeaders() throws IOException { + String line = null; + while (!((line = readCRLFLine()).equals(""))) { + int colonIndex = line.indexOf(':'); + // does it have a ':' separator + if (colonIndex == -1) + continue; + IcyTag tag = + new IcyTag( + line.substring(0, colonIndex), + line.substring(colonIndex + 1)); + //System.out.println(tag); + addTag(tag); + } + } + /** Read everything up to the next CRLF, return it as + a String. + */ + protected String readCRLFLine() throws IOException { + int i = 0; + for (; i < crlfBuffer.length; i++) { + byte aByte = (byte) read(); + if (aByte == '\r') { + // possible end of line + byte anotherByte = (byte) read(); + i++; // since we read again + if (anotherByte == '\n') { + break; // break out of while + } + else { + // oops, not end of line - put these in array + crlfBuffer[i - 1] = aByte; + crlfBuffer[i] = anotherByte; + } + } + else { + // if not \r + crlfBuffer[i] = aByte; + } + } // for + // get the string from the byte[]. i is 1 too high because of + // read-ahead in crlf block + return new String(crlfBuffer, 0, i - 1); + } + /** Reads and returns a single byte. + If the next byte is a metadata block, then that + block is read, stripped, and parsed before reading + and returning the first byte after the metadata block. + */ + public int read() throws IOException { + if (bytesUntilNextMetadata > 0) { + bytesUntilNextMetadata--; + return super.read(); + } + else if (bytesUntilNextMetadata == 0) { + // we need to read next metadata block + readMetadata(); + bytesUntilNextMetadata = metaint - 1; + // -1 because we read byte on next line + return super.read(); + } + else { + // no metadata in this stream + return super.read(); + } + } + /** Reads a block of bytes. If the next byte is known + to be a block of metadata, then that is read, parsed, + and stripped, and then a block of bytes is read and + returned. + Otherwise, it may read up to but + not into the next metadata block if + bytesUntilNextMetadata < length + */ + public int read(byte[] buf, int offset, int length) throws IOException { + // if not on metadata, do the usual read so long as we + // don't read past metadata + if (bytesUntilNextMetadata > 0) { + int adjLength = Math.min(length, bytesUntilNextMetadata); + int got = super.read(buf, offset, adjLength); + bytesUntilNextMetadata -= got; + return got; + } + else if (bytesUntilNextMetadata == 0) { + // read/parse the metadata + readMetadata(); + // now as above, except that we reset + // bytesUntilNextMetadata differently + + //int adjLength = Math.min(length, bytesUntilNextMetadata); + //int got = super.read(buf, offset, adjLength); + //bytesUntilNextMetadata = metaint - got; + + // Chop Fix - JavaZOOM (3 lines above seem buggy) + bytesUntilNextMetadata = metaint; + int adjLength = Math.min(length, bytesUntilNextMetadata); + int got = super.read(buf, offset, adjLength); + bytesUntilNextMetadata -= got; + // End fix - JavaZOOM + + + return got; + } + else { + // not even reading metadata + return super.read(buf, offset, length); + } + } + /** trivial return read (buf, 0, buf.length) + */ + public int read(byte[] buf) throws IOException { + return read(buf, 0, buf.length); + } + /** Read the next segment of metadata. The stream must + be right on the segment, ie, the next byte to read is + the metadata block count. The metadata is parsed and + new tags are added with addTag(), which fires events + */ + protected void readMetadata() throws IOException { + int blockCount = super.read(); + if (DEBUG) System.out.println("BLOCKCOUNT:"+blockCount); + // System.out.println ("blocks to read: " + blockCount); + int byteCount = (blockCount * 16); // 16 bytes per block + if (byteCount < 0) + return; // WTF?! + byte[] metadataBlock = new byte[byteCount]; + int index = 0; + // build an array of this metadata + while (byteCount > 0) { + int bytesRead = super.read(metadataBlock, index, byteCount); + index += bytesRead; + byteCount -= bytesRead; + } + // now parse it + if (blockCount > 0) + parseInlineIcyTags(metadataBlock); + } // readMetadata + /** Parse metadata from an in-stream "block" of bytes, add + a tag for each one. +

+ Hilariously, the inline data format is totally different + than the top-of-stream header. For example, here's a + block I saw on "Final Fantasy Radio": +

+	StreamTitle='Final Fantasy 8 - Nobuo Uematsu - Blue Fields';StreamUrl='';
+	
+ In other words: +
    +
  1. Tags are delimited by semicolons +
  2. Keys/values are delimited by equals-signs +
  3. Values are wrapped in single-quotes +
  4. Key names are in SentenceCase, not lowercase-dashed +
+ */ + protected void parseInlineIcyTags(byte[] tagBlock) { + String blockString = new String(tagBlock); + if (DEBUG) System.out.println("BLOCKSTR:"+blockString); + StringTokenizer izer = + new StringTokenizer(blockString, INLINE_TAG_SEPARATORS); + int i = 0; + while (izer.hasMoreTokens()) { + String tagString = izer.nextToken(); + int separatorIdx = tagString.indexOf('='); + if (separatorIdx == -1) + continue; // bogus tagString if no '=' + // try to strip single-quotes around value, if present + int valueStartIdx = + (tagString.charAt(separatorIdx + 1) == '\'') + ? separatorIdx + 2 + : separatorIdx + 1; + int valueEndIdx = + (tagString.charAt(tagString.length() - 1)) == '\'' + ? tagString.length() - 1 + : tagString.length(); + String name = tagString.substring(0, separatorIdx); + String value = tagString.substring(valueStartIdx, valueEndIdx); + // System.out.println (i++ + " " + name + ":" + value); + IcyTag tag = new IcyTag(name, value); + addTag(tag); + } + } + /** adds the tag to the HashMap of tags we have encountered + either in-stream or as headers, replacing any previous + tag with this name. + */ + protected void addTag(IcyTag tag) { + tags.put(tag.getName(), tag); + // fire this as an event too + tagParseSupport.fireTagParsed(this, tag); + } + /** Get the named tag from the HashMap of headers and + in-line tags. Null if no such tag has been encountered. + */ + public MP3Tag getTag(String tagName) { + return (MP3Tag) tags.get(tagName); + } + /** Get all tags (headers or in-stream) encountered thus far. + */ + public MP3Tag[] getTags() { + return (MP3Tag[]) tags.values().toArray(new MP3Tag[0]); + } + /** Returns a HashMap of all headers and in-stream tags + parsed so far. + */ + public HashMap getTagHash() { + return tags; + } + /** Adds a TagParseListener to be notified when this stream + parses MP3Tags. + */ + public void addTagParseListener(TagParseListener tpl) { + tagParseSupport.addTagParseListener(tpl); + } + /** Removes a TagParseListener, so it won't be notified when + this stream parses MP3Tags. + */ + public void removeTagParseListener(TagParseListener tpl) { + tagParseSupport.removeTagParseListener(tpl); + } + /** Quickie unit-test. + */ + public static void main(String args[]) { + byte[] chow = new byte[200]; + if (args.length != 1) { + //System.out.println("Usage: IcyInputStream "); + return; + } + try { + URL url = new URL(args[0]); + URLConnection conn = url.openConnection(); + conn.setRequestProperty("Icy-Metadata", "1"); + IcyInputStream icy = + new IcyInputStream( + new BufferedInputStream(conn.getInputStream())); + while (icy.available() > -1) { + // icy.read(); + icy.read(chow, 0, chow.length); + } + } + catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/tag/IcyTag.java b/songdbj/javazoom/spi/mpeg/sampled/file/tag/IcyTag.java new file mode 100644 index 0000000000..bbe70f1f3c --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/tag/IcyTag.java @@ -0,0 +1,42 @@ +/* + * IcyTag. + * + * jicyshout : http://sourceforge.net/projects/jicyshout/ + * + * JavaZOOM : mp3spi@javazoom.net + * http://www.javazoom.net + * + *----------------------------------------------------------------------- + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + *---------------------------------------------------------------------- + */ + +package javazoom.spi.mpeg.sampled.file.tag; +/** + * A tag parsed from an icecast tag. + */ +public class IcyTag extends MP3Tag implements StringableTag { + /** Create a new tag, from the parsed name and (String) value. + It looks like all Icecast tags are Strings (safe to assume + this going forward?) + */ + public IcyTag(String name, String stringValue) { + super(name, stringValue); + } + // so far as I know, all Icecast tags are strings + public String getValueAsString() { + return (String) getValue(); + } +} diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3MetadataParser.java b/songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3MetadataParser.java new file mode 100644 index 0000000000..81511064b8 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3MetadataParser.java @@ -0,0 +1,50 @@ +/* + * MP3MetadataParser. + * + * jicyshout : http://sourceforge.net/projects/jicyshout/ + * + * JavaZOOM : mp3spi@javazoom.net + * http://www.javazoom.net + * + *----------------------------------------------------------------------- + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + *---------------------------------------------------------------------- + */ + +package javazoom.spi.mpeg.sampled.file.tag; + +/** An object that fires off TagParseEvents as they are parsed + from a stream, ServerSocket, or other metadata source + */ +public interface MP3MetadataParser { + /** Adds a TagParseListener to be notified when this object + parses MP3Tags. + */ + public void addTagParseListener(TagParseListener tpl); + /** Removes a TagParseListener, so it won't be notified when + this object parses MP3Tags. + */ + public void removeTagParseListener(TagParseListener tpl); + /** Get all tags (headers or in-stream) encountered thusfar. + This is included in this otherwise Listener-like scheme + because most standards are a mix of start-of-stream + metadata tags (like the http headers or the stuff at the + top of an ice stream) and inline data. Implementations should + hang onto all tags they parse and provide them with this + call. Callers should first use this call to get initial + tags, then subscribe for events as the stream continues. + */ + public MP3Tag[] getTags(); +} diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3Tag.java b/songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3Tag.java new file mode 100644 index 0000000000..b545356240 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3Tag.java @@ -0,0 +1,52 @@ +/* + * MP3Tag. + * + * jicyshout : http://sourceforge.net/projects/jicyshout/ + * + * JavaZOOM : mp3spi@javazoom.net + * http://www.javazoom.net + * + *----------------------------------------------------------------------- + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + *---------------------------------------------------------------------- + */ + +package javazoom.spi.mpeg.sampled.file.tag; + +/** An individual piece of mp3 metadata, as a name/value pair. + Abstract just so that subclasses will indicate their + tagging scheme (Icy, ID3, etc.). + */ +public abstract class MP3Tag extends Object { + protected String name; + protected Object value; + public MP3Tag(String name, Object value) { + this.name = name; + this.value = value; + } + public String getName() { + return name; + } + public Object getValue() { + return value; + } + public String toString() { + return getClass().getName() + + " -- " + + getName() + + ":" + + getValue().toString(); + } +} diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3TagParseSupport.java b/songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3TagParseSupport.java new file mode 100644 index 0000000000..1ab6525512 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/tag/MP3TagParseSupport.java @@ -0,0 +1,62 @@ +/* + * MP3TagParseSupport. + * + * jicyshout : http://sourceforge.net/projects/jicyshout/ + * + * JavaZOOM : mp3spi@javazoom.net + * http://www.javazoom.net + * + *----------------------------------------------------------------------- + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + *---------------------------------------------------------------------- + */ + +package javazoom.spi.mpeg.sampled.file.tag; + +import java.util.ArrayList; +/** +*/ +public class MP3TagParseSupport extends Object { + ArrayList tagParseListeners; + /** trivial constructor, sets up listeners list. + */ + public MP3TagParseSupport() { + super(); + tagParseListeners = new ArrayList(); + } + /** Adds a TagParseListener to be notified when a stream + parses MP3Tags. + */ + public void addTagParseListener(TagParseListener tpl) { + tagParseListeners.add(tpl); + } + /** Removes a TagParseListener, so it won't be notified when + a stream parses MP3Tags. + */ + public void removeTagParseListener(TagParseListener tpl) { + tagParseListeners.add(tpl); + } + /** Fires the given event to all registered listeners + */ + public void fireTagParseEvent(TagParseEvent tpe) { + for (int i = 0; i < tagParseListeners.size(); i++) { + TagParseListener l = (TagParseListener) tagParseListeners.get(i); + l.tagParsed(tpe); + } + } + public void fireTagParsed(Object source, MP3Tag tag) { + fireTagParseEvent(new TagParseEvent(source, tag)); + } +} diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/tag/StringableTag.java b/songdbj/javazoom/spi/mpeg/sampled/file/tag/StringableTag.java new file mode 100644 index 0000000000..685c5207f9 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/tag/StringableTag.java @@ -0,0 +1,36 @@ +/* + * StringableTag. + * + * jicyshout : http://sourceforge.net/projects/jicyshout/ + * + * JavaZOOM : mp3spi@javazoom.net + * http://www.javazoom.net + * + *----------------------------------------------------------------------- + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + *---------------------------------------------------------------------- + */ + +package javazoom.spi.mpeg.sampled.file.tag; + +/** Indicates that the value of a tag is a string, and + provides a getValueAsString() method to get it. + Appropriate for tags like artist, title, etc. + */ +public interface StringableTag { + /** Return the value of this tag as a string. + */ + public String getValueAsString(); +} diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/tag/TagParseEvent.java b/songdbj/javazoom/spi/mpeg/sampled/file/tag/TagParseEvent.java new file mode 100644 index 0000000000..97e9ec1e19 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/tag/TagParseEvent.java @@ -0,0 +1,44 @@ +/* + * TagParseEvent. + * + * jicyshout : http://sourceforge.net/projects/jicyshout/ + * + * JavaZOOM : mp3spi@javazoom.net + * http://www.javazoom.net + * + *----------------------------------------------------------------------- + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + *---------------------------------------------------------------------- + */ + +package javazoom.spi.mpeg.sampled.file.tag; + +import java.util.EventObject; +/** Event to indicate that an MP3 tag was received through + some means (parsed in stream, received via UDP, whatever) + and converted into an MP3Tag object. + */ +public class TagParseEvent extends EventObject { + protected MP3Tag tag; + public TagParseEvent(Object source, MP3Tag tag) { + super(source); + this.tag = tag; + } + /** Get the tag that was parsed. + */ + public MP3Tag getTag() { + return tag; + } +} diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/tag/TagParseListener.java b/songdbj/javazoom/spi/mpeg/sampled/file/tag/TagParseListener.java new file mode 100644 index 0000000000..a630827297 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/tag/TagParseListener.java @@ -0,0 +1,37 @@ +/* + * TagParseListener. + * + * jicyshout : http://sourceforge.net/projects/jicyshout/ + * + * JavaZOOM : mp3spi@javazoom.net + * http://www.javazoom.net + * + *----------------------------------------------------------------------- + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + *---------------------------------------------------------------------- + */ + +package javazoom.spi.mpeg.sampled.file.tag; + +import java.util.EventListener; +/** EventListener to be implemented by objects that want to + get callbacks when MP3 tags are received. + */ +public interface TagParseListener extends EventListener { + /** Called when a tag is found (parsed from the stream, + received via UDP, etc.) + */ + public void tagParsed(TagParseEvent tpe); +} diff --git a/songdbj/javazoom/spi/vorbis/sampled/convert/DecodedVorbisAudioInputStream.java b/songdbj/javazoom/spi/vorbis/sampled/convert/DecodedVorbisAudioInputStream.java new file mode 100644 index 0000000000..b8e8577e13 --- /dev/null +++ b/songdbj/javazoom/spi/vorbis/sampled/convert/DecodedVorbisAudioInputStream.java @@ -0,0 +1,519 @@ +/* + * DecodedVorbisAudioInputStream + * + * JavaZOOM : vorbisspi@javazoom.net + * http://www.javazoom.net + * + * ---------------------------------------------------------------------------- + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * ---------------------------------------------------------------------------- + */ + +package javazoom.spi.vorbis.sampled.convert; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; + +import javazoom.spi.PropertiesContainer; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.convert.TAsynchronousFilteredAudioInputStream; + +import com.jcraft.jogg.Packet; +import com.jcraft.jogg.Page; +import com.jcraft.jogg.StreamState; +import com.jcraft.jogg.SyncState; +import com.jcraft.jorbis.Block; +import com.jcraft.jorbis.Comment; +import com.jcraft.jorbis.DspState; +import com.jcraft.jorbis.Info; + +/** + * This class implements the Vorbis decoding. + */ +public class DecodedVorbisAudioInputStream extends TAsynchronousFilteredAudioInputStream implements PropertiesContainer +{ + private InputStream oggBitStream_ = null; + + private SyncState oggSyncState_ = null; + private StreamState oggStreamState_ = null; + private Page oggPage_ = null; + private Packet oggPacket_ = null; + private Info vorbisInfo = null; + private Comment vorbisComment = null; + private DspState vorbisDspState = null; + private Block vorbisBlock = null; + + static final int playState_NeedHeaders = 0; + static final int playState_ReadData = 1; + static final int playState_WriteData = 2; + static final int playState_Done = 3; + static final int playState_BufferFull = 4; + static final int playState_Corrupt = -1; + private int playState; + + private int bufferMultiple_ = 4; + private int bufferSize_ = bufferMultiple_ * 256 * 2; + private int convsize = bufferSize_ * 2; + private byte[] convbuffer = new byte[convsize]; + private byte[] buffer = null; + private int bytes = 0; + private float[][][] _pcmf = null; + private int[] _index = null; + private int index = 0; + private int i = 0; + // bout is now a global so that we can continue from when we have a buffer full. + int bout = 0; + + private HashMap properties = null; + private long currentBytes = 0; + + /** + * Constructor. + */ + public DecodedVorbisAudioInputStream(AudioFormat outputFormat, AudioInputStream bitStream) + { + super(outputFormat, -1); + this.oggBitStream_ = bitStream; + init_jorbis(); + index = 0; + playState = playState_NeedHeaders; + properties = new HashMap(); + } + + /** + * Initializes all the jOrbis and jOgg vars that are used for song playback. + */ + private void init_jorbis() + { + oggSyncState_ = new SyncState(); + oggStreamState_ = new StreamState(); + oggPage_ = new Page(); + oggPacket_ = new Packet(); + vorbisInfo = new Info(); + vorbisComment = new Comment(); + vorbisDspState = new DspState(); + vorbisBlock = new Block(vorbisDspState); + buffer = null; + bytes = 0; + currentBytes = 0L; + oggSyncState_.init(); + } + + /** + * Return dynamic properties. + * + *
    + *
  • ogg.position.byte [Long], current position in bytes in the stream. + *
+ */ + public Map properties() + { + properties.put("ogg.position.byte",new Long(currentBytes)); + return properties; + } + /** + * Main loop. + */ + public void execute() + { + if(TDebug.TraceAudioConverter) + { + switch(playState) + { + case playState_NeedHeaders: + TDebug.out("playState = playState_NeedHeaders"); + break; + case playState_ReadData: + TDebug.out("playState = playState_ReadData"); + break; + case playState_WriteData: + TDebug.out("playState = playState_WriteData"); + break; + case playState_Done: + TDebug.out("playState = playState_Done"); + break; + case playState_BufferFull: + TDebug.out("playState = playState_BufferFull"); + break; + case playState_Corrupt: + TDebug.out("playState = playState_Corrupt"); + break; + } + } + // This code was developed by the jCraft group, as JOrbisPlayer.java, slightly + // modified by jOggPlayer developer and adapted by JavaZOOM to suit the JavaSound + // SPI. Then further modified by Tom Kimpton to correctly play ogg files that + // would hang the player. + switch(playState) + { + case playState_NeedHeaders: + try + { + // Headers (+ Comments). + readHeaders(); + } + catch(IOException ioe) + { + playState = playState_Corrupt; + return; + } + playState = playState_ReadData; + break; + + case playState_ReadData: + int result; + index = oggSyncState_.buffer(bufferSize_); + buffer = oggSyncState_.data; + bytes = readFromStream(buffer, index, bufferSize_); + if(TDebug.TraceAudioConverter) TDebug.out("More data : " + bytes); + if(bytes == -1) + { + playState = playState_Done; + if(TDebug.TraceAudioConverter) TDebug.out("Ogg Stream empty. Settings playState to playState_Done."); + break; + } + else + { + oggSyncState_.wrote(bytes); + if(bytes == 0) + { + if((oggPage_.eos() != 0) || (oggStreamState_.e_o_s != 0) || (oggPacket_.e_o_s != 0)) + { + if(TDebug.TraceAudioConverter) TDebug.out("oggSyncState wrote 0 bytes: settings playState to playState_Done."); + playState = playState_Done; + } + if(TDebug.TraceAudioConverter) TDebug.out("oggSyncState wrote 0 bytes: but stream not yet empty."); + break; + } + } + + result = oggSyncState_.pageout(oggPage_); + if(result == 0) + { + if(TDebug.TraceAudioConverter) TDebug.out("Setting playState to playState_ReadData."); + playState = playState_ReadData; + break; + } // need more data + if(result == -1) + { // missing or corrupt data at this page position + if(TDebug.TraceAudioConverter) TDebug.out("Corrupt or missing data in bitstream; setting playState to playState_ReadData"); + playState = playState_ReadData; + break; + } + + oggStreamState_.pagein(oggPage_); + + if(TDebug.TraceAudioConverter) TDebug.out("Setting playState to playState_WriteData."); + playState = playState_WriteData; + break; + + case playState_WriteData: + // Decoding ! + if(TDebug.TraceAudioConverter) TDebug.out("Decoding"); + while(true) + { + result = oggStreamState_.packetout(oggPacket_); + if(result == 0) + { + if(TDebug.TraceAudioConverter) TDebug.out("Packetout returned 0, going to read state."); + playState = playState_ReadData; + break; + } // need more data + else if(result == -1) + { + // missing or corrupt data at this page position + // no reason to complain; already complained above + if(TDebug.TraceAudioConverter) TDebug.out("Corrupt or missing data in packetout bitstream; going to read state..."); + // playState = playState_ReadData; + // break; + continue; + } + else + { + // we have a packet. Decode it + if(vorbisBlock.synthesis(oggPacket_) == 0) + { // test for success! + vorbisDspState.synthesis_blockin(vorbisBlock); + } + else + { + //if(TDebug.TraceAudioConverter) TDebug.out("vorbisBlock.synthesis() returned !0, going to read state"); + if(TDebug.TraceAudioConverter) TDebug.out("VorbisBlock.synthesis() returned !0, continuing."); + continue; + } + + outputSamples(); + if(playState == playState_BufferFull) + return; + + } // else result != -1 + } // while(true) + if(oggPage_.eos() != 0) + { + if(TDebug.TraceAudioConverter) TDebug.out("Settings playState to playState_Done."); + playState = playState_Done; + } + break; + case playState_BufferFull: + continueFromBufferFull(); + break; + + case playState_Corrupt: + if(TDebug.TraceAudioConverter) TDebug.out("Corrupt Song."); + // drop through to playState_Done... + case playState_Done: + oggStreamState_.clear(); + vorbisBlock.clear(); + vorbisDspState.clear(); + vorbisInfo.clear(); + oggSyncState_.clear(); + if(TDebug.TraceAudioConverter) TDebug.out("Done Song."); + try + { + if(oggBitStream_ != null) + { + oggBitStream_.close(); + } + getCircularBuffer().close(); + } + catch(Exception e) + { + if(TDebug.TraceAudioConverter) TDebug.out(e.getMessage()); + } + break; + } // switch + } + + /** + * This routine was extracted so that when the output buffer fills up, + * we can break out of the loop, let the music channel drain, then + * continue from where we were. + */ + private void outputSamples() + { + int samples; + while((samples = vorbisDspState.synthesis_pcmout(_pcmf, _index)) > 0) + { + float[][] pcmf = _pcmf[0]; + bout = (samples < convsize ? samples : convsize); + double fVal = 0.0; + // convert doubles to 16 bit signed ints (host order) and + // interleave + for(i = 0; i < vorbisInfo.channels; i++) + { + int pointer = i * 2; + //int ptr=i; + int mono = _index[i]; + for(int j = 0; j < bout; j++) + { + fVal = pcmf[i][mono + j] * 32767.; + int val = (int) (fVal); + if(val > 32767) + { + val = 32767; + } + if(val < -32768) + { + val = -32768; + } + if(val < 0) + { + val = val | 0x8000; + } + convbuffer[pointer] = (byte) (val); + convbuffer[pointer + 1] = (byte) (val >>> 8); + pointer += 2 * (vorbisInfo.channels); + } + } + if(TDebug.TraceAudioConverter) TDebug.out("about to write: " + 2 * vorbisInfo.channels * bout); + if(getCircularBuffer().availableWrite() < 2 * vorbisInfo.channels * bout) + { + if(TDebug.TraceAudioConverter) TDebug.out("Too much data in this data packet, better return, let the channel drain, and try again..."); + playState = playState_BufferFull; + return; + } + getCircularBuffer().write(convbuffer, 0, 2 * vorbisInfo.channels * bout); + if(bytes < bufferSize_) + if(TDebug.TraceAudioConverter) TDebug.out("Finished with final buffer of music?"); + if(vorbisDspState.synthesis_read(bout) != 0) + { + if(TDebug.TraceAudioConverter) TDebug.out("VorbisDspState.synthesis_read returned -1."); + } + } // while(samples...) + playState = playState_ReadData; + } + + private void continueFromBufferFull() + { + if(getCircularBuffer().availableWrite() < 2 * vorbisInfo.channels * bout) + { + if(TDebug.TraceAudioConverter) TDebug.out("Too much data in this data packet, better return, let the channel drain, and try again..."); + // Don't change play state. + return; + } + getCircularBuffer().write(convbuffer, 0, 2 * vorbisInfo.channels * bout); + // Don't change play state. Let outputSamples change play state, if necessary. + outputSamples(); + } + /** + * Reads headers and comments. + */ + private void readHeaders() throws IOException + { + if(TDebug.TraceAudioConverter) TDebug.out("readHeaders("); + index = oggSyncState_.buffer(bufferSize_); + buffer = oggSyncState_.data; + bytes = readFromStream(buffer, index, bufferSize_); + if(bytes == -1) + { + if(TDebug.TraceAudioConverter) TDebug.out("Cannot get any data from selected Ogg bitstream."); + throw new IOException("Cannot get any data from selected Ogg bitstream."); + } + oggSyncState_.wrote(bytes); + if(oggSyncState_.pageout(oggPage_) != 1) + { + if(bytes < bufferSize_) + { + throw new IOException("EOF"); + } + if(TDebug.TraceAudioConverter) TDebug.out("Input does not appear to be an Ogg bitstream."); + throw new IOException("Input does not appear to be an Ogg bitstream."); + } + oggStreamState_.init(oggPage_.serialno()); + vorbisInfo.init(); + vorbisComment.init(); + if(oggStreamState_.pagein(oggPage_) < 0) + { + // error; stream version mismatch perhaps + if(TDebug.TraceAudioConverter) TDebug.out("Error reading first page of Ogg bitstream data."); + throw new IOException("Error reading first page of Ogg bitstream data."); + } + if(oggStreamState_.packetout(oggPacket_) != 1) + { + // no page? must not be vorbis + if(TDebug.TraceAudioConverter) TDebug.out("Error reading initial header packet."); + throw new IOException("Error reading initial header packet."); + } + if(vorbisInfo.synthesis_headerin(vorbisComment, oggPacket_) < 0) + { + // error case; not a vorbis header + if(TDebug.TraceAudioConverter) TDebug.out("This Ogg bitstream does not contain Vorbis audio data."); + throw new IOException("This Ogg bitstream does not contain Vorbis audio data."); + } + //int i = 0; + i = 0; + while(i < 2) + { + while(i < 2) + { + int result = oggSyncState_.pageout(oggPage_); + if(result == 0) + { + break; + } // Need more data + if(result == 1) + { + oggStreamState_.pagein(oggPage_); + while(i < 2) + { + result = oggStreamState_.packetout(oggPacket_); + if(result == 0) + { + break; + } + if(result == -1) + { + if(TDebug.TraceAudioConverter) TDebug.out("Corrupt secondary header. Exiting."); + throw new IOException("Corrupt secondary header. Exiting."); + } + vorbisInfo.synthesis_headerin(vorbisComment, oggPacket_); + i++; + } + } + } + index = oggSyncState_.buffer(bufferSize_); + buffer = oggSyncState_.data; + bytes = readFromStream(buffer, index, bufferSize_); + if(bytes == -1) + { + break; + } + if(bytes == 0 && i < 2) + { + if(TDebug.TraceAudioConverter) TDebug.out("End of file before finding all Vorbis headers!"); + throw new IOException("End of file before finding all Vorbis headers!"); + } + oggSyncState_.wrote(bytes); + } + + byte[][] ptr = vorbisComment.user_comments; + String currComment = ""; + + for(int j = 0; j < ptr.length; j++) + { + if(ptr[j] == null) + { + break; + } + currComment = (new String(ptr[j], 0, ptr[j].length - 1)).trim(); + if(TDebug.TraceAudioConverter) TDebug.out("Comment: " + currComment); + } + convsize = bufferSize_ / vorbisInfo.channels; + vorbisDspState.synthesis_init(vorbisInfo); + vorbisBlock.init(vorbisDspState); + _pcmf = new float[1][][]; + _index = new int[vorbisInfo.channels]; + } + + /** + * Reads from the oggBitStream_ a specified number of Bytes(bufferSize_) worth + * starting at index and puts them in the specified buffer[]. + * + * @param buffer + * @param index + * @param bufferSize_ + * @return the number of bytes read or -1 if error. + */ + private int readFromStream(byte[] buffer, int index, int bufferSize_) + { + int bytes = 0; + try + { + bytes = oggBitStream_.read(buffer, index, bufferSize_); + } + catch(Exception e) + { + if(TDebug.TraceAudioConverter) TDebug.out("Cannot Read Selected Song"); + bytes = -1; + } + currentBytes = currentBytes + bytes; + return bytes; + } + + /** + * Close the stream. + */ + public void close() throws IOException + { + super.close(); + oggBitStream_.close(); + } +} diff --git a/songdbj/javazoom/spi/vorbis/sampled/convert/VorbisFormatConversionProvider.java b/songdbj/javazoom/spi/vorbis/sampled/convert/VorbisFormatConversionProvider.java new file mode 100644 index 0000000000..d1321f2590 --- /dev/null +++ b/songdbj/javazoom/spi/vorbis/sampled/convert/VorbisFormatConversionProvider.java @@ -0,0 +1,244 @@ +/* + * VorbisFormatConversionProvider. + * + * JavaZOOM : vorbisspi@javazoom.net + * http://www.javazoom.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +package javazoom.spi.vorbis.sampled.convert; + +import java.util.Arrays; +import javazoom.spi.vorbis.sampled.file.VorbisEncoding; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; + +import org.tritonus.share.sampled.convert.TMatrixFormatConversionProvider; + +/** + * ConversionProvider for VORBIS files. + */ +public class VorbisFormatConversionProvider extends TMatrixFormatConversionProvider +{ + private static final AudioFormat[] INPUT_FORMATS = + { + new AudioFormat(VorbisEncoding.VORBISENC, 32000.0F, -1, 1, -1, -1, false), // 0 + new AudioFormat(VorbisEncoding.VORBISENC, 32000.0F, -1, 2, -1, -1, false), // 1 + new AudioFormat(VorbisEncoding.VORBISENC, 44100.0F, -1, 1, -1, -1, false), // 2 + new AudioFormat(VorbisEncoding.VORBISENC, 44100.0F, -1, 2, -1, -1, false), // 3 + new AudioFormat(VorbisEncoding.VORBISENC, 48000.0F, -1, 1, -1, -1, false), // 4 + new AudioFormat(VorbisEncoding.VORBISENC, 48000.0F, -1, 2, -1, -1, false), // 5 + + new AudioFormat(VorbisEncoding.VORBISENC, 16000.0F, -1, 1, -1, -1, false), // 18 + new AudioFormat(VorbisEncoding.VORBISENC, 16000.0F, -1, 2, -1, -1, false), // 19 + new AudioFormat(VorbisEncoding.VORBISENC, 22050.0F, -1, 1, -1, -1, false), // 20 + new AudioFormat(VorbisEncoding.VORBISENC, 22050.0F, -1, 2, -1, -1, false), // 21 + new AudioFormat(VorbisEncoding.VORBISENC, 24000.0F, -1, 1, -1, -1, false), // 22 + new AudioFormat(VorbisEncoding.VORBISENC, 24000.0F, -1, 2, -1, -1, false), // 23 + + new AudioFormat(VorbisEncoding.VORBISENC, 8000.0F, -1, 1, -1, -1, false), // 36 + new AudioFormat(VorbisEncoding.VORBISENC, 8000.0F, -1, 2, -1, -1, false), // 37 + new AudioFormat(VorbisEncoding.VORBISENC, 11025.0F, -1, 1, -1, -1, false), // 38 + new AudioFormat(VorbisEncoding.VORBISENC, 11025.0F, -1, 2, -1, -1, false), // 39 + new AudioFormat(VorbisEncoding.VORBISENC, 12000.0F, -1, 1, -1, -1, false), // 40 + new AudioFormat(VorbisEncoding.VORBISENC, 12000.0F, -1, 2, -1, -1, false), // 41 + }; + + private static final AudioFormat[] OUTPUT_FORMATS = + { + new AudioFormat(8000.0F, 16, 1, true, false), // 0 + new AudioFormat(8000.0F, 16, 1, true, true), // 1 + new AudioFormat(8000.0F, 16, 2, true, false), // 2 + new AudioFormat(8000.0F, 16, 2, true, true), // 3 + /* 24 and 32 bit not yet possible + new AudioFormat(8000.0F, 24, 1, true, false), + new AudioFormat(8000.0F, 24, 1, true, true), + new AudioFormat(8000.0F, 24, 2, true, false), + new AudioFormat(8000.0F, 24, 2, true, true), + new AudioFormat(8000.0F, 32, 1, true, false), + new AudioFormat(8000.0F, 32, 1, true, true), + new AudioFormat(8000.0F, 32, 2, true, false), + new AudioFormat(8000.0F, 32, 2, true, true), + */ + new AudioFormat(11025.0F, 16, 1, true, false), // 4 + new AudioFormat(11025.0F, 16, 1, true, true), // 5 + new AudioFormat(11025.0F, 16, 2, true, false), // 6 + new AudioFormat(11025.0F, 16, 2, true, true), // 7 + /* 24 and 32 bit not yet possible + new AudioFormat(11025.0F, 24, 1, true, false), + new AudioFormat(11025.0F, 24, 1, true, true), + new AudioFormat(11025.0F, 24, 2, true, false), + new AudioFormat(11025.0F, 24, 2, true, true), + new AudioFormat(11025.0F, 32, 1, true, false), + new AudioFormat(11025.0F, 32, 1, true, true), + new AudioFormat(11025.0F, 32, 2, true, false), + new AudioFormat(11025.0F, 32, 2, true, true), + */ + new AudioFormat(12000.0F, 16, 1, true, false), // 8 + new AudioFormat(12000.0F, 16, 1, true, true), // 9 + new AudioFormat(12000.0F, 16, 2, true, false), // 10 + new AudioFormat(12000.0F, 16, 2, true, true), // 11 + /* 24 and 32 bit not yet possible + new AudioFormat(12000.0F, 24, 1, true, false), + new AudioFormat(12000.0F, 24, 1, true, true), + new AudioFormat(12000.0F, 24, 2, true, false), + new AudioFormat(12000.0F, 24, 2, true, true), + new AudioFormat(12000.0F, 32, 1, true, false), + new AudioFormat(12000.0F, 32, 1, true, true), + new AudioFormat(12000.0F, 32, 2, true, false), + new AudioFormat(12000.0F, 32, 2, true, true), + */ + new AudioFormat(16000.0F, 16, 1, true, false), // 12 + new AudioFormat(16000.0F, 16, 1, true, true), // 13 + new AudioFormat(16000.0F, 16, 2, true, false), // 14 + new AudioFormat(16000.0F, 16, 2, true, true), // 15 + /* 24 and 32 bit not yet possible + new AudioFormat(16000.0F, 24, 1, true, false), + new AudioFormat(16000.0F, 24, 1, true, true), + new AudioFormat(16000.0F, 24, 2, true, false), + new AudioFormat(16000.0F, 24, 2, true, true), + new AudioFormat(16000.0F, 32, 1, true, false), + new AudioFormat(16000.0F, 32, 1, true, true), + new AudioFormat(16000.0F, 32, 2, true, false), + new AudioFormat(16000.0F, 32, 2, true, true), + */ + new AudioFormat(22050.0F, 16, 1, true, false), // 16 + new AudioFormat(22050.0F, 16, 1, true, true), // 17 + new AudioFormat(22050.0F, 16, 2, true, false), // 18 + new AudioFormat(22050.0F, 16, 2, true, true), // 19 + /* 24 and 32 bit not yet possible + new AudioFormat(22050.0F, 24, 1, true, false), + new AudioFormat(22050.0F, 24, 1, true, true), + new AudioFormat(22050.0F, 24, 2, true, false), + new AudioFormat(22050.0F, 24, 2, true, true), + new AudioFormat(22050.0F, 32, 1, true, false), + new AudioFormat(22050.0F, 32, 1, true, true), + new AudioFormat(22050.0F, 32, 2, true, false), + new AudioFormat(22050.0F, 32, 2, true, true), + */ + new AudioFormat(24000.0F, 16, 1, true, false), // 20 + new AudioFormat(24000.0F, 16, 1, true, true), // 21 + new AudioFormat(24000.0F, 16, 2, true, false), // 22 + new AudioFormat(24000.0F, 16, 2, true, true), // 23 + /* 24 and 32 bit not yet possible + new AudioFormat(24000.0F, 24, 1, true, false), + new AudioFormat(24000.0F, 24, 1, true, true), + new AudioFormat(24000.0F, 24, 2, true, false), + new AudioFormat(24000.0F, 24, 2, true, true), + new AudioFormat(24000.0F, 32, 1, true, false), + new AudioFormat(24000.0F, 32, 1, true, true), + new AudioFormat(24000.0F, 32, 2, true, false), + new AudioFormat(24000.0F, 32, 2, true, true), + */ + new AudioFormat(32000.0F, 16, 1, true, false), // 24 + new AudioFormat(32000.0F, 16, 1, true, true), // 25 + new AudioFormat(32000.0F, 16, 2, true, false), // 26 + new AudioFormat(32000.0F, 16, 2, true, true), // 27 + /* 24 and 32 bit not yet possible + new AudioFormat(32000.0F, 24, 1, true, false), + new AudioFormat(32000.0F, 24, 1, true, true), + new AudioFormat(32000.0F, 24, 2, true, false), + new AudioFormat(32000.0F, 24, 2, true, true), + new AudioFormat(32000.0F, 32, 1, true, false), + new AudioFormat(32000.0F, 32, 1, true, true), + new AudioFormat(32000.0F, 32, 2, true, false), + new AudioFormat(32000.0F, 32, 2, true, true), + */ + new AudioFormat(44100.0F, 16, 1, true, false), // 28 + new AudioFormat(44100.0F, 16, 1, true, true), // 29 + new AudioFormat(44100.0F, 16, 2, true, false), // 30 + new AudioFormat(44100.0F, 16, 2, true, true), // 31 + /* 24 and 32 bit not yet possible + new AudioFormat(44100.0F, 24, 1, true, false), + new AudioFormat(44100.0F, 24, 1, true, true), + new AudioFormat(44100.0F, 24, 2, true, false), + new AudioFormat(44100.0F, 24, 2, true, true), + new AudioFormat(44100.0F, 32, 1, true, false), + new AudioFormat(44100.0F, 32, 1, true, true), + new AudioFormat(44100.0F, 32, 2, true, false), + new AudioFormat(44100.0F, 32, 2, true, true), + */ + new AudioFormat(48000.0F, 16, 1, true, false), // 32 + new AudioFormat(48000.0F, 16, 1, true, true), // 33 + new AudioFormat(48000.0F, 16, 2, true, false), // 34 + new AudioFormat(48000.0F, 16, 2, true, true), // 35 + /* 24 and 32 bit not yet possible + new AudioFormat(48000.0F, 24, 1, true, false), + new AudioFormat(48000.0F, 24, 1, true, true), + new AudioFormat(48000.0F, 24, 2, true, false), + new AudioFormat(48000.0F, 24, 2, true, true), + new AudioFormat(48000.0F, 32, 1, true, false), + new AudioFormat(48000.0F, 32, 1, true, true), + new AudioFormat(48000.0F, 32, 2, true, false), + new AudioFormat(48000.0F, 32, 2, true, true), + */ + }; + + private static final boolean t = true; + private static final boolean f = false; + + /* + * One row for each source format. + */ + private static final boolean[][] CONVERSIONS = + { + {f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,t,t,f,f,f,f, f,f,f,f,f,f}, // 0 + {f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,t,t,f,f, f,f,f,f,f,f}, // 1 + {f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,t,t, f,f,f,f,f,f}, // 2 + {f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, t,t,f,f,f,f}, // 3 + {f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,t,t,f,f}, // 4 + {f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,t,t}, // 5 + + {f,f,f,f,f,f,f,f,f,f, f,f,t,t,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f}, // 18 + {f,f,f,f,f,f,f,f,f,f, f,f,f,f,t,t,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f}, // 19 + {f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,t,t,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f}, // 20 + {f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,t,t, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f}, // 21 + {f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, t,t,f,f,f,f,f,f,f,f, f,f,f,f,f,f}, // 22 + {f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,t,t,f,f,f,f,f,f, f,f,f,f,f,f}, // 23 + + {t,t,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f}, // 36 + {f,f,t,t,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f}, // 37 + {f,f,f,f,t,t,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f}, // 38 + {f,f,f,f,f,f,t,t,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f}, // 39 + {f,f,f,f,f,f,f,f,t,t, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f}, // 40 + {f,f,f,f,f,f,f,f,f,f, t,t,f,f,f,f,f,f,f,f, f,f,f,f,f,f,f,f,f,f, f,f,f,f,f,f}, // 41 + + }; + + /** + * Constructor. + */ + public VorbisFormatConversionProvider() + { + super(Arrays.asList(INPUT_FORMATS), Arrays.asList(OUTPUT_FORMATS), CONVERSIONS); + } + + /** + * Returns converted AudioInputStream. + */ + public AudioInputStream getAudioInputStream(AudioFormat targetFormat, AudioInputStream audioInputStream) + { + if (isConversionSupported(targetFormat, audioInputStream.getFormat())) + { + return new DecodedVorbisAudioInputStream(targetFormat, audioInputStream); + } + else + { + throw new IllegalArgumentException("conversion not supported"); + } + } +} diff --git a/songdbj/javazoom/spi/vorbis/sampled/file/VorbisAudioFileFormat.java b/songdbj/javazoom/spi/vorbis/sampled/file/VorbisAudioFileFormat.java new file mode 100644 index 0000000000..28b7c92a2a --- /dev/null +++ b/songdbj/javazoom/spi/vorbis/sampled/file/VorbisAudioFileFormat.java @@ -0,0 +1,85 @@ +/* + * VorbisAudioFileFormat. + * + * JavaZOOM : vorbisspi@javazoom.net + * http://www.javazoom.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +package javazoom.spi.vorbis.sampled.file; + +import java.util.Map; + +import javax.sound.sampled.AudioFormat; + +import org.tritonus.share.sampled.file.TAudioFileFormat; + +/** + * @author JavaZOOM + */ +public class VorbisAudioFileFormat extends TAudioFileFormat +{ + /** + * Contructor. + * @param type + * @param audioFormat + * @param nLengthInFrames + * @param nLengthInBytes + */ + public VorbisAudioFileFormat(Type type, AudioFormat audioFormat, int nLengthInFrames, int nLengthInBytes, Map properties) + { + super(type, audioFormat, nLengthInFrames, nLengthInBytes, properties); + } + + /** + * Ogg Vorbis audio file format parameters. + * Some parameters might be unavailable. So availability test is required before reading any parameter. + * + *
AudioFileFormat parameters. + *
    + *
  • duration [Long], duration in microseconds. + *
  • title [String], Title of the stream. + *
  • author [String], Name of the artist of the stream. + *
  • album [String], Name of the album of the stream. + *
  • date [String], The date (year) of the recording or release of the stream. + *
  • copyright [String], Copyright message of the stream. + *
  • comment [String], Comment of the stream. + *
+ *
Ogg Vorbis parameters. + *
    + *
  • ogg.length.bytes [Integer], length in bytes. + *
  • ogg.bitrate.min.bps [Integer], minimum bitrate. + *
  • ogg.bitrate.nominal.bps [Integer], nominal bitrate. + *
  • ogg.bitrate.max.bps [Integer], maximum bitrate. + *
  • ogg.channels [Integer], number of channels 1 : mono, 2 : stereo. + *
  • ogg.frequency.hz [Integer], sampling rate in hz. + *
  • ogg.version [Integer], version. + *
  • ogg.serial [Integer], serial number. + *
  • ogg.comment.track [String], track number. + *
  • ogg.comment.genre [String], genre field. + *
  • ogg.comment.encodedby [String], encoded by field. + *
  • ogg.comment.ext [String], extended comments (indexed): + *
    For instance : + *
    ogg.comment.ext.1=Something + *
    ogg.comment.ext.2=Another comment + *
+ */ + public Map properties() + { + return super.properties(); + } +} diff --git a/songdbj/javazoom/spi/vorbis/sampled/file/VorbisAudioFileReader.java b/songdbj/javazoom/spi/vorbis/sampled/file/VorbisAudioFileReader.java new file mode 100644 index 0000000000..40bc9cadee --- /dev/null +++ b/songdbj/javazoom/spi/vorbis/sampled/file/VorbisAudioFileReader.java @@ -0,0 +1,502 @@ +/* + * VorbisAudioFileReader. + * + * JavaZOOM : vorbisspi@javazoom.net + * http://www.javazoom.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +package javazoom.spi.vorbis.sampled.file; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.HashMap; +import java.util.StringTokenizer; + +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 org.tritonus.share.TDebug; +import org.tritonus.share.sampled.file.TAudioFileReader; + +import com.jcraft.jogg.Packet; +import com.jcraft.jogg.Page; +import com.jcraft.jogg.StreamState; +import com.jcraft.jogg.SyncState; +import com.jcraft.jorbis.Block; +import com.jcraft.jorbis.Comment; +import com.jcraft.jorbis.DspState; +import com.jcraft.jorbis.Info; +import com.jcraft.jorbis.JOrbisException; +import com.jcraft.jorbis.VorbisFile; + +/** + * This class implements the AudioFileReader class and provides an + * Ogg Vorbis file reader for use with the Java Sound Service Provider Interface. + */ +public class VorbisAudioFileReader extends TAudioFileReader +{ + private SyncState oggSyncState_ = null; + private StreamState oggStreamState_ = null; + private Page oggPage_ = null; + private Packet oggPacket_ = null; + private Info vorbisInfo = null; + private Comment vorbisComment = null; + private DspState vorbisDspState = null; + private Block vorbisBlock = null; + private int bufferMultiple_ = 4; + private int bufferSize_ = bufferMultiple_ * 256 * 2; + private int convsize = bufferSize_ * 2; + private byte[] convbuffer = new byte[convsize]; + private byte[] buffer = null; + private int bytes = 0; + private int rate = 0; + private int channels = 0; + + private int index = 0; + private InputStream oggBitStream_ = null; + + private static final int INITAL_READ_LENGTH = 64000; + private static final int MARK_LIMIT = INITAL_READ_LENGTH + 1; + + public VorbisAudioFileReader() + { + super(MARK_LIMIT, true); + } + + /** + * Return the AudioFileFormat from the given file. + */ + public AudioFileFormat getAudioFileFormat(File file) throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) TDebug.out("getAudioFileFormat(File file)"); + InputStream inputStream = null; + try + { + inputStream = new BufferedInputStream(new FileInputStream(file)); + inputStream.mark(MARK_LIMIT); + AudioFileFormat aff = getAudioFileFormat(inputStream); + inputStream.reset(); + // Get Vorbis file info such as length in seconds. + VorbisFile vf = new VorbisFile(file.getAbsolutePath()); + return getAudioFileFormat(inputStream,(int) file.length(), (int) Math.round((vf.time_total(-1))*1000)); + } + catch (JOrbisException e) + { + throw new IOException(e.getMessage()); + } + finally + { + if (inputStream != null) inputStream.close(); + } + } + + /** + * Return the AudioFileFormat from the given URL. + */ + public AudioFileFormat getAudioFileFormat(URL url) throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) TDebug.out("getAudioFileFormat(URL url)"); + InputStream inputStream = url.openStream(); + try + { + return getAudioFileFormat(inputStream); + } + finally + { + if (inputStream != null) inputStream.close(); + } + } + + /** + * Return the AudioFileFormat from the given InputStream. + */ + public AudioFileFormat getAudioFileFormat(InputStream inputStream) throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) TDebug.out("getAudioFileFormat(InputStream inputStream)"); + try + { + if (!inputStream.markSupported()) inputStream = new BufferedInputStream(inputStream); + inputStream.mark(MARK_LIMIT); + return getAudioFileFormat(inputStream, AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED); + } + finally + { + inputStream.reset(); + } + } + + /** + * Return the AudioFileFormat from the given InputStream and length in bytes. + */ + public AudioFileFormat getAudioFileFormat(InputStream inputStream, long medialength) throws UnsupportedAudioFileException, IOException + { + return getAudioFileFormat(inputStream, (int) medialength, AudioSystem.NOT_SPECIFIED); + } + + + /** + * Return the AudioFileFormat from the given InputStream, length in bytes and length in milliseconds. + */ + protected AudioFileFormat getAudioFileFormat(InputStream bitStream, int mediaLength, int totalms) throws UnsupportedAudioFileException, IOException + { + HashMap aff_properties = new HashMap(); + HashMap af_properties = new HashMap(); + if (totalms == AudioSystem.NOT_SPECIFIED) + { + totalms = 0; + } + if (totalms <= 0) + { + totalms = 0; + } + else + { + aff_properties.put("duration",new Long(totalms*1000)); + } + oggBitStream_ = bitStream; + init_jorbis(); + index = 0; + try + { + readHeaders(aff_properties, af_properties); + } + catch (IOException ioe) + { + if (TDebug.TraceAudioFileReader) + { + TDebug.out(ioe.getMessage()); + } + throw new UnsupportedAudioFileException(ioe.getMessage()); + } + + String dmp = vorbisInfo.toString(); + if (TDebug.TraceAudioFileReader) + { + TDebug.out(dmp); + } + int ind = dmp.lastIndexOf("bitrate:"); + int minbitrate = -1; + int nominalbitrate = -1; + int maxbitrate = -1; + if (ind != -1) + { + dmp = dmp.substring(ind + 8, dmp.length()); + StringTokenizer st = new StringTokenizer(dmp, ","); + if (st.hasMoreTokens()) + { + minbitrate = Integer.parseInt(st.nextToken()); + } + if (st.hasMoreTokens()) + { + nominalbitrate = Integer.parseInt(st.nextToken()); + } + if (st.hasMoreTokens()) + { + maxbitrate = Integer.parseInt(st.nextToken()); + } + } + if (nominalbitrate > 0) af_properties.put("bitrate",new Integer(nominalbitrate)); + af_properties.put("vbr",new Boolean(true)); + + if (minbitrate > 0) aff_properties.put("ogg.bitrate.min.bps",new Integer(minbitrate)); + if (maxbitrate > 0) aff_properties.put("ogg.bitrate.max.bps",new Integer(maxbitrate)); + if (nominalbitrate > 0) aff_properties.put("ogg.bitrate.nominal.bps",new Integer(nominalbitrate)); + if (vorbisInfo.channels > 0) aff_properties.put("ogg.channels",new Integer(vorbisInfo.channels)); + if (vorbisInfo.rate > 0) aff_properties.put("ogg.frequency.hz",new Integer(vorbisInfo.rate)); + if (mediaLength > 0) aff_properties.put("ogg.length.bytes",new Integer(mediaLength)); + aff_properties.put("ogg.version",new Integer(vorbisInfo.version)); + + AudioFormat.Encoding encoding = VorbisEncoding.VORBISENC; + AudioFormat format = new VorbisAudioFormat(encoding, vorbisInfo.rate, AudioSystem.NOT_SPECIFIED, vorbisInfo.channels, AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED, true,af_properties); + AudioFileFormat.Type type = VorbisFileFormatType.OGG; + return new VorbisAudioFileFormat(VorbisFileFormatType.OGG, format, AudioSystem.NOT_SPECIFIED, mediaLength,aff_properties); + } + + /** + * Return the AudioInputStream from the given InputStream. + */ + public AudioInputStream getAudioInputStream(InputStream inputStream) throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) TDebug.out("getAudioInputStream(InputStream inputStream)"); + return getAudioInputStream(inputStream, AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED); + } + + /** + * Return the AudioInputStream from the given InputStream. + */ + public AudioInputStream getAudioInputStream(InputStream inputStream, int medialength, int totalms) throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) TDebug.out("getAudioInputStream(InputStream inputStreamint medialength, int totalms)"); + try + { + if (!inputStream.markSupported()) inputStream = new BufferedInputStream(inputStream); + inputStream.mark(MARK_LIMIT); + AudioFileFormat audioFileFormat = getAudioFileFormat(inputStream, medialength, totalms); + inputStream.reset(); + return new AudioInputStream(inputStream, audioFileFormat.getFormat(), audioFileFormat.getFrameLength()); + } + catch (UnsupportedAudioFileException e) + { + inputStream.reset(); + throw e; + } + catch (IOException e) + { + inputStream.reset(); + throw e; + } + } + + /** + * Return the AudioInputStream from the given File. + */ + public AudioInputStream getAudioInputStream(File file) throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) TDebug.out("getAudioInputStream(File file)"); + InputStream inputStream = new FileInputStream(file); + try + { + return getAudioInputStream(inputStream); + } + catch (UnsupportedAudioFileException e) + { + if (inputStream != null) inputStream.close(); + throw e; + } + catch (IOException e) + { + if (inputStream != null) inputStream.close(); + throw e; + } + } + + /** + * Return the AudioInputStream from the given URL. + */ + public AudioInputStream getAudioInputStream(URL url) throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) TDebug.out("getAudioInputStream(URL url)"); + InputStream inputStream = url.openStream(); + try + { + return getAudioInputStream(inputStream); + } + catch (UnsupportedAudioFileException e) + { + if (inputStream != null) inputStream.close(); + throw e; + } + catch (IOException e) + { + if (inputStream != null) inputStream.close(); + throw e; + } + } + + /** + * Reads headers and comments. + */ + private void readHeaders(HashMap aff_properties, HashMap af_properties) throws IOException + { + if(TDebug.TraceAudioConverter) TDebug.out("readHeaders("); + index = oggSyncState_.buffer(bufferSize_); + buffer = oggSyncState_.data; + bytes = readFromStream(buffer, index, bufferSize_); + if(bytes == -1) + { + if(TDebug.TraceAudioConverter) TDebug.out("Cannot get any data from selected Ogg bitstream."); + throw new IOException("Cannot get any data from selected Ogg bitstream."); + } + oggSyncState_.wrote(bytes); + if(oggSyncState_.pageout(oggPage_) != 1) + { + if(bytes < bufferSize_) + { + throw new IOException("EOF"); + } + if(TDebug.TraceAudioConverter) TDebug.out("Input does not appear to be an Ogg bitstream."); + throw new IOException("Input does not appear to be an Ogg bitstream."); + } + oggStreamState_.init(oggPage_.serialno()); + vorbisInfo.init(); + vorbisComment.init(); + aff_properties.put("ogg.serial",new Integer(oggPage_.serialno())); + if(oggStreamState_.pagein(oggPage_) < 0) + { + // error; stream version mismatch perhaps + if(TDebug.TraceAudioConverter) TDebug.out("Error reading first page of Ogg bitstream data."); + throw new IOException("Error reading first page of Ogg bitstream data."); + } + if(oggStreamState_.packetout(oggPacket_) != 1) + { + // no page? must not be vorbis + if(TDebug.TraceAudioConverter) TDebug.out("Error reading initial header packet."); + throw new IOException("Error reading initial header packet."); + } + if(vorbisInfo.synthesis_headerin(vorbisComment, oggPacket_) < 0) + { + // error case; not a vorbis header + if(TDebug.TraceAudioConverter) TDebug.out("This Ogg bitstream does not contain Vorbis audio data."); + throw new IOException("This Ogg bitstream does not contain Vorbis audio data."); + } + int i = 0; + while(i < 2) + { + while(i < 2) + { + int result = oggSyncState_.pageout(oggPage_); + if(result == 0) + { + break; + } // Need more data + if(result == 1) + { + oggStreamState_.pagein(oggPage_); + while(i < 2) + { + result = oggStreamState_.packetout(oggPacket_); + if(result == 0) + { + break; + } + if(result == -1) + { + if(TDebug.TraceAudioConverter) TDebug.out("Corrupt secondary header. Exiting."); + throw new IOException("Corrupt secondary header. Exiting."); + } + vorbisInfo.synthesis_headerin(vorbisComment, oggPacket_); + i++; + } + } + } + index = oggSyncState_.buffer(bufferSize_); + buffer = oggSyncState_.data; + bytes = readFromStream(buffer, index, bufferSize_); + if(bytes == -1) + { + break; + } + if(bytes == 0 && i < 2) + { + if(TDebug.TraceAudioConverter) TDebug.out("End of file before finding all Vorbis headers!"); + throw new IOException("End of file before finding all Vorbis headers!"); + } + oggSyncState_.wrote(bytes); + } + // Read Ogg Vorbis comments. + byte[][] ptr = vorbisComment.user_comments; + String currComment = ""; + int c = 0; + for(int j = 0; j < ptr.length; j++) + { + if(ptr[j] == null) + { + break; + } + currComment = (new String(ptr[j], 0, ptr[j].length - 1)).trim(); + if(TDebug.TraceAudioConverter) TDebug.out(currComment); + if (currComment.toLowerCase().startsWith("artist")) + { + aff_properties.put("author",currComment.substring(7)); + } + else if (currComment.toLowerCase().startsWith("title")) + { + aff_properties.put("title",currComment.substring(6)); + } + else if (currComment.toLowerCase().startsWith("album")) + { + aff_properties.put("album",currComment.substring(6)); + } + else if (currComment.toLowerCase().startsWith("date")) + { + aff_properties.put("date",currComment.substring(5)); + } + else if (currComment.toLowerCase().startsWith("copyright")) + { + aff_properties.put("copyright",currComment.substring(10)); + } + else if (currComment.toLowerCase().startsWith("comment")) + { + aff_properties.put("comment",currComment.substring(8)); + } + else if (currComment.toLowerCase().startsWith("genre")) + { + aff_properties.put("ogg.comment.genre",currComment.substring(6)); + } + else if (currComment.toLowerCase().startsWith("tracknumber")) + { + aff_properties.put("ogg.comment.track",currComment.substring(12)); + } + else + { + c++; + aff_properties.put("ogg.comment.ext."+c,currComment); + } + aff_properties.put("ogg.comment.encodedby",new String(vorbisComment.vendor, 0, vorbisComment.vendor.length - 1)); + } + } + + /** + * Reads from the oggBitStream_ a specified number of Bytes(bufferSize_) worth + * starting at index and puts them in the specified buffer[]. + * + * @return the number of bytes read or -1 if error. + */ + private int readFromStream(byte[] buffer, int index, int bufferSize_) + { + int bytes = 0; + try + { + bytes = oggBitStream_.read(buffer, index, bufferSize_); + } + catch (Exception e) + { + if (TDebug.TraceAudioFileReader) + { + TDebug.out("Cannot Read Selected Song"); + } + bytes = -1; + } + return bytes; + } + + /** + * Initializes all the jOrbis and jOgg vars that are used for song playback. + */ + private void init_jorbis() + { + oggSyncState_ = new SyncState(); + oggStreamState_ = new StreamState(); + oggPage_ = new Page(); + oggPacket_ = new Packet(); + vorbisInfo = new Info(); + vorbisComment = new Comment(); + vorbisDspState = new DspState(); + vorbisBlock = new Block(vorbisDspState); + buffer = null; + bytes = 0; + oggSyncState_.init(); + } +} diff --git a/songdbj/javazoom/spi/vorbis/sampled/file/VorbisAudioFormat.java b/songdbj/javazoom/spi/vorbis/sampled/file/VorbisAudioFormat.java new file mode 100644 index 0000000000..829ab2f8cd --- /dev/null +++ b/songdbj/javazoom/spi/vorbis/sampled/file/VorbisAudioFormat.java @@ -0,0 +1,66 @@ +/* + * VorbisAudioFormat. + * + * JavaZOOM : vorbisspi@javazoom.net + * http://www.javazoom.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +package javazoom.spi.vorbis.sampled.file; + +import java.util.Map; + +import javax.sound.sampled.AudioFormat; + +import org.tritonus.share.sampled.TAudioFormat; + +/** + * @author JavaZOOM + */ +public class VorbisAudioFormat extends TAudioFormat +{ + /** + * Constructor. + * @param encoding + * @param nFrequency + * @param SampleSizeInBits + * @param nChannels + * @param FrameSize + * @param FrameRate + * @param isBigEndian + * @param properties + */ + public VorbisAudioFormat(AudioFormat.Encoding encoding, float nFrequency, int SampleSizeInBits, int nChannels, int FrameSize, float FrameRate, boolean isBigEndian, Map properties) + { + super(encoding, nFrequency, SampleSizeInBits, nChannels, FrameSize, FrameRate, isBigEndian, properties); + } + + /** + * Ogg Vorbis audio format parameters. + * Some parameters might be unavailable. So availability test is required before reading any parameter. + * + *
AudioFormat parameters. + *
    + *
  • bitrate [Integer], bitrate in bits per seconds, average bitrate for VBR enabled stream. + *
  • vbr [Boolean], VBR flag. + *
+ */ + public Map properties() + { + return super.properties(); + } +} diff --git a/songdbj/javazoom/spi/vorbis/sampled/file/VorbisEncoding.java b/songdbj/javazoom/spi/vorbis/sampled/file/VorbisEncoding.java new file mode 100644 index 0000000000..7800f1556d --- /dev/null +++ b/songdbj/javazoom/spi/vorbis/sampled/file/VorbisEncoding.java @@ -0,0 +1,41 @@ +/* + * VorbisEncoding. + * + * JavaZOOM : vorbisspi@javazoom.net + * http://www.javazoom.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +package javazoom.spi.vorbis.sampled.file; + +import javax.sound.sampled.AudioFormat; + +/** + * Encodings used by the VORBIS audio decoder. + */ +public class VorbisEncoding extends AudioFormat.Encoding +{ + public static final AudioFormat.Encoding VORBISENC = new VorbisEncoding("VORBISENC"); + + /** + * Constructors. + */ + public VorbisEncoding(String name) + { + super(name); + } +} diff --git a/songdbj/javazoom/spi/vorbis/sampled/file/VorbisFileFormatType.java b/songdbj/javazoom/spi/vorbis/sampled/file/VorbisFileFormatType.java new file mode 100644 index 0000000000..f006bbfe1d --- /dev/null +++ b/songdbj/javazoom/spi/vorbis/sampled/file/VorbisFileFormatType.java @@ -0,0 +1,41 @@ +/* + * VorbisFileFormatType. + * + * JavaZOOM : vorbisspi@javazoom.net + * http://www.javazoom.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +package javazoom.spi.vorbis.sampled.file; + +import javax.sound.sampled.AudioFileFormat; + +/** + * FileFormatTypes used by the VORBIS audio decoder. + */ +public class VorbisFileFormatType extends AudioFileFormat.Type +{ + public static final AudioFileFormat.Type VORBIS = new VorbisFileFormatType("VORBIS", "ogg"); + public static final AudioFileFormat.Type OGG = new VorbisFileFormatType("OGG", "ogg"); + /** + * Constructor. + */ + public VorbisFileFormatType(String name, String extension) + { + super(name, extension); + } +} -- cgit v1.2.3