From 8ccf3d9c14fbbd3281dac09e4a0856661acbe067 Mon Sep 17 00:00:00 2001 From: Thomas Clark Date: Tue, 30 Dec 2014 02:17:03 -0500 Subject: [PATCH] Add AxisCamera Java class The API is basically the same as the C++ one. The JNI function for Priv_ReadJPEGString_C was manually renamed, since the python scripts don't name the C++ functions correctly, causing an UnsatisfiedLinkError at runtime. If further changes are made to the bindings, either the method will have to be manually renamed again after the code is regenerated, or the python scripts will have to be updated. The old ignored edu.wpi.first.wpilibj.camera package was removed. Change-Id: Icd37fc15c7bb41061568c3b2f580c6765cbf0300 --- wpilibc/wpilibC++Devices/include/nivision.h | 2 + .../src/Vision/AxisCamera.cpp | 4 - .../src/main/java/com/ni/vision/NIVision.java | 10 + .../wpi/first/wpilibj/camera/AxisCamera.java | 488 --------------- .../wpilibj/camera/AxisCameraException.java | 22 - .../edu/wpi/first/wpilibj/camera/package.html | 11 - .../wpi/first/wpilibj/vision/AxisCamera.java | 556 ++++++++++++++++++ wpilibj/wpilibJavaJNI/lib/NIVisionJNI.cpp | 11 + .../wpilibJavaJNI/nivision/nivision_2011.ini | 3 + 9 files changed, 582 insertions(+), 525 deletions(-) delete mode 100644 wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/camera/AxisCamera.java delete mode 100644 wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/camera/AxisCameraException.java delete mode 100644 wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/camera/package.html create mode 100644 wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/vision/AxisCamera.java diff --git a/wpilibc/wpilibC++Devices/include/nivision.h b/wpilibc/wpilibC++Devices/include/nivision.h index 14de95d41c..09bb9842e7 100644 --- a/wpilibc/wpilibC++Devices/include/nivision.h +++ b/wpilibc/wpilibC++Devices/include/nivision.h @@ -5339,5 +5339,7 @@ IMAQ_FUNC ReadTextReport* IMAQ_STDCALL imaqReadText(const Image* image, c IMAQ_FUNC ThresholdData* IMAQ_STDCALL imaqAutoThreshold(Image* dest, Image* source, int numClasses, ThresholdMethod method); IMAQ_FUNC ColorHistogramReport* IMAQ_STDCALL imaqColorHistogram(Image* image, int numClasses, ColorMode mode, const Image* mask); IMAQ_FUNC RakeReport* IMAQ_STDCALL imaqRake(const Image* image, const ROI* roi, RakeDirection direction, EdgeProcess process, const RakeOptions* options); + +IMAQ_FUNC int IMAQ_STDCALL Priv_ReadJPEGString_C(Image* image, const unsigned char* string, unsigned int stringLength); #endif diff --git a/wpilibc/wpilibC++Devices/src/Vision/AxisCamera.cpp b/wpilibc/wpilibC++Devices/src/Vision/AxisCamera.cpp index 5342111e77..47658d0eee 100644 --- a/wpilibc/wpilibC++Devices/src/Vision/AxisCamera.cpp +++ b/wpilibc/wpilibC++Devices/src/Vision/AxisCamera.cpp @@ -18,10 +18,6 @@ #include #include -/** Private NI function to decode JPEG */ -IMAQ_FUNC int Priv_ReadJPEGString_C(Image* _image, const unsigned char* _string, - uint32_t _stringLength); - static const unsigned int kMaxPacketSize = 1536; static const unsigned int kImageBufferAllocationIncrement = 1000; diff --git a/wpilibj/wpilibJavaDevices/src/main/java/com/ni/vision/NIVision.java b/wpilibj/wpilibJavaDevices/src/main/java/com/ni/vision/NIVision.java index a20aaf89c2..e9a5db39ab 100644 --- a/wpilibj/wpilibJavaDevices/src/main/java/com/ni/vision/NIVision.java +++ b/wpilibj/wpilibJavaDevices/src/main/java/com/ni/vision/NIVision.java @@ -25004,6 +25004,16 @@ public class NIVision { } private static native long _imaqRake(long image, long roi, int direction, int process, long options); + public static void Priv_ReadJPEGString_C(Image image, byte[] string) { + int stringLength = string.length; + ByteBuffer string_buf = null; + string_buf = ByteBuffer.allocateDirect(string.length); + putBytes(string_buf, string, 0, string.length); + _Priv_ReadJPEGString_C(image.getAddress(), getByteBufferAddress(string_buf), stringLength); + + } + private static native void _Priv_ReadJPEGString_C(long image, long string, int stringLength); + /** * Purpose : Include file for NI-IMAQdx library support. */ diff --git a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/camera/AxisCamera.java b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/camera/AxisCamera.java deleted file mode 100644 index 3325ebddb9..0000000000 --- a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/camera/AxisCamera.java +++ /dev/null @@ -1,488 +0,0 @@ -/*----------------------------------------------------------------------------*/ -/* Copyright (c) FIRST 2008-2012. All Rights Reserved. */ -/* Open Source Software - may be modified and shared by FRC teams. The code */ -/* must be accompanied by the FIRST BSD license file in the root directory of */ -/* the project. */ -/*----------------------------------------------------------------------------*/ -package edu.wpi.first.wpilibj.camera; - -import com.sun.jna.BlockingFunction; -import com.sun.jna.Function; -import com.sun.jna.NativeLibrary; -import com.sun.jna.Pointer; -import com.sun.jna.TaskExecutor; -import com.sun.squawk.VM; -import edu.wpi.first.wpilibj.DriverStation; -import edu.wpi.first.wpilibj.communication.UsageReporting; -import edu.wpi.first.wpilibj.image.ColorImage; -import edu.wpi.first.wpilibj.image.HSLImage; -import edu.wpi.first.wpilibj.image.NIVisionException; -import edu.wpi.first.wpilibj.util.BoundaryException; - -//TODO figure out where to use finally to free resources -//TODO go through old camera code and make sure all features are implemented -//TODO make work with all three passwords -//TODO get images of different types -//TODO continue attempting to connect until succesful -//TODO optimize and use Pointers in all locations which make sense - possibly JNA memcpy? -/** - * This class is a singleton used to configure and get images from the axis camera. - * @author dtjones - */ -public class AxisCamera { - - private static AxisCamera m_instance = null; - - /** - * Enumaration representing the different values which exposure may be set to. - */ - public static class ExposureT { - - /** - * The integer value of the enumeration. - */ - public final int value; - static final ExposureT[] allValues = new ExposureT[4]; - /** - * The Axis camera automatically determines what exposure level to use. - */ - public static final ExposureT automatic = new ExposureT(0); - /** - * Hold the current exposure level. - */ - public static final ExposureT hold = new ExposureT(1); - /** - * Set exposure for flicker free 50 hz. - */ - public static final ExposureT flickerfree50 = new ExposureT(2); - /** - * Set exposure for flicker free 60 hz. - */ - public static final ExposureT flickerfree60 = new ExposureT(3); - - private ExposureT(int value) { - this.value = value; - allValues[value] = this; - } - - private static ExposureT get(int val) { - return allValues[val]; - } - } - - /** - * Enumeration representing the different values which white balence may be - * set to. - */ - public static class WhiteBalanceT { - - /** - * The integer value of the enumeration. - */ - public final int value; - static final WhiteBalanceT[] allValues = new WhiteBalanceT[7]; - /** - * The axis camera automatically adjusts the whit balance. - */ - public static final WhiteBalanceT automatic = new WhiteBalanceT(0); - /** - * Hold the current white balance. - */ - public static final WhiteBalanceT hold = new WhiteBalanceT(1); - /** - * White balance for outdoors. - */ - public static final WhiteBalanceT fixedOutdoor1 = new WhiteBalanceT(2); - /** - * White balance for outdoors. - */ - public static final WhiteBalanceT fixedOutdoor2 = new WhiteBalanceT(3); - /** - * White balance for indoors. - */ - public static final WhiteBalanceT fixedIndoor = new WhiteBalanceT(4); - /** - * White balance for fourescent lighting. - */ - public static final WhiteBalanceT fixedFlour1 = new WhiteBalanceT(5); - /** - * White balance for fourescent lighting. - */ - public static final WhiteBalanceT fixedFlour2 = new WhiteBalanceT(6); - - private WhiteBalanceT(int value) { - this.value = value; - allValues[value] = this; - } - - private static WhiteBalanceT get(int value) { - return allValues[value]; - } - } - - /** - * Enumeration representing the image resoultion provided by the camera. - */ - public static class ResolutionT { - - /** - * The integer value of the enumeration. - */ - public final int value; - /** - * Number of pixels wide. - */ - public final int width; - /** - * Number of pixels tall. - */ - public final int height; - static final ResolutionT[] allValues = new ResolutionT[4]; - /** - * ImageBase is 640 pixels wide by 480 tall - */ - public static final ResolutionT k640x480 = new ResolutionT(0, 640, 480); - /** - * ImageBase is 640 pixels wide by 360 tall - */ - public static final ResolutionT k640x360 = new ResolutionT(1, 640, 360); - /** - * ImageBase is 320 pixels wide by 240 tall - */ - public static final ResolutionT k320x240 = new ResolutionT(2, 320, 240); - /** - * ImageBase is 160 pixels wide by 120 tall - */ - public static final ResolutionT k160x120 = new ResolutionT(3, 160, 120); - - private ResolutionT(int value, int horizontal, int vertical) { - this.value = value; - this.width = horizontal; - this.height = vertical; - allValues[value] = this; - } - - private static ResolutionT get(int value) { - return allValues[value]; - } - } - - /** - * Enumeration representing the orientation of the picture. - */ - public static class RotationT { - - /** - * The integer value of the enumeration. - */ - public final int value; - static final RotationT[] allValues = new RotationT[2]; - /** - * Picture is right side up. - */ - public static final RotationT k0 = new RotationT(0); - /** - * Picture is rotated 180 degrees. - */ - public static final RotationT k180 = new RotationT(1); - - private RotationT(int value) { - this.value = value; - allValues[value] = this; - } - - private static RotationT get(int value) { - return allValues[value]; - } - } - - /** - * Enumeration representing the exposure priority. - */ - public static class ExposurePriorityT { - - /** - * The integer value of the enumeration. - */ - public final int value; - static final ExposurePriorityT[] allValues = new ExposurePriorityT[3]; - /** - * Prioritize image quality. - */ - public static final ExposurePriorityT imageQuality = new ExposurePriorityT(0); - /** - * No prioritization. - */ - public static final ExposurePriorityT none = new ExposurePriorityT(50); - /** - * Prioritize frame rate. - */ - public static final ExposurePriorityT frameRate = new ExposurePriorityT(100); - - private ExposurePriorityT(int value) { - this.value = value; - allValues[value / 50] = this; - } - - private static ExposurePriorityT get(int value) { - return allValues[value / 50]; - } - } - - /** - * Get a reference to the AxisCamera, or initialize the AxisCamera if it - * has not yet been initialized. If the camera is connected to the - * Ethernet switch on the robot, then this address should be 10.x.y.11 - * where x.y are your team number subnet address (same as the other IP - * addresses on the robot network). - * @param address A string containing the IP address for the camera in the - * form "10.x.y.2" for cameras on the Ethernet switch or "192.168.0.90" - * for cameras connected to the 2nd Ethernet port on an 8-slot cRIO. - * @return A reference to the AxisCamera. - */ - public static synchronized AxisCamera getInstance(String address) { - if (m_instance == null) { - m_instance = new AxisCamera(address); - // XXX: Resource Reporting Fixes -// UsageReporting.report(UsageReporting.kResourceType_AxisCamera, 2); - } - return m_instance; - } - - /** - * Get a reference to the AxisCamera, or initialize the AxisCamera if it - * has not yet been initialized. By default this will connect to a camera - * with an IP address of 10.x.y.11 with the preference that the camera be - * connected to the Ethernet switch on the robot rather than port 2 of the - * 8-slot cRIO. - * @return A reference to the AxisCamera. - */ - public static synchronized AxisCamera getInstance() { - if (m_instance == null) { - DriverStation.getInstance().waitForData(); - int teamNumber = DriverStation.getInstance().getTeamNumber(); - String address = "10."+(teamNumber/100)+"."+(teamNumber%100)+".11"; - m_instance = new AxisCamera(address); - UsageReporting.report(UsageReporting.kResourceType_AxisCamera, 1); - } - - return m_instance; - } - private static final Function cameraStartFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraStart"); - - /** - * Axis camera constructor that calls the C++ library to actually create the instance. - * @param IPAddress - */ - AxisCamera(String IPAddress) { - Pointer ptr = Pointer.createStringBuffer(IPAddress); - cameraStartFn.call1(ptr); - } - private static final TaskExecutor cameraTaskExecutor = new TaskExecutor("camera task executor"); - private static final BlockingFunction getImageFn = NativeLibrary.getDefaultInstance().getBlockingFunction("AxisCameraGetImage"); - - static { - getImageFn.setTaskExecutor(cameraTaskExecutor); - } - - /** - * Get an image from the camera. Be sure to free the image when you are done with it. - * @return A new image from the camera. - */ - public ColorImage getImage() throws AxisCameraException, NIVisionException { - ColorImage image = new HSLImage(); - if (getImageFn.call1(image.image) == 0) { - image.free(); - throw new AxisCameraException("No image available"); - } - return image; - } - // Mid-stream gets & writes - private static final Function writeBrightnessFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraWriteBrightness"); - - /** - * Write the brightness for the camera to use. - * @param brightness This must be an integer between 0 and 100, with 100 being the brightest - */ - public void writeBrightness(int brightness) { - BoundaryException.assertWithinBounds(brightness, 0, 100); - writeBrightnessFn.call1(brightness); - } - private static final Function getBrightnessFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraGetBrightness"); - - /** - * Get the current brightness of the AxisCamera - * @return An integer representing the current brightness of the axis - * camera with 0 being the darkest and 100 being the brightest. - */ - public int getBrightness() { - return getBrightnessFn.call0(); - } - private static final Function writeWhiteBalenceFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraWriteWhiteBalance"); - - /** - * Write the WhiteBalance for the camera to use. - * @param whiteBalance The value to set the white balance to on the camera. - */ - public void writeWhiteBalance(WhiteBalanceT whiteBalance) { - writeWhiteBalenceFn.call1(whiteBalance.value); - } - private static final Function getWhiteBalanceFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraGetWhiteBalance"); - - /** - * Get the white balance set on the camera. - * @return The white balance currently set on the camera. - */ - public WhiteBalanceT getWhiteBalance() { - return WhiteBalanceT.get(getWhiteBalanceFn.call0()); - } - private static final Function writeColorLevelFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraWriteColorLevel"); - - /** - * Set the color level of images returned from the camera. - * @param value This must be an integer from 0 to 100 with 0 being black and white. - */ - public void writeColorLevel(int value) { - BoundaryException.assertWithinBounds(value, 0, 100); - writeColorLevelFn.call1(value); - } - private static final Function getColorLevelFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraGetColorLevel"); - - /** - * Get the color level of images being retunred from the camera. - * @return The color level of images being returned from the camera. 0 is black and white. - */ - public int getColorLevel() { - return getColorLevelFn.call0(); - } - private static final Function writeExposureControlFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraWriteExposureControl"); - - /** - * Write the exposure mode for the camera to use. - * @param value The expsure mode for the camera to use. - */ - public void writeExposureControl(ExposureT value) { - writeExposureControlFn.call1(value.value); - } - private static final Function getExposureControlFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraGetExposureControl"); - - /** - * Get the exposure mode that the camera is using. - * @return The exposure mode that the camera is using. - */ - public ExposureT getExposureControl() { - return ExposureT.get(getExposureControlFn.call0()); - } - private static final Function writeExposurePriorityFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraWriteExposurePriority"); - - /** - * Write the exposure priority for the camera to use. - * @param value The exposure priority to use. - */ - public void writeExposurePriority(ExposurePriorityT value) { - writeExposurePriorityFn.call1(value.value); - } - private static final Function getExposurePriorityFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraGetExposurePriority"); - - /** - * Get the exposure priority that the camera is using. - * @return The exposure priority that the camera is using - */ - public ExposurePriorityT getExposurePriority() { - return ExposurePriorityT.get(getExposurePriorityFn.call0()); - } - // New-Stream gets & writes - private static final Function writeResolutionFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraWriteResolution"); - - /** - * Set the resolution of the images to return. - * @param value The resolution imaga for the camera to return. - */ - public void writeResolution(ResolutionT value) { - writeResolutionFn.call1(value.value); - } - private static final Function getResolutionFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraGetResolution"); - - /** - * Get the resolution fo the images that the camera is returning. - * @return The resolution of the images that the camera is returning. - */ - public ResolutionT getResolution() { - return ResolutionT.get(getResolutionFn.call0()); - } - private static final Function writeCompressionFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraWriteCompression"); - - /** - * Write the level of JPEG compression to use on images returned from the camera. - * @param value The level of JPEG compression to use from 0 to 100 with 0 being the least compression. - */ - public void writeCompression(int value) { - BoundaryException.assertWithinBounds(value, 0, 100); - writeCompressionFn.call1(value); - } - private static final Function getCompressionFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraGetCompression"); - - /** - * Get the compression of the images eing returned by the camera. - * @return The level of compression of the image being returned from the - * camera with 0 being the least and 100 being the greatest. - */ - public int getCompression() { - return getCompressionFn.call0(); - } - private static final Function writeRotationFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraWriteRotation"); - - /** - * Write the rotation of images for the camera to return. - * @param value The rotation to be applied to images from the camera. - */ - public void writeRotation(RotationT value) { - writeRotationFn.call1(value.value); - } - private static final Function getRotationFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraGetRotation"); - - /** - * Get the rotation of the images returned from the camera. - * @return The rotation of the images returned from the camera. - */ - public RotationT getRotation() { - return RotationT.get(getRotationFn.call0()); - } - private static final Function freshImageFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraFreshImage"); - - /** - * Has the current image from the camera been retrieved yet. - * @return true if the latest image from the camera has not been retrieved yet. - */ - public boolean freshImage() { - return freshImageFn.call0() == 0 ? false : true; - } - private static final Function getMaxFPSFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraGetMaxFPS"); - - /** - * Get the maximum frames per second that the camera will generate. - * @return the max fps. - */ - public int getMaxFPS() { - return getMaxFPSFn.call0(); - } - private static final Function writeMaxFPSFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraWriteMaxFPS"); - - /** - * Set the maximum frames per second that the camera will generate. - * @param value the new max fps - */ - public void writeMaxFPS(int value) { - writeMaxFPSFn.call1(value); - } - private static final Function deleteInstanceFn = NativeLibrary.getDefaultInstance().getFunction("AxisCameraDeleteInstance"); - - static { - VM.addShutdownHook(new Thread() { - - public void run() { - deleteInstanceFn.call0(); - } - }); - } -} diff --git a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/camera/AxisCameraException.java b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/camera/AxisCameraException.java deleted file mode 100644 index 9797c34f3f..0000000000 --- a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/camera/AxisCameraException.java +++ /dev/null @@ -1,22 +0,0 @@ -/*----------------------------------------------------------------------------*/ -/* Copyright (c) FIRST 2008-2012. All Rights Reserved. */ -/* Open Source Software - may be modified and shared by FRC teams. The code */ -/* must be accompanied by the FIRST BSD license file in the root directory of */ -/* the project. */ -/*----------------------------------------------------------------------------*/ - -package edu.wpi.first.wpilibj.camera; - -/** - * An exception representing a problem with communicating with the camera. - * @author dtjones - */ -public class AxisCameraException extends Exception { - /** - * Create a new AxisCameraException. - * @param msg The message to pass with the AxisCameraException. - */ - public AxisCameraException(String msg) { - super(msg); - } -} diff --git a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/camera/package.html b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/camera/package.html deleted file mode 100644 index 7f85669c35..0000000000 --- a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/camera/package.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - FRC Camera Library - - - -Provides classes for interfacing to the camera. - - - \ No newline at end of file diff --git a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/vision/AxisCamera.java b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/vision/AxisCamera.java new file mode 100644 index 0000000000..c69ff5c981 --- /dev/null +++ b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/vision/AxisCamera.java @@ -0,0 +1,556 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2014. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.wpilibj.vision; + +import edu.wpi.first.wpilibj.image.ColorImage; +import edu.wpi.first.wpilibj.image.HSLImage; +import edu.wpi.first.wpilibj.image.NIVisionException; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.ByteBuffer; + +import static com.ni.vision.NIVision.Image; +import static com.ni.vision.NIVision.Priv_ReadJPEGString_C; +import static edu.wpi.first.wpilibj.Timer.delay; + +/** + * Axis M1011 network camera + */ +public class AxisCamera { + public enum WhiteBalance { + kAutomatic, + kHold, + kFixedOutdoor1, + kFixedOutdoor2, + kFixedIndoor, + kFixedFluorescent1, + kFixedFluorescent2, + } + + public enum ExposureControl { + kAutomatic, + kHold, + kFlickerFree50Hz, + kFlickerFree60Hz, + } + + public enum Resolution { + k640x480, + k480x360, + k320x240, + k240x180, + k176x144, + k160x120, + } + + public enum Rotation { + k0, k180 + } + + private static final String[] kWhiteBalanceStrings = + {"auto", "hold", "fixed_outdoor1", "fixed_outdoor2", "fixed_indoor", + "fixed_fluor1", "fixed_fluor2",}; + + private static final String[] kExposureControlStrings = + {"auto", "hold", "flickerfree50", "flickerfree60",}; + + private static final String[] kResolutionStrings = + {"640x480", "480x360", "320x240", "240x180", "176x144", "160x120",}; + + private static final String[] kRotationStrings = + {"0", "180",}; + + private final static int kImageBufferAllocationIncrement = 1000; + + private String m_cameraHost; + private Socket m_cameraSocket; + + private ByteBuffer m_imageData = ByteBuffer.allocate(5000); + private final Object m_imageDataLock = new Object(); + private boolean m_freshImage = false; + + private int m_brightness = 50; + private WhiteBalance m_whiteBalance = WhiteBalance.kAutomatic; + private int m_colorLevel = 50; + private ExposureControl m_exposureControl = ExposureControl.kAutomatic; + private int m_exposurePriority = 50; + private int m_maxFPS = 0; + private Resolution m_resolution = Resolution.k640x480; + private int m_compression = 50; + private Rotation m_rotation = Rotation.k0; + private final Object m_parametersLock = new Object(); + private boolean m_parametersDirty = true; + private boolean m_streamDirty = true; + + private boolean m_done = false; + + /** + * AxisCamera constructor + * + * @param cameraHost The host to find the camera at, typically an IP address + */ + public AxisCamera(String cameraHost) { + m_cameraHost = cameraHost; + m_captureThread.start(); + } + + /** + * Return true if the latest image from the camera has not been retrieved by calling GetImage() yet. + * @return true if the image has not been retrieved yet. + */ + public boolean isFreshImage() { + return m_freshImage; + } + + /** + * Get an image from the camera and store it in the provided image. + * + * @param image The imaq image to store the result in. This must be an HSL or RGB image. + * @return true upon success, false on a failure + */ + public boolean getImage(Image image) { + if (m_imageData.limit() == 0) { + return false; + } + + synchronized (m_imageDataLock) { + Priv_ReadJPEGString_C(image, m_imageData.array()); + } + + m_freshImage = false; + + return true; + } + + /** + * Get an image from the camera and store it in the provided image. + * + * @param image The image to store the result in. This must be an HSL or RGB image + * @return true upon success, false on a failure + */ + public boolean getImage(ColorImage image) { + return this.getImage(image.image); + } + + /** + * Instantiate a new image object and fill it with the latest image from the camera. + * + * @return a pointer to an HSLImage object + */ + public HSLImage getImage() throws NIVisionException { + HSLImage image = new HSLImage(); + this.getImage(image); + return image; + } + + /** + * Request a change in the brightness of the camera images. + * + * @param brightness valid values 0 .. 100 + */ + public void writeBrightness(int brightness) { + if (brightness < 0 || brightness > 100) { + throw new IllegalArgumentException("Brightness must be from 0 to 100"); + } + + synchronized (m_parametersLock) { + if (m_brightness != brightness) { + m_brightness = brightness; + m_parametersDirty = true; + } + } + } + + /** + * @return The configured brightness of the camera images + */ + public int getBrightness() { + synchronized (m_parametersLock) { + return m_brightness; + } + } + + /** + * Request a change in the white balance on the camera. + * + * @param whiteBalance Valid values from the WhiteBalance enum. + */ + public void writeWhiteBalance(WhiteBalance whiteBalance) { + synchronized (m_parametersLock) { + if (m_whiteBalance != whiteBalance) { + m_whiteBalance = whiteBalance; + m_parametersDirty = true; + } + } + } + + /** + * @return The configured white balances of the camera images + */ + public WhiteBalance getWhiteBalance() { + synchronized (m_parametersLock) { + return m_whiteBalance; + } + } + + /** + * Request a change in the color level of the camera images. + * + * @param colorLevel valid values are 0 .. 100 + */ + public void writeColorLevel(int colorLevel) { + if (colorLevel < 0 || colorLevel > 100) { + throw new IllegalArgumentException("Color level must be from 0 to 100"); + } + + synchronized (m_parametersLock) { + if (m_colorLevel != colorLevel) { + m_colorLevel = colorLevel; + m_parametersDirty = true; + } + } + } + + /** + * @return The configured color level of the camera images + */ + public int getColorLevel() { + synchronized (m_parametersLock) { + return m_colorLevel; + } + } + + /** + * Request a change in the camera's exposure mode. + * + * @param exposureControl A mode to write in the Exposure enum. + */ + public void writeExposureControl(ExposureControl exposureControl) { + synchronized (m_parametersLock) { + if (m_exposureControl != exposureControl) { + m_exposureControl = exposureControl; + m_parametersDirty = true; + } + } + } + + /** + * @return The configured exposure control mode of the camera + */ + public ExposureControl getExposureControl() { + synchronized (m_parametersLock) { + return m_exposureControl; + } + } + + /** + * Request a change in the exposure priority of the camera. + * + * @param exposurePriority Valid values are 0, 50, 100. + * 0 = Prioritize image quality + * 50 = None + * 100 = Prioritize frame rate + */ + public void writeExposurePriority(int exposurePriority) { + if (exposurePriority != 0 && exposurePriority != 50 && exposurePriority != 100) { + throw new IllegalArgumentException("Exposure priority must be 0, 50, or 100"); + } + + synchronized (m_parametersLock) { + if (m_exposurePriority != exposurePriority) { + m_exposurePriority = exposurePriority; + m_parametersDirty = true; + } + } + } + + /** + * @return The configured exposure priority of the camera + */ + public int getExposurePriority() { + synchronized (m_parametersLock) { + return m_exposurePriority; + } + } + + /** + * Write the maximum frames per second that the camera should send + * Write 0 to send as many as possible. + * + * @param maxFPS The number of frames the camera should send in a second, exposure permitting. + */ + public void writeMaxFPS(int maxFPS) { + synchronized (m_parametersLock) { + if (m_maxFPS != maxFPS) { + m_maxFPS = maxFPS; + m_parametersDirty = true; + m_streamDirty = true; + } + } + } + + /** + * @return The configured maximum FPS of the camera + */ + public int getMaxFPS() { + synchronized (m_parametersLock) { + return m_maxFPS; + } + } + + /** + * Write resolution value to camera. + * + * @param resolution The camera resolution value to write to the camera. + */ + public void writeResolution(Resolution resolution) { + synchronized (m_parametersLock) { + if (m_resolution != resolution) { + m_resolution = resolution; + m_parametersDirty = true; + m_streamDirty = true; + } + } + } + + /** + * @return The configured resolution of the camera (not necessarily the same + * resolution as the most recent image, if it was changed recently.) + */ + public Resolution getResolution() { + synchronized (m_parametersLock) { + return m_resolution; + } + } + + /** + * Write the compression value to the camera. + * + * @param compression Values between 0 and 100. + */ + public void writeCompression(int compression) { + if (compression < 0 || compression > 100) { + throw new IllegalArgumentException("Compression must be from 0 to 100"); + } + + synchronized (m_parametersLock) { + if (m_compression != compression) { + m_compression = compression; + m_parametersDirty = true; + m_streamDirty = true; + } + } + } + + /** + * @return The configured compression level of the camera images + */ + public int getCompression() { + synchronized (m_parametersLock) { + return m_compression; + } + } + + /** + * Write the rotation value to the camera. + * If you mount your camera upside down, use this to adjust the image for you. + * + * @param rotation A value from the {@link Rotation} enum + */ + public void writeRotation(Rotation rotation) { + synchronized (m_parametersLock) { + if (m_rotation != rotation) { + m_rotation = rotation; + m_parametersDirty = true; + m_streamDirty = true; + } + } + } + + /** + * @return The configured rotation mode of the camera + */ + public Rotation getRotation() { + synchronized (m_parametersLock) { + return m_rotation; + } + } + + /** + * Thread spawned by AxisCamera constructor to receive images from cam + */ + private Thread m_captureThread = new Thread(new Runnable() { + @Override + public void run() { + int consecutiveErrors = 0; + + // Loop on trying to setup the camera connection. This happens in a background + // thread so it shouldn't effect the operation of user programs. + while (!m_done) { + String requestString = "GET /mjpg/video.mjpg HTTP/1.1\n" + + "User-Agent: HTTPStreamClient\n" + + "Connection: Keep-Alive\n" + + "Cache-Control: no-cache\n" + + "Authorization: Basic RlJDOkZSQw==\n\n"; + + try { + m_cameraSocket = AxisCamera.this.createCameraSocket(requestString); + AxisCamera.this.readImagesFromCamera(); + consecutiveErrors = 0; + } catch (IOException e) { + consecutiveErrors++; + + if (consecutiveErrors > 5) { + e.printStackTrace(); + } + } + + delay(0.5); + } + } + }); + + /** + * This function actually reads the images from the camera. + */ + private void readImagesFromCamera() throws IOException { + DataInputStream cameraInputStream = new DataInputStream(m_cameraSocket.getInputStream()); + + while (!m_done) { + String line = cameraInputStream.readLine(); + + if (line.startsWith("Content-Length: ")) { + int contentLength = Integer.valueOf(line.substring(16)); + + /* Skip the next blank line */ + cameraInputStream.readLine(); + contentLength -= 4; + + /* The next four bytes are the JPEG magic number */ + byte[] data = new byte[contentLength]; + cameraInputStream.readFully(data); + + synchronized (m_imageDataLock) { + if (m_imageData.capacity() < data.length) { + m_imageData = ByteBuffer.allocate(data.length + kImageBufferAllocationIncrement); + } + + m_imageData.clear(); + m_imageData.limit(contentLength); + m_imageData.put(data); + + m_freshImage = true; + } + + if (this.writeParameters()) { + break; + } + + /* Skip the boundary and Content-Type header */ + cameraInputStream.readLine(); + cameraInputStream.readLine(); + } + } + + m_cameraSocket.close(); + } + + /** + * Send a request to the camera to set all of the parameters. This is called + * in the capture thread between each frame. This strategy avoids making lots + * of redundant HTTP requests, accounts for failed initial requests, and + * avoids blocking calls in the main thread unless necessary. + *

+ * This method does nothing if no parameters have been modified since it last + * completely successfully. + * + * @return true if the stream should be restarted due to a + * parameter changing. + */ + private boolean writeParameters() { + if (m_parametersDirty) { + String request = "GET /axis-cgi/admin/param.cgi?action=update"; + + synchronized (m_parametersLock) { + request += "&ImageSource.I0.Sensor.Brightness=" + m_brightness; + request += "&ImageSource.I0.Sensor.WhiteBalance=" + kWhiteBalanceStrings[m_whiteBalance.ordinal()]; + request += "&ImageSource.I0.Sensor.ColorLevel=" + m_colorLevel; + request += "&ImageSource.I0.Sensor.Exposure=" + kExposureControlStrings[m_exposureControl.ordinal()]; + request += "&ImageSource.I0.Sensor.ExposurePriority=" + m_exposurePriority; + request += "&Image.I0.Stream.FPS=" + m_maxFPS; + request += "&Image.I0.Appearance.Resolution=" + kResolutionStrings[m_resolution.ordinal()]; + request += "&Image.I0.Appearance.Compression=" + m_compression; + request += "&Image.I0.Appearance.Rotation=" + kRotationStrings[m_rotation.ordinal()]; + } + + request += " HTTP/1.1\n"; + request += "User-Agent: HTTPStreamClient\n"; + request += "Connection: Keep-Alive\n"; + request += "Cache-Control: no-cache\n"; + request += "Authorization: Basic RlJDOkZSQw==\n\n"; + + try { + Socket socket = this.createCameraSocket(request); + socket.close(); + + m_parametersDirty = false; + + if (m_streamDirty) { + m_streamDirty = false; + return true; + } else { + return false; + } + } catch (IOException | NullPointerException e) { + return false; + } + + } + + return false; + } + + /** + * Create a socket connected to camera + * Used to create a connection for reading images and setting parameters + * + * @param requestString The initial request string to send upon successful connection. + * @return The created socket + */ + private Socket createCameraSocket(String requestString) throws IOException { + /* Connect to the server */ + Socket socket = new Socket(); + socket.connect(new InetSocketAddress(m_cameraHost, 80), 5000); + + /* Send the HTTP headers */ + OutputStream socketOutputStream = socket.getOutputStream(); + socketOutputStream.write(requestString.getBytes()); + + return socket; + } + + @Override + public String toString() { + return "AxisCamera{" + + "FreshImage=" + isFreshImage() + + ", Brightness=" + getBrightness() + + ", WhiteBalance=" + getWhiteBalance() + + ", ColorLevel=" + getColorLevel() + + ", ExposureControl=" + getExposureControl() + + ", ExposurePriority=" + getExposurePriority() + + ", MaxFPS=" + getMaxFPS() + + ", Resolution=" + getResolution() + + ", Compression=" + getCompression() + + ", Rotation=" + getRotation() + + '}'; + } +} diff --git a/wpilibj/wpilibJavaJNI/lib/NIVisionJNI.cpp b/wpilibj/wpilibJavaJNI/lib/NIVisionJNI.cpp index 6793315535..26b29ba5f8 100644 --- a/wpilibj/wpilibJavaJNI/lib/NIVisionJNI.cpp +++ b/wpilibj/wpilibJavaJNI/lib/NIVisionJNI.cpp @@ -4890,6 +4890,17 @@ JNIEXPORT jlong JNICALL Java_com_ni_vision_NIVision__1imaqRake(JNIEnv* env, jcla return (jlong)rv; } +/* J: void Priv_ReadJPEGString_C(Image image, byte[] string) + * JN: void Priv_ReadJPEGString_C(long image, long string, int stringLength) + * C: int Priv_ReadJPEGString_C(Image* image, const unsigned char* string, unsigned int stringLength) + */ + +JNIEXPORT void JNICALL Java_com_ni_vision_NIVision__1Priv_1ReadJPEGString_1C(JNIEnv* env, jclass , jlong image, jlong string, jint stringLength) +{ + int rv = Priv_ReadJPEGString_C((Image*)image, (const unsigned char*)string, (unsigned int)stringLength); + if (rv == 0) throwJavaException(env); +} + /* * Purpose : Include file for NI-IMAQdx library support. */ diff --git a/wpilibj/wpilibJavaJNI/nivision/nivision_2011.ini b/wpilibj/wpilibJavaJNI/nivision/nivision_2011.ini index 848cd21007..025d2f18ba 100644 --- a/wpilibj/wpilibJavaJNI/nivision/nivision_2011.ini +++ b/wpilibj/wpilibJavaJNI/nivision/nivision_2011.ini @@ -820,6 +820,9 @@ retarraysize=numBarcodes retarraysize=numMatches [imaqColorHistogram] nullok=mask +[Priv_ReadJPEGString_C] +arraysize=string:stringLength +inparams=image,string ; block comment exclusion list [Block Comment]