Da ich mich in den letzten Wochen wieder einmal mit Javas Sicherheitsmechanismen und dem Erzeugen von Animationen beschäftigt habe, habe ich den Entschluss gefasst, die bisher mittels JMF AVIs in dWb+ zu erstetzen - nur wodurch?
Ich konnte JMF schon seit längerem nicht mehr einsetzen, da es sich - sobald ein SecurityManager aktiv ist - standhaft weigert, überhaupt irgendwelche Dateien zu schreiben und es immer Dateien schreiben möchte - ich habe keine Möglichkeit gefunden, in Streams zu schreiben, die ich ja dann selber in eine Datei umleiten könnte.
Also muste eine Alternative her. Es ist nicht einfach, Animationen oder Videos aus Java heraus zu erzeugen - die einzige Bibliothek, mit der ich früher einige Erfolge gefeiert hatte - Xuggler - wird schon längere Zeit nicht mehr gepflegt.
Ich fand dann aber ein Beispiel, wie man mittels Java Motion JPEG Dateien erzeugen kann und war angetan - allerdings benötigte dieser auch schon wieder 12 Jahre alte Code eine Generalüberholung. Und so sieht meine modernisierte Variante davon nun so aus:
import java.awt.image.RenderedImage; //Vector
import javax.imageio.IIOImage;
import javax.imageio.ImageIO; //encodingJPEG
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.stream.MemoryCacheImageOutputStream;
import java.awt.image.BufferedImage;
import java.io.Closeable;
import java.io.IOException;
//Original: https://github.com/tjrantal/JavaFrameByFrameVideo/blob/master/src/mjpeg/MjpegWriter.java
public class MjpegWriter extends java.lang.Object implements Closeable
{
private final static org.slf4j.Logger CLASS_LOGGER =org.slf4j.LoggerFactory.getLogger(MjpegWriter.class);
private long frameNo;
private java.util.List<Long> frames;
private java.util.List<Integer> fsizes;
private long movi;
private long[] frameP; //Frame pointer
private long[] position ; //File position pointer
private MemoryCacheImageOutputStream videoFile; //Video file to write to
private int height;
private int width;
private long frameCount;
private final float quality;
private enum State{
BeforeHeader,
WritingImages,
StreamClosed;
};
private State state=State.BeforeHeader;
private final int framerate;
private java.io.OutputStream os;
//Constructor
public MjpegWriter(java.io.OutputStream os)
{
this(os,0.9f,25);
}
public MjpegWriter(java.io.OutputStream os, float quality,int framerate)
{
frameP = new long[3];
position = new long[3];
frames = new java.util.LinkedList<Long>();
fsizes = new java.util.LinkedList<Integer>();
frameCount = 0;
this.os=os;
videoFile = new MemoryCacheImageOutputStream(os);
this.quality=quality;
this.framerate=framerate;
}
private void writeHeader() throws java.io.IOException
{
String text = "RIFF";
videoFile.write(text.getBytes("ISO-8859-1")); //Riff
videoFile.write(intToByteArray(0)); //File size in bytes-8
//AVI
text = "AVI ";
videoFile.write(text.getBytes("ISO-8859-1")); //AVI
text = "LIST";
videoFile.write(text.getBytes("ISO-8859-1")); //LIST
videoFile.write(intToByteArray(192)); //list size (header size-12)
text = "hdrl";
videoFile.write(text.getBytes("ISO-8859-1")); //main header
text = "avih";
videoFile.write(text.getBytes("ISO-8859-1")); //avi header
videoFile.write(intToByteArray(14*4)); //avih size in bytes
//AVI header data
videoFile.write(intToByteArray(1000*1000/framerate)); //usec per frame
videoFile.write(intToByteArray(framerate*3*width*height)); //bytes per sec
videoFile.write(intToByteArray(0)); //reserved
videoFile.write(intToByteArray(2320)); //flags (none required for raw)
frameP[0] = videoFile.getStreamPosition();
videoFile.write(intToByteArray(0)); //total frames (none required for raw)
videoFile.write(intToByteArray(0)); //initial frames (0 for non-interleaved)
videoFile.write(intToByteArray(1)); //No. of streams
videoFile.write(intToByteArray(width*height*3)); //Buffer size
videoFile.write(intToByteArray(width)); //Width
videoFile.write(intToByteArray(height)); //Height
videoFile.write(intToByteArray(0)); //Reserved[4] set to zero
videoFile.write(intToByteArray(0)); //Reserved[4] set to zero
videoFile.write(intToByteArray(0)); //Reserved[4] set to zero
videoFile.write(intToByteArray(0)); //Reserved[4] set to zero
//AVI header written
//Stream header
text = "LIST";
videoFile.write(text.getBytes("ISO-8859-1"));
videoFile.write(intToByteArray(116)); //list size in bytes
text = "strl"; //Stream list
videoFile.write(text.getBytes("ISO-8859-1")); //Stream list
text = "strh"; //Stream header
videoFile.write(text.getBytes("ISO-8859-1")); //videoFileStream header
videoFile.write(intToByteArray(56)); //videoFileStream header size in bytes
//Stream header data
text = "vids"; //Stream header
videoFile.write(text.getBytes("ISO-8859-1")); //videoFileStream header
text = "MJPG"; //Stream handler
videoFile.write(text.getBytes("ISO-8859-1")); //Stream handler
videoFile.write(intToByteArray(0)); //Flags
videoFile.write(intToByteArray(0)); //Priority none needed for raw..
videoFile.write(intToByteArray(0)); //Language needed for raw..
//InitialFrames not used -> no audio...
videoFile.write(intToByteArray(1)); //dwScale
videoFile.write(intToByteArray(framerate)); //dwRate
videoFile.write(intToByteArray(0)); //dwStart
frameP[1] = videoFile.getStreamPosition();
videoFile.write(intToByteArray(0)); //dwLength
videoFile.write(intToByteArray(0)); //dwBuffer
videoFile.write(intToByteArray(-1)); //dwQuality
videoFile.write(intToByteArray(0)); //dwSampleSize
videoFile.write(intToByteArray(0)); //rcFrame
videoFile.write(intToByteArray(0)); //rcFrame
//Stream header data written
text = "strf"; //Stream format
videoFile.write(text.getBytes("ISO-8859-1")); //videoFileStream format
videoFile.write(intToByteArray(40)); //videoFileStream fomat size in bytes
videoFile.write(intToByteArray(40)); //Format header length = 40
//format data
videoFile.write(intToByteArray(width)); //width
videoFile.write(intToByteArray(height)); //height
videoFile.write(shortToByteArray((short) 1)); //planes = must be 1
videoFile.write(shortToByteArray((short) 0)); //bitCount (0 =jpeg tai png)
text = "MJPG"; //Compression
videoFile.write(text.getBytes("ISO-8859-1")); //compression
videoFile.write(intToByteArray(width*height*3)); //image size in bytes
videoFile.write(intToByteArray(0)); //xpixels per meter
videoFile.write(intToByteArray(0)); //ypixels per meter
videoFile.write(intToByteArray(0)); //ClrUsed
videoFile.write(intToByteArray(0)); //ClrImportant
//format data written...
//DATA chunk!!
text = "LIST"; //MOVI chunk
videoFile.write(text.getBytes("ISO-8859-1")); //Movi chunk
frameP[2] = videoFile.getStreamPosition(); //Insert pointer for decoded data
videoFile.write(intToByteArray(0)); //Decoded Data size!!
movi = videoFile.getStreamPosition();
CLASS_LOGGER.trace("setting movi {}",movi);
text = "movi"; //MOVI list name
videoFile.write(text.getBytes("ISO-8859-1")); //Movi list name
}
//Functions
public void write_frame(RenderedImage image) throws IOException
{
if(state==State.BeforeHeader)
{
width=image.getWidth();
height= image.getHeight();
writeHeader();
state=State.WritingImages;
}
if(state==State.WritingImages)
{
String text = "00dc"; //MOVI chunk
position[0] = videoFile.getStreamPosition();
videoFile.write(text.getBytes("ISO-8859-1")); //Movi chunk
videoFile.write(intToByteArray(width * height * 3));//Movi chunk size place holder....
position[1] = videoFile.getStreamPosition();
write_JPEG(image);
if ((videoFile.getStreamPosition() - position[0]) % 4 > 0)
{ //Zero padding
byte[] padding = new byte[(int) ((videoFile.getStreamPosition() - position[0]) % 4)];
videoFile.write(padding);
}
position[2] = videoFile.getStreamPosition();
videoFile.seek(position[0] + 4);
videoFile.write(intToByteArray((int) (position[2] - position[1]))); //Movi chunk size
videoFile.seek(position[2]);
frames.add(position[0] - movi);
fsizes.add((int) (position[2] - position[1]));
++frameCount;
}
else
{
throw new IllegalStateException("not in state "+State.WritingImages+"!");
}
}
public void close() throws java.io.IOException{
long currentPosition = videoFile.getStreamPosition();
long idxPos = currentPosition;
CLASS_LOGGER.trace("seeking to {}",movi-4);
videoFile.seek(movi-4);
videoFile.write(intToByteArray((int) (currentPosition-movi))); //Movi chunk size
videoFile.seek(currentPosition);
//Write index
String text = "idx1"; //idx1 chunk
videoFile.write(text.getBytes("ISO-8859-1")); //idx1 chunk
videoFile.write(intToByteArray((int) (frameCount*4*4))); //index size
for (int k = 0;k<frameCount;k++){
text = "00dc"; //idx1 chunk
videoFile.write(text.getBytes("ISO-8859-1")); //idx1 chunk
videoFile.write(intToByteArray((int) 16)); //Key frame flag!!
videoFile.write(intToByteArray(frames.get(k).intValue())); //offset from BEGINNING of (thus the +4) movi!!
videoFile.write(intToByteArray((int) (fsizes.get(k)))); //chunk size
}
currentPosition = videoFile.getStreamPosition();
videoFile.seek(4);
videoFile.write(intToByteArray((int) (currentPosition-8))); //File size
videoFile.seek(frameP[0]);
videoFile.write(intToByteArray((int) frameCount));
videoFile.seek(frameP[1]);
videoFile.write(intToByteArray((int) frameCount)); //frames2
videoFile.seek(frameP[2]);
videoFile.write(intToByteArray((int) (idxPos-movi))); //encoded image data amount
//Index written...
//Close videoFile
videoFile.close();
os.close();
state=State.StreamClosed;
}
private void write_JPEG(RenderedImage data) throws IOException
{
BufferedImage bi;
//if the image has an alpha channel, the JPEG writer fails silently without outputting any data
//https://stackoverflow.com/a/66954103
if(data.getColorModel().hasAlpha())
{
CLASS_LOGGER.warn("Image has alpha channel - needs to be converted...");
bi=de.elbosso.ui.image.Utilities.applyAlphaAndRemoveAlphaChannel(data);
}
else
{
bi=de.elbosso.ui.image.Utilities.toBufferedImage(data);
}
java.io.ByteArrayOutputStream outData = new java.io.ByteArrayOutputStream();
ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpgWriteParam.setCompressionQuality(quality);
ImageOutputStream ios=new MemoryCacheImageOutputStream(outData);
jpgWriter.setOutput(ios);
IIOImage outputImage = new IIOImage(bi, null, null);
jpgWriter.write(null, outputImage, jpgWriteParam);
jpgWriter.dispose();
outData.close();
byte[] compressedData = outData.toByteArray();
CLASS_LOGGER.trace("compressed frame size {}",compressedData.length);
videoFile.write(compressedData);
}
private byte[] intToByteArray(int value){
// return new byte[] {(byte) (value >> 24 & 0xFF),(byte) (value >> 16 & 0xFF),(byte) (value >> 8 & 0xFF),(byte) (value & 0xFF)};
return new byte[] {(byte) (value & 0xFF),(byte) ((value >> 8) & 0xFF),(byte) ((value >> 16) & 0xFF),(byte) ((value >> 24) & 0xFF)};
}
private byte[] shortToByteArray(short value){
// return new byte[] {(byte) (value >> 8 & 0xFF),(byte) (value & 0xFF)};
return new byte[] {(byte) (value & 0xFF),(byte) ((value>>8) & 0xFF)};
}
private byte[] intArrayToByteArray(int[] arrayIn){
byte[] arrayOut = new byte[arrayIn.length*4];
byte[] temp;
for (int i= 0; i<arrayIn.length; ++i){
temp = intToByteArray(arrayIn[i]);
for (int j = 0; j< 4; ++j){
arrayOut[4*i+j] = temp[j];
}
}
return arrayOut;
}
}
07.02.2025
Ich hatte neulich hier einen Link zu Multi-Scale Truchet Patterns und habe seitdem den Algorithmus mit java umgesetzt und ihn als Teil meines Projekts zur Testdatengenerierung veröffentlicht.
OAuth und OTP
16.02.2020
Wie bereits beschrieben will ich mich demnächst näher mit OAuth befassen...
Weiterlesen...Android Basteln C und C++ Chaos Datenbanken Docker dWb+ ESP Wifi Garten Geo Go GUI Gui Hardware Java Jupyter JupyterBinder Komponenten Links Linux Markdown Markup Music Numerik OpenSource PKI-X.509-CA Präsentationen Python QBrowser Rants Raspi Revisited Security Software-Test sQLshell TeleGrafana Verschiedenes Video Virtualisierung Windows Upcoming...
Ich musste neulich darüber nachdenken, eine Parallelisierung für einen meiner ANT-Tasks in meinem Static Site Generator einzubauen.
WeiterlesenIch habe mich mit der Idee zu diesem Projekt Monate abgequält - hätte ich gewusst, was die eigentliche Implementierung für Qualen verursachen würde, hätte ich sie wahrscheinlich eingestampft.
WeiterlesenIch hatte neulich hier einen Link zu Multi-Scale Truchet Patterns und habe seitdem den Algorithmus mit java umgesetzt und ihn als Teil meines Projekts zur Testdatengenerierung veröffentlicht.
WeiterlesenManche nennen es Blog, manche Web-Seite - ich schreibe hier hin und wieder über meine Erlebnisse, Rückschläge und Erleuchtungen bei meinen Hobbies.
Wer daran teilhaben und eventuell sogar davon profitieren möchte, muss damit leben, daß ich hin und wieder kleine Ausflüge in Bereiche mache, die nichts mit IT, Administration oder Softwareentwicklung zu tun haben.
Ich wünsche allen Lesern viel Spaß und hin und wieder einen kleinen AHA!-Effekt...
PS: Meine öffentlichen Codeberg-Repositories findet man hier.