/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 <mixername>] [-e <buffersize>] [-i <buffersize>] <audiofile></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 <mixername></option></term>
92 * <listitem><para>selects a mixer to play on</para></listitem>
93 * </varlistentry>
94 * <varlistentry>
95 * <term><option>-e <buffersize></option></term>
96 * <listitem><para>the buffer size to use in the application ("extern")</para></listitem>
97 * </varlistentry>
98 * <varlistentry>
99 * <term><option>-i <buffersize></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