001/* A buffer that supports the reading of audio samples from a sound
002 file specified as a URL.
003
004 Copyright (c) 2000-2014 The Regents of the University of California.
005 All rights reserved.
006 Permission is hereby granted, without written agreement and without
007 license or royalty fees, to use, copy, modify, and distribute this
008 software and its documentation for any purpose, provided that the above
009 copyright notice and the following two paragraphs appear in all copies
010 of this software.
011
012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
016 SUCH DAMAGE.
017
018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
023 ENHANCEMENTS, OR MODIFICATIONS.
024
025 PT_COPYRIGHT_VERSION_2
026 COPYRIGHTENDKEY
027
028 */
029package ptolemy.media.javasound;
030
031import java.io.IOException;
032import java.net.URL;
033
034import javax.sound.sampled.AudioFormat;
035import javax.sound.sampled.AudioInputStream;
036import javax.sound.sampled.AudioSystem;
037import javax.sound.sampled.UnsupportedAudioFileException;
038
039///////////////////////////////////////////////////////////////////
040//// SoundReader
041
042/**
043 This class is a buffer that supports the reading of audio samples
044 from a sound file that is specified as a URL. Specifically, this
045 buffer supports the reading of double-valued audio samples. The
046 maximum valid range of sample values is from -1.0 to 1.0. Any
047 values outside of this range will be hard-clipped to fall within
048 this range.
049 <p>
050 <b>Supported file types</b>
051 <p>
052 Valid sound file formats are WAVE (.wav), AIFF (.aif, .aiff),
053 AU (.au). Valid sample rates are 8000, 11025, 22050, 44100, and
054 48000 Hz. Both 8 bit and 16 bit audio are supported. Mono and
055 stereo files are supported.
056 <p>
057 <b>Usage</b>
058 <p>
059 The path to the sound file, specified as a URL, is given as a
060 constructor parameter. The constructor also takes an array
061 length parameter, which is explained below. The constructor will
062 attempt to open the specified file.
063 <p>
064 After invoking the constructor, the getSamples() method should
065 be repeatedly invoked to read samples from the specified sound
066 file. The getSamples() method takes a multidimensional array
067 as a parameter. The first index represents the channel number
068 (0 for first channel, 1 for  second channel, etc.). The second
069 index represents the sample index within a channel. For each
070 channel i, the size of the array, getSamplesArray[i].length, must
071 be equal to the constructor parameter getSamplesArraySize.
072 Otherwise an exception will occur. When the end of the sound
073 file is reached, this method will return null.
074 <p>
075 The getChannels(), getSampleRate(), and getBitsPerSample()
076 methods may be invoked at any time to obtain information about
077 the format of the sound file.
078 <p>
079 When no more samples are desired, the closeFile() method should
080 be invoked to close the sound file. An exception will occur if
081 getSamples() is invoked at any point after closeFile() is invoked.
082 <p>
083 <b>Security Issues</b>
084 <p>
085 Applications have no restrictions on the  capturing or playback
086 of audio. Applets, however, may by default only capture
087 audio from a file specified as a URL on the same machine as the
088 one the applet was loaded from. Applet code is not allowed to
089 read or write native files. The .java.policy file may be
090 modified to grant applets more privileges.
091 <p>
092 Note: Requires Java 2 v1.3.0 or later.
093
094 @author Brian K. Vogel
095 @version $Id$
096 @since Ptolemy II 1.0
097 @Pt.ProposedRating Red (vogel)
098 @Pt.AcceptedRating Red (cxh)
099 @see ptolemy.media.javasound.SoundWriter
100 */
101public class SoundReader {
102    /** Construct a sound reader object that reads audio samples
103     *  from a sound file specified as a string describing a
104     *  URL and open the file at the specified URL.
105     *  For example, to capture samples from the
106     *  URL http://localhost/soundfile.wav, sourceURL should be set
107     *  to the string "http://localhost/soundfile.wav" Note that the
108     *  "http://" is required.
109     *  Note that it is still possible to capture audio from a file on
110     *  the local file system. For example, to capture from a sound
111     *  file located at "C:\someDir\someFile.wave", <i>sourceURL</i>
112     *  should be set to "file:c:\someDir\someFile.wave". Relative
113     *  file paths are not supported, so the complete path must always
114     *  be specified, using the prefix "file:" It is safe
115     *  to call getSamples() immediately after this constructor returns.
116     *
117     *  @param sourceURL A string describing a URL.
118     *  @param getSamplesArraySize The number of samples per channel
119     *   returned by getSamples().
120     *  @exception IOException If opening the sourceURL throws it,
121     *  if the file format is not supported or if there is no audio
122     *  to play.
123     */
124    public SoundReader(String sourceURL, int getSamplesArraySize)
125            throws IOException {
126        _productionRate = getSamplesArraySize;
127
128        // Create a URL corresponding to the sound file location.
129        URL soundURL = new URL(sourceURL);
130
131        // Open the specified sound file.
132        _openFileFromURL(soundURL);
133        _isAudioCaptureActive = true;
134    }
135
136    /** Construct a sound reader object that reads audio samples
137     *  from a sound file specified as a URL and open the file at
138     *  the specified URL. It is safe
139     *  to call getSamples() immediately after this constructor returns.
140     *  @param soundURL The URL of a sound file.
141     *  @param getSamplesArraySize The number of samples per channel
142     *   returned by getSamples().
143     *  @exception IOException If opening the sourceURL throws it,
144     *  if the file format is not supported or if there is no audio
145     *  to play.
146     */
147    public SoundReader(URL soundURL, int getSamplesArraySize)
148            throws IOException {
149        _productionRate = getSamplesArraySize;
150
151        // Open the specified sound file.
152        _openFileFromURL(soundURL);
153        _isAudioCaptureActive = true;
154    }
155
156    ///////////////////////////////////////////////////////////////////
157    ////                         public methods                    ////
158
159    /** Return the number of audio channels. This method should
160     *  be called while the file is open (i.e., before closeFile()
161     *  is called). Otherwise an exception will occur.
162     *  @return The number of audio channels.
163     *  @exception IllegalStateException If this method is called
164     *   before openFile() is called or after closeFile()
165     *   is called.
166     */
167    public int getChannels() throws IllegalStateException {
168        if (_isAudioCaptureActive == true) {
169            return _channels;
170        } else {
171            throw new IllegalStateException("SoundReader: "
172                    + "getChannels() was called while audio capture was"
173                    + " inactive (openFile() was never called).");
174        }
175    }
176
177    /** Return the sampling rate in Hz. An exception will occur if
178     *  this method is invoked after closeFile() is called.
179     *  @return The sample rate in Hz.
180     *  @exception IllegalStateException If this method is called
181     *   after closeFile()
182     *   is called.
183     */
184    public float getSampleRate() throws IllegalStateException {
185        if (_isAudioCaptureActive == true) {
186            return _sampleRate;
187        } else {
188            throw new IllegalStateException("SoundReader: "
189                    + "getSampleRate() was called while audio capture was"
190                    + " inactive (openFile() was never called).");
191        }
192    }
193
194    /** Return an array of captured audio samples. This method
195     *  should be repeatedly called to obtain audio data.
196     *  The returned audio samples will have values in the range
197     *  [-1, 1], regardless of the audio bit resolution (bits per
198     *  sample). This method performs a blocking read, so it is not
199     *  possible to invoke this method too frequently.
200     *  <p>
201     *  The array size is set by the <i>getSamplesSize</i>
202     *  parameter in the constructor.
203     *  @return Two dimensional array of captured audio samples.
204     *   Return null if end of the audio file is reached.
205     *   The first index represents the channel number (0 for
206     *   first channel, 1 for second channel, etc.). The second
207     *   index represents the sample index within a channel. For
208     *   example, <i>returned array</i>[n][m] contains the (m+1)th sample
209     *   of the (n+1)th channel. For each channel, n, the length of
210     *   <i>returned array</i>[n] is equal to <i>getSamplesSize</i>.
211     *
212     *  @exception IOException If there is a problem reading the audio
213     *   samples from the input file.
214     *  @exception IllegalStateException If closeFile() has already
215     *   been called.
216     */
217    public double[][] getSamples() throws IOException, IllegalStateException {
218        if (_isAudioCaptureActive == true) {
219            if (_debug) {
220                System.out.println("SoundReader: getSamples(): invoked");
221            }
222
223            int numBytesRead;
224
225            if (_debug) {
226                System.out.println(
227                        "SoundReader: getSamples(): " + "bytes available = "
228                                + _properFormatAudioInputStream.available());
229            }
230
231            // Capture audio from file.
232            numBytesRead = _properFormatAudioInputStream.read(_data);
233
234            if (_debug) {
235                System.out.println("SoundReader: getSamples(): "
236                        + "numBytesRead = " + numBytesRead);
237            }
238
239            if (numBytesRead == _data.length) {
240                // Convert byte array to double array.
241                _audioInDoubleArray = _byteArrayToDoubleArray(_data,
242                        _bytesPerSample, _channels);
243                return _audioInDoubleArray;
244            } else if (numBytesRead == -1) {
245                // Ran out of samples to play. This generally means
246                // that the end of the sound file has been reached.
247                if (_debug) {
248                    System.out.println("SoundReader: getSamples(): "
249                            + "numBytesRead = -1, so "
250                            + "returning null now...");
251                }
252
253                return null;
254            } else if (numBytesRead != _data.length) {
255                // Read fewer samples than productionRate many samples.
256                // Note: There appears to be a java sound bug that
257                // causes AudioInputStream.read(array) to sometimes
258                // return fewer bytes than requested, even though
259                // the end of the file has not yet been reached.
260                _audioInDoubleArray = _byteArrayToDoubleArray(_data,
261                        _bytesPerSample, _channels);
262                return _audioInDoubleArray;
263            } else {
264                return null;
265            }
266        } else {
267            throw new IllegalStateException("SoundReader: "
268                    + "getSamples() was called while audio capture was"
269                    + " inactive (openFile() was never called or "
270                    + "closeFile has already been called).");
271        }
272    }
273
274    /** Close the file at the specified URL. This method should
275     *  be called when no more calls to getSamples(). are required.
276     *
277     *  @exception IOException If there is a problem closing the
278     *  file.
279     */
280    public void closeFile() throws IOException {
281        System.out.println("SoundReader: closeFile() invoked");
282
283        if (_isAudioCaptureActive == true) {
284            // Free up audio system resources.
285            if (_audioInputStream != null) {
286                _audioInputStream.close();
287            }
288
289            if (_properFormatAudioInputStream != null) {
290                _properFormatAudioInputStream.close();
291            }
292        }
293
294        _isAudioCaptureActive = false;
295    }
296
297    /** Return the number of bits per audio sample. An exception
298     *  will occur if this method is called after closeFile() is
299     *  invoked.
300     *
301     * @return The sample size in bits.
302     *
303     * @exception IllegalStateException If this method is called
304     *  after closeFile()
305     *  is called.
306     */
307    public int getBitsPerSample() throws IllegalStateException {
308        if (_isAudioCaptureActive == true) {
309            return _sampleSizeInBits;
310        } else {
311            throw new IllegalStateException(
312                    "SoundReader: " + "getSampleSizeInBits() was called while "
313                            + "audio capture was"
314                            + " inactive (openFile() was never called).");
315        }
316    }
317
318    ///////////////////////////////////////////////////////////////////
319    ////                         private methods                   ////
320
321    /* Perform initialization. Open the file at the specified URL.
322     * Discover the format of the file and set the corresponding
323     * private variables to the sample rate, bits per samples, and
324     * number of audio channels of the file.
325     */
326    private void _openFileFromURL(URL soundURL) throws IOException {
327        if (soundURL != null) {
328            try {
329                _audioInputStream = AudioSystem.getAudioInputStream(soundURL);
330            } catch (UnsupportedAudioFileException e) {
331                throw new IOException("Unsupported AudioFile :" + e);
332            }
333        }
334
335        // make sure we have something to play
336        if (_audioInputStream == null) {
337            throw new IOException("No loaded audio to play back");
338        }
339
340        AudioFormat origFormat = _audioInputStream.getFormat();
341
342        // Now convert to PCM_SIGNED_BIG_ENDIAN so that can get double
343        // representation of samples.
344        _sampleRate = origFormat.getSampleRate();
345
346        if (_debug) {
347            System.out.println("SoundReader: sampling rate = " + _sampleRate);
348        }
349
350        _sampleSizeInBits = origFormat.getSampleSizeInBits();
351        _bytesPerSample = _sampleSizeInBits / 8;
352
353        if (_debug) {
354            System.out.println(
355                    "SoundReader: sample size in bits = " + _sampleSizeInBits);
356        }
357
358        _channels = origFormat.getChannels();
359
360        boolean signed = true;
361        boolean bigEndian = true;
362        AudioFormat format = new AudioFormat(_sampleRate, _sampleSizeInBits,
363                _channels, signed, bigEndian);
364
365        if (_debug) {
366            System.out.println("Converted format: " + format.toString());
367        }
368
369        try {
370            _properFormatAudioInputStream = AudioSystem
371                    .getAudioInputStream(format, _audioInputStream);
372        } catch (IllegalArgumentException e) {
373            // Interpret a failed conversion to mean that
374            // the input sound file has an unsupported format.
375            throw new IOException("Unsupported audio file format: " + e);
376        }
377
378        _frameSizeInBytes = format.getFrameSize();
379
380        // Array of audio samples in byte format.
381        _data = new byte[_productionRate * _frameSizeInBytes];
382
383    }
384
385    /* Convert a byte array of audio samples in linear signed pcm big endian
386     * format into a double array of audio samples (-1, 1) range.
387     * @param byteArray  The linear signed pcm big endian byte array
388     * formatted array representation of audio data.
389     * @param bytesPerSample Number of bytes per sample. Supported
390     * bytes per sample by this method are 8, 16, 24, 32.
391     * @param channels Number of audio channels. 1 for mono, 2 for
392     * stereo.
393     * @return Two dimensional array holding audio samples.
394     * For each channel, m, doubleArray[m] is a single dimensional
395     * array containing samples for channel m.
396     */
397    private double[][] _byteArrayToDoubleArray(byte[] byteArray,
398            int bytesPerSample, int channels) {
399        int lengthInSamples = byteArray.length / (bytesPerSample * channels);
400
401        // Check if we need to reallocate.
402        if (channels != _doubleArray.length
403                || lengthInSamples != _doubleArray[0].length) {
404            // Reallocate
405            _doubleArray = new double[channels][lengthInSamples];
406        }
407
408        //double maxSampleReciprocal = 1/(Math.pow(2, 8 * bytesPerSample - 1));
409        // Could use above line, but hopefully, code below will
410        // be faster.
411        double maxSampleReciprocal;
412
413        if (bytesPerSample == 2) {
414            // 1 / 32768
415            maxSampleReciprocal = 3.0517578125e-5;
416        } else if (bytesPerSample == 1) {
417            // 1 / 128
418            maxSampleReciprocal = 7.8125e-3;
419        } else if (bytesPerSample == 3) {
420            // 1 / 8388608
421            maxSampleReciprocal = 1.1920928955e07;
422        } else if (bytesPerSample == 4) {
423            // 1 / 147483648e9
424            maxSampleReciprocal = 4.655661287308e-10;
425        } else {
426            // Should not happen.
427            maxSampleReciprocal = 0;
428        }
429
430        // Check if we need to reallocate.
431        // Note: This test is really not needed since bytesPerSample
432        // is set in the constructor. It should never change.
433        if (bytesPerSample != _b.length) {
434            _b = new byte[bytesPerSample];
435        }
436
437        for (int currSamp = 0; currSamp < lengthInSamples; currSamp++) {
438            // For each channel,
439            for (int currChannel = 0; currChannel < channels; currChannel++) {
440                for (int i = 0; i < bytesPerSample; i += 1) {
441                    // Assume we are dealing with big endian.
442                    _b[i] = byteArray[currSamp * bytesPerSample * channels
443                            + bytesPerSample * currChannel + i];
444                }
445
446                int result = _b[0] >> 7;
447
448                for (int i = 0; i < bytesPerSample; i += 1) {
449                    result = (result << 8) + (_b[i] & 0xff);
450                }
451
452                _doubleArray[currChannel][currSamp] = result
453                        * maxSampleReciprocal;
454            }
455        }
456
457        return _doubleArray;
458    }
459
460    ///////////////////////////////////////////////////////////////////
461    ////                         private variables                 ////
462    private AudioInputStream _properFormatAudioInputStream;
463
464    private AudioInputStream _audioInputStream;
465
466    private int _productionRate;
467
468    // Array of audio samples in double format.
469    private double[][] _audioInDoubleArray;
470
471    // Array of audio samples in byte format.
472    private byte[] _data;
473
474    private int _frameSizeInBytes;
475
476    private int _sampleSizeInBits;
477
478    private float _sampleRate;
479
480    private int _channels;
481
482    private int _bytesPerSample;
483
484    private boolean _isAudioCaptureActive;
485
486    private byte[] _b = new byte[1];
487
488    private double[][] _doubleArray = new double[1][1];
489
490    /////////////// For debugging: ///////////////////////////////
491    // Set this variable to "true" to enable debugging information.
492    private boolean _debug = false;
493
494    ///////////////////////////////////////////////////////////////////
495}