VideoPlay demo enhancements
authorMichael Zucchi <michael@swordfish.com.au>
Sun, 12 May 2019 09:14:00 +0000 (18:44 +0930)
committerMichael Zucchi <michael@swordfish.com.au>
Sun, 12 May 2019 09:14:00 +0000 (18:44 +0930)
Make sure jni library is loaded for non-AVObject derived static methods.
Checkpoint ongoing work on doc improvements
Checkpoint ongoing work on accessor cleanup
Auto-generate some of the AVIOContext flags
AVIOContext improvements
JJMediaWriter improvements
Attach all threads as daemons in jni
Change AVPacket interface for accessing data buffer

28 files changed:
src/notzed.jjmpeg.demo/classes/au/notzed/jjmpeg/demo/video/VideoPlay.java
src/notzed.jjmpeg/classes/au/notzed/jjmpeg/AVChannelLayout.java
src/notzed.jjmpeg/classes/au/notzed/jjmpeg/AVCodec.java
src/notzed.jjmpeg/classes/au/notzed/jjmpeg/AVCodecContext.java
src/notzed.jjmpeg/classes/au/notzed/jjmpeg/AVCodecID.java
src/notzed.jjmpeg/classes/au/notzed/jjmpeg/AVError.java
src/notzed.jjmpeg/classes/au/notzed/jjmpeg/AVFormatContext.java
src/notzed.jjmpeg/classes/au/notzed/jjmpeg/AVIOContext.java
src/notzed.jjmpeg/classes/au/notzed/jjmpeg/AVIOException.java
src/notzed.jjmpeg/classes/au/notzed/jjmpeg/AVMediaType.java
src/notzed.jjmpeg/classes/au/notzed/jjmpeg/AVObject.java
src/notzed.jjmpeg/classes/au/notzed/jjmpeg/AVPacket.java
src/notzed.jjmpeg/classes/au/notzed/jjmpeg/AVPixelFormat.java
src/notzed.jjmpeg/classes/au/notzed/jjmpeg/AVRational.java
src/notzed.jjmpeg/classes/au/notzed/jjmpeg/AVSampleFormat.java
src/notzed.jjmpeg/classes/au/notzed/jjmpeg/AVUtil.java
src/notzed.jjmpeg/classes/au/notzed/jjmpeg/io/JJMediaReader.java
src/notzed.jjmpeg/classes/au/notzed/jjmpeg/io/JJMediaWriter.java
src/notzed.jjmpeg/gen/extract-defines.pl
src/notzed.jjmpeg/gen/gen.make
src/notzed.jjmpeg/jni/jj-avcodeccontext.c
src/notzed.jjmpeg/jni/jj-aviocontext.c
src/notzed.jjmpeg/jni/jj-aviocontext.def
src/notzed.jjmpeg/jni/jj-avpacket.c
src/notzed.jjmpeg/jni/jj-avpacket.def
src/notzed.jjmpeg/jni/jj-avutil.c
src/notzed.jjmpeg/jni/jjmpeg-jni.c
src/notzed.jjmpeg/jni/jjmpeg.h

index a2252d8..112c761 100644 (file)
@@ -24,21 +24,25 @@ import au.notzed.jjmpeg.io.JJMediaReader;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import static java.lang.Double.min;
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import java.time.Instant;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.List;
-import java.util.TimeZone;
 import java.util.concurrent.CountDownLatch;
 import javafx.application.Application;
 import javafx.application.Platform;
 import javafx.beans.InvalidationListener;
 import javafx.beans.Observable;
 import javafx.geometry.Insets;
+import javafx.geometry.Rectangle2D;
 import javafx.scene.Scene;
 import javafx.scene.control.Alert;
 import javafx.scene.control.ButtonType;
 import javafx.scene.image.ImageView;
 import javafx.scene.image.WritableImage;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyCodeCombination;
 import javafx.scene.layout.AnchorPane;
 import javafx.scene.layout.Background;
 import javafx.scene.layout.BackgroundFill;
@@ -47,6 +51,7 @@ import javafx.scene.paint.Color;
 import javafx.scene.text.Text;
 import javafx.scene.transform.Scale;
 import javafx.scene.transform.Translate;
+import javafx.stage.Screen;
 import javafx.stage.Stage;
 
 /**
@@ -87,7 +92,7 @@ public class VideoPlay extends Application {
        WritableImage image;
        Text label;
        Stage stage;
-       SimpleDateFormat time;
+       DateTimeFormatter time;
        long time0 = -1;
        long stamp0;
 
@@ -96,8 +101,7 @@ public class VideoPlay extends Application {
                height = vs.getHeight();
                frameReader = new FXPixelReader(vs.getFrame().getPixelReader(width, height, 0));
 
-               time = new SimpleDateFormat("HH:mm:ss.SSS");
-               time.setTimeZone(TimeZone.getTimeZone("GMT"));
+               time = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
 
                Platform.runLater(() -> {
                        label = new Text();
@@ -115,7 +119,20 @@ public class VideoPlay extends Application {
 
                        ap.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
 
-                       Scene scene = new Scene(ap, width, height);
+                       Rectangle2D bounds = Screen.getPrimary().getVisualBounds();
+                       double swidth = bounds.getWidth() - 32;
+                       double sheight = bounds.getHeight() - 32;
+                       Scene scene;
+                       if (width > swidth || height > sheight) {
+                               double scalex = swidth / width;
+                               double scaley = sheight / height;
+
+                               swidth = scalex < scaley ? swidth : width * scaley;
+                               sheight = scalex < scaley ? height * scalex : sheight;
+                               scene = new Scene(ap, swidth, sheight);
+                       } else {
+                               scene = new Scene(ap, width, height);
+                       }
 
                        stage = new Stage();
                        stage.setScene(scene);
@@ -145,6 +162,12 @@ public class VideoPlay extends Application {
 
                        ap.widthProperty().addListener(fitVideo);
                        ap.heightProperty().addListener(fitVideo);
+                       fitVideo.invalidated(null);
+
+                       scene.getAccelerators().put(new KeyCodeCombination(KeyCode.Q), () -> {
+                               cancel = true;
+                               stage.close();
+                       });
                });
        }
 
@@ -161,7 +184,8 @@ public class VideoPlay extends Application {
         * @param stamp
         */
        void videoFrame(JJMediaReader.JJReaderVideo vs, long stamp) {
-               String ts = time.format(new Date(stamp));
+               Instant x = Instant.ofEpochMilli(stamp + vs.getStartMS());
+               String ts = time.format(LocalTime.ofInstant(x, ZoneId.of("UTC")));
                CountDownLatch latch = new CountDownLatch(1);
 
                // Display the frame.  Must run on javafx thread.
@@ -196,9 +220,11 @@ public class VideoPlay extends Application {
         * @param x
         */
        void videoError(IOException x) {
-               Alert a = new Alert(Alert.AlertType.ERROR, x.getLocalizedMessage(), ButtonType.CLOSE);
-               a.showAndWait();
-               Platform.exit();
+               Platform.runLater(() -> {
+                       Alert a = new Alert(Alert.AlertType.ERROR, x.getLocalizedMessage(), ButtonType.CLOSE);
+                       a.showAndWait();
+                       Platform.exit();
+               });
        }
 
        volatile boolean cancel = false;
@@ -226,6 +252,7 @@ public class VideoPlay extends Application {
                        }
 
                } catch (AVIOException | FileNotFoundException ex) {
+                       ex.printStackTrace();
                        videoError(ex);
                }
        }
index aed143a..9a8bbd3 100644 (file)
@@ -20,7 +20,11 @@ package au.notzed.jjmpeg;
 
 /**
  */
