Merge remote-tracking branch 'sound/develop' into sound
This commit is contained in:
186
java/sound/src/old/Converter.java
Normal file
186
java/sound/src/old/Converter.java
Normal file
@@ -0,0 +1,186 @@
|
||||
package old;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javazoom.jl.decoder.Bitstream;
|
||||
import javazoom.jl.decoder.BitstreamException;
|
||||
import mimis.exception.worker.ActivateException;
|
||||
import mimis.exception.worker.DeactivateException;
|
||||
import mimis.worker.Worker;
|
||||
|
||||
import com.Ostermiller.util.CircularByteBuffer;
|
||||
|
||||
public class Converter extends Worker {
|
||||
public static final String COMMAND = "lame --mp3input --cbr %s - - --quiet";
|
||||
public static final int BYTES = 4096; // bytes
|
||||
public static final int BUFFER = 30000; // milliseconds
|
||||
public static final int BUFFERING = 1000; // milliseconds
|
||||
|
||||
protected int targetRate;
|
||||
protected int rate;
|
||||
protected int buffer;
|
||||
protected boolean convert;
|
||||
|
||||
protected Process process;
|
||||
protected InputStream sourceInputStream, processInputStream, inputStream;
|
||||
protected OutputStream processOutputStream;
|
||||
protected CircularByteBuffer circularByteBuffer;
|
||||
protected BufferWorker bufferWorker;
|
||||
|
||||
public Converter(InputStream inputStream) {
|
||||
this(inputStream, -1);
|
||||
}
|
||||
|
||||
public Converter(InputStream inputStream, int targetRate) {
|
||||
this.sourceInputStream = inputStream;
|
||||
this.targetRate = targetRate;
|
||||
bufferWorker = new BufferWorker();
|
||||
convert = false;
|
||||
}
|
||||
|
||||
public void exit() {
|
||||
super.exit();
|
||||
bufferWorker.exit();
|
||||
}
|
||||
|
||||
public synchronized void activate() throws ActivateException {
|
||||
/* Read bitrate */
|
||||
BufferedInputStream bufferedInputStream = new BufferedInputStream(sourceInputStream);
|
||||
Bitstream bitStream = new Bitstream(bufferedInputStream);
|
||||
try {
|
||||
rate = bitStream.readFrame().bitrate() / 1000;
|
||||
buffer = BUFFER * rate / 8;
|
||||
} catch (BitstreamException e) {
|
||||
log.error(e);
|
||||
throw new ActivateException();
|
||||
}
|
||||
|
||||
/* Check for need to convert */
|
||||
if (targetRate < 0 || rate == targetRate) {
|
||||
log.debug("No conversion required");
|
||||
inputStream = sourceInputStream;
|
||||
} else {
|
||||
log.debug("Converting from " + rate + "kbps to " + targetRate + "kbps");
|
||||
try {
|
||||
String command = String.format(COMMAND, rate > targetRate ? "-B " + targetRate : "-F -b " + targetRate);
|
||||
log.debug("Starting process: " + command);
|
||||
process = Runtime.getRuntime().exec(command);
|
||||
processInputStream = process.getInputStream();
|
||||
processOutputStream = process.getOutputStream();
|
||||
|
||||
/* Buffer output */
|
||||
circularByteBuffer = new CircularByteBuffer(CircularByteBuffer.INFINITE_SIZE);
|
||||
inputStream = circularByteBuffer.getInputStream();
|
||||
bufferWorker.start();
|
||||
convert = true;
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
throw new ActivateException();
|
||||
}
|
||||
}
|
||||
super.activate();
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
protected void deactivate() throws DeactivateException {
|
||||
super.deactivate();
|
||||
try {
|
||||
sourceInputStream.close();
|
||||
bufferWorker.stop();
|
||||
if (convert) {
|
||||
circularByteBuffer.clear();
|
||||
convert = false;
|
||||
}
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
throw new DeactivateException();
|
||||
}
|
||||
}
|
||||
|
||||
protected void work() {
|
||||
if (!convert) {
|
||||
try {
|
||||
synchronized (this) {
|
||||
wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
byte[] bytes = new byte[BYTES];
|
||||
int read = 0;
|
||||
try {
|
||||
log.debug("Writing input to process");
|
||||
while ((read = sourceInputStream.read(bytes)) > 0 && !deactivate) {
|
||||
/* Limit buffer size */
|
||||
while (inputStream.available() > buffer) {
|
||||
int progress = (int) ((1 - (inputStream.available() - buffer) / (float) buffer) * 100);
|
||||
log.trace("Waiting for buffer to empty: " + progress + "%");
|
||||
sleep(BUFFERING);
|
||||
}
|
||||
processOutputStream.write(bytes, 0, read);
|
||||
}
|
||||
processOutputStream.close();
|
||||
log.debug("Stopped writing input to process");
|
||||
process.waitFor();
|
||||
log.debug("Process finished");
|
||||
} catch (IOException e) {
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e);
|
||||
}
|
||||
stop();
|
||||
}
|
||||
|
||||
public synchronized InputStream getInputStream() {
|
||||
if (!active()) {
|
||||
if (!activate) {
|
||||
start();
|
||||
}
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
public synchronized void setInputStream(InputStream inputStream) {
|
||||
this.inputStream = inputStream;
|
||||
}
|
||||
|
||||
class BufferWorker extends Worker {
|
||||
protected void work() {
|
||||
byte[] bytes = new byte[BYTES];
|
||||
int read = 0;
|
||||
try {
|
||||
OutputStream bufferOutputStream = circularByteBuffer.getOutputStream();
|
||||
log.debug("Start buffering process output");
|
||||
while ((read = processInputStream.read(bytes, 0, BYTES)) > 0) {
|
||||
bufferOutputStream.write(bytes, 0, read);
|
||||
}
|
||||
log.debug("Finished buffering process output");
|
||||
bufferOutputStream.close();
|
||||
} catch (IOException e) {}
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Mp3 mp3 = new Mp3(new File("stream.mp3"), 128);
|
||||
InputStream inputStream = mp3.getInputStream();
|
||||
|
||||
/* Play */
|
||||
//Utils.play(inputStream);
|
||||
|
||||
/* Write to file */
|
||||
Utils.write(inputStream, new File("output.mp3"));
|
||||
mp3.exit();
|
||||
}
|
||||
}
|
||||
198
java/sound/src/old/List.java
Normal file
198
java/sound/src/old/List.java
Normal file
@@ -0,0 +1,198 @@
|
||||
package old;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Scanner;
|
||||
|
||||
import mimis.exception.worker.ActivateException;
|
||||
import mimis.worker.Worker;
|
||||
|
||||
import com.Ostermiller.util.BufferOverflowException;
|
||||
import com.Ostermiller.util.CircularByteBuffer;
|
||||
import com.Ostermiller.util.CircularObjectBuffer;
|
||||
|
||||
public class List extends Worker {
|
||||
public static final int STEP = 80; // milliseconds
|
||||
public static final int RATE = 192; // kbps
|
||||
public static final int OVERLAP = 20000; // milliseconds
|
||||
|
||||
protected File file;
|
||||
protected String[] fileArray;
|
||||
|
||||
protected int rate;
|
||||
protected int chunk;
|
||||
protected int overlap;
|
||||
protected byte[] bytes;
|
||||
protected boolean next;
|
||||
protected Mp3 mp3, nextMp3;
|
||||
|
||||
protected InputStream mp3InputStream;
|
||||
protected OutputStream audioOutputStream;
|
||||
protected CircularByteBuffer circularByteBuffer;
|
||||
protected CircularObjectBuffer<String> circularStringBuffer;
|
||||
|
||||
public List(File file) {
|
||||
this(file, RATE);
|
||||
}
|
||||
|
||||
public List(File file, int rate) {
|
||||
this.file = file;
|
||||
this.rate = rate;
|
||||
chunk = STEP * rate / 8;
|
||||
overlap = OVERLAP * RATE / 8;
|
||||
bytes = new byte[chunk];
|
||||
next = true;
|
||||
}
|
||||
|
||||
public void exit() {
|
||||
super.exit();
|
||||
if (mp3 != null) {
|
||||
mp3.exit();
|
||||
}
|
||||
if (nextMp3 != null) {
|
||||
nextMp3.exit();
|
||||
}
|
||||
}
|
||||
|
||||
protected synchronized void activate() throws ActivateException {
|
||||
try {
|
||||
Scanner scanner = new Scanner(file);
|
||||
ArrayList<String> fileList = new ArrayList<String>();
|
||||
while (scanner.hasNextLine()) {
|
||||
fileList.add(scanner.nextLine());
|
||||
}
|
||||
if (fileList.size() > 0) {
|
||||
fileArray = fileList.toArray(new String[0]);
|
||||
|
||||
circularByteBuffer = new CircularByteBuffer(CircularByteBuffer.INFINITE_SIZE);
|
||||
audioOutputStream = circularByteBuffer.getOutputStream();
|
||||
|
||||
circularStringBuffer = new CircularObjectBuffer<String>(CircularByteBuffer.INFINITE_SIZE);
|
||||
setNext();
|
||||
super.activate();
|
||||
notifyAll();
|
||||
return;
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
log.error(e);
|
||||
}
|
||||
throw new ActivateException();
|
||||
}
|
||||
|
||||
protected synchronized void work() {
|
||||
try {
|
||||
int left = chunk;
|
||||
while (left > 0) {
|
||||
/* Check for need to load next mp3 */
|
||||
int available = mp3InputStream == null ? -1 : mp3InputStream.available();
|
||||
boolean expect = mp3 == null ? false : mp3.active();
|
||||
|
||||
/* Act when no more data is expected */
|
||||
if (!expect) {
|
||||
if (available < overlap) {
|
||||
setNext();
|
||||
next = false;
|
||||
nextMp3.start();
|
||||
}
|
||||
if (available < 1) {
|
||||
swap();
|
||||
}
|
||||
}
|
||||
|
||||
/* Transfer data */
|
||||
int read = mp3InputStream.read(bytes, 0, left);
|
||||
left -= read;
|
||||
audioOutputStream.write(bytes, 0, read);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
/* Swap to next if stream has stopped */
|
||||
setNext();
|
||||
swap();
|
||||
} catch (IllegalStateException e) {
|
||||
log.error(e);
|
||||
}
|
||||
sleep(STEP);
|
||||
}
|
||||
|
||||
protected File getRandomFile() {
|
||||
return new File(fileArray[(int) (Math.random() * fileArray.length)]);
|
||||
}
|
||||
|
||||
public synchronized void setNext() {
|
||||
if (nextMp3 == null) {
|
||||
log.debug("Initialize next mp3");
|
||||
nextMp3 = new Mp3(getRandomFile(), rate);
|
||||
} else if (next) {
|
||||
log.debug("Load next mp3");
|
||||
nextMp3.setFile(getRandomFile());
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void next() {
|
||||
log.debug("Stop current mp3");
|
||||
mp3.stop();
|
||||
}
|
||||
|
||||
public void swap() {
|
||||
log.debug("Swap to next mp3");
|
||||
Mp3 swapMp3 = mp3;
|
||||
mp3 = nextMp3;
|
||||
nextMp3 = swapMp3;
|
||||
next = true;
|
||||
|
||||
/* Swap stream and announce title */
|
||||
mp3InputStream = mp3.getInputStream();
|
||||
try {
|
||||
circularStringBuffer.write(mp3.getTitle());
|
||||
} catch (BufferOverflowException e) {
|
||||
log.error(e);
|
||||
} catch (IllegalStateException e) {
|
||||
log.error(e);
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized InputStream getInputStream() {
|
||||
if (circularByteBuffer == null) {
|
||||
start();
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
return circularByteBuffer.getInputStream();
|
||||
}
|
||||
|
||||
public synchronized CircularObjectBuffer<String> getMetaBuffer() {
|
||||
if (circularStringBuffer == null) {
|
||||
start();
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
return circularStringBuffer;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
int rate = 192;
|
||||
List list = new List(new File("mp3"), rate);
|
||||
/*Shoutcast shoutcast = new Shoutcast(null, rate, 9876);
|
||||
shoutcast.start();
|
||||
shoutcast.setInputStream(list.getInputStream());
|
||||
shoutcast.setMetaBuffer(list.getMetaBuffer());*/
|
||||
while (true) {
|
||||
try {
|
||||
Thread.sleep(15000);
|
||||
list.next();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
79
java/sound/src/old/Mp3.java
Normal file
79
java/sound/src/old/Mp3.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package old;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
import mimis.exception.worker.ActivateException;
|
||||
|
||||
import org.farng.mp3.MP3File;
|
||||
import org.farng.mp3.TagException;
|
||||
|
||||
public class Mp3 extends Converter {
|
||||
protected File file;
|
||||
protected String title;
|
||||
|
||||
public Mp3(File file) {
|
||||
this(file, -1);
|
||||
}
|
||||
|
||||
public Mp3(File file, int targetRate) {
|
||||
super(null, targetRate);
|
||||
setFile(file);
|
||||
title = "";
|
||||
}
|
||||
|
||||
public synchronized void activate() throws ActivateException {
|
||||
/* Open file */
|
||||
try {
|
||||
sourceInputStream = new FileInputStream(file);
|
||||
} catch (FileNotFoundException e) {
|
||||
log.error(e);
|
||||
throw new ActivateException();
|
||||
}
|
||||
|
||||
/* Read ID3V2 tags */
|
||||
try {
|
||||
MP3File mp3File = new MP3File(file);
|
||||
String album = clean(mp3File.getID3v2Tag().getAlbumTitle());
|
||||
String artist = clean(mp3File.getID3v2Tag().getLeadArtist());
|
||||
String track = clean(mp3File.getID3v2Tag().getSongTitle());
|
||||
if (album.isEmpty()) {
|
||||
title = String.format("%s - %s", artist, track, album);
|
||||
} else {
|
||||
title = String.format("%s - %s {%s}", artist, track, album);
|
||||
}
|
||||
log.debug("Title: " + title);
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
} catch (TagException e) {
|
||||
log.error(e);
|
||||
}
|
||||
try {
|
||||
sourceInputStream.skip(100000);
|
||||
} catch (IOException e) {}
|
||||
super.activate();
|
||||
}
|
||||
|
||||
protected String clean(String input) {
|
||||
String output = input.replace("\0", "");
|
||||
return output.replace("<EFBFBD><EFBFBD>", "");
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setFile(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
final Mp3 mp3 = new Mp3(new File("input.mp3"), 128);
|
||||
Utils.write(mp3.getInputStream(), new File("one.mp3"));
|
||||
mp3.setFile(new File("stream.mp3"));
|
||||
Utils.write(mp3.getInputStream(), new File("two.mp3"));
|
||||
mp3.exit();
|
||||
}
|
||||
}
|
||||
42
java/sound/src/old/Utils.java
Normal file
42
java/sound/src/old/Utils.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package old;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javazoom.jl.decoder.JavaLayerException;
|
||||
import javazoom.jl.player.Player;
|
||||
|
||||
public class Utils {
|
||||
public static final int BUFFER = 2048; // bytes
|
||||
|
||||
public static void play(InputStream inputStream) {
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
new Player(new BufferedInputStream(inputStream)).play();
|
||||
} catch (JavaLayerException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void write(InputStream inputStream, File file) {
|
||||
byte[] bytes = new byte[BUFFER];
|
||||
int read = 0;
|
||||
try {
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(file);
|
||||
while ((read = inputStream.read(bytes)) > 0) {
|
||||
fileOutputStream.write(bytes, 0, read);
|
||||
}
|
||||
fileOutputStream.close();
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
8
java/sound/src/sound/Consumer.java
Normal file
8
java/sound/src/sound/Consumer.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package sound;
|
||||
|
||||
public interface Consumer {
|
||||
public void start(Producer producer);
|
||||
public void start();
|
||||
public void stop();
|
||||
public void exit();
|
||||
}
|
||||
13
java/sound/src/sound/Format.java
Normal file
13
java/sound/src/sound/Format.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package sound;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
|
||||
public interface Format extends Cloneable {
|
||||
public interface Standard extends Format {
|
||||
public AudioFormat getAudioFormat();
|
||||
}
|
||||
|
||||
public interface Mp3 extends Format {
|
||||
public int getRate();
|
||||
}
|
||||
}
|
||||
76
java/sound/src/sound/GreedyInputStream.java
Normal file
76
java/sound/src/sound/GreedyInputStream.java
Normal file
@@ -0,0 +1,76 @@
|
||||
package sound;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
public class GreedyInputStream extends BufferedInputStream {
|
||||
protected Log log = LogFactory.getLog(getClass());
|
||||
|
||||
protected static final int SLEEP = 500;
|
||||
protected static final int BUFFER_SIZE = 30000; // in bytes
|
||||
protected static final int MINIMUM_SIZE = 1000; // in bytes
|
||||
|
||||
protected int bufferSize;
|
||||
protected int minimumSize;
|
||||
protected boolean hoard;
|
||||
|
||||
public GreedyInputStream(InputStream inputStream) {
|
||||
this(inputStream, BUFFER_SIZE, MINIMUM_SIZE);
|
||||
}
|
||||
|
||||
public GreedyInputStream(InputStream inputStream, int bufferSize) {
|
||||
super(inputStream, bufferSize);
|
||||
this.bufferSize = bufferSize;
|
||||
hoard = true;
|
||||
}
|
||||
|
||||
public GreedyInputStream(InputStream inputStream, int bufferSize, int minimumSize) {
|
||||
this(inputStream, bufferSize);
|
||||
this.minimumSize = minimumSize;
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
hoard();
|
||||
byte[] buffer = new byte[1];
|
||||
in.read(buffer, 0, 1);
|
||||
return (int) buffer[0];
|
||||
}
|
||||
|
||||
public int read(byte[] buffer, int offset, int length) throws IOException {
|
||||
hoard();
|
||||
in.read(buffer, offset, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
public void hoard() throws IOException {
|
||||
int available = available();
|
||||
if (hoard && available < MINIMUM_SIZE) {
|
||||
long time = System.currentTimeMillis();
|
||||
do {
|
||||
try {
|
||||
Thread.sleep(SLEEP);
|
||||
} catch (InterruptedException e) {
|
||||
log.warn(e);
|
||||
}
|
||||
} while (available() < BUFFER_SIZE);
|
||||
log.debug(String.format("Buffered %d bytes in %s milliseconds", BUFFER_SIZE - available, System.currentTimeMillis() - time));
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() throws IOException {
|
||||
this.buf = new byte[buf.length];
|
||||
reset();
|
||||
}
|
||||
|
||||
public void drain() {
|
||||
drain(true);
|
||||
}
|
||||
|
||||
public void drain(boolean drain) {
|
||||
hoard = !drain;
|
||||
}
|
||||
}
|
||||
119
java/sound/src/sound/Port.java
Normal file
119
java/sound/src/sound/Port.java
Normal file
@@ -0,0 +1,119 @@
|
||||
package sound;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
|
||||
import mimis.exception.worker.ActivateException;
|
||||
import mimis.exception.worker.DeactivateException;
|
||||
import mimis.worker.Worker;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import sound.Format.Standard;
|
||||
import sound.SoxBuilder.File;
|
||||
import sound.SoxBuilder.Option;
|
||||
import sound.SoxBuilder.File.Type;
|
||||
|
||||
public class Port extends Worker implements Consumer {
|
||||
protected Log log = LogFactory.getLog(getClass());
|
||||
|
||||
protected static final int BUFFER_SIZE = 1024 * 4; // in bytes
|
||||
|
||||
protected String device;
|
||||
protected Producer producer;
|
||||
protected Process process;
|
||||
protected InputStream producerInputStream;
|
||||
protected OutputStream processOutputStream;
|
||||
protected ProcessBuilder processBuilder;
|
||||
|
||||
public Port() {
|
||||
this("0");
|
||||
}
|
||||
|
||||
public Port(String device) {
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
@SuppressWarnings("static-access")
|
||||
public void start(Producer producer) {
|
||||
this.producer = producer;
|
||||
producerInputStream = producer.getInputStream();
|
||||
|
||||
String command = "";
|
||||
if (producer instanceof Standard) {
|
||||
AudioFormat audioFormat = ((Standard) producer).getAudioFormat();
|
||||
SoxBuilder.addFile(File.setType(Type.STANDARD).setOptions(audioFormat));
|
||||
} else if (producer instanceof Format.Mp3) {
|
||||
SoxBuilder.addFile(File.setType(Type.STANDARD).setOption(File.Format.MP3));
|
||||
}
|
||||
command = SoxBuilder
|
||||
.setOption(Option.QUIET)
|
||||
.addFile(File.setType(Type.DEVICE))
|
||||
.build();
|
||||
|
||||
log.debug(String.format("Build process (\"%s\")", command));
|
||||
processBuilder = new ProcessBuilder(command.split(" "));
|
||||
processBuilder.environment().put("AUDIODEV", device);
|
||||
|
||||
start(true);
|
||||
}
|
||||
|
||||
protected void activate() throws ActivateException {
|
||||
producer.start();
|
||||
if (process == null) {
|
||||
try {
|
||||
process = processBuilder.start();
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
throw new ActivateException();
|
||||
}
|
||||
processOutputStream = process.getOutputStream();
|
||||
}
|
||||
super.activate();
|
||||
}
|
||||
|
||||
protected void deactivate() throws DeactivateException {
|
||||
super.deactivate();
|
||||
try {
|
||||
processOutputStream.flush();
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
throw new DeactivateException();
|
||||
}
|
||||
}
|
||||
|
||||
public void exit() {
|
||||
try {
|
||||
log.debug("close process output stream");
|
||||
processOutputStream.close();
|
||||
|
||||
log.debug("wait for process to terminate");
|
||||
process.waitFor();
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e);
|
||||
} finally {
|
||||
process = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void work() {
|
||||
try {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int read = producerInputStream.read(buffer, 0, buffer.length);
|
||||
if (read > 0) {
|
||||
processOutputStream.write(buffer, 0, read);
|
||||
} else {
|
||||
exit();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
10
java/sound/src/sound/Producer.java
Normal file
10
java/sound/src/sound/Producer.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package sound;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface Producer extends Format {
|
||||
public InputStream getInputStream();
|
||||
public void start();
|
||||
public void stop();
|
||||
public void exit();
|
||||
}
|
||||
159
java/sound/src/sound/Source.java
Normal file
159
java/sound/src/sound/Source.java
Normal file
@@ -0,0 +1,159 @@
|
||||
package sound;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
|
||||
import javazoom.jl.decoder.JavaLayerException;
|
||||
import javazoom.jl.player.Player;
|
||||
import mimis.exception.worker.ActivateException;
|
||||
import mimis.exception.worker.DeactivateException;
|
||||
import mimis.worker.Worker;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
public class Source implements Consumer {
|
||||
protected Log log = LogFactory.getLog(getClass());
|
||||
|
||||
protected static final int BUFFER_SIZE = 1024 * 4; // in bytes
|
||||
protected static final int PLAY_FRAMES = 10; // count
|
||||
|
||||
protected String name;
|
||||
protected Producer producer;
|
||||
protected InputStream producerInputStream;
|
||||
protected Worker worker;
|
||||
|
||||
public Source(String name) throws LineUnavailableException {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (worker != null) {
|
||||
worker.start(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void start(Producer producer) {
|
||||
this.producer = producer;
|
||||
producerInputStream = producer.getInputStream();
|
||||
if (worker != null) {
|
||||
worker.exit();
|
||||
}
|
||||
if (producer instanceof Format.Standard) {
|
||||
log.debug("Format.Standard");
|
||||
worker = new DefaultWorker((Format.Standard) producer);
|
||||
} else if (producer instanceof Format.Mp3) {
|
||||
log.debug("Format.Mp3");
|
||||
worker = new Mp3Worker((Format.Mp3) producer);
|
||||
}
|
||||
start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (worker != null) {
|
||||
worker.stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void exit() {
|
||||
if (worker != null) {
|
||||
worker.exit();
|
||||
}
|
||||
}
|
||||
|
||||
protected class DefaultWorker extends Worker {
|
||||
protected Format.Standard format;
|
||||
protected SourceDataLine line;
|
||||
|
||||
public DefaultWorker(Format.Standard format) {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
public void activate() throws ActivateException {
|
||||
AudioFormat audioFormat = format.getAudioFormat();
|
||||
try {
|
||||
if (line == null) {
|
||||
line = Tool.getSourceDataLine(name, audioFormat);
|
||||
}
|
||||
if (!line.isOpen()) {
|
||||
line.open();
|
||||
}
|
||||
} catch (LineUnavailableException e) {
|
||||
log.error(e);
|
||||
throw new ActivateException();
|
||||
}
|
||||
if (!line.isRunning()) {
|
||||
line.start();
|
||||
}
|
||||
super.activate();
|
||||
}
|
||||
|
||||
public void deactivate() throws DeactivateException {
|
||||
super.deactivate();
|
||||
line.flush();
|
||||
}
|
||||
|
||||
public void exit() {
|
||||
super.exit();
|
||||
line.close();
|
||||
}
|
||||
|
||||
protected void work() {
|
||||
try {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int read = producerInputStream.read(buffer, 0, buffer.length);
|
||||
if (read > 0) {
|
||||
line.write(buffer, 0, read);
|
||||
} else {
|
||||
exit();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected class Mp3Worker extends Worker {
|
||||
protected Format.Mp3 format;
|
||||
protected Player player;
|
||||
|
||||
public Mp3Worker(Format.Mp3 format) {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
public void activate() throws ActivateException {
|
||||
producer.start();
|
||||
super.activate();
|
||||
}
|
||||
|
||||
public void deactivate() throws DeactivateException {
|
||||
super.deactivate();
|
||||
producer.stop();
|
||||
}
|
||||
|
||||
public void exit() {
|
||||
super.exit();
|
||||
player.close();
|
||||
}
|
||||
|
||||
protected void work() {
|
||||
try {
|
||||
if (player == null) {
|
||||
player = new Player(producerInputStream);
|
||||
sleep(500);
|
||||
}
|
||||
player.play(PLAY_FRAMES);
|
||||
} catch (JavaLayerException e) {
|
||||
log.error(e);
|
||||
}
|
||||
if (player.isComplete()) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
322
java/sound/src/sound/SoxBuilder.java
Normal file
322
java/sound/src/sound/SoxBuilder.java
Normal file
@@ -0,0 +1,322 @@
|
||||
package sound;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
|
||||
import sound.SoxBuilder.Option.Combine;
|
||||
import sound.SoxBuilder.Option.Replay;
|
||||
|
||||
|
||||
public final class SoxBuilder {
|
||||
protected static SoxBuilder instance;
|
||||
protected static HashMap<String, String> optionMap;
|
||||
protected static String files;
|
||||
protected static String effects;
|
||||
|
||||
static {
|
||||
instance = new SoxBuilder();
|
||||
reset();
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
optionMap = new HashMap<String, String>();
|
||||
files = "";
|
||||
effects = "";
|
||||
}
|
||||
|
||||
public static SoxBuilder setOption(Option option, String value) {
|
||||
optionMap.put(option.getCode(), value);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static SoxBuilder setOption(Option option) {
|
||||
return SoxBuilder.setOption(option, "");
|
||||
}
|
||||
|
||||
public static SoxBuilder setOption(Option option, int value) {
|
||||
return SoxBuilder.setOption(option, String.valueOf(value));
|
||||
}
|
||||
|
||||
public static SoxBuilder setOption(Option option, Combine combine) {
|
||||
return SoxBuilder.setOption(option, combine.getCode());
|
||||
}
|
||||
|
||||
public static SoxBuilder setOption(Combine combine) {
|
||||
return SoxBuilder.setOption(Option.COMBINE, combine);
|
||||
}
|
||||
|
||||
public static SoxBuilder setOption(Option option, Replay replay) {
|
||||
return SoxBuilder.setOption(option, replay.toString().toLowerCase());
|
||||
}
|
||||
|
||||
public static SoxBuilder setOption(Replay replay) {
|
||||
return SoxBuilder.setOption(Option.REPLAY, replay);
|
||||
}
|
||||
|
||||
public static SoxBuilder addFile(File file) {
|
||||
files = String.format("%s %s", files, file.build());
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static SoxBuilder addEffect(Effect effect) {
|
||||
effects = String.format("%s %s", effects, effect.build());
|
||||
return instance;
|
||||
}
|
||||
|
||||
public String build() {
|
||||
String build = "sox";
|
||||
for (Entry<String, String> entry : optionMap.entrySet()) {
|
||||
String value = entry.getValue();
|
||||
if (value.equals("")) {
|
||||
build = String.format("%s %s", build, entry.getKey());
|
||||
} else {
|
||||
String option = String.format("%s %s", entry.getKey(), value);
|
||||
build = String.format("%s %s", build, option);
|
||||
}
|
||||
}
|
||||
build = String.format("%s%s%s", build, files, effects);
|
||||
reset();
|
||||
return build;
|
||||
}
|
||||
|
||||
public enum Environment {
|
||||
AUDIODRIVER, AUDIODEV
|
||||
}
|
||||
|
||||
public enum Option {
|
||||
BUFFER ("--buffer"), // default=8192
|
||||
INPUT_BUFFER ("--input-buffer"),
|
||||
CLOBBER ("--clobber"),
|
||||
COMBINE ("--combine"), // |Combine|
|
||||
NO_DITHER ("--no-dither"), // (-D)
|
||||
EFFECTS_FILE ("--efects-file"),
|
||||
GUARD ("--guard"), // (-G)
|
||||
MIX ("-m"), // (--combine mix)
|
||||
MERGE ("-M"), // (--combine merge)
|
||||
MAGIC ("--magic"),
|
||||
MULTI_THREADED ("--multi-threaded"),
|
||||
SINGLE_THREADED ("--single-threaded"),
|
||||
NORM ("--norm"), // [=dB-level]
|
||||
PLAY_RATE_ARG ("--play-rate-arg"),
|
||||
QUIET ("--no-show-progress"), // (-q)
|
||||
REPEATABLE ("-R"),
|
||||
REPLAY ("--replay-gain"), // |Replay|
|
||||
MULTIPLY ("-T"), // (--combine multiply)
|
||||
TEMP ("--temp");
|
||||
|
||||
protected String code;
|
||||
|
||||
private Option(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public enum Combine {
|
||||
CONCATENATE ("concatenate"),
|
||||
MERGE ("merge"), // (-M)
|
||||
MIX ("mix"), // (-m)
|
||||
MIX_POWER ("mix-power"),
|
||||
MULTIPLY ("multiply"), // (-T)
|
||||
SEQUENCE ("sequence");
|
||||
|
||||
protected String code;
|
||||
|
||||
private Combine(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Replay {
|
||||
TRACK, ALBUM, OFF
|
||||
}
|
||||
}
|
||||
|
||||
public static class File {
|
||||
protected static File instance;
|
||||
|
||||
protected static HashMap<String, String> optionMap;
|
||||
protected static Type type;
|
||||
|
||||
static {
|
||||
instance = new File();
|
||||
reset();
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
optionMap = new HashMap<String, String>();
|
||||
type = Type.PIPE;
|
||||
}
|
||||
|
||||
public static File setOption(Option option, String value) {
|
||||
optionMap.put(option.getCode(), value);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static File setOption(Option option) {
|
||||
return File.setOption(option, "");
|
||||
}
|
||||
|
||||
public static File setOption(Option option, int value) {
|
||||
return File.setOption(option, String.valueOf(value));
|
||||
}
|
||||
|
||||
public static File setOption(Option option, Encoding encoding) {
|
||||
return File.setOption(option, encoding.getCode());
|
||||
}
|
||||
|
||||
public static File setOption(Encoding encoding) {
|
||||
return File.setOption(Option.ENCODING, encoding);
|
||||
}
|
||||
|
||||
public static File setOption(Option option, Format format) {
|
||||
return File.setOption(option, format.toString().toLowerCase());
|
||||
}
|
||||
|
||||
public static File setOption( Format format) {
|
||||
return File.setOption(Option.FORMAT, format);
|
||||
}
|
||||
|
||||
public static File setOption(Option option, Endian endian) {
|
||||
return File.setOption(option, endian.toString().toLowerCase());
|
||||
}
|
||||
|
||||
public static File setOption(Endian endian) {
|
||||
return File.setOption(Option.ENDIAN, endian);
|
||||
}
|
||||
|
||||
public File setOptions(AudioFormat audioFormat) {
|
||||
setOption(Option.CHANNELS, audioFormat.getChannels());
|
||||
setOption(Option.RATE, String.format("%sk", String.valueOf(audioFormat.getSampleRate() / 1000f)));
|
||||
AudioFormat.Encoding encoding = audioFormat.getEncoding();
|
||||
int bits = audioFormat.getSampleSizeInBits();
|
||||
if (encoding.equals(AudioFormat.Encoding.ALAW)) {
|
||||
setOption(Format.AL);
|
||||
setOption(Encoding.A_LAW);
|
||||
} else if (encoding.equals(AudioFormat.Encoding.ULAW)) {
|
||||
setOption(Format.UL);
|
||||
setOption(Encoding.U_LAW);
|
||||
} else if (encoding.equals(AudioFormat.Encoding.PCM_SIGNED)) {
|
||||
setOption(Format.valueOf(String.format("S%d", bits)));
|
||||
setOption(Encoding.SIGNED_INTEGER);
|
||||
} else if (encoding.equals(AudioFormat.Encoding.PCM_UNSIGNED)) {
|
||||
setOption(Format.valueOf(String.format("U%d", bits)));
|
||||
setOption(Encoding.UNSIGNED_INTEGER);
|
||||
}
|
||||
setOption(audioFormat.isBigEndian() ? Endian.BIG : Endian.LITTLE);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static File setType(Type type) {
|
||||
File.type = type;
|
||||
return instance;
|
||||
}
|
||||
|
||||
public String build() {
|
||||
String build = type.getCode();
|
||||
for (Entry<String, String> entry : optionMap.entrySet()) {
|
||||
String value = entry.getValue();
|
||||
if (value.equals("")) {
|
||||
build = String.format("%s %s", entry.getKey(), build);
|
||||
} else {
|
||||
String option = String.format("%s %s", entry.getKey(), value);
|
||||
build = String.format("%s %s", option, build);
|
||||
}
|
||||
}
|
||||
reset();
|
||||
return build;
|
||||
}
|
||||
|
||||
public enum Option {
|
||||
BITS ("--bits"), // (-b)
|
||||
CHANNELS ("--channels"), // (-c)
|
||||
ENCODING ("--encoding"), // (-e), |Encoding|
|
||||
NO_GLOB ("--no-glob"),
|
||||
RATE ("--rate"), // (-r)
|
||||
FORMAT ("--type"), // (-t), |Format|
|
||||
ENDIAN ("--endian"), // (-L, -B, -x), |Endian|
|
||||
REVERSE_NIBBLES ("--reverse-nibbles"), // (-N)
|
||||
REVERSE_BITS ("--reverse-bits"), // (-X)
|
||||
/* Input only */
|
||||
IGNORE_LENGTH ("--ignore-length"),
|
||||
VOLUME ("--volume"), // (-v)
|
||||
/* Output only */
|
||||
ADD_COMMENT ("--add-comment"),
|
||||
COMMENT ("--comment"),
|
||||
COMMENT_FILE ("--comment-file"),
|
||||
COMPRESSION ("--compression"); // -C
|
||||
|
||||
protected String code;
|
||||
|
||||
private Option(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Encoding {
|
||||
SIGNED_INTEGER ("signed-integer"), // PCM data stored as signed integers
|
||||
UNSIGNED_INTEGER ("unsigned-integer"), // PCM data stored as unsigned integers
|
||||
FLOATING_POINT ("floating-point"), // PCM data stored as single precision (32-bit) or double precision (64-bit) floating-point numbers
|
||||
A_LAW ("a-lawW"), // International telephony standard for logarithmic encoding to 8 bits per sample (~13-bit PCM)
|
||||
U_LAW ("u-law"), // North American telephony standard for logarithmic encoding to 8 bits per sample (~14-bit PCM)
|
||||
MU_LAW ("mu-law"), // alias for u-law (~14-bit PCM)
|
||||
OKI_ADPCM ("oki-adpcm"), // OKI (VOX, Dialogic or Intel) 4-bit ADPCM (~12-bit PCM)
|
||||
IMA_ADPCM ("ima-adpcm"), // IMA (DVI) 4-bit ADPCM (~13-bit PCM)
|
||||
MS_ADPCM ("ms-adpcm"), // Microsoft 4-bit ADPCM (~14-bit PCM)
|
||||
GSM_FULL_RATE ("gsm-full-rate"); // Several audio formats used for digital wireless telephone calls
|
||||
|
||||
protected String code;
|
||||
|
||||
private Encoding(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Format {
|
||||
AIF, AIFC, AIFF, AIFFC, AL, AMB, AMR, ANY, ARL, AU, AVR, BIN, CAF, CDDA, CDR, CVS, CVSD, CVU, DAT, DVMS, EDU, F32, F64, FAP, FLAC, FSSD, GSM, GSRT, HCOM, HTK, IMA, IRCAM, LA, LPC, LPC10, LU, M3U, M4A, MAT, MAT4, MAT5, MAUD, MP2, MP3, MP4, NIST, OGG, PAF, PLS, PRC, PVF, RAW, S16, S24, S32, S8, SD2, SDS, SF, SLN, SMP, SND, SNDR, SNDT, SOU, SOX, SPH, TXW, U16, U24, U32, U8, UL, VMS, VOC, VORBIS, VOX, W64, WAV, WAVPCM, WV, WVE, XA, XI;
|
||||
}
|
||||
|
||||
public enum Endian {
|
||||
LITTLE, BIG, SWAP;
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
STANDARD ("-"), // -t must be given
|
||||
PIPE ("-p"), // (--sox-pipe)
|
||||
DEVICE ("-d"), // (--default-device)
|
||||
NULL ("-n"); // (--null)
|
||||
|
||||
protected String code;
|
||||
|
||||
private Type(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Effect {
|
||||
public String build() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
195
java/sound/src/sound/Stream.java
Normal file
195
java/sound/src/sound/Stream.java
Normal file
@@ -0,0 +1,195 @@
|
||||
package sound;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
|
||||
import mimis.exception.worker.ActivateException;
|
||||
import mimis.exception.worker.DeactivateException;
|
||||
import mimis.worker.Worker;
|
||||
|
||||
import com.Ostermiller.util.CircularByteBuffer;
|
||||
import com.Ostermiller.util.CircularObjectBuffer;
|
||||
|
||||
public class Stream extends Worker implements Producer, Format.Mp3 {
|
||||
public static final String HTTP = "http://shoutcast.omroep.nl:8104/";
|
||||
public static final int STEP = 80; // in milliseconds
|
||||
|
||||
protected String http;
|
||||
protected Socket socket;
|
||||
protected InputStream socketInputStream;
|
||||
protected OutputStreamWriter socketOutputStreamWriter;
|
||||
protected GreedyInputStream greedyInputStream;
|
||||
protected int meta;
|
||||
protected int rate;
|
||||
protected int chunk;
|
||||
protected int untilMeta;
|
||||
protected CircularByteBuffer audioCircularByteBuffer;
|
||||
protected CircularObjectBuffer<String> metaCircularObjectBuffer;
|
||||
protected String metaData;
|
||||
|
||||
public Stream() {
|
||||
this(HTTP);
|
||||
}
|
||||
|
||||
public Stream(String http) {
|
||||
super(true);
|
||||
this.http = http;
|
||||
meta = -1;
|
||||
rate = -1;
|
||||
audioCircularByteBuffer = new CircularByteBuffer(CircularByteBuffer.INFINITE_SIZE);
|
||||
metaCircularObjectBuffer = new CircularObjectBuffer<String>();
|
||||
}
|
||||
|
||||
protected void connect(URL url) {
|
||||
try {
|
||||
/* Open socket communication */
|
||||
socket = new Socket(url.getHost(), url.getPort());
|
||||
socketInputStream = socket.getInputStream();
|
||||
socketOutputStreamWriter = new OutputStreamWriter(socket.getOutputStream());
|
||||
|
||||
/* Write stream request */
|
||||
socketOutputStreamWriter.write("GET " + url.getFile() + " HTTP/1.1\r\n");
|
||||
socketOutputStreamWriter.write("Host: " + url.getHost() + "\r\n");
|
||||
socketOutputStreamWriter.write("Icy-MetaData: 1\r\n");
|
||||
socketOutputStreamWriter.write("Connection: close\r\n");
|
||||
socketOutputStreamWriter.write("\r\n");
|
||||
socketOutputStreamWriter.flush();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
protected void activate() throws ActivateException {
|
||||
try {
|
||||
/* Initialize connection */
|
||||
URL url = new URL(http);
|
||||
|
||||
/* Parse headers */
|
||||
connect(url);
|
||||
InputStreamReader inputStreamReader = new InputStreamReader(socketInputStream);
|
||||
StringBuffer stringBuffer = new StringBuffer();
|
||||
char character;
|
||||
int skip = 0;
|
||||
while ((character = (char) inputStreamReader.read()) > 0) {
|
||||
++skip;
|
||||
if (character == '\n') {
|
||||
/* Fetch relevant headers */
|
||||
String line = stringBuffer.toString().trim();
|
||||
if (line.startsWith("icy-metaint")) {
|
||||
meta = Integer.valueOf(line.substring(line.indexOf(":") + 1).trim());
|
||||
} else if (line.startsWith("icy-br")) {
|
||||
rate = Integer.valueOf(line.substring(line.indexOf(":") + 1).trim());
|
||||
} else if (line.equals("")) {
|
||||
break;
|
||||
}
|
||||
stringBuffer = new StringBuffer();
|
||||
} else {
|
||||
stringBuffer.append(character);
|
||||
}
|
||||
}
|
||||
inputStreamReader.close();
|
||||
|
||||
/* Reconnect to bypass pre-buffering problems */
|
||||
connect(url);
|
||||
socketInputStream = socket.getInputStream();
|
||||
socketInputStream.skip(skip);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
/* Calculate streaming parameters */
|
||||
untilMeta = meta;
|
||||
chunk = STEP * rate / 8;
|
||||
super.activate();
|
||||
}
|
||||
|
||||
public void deactivate() throws DeactivateException {
|
||||
super.deactivate();
|
||||
audioCircularByteBuffer.clear();
|
||||
metaCircularObjectBuffer.clear();
|
||||
try {
|
||||
greedyInputStream.clear();
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
throw new DeactivateException();
|
||||
}
|
||||
}
|
||||
|
||||
protected void work() {
|
||||
int left = chunk;
|
||||
|
||||
/* Handle media at appropriate times */
|
||||
while (meta > 0 && left >= untilMeta) {
|
||||
stream(untilMeta);
|
||||
meta();
|
||||
left -= untilMeta;
|
||||
untilMeta = meta;
|
||||
}
|
||||
|
||||
/* Stream at fixed rate */
|
||||
stream(left);
|
||||
untilMeta -= left;
|
||||
sleep(STEP);
|
||||
|
||||
}
|
||||
|
||||
protected void stream(int length) {
|
||||
try {
|
||||
byte[] bytes = new byte[length];
|
||||
int read = 0;
|
||||
while (length > 0 && (read = socketInputStream.read(bytes)) > 0) {
|
||||
length -= read;
|
||||
audioCircularByteBuffer.getOutputStream().write(bytes);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
protected void meta() {
|
||||
try {
|
||||
/* Retrieve data length */
|
||||
byte[] data = new byte[1];
|
||||
socketInputStream.read(data);
|
||||
int length = 16 * data[0];
|
||||
data = new byte[length];
|
||||
socketInputStream.read(data);
|
||||
|
||||
/* Check for new data */
|
||||
String newMetaData = new String(data);
|
||||
if (!newMetaData.isEmpty() && !newMetaData.equals(metaData)) {
|
||||
metaData = newMetaData;
|
||||
metaCircularObjectBuffer.write(new String(data));
|
||||
log.debug("data: " + metaData);
|
||||
}
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
} catch (IllegalStateException e) {
|
||||
log.error(e);
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e);
|
||||
}
|
||||
stop();
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
greedyInputStream = new GreedyInputStream(audioCircularByteBuffer.getInputStream());
|
||||
return greedyInputStream;
|
||||
}
|
||||
|
||||
public CircularObjectBuffer<String> getMetaBufferStream() {
|
||||
return metaCircularObjectBuffer;
|
||||
}
|
||||
|
||||
public int getRate() {
|
||||
return rate;
|
||||
}
|
||||
}
|
||||
91
java/sound/src/sound/Target.java
Normal file
91
java/sound/src/sound/Target.java
Normal file
@@ -0,0 +1,91 @@
|
||||
package sound;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.TargetDataLine;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
public class Target implements Producer, Format.Standard {
|
||||
protected Log log = LogFactory.getLog(getClass());
|
||||
|
||||
protected Standard format;
|
||||
|
||||
protected TargetDataLine line;
|
||||
protected InputStream targetInputStream;
|
||||
|
||||
protected AudioFormat audioFormat;
|
||||
|
||||
public Target(String name) throws LineUnavailableException {
|
||||
log.debug(String.format("Target \"%s\" without format", name));
|
||||
line = Tool.getTargetDataLine(name);
|
||||
audioFormat = line.getFormat();
|
||||
targetInputStream = new TargetInputStream();
|
||||
}
|
||||
|
||||
public Target(String name, AudioFormat audioFormat) throws LineUnavailableException {
|
||||
log.debug(String.format("Target \"%s\" with format: %s", name, audioFormat));
|
||||
this.audioFormat = audioFormat;
|
||||
line = Tool.getTargetDataLine(name, audioFormat);
|
||||
targetInputStream = new TargetInputStream();
|
||||
}
|
||||
|
||||
public AudioFormat getAudioFormat() {
|
||||
return audioFormat;
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
return targetInputStream;
|
||||
}
|
||||
|
||||
public class TargetInputStream extends InputStream {
|
||||
protected boolean open;
|
||||
|
||||
public TargetInputStream() {
|
||||
open = false;
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
start();
|
||||
byte[] buffer = new byte[1];
|
||||
line.read(buffer, 0, 1);
|
||||
return (int) buffer[0];
|
||||
}
|
||||
|
||||
public int read(byte[] buffer, int offset, int length) {
|
||||
start();
|
||||
line.read(buffer, offset, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
public int available() {
|
||||
start();
|
||||
return line.available();
|
||||
}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (!line.isOpen()) {
|
||||
try {
|
||||
line.open();
|
||||
} catch (LineUnavailableException e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
if (!line.isRunning()) {
|
||||
line.start();
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
line.flush();
|
||||
}
|
||||
|
||||
public void exit() {
|
||||
line.close();
|
||||
}
|
||||
}
|
||||
27
java/sound/src/sound/Test.java
Normal file
27
java/sound/src/sound/Test.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package sound;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
|
||||
public class Test {
|
||||
public static void main(String[] args) {
|
||||
AudioFormat audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 48000f, 16, 2, 4, 48000f, true);
|
||||
try {
|
||||
Producer p1 = new Target("Line-In (Creative SB X-Fi)");
|
||||
Producer p2 = new Target("Line 1 (Virtual Audio Cable)", audioFormat);
|
||||
Producer p3 = new Stream("http://ics2gss.omroep.nl:80/3fm-bb-mp3");
|
||||
Consumer c1 = new Source("Java Sound Audio Engine");
|
||||
Consumer c2 = new Port("Speakers (Creative SB X-Fi)");
|
||||
|
||||
c2.start(p3);
|
||||
|
||||
/*while (true) {
|
||||
Thread.sleep(3000);
|
||||
c2.stop();
|
||||
Thread.sleep(1000);
|
||||
c2.start();
|
||||
}*/
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
140
java/sound/src/sound/Tool.java
Normal file
140
java/sound/src/sound/Tool.java
Normal file
@@ -0,0 +1,140 @@
|
||||
package sound;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.DataLine;
|
||||
import javax.sound.sampled.Line;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.Mixer;
|
||||
import javax.sound.sampled.Port;
|
||||
import javax.sound.sampled.Port.Info;
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
import javax.sound.sampled.TargetDataLine;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
public class Tool {
|
||||
protected static Log log = LogFactory.getLog(Tool.class);
|
||||
|
||||
protected static HashMap<String, Device<TargetDataLine>> targetMap;
|
||||
protected static HashMap<String, Device<SourceDataLine>> sourceMap;
|
||||
protected static ArrayList<String> portList;
|
||||
|
||||
protected static ArrayList<String> targetList;
|
||||
protected static ArrayList<String> sourceList;
|
||||
|
||||
static {
|
||||
Tool tool = new Tool();
|
||||
|
||||
targetMap = new HashMap<String, Device<TargetDataLine>>();
|
||||
sourceMap = new HashMap<String, Device<SourceDataLine>>();
|
||||
targetList = new ArrayList<String>();
|
||||
sourceList = new ArrayList<String>();
|
||||
portList = new ArrayList<String>();
|
||||
|
||||
for (Mixer.Info mixerInfo : AudioSystem.getMixerInfo()) {
|
||||
String name = mixerInfo.getName();
|
||||
Mixer mixer = AudioSystem.getMixer(mixerInfo);
|
||||
|
||||
for (Line.Info lineInfo : mixer.getSourceLineInfo()) {
|
||||
String lineClassName = lineInfo.getLineClass().getName();
|
||||
if (lineClassName.equals("javax.sound.sampled.SourceDataLine")) {
|
||||
if (mixer.isLineSupported(lineInfo)) {
|
||||
log.debug("<Source> " + name);
|
||||
sourceMap.put(name, tool.new Device<SourceDataLine>(mixer, lineInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Line.Info lineInfo : mixer.getTargetLineInfo()) {
|
||||
String lineClassName = lineInfo.getLineClass().getName();
|
||||
if (lineClassName.equals("javax.sound.sampled.TargetDataLine")) {
|
||||
if (mixer.isLineSupported(lineInfo)) {
|
||||
log.debug("<Target> " + name);
|
||||
targetMap.put(name, tool.new Device<TargetDataLine>(mixer, lineInfo));
|
||||
}
|
||||
} else if (lineClassName.equals("javax.sound.sampled.Port")) {
|
||||
name = name.substring(5);
|
||||
try {
|
||||
Port port = (Port) mixer.getLine(lineInfo);
|
||||
Port.Info portInfo = (Info) port.getLineInfo();
|
||||
if (!targetMap.containsKey(name) || portInfo.equals(Port.Info.LINE_OUT) || portInfo.equals(Port.Info.SPEAKER)) {
|
||||
log.debug("<Port> " + name);
|
||||
portList.add(name);
|
||||
}
|
||||
} catch (LineUnavailableException e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String[] getTargets() {
|
||||
return targetMap.keySet().toArray(new String[0]);
|
||||
}
|
||||
|
||||
public static String[] getSources() {
|
||||
return sourceMap.keySet().toArray(new String[0]);
|
||||
}
|
||||
|
||||
public static String[] getPorts() {
|
||||
return portList.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public static TargetDataLine getTargetDataLine(String name) throws LineUnavailableException {
|
||||
if (targetMap.containsKey(name)) {
|
||||
return targetMap.get(name).getLine();
|
||||
} else {
|
||||
throw new LineUnavailableException();
|
||||
}
|
||||
}
|
||||
|
||||
public static TargetDataLine getTargetDataLine(String name, AudioFormat audioFormat) throws LineUnavailableException {
|
||||
if (targetMap.containsKey(name)) {
|
||||
return targetMap.get(name).getLine(audioFormat);
|
||||
} else {
|
||||
throw new LineUnavailableException();
|
||||
}
|
||||
}
|
||||
|
||||
public static SourceDataLine getSourceDataLine(String name) throws LineUnavailableException {
|
||||
if (sourceMap.containsKey(name)) {
|
||||
return sourceMap.get(name).getLine();
|
||||
} else {
|
||||
throw new LineUnavailableException();
|
||||
}
|
||||
}
|
||||
|
||||
public static SourceDataLine getSourceDataLine(String name, AudioFormat audioFormat) throws LineUnavailableException {
|
||||
if (sourceMap.containsKey(name)) {
|
||||
return sourceMap.get(name).getLine(audioFormat);
|
||||
} else {
|
||||
throw new LineUnavailableException();
|
||||
}
|
||||
}
|
||||
|
||||
public class Device<T> {
|
||||
protected Mixer mixer;
|
||||
protected Line.Info lineInfo;
|
||||
|
||||
public Device(Mixer mixer, Line.Info lineInfo) {
|
||||
this.mixer = mixer;
|
||||
this.lineInfo = lineInfo;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T getLine() throws LineUnavailableException {
|
||||
return (T) mixer.getLine(lineInfo);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T getLine(AudioFormat audioFormat) throws LineUnavailableException {
|
||||
DataLine.Info dataLineInfo = new DataLine.Info(lineInfo.getLineClass(), audioFormat);
|
||||
return (T) mixer.getLine(dataLineInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
java/sound/src/sound/Transducer.java
Normal file
43
java/sound/src/sound/Transducer.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package sound;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public class Transducer implements Consumer, Producer {
|
||||
public int rate;
|
||||
|
||||
public Transducer(Producer producer) {
|
||||
//setProducer(producer);
|
||||
}
|
||||
|
||||
public int getRate() {
|
||||
return rate;
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
public void start(Producer producer) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
}
|
||||
55
java/sound/src/test/SoundAudit.java
Normal file
55
java/sound/src/test/SoundAudit.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package test;
|
||||
|
||||
import javax.sound.sampled.*;
|
||||
public class SoundAudit {
|
||||
public static void main(String[] args) { try {
|
||||
System.out.println("OS: "+System.getProperty("os.name")+" "+
|
||||
System.getProperty("os.version")+"/"+
|
||||
System.getProperty("os.arch")+"\nJava: "+
|
||||
System.getProperty("java.version")+" ("+
|
||||
System.getProperty("java.vendor")+")\n");
|
||||
for (Mixer.Info thisMixerInfo : AudioSystem.getMixerInfo()) {
|
||||
System.out.println("Mixer: "+thisMixerInfo.getDescription()+
|
||||
" ["+thisMixerInfo.getName()+"]");
|
||||
Mixer thisMixer = AudioSystem.getMixer(thisMixerInfo);
|
||||
for (Line.Info thisLineInfo:thisMixer.getSourceLineInfo()) {
|
||||
if (thisLineInfo.getLineClass().getName().equals(
|
||||
"javax.sound.sampled.Port")) {
|
||||
Line thisLine = thisMixer.getLine(thisLineInfo);
|
||||
thisLine.open();
|
||||
System.out.println(" Source Port: "
|
||||
+thisLineInfo.toString());
|
||||
for (Control thisControl : thisLine.getControls()) {
|
||||
System.out.println(AnalyzeControl(thisControl));}
|
||||
thisLine.close();}}
|
||||
for (Line.Info thisLineInfo:thisMixer.getTargetLineInfo()) {
|
||||
if (thisLineInfo.getLineClass().getName().equals(
|
||||
"javax.sound.sampled.Port")) {
|
||||
Line thisLine = thisMixer.getLine(thisLineInfo);
|
||||
thisLine.open();
|
||||
System.out.println(" Target Port: "
|
||||
+thisLineInfo.toString());
|
||||
for (Control thisControl : thisLine.getControls()) {
|
||||
System.out.println(AnalyzeControl(thisControl));}
|
||||
thisLine.close();}}}
|
||||
} catch (Exception e) {e.printStackTrace();}}
|
||||
public static String AnalyzeControl(Control thisControl) {
|
||||
String type = thisControl.getType().toString();
|
||||
if (thisControl instanceof BooleanControl) {
|
||||
return " Control: "+type+" (boolean)"; }
|
||||
if (thisControl instanceof CompoundControl) {
|
||||
System.out.println(" Control: "+type+
|
||||
" (compound - values below)");
|
||||
String toReturn = "";
|
||||
for (Control children:
|
||||
((CompoundControl)thisControl).getMemberControls()) {
|
||||
toReturn+=" "+AnalyzeControl(children)+"\n";}
|
||||
return toReturn.substring(0, toReturn.length()-1);}
|
||||
if (thisControl instanceof EnumControl) {
|
||||
return " Control:"+type+" (enum: "+thisControl.toString()+")";}
|
||||
if (thisControl instanceof FloatControl) {
|
||||
return " Control: "+type+" (float: from "+
|
||||
((FloatControl) thisControl).getMinimum()+" to "+
|
||||
((FloatControl) thisControl).getMaximum()+")";}
|
||||
return " Control: unknown type";}
|
||||
}
|
||||
29
java/sound/src/test/lines/Main.java
Normal file
29
java/sound/src/test/lines/Main.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package test.lines;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.Line;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.Mixer;
|
||||
import javax.sound.sampled.TargetDataLine;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
System.out.println(System.getProperty("javax.sound.sampled.SourceDataLine"));
|
||||
|
||||
new AudioFormat(44100, 16, 2, true, false);
|
||||
|
||||
for (Mixer.Info mixerInfo : AudioSystem.getMixerInfo()) {
|
||||
Mixer mixer = AudioSystem.getMixer(mixerInfo);
|
||||
for (Line.Info lineInfo : mixer.getTargetLineInfo()) {
|
||||
try {
|
||||
Line line = mixer.getLine(lineInfo);
|
||||
if (mixer.isLineSupported(lineInfo) && line instanceof TargetDataLine) {
|
||||
new TargetLine(mixer, (TargetDataLine) line);
|
||||
}
|
||||
} catch (LineUnavailableException e) {}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
19
java/sound/src/test/lines/SourceLine.java
Normal file
19
java/sound/src/test/lines/SourceLine.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package test.lines;
|
||||
|
||||
import javax.sound.sampled.Mixer;
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
|
||||
public class SourceLine {
|
||||
//private Mixer mixer;
|
||||
private SourceDataLine line;
|
||||
|
||||
public SourceLine(Mixer mixer, SourceDataLine line) {
|
||||
//this.mixer = mixer;
|
||||
this.line = line;
|
||||
System.out.println("SOURCE " + mixer.getMixerInfo().getName() + " || " + line.getLineInfo());
|
||||
}
|
||||
|
||||
public int write(byte[] bytes, int offset, int length) {
|
||||
return line.write(bytes, offset, length);
|
||||
}
|
||||
}
|
||||
19
java/sound/src/test/lines/TargetLine.java
Normal file
19
java/sound/src/test/lines/TargetLine.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package test.lines;
|
||||
|
||||
import javax.sound.sampled.Mixer;
|
||||
import javax.sound.sampled.TargetDataLine;
|
||||
|
||||
public class TargetLine {
|
||||
//private Mixer mixer;
|
||||
private TargetDataLine line;
|
||||
|
||||
public TargetLine(Mixer mixer, TargetDataLine line) {
|
||||
//this.mixer = mixer;
|
||||
this.line = line;
|
||||
System.out.println("TARGET " + mixer.getMixerInfo().getName() + " || " + line.getLineInfo());
|
||||
}
|
||||
|
||||
public int read(byte[] bytes, int offset, int length) {
|
||||
return line.read(bytes, offset, length);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user