--- /dev/null
+#
+# Copyright (C) 2019 Michael Zucchi
+#
+# This is the copyright for jjmpeg/contrib/octave
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+TOP=../..
+
+jjmpeg-octave_JAVA := jjoctave/VideoReader.java \
+ $(shell find \
+ $(TOP)/../nativez/src/notzed.nativez/classes \
+ $(TOP)/src/notzed.jjmpeg/classes \
+ $(TOP)/bin/gen/notzed.jjmpeg/classes \
+ -name '*.java' -a \! -name 'module-info.java')
+
+$(TOP)/bin/jjmpeg-octave.jar: $(jjmpeg-octave_JAVA)
+ rm -rf $(TOP)/bin/octave
+ mkdir -p $(TOP)/bin/octave
+ javac -source 8 \
+ -d $(TOP)/bin/octave \
+ $(jjmpeg-octave_JAVA)
+ jar cf $(TOP)/bin/jjmpeg-octave.jar \
+ -C $(TOP)/bin/octave .
+
+clean:
+ rm -rf $(TOP)/bin/jjmpeg-octave.jar $(TOP)/bin/octave
--- /dev/null
+
+Copyright (C) 2017-2019 Michael Zucchi
+
+See the section LICENSE for details.
+
+INTRODUCTION
+------------
+
+This is a module for reading video data inside octave.
+
+It supports multiple streams and 16-bit frames and all of the input
+formats and protocols available in jjmpeg.
+
+There is a simplified interface in jjoctave/VideoReader.java and the
+jj*m files define a basic interface for reading frames and handling
+data layout.
+
+The octave sources are portable and work with a similar
+mostly-compatible tool but require different installation
+instructions.
+
+COMPILE
+-------
+
+First the normal build process must be completed successfully. This
+is required to create the native libraries and generated sources.
+
+Octave uses Java 8 and is thus incompatible with the standard
+modular-Java build of both jjmpeg and nativez.
+
+A trivial Makefile is included in this directory to recompile the core
+classes of jjmpeg and nativez into a single jar file. It requires
+that the nativez source is available in the same directory as jjmpeg
+and that the jdk-8 javac and jar are on the path.
+
+$ make
+
+It creates a new jar in `$(TOP)/bin/jjmpeg-octave.jar'.
+
+jjPlayAll.m can be used to check the build. libjjmpeg.so and
+libnativez.so must be in the library path before invoking octave.
+
+INSTALL
+-------
+
+Copy the jj*m files to a directory and add a javaclasspath.txt file
+which includes the path to the jjmpeg-octave.jar.
+
+libnativez.so and libjjmpeg.so must be in the LD_LIBRARY_PATH.
+
+For usage read the .m files.
+
+LICENSE
+-------
+
+This is licensed under GNU General Public License version 3, see
+../../src/jjmpeg.nativez/legal/LICENSE for the full details.
+
+ Copyright (C) 2019 Michael Zucchi
+
+ This program is free software: you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+
--- /dev/null
+../../bin/jjmpeg-octave.jar
--- /dev/null
+
+%% jjPlayAll play multi-stream video file.
+%
+% This simple demo will play all streams of a
+% video simultaneously in a single figure.
+%
+% jjPlayAll(src)
+%
+% src Path to video file.
+
+%
+% Copyright (C) 2019 Michael Zucchi
+%
+% This is the copyright for jjmpeg octave functions.
+%
+% This program is free software: you can redistribute it and/or modify
+% it under the terms of the GNU General Public License as published by
+% the Free Software Foundation, either version 3 of the License, or
+% (at your option) any later version.
+%
+% This program is distributed in the hope that it will be useful,
+% but WITHOUT ANY WARRANTY; without even the implied warranty of
+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+% GNU General Public License for more details.
+%
+% You should have received a copy of the GNU General Public License
+% along with this program. If not, see <http://www.gnu.org/licenses/>.
+%
+
+function jjPlayAll(src, sid)
+
+ v = jjVideoOpen(src);
+
+ streams = javaMethod('getStreamCount', v);
+
+ images = cell(streams, 1);
+ ncols = ceil(sqrt(streams));
+ nrows = ceil(streams/ncols);
+
+ sid = jjVideoNext(v);
+ while sid ~= -1
+ did = sid+1;
+ frame = jjVideoFrame(v, sid, 'rgb24');
+ if isempty(images{did})
+ subplot(nrows, ncols, did);
+ images{did} = imshow(frame);
+ else
+ set(images{did}, 'CData', frame);
+ end
+ drawnow;
+ sid = jjVideoNext(v);
+ end
+
+ jjVideoClose(v);
+end
+
--- /dev/null
+
+%% jjVideoClose Close video file.
+%
+% jjVideoClose(video)
+%
+% Closes video file, it should no longer be used.
+%
+% See also jjVideoOpen().
+
+%
+% Copyright (C) 2019 Michael Zucchi
+%
+% This is the copyright for jjmpeg octave functions.
+%
+% This program is free software: you can redistribute it and/or modify
+% it under the terms of the GNU General Public License as published by
+% the Free Software Foundation, either version 3 of the License, or
+% (at your option) any later version.
+%
+% This program is distributed in the hope that it will be useful,
+% but WITHOUT ANY WARRANTY; without even the implied warranty of
+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+% GNU General Public License for more details.
+%
+% You should have received a copy of the GNU General Public License
+% along with this program. If not, see <http://www.gnu.org/licenses/>.
+%
+
+function jjVideoClose(video)
+ javaMethod('close', video);
+end
--- /dev/null
+%% jjVideoFrame Read video frame pixels.
+%
+% [frame] = jjVideoFrame(video, streamid, format)
+%
+% Retrieve the video frame pixels for the current frame.
+%
+% video A previously opened video.
+%
+% streamid The last streamid returned by the last call to
+% jjNextFrame(video). Default is 0.
+%
+% format The desired pixel format. Default is 'rgb24'.
+%
+% 'rgb24' 3 channel 'uint8'
+% 'rgb48' 3 channel 'uint16'
+% 'grey8' 1 channel 'uint8'
+% 'grey16' 1 channel 'uint16'
+%
+% frame The image pixels as a [height width depth] matrix of the
+% given type.
+%
+% See also jjVideoNext()
+
+%
+% Copyright (C) 2019 Michael Zucchi
+%
+% This is the copyright for jjmpeg octave functions.
+%
+% This program is free software: you can redistribute it and/or modify
+% it under the terms of the GNU General Public License as published by
+% the Free Software Foundation, either version 3 of the License, or
+% (at your option) any later version.
+%
+% This program is distributed in the hope that it will be useful,
+% but WITHOUT ANY WARRANTY; without even the implied warranty of
+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+% GNU General Public License for more details.
+%
+% You should have received a copy of the GNU General Public License
+% along with this program. If not, see <http://www.gnu.org/licenses/>.
+%
+
+function [frame] = jjVideoFrame(v, streamid, format)
+ switch nargin
+ case 1
+ streamid = 1;
+ format = 'rgb24';
+ case 2
+ format = 'rgb24';
+ end
+
+ % This is hardcoded as matlab isn't compatible with octave for field refs
+ switch format
+ case 'rgb24'
+ fmt = 0;
+ case 'grey8'
+ fmt = 1;
+ case 'rgb48'
+ fmt = 2;
+ case 'grey16'
+ fmt = 3;
+ otherwise
+ error('unknown pixel format');
+ end
+
+ frame = javaMethod('getPixels', v, streamid, fmt);
+ width = javaMethod('getWidth', v, streamid);
+ height = javaMethod('getHeight', v, streamid);
+
+ switch fmt
+ case 0
+ frame = typecast(frame, 'uint8');
+ frame = reshape(frame, [3 width height]);
+ frame = permute(frame, [3 2 1]);
+ case 1
+ frame = typecast(frame, 'uint8');
+ frame = reshape(frame, [height width]);
+ case 2
+ frame = typecast(frame, 'uint16');
+ frame = reshape(frame, [3 width height]);
+ frame = permute(frame, [3 2 1]);
+ case 3
+ frame = typecast(frame, 'uint16');
+ frame = reshape(frame, [height width]);
+ end
+end
--- /dev/null
+%% jjVideoNext Advance to the next frame.
+%
+% streamid = jjVideoNext(video)
+%
+% Advance to the next frame in the video.
+%
+% video An opened video.
+%
+% streamid[in] The desired streamid, -1 or missing for any stream.
+%
+% streamid[out] The index of the stream the frame belongs to.
+%
+% See also jjVideoOpen(), jjVideoFrame()
+
+%
+% Copyright (C) 2019 Michael Zucchi
+%
+% This is the copyright for jjmpeg octave functions.
+%
+% This program is free software: you can redistribute it and/or modify
+% it under the terms of the GNU General Public License as published by
+% the Free Software Foundation, either version 3 of the License, or
+% (at your option) any later version.
+%
+% This program is distributed in the hope that it will be useful,
+% but WITHOUT ANY WARRANTY; without even the implied warranty of
+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+% GNU General Public License for more details.
+%
+% You should have received a copy of the GNU General Public License
+% along with this program. If not, see <http://www.gnu.org/licenses/>.
+%
+
+function [streamid] = jjVideoNext(video, streamid)
+ if nargin == 1
+ streamid = javaMethod('nextFrame', video);
+ else
+ streamid = javaMethod('nextFrame', video, streamid);
+ end
+end
+
--- /dev/null
+
+%% jjVideoOpen Open video file.
+%
+% video = jjVideoOpen(path)
+%
+% Open video file for reading.
+%
+% path The file path.
+%
+% video The video handle (object).
+%
+% See also jjVideoClose().
+
+%
+% Copyright (C) 2019 Michael Zucchi
+%
+% This is the copyright for jjmpeg octave functions.
+%
+% This program is free software: you can redistribute it and/or modify
+% it under the terms of the GNU General Public License as published by
+% the Free Software Foundation, either version 3 of the License, or
+% (at your option) any later version.
+%
+% This program is distributed in the hope that it will be useful,
+% but WITHOUT ANY WARRANTY; without even the implied warranty of
+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+% GNU General Public License for more details.
+%
+% You should have received a copy of the GNU General Public License
+% along with this program. If not, see <http://www.gnu.org/licenses/>.
+%
+
+function [video] = jjVideoOpen(path)
+ video = javaObject('jjoctave.VideoReader', path);
+end
--- /dev/null
+/*
+ * Copyright (c) 2019 Michael Zucchi
+ *
+ * This file is part of jjmpeg, a java binding to ffmpeg's libraries.
+ *
+ * jjmpeg is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * jjmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with jjmpeg. If not, see <http://www.gnu.org/licenses/>.
+ */
+package jjoctave;
+
+import au.notzed.jjmpeg.AVFrame;
+import au.notzed.jjmpeg.AVMediaTypeBits;
+import au.notzed.jjmpeg.AVPixelFormat;
+import au.notzed.jjmpeg.AVPixelReader;
+import au.notzed.jjmpeg.io.JJMediaReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Object for managing multi-stream video sources for octave use.
+ * <p>
+ * This exports the frame pixels as native AVPixelFormat formats as signed java types,
+ * i.e. channel-packed in row order.
+ * <p>
+ * octave expects images to be in planar format using unsigned types. The following code
+ * can be used to transform the data, if required.
+ * <pre>
+ * frame = javaMethod('getPixels', v, streamid, fmt);
+ * width = javaMethod('getWidth', v, streamid);
+ * height = javaMethod('getHeight', v, streamid);
+ *
+ * switch fmt
+ * case 0
+ * frame = typecast(frame, 'uint8');
+ * frame = reshape(frame, [3 width height]);
+ * frame = permute(frame, [3 2 1]);
+ * case 1
+ * frame = typecast(frame, 'uint8');
+ * frame = reshape(frame, [height width]);
+ * case 2
+ * frame = typecast(frame, 'uint16');
+ * frame = reshape(frame, [3 width height]);
+ * frame = permute(frame, [3 2 1]);
+ * case 3
+ * frame = typecast(frame, 'uint16');
+ * frame = reshape(frame, [height width]);
+ * end
+ * </pre>
+ */
+public class VideoReader {
+
+ JJMediaReader reader;
+ final JJMediaReader.JJReaderVideo[] videos;
+ final AVFrame[] frames;
+ final AVPixelReader[] pixels;
+
+ public static final int FMT_UINT8_RGB = 0;
+ public static final int FMT_UINT8_I = 1;
+ public static final int FMT_UINT16_RGB = 2;
+ public static final int FMT_UINT16_I = 3;
+
+ /**
+ * Open a video file for reading frames.
+ *
+ * @param path Path to file.
+ * @throws IOException On an i/o error.
+ * @see #close
+ */
+ public VideoReader(String path) throws IOException {
+ reader = new JJMediaReader(path);
+ List<JJMediaReader.JJReaderVideo> streams = new ArrayList<>();
+
+ for (JJMediaReader.JJReaderStream rs: reader.streams()) {
+ if (rs.getType() == AVMediaTypeBits.AVMEDIA_TYPE_VIDEO) {
+ JJMediaReader.JJReaderVideo rv = (JJMediaReader.JJReaderVideo) rs;
+ rs.open();
+ rs.getContext().setThreadCount(4);
+ streams.add(rv);
+ }
+ }
+
+ videos = new JJMediaReader.JJReaderVideo[streams.size()];
+ frames = new AVFrame[videos.length];
+ pixels = new AVPixelReader[videos.length];
+ for (int i = 0; i < streams.size(); i++) {
+ JJMediaReader.JJReaderVideo rv = streams.get(i);
+ videos[i] = rv;
+ frames[i] = rv.getFrame();
+ pixels[i] = frames[i].getPixelReader(rv.getWidth(), rv.getHeight(), 0);
+ }
+ }
+
+ /**
+ * Close the video stream.
+ * <p>
+ * Video streams should be closed explicitly when they are no longer needed,
+ * however they will be garabage collected either way.
+ */
+ public void close() {
+ if (reader != null) {
+ reader.close();
+ reader = null;
+ Arrays.fill(videos, null);
+ Arrays.fill(frames, null);
+ Arrays.fill(pixels, null);
+ }
+ }
+
+ /**
+ * Retrieve the number of video streams in the file.
+ *
+ * @return The number of streams found.
+ */
+ public int getStreamCount() {
+ return videos.length;
+ }
+
+ /**
+ * Retrieve the frame width of the specified stream.
+ * <p>
+ * This may be called at any time.
+ *
+ * @param sid Stream id.
+ * @return The video frame width.
+ * @throws ArrayIndexOutOfBoundsException if sid is invalid.
+ */
+ public int getWidth(int sid) {
+ return videos[sid].getWidth();
+ }
+
+ /**
+ * Retrieve the frame height of the specified stream.
+ * <p>
+ * This may be called at any time.
+ *
+ * @param sid Stream id.
+ * @return The video frame height.
+ * @throws ArrayIndexOutOfBoundsException if sid is invalid.
+ */
+ public int getHeight(int sid) {
+ return videos[sid].getHeight();
+ }
+
+ /**
+ * Retrieve the timestamp of the current frame in milliseconds.
+ * <p>
+ * The timestamp is only valid if the sid matches the last sid
+ * returned by nextFrame().
+ *
+ * @param sid Stream to query.
+ * @return Frame timestamp in milliseconds.
+ */
+ public long getTimeStamp(int sid) {
+ return videos[sid].convertPTS(frames[sid].getPTS());
+ }
+
+ /**
+ * Retrieve frame pixels in a specific format.
+ * <p>
+ * This converts the frame to the specified format and
+ * returns it as a single-dimensional vector of the specified type.
+ * <p>
+ * The frame data is only valid if the sid matches the last sid
+ * returned by nextFrame().
+ *
+ * @param sid Stream id of frame.
+ * @param format One of the FMT_* constants.
+ * @return A primitive array containing the pixels. It will contain width * height * depth
+ * elements in channel-packed row order.
+ * @see #nextFrame()
+ */
+ public Object getPixels(int sid, int format) {
+ JJMediaReader.JJReaderVideo vs = videos[sid];
+ int width = vs.getWidth();
+ int height = vs.getHeight();
+ byte[] bytes;
+ short[] shorts;
+
+ switch (format) {
+ case FMT_UINT8_RGB:
+ bytes = new byte[width * height * 3];
+ pixels[sid].getPixels(0, height, AVPixelFormat.AV_PIX_FMT_RGB24, bytes, 0, width * 3);
+ return bytes;
+ case FMT_UINT8_I:
+ bytes = new byte[width * height];
+ pixels[sid].getPixels(0, height, AVPixelFormat.AV_PIX_FMT_GRAY8, bytes, 0, width);
+ return bytes;
+ case FMT_UINT16_RGB:
+ shorts = new short[width * height * 3];
+ pixels[sid].getPixels(0, height, AVPixelFormat.AV_PIX_FMT_RGB48, shorts, 0, width * 3);
+ return shorts;
+ case FMT_UINT16_I:
+ shorts = new short[width * height];
+ pixels[sid].getPixels(0, height, AVPixelFormat.AV_PIX_FMT_GRAY16, shorts, 0, width);
+ return shorts;
+ }
+ return null;
+ }
+
+ /**
+ * Advance to the next frame of the specified stream.
+ * <p>
+ * Intervening frames from other streams will be dropped.
+ *
+ * @param sid Desired stream, or -1 for any stream.
+ * @return The streamid of the frame received or -1 for end of file.
+ * @throws IOException On an i/o error.
+ */
+ public int nextFrame(int sid) throws IOException {
+ int next;
+
+ if (sid == -1)
+ return nextFrame();
+
+ do {
+ next = nextFrame();
+ } while (next != -1 && next != sid);
+
+ return next;
+ }
+
+ /**
+ * Advance to the next frame in the file.
+ *
+ * @return The streamid of the frame received or -1 for end of file.
+ * @throws IOException On an i/o error.
+ */
+ public int nextFrame() throws IOException {
+ while (true) {
+ JJMediaReader.JJReaderStream rs = reader.readFrame();
+ if (rs == null)
+ return -1;
+ for (int i = 0; i < videos.length; i++)
+ if (videos[i] == rs) {
+ return i;
+ }
+ }
+ }
+}