Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/capture magewell #183

Merged
merged 20 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
cdf839b
Initial attempt at making the SCS2 playback use Bytedeco since the re…
Jul 16, 2023
2b80f8e
Got SCS2 working with BytedecoWindowsDemuxer, all the tests pass, sti…
Jul 18, 2023
019a8e7
Fix build.gradle issue where libraries were missing
PotatoPeeler3000 Jul 19, 2023
ce51f59
Merge branch 'refs/heads/develop' into capture-video-demo
PotatoPeeler3000 Jul 30, 2024
660804f
Updated the VideoViewer to be able to load both the BlackMagicVideoDa…
PotatoPeeler3000 Jul 30, 2024
c92c014
Updated interface to not specify the type when its being used. Pulled…
PotatoPeeler3000 Aug 2, 2024
e5fae06
Rename variables to be more clear, and add a comment
PotatoPeeler3000 Aug 2, 2024
09d9931
Removed the ability to just export a video, wasn't being used, sepera…
PotatoPeeler3000 Aug 5, 2024
e1b4885
Just a complete mess, need to go to other desk and sort it with my mu…
PotatoPeeler3000 Aug 6, 2024
764e9b0
Updated the way we crop logs with MagewellMuxer, cropping logs now works
PotatoPeeler3000 Aug 6, 2024
dcfbab6
:arrow_up: ihmc-robot-data-logger 0.29.1 (#184)
ds58 Aug 7, 2024
758194d
Check that the next frame is greater then the previous one
PotatoPeeler3000 Aug 7, 2024
01b673a
Cleaned up tests and implementation, added docs
PotatoPeeler3000 Aug 7, 2024
18c6241
Bug fixes with video times stamps, video fps, video quality
PotatoPeeler3000 Aug 8, 2024
de61dba
Bump logger version to get acceess to MagewellMuxer and MagewellDemuxer
PotatoPeeler3000 Aug 21, 2024
cb66e0e
Merge branch 'develop' into feature/capture-magewell
PotatoPeeler3000 Aug 21, 2024
2152bc9
Didn't mean to commit this change, reverted
PotatoPeeler3000 Aug 21, 2024
d439e1a
Update CameraType to follow convention, bumped version to get access …
PotatoPeeler3000 Aug 21, 2024
5cc7a9b
Merge branch 'develop' into feature/capture-magewell
PotatoPeeler3000 Aug 21, 2024
8242bb4
Set debug parameter to false
PotatoPeeler3000 Aug 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion scs2-session-logger/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ mainDependencies {
api("us.ihmc:scs2-session:source")
api("us.ihmc:scs2-simulation:source") // TODO Need to fix this, it needs the Robot.

api("us.ihmc:ihmc-robot-data-logger:0.29.3")
api("us.ihmc:ihmc-robot-data-logger:0.29.4")
api("com.github.luben:zstd-jni:1.5.5-10")
api("org.antlr:antlr4-runtime:4.13.1")
//api("org.lz4:lz4-java:1.8.0")
Expand Down
PotatoPeeler3000 marked this conversation as resolved.
Show resolved Hide resolved
PotatoPeeler3000 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package us.ihmc.scs2.sessionVisualizer.jfx.session.log;

import us.ihmc.codecs.demuxer.MP4VideoDemuxer;
import us.ihmc.codecs.generated.YUVPicture;
import us.ihmc.concurrent.ConcurrentCopier;
import us.ihmc.robotDataLogger.Camera;
import us.ihmc.scs2.session.log.ProgressConsumer;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;

public class BlackMagicVideoDataReader implements VideoDataReader
{
private final TimestampScrubber timestampScrubber;
PotatoPeeler3000 marked this conversation as resolved.
Show resolved Hide resolved
private final String name;

private final MP4VideoDemuxer demuxer;
private final JavaFXPictureConverter converter = new JavaFXPictureConverter();

private final File videoFile;
private final Camera camera;
private final ConcurrentCopier<FrameData> imageBuffer = new ConcurrentCopier<>(FrameData::new);

public BlackMagicVideoDataReader(Camera camera, File dataDirectory, boolean hasTimeBase) throws IOException
{
this.camera = camera;
name = camera.getNameAsString();
boolean interlaced = camera.getInterlaced();

if (!hasTimeBase)
{
System.err.println("Video data is using timestamps instead of frame numbers. Falling back to seeking based on timestamp.");
PotatoPeeler3000 marked this conversation as resolved.
Show resolved Hide resolved
}

videoFile = new File(dataDirectory, camera.getVideoFileAsString());

if (!videoFile.exists())
{
throw new IOException("Cannot find video: " + videoFile);
}

demuxer = new MP4VideoDemuxer(videoFile);

File timestampFile = new File(dataDirectory, camera.getTimestampFileAsString());
this.timestampScrubber = new TimestampScrubber(timestampFile, hasTimeBase, interlaced);
}

@Override
public int getImageHeight()
{
return 0;
}

@Override
public int getImageWidth()
{
return 0;
}

public void readVideoFrame(long queryRobotTimestamp)
{
long videoTimestamp = timestampScrubber.getVideoTimestampFromRobotTimestamp(queryRobotTimestamp);
long currentRobotTimestamp = timestampScrubber.getCurrentRobotTimestamp();

try
{
demuxer.seekToPTS(videoTimestamp);
FrameData copyForWriting = imageBuffer.getCopyForWriting();
copyForWriting.queryRobotTimestamp = queryRobotTimestamp;
copyForWriting.currentRobotTimestamp = currentRobotTimestamp;
copyForWriting.currentVideoTimestamp = videoTimestamp;
copyForWriting.currentDemuxerTimestamp = demuxer.getCurrentPTS();
YUVPicture nextFrame = demuxer.getNextFrame(); // Increment frame index after getting frame.
copyForWriting.frame = converter.toFXImage(nextFrame, copyForWriting.frame);

imageBuffer.commit();
}
catch (IOException e)
{
e.printStackTrace();
}
}

public void cropVideo(File outputFile, File timestampFile, long startTimestamp, long endTimestamp, ProgressConsumer monitor) throws IOException
{
long startVideoTimestamp = timestampScrubber.getVideoTimestampFromRobotTimestamp(startTimestamp);
long endVideoTimestamp = timestampScrubber.getVideoTimestampFromRobotTimestamp(endTimestamp);

int framerate = VideoConverter.cropBlackMagicVideo(videoFile, outputFile, startVideoTimestamp, endVideoTimestamp, monitor);

PrintWriter timestampWriter = new PrintWriter(timestampFile);
timestampWriter.println(1);
timestampWriter.println(framerate);

long pts = 0;
/*
* PTS gets reordered to be monotonically increasing starting from 0
*/
for (int i = 0; i < timestampScrubber.getRobotTimestampsLength(); i++)
{
long robotTimestamp = timestampScrubber.getRobotTimestampAtIndex(i);

if (robotTimestamp >= startTimestamp && robotTimestamp <= endTimestamp)
{
timestampWriter.print(robotTimestamp);
timestampWriter.print(" ");
timestampWriter.println(pts);
pts++;
}
else if (robotTimestamp > endTimestamp)
{
break;
}
}

timestampWriter.close();
}

public String getName()
{
return name;
}

public Camera getCamera()
{
return camera;
}

public FrameData pollCurrentFrame()
{
return imageBuffer.getCopyForReading();
}

public int getCurrentIndex()
{
return timestampScrubber.getCurrentIndex();
}

public boolean replacedRobotTimestampsContainsIndex(int index)
{
return timestampScrubber.getReplacedRobotTimestampIndex(index);
}
}
PotatoPeeler3000 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package us.ihmc.scs2.sessionVisualizer.jfx.session.log;

import javafx.scene.image.WritableImage;

/**
* This class is used for debugging timestamp delays in the videos when viewing them in SCS2. In order to see the timestamp debugging information change the
* boolean in {@link VideoViewer#LOGGER_VIDEO_DEBUG} to true. This can also be set as an environmental variable.
*/
public class FrameData
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make this a java record instead of a class?

{
public WritableImage frame;
public long queryRobotTimestamp;
public long currentRobotTimestamp;
public long currentVideoTimestamp;
public long currentDemuxerTimestamp;
}
PotatoPeeler3000 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package us.ihmc.scs2.sessionVisualizer.jfx.session.log;

import javafx.scene.image.Image;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.JavaFXFrameConverter;
import us.ihmc.robotDataLogger.Camera;
import us.ihmc.robotDataLogger.logger.MagewellDemuxer;
import us.ihmc.robotDataLogger.logger.MagewellMuxer;
import us.ihmc.scs2.session.log.ProgressConsumer;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;

public class MagewellVideoDataReader implements VideoDataReader
{
private final TimestampScrubber timestampScrubber;
private final String name;

private final MagewellDemuxer magewellDemuxer;

private final Camera camera;
private final FrameData frameData = new FrameData();

public MagewellVideoDataReader(Camera camera, File dataDirectory, boolean hasTimeBase) throws IOException
{
this.camera = camera;
name = camera.getNameAsString();
boolean interlaced = camera.getInterlaced();

if (!hasTimeBase)
{
System.err.println("Video data is using timestamps instead of frame numbers. Falling back to seeking based on timestamp.");
PotatoPeeler3000 marked this conversation as resolved.
Show resolved Hide resolved
}

File videoFile = new File(dataDirectory, camera.getVideoFileAsString());

if (!videoFile.exists())
{
throw new IOException("Cannot find video: " + videoFile);
}

magewellDemuxer = new MagewellDemuxer(videoFile);

File timestampFile = new File(dataDirectory, camera.getTimestampFileAsString());
this.timestampScrubber = new TimestampScrubber(timestampFile, hasTimeBase, interlaced);
}

public int getImageHeight()
{
return magewellDemuxer.getImageHeight();
}

public int getImageWidth()
{
return magewellDemuxer.getImageWidth();
}

public void readVideoFrame(long queryRobotTimestamp)
{
long currentVideoTimestamps = timestampScrubber.getVideoTimestampFromRobotTimestamp(queryRobotTimestamp);
long currentRobotTimestamp = timestampScrubber.getCurrentRobotTimestamp();

magewellDemuxer.seekToPTS(currentVideoTimestamps);

// This is a copy that can be shown in the video view to debug timestamp issues
{
FrameData copyForWriting = frameData;
copyForWriting.queryRobotTimestamp = queryRobotTimestamp;
copyForWriting.currentRobotTimestamp = currentRobotTimestamp;
copyForWriting.currentVideoTimestamp = currentVideoTimestamps;
copyForWriting.currentDemuxerTimestamp = magewellDemuxer.getCurrentPTS();
}

Frame nextFrame = magewellDemuxer.getNextFrame();
frameData.frame = convertFrameToWritableImage(nextFrame);
}

/**
* This class converts a {@link Frame} to a {@link WritableImage} in order to be displayed correctly in JavaFX.
*
* @param frameToConvert is the next frame we want to visualize so we convert it to be compatible with JavaFX
* @return {@link WritableImage}
*/
public WritableImage convertFrameToWritableImage(Frame frameToConvert)
{
Image currentImage;

if (frameToConvert == null)
{
return null;
}

try (JavaFXFrameConverter frameConverter = new JavaFXFrameConverter())
{
currentImage = frameConverter.convert(frameToConvert);
}
WritableImage writableImage = new WritableImage((int) currentImage.getWidth(), (int) currentImage.getHeight());
PixelReader pixelReader = currentImage.getPixelReader();
PixelWriter pixelWriter = writableImage.getPixelWriter();

for (int y = 0; y < currentImage.getHeight(); y++)
{
for (int x = 0; x < currentImage.getWidth(); x++)
{
pixelWriter.setArgb(x, y, pixelReader.getArgb(x, y));
}
}

return writableImage;
}

public Frame getFrame()
{
return magewellDemuxer.getNextFrame();
}

public void cropVideo(File outputFile, File timestampFile, long startTimestamp, long endTimestamp, ProgressConsumer progressConsumer) throws IOException
{
long startVideoTimestamp = timestampScrubber.getVideoTimestampFromRobotTimestamp(startTimestamp);
long endVideoTimestamp = timestampScrubber.getVideoTimestampFromRobotTimestamp(endTimestamp);

long[] robotTimestampsForCroppedLog = timestampScrubber.getCroppedRobotTimestamps(startTimestamp, endTimestamp);
long[] videoTimestampsForCroppedLog = new long[robotTimestampsForCroppedLog.length];
int i = 0;

// This stuff is used to print to SCS2 so the user knows how the cropped log is going, progress wise
long startFrame = getFrameAtTimestamp(startVideoTimestamp, magewellDemuxer); // This also moves the stream to the startFrame
long endFrame = getFrameAtTimestamp(endVideoTimestamp, magewellDemuxer);
long numberOfFrames = endFrame - startFrame;
int frameRate = (int) magewellDemuxer.getFrameRate();

magewellDemuxer.seekToPTS(startVideoTimestamp);

PrintWriter timestampWriter = new PrintWriter(timestampFile);
timestampWriter.println(1 + "\n" + frameRate);

long startTime = System.currentTimeMillis();

MagewellMuxer magewellMuxer = new MagewellMuxer(outputFile, magewellDemuxer.getImageWidth(), magewellDemuxer.getImageHeight());
magewellMuxer.start();

Frame frame;
while ((frame = magewellDemuxer.getNextFrame()) != null && magewellDemuxer.getFrameNumber() <= endFrame)
{
// We want to write all the frames at once to get equal timestamps between frames. When recording from the camera we have a fixed rate at which we
// receive frames, so we don't need to worry about it, here however, we don't have that so we cna grab the next frame as fast as possible. However if the
// timestamps between frames aren't large enough, things won't work. (maybe :))
long videoTimestamp = 1000 * (System.currentTimeMillis() - startTime);
magewellMuxer.recordFrame(frame, videoTimestamp);
videoTimestampsForCroppedLog[i] = magewellMuxer.getTimeStamp();
i++;

if (progressConsumer != null)
{
progressConsumer.info("frame %d/%d".formatted(magewellDemuxer.getFrameNumber() - startFrame, numberOfFrames));
progressConsumer.progress((double) (magewellDemuxer.getFrameNumber() - startFrame) / (double) numberOfFrames);
}
}

for (i = 0; i < videoTimestampsForCroppedLog.length; i++)
{
timestampWriter.print(robotTimestampsForCroppedLog[i]);
timestampWriter.print(" ");
timestampWriter.println(videoTimestampsForCroppedLog[i]);
}

magewellMuxer.close();
timestampWriter.close();
}

private static long getFrameAtTimestamp(long endCameraTimestamp, MagewellDemuxer magewellDemuxer)
{
magewellDemuxer.seekToPTS(endCameraTimestamp);
return magewellDemuxer.getFrameNumber();
}

public String getName()
{
return name;
}

public Camera getCamera()
{
return camera;
}

public FrameData pollCurrentFrame()
{
return frameData;
}

public int getCurrentIndex()
{
return timestampScrubber.getCurrentIndex();
}

public boolean replacedRobotTimestampsContainsIndex(int index)
{
return timestampScrubber.getReplacedRobotTimestampIndex(index);
}
}
Loading
Loading