001    /* 
002       Copyright (C) 2005 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.sound.sampled;
040    
041    import java.io.IOException;
042    import java.io.InputStream;
043    
044    /**
045     * This is an InputStream which is specialized for reading audio files.
046     * In particular it only allows operations to act on a multiple of
047     * the audio stream's frame size.
048     * @since 1.3
049     */
050    public class AudioInputStream extends InputStream
051    {
052      /** The format of the audio stream.  */
053      protected AudioFormat format;
054    
055      /** The length of the audio stream in frames.  */
056      protected long frameLength;
057    
058      /** The current frame position, starting from frame zero.  */ 
059      protected long framePos;
060    
061      /** The size of a frame in bytes.  */
062      protected int frameSize;
063    
064      // I wonder why this class doesn't inherit from FilterInputStream.
065      private InputStream input;
066    
067      // The saved frame position, used for mark/reset.
068      private long markedFramePos;
069    
070      /**
071       * Create a new AudioInputStream given an underlying InputStream,
072       * the audio format, and the length of the data in frames.  The
073       * frame size is taken from the format.
074       * @param is the underlying input stream
075       * @param fmt the format of the data
076       * @param length the length of the data in frames
077       */
078      public AudioInputStream(InputStream is, AudioFormat fmt, long length)
079      {
080        this.format = fmt;
081        this.frameLength = length;
082        this.framePos = 0;
083        this.frameSize = fmt.getFrameSize();
084        this.input = is;
085      }
086    
087      /**
088       * Create a new AudioInputStream given a TargetDataLine.  The audio
089       * format and the frame size are taken from the line.
090       * @param line the TargetDataLine
091       */
092      public AudioInputStream(TargetDataLine line)
093      {
094        this(new TargetInputStream(line), line.getFormat(),
095             AudioSystem.NOT_SPECIFIED);
096      }
097    
098      /**
099       * Return the number of bytes available to be read from the
100       * underlying stream.  This wrapper method ensures that the result
101       * is always a multiple of the frame size.
102       */
103      public int available() throws IOException
104      {
105        int result = input.available();
106        // Ensure result is a multiple of the frame size.
107        if (frameSize != AudioSystem.NOT_SPECIFIED)
108          result -= result % frameSize;
109        return result;
110      }
111    
112      /**
113       * Close the stream.
114       */
115      public void close() throws IOException
116      {
117        input.close();
118      }
119    
120      /**
121       * Get the format associated with this stream.
122       * @return the AudioFormat
123       */
124      public AudioFormat getFormat()
125      {
126        return format;
127      }
128    
129      /**
130       * Get the length of this stream in frames.  Note that this
131       * may be AudioSystem#NOT_SPECIFIED.
132       * @return the length of the stream in frames
133       */
134      public long getFrameLength()
135      {
136        return frameLength;
137      }
138    
139      public void mark(int limit)
140      {
141        input.mark(limit);
142        markedFramePos = framePos;
143      }
144    
145      /**
146       * Return true if the underlying stream supports mark and reset,
147       * false otherwise.
148       */
149      public boolean markSupported()
150      {
151        return input.markSupported();
152      }
153    
154      /**
155       * Read a single byte from the underlying stream.  If the frame
156       * size is set, and is not one byte, an IOException will be thrown.
157       */
158      public int read() throws IOException
159      {
160        if (frameSize != 1)
161          throw new IOException("frame size must be 1 for read()");
162        int result;
163        if (framePos == frameLength)
164          result = -1;
165        else
166          result = input.read();
167        if (result != -1)
168          ++framePos;
169        return result;
170      }
171    
172      public int read(byte[] buf) throws IOException
173      {
174        return read(buf, 0, buf.length);
175      }
176    
177      public int read(byte[] buf, int offset, int length) throws IOException
178      {
179        int result;
180        if (framePos == frameLength)
181          result = -1;
182        else
183          {
184            int myFrameSize = (frameSize == AudioSystem.NOT_SPECIFIED
185                               ? 1 : frameSize);
186            // Ensure length is a multiple of frame size.
187            length -= length % myFrameSize;
188    
189            result = 0;
190            while (result == 0 || result % myFrameSize != 0)
191              {
192                int val = input.read(buf, offset, length);
193                if (val < 0)
194                  {
195                    // This is a weird situation as we might have read a
196                    // frame already.  It isn't clear at all what to do if
197                    // we only found a partial frame.  For now we just
198                    // return whatever we did find.
199                    if (result == 0)
200                      return -1;
201                    result -= result % myFrameSize;
202                    break;
203                  }
204                result += val;
205              }
206            // assert result % myFrameSize == 0;
207            framePos += result / myFrameSize;
208          }
209        return result;
210      }
211    
212      public void reset() throws IOException
213      {
214        input.reset();
215        framePos = markedFramePos;    
216      }
217    
218      public long skip(long n) throws IOException
219      {
220        if (frameSize != AudioSystem.NOT_SPECIFIED)
221          n -= n % frameSize;
222        long actual = input.skip(n);
223        if (frameSize != AudioSystem.NOT_SPECIFIED)
224          framePos += actual / frameSize;
225        return actual;
226      }
227    
228      private static class TargetInputStream extends InputStream
229      {
230        private TargetDataLine line;
231        private byte[] buf;
232    
233        /**
234         * Create a new TargetInputStream.
235         * @param line the line to wrap
236         */
237        public TargetInputStream(TargetDataLine line)
238        {
239          this.line = line;
240          // FIXME: do we have to call line.open()?
241        }
242    
243        public synchronized int read() throws IOException
244        {
245          if (buf == null)
246            buf = new byte[1];
247          int count = read(buf, 0, 1);
248          if (count < 0)
249            return -1;
250          return buf[0];
251        }
252    
253        public int read(byte[] buf, int offset, int length) throws IOException
254        {
255          return line.read(buf, offset, length);
256        }
257      }
258    }