-Copyright (C) 2017-2019 Michael Zucchi
+Copyright (C) 2017-2022 Michael Zucchi
See the section LICENSE for details.
Introduction
------------
-jjmpeg is a JNI based Java binding for the suite of libraries provided
-by FFmpeg.
-
-The original version of jjmpeg was released sometime in the distant
-past and has slowly got further and further behind the current FFmpeg
-version. This is a completely new implementation intended as a
-project to catch up with the current API.
+jjmpeg is a java.lang.foreign based Java binding for the suite of
+libraries provided by FFmpeg.
Compile
-------
-jdk (javac, jar) and native compiler tools (GNU make, cc, cpp, perl)
-must be in path. In addition the program cproto must be installed.
-JDK 13 is the baseline but JDK 9+ may work.
+Requires OpenJDK 19 and a compatible version of the ffmpeg sdk.
The prequisitve project notzed.nativez must also have previously been
-compiled and will be automatically used if it is present in the
-directory above this one (../nativez/).
+compiled to the 'sdk' target and will be automatically used if it is
+present in the directory above this one (../nativez/), as configured
+in config.make. The jdk-foriegn branch must be used with this version.
First copy config.make.in to config.make and adjust configuration
parameters.
`bin/<target>/lib/<module>.jar' Modular jar for ide.
`bin/<target>/lib/*.so' Platform specific
-`bin/<target>/bin/*.dll' runtime libraries.
+`bin/<target>/bin/*.dll' runtime libraries
`bin/<target>/jmods/<module>.jmod' Target specific .jmod.
NetBeans
--------
-Before the source can be easily edited in NetBeans one must
-first run the makefile to generate any auto-generated files.
+Before the source can be easily edited in NetBeans one must first run
+the makefile to generate any auto-generated files. A full build may
+be used but at the least the 'gen' target must be built.
$ make gen
Development
-----------
-Some notes on internals.
-
-Java Native Interface
-- - - - - - - - - - -
-
-All jj-*.c files are independent of one another, they may only
-reference functions and variables from jjmpeg.c.
-
-The native interface is implemented using the notzed.nativez project.
-This allows it to support full garbage collection of all relevant
-objects. In some cases this currently requires references to be
-manually kept to ensure they survive for the relevant C side objects.
-All objects are unique (and keyed) based on their C pointer address.
-
-If used with care, objects can also be manually released safely.
-
-Unlike earlier versions of jjmpeg, version 3+ is implemented almost
-entirely using hand-rolled Java and macro-assisted C source-code.
-Also unlike earlier versions almost all code is written directly in C
-with only skeleton Java wrappers of native method signatures. This
-requires less work and produces smaller code than previous versions.
+This uses notzed.nativez foriegn-abi branch implementation to create a
+custom 'object oriented' binding directly from ffmpeg headers.
-To further reduce code-size most initialisation is implemented via
-tables.
+Only a small subset of the API has been converted so far, enough to
+build the examples.
Enumerations
- - - - - -
-A set of small(ish) perl scripts are used to extract various
-enumeration and #define tables directly from the FFmpeg development
-header files. They generate interfaces which are "implemented" to add
-the defines to the desired structures. These reside in the scripts
-directory.
-
-All enumerations were converted to final integer values for
+All enumerations are converted to final integer values for
consistency. Where the corresponding functions exist, static
valueOf() and toString() are available for obvious purposes.
-The perl scripts generate C code which is then compiled and executed
-to create the Java interfaces.
-
Exceptions
-- - - - -
-
-Exceptions fall into two categories, AVIOException and AVException,
-both checked exceptions. The former derives from IOException for
-simplicity.
+- - - - -
-They contain the error code (errno and AVERROR defines) and a
-description.
-
-Where it makes sense other Java checked and unchecked exceptions such
-as FileNotFoundException or OutOfMemoryError are also thrown.
+These are not implemented (yet?). Error return values need to be
+checked and these have not been fully implemented in the high level
+interfaces either.
Linkage
- - - -
-All libraries are dynamically linked at runtime. Missing functions
-result in an initialisation failure.
-
-Method signatures are automatically extracted from headers via the
-script src/notzed.jjmpeg/jni/extract-proto.pl based on per-file .def
-files. This also generates a table for initialising the pointers.
+All libraries are dynamically linked via java.lang.foriegn mechanisms.
Build System
- - - - - -
JJMediaWriter. These have been simplified somewhat from the previous
implementation, but it takes less code to use them anyway.
-Most functions that should throw exceptions do although that which
-they throw may be tweaked.
-
-Many accessors have been implemented, in some cases they have been
-Javaised or NotZedised (e.g. setFlags(mask, value)) for a cleaner
-interface. Because of the complexity of the libraries and objects it
-is not always clear which members need to be accessed by user code so
-some of the choices are arbitrary. Or I just gave up part way
-through. They are easy to add.
+Exceptions are not implemented at this time.
There are two sub-modules notzed.jjmpeg.fx and notzed.jjmpeg.awt which
supply the JavaFX and Swing/AWT specific functionality.
NATIVEZ_HOME=../nativez/bin/$(TARGET)
JAVAMODPATH = $(JAVAFX_HOME)/lib $(NATIVEZ_HOME)/lib
-JAVACFLAGS +=
+JAVACFLAGS += --enable-preview --source 19
JAVAC ?= $(JAVA_HOME)/bin/javac
JAR ?= $(JAVA_HOME)/bin/jar
jar.compress=false
javac.classpath=
# Space-separated list of extra javac options
-javac.compilerargs=-Xlint:unchecked
+javac.compilerargs=-Xlint:unchecked --enable-preview
javac.deprecation=false
javac.external.vm=false
javac.modulepath=\
javac.processormodulepath=
javac.processorpath=\
${javac.classpath}
-javac.source=18
-javac.target=18
+javac.source=19
+javac.target=19
javac.test.classpath=\
${javac.classpath}
javac.test.modulepath=\
# Space-separated list of JVM arguments used when running the project.
# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
# To set system properties for unit tests define test-sys-prop.name=value:
-run.jvmargs=--enable-native-access notzed.nativez,notzed.jjmpeg
+run.jvmargs=--enable-native-access notzed.nativez,notzed.jjmpeg --enable-preview
run.modulepath=\
${javac.modulepath}:\
${build.modules.dir}
import au.notzed.jjmpeg.AVSampleReader;
import au.notzed.jjmpeg.io.JJMediaReader;
import java.io.IOException;
+import java.lang.foreign.MemorySession;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
-import jdk.incubator.foreign.ResourceScope;
/**
* Plays audio files.
return;
}
- try (ResourceScope scope = ResourceScope.newConfinedScope();
+ try (MemorySession scope = MemorySession.openConfined();
JJMediaReader mr = new JJMediaReader(path, scope)) {
JJMediaReader.JJReaderAudio as = mr.openDefaultStream(JJMediaReader.TYPE_AUDIO);
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
+import java.lang.foreign.MemorySession;
import java.util.IllegalFormatException;
import javax.imageio.ImageIO;
-import jdk.incubator.foreign.ResourceScope;
/**
* Export a video as frames.
System.err.println("Unknown extension");
type = format.substring(dot + 1).toLowerCase();
- try (ResourceScope scope = ResourceScope.newConfinedScope();
+ try (MemorySession scope = MemorySession.openConfined();
JJMediaReader mr = new JJMediaReader(path, scope)) {
int index = 0;
JJMediaReader.JJReaderVideo vs = mr.openDefaultStream(JJMediaReader.TYPE_VIDEO);
import java.io.FileNotFoundException;
import java.io.IOException;
import static java.lang.Double.min;
+import java.lang.foreign.MemorySession;
import java.time.Instant;
import java.time.LocalTime;
import java.time.ZoneId;
import javafx.stage.FileChooser;
import javafx.stage.Screen;
import javafx.stage.Stage;
-import jdk.incubator.foreign.ResourceScope;
/**
* A simple JavaFX demo which plays a video.
* @param path
*/
void reader(String path) {
- try (ResourceScope scope = ResourceScope.newConfinedScope();
+ try (MemorySession scope = MemorySession.openConfined();
JJMediaReader mr = new JJMediaReader(path, scope)) {
JJMediaReader.JJReaderVideo vs = mr.openDefaultStream(JJMediaReader.TYPE_VIDEO);
import au.notzed.jjmpeg.AVPixelFormat;
import au.notzed.jjmpeg.io.JJMediaReader;
import java.io.IOException;
-import jdk.incubator.foreign.ResourceScope;
+import java.lang.foreign.MemorySession;
/**
* A simple video scanner.
String path = args[0];
- try (ResourceScope scope = ResourceScope.newConfinedScope();
+ try (MemorySession scope = MemorySession.openConfined();
JJMediaReader mr = new JJMediaReader(path, scope)) {
int count = 0;
JJMediaReader.JJReaderVideo vs = mr.openDefaultStream(JJMediaReader.TYPE_VIDEO);
package au.notzed.jjmpeg;
import au.notzed.nativez.*;
+import java.lang.foreign.MemorySession;
import java.util.logging.Logger;
-import jdk.incubator.foreign.ResourceScope;
/**
*
System.loadLibrary("avutil");
AVUtil.setLogger(AVUtil.createAVLogger(Logger.getLogger("notzed.jjmpeg")),
- ResourceScope.globalScope());
+ MemorySession.global());
}
/**
package au.notzed.jjmpeg;
import java.nio.Buffer;
-import java.nio.ByteBuffer;
import java.nio.BufferOverflowException;
-import jdk.incubator.foreign.*;
import au.notzed.nativez.*;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.MemorySession;
public class FramePixelReader implements AVPixelReader {
final int dwidth, dheight, flags;
int dfmt = -1;
- ResourceScope scope = ResourceScope.newImplicitScope();
+ MemorySession scope = MemorySession.openImplicit();
MemorySegment seg;
public FramePixelReader(AVFrame frame, int dwidth, int dheight, int flags) {
public <T extends Buffer> void getPixels(int y, int h, int fmt, T buffer, int scanlineStride) {
if (buffer.isDirect())
- getPixels(y, h, fmt, MemorySegment.ofByteBuffer((ByteBuffer)buffer), scanlineStride);
+ getPixels(y, h, fmt, MemorySegment.ofBuffer(buffer), scanlineStride);
else
- getPixelsHeap(y, h, fmt, MemorySegment.ofByteBuffer((ByteBuffer)buffer), scanlineStride);
+ getPixelsHeap(y, h, fmt, MemorySegment.ofBuffer(buffer), scanlineStride);
}
public void getPixels(int y, int h, int fmt, byte[] buffer, int offset, int scanlineStride) {
import au.notzed.nativez.Frame;
import au.notzed.nativez.HandleArray;
import java.io.FileNotFoundException;
+import java.lang.foreign.MemorySession;
import java.util.AbstractList;
import java.util.List;
import java.util.logging.Logger;
-import jdk.incubator.foreign.ResourceScope;
/**
* High level interface for scanning audio and video frames.
* @see #getStreams
* @see #readFrame
*/
- public JJMediaReader(String name, ResourceScope scope) throws AVIOException, FileNotFoundException {
+ public JJMediaReader(String name, MemorySession scope) throws AVIOException, FileNotFoundException {
this(name, null, null, scope);
}
- public JJMediaReader(String name, AVInputFormat fmt, HandleArray<AVDictionary> options, ResourceScope scope) throws AVIOException, FileNotFoundException {
+ public JJMediaReader(String name, AVInputFormat fmt, HandleArray<AVDictionary> options, MemorySession scope) throws AVIOException, FileNotFoundException {
this(AVFormatContext.openInput(name, fmt, options, scope));
}
import au.notzed.jjmpeg.AVMediaType;
import au.notzed.jjmpeg.AVSampleFormat;
import au.notzed.nativez.Frame;
+import java.lang.foreign.MemorySession;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
-import jdk.incubator.foreign.ResourceScope;
/**
* High level media file writer interface.
*
* @param filename
*/
- public JJMediaWriter(String filename, ResourceScope scope) throws AVIOException {
+ public JJMediaWriter(String filename, MemorySession scope) throws AVIOException {
this.filename = filename;
oc = AVFormatContext.allocOutputContext(null, null, filename, scope);
};
}
- public static void setLogger(AVLogger log, ResourceScope scope) {
+ public static void setLogger(AVLogger log, MemorySession scope) {
AVUtil.av_log_set_callback(AVLogFunc.upcall((avcl, level, fmt, valist) -> {
try (Frame frame = Frame.frame()) {
ByteArray line = ByteArray.createArray(1024, frame);