001/* A buffer that supports the writing of audio samples to a sound file. 002 003 Copyright (c) 2000-2014 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028package ptolemy.media.javasound; 029 030import java.io.ByteArrayInputStream; 031import java.io.File; 032import java.io.IOException; 033import java.util.ArrayList; 034import java.util.StringTokenizer; 035 036import javax.sound.sampled.AudioFileFormat; 037import javax.sound.sampled.AudioFormat; 038import javax.sound.sampled.AudioInputStream; 039import javax.sound.sampled.AudioSystem; 040 041/////////////////////////////////////////////////////////////////// 042//// SoundWriter 043 044/** 045 This class is a buffer that supports the writing of audio samples 046 to a sound file. Specifically, this buffer supports the writing of 047 double-valued audio samples. The maximum valid range of sample 048 values is from -1.0 to 1.0. Any values outside of this range will 049 be hard-clipped to fall within this range. 050 <p> 051 <b>Supported file types</b> 052 <p> 053 Valid sound file formats are WAVE (.wav), AIFF (.aif, .aiff), 054 AU (.au). Valid sample rates are 8000, 11025, 22050, 44100, and 055 48000 Hz. Both 8 bit and 16 bit audio are supported. Mono and 056 stereo files are supported. 057 <p> 058 <b>Usage</b> 059 <p> 060 The path to the sound file to write is given as a constructor 061 parameter, along with parameters that specify the desired 062 audio format. The constructor also takes an array length 063 parameter, which is explained below. 064 <p> 065 After invoking the constructor, the putSamples() method should 066 be repeatedly invoked to write samples to the specified sound 067 file. This method is blocking, so it will not return until the 068 samples have been written. The putSamples() method takes a 069 multidimensional array as a parameter. The first index 070 represents the channel number (0 for first channel, 1 for second 071 channel, etc.). The second index represents the sample index within 072 a channel. For each channel i, the size of the array, 073 putSamplesArray[i].length, must be equal to the constructor 074 parameter putSamplesArraySize. Otherwise an exception will 075 occur. Thus, each call to putSamples() writes putSamplesArraySize 076 on each channel. It should be noted that the putSamples() method 077 does not write samples directly to a sound file, but instead 078 writes samples to an internal array. The internal array of 079 samples is written to the sound file by the closeFile() method. 080 <p> 081 The closeFile() method should be invoked when no more samples 082 need to be written. This method will write the internal array 083 of samples to the output file and close the file. It is not 084 possible to write any more samples after closeFile() has been 085 called. An exception will occur if putSamples() is invoked 086 at any point after closeFile() is invoked. 087 <p> 088 <b>Security issues</b> 089 <p> 090 Applications have no restrictions on the 091 capturing or playback of audio. Applet code is not allowed to 092 write native files by default. The .java.policy file must be 093 modified to grant applets more privileges. 094 <p> 095 Note: Requires Java 2 v1.3.0 or later. 096 097 @author Brian K. Vogel 098 @version $Id$ 099 @since Ptolemy II 1.0 100 @Pt.ProposedRating Red (vogel) 101 @Pt.AcceptedRating Red (cxh) 102 @see ptolemy.media.javasound.SoundCapture 103 */ 104public class SoundWriter { 105 /** Construct a sound writer object with the specified name. 106 * Valid sound file formats are WAVE (.wav), AIFF (.aif, 107 * .aiff), AU (.au). The file format is automatically 108 * determined from the file extension. 109 * 110 * @param fileName The file name to create. If the file already 111 * exists, overwrite it. Valid sound file formats are WAVE (.wav), 112 * AIFF (.aif, .aiff), AU (.au). The file format to write is 113 * determined automatically from the file extension. 114 * @param sampleRate Sample rate in Hz. Must be in the range: 8000 115 * to 48000. 116 * @param bitsPerSample Number of bits per sample (valid choices are 117 * 8 or 16). 118 * @param channels Number of audio channels. 1 for mono, 2 for 119 * stereo. 120 * @param putSamplesArraySize Size of the array parameter of 121 * putSamples(). There is no restriction on the value of 122 * this parameter, but typical values are 64-2024. 123 */ 124 public SoundWriter(String fileName, float sampleRate, int bitsPerSample, 125 int channels, int putSamplesArraySize) { 126 _isAudioWriterActive = false; 127 this._fileName = fileName; 128 this._bitsPerSample = bitsPerSample; 129 this._sampleRate = sampleRate; 130 this._channels = channels; 131 _initializeAudio(); 132 _isAudioWriterActive = true; 133 134 if (_debug) { 135 System.out.println( 136 "SoundWriter: constructor : fileName = " + fileName); 137 System.out.println("SoundWriter: constructor : bitsPerSample = " 138 + bitsPerSample); 139 System.out.println( 140 "SoundWriter: constructor : sampleRate = " + sampleRate); 141 System.out.println( 142 "SoundWriter: constructor : channels = " + channels); 143 System.out 144 .println("SoundWriter: constructor : putSamplesArraySize = " 145 + putSamplesArraySize); 146 } 147 } 148 149 /////////////////////////////////////////////////////////////////// 150 /// Public Methods /// 151 152 /** Append the audio data contained in <i>putSamplesArray</i> 153 * to an internal array. The audio samples in this array will 154 * be written to the sound file specified in the constructor 155 * when the closeFile() method is invoked. 156 * <p> 157 * The samples should be in the range (-1, 1). Samples that are 158 * outside this range will be hard-clipped so that they fall 159 * within this range. 160 * @param putSamplesArray A two dimensional array containing 161 * the samples to play or write to a file. The first index 162 * represents the channel number (0 for first channel, 1 for 163 * second channel, etc.). The second index represents the 164 * sample index within a channel. For example, 165 * putSamplesArray[n][m] contains the (m+1)th sample 166 * of the (n+1)th channel. putSamplesArray should be a 167 * rectangular array such that putSamplesArray.length() gives 168 * the number of channels and putSamplesArray[n].length() is 169 * equal to <i>putSamplesSize</i>, for all channels n. An exception 170 * will occur if this is not the case. 171 * 172 * @exception IllegalStateException If closeFile() has already 173 * been called. 174 */ 175 public void putSamples(double[][] putSamplesArray) 176 throws IllegalStateException { 177 if (_isAudioWriterActive == true) { 178 // Convert array of double valued samples into 179 // the proper byte array format. 180 _data = _doubleArrayToByteArray(putSamplesArray, _bytesPerSample, 181 _channels); 182 183 // Add new audio data to the file buffer array. 184 for (byte element : _data) { 185 _toFileBuffer.add(Byte.valueOf(element)); 186 } 187 } else { 188 throw new IllegalStateException("SoundWriter: " 189 + "putSamples() was called while audio playback was" 190 + " inactive (startPlayback() was never called or " 191 + "stopPlayback has already been called)."); 192 } 193 } 194 195 /** Open a the file specified in the constructor for writing, 196 * write the accumulated audio samples (obtained via 197 * putSamples()) to the file specified in the constructor, 198 * and close the file. This method should be called when 199 * no more calls to putSamples() are required. An exception 200 * will occur if putSamples() is called after this method is invoked. 201 * 202 * @exception IOException If there is a problem closing the 203 * audio resources, or if the "write audio data 204 * to file" constructor was used and the sound file has an 205 * unsupported format. 206 */ 207 public void closeFile() throws IOException { 208 // IMPLEMENTATION NOTE: It is probably better to open the 209 // file for writing in the constructor. putSamples() should 210 // probably write samples to the file on each invocation. 211 // closeFile() should only close the file. This would result in 212 // more efficient operation and reduced memory usage. There 213 // does not appear to be an easy way to do this in javasound, 214 // however. This is because in javasound, there is an 215 // AudioInputStream but no AudioOutputStream class. 216 if (_debug) { 217 System.out.println("SoundWriter: stopPlayback(): invoked"); 218 } 219 220 if (_isAudioWriterActive == true) { 221 // Record data to sound file. 222 _stopPlaybackToFile(); 223 } 224 225 _isAudioWriterActive = false; 226 } 227 228 /////////////////////////////////////////////////////////////////// 229 //// private methods //// 230 231 /** Perform initialization that must be done before putSamples() 232 * can be invoked. 233 */ 234 private void _initializeAudio() { 235 // FIXME: Performance is not great when the incoming audio 236 // samples are being captured in real-time, possibly 237 // due to resizing of the ArrayList. 238 // 239 // Array to hold all data to be saved to file. Grows 240 // as new data are added (via putSamples()). 241 // Each element is a byte of audio data. 242 _toFileBuffer = new ArrayList(); 243 244 boolean signed = true; 245 boolean bigEndian = true; 246 247 _playToFileFormat = new AudioFormat(_sampleRate, _bitsPerSample, 248 _channels, signed, bigEndian); 249 250 _frameSizeInBytes = _playToFileFormat.getFrameSize(); 251 _bytesPerSample = _bitsPerSample / 8; 252 } 253 254 /** Open a the file specified in the constructor for writing, 255 * write the accumulated audio samples (obtained via 256 * putSamples()) to the file specified in the constructor, 257 * and close the file. This method should be called when 258 * no more calls to putSamples() are required. 259 */ 260 private void _stopPlaybackToFile() throws IOException { 261 int size = _toFileBuffer.size(); 262 byte[] audioBytes = new byte[size]; 263 264 for (int i = 0; i < size; i++) { 265 Byte j = (Byte) _toFileBuffer.get(i); 266 audioBytes[i] = j.byteValue(); 267 } 268 269 ByteArrayInputStream byteInputArrayStream = new ByteArrayInputStream( 270 audioBytes); 271 272 AudioInputStream audioInputStream = new AudioInputStream( 273 byteInputArrayStream, _playToFileFormat, 274 audioBytes.length / _frameSizeInBytes); 275 276 outFile = new File(_fileName); 277 278 try { 279 StringTokenizer st = new StringTokenizer(_fileName, "."); 280 281 // Do error checking: 282 if (st.countTokens() != 2) { 283 throw new IOException("Error: Incorrect " + "file name format. " 284 + "Format: filename.extension"); 285 } 286 287 st.nextToken(); // Advance to the file extension. 288 289 String fileExtension = st.nextToken(); 290 291 if (fileExtension.equalsIgnoreCase("au")) { 292 // Save the file. 293 AudioSystem.write(audioInputStream, AudioFileFormat.Type.AU, 294 outFile); 295 } else if (fileExtension.equalsIgnoreCase("aiff")) { 296 // Save the file. 297 AudioSystem.write(audioInputStream, AudioFileFormat.Type.AIFF, 298 outFile); 299 } else if (fileExtension.equalsIgnoreCase("wave")) { 300 // Save the file. 301 AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, 302 outFile); 303 } else if (fileExtension.equalsIgnoreCase("wav")) { 304 // Save the file. 305 AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, 306 outFile); 307 } else if (fileExtension.equalsIgnoreCase("aifc")) { 308 // Save the file. 309 AudioSystem.write(audioInputStream, AudioFileFormat.Type.AIFC, 310 outFile); 311 } else { 312 throw new IOException("Error saving " 313 + "file: Unknown file format: " + fileExtension); 314 } 315 } catch (IOException e) { 316 throw new IOException("SoundWriter: error saving" + " file: " + e); 317 } 318 } 319 320 /* Convert a double array of audio samples into a byte array of 321 * audio samples in linear signed pcm big endian format. The 322 * samples contained in <i>doubleArray</i> should be in the 323 * range (-1, 1). Samples outside this range will be hard clipped 324 * to the range (-1, 1). 325 * @param doubleArray Two dimensional array holding audio samples. 326 * For each channel, m, doubleArray[m] is a single dimensional 327 * array containing samples for channel m. 328 * @param bytesPerSample Number of bytes per sample. Supported 329 * bytes per sample by this method are 8, 16, 24, 32. 330 * @param channels Number of audio channels. 331 * @return The linear signed pcm big endian byte array formatted 332 * array representation of <i>doubleArray</i>. The length of 333 * the returned array is (doubleArray.length*bytesPerSample*channels). 334 */ 335 private byte[] _doubleArrayToByteArray(double[][] doubleArray, 336 int bytesPerSample, int channels) { 337 // All channels had better have the same number 338 // of samples! This is not checked! 339 int lengthInSamples = doubleArray[0].length; 340 341 //double maxSample = Math.pow(2, 8 * bytesPerSample - 1); 342 // Could use above line, but hopefully, code below will 343 // be faster. 344 double maxSample; 345 double maxDoubleValuedSample; 346 347 if (bytesPerSample == 2) { 348 maxSample = 32768; 349 } else if (bytesPerSample == 1) { 350 maxSample = 128; 351 } else if (bytesPerSample == 3) { 352 maxSample = 8388608; 353 } else if (bytesPerSample == 4) { 354 maxSample = 147483648e9; 355 } else { 356 // Should not happen. 357 maxSample = 0; 358 } 359 360 maxDoubleValuedSample = (maxSample - 2) / maxSample; 361 362 byte[] byteArray = new byte[lengthInSamples * bytesPerSample 363 * channels]; 364 byte[] b = new byte[bytesPerSample]; 365 366 for (int currSamp = 0; currSamp < lengthInSamples; currSamp++) { 367 int l; 368 369 // For each channel, 370 for (int currChannel = 0; currChannel < channels; currChannel++) { 371 // Perform clipping, if necessary. 372 if (doubleArray[currChannel][currSamp] >= maxDoubleValuedSample) { 373 l = (int) maxSample - 2; 374 } else if (doubleArray[currChannel][currSamp] <= -maxDoubleValuedSample) { 375 l = (int) -maxSample + 2; 376 } else { 377 // signed integer representation of current sample of the 378 // current channel. 379 l = (int) (doubleArray[currChannel][currSamp] * maxSample); 380 } 381 382 // Create byte representation of current sample. 383 for (int i = 0; i < bytesPerSample; i += 1, l >>= 8) { 384 b[bytesPerSample - i - 1] = (byte) l; 385 } 386 387 // Copy the byte representation of current sample to 388 // the linear signed pcm big endian formatted byte array. 389 for (int i = 0; i < bytesPerSample; i += 1) { 390 byteArray[currSamp * bytesPerSample * channels 391 + bytesPerSample * currChannel + i] = b[i]; 392 } 393 } 394 } 395 396 return byteArray; 397 } 398 399 /////////////////////////////////////////////////////////////////// 400 //// private variables //// 401 // The output file to write to. 402 private File outFile; 403 404 private String _fileName; 405 406 private int _bitsPerSample; 407 408 private float _sampleRate; 409 410 private int _channels; 411 412 // Array of audio samples in byte format. 413 private byte[] _data; 414 415 private int _frameSizeInBytes; 416 417 private ArrayList _toFileBuffer; 418 419 // This is the format of _toFileBuffer. 420 private AudioFormat _playToFileFormat; 421 422 private int _bytesPerSample; 423 424 private boolean _isAudioWriterActive; 425 426 /////////////// For debugging: /////////////////////////////// 427 // Set this variable to "true" to enable debugging information. 428 //private boolean _debug = true; 429 private boolean _debug = false; 430 431 /////////////////////////////////////////////////////////////////// 432}