package org.jsresources;
/*
* TimedAudioRecorder.java
*
* This file is derived from SimpleAudioRecorder which is part of jsresources.org
*/
/*
* Portions Copyright (c) 2004 DFKI GmbH.
* Portions Copyright (c) 1999 - 2003 by Matthias Pfisterer
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
|<--- this code is formatted to fit into 80 columns --->|
*/
import java.io.File;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.TargetDataLine;
/**
* <p>
* TimedAudioRecorder: Recording to an audio file (timed version)
* </p>
*
* <p>
* Purpose: Records audio data and stores it in a file. The data is recorded in CD quality (44.1 kHz, 16 bit linear, stereo) and
* stored in a .wav file.
* </p>
*
* <p>
* Usage: java TimedAudioRecorder choice="plain" -h java TimedAudioRecorder choice="plain" audiofile
* </p>
*
* <p>
* Parameters: -h print usage information, then exit audiofile the file name of the audio file that should be produced from the
* recorded data
* </p>
*
* <p>
* Bugs, limitations: You cannot select audio formats and the audio file type on the command line. See AudioRecorder for a version
* that has more advanced options. Due to a bug in the Sun jdk1.3/1.4, this program does not work with it.
* </p>
*
* <p>
* Source code: <a href="TimedAudioRecorder.java.html">TimedAudioRecorder.java </a>
* </p>
*
*/
public class TimedAudioRecorder extends Thread {
private TargetDataLine m_line;
private AudioFileFormat.Type m_targetType;
private AudioInputStream m_audioInputStream;
private File m_outputFile;
private long timeout;
public TimedAudioRecorder(TargetDataLine line, AudioFileFormat.Type targetType, File file, long timeout) {
m_line = line;
m_audioInputStream = new AudioInputStream(line);
m_targetType = targetType;
m_outputFile = file;
this.timeout = timeout;
}
/**
* Starts the recording. To accomplish this, (i) the line is started and (ii) the thread is started.
*/
public void start() {
/*
* Starting the TargetDataLine. It tells the line that we now want to read data from it. If this method isn't called, we
* won't be able to read data from the line at all.
*/
m_line.start();
/*
* Starting the thread. This call results in the method 'run()' (see below) being called. There, the data is actually read
* from the line.
*/
super.start();
}
/**
* Stops the recording.
*
* Note that stopping the thread explicitely is not necessary. Once no more data can be read from the TargetDataLine, no more
* data be read from our AudioInputStream. And if there is no more data from the AudioInputStream, the method
* 'AudioSystem.write()' (called in 'run()' returns. Returning from 'AudioSystem.write()' is followed by returning from
* 'run()', and thus, the thread is terminated automatically.
*
* It's not a good idea to call this method just 'stop()' because stop() is a (deprecated) method of the class 'Thread'. And
* we don't want to override this method.
*/
public void stopRecording() {
m_line.stop();
m_line.close();
}
/**
* Main working method. You may be surprised that here, just 'AudioSystem.write()' is called. But internally, it works like
* this: AudioSystem.write() contains a loop that is trying to read from the passed AudioInputStream. Since we have a special
* AudioInputStream that gets its data from a TargetDataLine, reading from the AudioInputStream leads to reading from the
* TargetDataLine. The data read this way is then written to the passed File. Before writing of audio data starts, a header is
* written according to the desired audio file type. Reading continues untill no more data can be read from the
* AudioInputStream. In our case, this happens if no more data can be read from the TargetDataLine. This, in turn, happens if
* the TargetDataLine is stopped or closed (which implies stopping). (Also see the comment above.) Then, the file is closed
* and 'AudioSystem.write()' returns.
*/
public void run() {
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
stopRecording();
timer.cancel();
}
}, timeout);
try {
AudioSystem.write(m_audioInputStream, m_targetType, m_outputFile);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
if (args.length < 3 || args[0].equals("-h")) {
printUsageAndExit();
}
long duration = 0;
long rate = 16000;
int channels = 1; // mono
int i = 0;
// Go through all arguments except the last one
while (i < args.length - 1) {
if (args[i].equals("-dur")) {
duration = Long.parseLong(args[i + 1]);
i += 2;
continue;
} else if (args[i].equals("-mono")) {
channels = 1;
i++;
continue;
} else if (args[i].equals("-stereo")) {
channels = 2;
i++;
continue;
} else if (args[i].equals("-rate")) {
rate = Long.parseLong(args[i + 1]);
i += 2;
continue;
} else { // unknown option
printUsageAndExit();
}
}
/*
* We have made shure that there is only one command line argument. This is taken as the filename of the soundfile to
* store to.
*/
String strFilename = args[args.length - 1];
File outputFile = new File(strFilename);
AudioFormat audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, rate, 16, channels, 2 * channels, rate, false);
/*
* Now, we are trying to get a TargetDataLine. The TargetDataLine is used later to read audio data from it. If requesting
* the line was successful, we are opening it (important!).
*/
DataLine.Info info = new DataLine.Info(TargetDataLine.class, audioFormat);
TargetDataLine targetDataLine = null;
try {
targetDataLine = (TargetDataLine) AudioSystem.getLine(info);
targetDataLine.open(audioFormat);
} catch (LineUnavailableException e) {
out("unable to get a recording line");
e.printStackTrace();
System.exit(1);
}
/*
* Again for simplicity, we've hardcoded the audio file type, too.
*/
AudioFileFormat.Type targetType = AudioFileFormat.Type.WAVE;
/*
* Now, we are creating an SimpleAudioRecorder object. It contains the logic of starting and stopping the recording,
* reading audio data from the TargetDataLine and writing the data to a file.
*/
TimedAudioRecorder recorder = new TimedAudioRecorder(targetDataLine, targetType, outputFile, duration);
/*
* Here, the recording is actually started.
*/
recorder.start();
out("Recording...");
try {
recorder.join();
} catch (InterruptedException ie) {
}
out("Recording stopped.");
}
private static void printUsageAndExit() {
out("TimedAudioRecorder: usage:");
out("\tjava org.jsresources.TimedAudioRecorder -h");
out("\tjava org.jsresources.TimeedAudioRecorder -dur <dur> [-rate <samplerate>] [-stereo|-mono] <audiofile>");
System.exit(0);
}
private static void out(String strMessage) {
System.out.println(strMessage);
}
}
/** * SimpleAudioRecorder.java ** */