Rework rendering

Adds default, stereoscopic, tiling, cubic and equirectangular frame rendering
This commit is contained in:
johni0702
2015-05-23 14:56:11 +02:00
parent 361ba73978
commit 18f22f57d0
30 changed files with 2167 additions and 207 deletions

View File

@@ -1,6 +1,6 @@
package eu.crushedpixel.replaymod.video;
import eu.crushedpixel.replaymod.ReplayMod;
import com.google.common.base.Preconditions;
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
import org.monte.media.*;
import org.monte.media.FormatKeys.MediaType;
@@ -9,132 +9,170 @@ import org.monte.media.math.Rational;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class VideoWriter {
private static final String DATE_FORMAT = "yyyy_MM_dd_HH_mm_ss";
private static final SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
private static final SimpleDateFormat FILE_FORMAT = new SimpleDateFormat(DATE_FORMAT);
private static final String VIDEO_EXTENSION = ".avi";
private static MovieWriter out;
private static File file;
private static boolean isRecording = false;
private static boolean requestFinish = false;
private static boolean abort = false;
private final File file;
private final MovieWriter out;
private final Buffer buf;
private final int track;
private static Buffer buf;
private static int track;
private static Queue<BufferedImage> toWrite = new LinkedBlockingQueue<BufferedImage>();
private volatile boolean active = true;
private volatile boolean cancelled = false;
public static boolean isRecording() {
return isRecording;
private int queueLimit;
private final Queue<BufferedImage> toWrite;
private final Thread writerThread;
private final Lock lock = new ReentrantLock();
private final Condition emptyCondition = lock.newCondition();
private final Condition noLongerEmptyCondition = lock.newCondition();
private final Condition noLongerFullCondition = lock.newCondition();
public VideoWriter(int width, int height, int fps, float quality) throws IOException {
this(width, height, fps, quality, Integer.MAX_VALUE);
}
public static void startRecording(int width, int height) {
if(isRecording) {
IllegalStateException up = new IllegalStateException("VideoWriter is already recording!");
throw up; //lolololo
}
isRecording = true;
public VideoWriter(int width, int height, int fps, float quality, int queueLimit) throws IOException {
this.queueLimit = queueLimit;
this.toWrite = new LinkedList<BufferedImage>();
toWrite = new LinkedBlockingQueue<BufferedImage>();
File folder = ReplayFileIO.getRenderFolder();
String fileName = FILE_FORMAT.format(Calendar.getInstance().getTime());
file = new File(folder, fileName + VIDEO_EXTENSION);
Files.createFile(file.toPath());
try {
File folder = ReplayFileIO.getRenderFolder();
out = Registry.getInstance().getWriter(file);
Format format = new Format(FormatKeys.MediaTypeKey, MediaType.VIDEO,
FormatKeys.EncodingKey, VideoFormatKeys.ENCODING_AVI_MJPG,
FormatKeys.FrameRateKey, new Rational(fps, 1),
VideoFormatKeys.WidthKey, width,
VideoFormatKeys.HeightKey, height,
VideoFormatKeys.DepthKey, 24,
VideoFormatKeys.QualityKey, quality);
String fileName = sdf.format(Calendar.getInstance().getTime());
track = out.addTrack(format);
file = new File(folder, fileName + VIDEO_EXTENSION);
file.createNewFile();
buf = new Buffer();
buf.format = new Format(VideoFormatKeys.DataClassKey, BufferedImage.class);
buf.sampleDuration = out.getFormat(track).get(VideoFormatKeys.FrameRateKey).inverse();
out = Registry.getInstance().getWriter(file);
Format format = new Format(FormatKeys.MediaTypeKey, MediaType.VIDEO,
FormatKeys.EncodingKey, VideoFormatKeys.ENCODING_AVI_MJPG,
FormatKeys.FrameRateKey, new Rational(ReplayMod.replaySettings.getVideoFramerate(), 1),
VideoFormatKeys.WidthKey, width,
VideoFormatKeys.HeightKey, height,
VideoFormatKeys.DepthKey, 24,
VideoFormatKeys.QualityKey, (float) ReplayMod.replaySettings.getVideoQuality());
track = out.addTrack(format);
buf = new Buffer();
buf.format = new Format(VideoFormatKeys.DataClassKey, BufferedImage.class);
buf.sampleDuration = out.getFormat(track).get(VideoFormatKeys.FrameRateKey).inverse();
} catch(IOException e) {
e.printStackTrace();
}
Thread t = new Thread(new Runnable() {
writerThread = new Thread(new Runnable() {
@Override
public void run() {
while(true) {
if(toWrite.isEmpty() || abort) {
if(requestFinish) {
requestFinish = false;
isRecording = false;
try {
out.close();
if(abort) {
file.delete();
}
} catch(IOException e) {
e.printStackTrace();
}
abort = false;
toWrite = new LinkedBlockingQueue<BufferedImage>();
return;
}
while(!cancelled && (active || !toWrite.isEmpty())) {
try {
lock.lockInterruptibly();
BufferedImage img;
try {
Thread.sleep(10);
} catch(Exception e) {
img = toWrite.poll();
if (img == null) {
noLongerEmptyCondition.await();
img = toWrite.poll();
}
noLongerFullCondition.signal();
if (toWrite.isEmpty()) {
emptyCondition.signalAll();
}
} finally {
lock.unlock();
}
buf.data = img;
try {
out.write(track, buf);
} catch (IOException e) {
e.printStackTrace();
}
} else {
write();
} catch (InterruptedException ignored) {
}
}
try {
toWrite.clear();
out.close();
if (cancelled) {
Files.delete(file.toPath());
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
t.start();
writerThread.start();
}
public static void writeImage(BufferedImage image) {
if(requestFinish || !isRecording) {
IllegalStateException up = new IllegalStateException(
"The VideoWriter is currently not available. Please try again later.");
throw up; //lolololo^2
}
toWrite.add(image);
public void setQueueLimit(int limit) {
this.queueLimit = limit;
}
public static void endRecording() {
if(!isRecording) return;
requestFinish = true;
}
/**
* Add the image to the writer queue.
* @param image The image
* @param waitIfFull Whether to wait if the queue is full or to return immediately
* @return {@code} true if the image was added, {@code false} if it could not be added due to the queue being full
* and {@code waitIfFull} being {@code false}
*/
public boolean writeImage(BufferedImage image, boolean waitIfFull) {
Preconditions.checkState(active, "This VideoWriter has already been closed.");
private static void write() {
lock.lock();
try {
BufferedImage img = toWrite.poll();
if(img != null) {
buf.data = img;
out.write(track, buf);
while (toWrite.size() >= queueLimit) {
if (waitIfFull) {
noLongerFullCondition.await();
} else {
return false;
}
}
} catch(IOException e) {
toWrite.offer(image);
noLongerEmptyCondition.signal();
return true;
} catch (InterruptedException ignored) {
} finally {
lock.unlock();
}
return false;
}
public void endRecording() {
active = false;
writerThread.interrupt();
}
public void abortRecording() {
cancelled = true;
active = false;
writerThread.interrupt();
}
public void waitTillQueueEmpty() {
lock.lock();
try {
if (!toWrite.isEmpty()) {
emptyCondition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void abortRecording() {
requestFinish = true;
abort = true;
public void waitForFinish() throws InterruptedException {
Preconditions.checkState(!active, "Video writer still active.");
writerThread.join();
}
}