/*
* Mobicents, Communications Middleware
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party
* contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
*
* Boston, MA 02110-1301 USA
*/
package org.mobicents.media.server.impl.resource.audio;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat.Encoding;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import org.mobicents.media.Buffer;
import org.mobicents.media.Format;
import org.mobicents.media.format.AudioFormat;
import org.mobicents.media.server.impl.AbstractSink;
import org.mobicents.media.server.impl.NotifyEventImpl;
import org.mobicents.media.server.impl.rtp.sdp.AVProfile;
import org.mobicents.media.server.spi.dsp.Codec;
import org.mobicents.media.server.spi.events.NotifyEvent;
import org.mobicents.media.server.spi.resource.Recorder;
import org.xiph.speex.spi.SpeexEncoding;
/**
* Implements Audio recorder.
* Audio recorder supports WAV, GSM and speeex media types.
* Supported formats are: G711(a-law,u-law), Linear PCM, Speex.
*
* @author Oleg Kulikov
* @author amit bhayani
*/
public class RecorderImpl extends AbstractSink implements Recorder {
private final static Format[] FORMATS = new Format[]{
AVProfile.PCMA,
AVProfile.PCMU,
AVProfile.SPEEX,
Codec.LINEAR_AUDIO
};
private final static AudioFileFormat.Type GSM = new AudioFileFormat.Type("GSM0610", ".gsm");
private final static AudioFileFormat.Type SPEEX = new AudioFileFormat.Type("SPEEX", ".spx");
/** GSM Encoding constant used by Java Sound API */
private final static Encoding GSM_ENCODING = new Encoding("GSM0610");
private final NotifyEventImpl completedEvent;
private final static ExecutorService executor = Executors.newSingleThreadExecutor();
private String recordDir = "";
private AudioFileFormat.Type fileType = AudioFileFormat.Type.WAVE;
private RecorderCache recorderCache;
private FileOutputStream outputStream;
private AudioInputStream inputStream;
private volatile boolean first = true;
private javax.sound.sampled.AudioFormat format;
private boolean isAcceptable = false;
private Format fmt;
/**
* Creates new instance if Recorder.
*
* @param name the name of the recorder to be created.
*/
public RecorderImpl(String name) {
super(name);
completedEvent = new NotifyEventImpl(this, NotifyEventImpl.COMPLETED);
}
/**
* (Non Java-doc.)
*
* @see org.mobicents.media.server.spi.resource.Recorder#setRecordDir(java.lang.String)
*/
public void setRecordDir(String recordDir) {
if (recordDir == null) {
throw new IllegalArgumentException("RecordDir cannot be null");
}
this.recordDir = recordDir;
}
/**
* (Non Java-doc.)
*
* @see org.mobicents.media.server.spi.resource.Recorder#setRecordFile(String)
*/
public void setRecordFile(String uri) throws IOException, FileNotFoundException {
//if output stream is still open try to close it before doing something
if (outputStream != null) {
outputStream.close();
outputStream = null;
}
//construct folders first.
//folders have to be created before creating bore file
String[] tokens = uri.split("/");
String fileName = tokens[tokens.length - 1];
String path = recordDir + "/";
for (int i = 0; i < tokens.length - 1; i++) {
path += tokens[i] + "/";
}
//create folders
java.io.File file = new java.io.File(path);
file.mkdirs();
//prepare output stream
outputStream = new FileOutputStream(path + fileName);
}
@Override
public void stop() {
if (recorderCache != null && recorderCache.available() > 0) {
inputStream = new AudioInputStream(recorderCache, format, recorderCache.available());
recorderCache.unblock();
executor.submit(new RecorderRunnable());
}
super.stop();
}
@Override
public void start() {
super.start();
this.first = true;
}
/**
* Closes all previousle opened resources.
*/
private void release() {
try {
if (outputStream != null) {
outputStream.flush();
outputStream.close();
}
if (this.inputStream != null) {
this.inputStream.close();
this.inputStream = null;
}
} catch (Exception e) {
}
}
public Format[] getFormats() {
return FORMATS;
}
public boolean isAcceptable(Format format) {
if (fmt != null && fmt.matches(format)) {
return isAcceptable;
}
for (int i = 0; i < FORMATS.length; i++) {
if (FORMATS[i].matches(format)) {
fmt = format;
isAcceptable = true;
return isAcceptable;
}
}
isAcceptable = false;
return isAcceptable;
}
/**
* Converts format encoding.
*
* @param encodingName format's encoding name.
* @return the name of format used by Java Sound.
*/
private javax.sound.sampled.AudioFormat.Encoding getEncoding(String encodingName) {
if (encodingName.equalsIgnoreCase(AudioFormat.ALAW)) {
return javax.sound.sampled.AudioFormat.Encoding.ALAW;
} else if (encodingName.equalsIgnoreCase(AudioFormat.ULAW)) {
return javax.sound.sampled.AudioFormat.Encoding.ULAW;
} else if (encodingName.equalsIgnoreCase(AudioFormat.SPEEX)) {
return SpeexEncoding.SPEEX;
} else if (encodingName.equalsIgnoreCase(AudioFormat.GSM)) {
return GSM_ENCODING;
} else {
return javax.sound.sampled.AudioFormat.Encoding.PCM_SIGNED;
}
}
/**
* Initializes Recorder.
*
* This methods is called when first packet arrives and format becomes known.
*
* @param fmt the format of the first arrived packet.
*/
private void openRecorderLine(AudioFormat fmt) {
float sampleRate = (float) fmt.getSampleRate();
int sampleSizeInBits = fmt.getSampleSizeInBits();
int channels = fmt.getChannels();
boolean bigEndian = fmt.getEndian() == 1;
Encoding encoding = getEncoding(fmt.getEncoding());
// int frameSize = (channels == AudioSystem.NOT_SPECIFIED || sampleSizeInBits == AudioSystem.NOT_SPECIFIED) ?
// AudioSystem.NOT_SPECIFIED
// : ((sampleSizeInBits + 7) / 8) * channels;
format = new javax.sound.sampled.AudioFormat(
encoding, sampleRate, sampleSizeInBits, channels, 1, sampleRate, bigEndian);
//assign file type
if (encoding == SpeexEncoding.SPEEX) {
fileType = SPEEX;
} else {
fileType = AudioFileFormat.Type.WAVE;
}
recorderCache = new RecorderCache();
}
@Override
public void onMediaTransfer(Buffer buffer) throws IOException {
if (first) {
first = false;
openRecorderLine((AudioFormat) buffer.getFormat());
}
recorderCache.push(buffer);
}
private class RecorderRunnable implements Runnable {
public RecorderRunnable() {
}
public void run() {
try {
AudioSystem.write(inputStream, fileType, outputStream);
sendEvent(completedEvent);
} catch (Exception e) {
failed(NotifyEvent.RX_FAILED, e);
} finally {
release();
}
}
}
}