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}