import java.util.*; import java.util.zip.*; import java.util.List; import java.util.regex.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; import java.util.concurrent.locks.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.text.*; import javax.swing.table.*; import java.io.*; import java.net.*; import java.lang.reflect.*; import java.lang.ref.*; import java.lang.management.*; import java.security.*; import java.security.spec.*; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import javax.imageio.*; import java.math.*; import javax.sound.sampled.DataLine; import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.TargetDataLine; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.Mixer; import javax.sound.sampled.AudioFileFormat; import gnu.getopt.Getopt; // getopt class main { // AudioLoop from jsresources.org static boolean DEBUG; static final int DEFAULT_INTERNAL_BUFSIZ = 40960; static final int DEFAULT_EXTERNAL_BUFSIZ = 40960; public static void main(final String[] args) throws Exception { String strMixerName = null; float fFrameRate = 44100.0F; int nInternalBufferSize = DEFAULT_INTERNAL_BUFSIZ; int nExternalBufferSize = DEFAULT_EXTERNAL_BUFSIZ; Getopt g = new Getopt("AudioLoop", args, "hlr:i:e:M:D"); int c; while ((c = g.getopt()) != -1) { switch (c) { case 'h': printUsage(); return; case 'l': javaSound_listMixers(); return; case 'r': fFrameRate = Float.parseFloat(g.getOptarg()); if (DEBUG) { out("AudioLoop.main(): frame rate: " + fFrameRate); } break; case 'i': nInternalBufferSize = Integer.parseInt(g.getOptarg()); if (DEBUG) { out("AudioLoop.main(): internal buffer size: " + nInternalBufferSize); } break; case 'e': nExternalBufferSize = Integer.parseInt(g.getOptarg()); if (DEBUG) { out("AudioLoop.main(): external buffer size: " + nExternalBufferSize); } break; case 'M': strMixerName = g.getOptarg(); if (DEBUG) { out("AudioLoop.main(): mixer name: " + strMixerName); } break; case 'D': DEBUG = true; break; case '?': printUsage(); return; default: out("AudioLoop.main(): getopt() returned: " + c); break; } } AudioFormat audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, fFrameRate, 16, 2, 4, fFrameRate, false); if (DEBUG) { out("AudioLoop.main(): audio format: " + audioFormat); } AudioLoop audioLoop = null; try { audioLoop = new AudioLoop(audioFormat, nInternalBufferSize, nExternalBufferSize, strMixerName); } catch (LineUnavailableException e) { e.printStackTrace(); System.exit(1); } audioLoop.start(); } // TODO: params for audio quality, optionally use compression and decompression in the loop (see ~/AudioLoop.java) /** AudioLoop Recording and playing back the recorded data immediately Purpose This program opens two lines: one for recording and one for playback. In an infinite loop, it reads data from the recording line and writes them to the playback line. You can use this to measure the delays inside Java Sound: Speak into the microphone and wait untill you hear yourself in the speakers. This can be used to experience the effect of changing the buffer sizes: use the and options. You will notice that the delays change, too. Usage java AudioLoop java AudioLoop Parameters lists the available mixers selects a mixer to play on the buffer size to use in the application ("extern") the buffer size to use in Java Sound ("intern") Bugs, limitations There is no way to stop the program besides brute force (ctrl-C). There is no way to set the audio quality. The example requires that the soundcard and its driver as well as the Java Sound implementation support full-duplex operation. In Linux either use Tritonus or enable full-duplex in Sun's Java Sound implementation (search the archive of java-linux). Source code AudioLoop.java, AudioCommon.java, gnu.getopt.Getopt */ static class AudioLoop extends Thread { private TargetDataLine m_targetLine; private SourceDataLine m_sourceLine; private boolean m_bRecording; private int m_nExternalBufferSize; /* * We have to pass an AudioFormat to describe in which * format the audio data should be recorded and played. */ public AudioLoop(AudioFormat format, int nInternalBufferSize, int nExternalBufferSize, String strMixerName) throws LineUnavailableException { Mixer mixer = null; if (strMixerName != null) { Mixer.Info mixerInfo = javaSound_getMixerInfo(strMixerName); if (DEBUG) { out("AudioLoop.(): mixer info: " + mixerInfo); } mixer = AudioSystem.getMixer(mixerInfo); if (DEBUG) { out("AudioLoop.(): mixer: " + mixer); } } /* * We retrieve and open the recording and the playback line. */ DataLine.Info targetInfo = new DataLine.Info(TargetDataLine.class, format, nInternalBufferSize); DataLine.Info sourceInfo = new DataLine.Info(SourceDataLine.class, format, nInternalBufferSize); if (mixer != null) { m_targetLine = (TargetDataLine) mixer.getLine(targetInfo); m_sourceLine = (SourceDataLine) mixer.getLine(sourceInfo); } else { m_targetLine = (TargetDataLine) AudioSystem.getLine(targetInfo); m_sourceLine = (SourceDataLine) AudioSystem.getLine(sourceInfo); } if (DEBUG) { out("AudioLoop.(): SourceDataLine: " + m_sourceLine); } if (DEBUG) { out("AudioLoop.(): TargetDataLine: " + m_targetLine); } m_targetLine.open(format, nInternalBufferSize); m_sourceLine.open(format, nInternalBufferSize); m_nExternalBufferSize = nExternalBufferSize; } public void start() { m_targetLine.start(); m_sourceLine.start(); super.start(); } public void run() { byte[] abBuffer = new byte[m_nExternalBufferSize]; int nBufferSize = abBuffer.length; m_bRecording = true; while (m_bRecording) { if (DEBUG) { out("Trying to read: " + nBufferSize); } /* * read a block of data from the recording line. */ int nBytesRead = m_targetLine.read(abBuffer, 0, nBufferSize); if (DEBUG) { out("Read: " + nBytesRead); } /* * And now, we write the block to the playback * line. */ m_sourceLine.write(abBuffer, 0, nBytesRead); } } } static void out(String s) { print(s); } static void printUsage() { out("AudioLoop: usage:"); out("\tjava AudioLoop -h"); out("\tjava AudioLoop -l"); out("\tjava AudioLoop [-D] [-M ] [-e ] [-i ]"); } static volatile StringBuffer local_log = new StringBuffer(); // not redirected static volatile StringBuffer print_log = local_log; // might be redirected, e.g. to main bot // in bytes - will cut to half that static volatile int print_log_max = 1024*1024; static volatile int local_log_max = 100*1024; //static int print_maxLineLength = 0; // 0 = unset static boolean print_silent; // total mute if set static Object print_byThread_lock = new Object(); static volatile ThreadLocal> print_byThread; // special handling by thread static void print() { print(""); } // slightly overblown signature to return original object... static A print(A o) { ping(); if (print_silent) return o; String s = String.valueOf(o) + "\n"; print_noNewLine(s); return o; } static void print_noNewLine(String s) { if (print_byThread != null) { F1 f = print_byThread.get(); if (f != null) if (isFalse(f.get(s))) return; } print_raw(s); } static void print_raw(String s) { s = fixNewLines(s); // TODO if (print_maxLineLength != 0) StringBuffer loc = local_log; StringBuffer buf = print_log; int loc_max = print_log_max; if (buf != loc && buf != null) { print_append(buf, s, print_log_max); loc_max = local_log_max; } if (loc != null) print_append(loc, s, loc_max); System.out.print(s); } static void print(long l) { print(String.valueOf(l)); } static void print(char c) { print(String.valueOf(c)); } static void print_append(StringBuffer buf, String s, int max) { synchronized(buf) { buf.append(s); max /= 2; if (buf.length() > max) try { int newLength = max/2; int ofs = buf.length()-newLength; String newString = buf.substring(ofs); buf.setLength(0); buf.append("[...] ").append(newString); } catch (Exception e) { buf.setLength(0); } } } static Map _registerThread_threads = Collections.synchronizedMap(new WeakHashMap()); static Thread _registerThread(Thread t) { _registerThread_threads.put(t, true); return t; } static void _registerThread() { _registerThread(Thread.currentThread()); } static Mixer.Info javaSound_getMixerInfo(String strMixerName) { Mixer.Info[] aInfos = AudioSystem.getMixerInfo(); for (int i = 0; i < aInfos.length; i++) if (aInfos[i].getName().equals(strMixerName)) return aInfos[i]; return null; } static void javaSound_listMixers() { print("Available Mixers:"); Mixer.Info[] aInfos = AudioSystem.getMixerInfo(); for (int i = 0; i < aInfos.length; i++) print(aInfos[i].getName()); if (aInfos.length == 0) print("[No mixers available]"); } static String fixNewLines(String s) { return s.replace("\r\n", "\n").replace("\r", "\n"); } static volatile boolean ping_pauseAll; static int ping_sleep = 100; // poll pauseAll flag every 100 static volatile boolean ping_anyActions; static Map ping_actions = synchroMap(new WeakHashMap()); // always returns true static boolean ping() { if (ping_pauseAll || ping_anyActions) ping_impl(); return true; } // returns true when it slept static boolean ping_impl() { try { if (ping_pauseAll && !isAWTThread()) { do Thread.sleep(ping_sleep); while (ping_pauseAll); return true; } if (ping_anyActions) { Object action; synchronized(mc()) { action = ping_actions.get(currentThread()); if (action instanceof Runnable) ping_actions.remove(currentThread()); if (ping_actions.isEmpty()) ping_anyActions = false; } if (action instanceof Runnable) ((Runnable) action).run(); else if (eq(action, "cancelled")) throw fail("Thread cancelled."); } return false; } catch (Exception __e) { throw rethrow(__e); } } static boolean isFalse(Object o) { return eq(false, o); } static Map synchroMap() { return synchroHashMap(); } static Map synchroMap(Map map) { return Collections.synchronizedMap(map); } static Thread currentThread() { return Thread.currentThread(); } static boolean eq(Object a, Object b) { return a == null ? b == null : a == b || a.equals(b); } static RuntimeException fail() { throw new RuntimeException("fail"); } static RuntimeException fail(Throwable e) { throw asRuntimeException(e); } static RuntimeException fail(Object msg) { throw new RuntimeException(String.valueOf(msg)); } static RuntimeException fail(String msg) { throw new RuntimeException(unnull(msg)); } static RuntimeException fail(String msg, Throwable innerException) { throw new RuntimeException(msg, innerException); } static Object mc() { return getMainClass(); } // TODO: test if android complains about this static boolean isAWTThread() { if (isAndroid()) return false; if (isHeadless()) return false; return isAWTThread_awt(); } static boolean isAWTThread_awt() { return SwingUtilities.isEventDispatchThread(); } static Class getMainClass() { return main.class; } static Class getMainClass(Object o) { try { return (o instanceof Class ? (Class) o : o.getClass()).getClassLoader().loadClass("main"); } catch (Exception __e) { throw rethrow(__e); } } static Boolean isHeadless_cache; static boolean isHeadless() { if (isHeadless_cache != null) return isHeadless_cache; if (GraphicsEnvironment.isHeadless()) return isHeadless_cache = true; // Also check if AWT actually works. // If DISPLAY variable is set but no X server up, this will notice. try { SwingUtilities.isEventDispatchThread(); return isHeadless_cache = false; } catch (Throwable e) { return isHeadless_cache = true; } } static String unnull(String s) { return s == null ? "" : s; } static List unnull(List l) { return l == null ? emptyList() : l; } static Iterable unnull(Iterable i) { return i == null ? emptyList() : i; } static Object[] unnull(Object[] a) { return a == null ? new Object[0] : a; } static BitSet unnull(BitSet b) { return b == null ? new BitSet() : b; } static RuntimeException asRuntimeException(Throwable t) { return t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t); } static Map synchroHashMap() { return Collections.synchronizedMap(new HashMap()); } static int isAndroid_flag; static boolean isAndroid() { if (isAndroid_flag == 0) isAndroid_flag = System.getProperty("java.vendor").toLowerCase().indexOf("android") >= 0 ? 1 : -1; return isAndroid_flag > 0; } static List emptyList() { return new ArrayList(); //ret Collections.emptyList(); } static abstract class F1 { abstract B get(A a); } static abstract class F { abstract B get(A a); } static RuntimeException rethrow(Throwable e) { throw asRuntimeException(e); } // get purpose 1: access a list/array/map (safer version of x.get(y)) static A get(List l, int idx) { return l != null && idx >= 0 && idx < l(l) ? l.get(idx) : null; } // seems to conflict with other signatures /*static B get(Map map, A key) { ret map != null ? map.get(key) : null; }*/ static A get(A[] l, int idx) { return idx >= 0 && idx < l(l) ? l[idx] : null; } // default to false static boolean get(boolean[] l, int idx) { return idx >= 0 && idx < l(l) ? l[idx] : false; } // get purpose 2: access a field by reflection or a map static Object get(Object o, String field) { try { if (o instanceof Class) return get((Class) o, field); if (o instanceof Map) return ((Map) o).get(field); Field f = getOpt_findField(o.getClass(), field); if (f != null) { f.setAccessible(true); return f.get(o); } } catch (Exception e) { throw asRuntimeException(e); } throw new RuntimeException("Field '" + field + "' not found in " + o.getClass().getName()); } static Object get_raw(Object o, String field) { try { Field f = get_findField(o.getClass(), field); f.setAccessible(true); return f.get(o); } catch (Exception e) { throw new RuntimeException(e); } } static Object get(Class c, String field) { try { Field f = get_findStaticField(c, field); f.setAccessible(true); return f.get(null); } catch (Exception e) { throw new RuntimeException(e); } } static Field get_findStaticField(Class c, String field) { Class _c = c; do { for (Field f : _c.getDeclaredFields()) if (f.getName().equals(field) && (f.getModifiers() & Modifier.STATIC) != 0) return f; _c = _c.getSuperclass(); } while (_c != null); throw new RuntimeException("Static field '" + field + "' not found in " + c.getName()); } static Field get_findField(Class c, String field) { Class _c = c; do { for (Field f : _c.getDeclaredFields()) if (f.getName().equals(field)) return f; _c = _c.getSuperclass(); } while (_c != null); throw new RuntimeException("Field '" + field + "' not found in " + c.getName()); } static int l(Object[] a) { return a == null ? 0 : a.length; } static int l(boolean[] a) { return a == null ? 0 : a.length; } static int l(byte[] a) { return a == null ? 0 : a.length; } static int l(int[] a) { return a == null ? 0 : a.length; } static int l(float[] a) { return a == null ? 0 : a.length; } static int l(char[] a) { return a == null ? 0 : a.length; } static int l(Collection c) { return c == null ? 0 : c.size(); } static int l(Map m) { return m == null ? 0 : m.size(); } static int l(CharSequence s) { return s == null ? 0 : s.length(); } static long l(File f) { return f == null ? 0 : f.length(); } static int l(Object o) { return o instanceof String ? l((String) o) : o instanceof Map ? l((Map) o) : l((Collection) o); // incomplete } static Field getOpt_findField(Class c, String field) { Class _c = c; do { for (Field f : _c.getDeclaredFields()) if (f.getName().equals(field)) return f; _c = _c.getSuperclass(); } while (_c != null); return null; } }