mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Add a basic PoseStreamer implementation for streaming mocap data
This commit is contained in:
39
src/main/java/dev/slimevr/poserecorder/PoseFileStream.java
Normal file
39
src/main/java/dev/slimevr/poserecorder/PoseFileStream.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package dev.slimevr.poserecorder;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import io.eiren.vr.processor.HumanSkeleton;
|
||||
|
||||
public abstract class PoseFileStream implements AutoCloseable {
|
||||
|
||||
protected DataOutputStream dataStream;
|
||||
|
||||
protected PoseFileStream(OutputStream outputStream) {
|
||||
this.dataStream = new DataOutputStream(new BufferedOutputStream(outputStream));
|
||||
}
|
||||
|
||||
protected PoseFileStream(File file) throws FileNotFoundException {
|
||||
this(new FileOutputStream(file));
|
||||
}
|
||||
|
||||
protected PoseFileStream(String file) throws FileNotFoundException {
|
||||
this(new FileOutputStream(file));
|
||||
}
|
||||
|
||||
abstract boolean writeHeader(HumanSkeleton skeleton) throws IOException;
|
||||
|
||||
abstract boolean writeFrame(HumanSkeleton skeleton) throws IOException;
|
||||
|
||||
abstract boolean writeFooter(HumanSkeleton skeleton) throws IOException;
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
dataStream.close();
|
||||
}
|
||||
}
|
||||
@@ -67,16 +67,16 @@ public class PoseRecorder {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized Future<PoseFrames> startFrameRecording(int numFrames, long interval) {
|
||||
return startFrameRecording(numFrames, interval, server.getAllTrackers());
|
||||
public synchronized Future<PoseFrames> startFrameRecording(int numFrames, long intervalMs) {
|
||||
return startFrameRecording(numFrames, intervalMs, server.getAllTrackers());
|
||||
}
|
||||
|
||||
public synchronized Future<PoseFrames> startFrameRecording(int numFrames, long interval, List<Tracker> trackers) {
|
||||
public synchronized Future<PoseFrames> startFrameRecording(int numFrames, long intervalMs, List<Tracker> trackers) {
|
||||
if(numFrames < 1) {
|
||||
throw new IllegalArgumentException("numFrames must at least have a value of 1");
|
||||
}
|
||||
if(interval < 1) {
|
||||
throw new IllegalArgumentException("interval must at least have a value of 1");
|
||||
if(intervalMs < 1) {
|
||||
throw new IllegalArgumentException("intervalMs must at least have a value of 1");
|
||||
}
|
||||
if(trackers == null) {
|
||||
throw new IllegalArgumentException("trackers must not be null");
|
||||
@@ -107,10 +107,10 @@ public class PoseRecorder {
|
||||
this.frameCursor = 0;
|
||||
this.numFrames = numFrames;
|
||||
|
||||
frameRecordingInterval = interval;
|
||||
frameRecordingInterval = intervalMs;
|
||||
nextFrameTimeMs = -1L;
|
||||
|
||||
LogManager.log.info("[PoseRecorder] Recording " + numFrames + " samples at a " + interval + " ms frame interval");
|
||||
LogManager.log.info("[PoseRecorder] Recording " + numFrames + " samples at a " + intervalMs + " ms frame interval");
|
||||
|
||||
currentRecording = new CompletableFuture<PoseFrames>();
|
||||
return currentRecording;
|
||||
|
||||
94
src/main/java/dev/slimevr/poserecorder/PoseStreamer.java
Normal file
94
src/main/java/dev/slimevr/poserecorder/PoseStreamer.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package dev.slimevr.poserecorder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import io.eiren.util.ann.VRServerThread;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import io.eiren.vr.VRServer;
|
||||
import io.eiren.vr.processor.HumanSkeleton;
|
||||
|
||||
public class PoseStreamer {
|
||||
|
||||
protected long frameRecordingInterval = 60L;
|
||||
protected long nextFrameTimeMs = -1L;
|
||||
|
||||
private HumanSkeleton skeleton;
|
||||
private PoseFileStream poseFileStream;
|
||||
|
||||
protected final VRServer server;
|
||||
|
||||
public PoseStreamer(VRServer server) {
|
||||
this.server = server;
|
||||
|
||||
// Register callbacks/events
|
||||
server.addSkeletonUpdatedCallback(this::onSkeletonUpdated);
|
||||
server.addOnTick(this::onTick);
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
public void onSkeletonUpdated(HumanSkeleton skeleton) {
|
||||
this.skeleton = skeleton;
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
public void onTick() {
|
||||
PoseFileStream poseFileStream = this.poseFileStream;
|
||||
if (poseFileStream != null) {
|
||||
|
||||
long curTime = System.currentTimeMillis();
|
||||
if (curTime >= nextFrameTimeMs) {
|
||||
nextFrameTimeMs += frameRecordingInterval;
|
||||
|
||||
// To prevent duplicate frames, make sure the frame time is always in the future
|
||||
if (nextFrameTimeMs <= curTime) {
|
||||
nextFrameTimeMs = curTime + frameRecordingInterval;
|
||||
}
|
||||
|
||||
try {
|
||||
poseFileStream.writeFrame(skeleton);
|
||||
} catch (Exception e) {
|
||||
// Handle any exceptions without crashing the program
|
||||
LogManager.log.severe("[PoseStreamer] Exception while saving frame", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setFrameInterval(long intervalMs) {
|
||||
if(intervalMs < 1) {
|
||||
throw new IllegalArgumentException("intervalMs must at least have a value of 1");
|
||||
}
|
||||
|
||||
this.frameRecordingInterval = intervalMs;
|
||||
}
|
||||
|
||||
public void setOutput(PoseFileStream poseFileStream) throws IOException {
|
||||
poseFileStream.writeHeader(skeleton);
|
||||
this.poseFileStream = poseFileStream;
|
||||
nextFrameTimeMs = -1L; // Reset the frame timing
|
||||
}
|
||||
|
||||
public PoseFileStream getOutput() {
|
||||
return poseFileStream;
|
||||
}
|
||||
|
||||
public void closeOutput() {
|
||||
PoseFileStream poseFileStream = this.poseFileStream;
|
||||
|
||||
if (poseFileStream != null) {
|
||||
try {
|
||||
poseFileStream.writeFooter(skeleton);
|
||||
} catch (Exception e) {
|
||||
// Ignore
|
||||
LogManager.log.severe("[PoseStreamer] Exception while writing file footer", e);
|
||||
}
|
||||
|
||||
try {
|
||||
poseFileStream.close();
|
||||
} catch (Exception e) {
|
||||
// Ignore
|
||||
LogManager.log.severe("[PoseStreamer] Exception while closing file stream", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user