001/* 
002   Copyright (C) 2005 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.sound.sampled;
040
041import java.io.IOException;
042import 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 */
050public 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}