// Copyright 2014-2025 Jesper Larsson
//
// This file is part of Klipspringer, <https://klipspringer.avadeaux.net/>
//
// Klipspringer is free software: you can redistribute it and/or modify it under the terms of the
// GNU General Public License as published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// Klipspringer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with Klipspringer. If
// not, see <https://www.gnu.org/licenses/>.
package net.avadeaux.klipspringer;
import java.nio.file.Path;
import java.nio.file.Paths;
import net.avadeaux.klipspringer.codec.*;
public class DevicePlayer {
private void run(String[] args) {
String device = null;
String output = Terminal.inTty() ? null : "none";
double obufsec = 2.0;
int urunreco = 2;
int xbufframes = 131072;
String formatString = "";
Path recdir = Paths.get("");
double ibufsec = 4.0;
String controlsock = null;
int controlthreads = 20;
boolean cmdintf = true;
boolean quiet = false;
OutputSpec.Notify noti = new OutputSpec.Notify();
String helpText = "Arguments:\n"
+"-device <string> Input device. If not specified, use audio system default\n"
+"-format <string> Default device format options. Default: '-r 44100 -b 16 -c 2 -e signed -B'\n"
+"-ibufsec <float> Input device PCM buffer time. Default is "+ibufsec+"\n"
+"-output <string> PCM output device or URL. Default is standard sound device if interactive\n"
+"-obufsec <float> External buffer time on PCM output. Default is "+obufsec+"\n"
+"-urunreco <int> Times to attempt recovery after buffer underrun. Default is "+urunreco+"\n"
+"-xbufframes <int> Minimum number of sample frames in transfer buffer. Default is "+xbufframes+"\n"
+"-controlsock <path> Socket path for control server. By default, there is no control server\n"
+"-controlthreads <int> Max threads in control server. Default is "+controlthreads+"\n"
+"-cmdintf <boolean> Use interactive command interface when applicable. Default is "+cmdintf+"\n"
+"-recdir <path> Directory of recorded files. Default is working directory\n"
+"-q Suppress TTY output\n"
+"-help or --help Print this message\n\n"
+"TTY key commands:\n"
+" SPACE Stop/start capture and playback\n"
+" r Start/stop recording file\n"
+" q Quit\n";
for (int argPos = 0; argPos < args.length;) {
String o = args[argPos++];
if ("-q" .equals(o)) { quiet = true; continue; }
String v = argPos < args.length ? args[argPos++] : "";
if ("-device" .equals(o)) { device = v; }
else if ("-format" .equals(o)) { formatString = v; }
else if ("-ibufsec" .equals(o)) { ibufsec = Double.parseDouble(v); }
else if ("-output" .equals(o)) { output = v; }
else if ("-obufsec" .equals(o)) { obufsec = Double.parseDouble(v); }
else if ("-urunreco" .equals(o)) { urunreco = Integer.parseInt(v); }
else if ("-xbufframes" .equals(o)) { xbufframes = Integer.parseInt(v); }
else if ("-controlsock" .equals(o)) { controlsock = v; }
else if ("-controlthreads" .equals(o)) { controlthreads = Integer.parseInt(v); }
else if ("-cmdintf" .equals(o)) { cmdintf = Boolean.parseBoolean(v); }
else if ("-recdir" .equals(o)) { recdir = Paths.get(v); }
else if (o.startsWith("-h") || o.startsWith("--h")) { System.out.println(helpText); return; }
else {
System.err.println("Unregognized argument "+o+" (use -help for usage)");
System.exit(2);
}
}
PcmFormat format = Track.rawFormat(formatString);
DeviceTerminalDisplay display = null;
try {
DeviceFeeder feeder = new DeviceFeeder(device, format, ibufsec, recdir.toAbsolutePath(), xbufframes);
Device.Player.Factory outFact = OutputSpec.process(output, obufsec, urunreco, noti);
if (controlsock != null && controlsock.length() > 0) {
Path sock = Path.of(controlsock);
AsyncCommandServer cmdServ = new AsyncCommandServer(new DeviceCommandInterface(feeder), sock, controlthreads);
new Thread("Control Server") {
public void run() {
System.out.println("Ready control socket "+sock);
cmdServ.run();
}
}.start();
}
new Thread("Device feeder") {
public void run() {
try {
feeder.runFeed();
} catch (Throwable th) {
th.printStackTrace();
System.exit(1);
} finally {
System.exit(0); // in case some thread is stuck in wait state
}
}
}.start();
if (!quiet && Terminal.outTty()) {
DeviceTerminalDisplay d = display = new DeviceTerminalDisplay(format);
new Thread("Terminal display") {
public void run() {
noti.verifyReady();
d.run();
}
}.start();
}
DeviceFeeder.Play play = outFact == null ? null : feeder.fetchStream(outFact);
if (display != null) { display.startPlay(play); }
if (cmdintf && Terminal.inTty()) {
DeviceKeyInterface intf = new DeviceKeyInterface(feeder, outFact, display);
DeviceTerminalDisplay d = display;
new Thread("Interactive Interface") {
public void run() {
try {
intf.runUntilQuit(play);
} catch (Throwable th) {
if (d != null && Terminal.errTty()) { d.nobackup(); }
System.err.println(th);
if (System.getProperty("klipspringer.debug") != null) { th.printStackTrace(); }
System.exit(1);
}
System.exit(0);
}
}.start();
}
} catch (Throwable th) {
if (display != null && Terminal.errTty()) { display.nobackup(); }
System.err.println(th);
if (System.getProperty("klipspringer.debug") != null) { th.printStackTrace(); }
System.exit(1);
}
}
public static void main(String[] args) {
new DevicePlayer().run(args);
}
}
Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home