-public class AVChannelLayout extends AVObject implements AVChannelLayoutBits {
+public class AVChannelLayout implements AVChannelLayoutBits {
+
+       static {
+               AVObject.init();
+       }
 
        /**
         * Call av_get_channel_layout(name)
@@ -48,7 +52,4 @@ public class AVChannelLayout extends AVObject implements AVChannelLayoutBits {
         */
        public static native long getDefaultLayout(int nchannels);
 
-       private AVChannelLayout(long p) {
-               super(p);
-       }
 }
index 38fe130..cd1356e 100644 (file)
@@ -51,15 +51,34 @@ public class AVCodec extends AVObject implements AVCodecBits, AVDiscardBits {
 
        public static native AVCodec findEncoder(String name);
 
-       /*
-       Accessors
+       /**
+        * Get AVCodec.name.
+        *
+        * @return
         */
        public native String getName();
 
+       /**
+        * Get AVCodec.long_name.
+        *
+        * @return
+        */
        public native String getLongName();
 
+       /**
+        * Get AVCodec.type.
+        *
+        * @return
+        * @see AVMediaType
+        */
        public native int getType();
 
+       /**
+        * Get AVCodec.id.
+        *
+        * @return
+        * @see AVCodecID
+        */
        public native int getID();
 
        public native int getCapabilities();
index 9e5e8e5..d3fae47 100644 (file)
@@ -21,6 +21,14 @@ package au.notzed.jjmpeg;
 /**
  * AVCodecContext accessors.
  * <p>
+ * The accessors here are currently implemented as direct struct accessors but
+ * most fields may also be accessed by the {@link AVOptions} accessor methods. The
+ * AVOptions methods are somewhat inconvenient but provide additional
+ * validation checks for setting options.
+ * <p>
+ * Note that there are many other options, these are documented in libavcodec/avcodec.h and the ffmpeg
+ * source in libavcodec/options_table.h. This can also be retrieved at runtime
+ * via
  */
 public class AVCodecContext extends AVOptions implements AVCodecContextBits {
 
@@ -28,46 +36,84 @@ public class AVCodecContext extends AVOptions implements AVCodecContextBits {
                super(p);
        }
 
+       /**
+        * Free the context.
+        * <p>
+        * Calls avcodec_free_context().
+        *
+        * @param p
+        */
        private static native void release(long p);
 
-       /*
-        * Methods
-        */
        /**
+        * Allocate and initialise an AVCodecContext.
+        * <p>
+        * Calls avcodec_alloc_context3().
         *
-        * @param codec
+        * @param codec Codec used to initialise parameters, may be null.
         * @return
         */
        public static native AVCodecContext allocContext(AVCodec codec);
 
-       // avcodec_open2
+       /**
+        * Open a context.
+        * <p>
+        * Calls avcodec_open2().
+        *
+        * @param codec Codec used. It must match the one used in allocContext().
+        * @param options Codec options.
+        * @throws AVIOException is thrown iv avcodec_open2 returns an error.
+        */
        public native void open(AVCodec codec, AVDictionary options) throws AVIOException;
 
+       /**
+        * Flush internal buffers.
+        * <p>
+        * Used to reset codec state, for example after a seek.
+        * <p>
+        * Calls avcodec_flush_buffers().
+        */
        public native void flushBuffers();
 
+       /**
+        * Align dimensions suitable for codec.
+        * <p>
+        * Calls avcodec_align_dimensions().
+        *
+        * @param input Input dimensions.
+        * @return Modified dimensions.
+        */
        public native AVSize alignDimensions(AVSize input);
-       
+
        /**
-        * Calls avcodec_send_packet()
+        * Submit raw packet to a decoder.
+        * <p>
+        * Packets are submitted to a decoder which generates output frames.
         * <p>
         * If the return value is EAGAIN or AVERROR_EOF then this function
         * returns false, for all other error values it throws an exception.
+        * <p>
+        * Calls avcodec_send_packet().
         *
         * @param pkt
         * @return true if the packet was accepted, false if a frame must be received first.
         * @throws AVIOException
+        * @see #receiveFrame(au.notzed.jjmpeg.AVFrame)
         */
        public native boolean sendPacket(AVPacket pkt) throws AVIOException;
 
        /**
-        * Calls avcodec_receive_frame().
+        * Retrieve a decoded frame from a decoder.
         * <p>
-        * If the return value is EAGAIN or AVERROR_EOF then this function
-        * returns false, for all other error values it throws an exception.
+        * If the return value from avcodec_receive_frame() is EAGAIN or AVERROR_EOF
+        * then this function returns false, for all other error values it throws an exception.
+        * <p>
+        * Calls avcodec_receive_frame().
         *
         * @param frame
         * @return Returns true if the frame is complete, false otherwise.
         * @throws AVIOException
+        * @see #sendPacket(au.notzed.jjmpeg.AVPacket)
         */
        public native boolean receiveFrame(AVFrame frame) throws AVIOException;
 
@@ -95,203 +141,346 @@ public class AVCodecContext extends AVOptions implements AVCodecContextBits {
         */
        public native boolean receivePacket(AVPacket pkt) throws AVIOException;
 
-       /*
-        * Accessors
+       /**
+        * Get encoding/decoding AVCodecContext.codec_type.
+        *
+        * @return
+        * @see AVMediaType
         */
        public native int getCodecType();
 
+       /**
+        * Set encoding/decoding AVCodecContext.codec_type.
+        *
+        * @param val
+        * @see AVMediaType
+        */
        public native void setCodecType(int val);
 
+       /**
+        * Get encoding/decoding AVCodecContext.codec_id.
+        *
+        * @return
+        * @see AVCodecID
+        */
        public native int getCodecID();
 
+       /**
+        * Set encoding/decoding AVCodecContext.codec_id.
+        *
+        * @param val
+        * @see AVCodecID
+        */
        public native void setCodecID(int val);
 
-       public native int getCodecTag();
-
-       public native void setCodecTag(int val);
-
+       /**
+        * Get encoding/decoding AVCodecContext.bit_rate.
+        * <p>
+        * AVOption int "b" or "ab"
+        *
+        * @return
+        */
        public native long getBitRate();
 
-       public native void setBitRate(long val);
-
-       public native int getBitRateTolerance();
-
-       public native void setBitRateTolerance(int val);
+       /**
+        * Set encoding/decoding AVCodecContext.bit_rate.
+        * <p>
+        * AVOption int "b" or "ab"
+        *
+        * @param bitrate
+        */
+       public native void setBitRate(long bitrate);
 
+       /**
+        * Get encoding AVCodecContext.global_quality.
+        * <p>
+        * AVOption int "global_quality".
+        *
+        * @return
+        */
        public native int getGlobalQuality();
 
-       public native void setGlobalQuality(int val);
-
-       public native int getCompressionLevel();
-
-       public native void setCompressionLevel(int val);
+       /**
+        * Set AVCodecContext.global_quality.
+        * <p>
+        * AVOption int "global_quality".
+        *
+        * @param quality
+        */
+       public native void setGlobalQuality(int quality);
 
        /**
-        * Get the contents of flags and flags2 together as a single 64-bit value.
+        * Get the encoding/decoding AVCodecContext.flags and AVCodecContext.flags2.
+        * <p>
+        * This combines .flags and .flags2 into a single 64-bit flag set. The
+        * AV_CODE_FLAG2_* constants have been shifted appropriately.
+        * <p>
+        * AVOption int "flag", also specific flags "unaligned", "mv4", and so on.
         *
         * @return
         */
        public native long getFlags();
 
        /**
-        * Set or clear flags and flags2.
+        * Adjust the encoding/decoding AVCodecContext.flags and AVCodecContext.flags2.
+        * <p>
+        * This performs a combined get and set operation on the flags allowing any cobimation
+        * of flags to be set in a single call.
+        * <p>
+        * AVOption int "flag", also specific flags "unaligned", "mv4", and so on.
         *
-        * @param mask Mask of bits to change. Only flags with a bit set here will be altered.
+        * @param mask Mask of bits to change. Flags with a bit set here will be set to the values in flags.
         * @param flags Value to set bits to.
+        * @see #getFlags() for an explanation of the flag values.
         */
        public native void setFlags(long mask, long flags);
 
        /**
-        * Get the timebase.
+        * Get the encoding AVCodecContext.time_base.
         * <p>
-        * Note that the return value is a value type (copy) not a reference.
+        * AVOption rational (q) "time_base".
         *
-        * @return
+        * @return A copy of the time_base (adjustments will not be carried through).
         */
        public native AVRational getTimeBase();
 
-       public native void setTimeBase(AVRational val);
-
-       public native int getTicksPerFrame();
-
-       public native void setTicksPerFrame(int val);
+       /**
+        * Set the encoding AVCodecContext.time_base.
+        * <p>
+        * A time_base must always be set for encoding.
+        * <p>
+        * AVOption rational (q) "time_base".
+        *
+        * @param timebase
+        */
+       public native void setTimeBase(AVRational timebase);
 
+       /**
+        * Get the encoding/decoding AVCodecContext.delay.
+        * <p>
+        * AVOption int "delay".
+        *
+        * @return
+        */
        public native int getDelay();
 
-       /*
-               Video only options
+       /**
+        * Get video encoding/decoding AVCodecContext.width.
+        * <p>
+        * AVOption image size "video_size".
+        *
+        * @return
         */
        public native int getWidth();
 
-       public native void setWidth(int val);
+       /**
+        * Set video encoding/decoding AVCodecContext.width.
+        * <p>
+        * AVOption image size "video_size".
+        *
+        * @param width
+        */
+       public native void setWidth(int width);
 
+       /**
+        * Get video encoding/decoding AVCodecContext.height.
+        * <p>
+        * AVOption image size "video_size".
+        *
+        * @return
+        */
        public native int getHeight();
 
-       public native void setHeight(int val);
+       /**
+        * Set video encoding/decoding AVCodecContext.width.
+        * <p>
+        * AVOption image size "video_size".
+        *
+        * @param height
+        */
+       public native void setHeight(int height);
 
+       /**
+        * Get video encoding/decoding AVCodecContext.sample_aspect_ratio.
+        * <p>
+        * AVOption rational "aspect" or "sar".
+        *
+        * @return
+        */
        public native AVRational getAspectRatio();
 
-       public native void setAspectRatio(AVRational r);
+       /**
+        * Set video encoding/decoding AVCodecContext.sample_aspect_ratio.
+        * <p>
+        * AVOption rational "aspect" or "sar".
+        *
+        * @param ratio
+        */
+       public native void setAspectRatio(AVRational ratio);
 
-       // codedWidth, codedHeight
+       /**
+        * Get video encoding AVCodecContext.gop_size.
+        * <p>
+        * AVOption int "g".
+        *
+        * @return
+        */
        public native int getGOPSize();
 
+       /**
+        * Set video encoding AVCodecContext.gop_size.
+        * <p>
+        * AVOption int "g".
+        *
+        * @param val
+        */
        public native void setGOPSize(int val);
 
+       /**
+        * Get video encoding/decoding AVCodecContext.pix_fmt.
+        * <p>
+        * AVOption pixel format "pixel_format".
+        *
+        * @return
+        * @see AVPixelFormat
+        */
        public native int getPixelFormat();
 
-       public native void setPixelFormat(int val);
-
-       //
-       public native int getMaxBFrames();
-
-       public native void setMaxBFrames(int val);
-
-       public native float getBQuantFactor();
-
-       public native void setBQuantFactor(float val);
-
-       public native float getBQuantOffset();
-
-       public native void setBQuantOffset(float val);
-
-       public native boolean hasBFrames();
-
-       public native float getIQuantFactor();
-
-       public native void setIQuantFactor(float val);
-
-       public native float getIQuantOffset();
-
-       public native void setIQuantOffset(float val);
-// And a whole lot more ...
-//     
-
-       public native int getMBDecision();
-
-       public native void setMBDecision(int val);
-// ...
+       /**
+        * Set video encoding/decoding AVCodecContext.pix_fmt.
+        * <p>
+        * AVOption pixel format "pixel_format".
+        *
+        * @param format
+        * @see AVPixelFormat
+        */
+       public native void setPixelFormat(int format);
 
-       /*
-               Audtio only options
+       /**
+        * Get audio encoding/decoding AVCodecContext.sample_rate.
+        * <p>
+        * AVOption int "ar".
+        *
+        * @return
         */
        public native int getSampleRate();
 
-       public native void setSampleRate(int val);
+       /**
+        * Set audio encoding/decoding AVCodecContext.sample_rate.
+        * <p>
+        * AVOption int "ar".
+        *
+        * @param hertz
+        */
+       public native void setSampleRate(int hertz);
 
+       /**
+        * Get audio encoding/decoding AVCodecContext.channels.
+        * <p>
+        * AVOption int "ac".
+        *
+        * @return
+        */
        public native int getNumChannels();
 
-       public native void setNumChannels(int val);
+       /**
+        * Set audio encoding/decoding AVCodecContext.channels.
+        * <p>
+        * AVOption int "ac".
+        *
+        * @param nchannels
+        */
+       public native void setNumChannels(int nchannels);
 
+       /**
+        * Get audio encoding/decoding AVCodecContext.sample_fmt.
+        *
+        * @return
+        * @see AVSampleFormat
+        */
        public native int getSampleFormat();
 
-       public native void setSampleFormat(int val);
+       /**
+        * Set audio encoding/decoding AVCodecContext.sample_fmt.
+        *
+        * @param format
+        * @see AVSampleFormat
+        */
+       public native void setSampleFormat(int format);
 
+       /**
+        * Get audio encoding/decoding AVCodecContext.frame_size.
+        * <p>
+        * AVOption int "frame_size".
+        *
+        * @return
+        */
        public native int getFrameSize();
 
-       public native void setFrameSize(int val);
-
+       /**
+        * Get audio encoding/decoding AVCodecContext.frame_number.
+        * <p>
+        * AVOption int "frame_number".
+        *
+        * @return
+        */
        public native int getFrameNumber();
 
-// int block_align
-// int cutoff
+       /**
+        * Get audio encoding/decoding AVCodecContext.channel_layout.
+        * <p>
+        * AVOption channel layout "channel_layout".
+        *
+        * @return
+        * @see AVChannelLayout
+        */
        public native long getChannelLayout();
 
-       public native void setChannelLayout(long val);
+       /**
+        * Set audio encoding/decoding AVCodecContext.channel_layout.
+        * <p>
+        * AVOption channel layout "channel_layout".
+        *
+        * @param layout
+        * @see AVChannelLayout
+        */
+       public native void setChannelLayout(long layout);
 
+       /**
+        * Get audio decoding AVCodecContext.request_channel_layout.
+        * <p>
+        * AVOption channel layout "request_channel_layout".
+        *
+        * @return
+        * @see AVChannelLayout
+        */
        public native long getRequestChannelLayout();
 
-       public native void setRequestChannelLayout(long val);
-
-//  enum AVAudioServiceType audio_service_type
-//     enum AVSampleFormat request_sample_fmt;
-
-       /*
-       Encoding parameters
+       /**
+        * Set audio decoding AVCodecContext.request_channel_layout.
+        * <p>
+        * AVOption channel layout "request_channel_layout".
+        *
+        * @param layout
+        * @see AVChannelLayout
         */
-// float qcompress
-// float qblur
-// float qmin
-// float qmax
-// float max_qdiff
-// int rc_buffer_size
-// int rc_override_count
-// RCOverride *rc_override
-// int64_t rc_max_rate
-// int64_t rc_min_rate
-// ... more rc stuff
-// ...
-// int trellis
-// ...
-// int workaround_bugs
-       public native int getStrictStdCompliance();
-
-       public native void setStrictStdCompliance(int val);
-
-       public native int getErrorConcealment();
-
-       public native void setErrorConcealment(int val);
-// int debug
-
-       public native int getErrorRecognition();
-
-       public native void setErrorRecognition(int val);
-
-       public native int getDCTAlgo();
-
-       public native void setDCTAlgo(int val);
-
-       public native int getIDCTAlgo();
-
-       public native void setIDCTAlgo(int val);
+       public native void setRequestChannelLayout(long layout);
 
+       /**
+        * Get encoding/decoding AVCodecContext.thread_count.
+        * <p>
+        * AVOption int "threads".
+        *
+        * @return
+        */
        public native int getThreadCount();
 
-       public native void setThreadCount(int val);
-// ...
-
-       public native int getProfile();
-
-       public native void setProfile(int val);
-// ...
+       /**
+        * Set encoding/decoding AVCodecContext.thread_count.
+        * <p>
+        * AVOption int "threads".
+        *
+        * @param nthreads
+        */
+       public native void setThreadCount(int nthreads);
 }
index 46b21b7..ab39bb8 100644 (file)
@@ -22,6 +22,10 @@ package au.notzed.jjmpeg;
  */
 public class AVCodecID implements AVCodecIDBits {
 
+       static {
+               AVObject.init();
+       }
+
        /**
         * Call avcodec_get_name(value)
         */
index b374db7..7367b51 100644 (file)
@@ -22,6 +22,10 @@ package au.notzed.jjmpeg;
  *
  */
 public class AVError implements AVErrorBits {
-       
+
+       static {
+               AVObject.init();
+       }
+
        public static native String toString(int errno);
 }
index 24eae74..f7f1d7e 100644 (file)
@@ -38,7 +38,6 @@ public class AVFormatContext extends AVOptions {
 
        // hmm?
        //public native void setOutputFormat(AVOutputFormat val);
-
        public native AVIOContext getIOContext();
 
        public native void setIOContext(AVIOContext val);
@@ -132,7 +131,7 @@ public class AVFormatContext extends AVOptions {
        // returns false on end of file
        public native boolean readFrame(AVPacket pkt) throws AVIOException;
 
-               public native void seekFrame(int stream_index, long timestamp, int flags) throws AVIOException;
+       public native void seekFrame(int stream_index, long timestamp, int flags) throws AVIOException;
 
        public native void seekFile(int stream_index, long min_ts, long ts, long max_ts, int flags) throws AVIOException;
 
@@ -153,6 +152,14 @@ public class AVFormatContext extends AVOptions {
        // returns true if flushed
        public native boolean writeFrame(AVPacket pktkt) throws AVIOException;
 
+       /**
+        * Write a frame to the output.
+        * <p>
+        * Calls av_interleaved_write_frame().
+        *
+        * @param pkt Packet containing data.
+        * @throws AVIOException on error.
+        */
        public native void interleavedWriteFrame(AVPacket pkt) throws AVIOException;
 
        public native void writeUncodedFrame(int stream_index, AVFrame frame) throws AVIOException;
index 3b0c499..ea1f893 100644 (file)
@@ -23,39 +23,25 @@ import java.nio.ByteBuffer;
 /**
  *
  */
-public class AVIOContext extends AVOptions {
+public class AVIOContext extends AVOptions implements AVIOContextBits {
+
+       /**
+        * Maintains references required for callbacks.
+        * <p>
+        * This is a {@link au.notzed.nativez.ReferenceZ} object.
+        */
+       Object opaque;
 
-       // TODO: autogen?
-       public static final int AVSEEK_SIZE = 0x10000;
-       public static final int AVSEEK_FORCE = 0x20000;
        //
        public static final int AVSEEK_SET = 0;
        public static final int AVSEEK_CUR = 1;
        public static final int AVSEEK_END = 2;
-       //
-       public static final int ALLOC_WRITE = 1;
-       public static final int ALLOC_STREAMED = 2;
-       // flags for open
-       public static final int AVIO_FLAG_READ = 1;
-       public static final int AVIO_FLAG_WRITE = 2;
-       public static final int AVIO_FLAG_READ_WRITE = 3;
-       // flags for alloc seekable
-       /**
-        * Seeking works like for a local file.
-        */
-       public static final int AVIO_SEEKABLE_NORMAL = (1 << 0);
-
        /**
-        * Seeking by timestamp with avio_seek_time() is possible.
-        */
-       public static final int AVIO_SEEKABLE_TIME = (1 << 1);
-       /**
-        * pseudo-flag for alloc() which sets writable to avio_context_create.
+        * If set then alloc() will pass write_flag=1 to avio_alloc_context().
         */
        public static final int AVIO_WRITABLE = (1 << 31);
        /**
-        * pseudo-flaga for alloc() which sets the direct indicator on the
-        * created context.
+        * If set, then alloc() will set AVIOContext.direct=1.
         */
        public static final int AVIO_DIRECT = (1 << 30);
 
@@ -65,27 +51,58 @@ public class AVIOContext extends AVOptions {
 
        private static native void release(long p);
 
-       //?public native int getSeekable();
        /**
-        * Calls avio_open.
+        * Create an AVIOContext backed by libavformat i/o.
         * <p>
-        * TODO: should it call avio_open2?
+        * Calls avio_open2().
         *
         * @param url
         * @param avio_flags AVIO_FLAGS_*.
+        * @param interrupt_cb If non-null, supplies interrupt-check function.
+        * @param options
+        * @return
+        * @throws au.notzed.jjmpeg.AVIOException
+        */
+       public static native AVIOContext open(String url, int avio_flags, AVIOInterrupt interrupt_cb, AVDictionary options) throws AVIOException;
+
+       /**
+        * Create an AVIOContext backed by libavformat i/o with no callback or options.
+        *
+        * @param url
+        * @param avio_flags
         * @return
+        * @throws AVIOException
         */
-       public static native AVIOContext open(String url, int avio_flags) throws AVIOException;
+       public static AVIOContext open(String url, int avio_flags) throws AVIOException {
+               return open(url, avio_flags, null, null);
+       }
 
        /**
+        * Create an AVIOContext backed by Java I/O.
         *
         * @param buffer_size
         * @param alloc_flags combination of AVIO_SEEKABLE_*, AVIO_WRITABLE, and AVIO_DIRECT.
-        * @param handler
+        * @param handler Functions which implement i/o.
         * @return
         */
        public static native AVIOContext alloc(int buffer_size, int alloc_flags, AVIOHandler handler);
 
+       /**
+        * Callback hook for interrupt checking.
+        */
+       public interface AVIOInterrupt {
+
+               /**
+                * Called during blocking operations.
+                *
+                * @return true if the blocking i/o should abort.
+                */
+               public boolean isInterrupted();
+       }
+
+       /**
+        * Callback hooks which allow Java I/O functions to provide I/O for FFmpeg.
+        */
        public interface AVIOHandler {
 
                /**
@@ -104,6 +121,13 @@ public class AVIOContext extends AVOptions {
                 */
                public int writePacket(ByteBuffer src);
 
+               /**
+                * Called to perform seek.
+                *
+                * @param offset
+                * @param whence AVSEEK_* value.
+                * @return
+                */
                public long seek(long offset, int whence);
        }
 }
index d31bb2e..e753419 100644 (file)
@@ -21,7 +21,9 @@ package au.notzed.jjmpeg;
 import java.io.IOException;
 
 /**
- *
+ * General exception for FFmpeg errors.
+ * <p>
+ * This contains the error code which triggered the exception.
  */
 public class AVIOException extends IOException {
 
index 9c88e45..0e32eba 100644 (file)
@@ -23,6 +23,10 @@ package au.notzed.jjmpeg;
  */
 public class AVMediaType implements AVMediaTypeBits {
 
+       static {
+               AVObject.init();
+       }
+
        /**
         * Call av_get_media_type_string()
         */
index 63ff264..0bb6d50 100644 (file)
@@ -41,7 +41,17 @@ public abstract class AVObject extends NativeZ {
 
                AVUtil.setLogger(AVUtil.createAVLogger(Logger.getLogger("notzed.jjmpeg")));
        }
-       
+
+       /**
+        * Initialise AVObject.
+        * <p>
+        * This function does nothing itself but ensures the native library is
+        * loaded and can be used by objects which DO NOT derive from AVObject
+        * but need to use native calls.
+        */
+       public static void init() {
+       }
+
        /**
         * Handy iterator for various uses in the library.
         */
index b218a38..50ff939 100644 (file)
@@ -45,7 +45,7 @@ public class AVPacket extends AVObject implements Cloneable {
         * Flag is used to discard packets which are required to maintain valid
         * decoder state but are not required for output and should be dropped
         * after decoding.
-        *
+        * <p>
         */
        public static final int AV_PKT_FLAG_DISCARD = 0x0004;
        /**
@@ -74,14 +74,23 @@ public class AVPacket extends AVObject implements Cloneable {
        /**
         * Retrieve the current data buffer pointer and size.
         * <p>
-        * This value should not be saved between calls which might update the packet.
-        * <p>
-        * TODO: do i even need this?
+        * This value must not be saved between calls which might update the packet.
         *
         * @return
         */
        public native ByteBuffer getData();
 
+       /**
+        * Resize then retrieve the current data buffer pointer and size.
+        * <p>
+        * This will call av_packet_grow() or av_packet_shrink() as necessary
+        * to set the data size, and will return a reference to it.
+        *
+        * @param size
+        * @return
+        */
+       public native ByteBuffer getData(long size);
+
        /**
         * Clear and free any data pointers and returns the packet to an initialised state.
         * <p>
@@ -105,6 +114,10 @@ public class AVPacket extends AVObject implements Cloneable {
 
        public native void setDTS(long val);
 
+       public native long getDuration();
+
+       public native void setDuration(long val);
+
        public native int getSize();
 
        public native int getStreamIndex();
index 65166dc..8c578ce 100644 (file)
@@ -22,6 +22,10 @@ package au.notzed.jjmpeg;
  */
 public class AVPixelFormat implements AVPixelFormatBits {
 
+       static {
+               AVObject.init();
+       }
+
        /**
         * Call av_get_pix_fmt(name)
         */
index 0dcac36..7dd1674 100644 (file)
@@ -35,6 +35,10 @@ public class AVRational extends Number implements Comparable<AVRational> {
                this.den = den;
        }
 
+       static {
+               AVObject.init();
+       }
+
        /**
         * Calls av_d2q
         *
@@ -112,7 +116,7 @@ public class AVRational extends Number implements Comparable<AVRational> {
         * Compare two rationals.
         *
         * @param b Second rational
-        * 
+        *
         * Taken from avutil/rational.c
         *
         * @return One of the following values:
index c5bd2e8..5210857 100644 (file)
@@ -22,15 +22,17 @@ package au.notzed.jjmpeg;
  */
 public class AVSampleFormat implements AVSampleFormatBits {
 
+       static {
+               AVObject.init();
+       }
+
        /**
         * Call av_get_sample_fmt(name)
         */
-
        public static native int valueOf(String name);
-       
+
        /**
         * Call av_get_sample_fmt_name(value)
         */
        public static native String toString(int value);
 }
-
index 5a42751..dce3c7d 100644 (file)
@@ -29,6 +29,10 @@ import java.util.logging.Logger;
  */
 public class AVUtil {
 
+       static {
+               AVObject.init();
+       }
+
        /**
         * Call av_log_set_callback() with the provided logger.
         *
index e891247..16aa7c5 100644 (file)
@@ -443,6 +443,15 @@ public class JJMediaReader implements AutoCloseable {
                        return duration;
                }
 
+               /**
+                * Retrieve stream start time in milliseconds.
+                *
+                * @return
+                */
+               public long getStartMS() {
+                       return startms;
+               }
+
                public boolean isOpened() {
                        return opened;
                }
index 66acaec..aff403c 100644 (file)
@@ -27,6 +27,7 @@
  */
 package au.notzed.jjmpeg.io;
 
+import au.notzed.jjmpeg.AVChannelLayout;
 import au.notzed.jjmpeg.AVCodec;
 import au.notzed.jjmpeg.AVCodecContext;
 import au.notzed.jjmpeg.AVFormatContext;
@@ -35,7 +36,6 @@ import au.notzed.jjmpeg.AVOutputFormat;
 import au.notzed.jjmpeg.AVPacket;
 import au.notzed.jjmpeg.AVRational;
 import au.notzed.jjmpeg.AVStream;
-import au.notzed.jjmpeg.AVCodecID;
 import au.notzed.jjmpeg.AVError;
 import au.notzed.jjmpeg.AVIOContext;
 import au.notzed.jjmpeg.AVPixelFormat;
@@ -47,16 +47,18 @@ import java.util.List;
 import java.util.logging.Logger;
 
 /**
- * High level av file writer interface
+ * High level av file writer interface.
  * <p>
- * @author notzed
+ * This is still work in progress and subject to change.
  */
-public class JJMediaWriter {
+public class JJMediaWriter implements AutoCloseable {
 
        List<JJWriterStream> streams = new ArrayList<>();
        String filename;
        AVFormatContext oc;
        AVIOContext output; // need to keep ref around
+       // writeTrailer must only be called if writeHeader was successful.
+       boolean writeTrailer;
 
        /**
         * Create a new stream for writing to a file.
@@ -66,16 +68,11 @@ public class JJMediaWriter {
         * and then call close().
         *
         * @param filename
-        * <p>
-        * @throws AVInvalidFormatException
         */
        public JJMediaWriter(String filename) throws AVIOException {
                this.filename = filename;
 
-               /* allocate the output media context */
                oc = AVFormatContext.allocContext(null, null, filename);
-
-               // Caller now calls addVideoStream, etc.
        }
 
        /**
@@ -90,177 +87,170 @@ public class JJMediaWriter {
        /**
         * The stream must be opened after adding streams and before writing to them.
         * <p>
-        * @throws AVInvalidFormatException
-        * @throws AVInvalidCodecException
         * @throws AVIOException
         */
        public void open() throws AVIOException {
                /* now that all the parameters are set, we can open the audio and
                 video codecs and allocate the necessary encode buffers */
-               for (JJWriterStream sd: streams) {
+               for (JJWriterStream sd : streams) {
                        sd.open();
                }
 
                /* open the output file, if needed */
                //if (!(format->flags & AVFMT_NOFILE)) {
-               output = AVIOContext.open(filename, AVIOContext.AVIO_FLAG_WRITE);
+               output = AVIOContext.open(filename, AVIOContext.AVIO_FLAG_WRITE, null, null);
                oc.setIOContext(output);
 
                /* write the stream header, if any */
                oc.writeHeader(null);
-       }
-
-       /**
-        * Add a stream using the default video codec for this stream.
-        * The streamid is the index of the stream in the streams list.
-        * <p>
-        * @param width
-        * @param height
-        * @param frame_rate
-        * @param bit_rate
-        * <p>
-        * @return
-        */
-       public JJWriterVideo addVideoStream(int width, int height, int frame_rate, long bit_rate) throws AVIOException {
-               return addVideoStream(getFormat().getVideoCodec(), streams.size(), width, height, 1, frame_rate, bit_rate);
-       }
-
-       public JJWriterVideo addVideoStream(int width, int height, int frame_rate_num, int frame_rate_den, long bit_rate) throws AVIOException {
-               return addVideoStream(getFormat().getVideoCodec(), streams.size(), width, height, frame_rate_num, frame_rate_den, bit_rate);
-       }
-
-       public JJWriterVideo addVideoStream(int codec_id, int streamid, int width, int height, int frame_rate_den, long bit_rate) throws AVIOException {
-               return addVideoStream(codec_id, streamid, width, height, 1, frame_rate_den, bit_rate);
+               writeTrailer = true;
        }
 
        /**
         * Add a video stream.
         * <p>
-        * @param codec_id
-        * @param width
-        * @param height
-        * @param frame_rate
-        * @param bit_rate
-        * <p>
+        * Creates and adds a video stream. The video stream size, pixel format, and bitrate must still be initialised.
+        *
+        * @param codec If null then use the format default codec. If non-null must be a video codec.
+        * @param streamid StreamID to create, or -1 to use the next available slot.
         * @return
-        * @throws AVInvalidStreamException
+        * @throws AVIOException
         */
-       public JJWriterVideo addVideoStream(int codec_id, int streamid, int width, int height, int frame_rate_num, int frame_rate_den, long bit_rate) throws AVIOException {
+       public JJWriterVideo addVideoStream(AVCodec codec, int streamid) throws AVIOException {
                AVCodecContext c;
+               JJWriterVideo vs;
                AVStream st;
 
-               // TODO: all this crap should probably go into JJWriterVideo constructor
-               AVCodec codec = AVCodec.findEncoder(codec_id);
+               if (codec == null) {
+                       codec = AVCodec.findEncoder(getFormat().getVideoCodec());
+                       if (codec == null)
+                               throw new AVIOException(AVError.AVERROR_STREAM_NOT_FOUND);
+               } else if (codec.getType() != AVMediaType.AVMEDIA_TYPE_VIDEO)
+                       throw new AVIOException(AVError.AVERROR_STREAM_NOT_FOUND);
 
                c = AVCodecContext.allocContext(codec);
-
                st = oc.newStream(codec);
-               if (st == null) {
+               if (st == null)
                        throw new AVIOException(AVError.AVERROR_STREAM_NOT_FOUND);
-               }
-               st.setID(streams.size());
 
-               c.setCodecID(codec_id);
-               c.setCodecType(AVMediaType.AVMEDIA_TYPE_VIDEO);
-               /* put sample parameters */
-               c.setBitRate(bit_rate);
+               st.setID(streamid == -1 ? streams.size() : streamid);
 
-               /* resolution must be a multiple of two */
-               c.setWidth(width);
-               c.setHeight(height);
-               /* time base: this is the fundamental unit of time (in seconds) in terms
-                of which frame timestamps are represented. for fixed-fps content,
-                timebase should be 1/framerate and timestamp increments should be
-                identically 1. */
-               st.setTimeBase(new AVRational(frame_rate_num, frame_rate_den));
-               c.setTimeBase(new AVRational(frame_rate_num, frame_rate_den));
-               c.setGOPSize(12);
-               /* emit one intra frame every twelve frames at most */
-
-               c.setPixelFormat(AVPixelFormat.AV_PIX_FMT_YUV420P);
-               if (codec_id == AVCodecID.AV_CODEC_ID_MPEG2VIDEO) {
-                       /* just for testing, we also add B frames */
-                       c.setMaxBFrames(2);
-               }
-               if (codec_id == AVCodecID.AV_CODEC_ID_MPEG1VIDEO) {
-                       /* Needed to avoid using macroblocks in which some coeffs overflow.
-                        This does not happen with normal video, it just happens here as
-                        the motion of the chroma plane does not match the luma plane. */
-                       c.setMBDecision(2);
-               }
-               // FIXME: some formats want stream headers to be separate
-               if ((oc.getOutputFormat().getFlags() & AVOutputFormat.AVFMT_GLOBALHEADER) != 0) {
-                       c.setFlags(AVCodecContext.AV_CODEC_FLAG_GLOBAL_HEADER, AVCodecContext.AV_CODEC_FLAG_GLOBAL_HEADER);
-               }
+               c.setCodecID(codec.getID());
+               c.setCodecType(codec.getType());
 
-               Logger.getLogger("jjmpeg.io")
-                       .fine(()
-                               -> String.format("add avideo stream %s %d [%dx%d %s aspect %s]\n",
-                               codec.getName(), c.getBitRate(),
-                               c.getWidth(), c.getHeight(), AVPixelFormat.toString(c.getPixelFormat()),
-                               c.getAspectRatio()));
+               if ((oc.getOutputFormat().getFlags() & AVOutputFormat.AVFMT_GLOBALHEADER) != 0)
+                       c.setFlags(AVCodecContext.AV_CODEC_FLAG_GLOBAL_HEADER, AVCodecContext.AV_CODEC_FLAG_GLOBAL_HEADER);
 
-               JJWriterVideo sd = new JJWriterVideo(st, c);
-               streams.add(sd);
+               vs = new JJWriterVideo(st, c);
+               streams.add(vs);
 
-               return sd;
+               return vs;
        }
 
        /**
-        * Add a new audio stream.
+        * Add an initialise a video stream.
         * <p>
-        * @param codec_id
-        * @param streamid
-        * @param fmt
-        * @param sample_rate
-        * @param bit_rate
+        * The supplied parameters are used to initialise the corresponding fields of the AVCodecContext. Other fields initialised
+        * are:
+        * <ul>
+        * <li>AVCodecContext.TimeBase=1 / frame_rate
+        * <li>AVStream.TimeBase=1 / frame_rate
+        * </ul>
+        *
+        * @param codec If null then use the format default codec. If non-null must be a video codec.
+        * @param streamid StreamID to create, or -1 to use the next available slot.
+        * @param pixel_format AVPixelFormat.
+        * @param width Width of video stream.
+        * @param height Height of video stream.
+        * @param frame_rate Display frame rate. It's inverse is also set as the stream and codec timebase.
+        * @param bit_rate Bitrate of encoding.
         * <p>
         * @return
-        * @throws AVInvalidStreamException
+        * @throws au.notzed.jjmpeg.AVIOException on error
         */
-       public JJWriterAudio addAudioStream(int codec_id, int streamid, int fmt, int sample_rate, int channels, long bit_rate) throws AVIOException {
+       public JJWriterVideo addVideoStream(AVCodec codec, int streamid, int pixel_format, int width, int height, AVRational frame_rate, long bit_rate) throws AVIOException {
+               JJWriterVideo sd = addVideoStream(codec, streamid);
+
+               /* Initialise provided parameters */
+               AVCodecContext c = sd.c;
+               AVStream st = sd.stream;
+
+               c.setWidth(width);
+               c.setHeight(height);
+
+               st.setAverageFrameRate(frame_rate);
+               sd.setTimeBase(new AVRational(frame_rate.den, frame_rate.num));
+
+               c.setBitRate(bit_rate);
+               c.setPixelFormat(pixel_format);
+
+               return sd;
+       }
+
+       public JJWriterAudio addAudioStream(AVCodec codec, int streamid) throws AVIOException {
                AVCodecContext c;
+               JJWriterAudio as;
                AVStream st;
 
-               AVCodec codec = AVCodec.findEncoder(codec_id);
+               if (codec == null) {
+                       codec = AVCodec.findEncoder(getFormat().getAudioCodec());
+                       if (codec == null)
+                               throw new AVIOException(AVError.AVERROR_STREAM_NOT_FOUND);
+               } else if (codec.getType() != AVMediaType.AVMEDIA_TYPE_AUDIO)
+                       throw new AVIOException(AVError.AVERROR_STREAM_NOT_FOUND);
 
                c = AVCodecContext.allocContext(codec);
-
                st = oc.newStream(codec);
-               if (st == null) {
+               if (st == null)
                        throw new AVIOException(AVError.AVERROR_STREAM_NOT_FOUND);
-               }
-
-               c.setCodecID(codec_id);
-               c.setCodecType(AVMediaType.AVMEDIA_TYPE_AUDIO);
 
-               c.setSampleFormat(fmt);
-               c.setBitRate(bit_rate);
-               c.setSampleRate(sample_rate);
+               st.setID(streamid == -1 ? streams.size() : streamid);
 
-               c.setNumChannels(channels);
-               c.setChannelLayout(4);
+               c.setCodecID(codec.getID());
+               c.setCodecType(codec.getType());
 
-               if ((oc.getOutputFormat().getFlags() & AVOutputFormat.AVFMT_GLOBALHEADER) != 0) {
+               if ((oc.getOutputFormat().getFlags() & AVOutputFormat.AVFMT_GLOBALHEADER) != 0)
                        c.setFlags(AVCodecContext.AV_CODEC_FLAG_GLOBAL_HEADER, AVCodecContext.AV_CODEC_FLAG_GLOBAL_HEADER);
-               }
 
-               Logger.getLogger("jjmpeg.io")
-                       .fine(()
-                               -> String.format("add audio stream audio %s %d [x%d %dHz %s]\n",
-                               codec.getName(), c.getBitRate(),
-                               c.getNumChannels(), c.getSampleRate(), AVSampleFormat.toString(c.getSampleFormat())));
+               as = new JJWriterAudio(st, c);
+               streams.add(as);
 
-               JJWriterAudio as = new JJWriterAudio(st, c);
+               return as;
+       }
 
-               streams.add(as);
+       /**
+        * Add an initialise an audio stream.
+        * <p>
+        * @param codec If null then use the format default codec. If non-null must be a video codec.
+        * @param streamid StreamID to create, or -1 to use the next available slot.
+        * @param sample_format AVSampleFormat
+        * @param sample_rate Cycles per second.
+        * @param layout AVChannelLayout
+        * @param bit_rate Encoding bit rate.
+        * <p>
+        * @return
+        * @throws au.notzed.jjmpeg.AVIOException
+        * @see AVSampleFormat
+        * @see AVChannelLayout
+        * @see AVChannelLayout#getDefaultLayout(int)
+        */
+       public JJWriterAudio addAudioStream(AVCodec codec, int streamid, int sample_format, int sample_rate, long layout, long bit_rate) throws AVIOException {
+               JJWriterAudio as = addAudioStream(codec, streamid);
+               AVCodecContext c = as.c;
+
+               c.setSampleFormat(sample_format);
+               c.setBitRate(bit_rate);
+               c.setSampleRate(sample_rate);
+
+               c.setNumChannels(AVChannelLayout.getNumChannels(layout));
+               c.setChannelLayout(layout);
 
                return as;
        }
 
        public void flush() throws AVIOException {
-               for (JJWriterStream sd: streams) {
-                       sd.addFrame(null);
+               for (JJWriterStream sd : streams) {
+                       sd.writeFrame(null);
                }
        }
 
@@ -270,19 +260,17 @@ public class JJMediaWriter {
        public void close() throws AVIOException {
 
                flush();
-               /* write the trailer, if any.  the trailer must be written
-                * before you close the CodecContexts open when you wrote the
-                * header; otherwise write_trailer may try to use memory that
-                * was freed on av_codec_close() */
-               oc.writeTrailer();
-
-               /* close each codec */
-               for (JJWriterStream sd: streams) {
-                       sd.close();
+               if (writeTrailer) {
+                       /* write the trailer, if any.  the trailer must be written
+                       * before you close the CodecContexts open when you wrote the
+                       * header; otherwise write_trailer may try to use memory that
+                       * was freed on av_codec_close() */
+                       oc.writeTrailer();
                }
 
-               /* close the output file */
-               //output.close();
+               for (JJWriterStream sd : streams) {
+                       sd.close();
+               }
 
                /* free the stream */
                oc.release();
@@ -322,6 +310,21 @@ public class JJMediaWriter {
                        return c;
                }
 
+               public void setTimeBase(AVRational timeBase) {
+                       stream.setTimeBase(timeBase);
+                       c.setTimeBase(timeBase);
+               }
+
+               public void setBitRate(long bit_rate) {
+                       c.setBitRate(bit_rate);
+               }
+
+               /**
+                * Open a write stream.
+                * This performs the step of set
+                *
+                * @throws AVIOException
+                */
                public void open() throws AVIOException {
                        AVCodec codec = AVCodec.findEncoder(c.getCodecID());
 
@@ -332,12 +335,38 @@ public class JJMediaWriter {
                        opened = true;
                }
 
-               public void addFrame(AVFrame frame) throws AVIOException {
+               /**
+                * Write encoded packet.
+                * If the data is already in the correct format then use this to
+                * add a packet to the stream.
+                * <p>
+                * This sets the packet streamIndex and rescales the timestamp and calls
+                * {@link AVFormatContext#interleavedWriteFrame(au.notzed.jjmpeg.AVPacket)}
+                *
+                * @param packet
+                * @throws AVIOException
+                */
+               public void writePacket(AVPacket packet) throws AVIOException {
+                       packet.rescaleTS(c.getTimeBase(), stream.getTimeBase());
+                       packet.setStreamIndex(stream.getIndex());
+                       oc.interleavedWriteFrame(packet);
+               }
+
+               /**
+                * Write unencoded frame.
+                * <p>
+                * The frame is passed through the
+                * {@link AVCodecContext#sendFrame(au.notzed.jjmpeg.AVFrame) /
+                * {@link AVCodecContext#receivePacket(au.notzed.jjmpeg.AVPacket)
+                * sequence and then written to the output.
+                *
+                * @param frame
+                * @throws AVIOException on error.
+                */
+               public void writeFrame(AVFrame frame) throws AVIOException {
                        if (c.sendFrame(frame)) {
                                while (c.receivePacket(packet)) {
-                                       packet.rescaleTS(c.getTimeBase(), stream.getTimeBase());
-                                       packet.setStreamIndex(stream.getIndex());
-                                       oc.interleavedWriteFrame(packet);
+                                       writePacket(packet);
                                }
                        }
                }
@@ -364,6 +393,30 @@ public class JJMediaWriter {
                        super.open();
 
                        frame = AVFrame.alloc(c.getPixelFormat(), c.getWidth(), c.getHeight());
+
+                       Logger.getLogger("jjmpeg.io")
+                               .fine(()
+                                       -> String.format("open video stream %s %d [%dx%d %s aspect %s]\n",
+                                       AVCodec.findEncoder(c.getCodecID()).getName(),
+                                       c.getBitRate(),
+                                       c.getWidth(), c.getHeight(), AVPixelFormat.toString(c.getPixelFormat()),
+                                       c.getAspectRatio()));
+               }
+
+               public void setWidth(int width) {
+                       c.setWidth(width);
+               }
+
+               public void setHeight(int height) {
+                       c.setHeight(height);
+               }
+
+               public void setPixelFormat(int format) {
+                       c.setPixelFormat(format);
+               }
+
+               public void setAverateFrameRate(AVRational frameRate) {
+                       stream.setAverageFrameRate(frameRate);
                }
 
                @Override
@@ -382,13 +435,13 @@ public class JJMediaWriter {
                 * <p>
                 * @param frame Video frame to write, or null to flush buffers.
                 * <p>
-                * @throws AVEncodingError
-                * @throws AVIOException
+                * @throws AVIOException on error.
                 */
-               public void addFrame(AVFrame frame) throws AVIOException {
-                       if (frame != null)
-                               frame.setPTS(nextPTS++);
-                       super.addFrame(frame);
+               @Override
+               public void writeFrame(AVFrame frame) throws AVIOException {
+                       //if (frame != null)
+                       //      frame.setPTS(nextPTS++);
+                       super.writeFrame(frame);
                }
 
        }
@@ -399,6 +452,22 @@ public class JJMediaWriter {
                        super(stream, c);
                }
 
+               public void setSampleFormat(int sample_format) {
+                       c.setSampleFormat(sample_format);
+               }
+
+               public void setSampleRate(int sample_rate) {
+                       c.setSampleRate(sample_rate);
+               }
+
+               public void setNumChannels(int channels) {
+                       c.setNumChannels(channels);
+               }
+
+               public void setChannelLayout(long layout) {
+                       c.setChannelLayout(layout);
+               }
+
                @Override
                public void open() throws AVIOException {
                        super.open();
@@ -409,6 +478,13 @@ public class JJMediaWriter {
 
                        // ??? unchecked
                        frame = AVFrame.alloc(c.getSampleFormat(), c.getNumChannels(), 2048);
+
+                       Logger.getLogger("jjmpeg.io")
+                               .fine(()
+                                       -> String.format("open audio stream %s %d [x%d %dHz %s]\n",
+                                       AVCodec.findEncoder(c.getCodecID()).getName(),
+                                       c.getBitRate(),
+                                       c.getNumChannels(), c.getSampleRate(), AVSampleFormat.toString(c.getSampleFormat())));
                }
 
                @Override
index cd82b46..cddf729 100644 (file)
@@ -175,11 +175,13 @@ sub scan {
            my $def = $1;
            #print $1."\n";
            if ($lastc ne "") {
-               #$lastc =~ s@\t+@\t@g;
+               $lastc =~ s@\"@\\\"@g;
                print C "\tprintf(\"%s\", \"$lastc\");\n";          
            } elsif (m@///< (.*)@ || m@/\*\|<(.*)\*@) {
+               my $com = $1;
+               $com =~ s@\"@\\\"@g;
                # handle single-line comments, perhaps these should override
-               print C "\tprintf(\"\\t/**\\n\\t * $1\\n\\t */\\n\");\n";
+               print C "\tprintf(\"\\t/**\\n\\t * $com\\n\\t */\\n\");\n";
            }
        
            print C "\tprintf(\"\\tpublic final static $o{type} $def = $o{cfmt};\\n\", $o{ctype}$def$o{shift});\n";
index 9bada1d..6281db8 100644 (file)
@@ -28,6 +28,7 @@ notzed.jjmpeg_JAVA_GENERATED =                        \
  au/notzed/jjmpeg/AVCodecIDBits.java           \
  au/notzed/jjmpeg/AVDiscardBits.java           \
  au/notzed/jjmpeg/AVErrorBits.java             \
+ au/notzed/jjmpeg/AVIOContextBits.java         \
  au/notzed/jjmpeg/AVMediaTypeBits.java         \
  au/notzed/jjmpeg/AVOptionsBits.java           \
  au/notzed/jjmpeg/AVPixelFormatBits.java       \
@@ -87,6 +88,10 @@ $(tmp)/AVCodecBits-gen.c: $(extract_defines) $(dep)
        @install -d $(@D)
        perl $(extract_defines) -c -interface AVCodecBits -f $(FFMPEG_HOME) -header libavcodec/avcodec.h -d AV_CODEC_CAP_ $@
 
+$(tmp)/AVIOContextBits-gen.c: $(extract_defines) $(dep)
+       @install -d $(@D)
+       perl $(extract_defines) -c -interface AVIOContextBits -f $(FFMPEG_HOME) -header libavformat/avio.h -d AVSEEK_ -d AVIO_FLAG_ -d AVIO_SEEKABLE_ $@
+
 $(tmp)/AVOptionsBits-gen.c: $(extract_defines) $(dep)
        @install -d $(@D)
        perl $(extract_defines) -c -interface AVOptionsBits -f $(FFMPEG_HOME) -header libavutil/opt.h -d AV_OPT_ $@
index 78bda20..bd8eb93 100644 (file)
@@ -35,13 +35,8 @@ jint AVCodecContext_OnLoad(JavaVM *vmi, JNIEnv *env) {
 
 GS_int(AVCodecContext, CodecType, codec_type)
 GS_int(AVCodecContext, CodecID, codec_id)
-GS_int(AVCodecContext, CodecTag, codec_tag)
 GS_long(AVCodecContext, BitRate, bit_rate)
-GS_int(AVCodecContext, BitRateTolerance, bit_rate_tolerance)
 GS_int(AVCodecContext, GlobalQuality, global_quality)
-GS_int(AVCodecContext, CompressionLevel, compression_level)
-//GS_int(AVCodecContext, Flags1, flags)
-//GS_int(AVCodecContext, Flags2, flags2)
 
 JNIEXPORT jlong JNICALL MAKE_JJNAME(AVCodecContext, getFlags)
 (JNIEnv *env , jobject jo) {
@@ -77,7 +72,6 @@ JNIEXPORT void JNICALL MAKE_JJNAME(AVCodecContext, setTimeBase)
        o->time_base = AVRational_get(env, jtb);
 }
 
-GS_int(AVCodecContext, TicksPerFrame, ticks_per_frame)
 GET_prim(jint, AVCodecContext, Delay, delay)
 
 /* Video options */
@@ -88,35 +82,17 @@ GS_int(AVCodecContext, GOPSize, gop_size)
 
 GS_int(AVCodecContext, PixelFormat, pix_fmt)
 
-GS_int(AVCodecContext, MaxBFrames, max_b_frames)
-GS_float(AVCodecContext, BQuantFactor, b_quant_factor)
-GS_float(AVCodecContext, BQuantOffset, b_quant_offset)
-
-GET_bool(AVCodecContext, hasBFrames, has_b_frames)
-
-GS_float(AVCodecContext, IQuantFactor, i_quant_factor)
-GS_float(AVCodecContext, IQuantOffset, i_quant_offset)
-
-GS_int(AVCodecContext, MBDecision, mb_decision)
-
 /* Audio options */
 GS_int(AVCodecContext, SampleRate, sample_rate)
 GS_int(AVCodecContext, NumChannels, channels)
 GS_int(AVCodecContext, SampleFormat, sample_fmt)
-GS_int(AVCodecContext, FrameSize, frame_size)
+GET_prim(jint, AVCodecContext, FrameSize, frame_size)
 GET_prim(jint, AVCodecContext, FrameNumber, frame_number)
 GS_long(AVCodecContext, ChannelLayout, channel_layout)
 GS_long(AVCodecContext, RequestChannelLayout, request_channel_layout)
-GS_int(AVCodecContext, StrictStdCompliance, strict_std_compliance)
-
-GS_int(AVCodecContext, ErrorConcealment, error_concealment)
-GS_int(AVCodecContext, ErrorRecognition, err_recognition) // !!
-
-GS_int(AVCodecContext, DCTAlgo, dct_algo)
-GS_int(AVCodecContext, IDCTAlgo, idct_algo)
 
+/* other options */
 GS_int(AVCodecContext, ThreadCount, thread_count)
-GS_int(AVCodecContext, Profile, profile)
 
 /* ********************************************************************** */
 /* Methods */
index 2b27ac2..51725b4 100644 (file)
@@ -29,27 +29,28 @@ jint AVIOContext_OnLoad(JavaVM *vmi, JNIEnv *env) {
 
 /* ********************************************************************** */
 
+/*
+ * All the callbacks use this structure to find the java instance
+ * which implement it.
+ *
+ * AVIOContext.open() has a further complication in that there is
+ * nowhere to store a pointer to this.  To get around this if an
+ * interrupt callback is supplied it is referenced in an instance of a
+ * nativez.ReferenceZ object and set on the java AVIOContext.opaque
+ * field.
+ */
 struct jj_io {
-       // AVIOHandler global reference
+       // AVIOHandler or AVIOInterrupt depending on how it was created
        jobject jhandler;
-       // direct ByteBuffer for this data
-       // I don't think this will work looking at avio*.c
-       //jobject jbuffer;
-       //uint8_t *buffer;
 };
 
 static int jj_read_packet(void *opaque, uint8_t *buf, int buf_size) {
-       JNIEnv *env = nativez_AttachCurrentThread();
+       JNIEnv *env = nativez_AttachCurrentThreadAsDaemon();
        
        if (env) {
                struct jj_io *jjio = opaque;
-               //size_t position = (buf - jjio->buffer);
                jvalue args[1];
 
-               //jjbufferSetLimit(env, jjio->jbuffer, (int)(position + buf_size));
-               //jjbufferSetPosition(env, jjio->jbuffer, (int)(position));
-               //args[0].l = jjio->jbuffer;
-
                args[0].l = nativez_NewDirectBuffer(env, buf, buf_size);
 
                return (*env)->CallIntMethodA(env, jjio->jhandler, AVIOHandler_readPacket_l, args);
@@ -59,17 +60,12 @@ static int jj_read_packet(void *opaque, uint8_t *buf, int buf_size) {
 }
 
 static int jj_write_packet(void *opaque, uint8_t *buf, int buf_size) {
-       JNIEnv *env = nativez_AttachCurrentThread();
+       JNIEnv *env = nativez_AttachCurrentThreadAsDaemon();
 
        if (env) {
                struct jj_io *jjio = opaque;
-               //size_t position = (buf - jjio->buffer);
                jvalue args[1];
 
-               //jjbufferSetLimit(env, jjio->jbuffer, (int)(position + buf_size));
-               //jjbufferSetPosition(env, jjio->jbuffer, (int)(position));
-               //args[0].l = jjio->jbuffer;
-
                args[0].l = nativez_NewDirectBuffer(env, buf, buf_size);
 
                return (*env)->CallIntMethodA(env, jjio->jhandler, AVIOHandler_writePacket_l, args);
@@ -79,7 +75,7 @@ static int jj_write_packet(void *opaque, uint8_t *buf, int buf_size) {
 }
 
 static int64_t jj_seek(void *opaque, int64_t offset, int whence) {
-       JNIEnv *env = nativez_AttachCurrentThread();
+       JNIEnv *env = nativez_AttachCurrentThreadAsDaemon();
 
        if (env) {
                struct jj_io *jjio = opaque;
@@ -94,6 +90,18 @@ static int64_t jj_seek(void *opaque, int64_t offset, int whence) {
        }
 }
 
+static int jj_interrupt(void *opaque) {
+       JNIEnv *env = nativez_AttachCurrentThreadAsDaemon();
+
+       if (env) {
+               struct jj_io *jjio = opaque;
+
+               return (*env)->CallBooleanMethodA(env, jjio->jhandler, AVIOInterrupt_isInterrupted_, NULL);
+       } else {
+               return 1;
+       }
+}
+
 JNIEXPORT void JNICALL Java_au_notzed_jjmpeg_AVIOContext_release
 (JNIEnv *env, jclass jc, jlong jic) {
        AVIOContext *ic = (void *)(uintptr_t)jic;
@@ -102,29 +110,63 @@ JNIEXPORT void JNICALL Java_au_notzed_jjmpeg_AVIOContext_release
                struct jj_io *jjio = ic->opaque;
 
                (*env)->DeleteGlobalRef(env, jjio->jhandler);
-               //(*env)->DeleteGlobalRef(env, jjio->jbuffer);
-               //DLCALL(av_free)(jjio->buffer);
-               free(jjio);
-               
                DLCALL(av_free)(ic->buffer);
                DLCALL(avio_context_free)(&ic);
+               free(jjio);
        } else {
                DLCALL(avio_close)(ic);
        }
 }
 
 JNIEXPORT jobject JNICALL Java_au_notzed_jjmpeg_AVIOContext_open
-(JNIEnv *env, jclass jc, jstring jurl, jint flags) {
-       const char *url = nativez_GetString(env, jurl);
-       AVIOContext *ic = NULL;
+(JNIEnv *env, jclass jc, jstring jurl, jint flags, jobject jcb, jobject joptions) {
+       AVIOContext *io = NULL;
        int res;
+       struct jj_io *jjio = NULL;
+       AVIOInterruptCB cb = { 0, 0 };
+       jobject jio = NULL;
+       jobject jrefs = NULL;
+       
+       jjio = malloc(sizeof(*jjio));
+       if (!jjio) {
+               nativez_ThrowOutOfMemoryError(env, "Allocating handler");
+               return NULL;
+       }
+       
+       if (jcb) {
+               // FIXME: check returns
+               jrefs = ReferenceZ_create(env, &jcb, 1);
+               jjio->jhandler = (*env)->NewGlobalRef(env, jcb);
+               cb.callback = jj_interrupt;
+               cb.opaque = jjio;
+       }
        
-       res = DLCALL(avio_open)(&ic, url, flags);
-       if (res < 0)
+       AVDictionary *options = AVDictionary_get(env, joptions);
+       const char *url = nativez_GetString(env, jurl);
+       
+       res = DLCALL(avio_open2)(&io, url, flags, &cb, &options);
+       AVDictionary_set(env, joptions, options, 1);
+
+       if (res < 0) {
                jjthrowAVIOException(env, res, url);
+               goto fail;
+       }
+
+       jio = NativeZ_create(env, jc, io);
+       if (!jio)
+               goto fail;
+
+       
+       (*env)->SetObjectField(env, jio, AVIOContext_opaque, jrefs);
+
+       return jio;
+ fail:
        nativez_ReleaseString(env, jurl, url);
+       if (jjio->jhandler)
+               (*env)->DeleteGlobalRef(env, jjio->jhandler);
+       free(jjio);
 
-       return NativeZ_create(env, jc, ic);
+       return NULL;
 }
 
 JNIEXPORT jobject JNICALL Java_au_notzed_jjmpeg_AVIOContext_alloc
@@ -132,35 +174,40 @@ JNIEXPORT jobject JNICALL Java_au_notzed_jjmpeg_AVIOContext_alloc
        AVIOContext *io = NULL;
        struct jj_io *jjio;
        uint8_t *buffer;
+       jobject jio = NULL;
 
        jjio = malloc(sizeof(*jjio));
-       if (jjio) {
-               buffer = DLCALL(av_malloc)(buffer_size);
-               if (buffer) {
-                       int write_flag = (jflags & au_notzed_jjmpeg_AVIOContext_AVIO_WRITABLE) != 0;
+       if (!jjio)
+               goto fail_nojjio;
 
-                       jjio->jhandler = (*env)->NewGlobalRef(env, jhandler);
-                       //jjio->jbuffer = jjnewDirectBuffer(env, jjio->buffer, buffer_size);
+       buffer = DLCALL(av_malloc)(buffer_size);
+       if (!buffer)
+               goto fail_nobuffer;
+       
+       jjio->jhandler = (*env)->NewGlobalRef(env, jhandler);
                
-                       io = DLCALL(avio_alloc_context)(buffer, buffer_size, write_flag, jjio,
-                                                     jj_read_packet,
-                                                     jj_write_packet,
-                                                     jj_seek);
-                       if (io) {
-                               io->seekable = jflags & 3;
-                               io->direct = (jflags & au_notzed_jjmpeg_AVIOContext_AVIO_DIRECT) != 0;
-                       } else {
-                               nativez_ThrowOutOfMemoryError(env, "Allocating io context");
-                               free(buffer);
-                               free(jjio);
-                       }
-               } else {
-                       nativez_ThrowOutOfMemoryError(env, "Allocating buffer");
-                       free(jjio);
-               }
-       } else {
-               nativez_ThrowOutOfMemoryError(env, "Allocating handler");
-       }
-
-       return NativeZ_create(env, jc, io);
+       io = DLCALL(avio_alloc_context)(buffer, buffer_size,
+                                       (jflags & au_notzed_jjmpeg_AVIOContext_AVIO_WRITABLE) != 0,
+                                       jjio,
+                                       jj_read_packet,
+                                       jj_write_packet,
+                                       jj_seek);
+       if (!io)
+               goto fail;
+
+       io->seekable = jflags & (AVIO_SEEKABLE_NORMAL | AVIO_SEEKABLE_TIME);
+       io->direct = (jflags & au_notzed_jjmpeg_AVIOContext_AVIO_DIRECT) != 0;
+       jio = NativeZ_create(env, jc, io);
+       if (!jio)
+               goto fail;
+
+       return jio;
+ fail:
+       (*env)->DeleteGlobalRef(env, jjio->jhandler);
+       free(buffer);
+ fail_nobuffer:
+       free(jjio);
+ fail_nojjio:
+       nativez_ThrowOutOfMemoryError(env, "Allocating io context");
+       return NULL;
 }
index f1a2fe7..fec52b1 100644 (file)
@@ -3,7 +3,7 @@ header avformat libavformat/avio.h {
        avio_alloc_context
        avio_context_free
 
-       avio_open
+       avio_open2
        avio_close
 }
 
@@ -12,6 +12,14 @@ header avutil libavutil/mem.h {
        av_free
 }
 
+java AVIOContext au/notzed/jjmpeg/AVIOContext {
+     opaque, Ljava/lang/Object;
+}
+
+java AVIOInterrupt au/notzed/jjmpeg/AVIOContext$AVIOInterrupt {
+     isInterrupted, ()Z
+}
+
 java AVIOHandler au/notzed/jjmpeg/AVIOContext$AVIOHandler {
      readPacket, (Ljava/nio/ByteBuffer;)I
      writePacket, (Ljava/nio/ByteBuffer;)I
index bd57fa4..fe2f3e1 100644 (file)
@@ -34,6 +34,7 @@ jint AVPacket_OnLoad(JavaVM *vmi, JNIEnv *env) {
 
 GS_long(AVPacket, PTS, pts)
 GS_long(AVPacket, DTS, dts)
+GS_long(AVPacket, Duration, duration)
 GET_prim(jint, AVPacket, Size, size)
 GS_int(AVPacket, StreamIndex, stream_index)
 GET_prim(jint, AVPacket, Flags, flags)
@@ -61,14 +62,16 @@ JNIEXPORT jobject JNICALL Java_au_notzed_jjmpeg_AVPacket_alloc
 (JNIEnv *env, jclass jc) {
        AVPacket *packet = DLCALL(av_packet_alloc)();
 
-       //if (!packet) // outofmemoryerror?
-
-       return NativeZ_create(env, jc, packet);
+       if (nativez_NonNull(env, "Allocating AVPacket", packet)) {
+               DLCALL(av_init_packet)(packet);
+               return NativeZ_create(env, jc, packet);
+       }
+       return NULL;
 }
 
 /* **************************************** */
 
-JNIEXPORT jobject JNICALL Java_au_notzed_jjmpeg_AVPacket_getData
+JNIEXPORT jobject JNICALL Java_au_notzed_jjmpeg_AVPacket_getData__
 (JNIEnv *env, jobject jpacket) {
        AVPacket *packet = NativeZ_getP(env, jpacket);
 
@@ -78,6 +81,25 @@ JNIEXPORT jobject JNICALL Java_au_notzed_jjmpeg_AVPacket_getData
        return NULL;
 }
 
+JNIEXPORT jobject JNICALL Java_au_notzed_jjmpeg_AVPacket_getData__J
+(JNIEnv *env, jobject jpacket, jlong size) {
+       AVPacket *packet = NativeZ_getP(env, jpacket);
+
+       if (packet->size < size) {
+               if (av_grow_packet(packet, size - packet->size) != 0) {
+                       nativez_ThrowOutOfMemoryError(env, "Unable to grow packet");
+                       return NULL;
+               }
+       } else {
+               av_shrink_packet(packet, size);
+       }
+       
+       if (nativez_NonNull(env, "No data pointer", packet->data))
+               return nativez_NewDirectBuffer(env, packet->data, packet->size);
+
+       return NULL;
+}
+
 JNIEXPORT void JNICALL Java_au_notzed_jjmpeg_AVPacket_clear
 (JNIEnv *env, jobject jpacket) {
        AVPacket *packet = NativeZ_getP(env, jpacket);
@@ -90,8 +112,9 @@ JNIEXPORT jobject JNICALL Java_au_notzed_jjmpeg_AVPacket_clone
        AVPacket *packet = NativeZ_getP(env, jpacket);
        AVPacket *dst = DLCALL(av_packet_clone)(packet);
 
-       // if !dst ...
-       return NativeZ_create(env, AVPacket_classid, dst);      
+       if (nativez_NonNull(env, "Cloning AVPacket", dst))
+               return NativeZ_create(env, AVPacket_classid, dst);
+       return NULL;
 }
 
 JNIEXPORT void JNICALL Java_au_notzed_jjmpeg_AVPacket_rescaleTS
index 3352b5a..92c42ae 100644 (file)
@@ -4,6 +4,8 @@ header avcodec libavcodec/avcodec.h {
        av_packet_unref
        av_packet_clone
        av_packet_rescale_ts
+
+       av_init_packet
 }
 
 java AVPacket au/notzed/jjmpeg/AVPacket {
index a8d4bb7..8de4a15 100644 (file)
@@ -30,7 +30,7 @@ jint AVUtil_OnLoad(JavaVM *vmi, JNIEnv *env) {
 static jobject jlogger_ref;
 
 static void log_callback(void*ptr, int level, const char*fmt, va_list vl) {
-       JNIEnv *env = nativez_AttachCurrentThread();
+       JNIEnv *env = nativez_AttachCurrentThreadAsDaemon();
 
        if (env) {
                char log[1024];
index a59381c..3c9a433 100644 (file)
@@ -83,7 +83,7 @@ void jjthrowAVIOException(JNIEnv *env, int error, const char *msg) {
 
 /* ********************************************************************** */
 
-jstring jjnewIntArrayT(JNIEnv *env, const int *ap, int terminal) {
+jintArray jjnewIntArrayT(JNIEnv *env, const int *ap, int terminal) {
        jintArray ret = NULL;
 
        if (ap) {
@@ -94,6 +94,7 @@ jstring jjnewIntArrayT(JNIEnv *env, const int *ap, int terminal) {
                ret = (*env)->NewIntArray(env, len);
                (*env)->SetIntArrayRegion(env, ret, 0, len, (const jint *)ap);
        }
+
        return ret;
 }
 
index d29f641..be1aa4c 100644 (file)
  * @param ap Array pointer, may be NULL.
  * @param terminal Terminating value.
  */
-jstring jjnewIntArrayT(JNIEnv *env, const int *ap, int terminal);
+jintArray jjnewIntArrayT(JNIEnv *env, const int *ap, int terminal);
 
 void jjthrowAVIOException(JNIEnv *env, int error, const char *msg);