Clamp high precision timer to specific tick drift

This commit is contained in:
Manevolent
2019-01-22 11:50:03 -05:00
parent 3e8991929e
commit 0fed4c68ce
3 changed files with 44 additions and 25 deletions
@@ -93,6 +93,9 @@ public class MusicPlayer {
long wake = System.nanoTime(); long wake = System.nanoTime();
long delay = 150 * 1_000_000; // 50ms interval long delay = 150 * 1_000_000; // 50ms interval
long sleep; long sleep;
long start = System.nanoTime();
double volume = 0.5D;
while (true) { while (true) {
int available = sink.availableInput(); int available = sink.availableInput();
@@ -101,6 +104,9 @@ public class MusicPlayer {
if (frameQueue.peek() == null) { if (frameQueue.peek() == null) {
try { try {
AudioFrame frame = audioSourceSubstream.next(); AudioFrame frame = audioSourceSubstream.next();
for (int i = 0; i < frame.getLength(); i ++)
frame.getSamples()[i] *= volume;
Collection<AudioFrame> frameList = resampleFilter.apply(frame); Collection<AudioFrame> frameList = resampleFilter.apply(frame);
frameQueue.addAll(frameList); frameQueue.addAll(frameList);
} catch (EOFException ex) { } catch (EOFException ex) {
@@ -130,6 +136,7 @@ public class MusicPlayer {
wake += delay; wake += delay;
sleep = (wake - System.nanoTime()) / 1_000_000; sleep = (wake - System.nanoTime()) / 1_000_000;
System.err.println(((double)(System.nanoTime() - start) / 1_000_000_000D) + " " + (sink.getPacketsSent() / 50D) + " " + sink.getUnderflows());
if (sleep > 0) Thread.sleep(sleep); if (sleep > 0) Thread.sleep(sleep);
} }
@@ -24,7 +24,7 @@ import com.github.manevolent.ts3j.protocol.packet.handler.client.LocalClientHand
import com.github.manevolent.ts3j.protocol.packet.handler.client.LocalClientHandlerRetrievingData; import com.github.manevolent.ts3j.protocol.packet.handler.client.LocalClientHandlerRetrievingData;
import com.github.manevolent.ts3j.protocol.packet.statistics.PacketStatistics; import com.github.manevolent.ts3j.protocol.packet.statistics.PacketStatistics;
import com.github.manevolent.ts3j.protocol.packet.transformation.InitPacketTransformation; import com.github.manevolent.ts3j.protocol.packet.transformation.InitPacketTransformation;
import com.github.manevolent.ts3j.util.HighPrecisionRecurrentTask; import com.github.manevolent.ts3j.util.HighPrecisionTimer;
import com.github.manevolent.ts3j.util.Ts3Debugging; import com.github.manevolent.ts3j.util.Ts3Debugging;
import com.github.manevolent.ts3j.util.Pair; import com.github.manevolent.ts3j.util.Pair;
@@ -387,9 +387,9 @@ public class LocalTeamspeakClientSocket
waitForState(ClientConnectionState.CONNECTED, timeout); waitForState(ClientConnectionState.CONNECTED, timeout);
microphoneThread = new HighPrecisionRecurrentTask( microphoneThread = new HighPrecisionTimer(
20, 20,
0.01F, 5F, // up to 100ms of drift/catchup
new MicrophoneTask() new MicrophoneTask()
); );
@@ -23,52 +23,60 @@ import java.util.concurrent.locks.LockSupport;
* *
* Time of execution is held in account when calculating the time to sleep until the next execution. * Time of execution is held in account when calculating the time to sleep until the next execution.
*/ */
public class HighPrecisionRecurrentTask extends Thread { public class HighPrecisionTimer extends Thread {
private final int periodInNanos; private final int periodInNanos;
private final int spinningTimeNanos; private final int maximumDriftInNanos;
private final Runnable target; private final Runnable target;
private volatile boolean stop; private volatile boolean stop;
/** /**
* @param periodInMilis The period in milliseconds of the task execution. * @param periodInMilis The period in milliseconds of the task execution.
* @param percentageOfSleepSpinning The percentage of total sleeping that should be done spinning to increase accuracy. * @param catchupTicks The number of ticks allowed to consecutively catch up to
* @param target The task to run. * @param target The task to run.
*/ */
public HighPrecisionRecurrentTask(int periodInMilis, float percentageOfSleepSpinning, Runnable target) { public HighPrecisionTimer(int periodInMilis,
float catchupTicks,
Runnable target) {
super("TS3J AudioThread"); super("TS3J AudioThread");
if (percentageOfSleepSpinning < 0 || percentageOfSleepSpinning > 1)
throw new IllegalArgumentException("percentageOfSleepSpinning < 0 | percentageOfSleepSpinning > 1");
this.periodInNanos = periodInMilis * 1_000_000; this.periodInNanos = periodInMilis * 1_000_000;
this.spinningTimeNanos = (int) (periodInNanos * percentageOfSleepSpinning); this.maximumDriftInNanos = (int) Math.floor((double)periodInNanos * (double)catchupTicks);
this.target = target; this.target = target;
} }
public boolean isStop() { public void gracefulStop() {
return stop; if (!this.stop) {
this.stop = true;
//if (isAlive()) interrupt();
} }
public void setStop(boolean stop) {
this.stop = stop;
} }
@Override @Override
@SuppressWarnings(value = "empty-statement") @SuppressWarnings(value = "empty-statement")
public void run() { public void run() {
long nextTarget = 0;
while (!stop) {
long now = System.nanoTime(); long now = System.nanoTime();
long nextTarget = now;
while (!stop) {
target.run(); target.run();
long total = System.nanoTime() - now;
nextTarget = now + periodInNanos - total; nextTarget += periodInNanos;
sleepFor(periodInNanos - total - spinningTimeNanos);
now = System.nanoTime();
// Clamp the next target to the maximum "drift" allowed
if (nextTarget - now > maximumDriftInNanos)
nextTarget = now - maximumDriftInNanos;
if (sleepFor(nextTarget - now)) {
while (nextTarget > System.nanoTime()) { while (nextTarget > System.nanoTime()) {
; //consume cycles ; //consume cycles
} }
} }
} }
}
private void sleepFor(long nanos) { private boolean sleepFor(long nanos) {
if (nanos > 0) { if (nanos > 0) {
long elapsed = 0; long elapsed = 0;
while (elapsed < nanos) { while (elapsed < nanos) {
@@ -76,7 +84,11 @@ public class HighPrecisionRecurrentTask extends Thread {
LockSupport.parkNanos(nanos - elapsed); LockSupport.parkNanos(nanos - elapsed);
elapsed += System.nanoTime() - t0; elapsed += System.nanoTime() - t0;
} }
return true;
} }
return false;
} }
} }