Skip to content

Commit

Permalink
Add humble library to produce video
Browse files Browse the repository at this point in the history
  • Loading branch information
sebhoerl committed Nov 4, 2021
1 parent 16566f3 commit 1f9690f
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 9 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,8 @@ ffmpeg -framerate 25 -i video_%d.png -c:v libx264 -profile:v high -crf 20 -pix_f
An example output can be seen in `example/output.mp4`.

![Example](example/output.png)

## Video output

As of November 2021, the renderer can also produce videos directly. For that, add the `outputFormat` option to the configuration file and set it to `Video`. The output path will then be interpreted as a path to a video file and the video will be created at this place.

5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
<artifactId>matsim</artifactId>
<version>${matsim.version}</version>
</dependency>
<dependency>
<groupId>io.humble</groupId>
<artifactId>humble-video-all</artifactId>
<version>0.3.0</version>
</dependency>
</dependencies>

<build>
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/ch/ethzm/matsim/renderer/config/RenderConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ public class RenderConfig {
public String eventsPath;
public String networkPath;

public enum OutputFormat {
None, Images, Video
}

public OutputFormat outputFormat = OutputFormat.Video;

public double startTime = 0.0;
public double endTime = 24 * 3600.0;
public double secondsPerFrame = 120.0;
Expand Down Expand Up @@ -44,9 +50,13 @@ public void validate() {

File outputFile = new File(outputPath);

if (outputFile.exists() && !outputFile.isDirectory()) {
if (outputFile.exists() && !outputFile.isDirectory() && outputFormat.equals(OutputFormat.Images)) {
throw new IllegalStateException("Output path is not a directory");
}

if (outputFile.exists() && !outputFile.isFile() && outputFormat.equals(OutputFormat.Video)) {
throw new IllegalStateException("Output path is not a file");
}

networks.forEach(NetworkConfig::validate);
vehicles.forEach(VehicleConfig::validate);
Expand Down
101 changes: 93 additions & 8 deletions src/main/java/ch/ethzm/matsim/renderer/main/RenderFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,21 @@
import ch.ethzm.matsim.renderer.config.ActivityConfig;
import ch.ethzm.matsim.renderer.config.NetworkConfig;
import ch.ethzm.matsim.renderer.config.RenderConfig;
import ch.ethzm.matsim.renderer.config.RenderConfig.OutputFormat;
import ch.ethzm.matsim.renderer.config.VehicleConfig;
import ch.ethzm.matsim.renderer.network.LinkDatabase;
import ch.ethzm.matsim.renderer.traversal.TraversalDatabase;
import ch.ethzm.matsim.renderer.traversal.VehicleDatabase;
import io.humble.video.Codec;
import io.humble.video.Encoder;
import io.humble.video.MediaPacket;
import io.humble.video.MediaPicture;
import io.humble.video.Muxer;
import io.humble.video.MuxerFormat;
import io.humble.video.PixelFormat;
import io.humble.video.Rational;
import io.humble.video.awt.MediaPictureConverter;
import io.humble.video.awt.MediaPictureConverterFactory;

public class RenderFrame extends JPanel {
final private TraversalDatabase traversalDatabase;
Expand Down Expand Up @@ -63,6 +74,9 @@ public RenderFrame(TraversalDatabase traversalDatabase, LinkDatabase linkDatabas
this.activityTypeMapper = activityTypeMapper;
this.vehicleDatabase = vehicleDatabase;

this.makeVideo = renderConfig.outputFormat.equals(OutputFormat.Images)
|| renderConfig.outputFormat.equals(OutputFormat.Video);

this.renderConfig = renderConfig;
this.time = renderConfig.startTime;
windowWidth = renderConfig.width;
Expand Down Expand Up @@ -126,7 +140,7 @@ public RenderFrame(TraversalDatabase traversalDatabase, LinkDatabase linkDatabas

private double time;
private long previousRenderTime = -1;
private boolean makeVideo = true;
private final boolean makeVideo;

private double timeStepPerSecond; // 600; // 120.0;
double framesPerSecond = 25.0;
Expand Down Expand Up @@ -163,6 +177,18 @@ public void paintComponent(Graphics windowGraphics) {

if (time > renderConfig.endTime) {
if (makeVideo) {
if (renderConfig.outputFormat.equals(OutputFormat.Video)) {
if (muxer != null) {
do {
encoder.encode(packet, null);
if (packet.isComplete())
muxer.write(packet, false);
} while (packet.isComplete());

muxer.close();
}
}

System.exit(1);
} else {
time = renderConfig.startTime;
Expand Down Expand Up @@ -251,19 +277,78 @@ public void paintComponent(Graphics windowGraphics) {
}

if (makeVideo) {
try {
synchronized (imageLock) {
ImageIO.write(surface, "png",
new File(String.format(renderConfig.outputPath + "/video_%d.png", frameIndex)));
if (renderConfig.outputFormat.equals(OutputFormat.Images)) {
try {
synchronized (imageLock) {
ImageIO.write(surface, "png",
new File(String.format(renderConfig.outputPath + "/video_%d.png", frameIndex)));

System.out.println(Time.writeTime(time));
}
} catch (IOException e) {
e.printStackTrace();
}
} else {
try {
if (muxer == null) {
muxer = Muxer.make(renderConfig.outputPath, null, null);

MuxerFormat format = muxer.getFormat();
Codec codec = Codec.findEncodingCodec(format.getDefaultVideoCodecId());

encoder = Encoder.make(codec);
encoder.setWidth(renderConfig.width);
encoder.setHeight(renderConfig.height);
// We are going to use 420P as the format because that's what most video formats
// these days use
final PixelFormat.Type pixelformat = PixelFormat.Type.PIX_FMT_YUV420P;
encoder.setPixelFormat(pixelformat);
encoder.setTimeBase(Rational.make(1.0 / 25.0));

System.out.println(Time.writeTime(time));
if (format.getFlag(MuxerFormat.Flag.GLOBAL_HEADER)) {
encoder.setFlag(Encoder.Flag.FLAG_GLOBAL_HEADER, true);
}

encoder.open(null, null);
muxer.addNewStream(encoder);

muxer.open(null, null);

picture = MediaPicture.make(encoder.getWidth(), encoder.getHeight(), pixelformat);
picture.setTimeBase(Rational.make(1.0 / 25.0));

packet = MediaPacket.make();
}

do {
BufferedImage newImage = new BufferedImage(surface.getWidth(), surface.getHeight(),
BufferedImage.TYPE_3BYTE_BGR);
newImage.getGraphics().drawImage(surface, 0, 0, null);

if (converter == null) {
converter = MediaPictureConverterFactory.createConverter(newImage, picture);
}

converter.toPicture(picture, newImage, frameIndex);

encoder.encode(packet, picture);
if (packet.isComplete()) {
muxer.write(packet, false);
}
} while (packet.isComplete());
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}

super.paintComponent(windowGraphics);
windowGraphics.drawImage(surface, 0, 0, this);
}

private Muxer muxer = null;
private Encoder encoder = null;
private MediaPicture picture = null;
private MediaPacket packet = null;
private MediaPictureConverter converter = null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package ch.ethzm.matsim.renderer.presets;

import java.util.Arrays;

import ch.ethzm.matsim.renderer.config.ActivityConfig;
import ch.ethzm.matsim.renderer.config.NetworkConfig;
import ch.ethzm.matsim.renderer.config.RenderConfig;
import ch.ethzm.matsim.renderer.config.RenderConfig.OutputFormat;
import ch.ethzm.matsim.renderer.config.VehicleConfig;
import ch.ethzm.matsim.renderer.main.RunRenderer;

public class RunNantesVisualization {
static public void main(String[] args) {
// START CONFIGURATION

RenderConfig renderConfig = new RenderConfig();

renderConfig.width = 1280;
renderConfig.height = 720;

renderConfig.networkPath = "output_network.xml.gz";
renderConfig.eventsPath = "output_events.xml.gz";
renderConfig.outputPath = "video.mp4";
renderConfig.outputFormat = OutputFormat.Video;

renderConfig.startTime = 8.0 * 3600.0;
renderConfig.endTime = 10.0 * 3600.0;
renderConfig.secondsPerFrame = 120.0;

renderConfig.showTime = false;

renderConfig.center = Arrays.asList(355424.0, 6689212.0);
renderConfig.zoom = 20000.0;

NetworkConfig roadNetwork = new NetworkConfig();
renderConfig.networks.add(roadNetwork);
roadNetwork.modes = Arrays.asList("car");
roadNetwork.color = Arrays.asList(240, 240, 240);

NetworkConfig subwayNetwork = new NetworkConfig();
renderConfig.networks.add(subwayNetwork);
subwayNetwork.modes = Arrays.asList("subway", "rail");
subwayNetwork.color = Arrays.asList(200, 200, 200);

VehicleConfig otherVehicle = new VehicleConfig();
renderConfig.vehicles.add(otherVehicle);
otherVehicle.color = Arrays.asList(160, 160, 160);
otherVehicle.size = 2;

VehicleConfig ptVehicle = new VehicleConfig();
renderConfig.vehicles.add(ptVehicle);
ptVehicle.contains = Arrays.asList("subway", "rail");
ptVehicle.color = Arrays.asList(0, 0, 0); // .asList(7, 145, 222);
ptVehicle.size = 4;

ActivityConfig workActivity = new ActivityConfig();
renderConfig.activities.add(workActivity);
workActivity.types.add("work");
workActivity.maximumLifetime = 300.0;
workActivity.size = 12;
workActivity.color = Arrays.asList(7, 145, 222);

// END CONFIGURATION

RunRenderer.run(renderConfig);
}
}

0 comments on commit 1f9690f

Please sign in to comment.