/******************************************************************************
* *
* Copyright (c) 1999-2004 Wimba S.A., All Rights Reserved. *
* *
* COPYRIGHT: *
* This software is the property of Wimba S.A. *
* This software is redistributed under the Xiph.org variant of *
* the BSD license. *
* 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. *
* - Neither the name of Wimba, the Xiph.org Foundation nor the names of *
* its contributors may be used to endorse or promote products derived *
* from this software without specific prior written permission. *
* *
* WARRANTIES: *
* This software is made available by the authors in the hope *
* that it will be useful, but without any warranty. *
* Wimba S.A. is not liable for any consequence related to the *
* use of the provided software. *
* *
* Class: Recorder.java *
* *
* Author: Marc GIMPEL *
* *
* Date: Jun 1, 2004 *
* *
******************************************************************************/
/* $Id: Recorder.java,v 1.2 2004/10/21 16:21:58 mgimpel Exp $ */
package org.xiph.speex.player;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
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;
import javax.swing.JButton;
import javax.swing.JFrame;
import org.xiph.speex.spi.SpeexEncoding;
/**
* JavaSound Recorder.
*
* @author Marc Gimpel, Wimba S.A. (mgimpel@horizonwimba.com)
* @version $Revision: 1.2 $
*/
public class Recorder
extends Player
{
/** Revision Number */
public static final String REVISION = "$Revision: 1.2 $";
/** Audio sampled at 8 kHz (telephone quality). */
public static final String SAMPLERATE_8KHZ = "8000 Hz";
/** Audio sampled at 11 kHz. */
public static final String SAMPLERATE_11KHZ = "11025 Hz";
/** Audio sampled at 16 kHz (wideband). */
public static final String SAMPLERATE_16KHZ = "16000 Hz";
/** Audio sampled at 22 kHz (FM radio quality). */
public static final String SAMPLERATE_22KHZ = "22050 Hz";
/** Audio sampled at 32 kHz (ultra-wideband). */
public static final String SAMPLERATE_32KHZ = "32000 Hz";
/** Audio sampled at 44 kHz (CD quality). */
public static final String SAMPLERATE_44KHZ = "44100 Hz";
/** Mono Audio (1 channel). */
public static final String CHANNELS_MONO = "mono";
/** Stereo Audio (2 channels). */
public static final String CHANNELS_STEREO = "stereo";
// Possible States for the Finite State Machine
/** Finite State Machine State: Recording */
protected static final int STATE_RECORDING = 6;
/** Finite State Machine State: Recording Paused */
protected static final int STATE_REC_PAUSED = 7;
/** Record Button */
protected JButton recordButton;
protected Capture capture;
protected byte[] audio;
//--------------------------------------------------------------------------
// Initialization code
//--------------------------------------------------------------------------
/**
* Command Line entrance.
* @param args
*/
public static void main(final String[] args)
{
String filename = null;
if (args.length > 0) {
filename = args[0];
}
final Recorder recorder = new Recorder(filename);
recorder.init();
JFrame frame = new JFrame("Recorder");
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {System.exit(0);}
public void windowDeiconified(WindowEvent e) { recorder.start(); }
public void windowIconified(WindowEvent e) { recorder.stop(); }
});
frame.getContentPane().add("Center", recorder);
frame.pack();
frame.setVisible(true);
}
/**
* Build a Recorder.
* @param file
*/
public Recorder(final String file)
{
super(file);
}
/**
* Initialize the Player Component.
*/
public void init()
{
super.init();
capture = new Capture();
}
/**
* Returns an InputSteam containing the Audio to playback.
* @return an InputSteam containing the Audio to playback.
* @throws IOException
*/
protected InputStream getAudioStream()
throws IOException
{
if (audio == null) {
return new BufferedInputStream(audioFile.openStream());
}
else {
return new ByteArrayInputStream(audio);
}
}
//--------------------------------------------------------------------------
// Running code
//--------------------------------------------------------------------------
/**
* Capture thread
*/
protected class Capture
implements Runnable
{
protected ByteArrayOutputStream out;
protected AudioInputStream audioInputStream;
protected AudioFormat audioFormat;
protected DataLine.Info info;
protected AudioFileFormat.Type targetType;
protected TargetDataLine line;
protected byte[] buffer;
protected Thread thread;
/**
* Start the playback thread which fills the JavaSound playback buffer.
*/
protected void start() {
thread = new Thread(this);
thread.setName("Capture");
thread.start();
}
/**
* Stop the playback thread and destroy all resources.
*/
protected void stop() {
// Stop the thread
thread = null;
// Close the line
if (line != null) {
line.stop();
line.close();
line = null;
}
// stop and close the output stream
try {
out.flush();
out.close();
} catch (IOException ex) {
ex.printStackTrace();
}
// load bytes into the audio input stream for playback
audio = out.toByteArray();
System.out.println("size="+audio.length);
}
/**
* Setup the JavaSound System to play the Audio.
*/
protected void setupSound()
{
// define the required attributes for our line,
// and make sure a compatible line is supported.
audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
44100.0F, 16, 1, 2, 44100.0F, 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!).
*/
info = new DataLine.Info(TargetDataLine.class, audioFormat);
try {
line = (TargetDataLine) AudioSystem.getLine(info);
line.open(audioFormat);
}
catch (LineUnavailableException e) {
e.printStackTrace();
}
catch (SecurityException e) {
// An applet requires the following permissions to record audio:
// permission javax.sound.sampled.AudioPermission : record
e.printStackTrace();
}
audioInputStream = new AudioInputStream(line);
AudioFormat targetFormat = new AudioFormat(SpeexEncoding.SPEEX_Q6,
audioFormat.getSampleRate(),
-1, // sample size in bits
audioFormat.getChannels(),
-1, // frame size
-1, // frame rate
false); // little endian
audioInputStream = AudioSystem.getAudioInputStream(targetFormat, audioInputStream);
audioFormat = audioInputStream.getFormat();
out = new ByteArrayOutputStream();
// setup copying buffer
buffer = new byte[128000];
}
/**
* The code that runs in the thread and recovers the JavaSound capture
* buffer.
* Implemented from Runnable interface.
*/
public void run()
{
int read = 0;
while (thread != null && state == STATE_RECORDING && read != -1) {
try {
read = audioInputStream.read(buffer, 0, buffer.length);
}
catch (IOException e) {
e.printStackTrace();
}
if (read > 0) {
out.write(buffer, 0, read);
}
}
}
} // End class Capture
//--------------------------------------------------------------------------
// Action processing code
//--------------------------------------------------------------------------
/**
* Process Actions when button are pressed.
* Implemented from ActionListener interface.
*/
public void actionPerformed(final ActionEvent e) {
if (e.getSource() == timer) {
progressBar.setValue(getProgress());
}
else {
if ("Play".equals(e.getActionCommand())) {
playIt();
}
else if ("Record".equals(e.getActionCommand())) {
recordIt();
}
else if ("Pause".equals(e.getActionCommand())) {
if (state == STATE_PAUSED) {
playIt();
}
else if (state == STATE_REC_PAUSED) {
recordIt();
}
else {
pauseIt();
}
}
else if ("Stop".equals(e.getActionCommand())) {
stopIt();
}
else {
}
}
}
/**
*
*/
public synchronized void stopIt()
{
recordButton.setEnabled(false);
playButton.setEnabled(false);
pauseButton.setEnabled(false);
stopButton.setEnabled(false);
oldstate = state;
state = STATE_STOPPED;
if (oldstate == STATE_PLAYING || oldstate == STATE_PAUSED) {
playback.stop();
}
else if (oldstate == STATE_RECORDING || oldstate == STATE_REC_PAUSED) {
capture.stop();
}
timer.stop();
progressBar.setValue(0);
recordButton.setEnabled(true);
playButton.setEnabled(true);
pauseButton.setEnabled(false);
stopButton.setEnabled(false);
}
/**
*
*/
public synchronized void playIt()
{
recordButton.setEnabled(false);
playButton.setEnabled(false);
pauseButton.setEnabled(false);
stopButton.setEnabled(false);
oldstate = state;
state = STATE_PLAYING;
if (oldstate == STATE_STOPPED) {
playback.setupSound();
}
if (playback.thread == null || !playback.thread.isAlive()) {
playback.start();
}
playback.line.start();
timer.start();
recordButton.setEnabled(false);
playButton.setEnabled(false);
pauseButton.setEnabled(true);
stopButton.setEnabled(true);
}
/**
*
*/
public synchronized void pauseIt()
{
recordButton.setEnabled(false);
playButton.setEnabled(false);
pauseButton.setEnabled(false);
stopButton.setEnabled(false);
oldstate = state;
if (oldstate == STATE_PLAYING) {
state = STATE_PAUSED;
playback.line.stop();
recordButton.setEnabled(false);
playButton.setEnabled(true);
}
else if (oldstate == STATE_RECORDING) {
state = STATE_REC_PAUSED;
capture.line.stop();
recordButton.setEnabled(true);
playButton.setEnabled(false);
}
timer.stop();
pauseButton.setEnabled(true);
stopButton.setEnabled(true);
}
/**
*
*/
public synchronized void recordIt()
{
recordButton.setEnabled(false);
playButton.setEnabled(false);
pauseButton.setEnabled(false);
stopButton.setEnabled(false);
oldstate = state;
state = STATE_RECORDING;
if (oldstate == STATE_STOPPED) {
capture.setupSound();
}
if (capture.thread == null || !capture.thread.isAlive()) {
capture.start();
}
capture.line.start();
timer.start();
recordButton.setEnabled(false);
playButton.setEnabled(false);
pauseButton.setEnabled(true);
stopButton.setEnabled(true);
}
/**
* Return the progress of the playback.
* @return the progress of the playback.
*/
protected int getProgress()
{
audioLength = 500000;
if (state == STATE_PLAYING || state == STATE_PAUSED) {
return super.getProgress();
}
else if (state == STATE_RECORDING || state == STATE_REC_PAUSED) {
return capture.line.getFramePosition() * 1000 / audioLength;
}
else {
return 0;
}
}
//--------------------------------------------------------------------------
// GUI code
//--------------------------------------------------------------------------
/**
* Create the ButtonPanel for the recorder.
* The recorder button panel that should look something like this:
* <pre>
* +--------------------------------+
* | +------+ +----+ +-----+ +----+ |
* | |record| |play| |pause| |stop| | Button Panel
* | +------+ +----+ +-----+ +----+ |
* +--------------------------------+
* </pre>
*/
protected void createButtonPanel()
{
recordButton = buildButton("Record", "Record",
"/images/player_record.gif",
"/images/player_record2.gif",
"/images/player_record3.gif", this);
playerButtonPane.add(recordButton);
super.createButtonPanel();
}
}