initial commit

This commit is contained in:
2013-03-22 19:19:12 +01:00
commit 817fcc796d
44 changed files with 1931 additions and 0 deletions

8
src/sound/Consumer.java Normal file
View 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
src/sound/Format.java Normal file
View 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();
}
}

View 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
src/sound/Port.java Normal file
View 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
src/sound/Producer.java Normal file
View 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
src/sound/Source.java Normal file
View 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
src/sound/SoxBuilder.java Normal file
View 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
src/sound/Stream.java Normal file
View 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
src/sound/Target.java Normal file
View 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
src/sound/Test.java Normal file
View 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
src/sound/Tool.java Normal file
View 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
src/sound/Transducer.java Normal file
View 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
}
}