/Users/lyon/j4p/src/sound/recorder/AudioRecorder.java

1    package sound.recorder; 
2     
3    /** 
4     * DocJava, Inc. 
5     * http://www.docjava.com 
6     * Programmer: dlyon 
7     * Date: Nov 22, 2004 
8     * Time: 3:42:02 PM 
9     */ 
10   /* 
11    *  AudioRecorder.java 
12    * 
13    *  This file is part of jsresources.org 
14    */ 
15    
16   /* 
17    * Copyright (c) 1999 - 2003 by Matthias Pfisterer 
18    * All rights reserved. 
19    * 
20    * Redistribution and use in source and binary forms, with or without 
21    * modification, are permitted provided that the following conditions 
22    * are met: 
23    * 
24    * - Redistributions of source code must retain the above copyright notice, 
25    *   this list of conditions and the following disclaimer. 
26    * - Redistributions in binary form must reproduce the above copyright 
27    *   notice, this list of conditions and the following disclaimer in the 
28    *   documentation and/or other materials provided with the distribution. 
29    * 
30    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
31    * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
32    * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
33    * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
34    * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
35    * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
36    * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
37    * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
38    * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
39    * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
40    * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
41    * OF THE POSSIBILITY OF SUCH DAMAGE. 
42    */ 
43    
44    
45    
46   import futils.Futil; 
47    
48   import javax.sound.sampled.AudioFileFormat; 
49   import javax.sound.sampled.AudioFormat; 
50   import javax.sound.sampled.AudioInputStream; 
51   import javax.sound.sampled.AudioSystem; 
52   import javax.sound.sampled.TargetDataLine; 
53   import java.io.ByteArrayInputStream; 
54   import java.io.ByteArrayOutputStream; 
55   import java.io.File; 
56   import java.io.IOException; 
57   import java.io.OutputStream; 
58    
59   // IDEA: recording format vs. storage format; possible conversion? 
60    
61   /** 
62    * <titleabbrev>AudioRecorder</titleabbrev> 
63    * <title>Recording to an audio file (advanced version)</title> 
64    * <p/> 
65    * <formalpara><title>Purpose</title> 
66    * <para> 
67    * This program opens two lines: one for recording and one 
68    * for playback. In an infinite loop, it reads data from 
69    * the recording line and writes them to the playback line. 
70    * You can use this to measure the delays inside Java Sound: 
71    * Speak into the microphone and wait untill you hear 
72    * yourself in the speakers.  This can be used to 
73    * experience the effect of changing the buffer sizes: use 
74    * the '-e' and '-i' options. You will notice that the 
75    * delays change, too. 
76    * </para></formalpara> 
77    * <p/> 
78    * <formalpara><title>Usage</title> 
79    * <para> 
80    * <synopsis>java AudioRecorder -l</synopsis> 
81    * <synopsis>java AudioRecorder [-M &lt;mixername&gt;] [-e &lt;buffersize&gt;] [-i &lt;buffersize&gt;] &lt;audiofile&gt;</synopsis> 
82    * </para></formalpara> 
83    * <p/> 
84    * <formalpara><title>Parameters</title> 
85    * <variablelist> 
86    * <varlistentry> 
87    * <term><option>-l</option></term> 
88    * <listitem><para>lists the available mixers</para></listitem> 
89    * </varlistentry> 
90    * <varlistentry> 
91    * <term><option>-M &lt;mixername&gt;</option></term> 
92    * <listitem><para>selects a mixer to play on</para></listitem> 
93    * </varlistentry> 
94    * <varlistentry> 
95    * <term><option>-e &lt;buffersize&gt;</option></term> 
96    * <listitem><para>the buffer size to use in the application ("extern")</para></listitem> 
97    * </varlistentry> 
98    * <varlistentry> 
99    * <term><option>-i &lt;buffersize&gt;</option></term> 
100   * <listitem><para>the buffer size to use in Java Sound ("intern")</para></listitem> 
101   * </varlistentry> 
102   * </variablelist> 
103   * </formalpara> 
104   * <p/> 
105   * <formalpara><title>Bugs, limitations</title> 
106   * <para> 
107   * There is no way to stop the program besides brute force 
108   * (ctrl-C). There is no way to set the audio quality. 
109   * </para></formalpara> 
110   * <p/> 
111   * <formalpara><title>Source code</title> 
112   * <para> 
113   * <ulink url="AudioRecorder.java.html">AudioRecorder.java</ulink>, 
114   * <ulink url="AudioCommon.java.html">AudioCommon.java</ulink>, 
115   * <ulink url="http://www.urbanophile.com/arenn/hacking/download.html">gnu.getopt.Getopt</ulink> 
116   * </para> 
117   * </formalpara> 
118   */ 
119  public class AudioRecorder { 
120      private static final SupportedFormat[] SUPPORTED_FORMATS = 
121              { 
122                  new SupportedFormat("s8", 
123                          AudioFormat.Encoding.PCM_SIGNED, 8, true), 
124                  new SupportedFormat("u8", 
125                          AudioFormat.Encoding.PCM_UNSIGNED, 8, true), 
126                  new SupportedFormat("s16_le", 
127                          AudioFormat.Encoding.PCM_SIGNED, 16, false), 
128                  new SupportedFormat("s16_be", 
129                          AudioFormat.Encoding.PCM_SIGNED, 16, true), 
130                  new SupportedFormat("u16_le", 
131                          AudioFormat.Encoding.PCM_UNSIGNED, 16, false), 
132                  new SupportedFormat("u16_be", 
133                          AudioFormat.Encoding.PCM_UNSIGNED, 16, true), 
134                  new SupportedFormat("s24_le", 
135                          AudioFormat.Encoding.PCM_SIGNED, 24, false), 
136                  new SupportedFormat("s24_be", 
137                          AudioFormat.Encoding.PCM_SIGNED, 24, true), 
138                  new SupportedFormat("u24_le", 
139                          AudioFormat.Encoding.PCM_UNSIGNED, 24, false), 
140                  new SupportedFormat("u24_be", 
141                          AudioFormat.Encoding.PCM_UNSIGNED, 24, true), 
142                  new SupportedFormat("s32_le", 
143                          AudioFormat.Encoding.PCM_SIGNED, 32, false), 
144                  new SupportedFormat("s32_be", 
145                          AudioFormat.Encoding.PCM_SIGNED, 32, true), 
146                  new SupportedFormat("u32_le", 
147                          AudioFormat.Encoding.PCM_UNSIGNED, 32, false), 
148                  new SupportedFormat("u32_be", 
149                          AudioFormat.Encoding.PCM_UNSIGNED, 32, true), 
150              }; 
151      private static final String DEFAULT_FORMAT = "s16_le"; 
152      private static final int DEFAULT_CHANNELS = 2; 
153      private static final float DEFAULT_RATE = 44100.0F; 
154      private static final AudioFileFormat.Type DEFAULT_TARGET_TYPE = AudioFileFormat.Type.WAVE; 
155   
156   
157      public static void main(String[] args) { 
158          /* 
159           *  Parsing of command-line options takes place... 
160           */ 
161          String strMixerName = null; 
162          int nInternalBufferSize = AudioSystem.NOT_SPECIFIED; 
163          String strFormat = DEFAULT_FORMAT; 
164          int nChannels = DEFAULT_CHANNELS; 
165          float fRate = DEFAULT_RATE; 
166          String strExtension = null; 
167          boolean bDirectRecording = false; 
168          System.out.println("format="+strFormat); 
169   
170   
171   
172          File outputFile =Futil.getWriteFile("enter a output audio file"); 
173   
174          /* For convenience, we have some shortcuts to set the 
175             properties needed for constructing an AudioFormat. 
176          */ 
177          if (strFormat.equals("phone")) { 
178              // 8 kHz, 8 bit unsigned, mono 
179              fRate = 8000.0F; 
180              strFormat = "u8"; 
181              nChannels = 1; 
182          } else if (strFormat.equals("radio")) { 
183              // 22.05 kHz, 16 bit signed, mono 
184              fRate = 22050.0F; 
185              strFormat = "s16_le"; 
186              nChannels = 1; 
187          } else if (strFormat.equals("cd")) { 
188              // 44.1 kHz, 16 bit signed, stereo, little-endian 
189              fRate = 44100.0F; 
190              strFormat = "s16_le"; 
191              nChannels = 2; 
192          } else if (strFormat.equals("dat")) { 
193              // 48 kHz, 16 bit signed, stereo, little-endian 
194              fRate = 48000.0F; 
195              strFormat = "s16_le"; 
196              nChannels = 2; 
197          } 
198   
199          /* Here, we are constructing the AudioFormat to use for the 
200             recording. Sample rate (fRate) and number of channels 
201             (nChannels) are already set safely, since they have 
202             default values set at the very top. The other properties 
203             needed for AudioFormat are derived from the 'format' 
204             specification (strFormat). 
205          */ 
206          int nOutputFormatIndex = -1; 
207          for (int i = 0; i < SUPPORTED_FORMATS.length; i++) { 
208              if (SUPPORTED_FORMATS[i].getName().equals(strFormat)) { 
209                  nOutputFormatIndex = i; 
210                  break; 
211              } 
212          } 
213          /* If we haven't found the format (string) requested by the 
214             user, we switch to a default format. 
215          */ 
216          if (nOutputFormatIndex == -1) { 
217              out("warning: output format '" + strFormat + "' not supported; using default output format '" + DEFAULT_FORMAT + "'"); 
218              /* This is the index of "s16_le". Yes, it's 
219                 a bit quick & dirty to hardcode the index here. 
220              */ 
221              nOutputFormatIndex = 2; 
222          } 
223          AudioFormat.Encoding encoding = SUPPORTED_FORMATS[nOutputFormatIndex].getEncoding(); 
224          ; 
225          int nBitsPerSample = SUPPORTED_FORMATS[nOutputFormatIndex].getSampleSize(); 
226          boolean bBigEndian = SUPPORTED_FORMATS[nOutputFormatIndex].getBigEndian(); 
227          int nFrameSize = (nBitsPerSample / 8) * nChannels; 
228          AudioFormat audioFormat = new AudioFormat(encoding, fRate, nBitsPerSample, nChannels, nFrameSize, fRate, bBigEndian); 
229   
230   
231   
232          AudioFileFormat.Type targetType = null; 
233          if (strExtension != null) { 
234              targetType = AudioCommon.findTargetType(strExtension); 
235              if (targetType == null) { 
236                  out("target type '" + strExtension + "' is not supported."); 
237                  out("using default type '" + DEFAULT_TARGET_TYPE.getExtension() + "'"); 
238                  targetType = DEFAULT_TARGET_TYPE; 
239              } 
240          } else { 
241              out("target type is neither specified nor can be guessed from the target file name."); 
242              out("using default type '" + DEFAULT_TARGET_TYPE.getExtension() + "'"); 
243              targetType = DEFAULT_TARGET_TYPE; 
244          } 
245   
246          TargetDataLine targetDataLine = null; 
247          targetDataLine = AudioCommon.getTargetDataLine(strMixerName, audioFormat, nInternalBufferSize); 
248          if (targetDataLine == null) { 
249              out("can't get TargetDataLine, exiting."); 
250              System.exit(1); 
251          } 
252          Recorder recorder = null; 
253          if (bDirectRecording) { 
254              recorder = new DirectRecorder(targetDataLine, 
255                      targetType, 
256                      outputFile); 
257          } else { 
258              recorder = new BufferingRecorder(targetDataLine, 
259                      targetType, 
260                      outputFile); 
261          } 
262   
263          out("Press ENTER to start the recording."); 
264          try { 
265              System.in.read(); 
266          } catch (IOException e) { 
267              e.printStackTrace(); 
268          } 
269          recorder.start(); 
270          out("Recording..."); 
271          out("Press ENTER to stop the recording."); 
272          try { 
273              System.in.read(); 
274          } catch (IOException e) { 
275              e.printStackTrace(); 
276          } 
277          recorder.stopRecording(); 
278          out("Recording stopped."); 
279          // System.exit(0); 
280      } 
281   
282      private static void printUsageAndExit() { 
283          out("AudioRecorder: usage:"); 
284          out("\tjava AudioRecorder -l"); 
285          out("\tjava AudioRecorder -L"); 
286          out("\tjava AudioRecorder [-f <format>] [-c <numchannels>] [-r <samplingrate>] [-t <targettype>] [-M <mixername>] <soundfile>"); 
287          System.exit(0); 
288      } 
289   
290      /** 
291       * TODO: 
292       */ 
293      private static void out(String strMessage) { 
294          System.out.println(strMessage); 
295      } 
296   
297   
298   
299  ///////////// inner classes //////////////////// 
300   
301   
302      /** 
303       * TODO: 
304       */ 
305      private static class SupportedFormat { 
306          /** 
307           * The name of the format. 
308           */ 
309          private String m_strName; 
310          /** 
311           * The encoding of the format. 
312           */ 
313          private AudioFormat.Encoding m_encoding; 
314          /** 
315           * The sample size of the format. 
316           * This value is in bits for a single sample 
317           * (not for a frame). 
318           */ 
319          private int m_nSampleSize; 
320          /** 
321           * The endianess of the format. 
322           */ 
323          private boolean m_bBigEndian; 
324   
325          // sample size is in bits 
326          /** 
327           * Construct a new supported format. 
328           * 
329           * @param strName     the name of the format. 
330           * @param encoding    the encoding of the format. 
331           * @param nSampleSize the sample size of the format, in bits. 
332           * @param bBigEndian  the endianess of the format. 
333           */ 
334          public SupportedFormat(String strName, 
335                                 AudioFormat.Encoding encoding, 
336                                 int nSampleSize, 
337                                 boolean bBigEndian) { 
338              m_strName = strName; 
339              m_encoding = encoding; 
340              m_nSampleSize = nSampleSize; 
341          } 
342   
343          /** 
344           * Returns the name of the format. 
345           */ 
346          public String getName() { 
347              return m_strName; 
348          } 
349   
350          /** 
351           * Returns the encoding of the format. 
352           */ 
353          public AudioFormat.Encoding getEncoding() { 
354              return m_encoding; 
355          } 
356   
357          /** 
358           * Returns the sample size of the format. 
359           * This value is in bits. 
360           */ 
361          public int getSampleSize() { 
362              return m_nSampleSize; 
363          } 
364   
365          /** 
366           * Returns the endianess of the format. 
367           */ 
368          public boolean getBigEndian() { 
369              return m_bBigEndian; 
370          } 
371      } 
372   
373   
374      /////////////////////////////////////////////// 
375   
376   
377      public static interface Recorder { 
378          public void start(); 
379   
380          public void stopRecording(); 
381      } 
382   
383      public static class AbstractRecorder 
384              extends Thread 
385              implements Recorder { 
386          protected TargetDataLine m_line; 
387          protected AudioFileFormat.Type m_targetType; 
388          protected File m_file; 
389          protected boolean m_bRecording; 
390   
391          public AbstractRecorder(TargetDataLine line, 
392                                  AudioFileFormat.Type targetType, 
393                                  File file) { 
394              m_line = line; 
395              m_targetType = targetType; 
396              m_file = file; 
397          } 
398   
399          /** 
400           * Starts the recording. 
401           * To accomplish this, (i) the line is started and (ii) the 
402           * thread is started. 
403           */ 
404          public void start() { 
405              m_line.start(); 
406              super.start(); 
407          } 
408   
409          public void stopRecording() { 
410              // for recording, the line needs to be stopped 
411              // before draining (especially if you're still 
412              // reading from it) 
413              m_line.stop(); 
414              m_line.drain(); 
415              m_line.close(); 
416              m_bRecording = false; 
417          } 
418      } 
419   
420      public static class DirectRecorder 
421              extends AbstractRecorder { 
422          private AudioInputStream m_audioInputStream; 
423   
424          public DirectRecorder(TargetDataLine line, 
425                                AudioFileFormat.Type targetType, 
426                                File file) { 
427              super(line, targetType, file); 
428              m_audioInputStream = new AudioInputStream(line); 
429          } 
430   
431          public void run() { 
432              try { 
433   
434                  AudioSystem.write(m_audioInputStream, 
435                          m_targetType, 
436                          m_file); 
437   
438              } catch (IOException e) { 
439                  e.printStackTrace(); 
440              } 
441          } 
442      } 
443   
444      public static class BufferingRecorder 
445              extends AbstractRecorder { 
446          public BufferingRecorder(TargetDataLine line, 
447                                   AudioFileFormat.Type targetType, 
448                                   File file) { 
449              super(line, targetType, file); 
450          } 
451   
452          public void run() { 
453              ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 
454              OutputStream outputStream = byteArrayOutputStream; 
455              AudioFormat format = writeData(outputStream); 
456              closeOutputStream(byteArrayOutputStream); 
457              writeData(byteArrayOutputStream, format); 
458          } 
459   
460          private void writeData(ByteArrayOutputStream byteArrayOutputStream, AudioFormat format) { 
461              byte[] abData = byteArrayOutputStream.toByteArray(); 
462              ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(abData); 
463              AudioInputStream audioInputStream = new AudioInputStream( 
464                      byteArrayInputStream, 
465                      format, abData.length / format.getFrameSize()); 
466              try { 
467                  AudioSystem.write(audioInputStream, m_targetType, m_file); 
468              } catch (IOException e) { 
469                  e.printStackTrace(); 
470              } 
471          } 
472   
473          private void closeOutputStream(ByteArrayOutputStream byteArrayOutputStream) { 
474              try { 
475                  byteArrayOutputStream.close(); 
476              } catch (IOException e) { 
477                  e.printStackTrace(); 
478              } 
479          } 
480   
481          private AudioFormat writeData(OutputStream outputStream) { 
482              // TODO: intelligent size 
483              byte[] abBuffer = new byte[65536]; 
484              AudioFormat format = m_line.getFormat(); 
485              int nFrameSize = format.getFrameSize(); 
486              int nBufferFrames = abBuffer.length / nFrameSize; 
487              m_bRecording = true; 
488              while (m_bRecording) { 
489                  int nFramesRead = m_line.read(abBuffer, 0, nBufferFrames); 
490   
491                  int nBytesToWrite = nFramesRead * nFrameSize; 
492                  try { 
493                      outputStream.write(abBuffer, 0, nBytesToWrite); 
494                  } catch (IOException e) { 
495                      e.printStackTrace(); 
496                  } 
497              } 
498              return format; 
499          } 
500      } 
501  } 
502   
503  /*** AudioRecorder.java ***/ 
504   
505