/**
* Xtreme Media Player a cross-platform media player.
* Copyright (C) 2005-2011 Besmir Beqiri
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package xtrememp.player.dsp;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.SourceDataLine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class provides synchronization between a digital signal processor and
* speaker output.
*
* Based on KJ-DSS project by Kristofer Fudalewski (http://sirk.sytes.net).
*
* @author Besmir Beqiri
*/
public class DigitalSignalSynchronizer implements LineListener, Runnable {
private static Logger logger = LoggerFactory.getLogger(DigitalSignalSynchronizer.class);
public static final int DEFAULT_FPS = 60;
public static final int DEFAULT_SAMPLE_SIZE = 2048;
private final List<DigitalSignalProcessor> dspList;
private final ScheduledExecutorService execService;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock rLock = lock.readLock();
private final Lock wLock = lock.writeLock();
private final Condition writeCondition = wLock.newCondition();
private ScheduledFuture schedFuture;
private int sampleSize = DEFAULT_SAMPLE_SIZE;
private int framesPerSecond = DEFAULT_FPS;
private SourceDataLine sourceDataLine;
private ByteBuffer audioDataBuffer;
private DssContext dssContext;
/**
* Default constructor.
*/
public DigitalSignalSynchronizer() {
this(DEFAULT_SAMPLE_SIZE, DEFAULT_FPS);
}
/**
* @param sampleSize The sample size to extract from audio data sent to the SourceDataLine.
* @param framesPerSecond The desired refresh rate per second of registered DSP's.
*/
public DigitalSignalSynchronizer(int sampleSize, int framesPerSecond) {
this.sampleSize = sampleSize;
this.framesPerSecond = framesPerSecond;
this.dspList = new CopyOnWriteArrayList<DigitalSignalProcessor>();
this.execService = Executors.newSingleThreadScheduledExecutor();
}
/**
* Adds a DSP to the DSS and forwards any audio data to it at the specified frame rate.
*
* @param dsp A class implementing the DigitalSignalProcessor interface.
*/
public void add(DigitalSignalProcessor dsp) {
if (dsp == null) {
throw new IllegalArgumentException();
}
if (sourceDataLine != null) {
dsp.init(sampleSize, sourceDataLine);
}
if (dspList.add(dsp)) {
logger.info("DSP added");
}
wLock.lock();
try {
writeCondition.signal();
} finally {
wLock.unlock();
}
}
/**
* Removes the specified DSP from this DSS if it exists.
*
* @param dsp A class implementing the DigitalSignalProcessor interface.
*/
public void remove(DigitalSignalProcessor dsp) {
if (dsp == null) {
throw new IllegalArgumentException();
}
if (dspList.remove(dsp)) {
logger.info("DSP removed");
}
}
/**
* Start monitoring the specified SourceDataLine.
*
* @param sdl a SourceDataLine.
*/
protected void open(SourceDataLine sdl) {
//Stop processing previous source data line.
if (schedFuture != null && !schedFuture.isCancelled()) {
stop();
}
sourceDataLine = sdl;
dssContext = new DssContext(sourceDataLine, sampleSize);
audioDataBuffer = ByteBuffer.allocate(sdl.getBufferSize());
//Initialize DSP registered with this DSS.
for (DigitalSignalProcessor dsp : dspList) {
dsp.init(sampleSize, sourceDataLine);
}
}
protected void start() {
long delay = Math.round(1000 / framesPerSecond);
schedFuture = execService.scheduleWithFixedDelay(this, 0, delay, TimeUnit.MILLISECONDS);
}
protected boolean isRunning() {
if (schedFuture != null) {
return !schedFuture.isDone();
}
return false;
}
/**
* Stop monitoring the current SourceDataLine and release resources.
*/
protected void stop() {
if (schedFuture != null) {
schedFuture.cancel(true);
}
}
protected void close() {
if (schedFuture != null) {
schedFuture.cancel(true);
}
if (audioDataBuffer != null) {
audioDataBuffer.clear();
}
}
/**
* Writes part of specified buffer to the monitored source data line an any registered DSPs.
*
* @param audioData Data to write.
* @param offset Offset to start reading from the buffer.
* @param length The length from the specified offset to read.
*/
public void writeAudioData(byte[] audioData, int offset, int length) {
if (audioDataBuffer == null) {
return;
}
wLock.lock();
try {
if (audioDataBuffer.remaining() < length) {
audioDataBuffer.clear();
}
audioDataBuffer.put(audioData, offset, length);
} finally {
wLock.unlock();
}
}
@Override
public void run() {
if (!dspList.isEmpty()) {
rLock.lock();
try {
dssContext.normalizeData(audioDataBuffer);
} finally {
rLock.unlock();
}
//Dispatch sample data to digtal signal processors.
for (DigitalSignalProcessor dsp : dspList) {
dsp.process(dssContext);
}
} else {
wLock.lock();
try {
writeCondition.awaitUninterruptibly();
} finally {
wLock.unlock();
}
}
}
@Override
public void update(LineEvent event) {
LineEvent.Type type = event.getType();
wLock.lock();
try {
if (type.equals(LineEvent.Type.OPEN)) {
open((SourceDataLine) event.getLine());
} else if (type.equals(LineEvent.Type.START)) {
start();
} else if (type.equals(LineEvent.Type.STOP)) {
stop();
} else if (type.equals(LineEvent.Type.CLOSE)) {
close();
}
} finally {
wLock.unlock();
}
}
}