Prepare cscore for merge into allwpilib.

This commit is contained in:
Peter Johnson
2017-12-20 19:28:58 -08:00
parent 6d3d52f923
commit ea73c10cd8
166 changed files with 0 additions and 335 deletions

View File

@@ -0,0 +1,347 @@
The GNU General Public License (GPL)
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies of this license
document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your freedom to share
and change it. By contrast, the GNU General Public License is intended to
guarantee your freedom to share and change free software--to make sure the
software is free for all its users. This General Public License applies to
most of the Free Software Foundation's software and to any other program whose
authors commit to using it. (Some other Free Software Foundation software is
covered by the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not price. Our
General Public Licenses are designed to make sure that you have the freedom to
distribute copies of free software (and charge for this service if you wish),
that you receive source code or can get it if you want it, that you can change
the software or use pieces of it in new free programs; and that you know you
can do these things.
To protect your rights, we need to make restrictions that forbid anyone to deny
you these rights or to ask you to surrender the rights. These restrictions
translate to certain responsibilities for you if you distribute copies of the
software, or if you modify it.
For example, if you distribute copies of such a program, whether gratis or for
a fee, you must give the recipients all the rights that you have. You must
make sure that they, too, receive or can get the source code. And you must
show them these terms so they know their rights.
We protect your rights with two steps: (1) copyright the software, and (2)
offer you this license which gives you legal permission to copy, distribute
and/or modify the software.
Also, for each author's protection and ours, we want to make certain that
everyone understands that there is no warranty for this free software. If the
software is modified by someone else and passed on, we want its recipients to
know that what they have is not the original, so that any problems introduced
by others will not reflect on the original authors' reputations.
Finally, any free program is threatened constantly by software patents. We
wish to avoid the danger that redistributors of a free program will
individually obtain patent licenses, in effect making the program proprietary.
To prevent this, we have made it clear that any patent must be licensed for
everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and modification
follow.
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains a notice
placed by the copyright holder saying it may be distributed under the terms of
this General Public License. The "Program", below, refers to any such program
or work, and a "work based on the Program" means either the Program or any
derivative work under copyright law: that is to say, a work containing the
Program or a portion of it, either verbatim or with modifications and/or
translated into another language. (Hereinafter, translation is included
without limitation in the term "modification".) Each licensee is addressed as
"you".
Activities other than copying, distribution and modification are not covered by
this License; they are outside its scope. The act of running the Program is
not restricted, and the output from the Program is covered only if its contents
constitute a work based on the Program (independent of having been made by
running the Program). Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's source code as
you receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice and
disclaimer of warranty; keep intact all the notices that refer to this License
and to the absence of any warranty; and give any other recipients of the
Program a copy of this License along with the Program.
You may charge a fee for the physical act of transferring a copy, and you may
at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion of it, thus
forming a work based on the Program, and copy and distribute such modifications
or work under the terms of Section 1 above, provided that you also meet all of
these conditions:
a) You must cause the modified files to carry prominent notices stating
that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in whole or
in part contains or is derived from the Program or any part thereof, to be
licensed as a whole at no charge to all third parties under the terms of
this License.
c) If the modified program normally reads commands interactively when run,
you must cause it, when started running for such interactive use in the
most ordinary way, to print or display an announcement including an
appropriate copyright notice and a notice that there is no warranty (or
else, saying that you provide a warranty) and that users may redistribute
the program under these conditions, and telling the user how to view a copy
of this License. (Exception: if the Program itself is interactive but does
not normally print such an announcement, your work based on the Program is
not required to print an announcement.)
These requirements apply to the modified work as a whole. If identifiable
sections of that work are not derived from the Program, and can be reasonably
considered independent and separate works in themselves, then this License, and
its terms, do not apply to those sections when you distribute them as separate
works. But when you distribute the same sections as part of a whole which is a
work based on the Program, the distribution of the whole must be on the terms
of this License, whose permissions for other licensees extend to the entire
whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest your
rights to work written entirely by you; rather, the intent is to exercise the
right to control the distribution of derivative or collective works based on
the Program.
In addition, mere aggregation of another work not based on the Program with the
Program (or with a work based on the Program) on a volume of a storage or
distribution medium does not bring the other work under the scope of this
License.
3. You may copy and distribute the Program (or a work based on it, under
Section 2) in object code or executable form under the terms of Sections 1 and
2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable source
code, which must be distributed under the terms of Sections 1 and 2 above
on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three years, to
give any third party, for a charge no more than your cost of physically
performing source distribution, a complete machine-readable copy of the
corresponding source code, to be distributed under the terms of Sections 1
and 2 above on a medium customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer to
distribute corresponding source code. (This alternative is allowed only
for noncommercial distribution and only if you received the program in
object code or executable form with such an offer, in accord with
Subsection b above.)
The source code for a work means the preferred form of the work for making
modifications to it. For an executable work, complete source code means all
the source code for all modules it contains, plus any associated interface
definition files, plus the scripts used to control compilation and installation
of the executable. However, as a special exception, the source code
distributed need not include anything that is normally distributed (in either
source or binary form) with the major components (compiler, kernel, and so on)
of the operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the source
code from the same place counts as distribution of the source code, even though
third parties are not compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program except as
expressly provided under this License. Any attempt otherwise to copy, modify,
sublicense or distribute the Program is void, and will automatically terminate
your rights under this License. However, parties who have received copies, or
rights, from you under this License will not have their licenses terminated so
long as such parties remain in full compliance.
5. You are not required to accept this License, since you have not signed it.
However, nothing else grants you permission to modify or distribute the Program
or its derivative works. These actions are prohibited by law if you do not
accept this License. Therefore, by modifying or distributing the Program (or
any work based on the Program), you indicate your acceptance of this License to
do so, and all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the Program),
the recipient automatically receives a license from the original licensor to
copy, distribute or modify the Program subject to these terms and conditions.
You may not impose any further restrictions on the recipients' exercise of the
rights granted herein. You are not responsible for enforcing compliance by
third parties to this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues), conditions
are imposed on you (whether by court order, agreement or otherwise) that
contradict the conditions of this License, they do not excuse you from the
conditions of this License. If you cannot distribute so as to satisfy
simultaneously your obligations under this License and any other pertinent
obligations, then as a consequence you may not distribute the Program at all.
For example, if a patent license would not permit royalty-free redistribution
of the Program by all those who receive copies directly or indirectly through
you, then the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply and
the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any patents or
other property right claims or to contest validity of any such claims; this
section has the sole purpose of protecting the integrity of the free software
distribution system, which is implemented by public license practices. Many
people have made generous contributions to the wide range of software
distributed through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing to
distribute software through any other system and a licensee cannot impose that
choice.
This section is intended to make thoroughly clear what is believed to be a
consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in certain
countries either by patents or by copyrighted interfaces, the original
copyright holder who places the Program under this License may add an explicit
geographical distribution limitation excluding those countries, so that
distribution is permitted only in or among countries not thus excluded. In
such case, this License incorporates the limitation as if written in the body
of this License.
9. The Free Software Foundation may publish revised and/or new versions of the
General Public License from time to time. Such new versions will be similar in
spirit to the present version, but may differ in detail to address new problems
or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any later
version", you have the option of following the terms and conditions either of
that version or of any later version published by the Free Software Foundation.
If the Program does not specify a version number of this License, you may
choose any version ever published by the Free Software Foundation.
10. If you wish to incorporate parts of the Program into other free programs
whose distribution conditions are different, write to the author to ask for
permission. For software which is copyrighted by the Free Software Foundation,
write to the Free Software Foundation; we sometimes make exceptions for this.
Our decision will be guided by the two goals of preserving the free status of
all derivatives of our free software and of promoting the sharing and reuse of
software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE
PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE
PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR
INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA
BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER
OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible
use to the public, the best way to achieve this is to make it free software
which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach
them to the start of each source file to most effectively convey the exclusion
of warranty; and each file should have at least the "copyright" line and a
pointer to where the full notice is found.
One line to give the program's name and a brief idea of what it does.
Copyright (C) <year> <name of author>
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 2 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, write to the Free Software Foundation, Inc., 59
Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this when it
starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author Gnomovision comes
with ABSOLUTELY NO WARRANTY; for details type 'show w'. This is free
software, and you are welcome to redistribute it under certain conditions;
type 'show c' for details.
The hypothetical commands 'show w' and 'show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may be
called something other than 'show w' and 'show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your school,
if any, to sign a "copyright disclaimer" for the program, if necessary. Here
is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
'Gnomovision' (which makes passes at compilers) written by James Hacker.
signature of Ty Coon, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General Public
License instead of this License.
"CLASSPATH" EXCEPTION TO THE GPL
Certain source files distributed by Oracle America and/or its affiliates are
subject to the following clarification and special exception to the GPL, but
only where Oracle has expressly included in the particular source file's header
the words "Oracle designates this particular file as subject to the "Classpath"
exception as provided by Oracle in the LICENSE file that accompanied this code."
Linking this library statically or dynamically with other modules is making
a combined work based on this library. Thus, the terms and conditions of
the GNU General Public License cover the whole combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent modules,
and to copy and distribute the resulting executable under terms of your
choice, provided that you also meet, for each linked independent module,
the terms and conditions of the license of that module. An independent
module is a module which is not derived from or based on this library. If
you modify this library, you may extend this exception to your version of
the library, but you are not obligated to do so. If you do not wish to do
so, delete this exception statement from your version.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#ifndef _JAVASOFT_JNI_MD_H_
#define _JAVASOFT_JNI_MD_H_
#ifndef __has_attribute
#define __has_attribute(x) 0
#endif
#if (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4) && (__GNUC_MINOR__ > 2))) || __has_attribute(visibility)
#define JNIEXPORT __attribute__((visibility("default")))
#define JNIIMPORT __attribute__((visibility("default")))
#else
#define JNIEXPORT
#define JNIIMPORT
#endif
#define JNICALL
typedef int jint;
#ifdef _LP64 /* 64-bit Solaris */
typedef long jlong;
#else
typedef long long jlong;
#endif
typedef signed char jbyte;
#endif /* !_JAVASOFT_JNI_MD_H_ */

View File

@@ -0,0 +1,19 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2017-2018 FIRST. 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.cscore;
import edu.wpi.cscore.CameraServerJNI;
import edu.wpi.first.wpiutil.RuntimeDetector;
public class DevMain {
public static void main(String[] args) {
System.out.println("Hello World!");
System.out.println(RuntimeDetector.getPlatformPath());
System.out.println(CameraServerJNI.getHostname());
}
}

View File

@@ -0,0 +1,12 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2017-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include <iostream>
#include "cscore.h"
int main() { std::cout << cs::GetHostname() << std::endl; }

View File

@@ -0,0 +1,43 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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.cscore;
/**
* A source that represents an Axis IP camera.
*/
public class AxisCamera extends HttpCamera {
private static String hostToUrl(String host) {
return "http://" + host + "/mjpg/video.mjpg";
}
private static String[] hostToUrl(String[] hosts) {
String[] urls = new String[hosts.length];
for (int i = 0; i < hosts.length; i++) {
urls[i] = hostToUrl(hosts[i]);
}
return urls;
}
/**
* Create a source for an Axis IP camera.
* @param name Source name (arbitrary unique identifier)
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
*/
public AxisCamera(String name, String host) {
super(name, hostToUrl(host), HttpCameraKind.kAxis);
}
/**
* Create a source for an Axis IP camera.
* @param name Source name (arbitrary unique identifier)
* @param hosts Array of Camera host IPs/DNS names
*/
public AxisCamera(String name, String[] hosts) {
super(name, hostToUrl(hosts), HttpCameraKind.kAxis);
}
}

View File

@@ -0,0 +1,283 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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.cscore;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.function.Consumer;
import org.opencv.core.Core;
import edu.wpi.first.wpiutil.RuntimeDetector;
public class CameraServerJNI {
static boolean libraryLoaded = false;
static File jniLibrary = null;
static boolean cvLibraryLoaded = false;
static File cvJniLibrary = null;
static {
if (!libraryLoaded) {
try {
System.loadLibrary("cscore");
} catch (UnsatisfiedLinkError e) {
try {
String resname = RuntimeDetector.getLibraryResource("cscore");
InputStream is = CameraServerJNI.class.getResourceAsStream(resname);
if (is != null) {
// create temporary file
if (System.getProperty("os.name").startsWith("Windows"))
jniLibrary = File.createTempFile("CameraServerJNI", ".dll");
else if (System.getProperty("os.name").startsWith("Mac"))
jniLibrary = File.createTempFile("libCameraServerJNI", ".dylib");
else
jniLibrary = File.createTempFile("libCameraServerJNI", ".so");
// flag for delete on exit
jniLibrary.deleteOnExit();
OutputStream os = new FileOutputStream(jniLibrary);
byte[] buffer = new byte[1024];
int readBytes;
try {
while ((readBytes = is.read(buffer)) != -1) {
os.write(buffer, 0, readBytes);
}
} finally {
os.close();
is.close();
}
System.load(jniLibrary.getAbsolutePath());
} else {
System.loadLibrary("cscore");
}
} catch (IOException ex) {
ex.printStackTrace();
System.exit(1);
}
}
libraryLoaded = true;
}
String opencvName = Core.NATIVE_LIBRARY_NAME;
if (!cvLibraryLoaded) {
try {
System.loadLibrary(opencvName);
} catch (UnsatisfiedLinkError e) {
try {
String resname = RuntimeDetector.getLibraryResource(opencvName);
InputStream is = CameraServerJNI.class.getResourceAsStream(resname);
if (is != null) {
// create temporary file
if (System.getProperty("os.name").startsWith("Windows"))
cvJniLibrary = File.createTempFile("OpenCVJNI", ".dll");
else if (System.getProperty("os.name").startsWith("Mac"))
cvJniLibrary = File.createTempFile("libOpenCVJNI", ".dylib");
else
cvJniLibrary = File.createTempFile("libOpenCVJNI", ".so");
// flag for delete on exit
cvJniLibrary.deleteOnExit();
OutputStream os = new FileOutputStream(cvJniLibrary);
byte[] buffer = new byte[1024];
int readBytes;
try {
while ((readBytes = is.read(buffer)) != -1) {
os.write(buffer, 0, readBytes);
}
} finally {
os.close();
is.close();
}
System.load(cvJniLibrary.getAbsolutePath());
} else {
System.loadLibrary(opencvName);
}
} catch (IOException ex) {
ex.printStackTrace();
System.exit(1);
}
}
cvLibraryLoaded = true;
}
}
public static void ForceLoad() {}
//
// Property Functions
//
public static native int getPropertyKind(int property);
public static native String getPropertyName(int property);
public static native int getProperty(int property);
public static native void setProperty(int property, int value);
public static native int getPropertyMin(int property);
public static native int getPropertyMax(int property);
public static native int getPropertyStep(int property);
public static native int getPropertyDefault(int property);
public static native String getStringProperty(int property);
public static native void setStringProperty(int property, String value);
public static native String[] getEnumPropertyChoices(int property);
//
// Source Creation Functions
//
public static native int createUsbCameraDev(String name, int dev);
public static native int createUsbCameraPath(String name, String path);
public static native int createHttpCamera(String name, String url, int kind);
public static native int createHttpCameraMulti(String name, String[] urls, int kind);
public static native int createCvSource(String name, int pixelFormat, int width, int height, int fps);
//
// Source Functions
//
public static native int getSourceKind(int source);
public static native String getSourceName(int source);
public static native String getSourceDescription(int source);
public static native long getSourceLastFrameTime(int source);
public static native boolean isSourceConnected(int source);
public static native int getSourceProperty(int source, String name);
public static native int[] enumerateSourceProperties(int source);
public static native VideoMode getSourceVideoMode(int source);
public static native boolean setSourceVideoMode(int source, int pixelFormat, int width, int height, int fps);
public static native boolean setSourcePixelFormat(int source, int pixelFormat);
public static native boolean setSourceResolution(int source, int width, int height);
public static native boolean setSourceFPS(int source, int fps);
public static native VideoMode[] enumerateSourceVideoModes(int source);
public static native int[] enumerateSourceSinks(int source);
public static native int copySource(int source);
public static native void releaseSource(int source);
//
// Camera Source Common Property Fuctions
//
public static native void setCameraBrightness(int source, int brightness);
public static native int getCameraBrightness(int source);
public static native void setCameraWhiteBalanceAuto(int source);
public static native void setCameraWhiteBalanceHoldCurrent(int source);
public static native void setCameraWhiteBalanceManual(int source, int value);
public static native void setCameraExposureAuto(int source);
public static native void setCameraExposureHoldCurrent(int source);
public static native void setCameraExposureManual(int source, int value);
//
// UsbCamera Source Functions
//
public static native String getUsbCameraPath(int source);
//
// HttpCamera Source Functions
//
public static native int getHttpCameraKind(int source);
public static native void setHttpCameraUrls(int source, String[] urls);
public static native String[] getHttpCameraUrls(int source);
//
// OpenCV Source Functions
//
public static native void putSourceFrame(int source, long imageNativeObj);
public static native void notifySourceError(int source, String msg);
public static native void setSourceConnected(int source, boolean connected);
public static native void setSourceDescription(int source, String description);
public static native int createSourceProperty(int source, String name, int kind, int minimum, int maximum, int step, int defaultValue, int value);
public static native void setSourceEnumPropertyChoices(int source, int property, String[] choices);
//
// Sink Creation Functions
//
public static native int createMjpegServer(String name, String listenAddress, int port);
public static native int createCvSink(String name);
//public static native int createCvSinkCallback(String name,
// void (*processFrame)(long time));
//
// Sink Functions
//
public static native int getSinkKind(int sink);
public static native String getSinkName(int sink);
public static native String getSinkDescription(int sink);
public static native void setSinkSource(int sink, int source);
public static native int getSinkSourceProperty(int sink, String name);
public static native int getSinkSource(int sink);
public static native int copySink(int sink);
public static native void releaseSink(int sink);
//
// MjpegServer Sink Functions
//
public static native String getMjpegServerListenAddress(int sink);
public static native int getMjpegServerPort(int sink);
//
// OpenCV Sink Functions
//
public static native void setSinkDescription(int sink, String description);
public static native long grabSinkFrame(int sink, long imageNativeObj);
public static native long grabSinkFrameTimeout(int sink, long imageNativeObj, double timeout);
public static native String getSinkError(int sink);
public static native void setSinkEnabled(int sink, boolean enabled);
//
// Listener Functions
//
public static native int addListener(Consumer<VideoEvent> listener,
int eventMask, boolean immediateNotify);
public static native void removeListener(int handle);
//
// Telemetry Functions
//
public enum TelemetryKind {
kSourceBytesReceived(1),
kSourceFramesReceived(2);
private int value;
private TelemetryKind(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public static native void setTelemetryPeriod(double seconds);
public static native double getTelemetryElapsedTime();
public static native long getTelemetryValue(int handle, int kind);
public static long getTelemetryValue(int handle, TelemetryKind kind) {
return getTelemetryValue(handle, kind.getValue());
}
public static native double getTelemetryAverageValue(int handle, int kind);
public static double getTelemetryAverageValue(int handle, TelemetryKind kind) {
return getTelemetryAverageValue(handle, kind.getValue());
}
//
// Logging Functions
//
@FunctionalInterface
public interface LoggerFunction {
void apply(int level, String file, int line, String msg);
}
public static native void setLogger(LoggerFunction func, int minLevel);
//
// Utility Functions
//
public static native UsbCameraInfo[] enumerateUsbCameras();
public static native int[] enumerateSources();
public static native int[] enumerateSinks();
public static native String getHostname();
public static native String[] getNetworkInterfaces();
}

View File

@@ -0,0 +1,96 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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.cscore;
import org.opencv.core.Mat;
/**
* A sink for user code to accept video frames as OpenCV images.
*/
public class CvSink extends VideoSink {
/**
* Create a sink for accepting OpenCV images.
* WaitForFrame() must be called on the created sink to get each new
* image.
* @param name Source name (arbitrary unique identifier)
*/
public CvSink(String name) {
super(CameraServerJNI.createCvSink(name));
}
/// Create a sink for accepting OpenCV images in a separate thread.
/// A thread will be created that calls WaitForFrame() and calls the
/// processFrame() callback each time a new frame arrives.
/// @param name Source name (arbitrary unique identifier)
/// @param processFrame Frame processing function; will be called with a
/// time=0 if an error occurred. processFrame should call GetImage()
/// or GetError() as needed, but should not call (except in very
/// unusual circumstances) WaitForImage().
//public CvSink(llvm::StringRef name,
// std::function<void(uint64_t time)> processFrame) {
// super(CameraServerJNI.createCvSinkCallback(name, processFrame));
//}
/**
* Set sink description.
* @param description Description
*/
public void setDescription(String description) {
CameraServerJNI.setSinkDescription(m_handle, description);
}
/**
* Wait for the next frame and get the image.
* Times out (returning 0) after 0.225 seconds.
* The provided image will have three 3-bit channels stored in BGR order.
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message)
*/
public long grabFrame(Mat image) {
return grabFrame(image, 0.225);
}
/**
* Wait for the next frame and get the image.
* Times out (returning 0) after timeout seconds.
* The provided image will have three 3-bit channels stored in BGR order.
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in 1 us increments.
*/
public long grabFrame(Mat image, double timeout) {
return CameraServerJNI.grabSinkFrameTimeout(m_handle, image.nativeObj, timeout);
}
/**
* Wait for the next frame and get the image. May block forever.
* The provided image will have three 3-bit channels stored in BGR order.
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in 1 us increments.
*/
public long grabFrameNoTimeout(Mat image) {
return CameraServerJNI.grabSinkFrame(m_handle, image.nativeObj);
}
/**
* Get error string. Call this if WaitForFrame() returns 0 to determine
* what the error is.
*/
public String getError() {
return CameraServerJNI.getSinkError(m_handle);
}
/**
* Enable or disable getting new frames.
* Disabling will cause processFrame (for callback-based CvSinks) to not
* be called and WaitForFrame() to not return. This can be used to save
* processor resources when frames are not needed.
*/
public void setEnabled(boolean enabled) {
CameraServerJNI.setSinkEnabled(m_handle, enabled);
}
}

View File

@@ -0,0 +1,145 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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.cscore;
import org.opencv.core.Mat;
/**
* A source that represents a video camera.
*/
public class CvSource extends VideoSource {
/**
* Create an OpenCV source.
* @param name Source name (arbitrary unique identifier)
* @param mode Video mode being generated
*/
public CvSource(String name, VideoMode mode) {
super(CameraServerJNI.createCvSource(name, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps));
}
/**
* Create an OpenCV source.
* @param name Source name (arbitrary unique identifier)
* @param pixelFormat Pixel format
* @param width width
* @param height height
* @param fps fps
*/
public CvSource(String name, VideoMode.PixelFormat pixelFormat, int width, int height, int fps) {
super(CameraServerJNI.createCvSource(name, pixelFormat.getValue(), width, height, fps));
}
/**
* Put an OpenCV image and notify sinks.
* Only 8-bit single-channel or 3-channel (with BGR channel order) images
* are supported. If the format, depth or channel order is different, use
* Mat.convertTo() and/or cvtColor() to convert it first.
* @param image OpenCV image
*/
public void putFrame(Mat image) {
CameraServerJNI.putSourceFrame(m_handle, image.nativeObj);
}
/**
* Signal sinks that an error has occurred. This should be called instead
* of NotifyFrame when an error occurs.
*/
public void notifyError(String msg) {
CameraServerJNI.notifySourceError(m_handle, msg);
}
/**
* Set source connection status. Defaults to true.
* @param connected True for connected, false for disconnected
*/
public void setConnected(boolean connected) {
CameraServerJNI.setSourceConnected(m_handle, connected);
}
/**
* Set source description.
* @param description Description
*/
public void setDescription(String description) {
CameraServerJNI.setSourceDescription(m_handle, description);
}
/**
* Create a property.
* @param name Property name
* @param kind Property kind
* @param minimum Minimum value
* @param maximum Maximum value
* @param step Step value
* @param defaultValue Default value
* @param value Current value
* @return Property
*/
public VideoProperty createProperty(String name, VideoProperty.Kind kind, int minimum, int maximum, int step, int defaultValue, int value) {
return new VideoProperty(
CameraServerJNI.createSourceProperty(m_handle, name, kind.getValue(), minimum, maximum, step, defaultValue, value));
}
/// Create an integer property.
/// @param name Property name
/// @param minimum Minimum value
/// @param maximum Maximum value
/// @param step Step value
/// @param defaultValue Default value
/// @param value Current value
/// @return Property
public VideoProperty createIntegerProperty(String name, int minimum, int maximum, int step, int defaultValue, int value) {
return new VideoProperty(
CameraServerJNI.createSourceProperty(m_handle, name, VideoProperty.Kind.kInteger.getValue(), minimum, maximum, step, defaultValue, value));
}
/// Create a boolean property.
/// @param name Property name
/// @param defaultValue Default value
/// @param value Current value
/// @return Property
public VideoProperty createBooleanProperty(String name, boolean defaultValue, boolean value) {
return new VideoProperty(
CameraServerJNI.createSourceProperty(m_handle, name, VideoProperty.Kind.kBoolean.getValue(), 0, 1, 1, defaultValue ? 1 : 0, value ? 1 : 0));
}
/// Create a string property.
/// @param name Property name
/// @param value Current value
/// @return Property
public VideoProperty createStringProperty(String name, String value) {
VideoProperty prop = new VideoProperty(
CameraServerJNI.createSourceProperty(m_handle, name, VideoProperty.Kind.kString.getValue(), 0, 0, 0, 0, 0));
prop.setString(value);
return prop;
}
/// Create a property with a change callback.
/// @param name Property name
/// @param kind Property kind
/// @param minimum Minimum value
/// @param maximum Maximum value
/// @param step Step value
/// @param defaultValue Default value
/// @param value Current value
/// @param onChange Callback to call when the property value changes
/// @return Property
//public VideoProperty createProperty(
// String name, VideoProperty.Kind kind, int minimum, int maximum, int step, int defaultValue, int value,
// std::function<void(VideoProperty property)>
// onChange);
/**
* Configure enum property choices.
* @param property Property
* @param choices Choices
*/
public void SetEnumPropertyChoices(VideoProperty property, String[] choices) {
CameraServerJNI.setSourceEnumPropertyChoices(m_handle, property.m_handle, choices);
}
}

View File

@@ -0,0 +1,96 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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.cscore;
/**
* A source that represents a MJPEG-over-HTTP (IP) camera.
*/
public class HttpCamera extends VideoCamera {
public enum HttpCameraKind {
kUnknown(0), kMJPGStreamer(1), kCSCore(2), kAxis(3);
private int value;
private HttpCameraKind(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public static HttpCameraKind getHttpCameraKindFromInt(int kind) {
switch (kind) {
case 1: return HttpCameraKind.kMJPGStreamer;
case 2: return HttpCameraKind.kCSCore;
case 3: return HttpCameraKind.kAxis;
default: return HttpCameraKind.kUnknown;
}
}
/**
* Create a source for a MJPEG-over-HTTP (IP) camera.
* @param name Source name (arbitrary unique identifier)
* @param url Camera URL (e.g. "http://10.x.y.11/video/stream.mjpg")
*/
public HttpCamera(String name, String url) {
super(CameraServerJNI.createHttpCamera(name, url, HttpCameraKind.kUnknown.getValue()));
}
/**
* Create a source for a MJPEG-over-HTTP (IP) camera.
* @param name Source name (arbitrary unique identifier)
* @param url Camera URL (e.g. "http://10.x.y.11/video/stream.mjpg")
* @param kind Camera kind (e.g. kAxis)
*/
public HttpCamera(String name, String url, HttpCameraKind kind) {
super(CameraServerJNI.createHttpCamera(name, url, kind.getValue()));
}
/**
* Create a source for a MJPEG-over-HTTP (IP) camera.
* @param name Source name (arbitrary unique identifier)
* @param urls Array of Camera URLs
*/
public HttpCamera(String name, String[] urls) {
super(CameraServerJNI.createHttpCameraMulti(name, urls, HttpCameraKind.kUnknown.getValue()));
}
/**
* Create a source for a MJPEG-over-HTTP (IP) camera.
* @param name Source name (arbitrary unique identifier)
* @param urls Array of Camera URLs
* @param kind Camera kind (e.g. kAxis)
*/
public HttpCamera(String name, String[] urls, HttpCameraKind kind) {
super(CameraServerJNI.createHttpCameraMulti(name, urls, kind.getValue()));
}
/**
* Get the kind of HTTP camera.
* Autodetection can result in returning a different value than the camera
* was created with.
*/
public HttpCameraKind getHttpCameraKind() {
return getHttpCameraKindFromInt(CameraServerJNI.getHttpCameraKind(m_handle));
}
/**
* Change the URLs used to connect to the camera.
*/
public void setUrls(String[] urls) {
CameraServerJNI.setHttpCameraUrls(m_handle, urls);
}
/**
* Get the URLs used to connect to the camera.
*/
public String[] getUrls() {
return CameraServerJNI.getHttpCameraUrls(m_handle);
}
}

View File

@@ -0,0 +1,46 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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.cscore;
/**
* A sink that acts as a MJPEG-over-HTTP network server.
*/
public class MjpegServer extends VideoSink {
/**
* Create a MJPEG-over-HTTP server sink.
* @param name Sink name (arbitrary unique identifier)
* @param listenAddress TCP listen address (empty string for all addresses)
* @param port TCP port number
*/
public MjpegServer(String name, String listenAddress, int port) {
super(CameraServerJNI.createMjpegServer(name, listenAddress, port));
}
/**
* Create a MJPEG-over-HTTP server sink.
* @param name Sink name (arbitrary unique identifier)
* @param port TCP port number
*/
public MjpegServer(String name, int port) {
this(name, "", port);
}
/**
* Get the listen address of the server.
*/
public String getListenAddress() {
return CameraServerJNI.getMjpegServerListenAddress(m_handle);
}
/**
* Get the port number of the server.
*/
public int getPort() {
return CameraServerJNI.getMjpegServerPort(m_handle);
}
}

View File

@@ -0,0 +1,46 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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.cscore;
/**
* A source that represents a USB camera.
*/
public class UsbCamera extends VideoCamera {
/**
* Create a source for a USB camera based on device number.
* @param name Source name (arbitrary unique identifier)
* @param dev Device number (e.g. 0 for /dev/video0)
*/
public UsbCamera(String name, int dev) {
super(CameraServerJNI.createUsbCameraDev(name, dev));
}
/**
* Create a source for a USB camera based on device path.
* @param name Source name (arbitrary unique identifier)
* @param path Path to device (e.g. "/dev/video0" on Linux)
*/
public UsbCamera(String name, String path) {
super(CameraServerJNI.createUsbCameraPath(name, path));
}
/**
* Enumerate USB cameras on the local system.
* @return Vector of USB camera information (one for each camera)
*/
public static UsbCameraInfo[] enumerateUsbCameras() {
return CameraServerJNI.enumerateUsbCameras();
}
/**
* Get the path to the device.
*/
public String getPath() {
return CameraServerJNI.getUsbCameraPath(m_handle);
}
}

View File

@@ -0,0 +1,34 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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.cscore;
/**
* USB camera information
*/
public class UsbCameraInfo {
public UsbCameraInfo(int dev, String path, String name) {
this.dev = dev;
this.path = path;
this.name = name;
}
/**
* Device number (e.g. N in '/dev/videoN' on Linux)
*/
public int dev;
/**
* Path to device if available (e.g. '/dev/video0' on Linux)
*/
public String path;
/**
* Vendor/model name of the camera as provided by the USB driver
*/
public String name;
}

View File

@@ -0,0 +1,81 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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.cscore;
/**
* A source that represents a video camera.
*/
public class VideoCamera extends VideoSource {
public class WhiteBalance {
public static final int kFixedIndoor = 3000;
public static final int kFixedOutdoor1 = 4000;
public static final int kFixedOutdoor2 = 5000;
public static final int kFixedFluorescent1 = 5100;
public static final int kFixedFlourescent2 = 5200;
}
protected VideoCamera(int handle) {
super(handle);
}
/**
* Set the brightness, as a percentage (0-100).
*/
public synchronized void setBrightness(int brightness) {
CameraServerJNI.setCameraBrightness(m_handle, brightness);
}
/**
* Get the brightness, as a percentage (0-100).
*/
public synchronized int getBrightness() {
return CameraServerJNI.getCameraBrightness(m_handle);
}
/**
* Set the white balance to auto.
*/
public synchronized void setWhiteBalanceAuto() {
CameraServerJNI.setCameraWhiteBalanceAuto(m_handle);
}
/**
* Set the white balance to hold current.
*/
public synchronized void setWhiteBalanceHoldCurrent() {
CameraServerJNI.setCameraWhiteBalanceHoldCurrent(m_handle);
}
/**
* Set the white balance to manual, with specified color temperature.
*/
public synchronized void setWhiteBalanceManual(int value) {
CameraServerJNI.setCameraWhiteBalanceManual(m_handle, value);
}
/**
* Set the exposure to auto aperture.
*/
public synchronized void setExposureAuto() {
CameraServerJNI.setCameraExposureAuto(m_handle);
}
/**
* Set the exposure to hold current.
*/
public synchronized void setExposureHoldCurrent() {
CameraServerJNI.setCameraExposureHoldCurrent(m_handle);
}
/**
* Set the exposure to manual, as a percentage (0-100).
*/
public synchronized void setExposureManual(int value) {
CameraServerJNI.setCameraExposureManual(m_handle, value);
}
}

View File

@@ -0,0 +1,109 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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.cscore;
/**
* Video event
*/
public class VideoEvent {
public enum Kind {
kUnknown(0x0000),
kSourceCreated(0x0001),
kSourceDestroyed(0x0002),
kSourceConnected(0x0004),
kSourceDisconnected(0x0008),
kSourceVideoModesUpdated(0x0010),
kSourceVideoModeChanged(0x0020),
kSourcePropertyCreated(0x0040),
kSourcePropertyValueUpdated(0x0080),
kSourcePropertyChoicesUpdated(0x0100),
kSinkSourceChanged(0x0200),
kSinkCreated(0x0400),
kSinkDestroyed(0x0800),
kSinkEnabled(0x1000),
kSinkDisabled(0x2000),
kNetworkInterfacesChanged(0x4000),
kTelemetryUpdated(0x8000);
private int value;
private Kind(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public static Kind getKindFromInt(int kind) {
switch (kind) {
case 0x0001: return Kind.kSourceCreated;
case 0x0002: return Kind.kSourceDestroyed;
case 0x0004: return Kind.kSourceConnected;
case 0x0008: return Kind.kSourceDisconnected;
case 0x0010: return Kind.kSourceVideoModesUpdated;
case 0x0020: return Kind.kSourceVideoModeChanged;
case 0x0040: return Kind.kSourcePropertyCreated;
case 0x0080: return Kind.kSourcePropertyValueUpdated;
case 0x0100: return Kind.kSourcePropertyChoicesUpdated;
case 0x0200: return Kind.kSinkSourceChanged;
case 0x0400: return Kind.kSinkCreated;
case 0x0800: return Kind.kSinkDestroyed;
case 0x1000: return Kind.kSinkEnabled;
case 0x2000: return Kind.kSinkDisabled;
case 0x4000: return Kind.kNetworkInterfacesChanged;
default: return Kind.kUnknown;
}
}
VideoEvent(int kind, int source, int sink, String name, int pixelFormat,
int width, int height, int fps, int property, int propertyKind,
int value, String valueStr) {
this.kind = getKindFromInt(kind);
this.sourceHandle = source;
this.sinkHandle = sink;
this.name = name;
this.mode = new VideoMode(pixelFormat, width, height, fps);
this.propertyHandle = property;
this.propertyKind = VideoProperty.getKindFromInt(propertyKind);
this.value = value;
this.valueStr = valueStr;
}
public Kind kind;
// Valid for kSource* and kSink* respectively
public int sourceHandle;
public int sinkHandle;
// Source/sink/property name
public String name;
// Fields for kSourceVideoModeChanged event
public VideoMode mode;
// Fields for kSourceProperty* events
public int propertyHandle;
public VideoProperty.Kind propertyKind;
public int value;
public String valueStr;
public VideoSource getSource() {
return new VideoSource(CameraServerJNI.copySource(sourceHandle));
}
public VideoSink getSink() {
return new VideoSink(CameraServerJNI.copySink(sinkHandle));
}
public VideoProperty getProperty() {
return new VideoProperty(propertyHandle, propertyKind);
}
}

View File

@@ -0,0 +1,22 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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.cscore;
/**
* An exception raised by the camera server.
*/
public class VideoException extends RuntimeException {
public VideoException(String msg) {
super(msg);
}
@Override
public String toString() {
return "VideoException [" + super.toString() + "]";
}
}

View File

@@ -0,0 +1,41 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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.cscore;
import java.util.function.Consumer;
/**
* An event listener. This calls back to a desigated callback function when
* an event matching the specified mask is generated by the library.
*/
public class VideoListener {
/**
* Create an event listener.
* @param listener Listener function
* @param eventMask Bitmask of VideoEvent.Type values
* @param immediateNotify Whether callback should be immediately called with
* a representative set of events for the current library state.
*/
public VideoListener(Consumer<VideoEvent> listener, int eventMask,
boolean immediateNotify) {
m_handle = CameraServerJNI.addListener(listener, eventMask, immediateNotify);
}
public synchronized void free() {
if (m_handle != 0) {
CameraServerJNI.removeListener(m_handle);
}
m_handle = 0;
}
public boolean isValid() {
return m_handle != 0;
}
private int m_handle;
}

View File

@@ -0,0 +1,65 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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.cscore;
/**
* Video mode
*/
public class VideoMode {
public enum PixelFormat {
kUnknown(0), kMJPEG(1), kYUYV(2), kRGB565(3), kBGR(4), kGray(5);
private int value;
private PixelFormat(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
private static final PixelFormat[] m_pixelFormatValues = PixelFormat.values();
public static PixelFormat getPixelFormatFromInt(int pixelFormat) {
return m_pixelFormatValues[pixelFormat];
}
public VideoMode(int pixelFormat, int width, int height, int fps) {
this.pixelFormat = getPixelFormatFromInt(pixelFormat);
this.width = width;
this.height = height;
this.fps = fps;
}
public VideoMode(PixelFormat pixelFormat, int width, int height, int fps) {
this.pixelFormat = pixelFormat;
this.width = width;
this.height = height;
this.fps = fps;
}
/**
* Pixel format
*/
public PixelFormat pixelFormat;
/**
* Width in pixels
*/
public int width;
/**
* Height in pixels
*/
public int height;
/**
* Frames per second
*/
public int fps;
}

View File

@@ -0,0 +1,113 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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.cscore;
public class VideoProperty {
public enum Kind {
kNone(0), kBoolean(1), kInteger(2), kString(4), kEnum(8);
private int value;
private Kind(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public static Kind getKindFromInt(int kind) {
switch (kind) {
case 1: return Kind.kBoolean;
case 2: return Kind.kInteger;
case 4: return Kind.kString;
case 8: return Kind.kEnum;
default: return Kind.kNone;
}
}
public String getName() {
return CameraServerJNI.getPropertyName(m_handle);
}
public Kind getKind() {
return m_kind;
}
public boolean isValid() {
return m_kind != Kind.kNone;
}
// Kind checkers
public boolean isBoolean() {
return m_kind == Kind.kBoolean;
}
public boolean isInteger() {
return m_kind == Kind.kInteger;
}
public boolean isString() {
return m_kind == Kind.kString;
}
public boolean isEnum() {
return m_kind == Kind.kEnum;
}
public int get() {
return CameraServerJNI.getProperty(m_handle);
}
public void set(int value) {
CameraServerJNI.setProperty(m_handle, value);
}
public int getMin() {
return CameraServerJNI.getPropertyMin(m_handle);
}
public int getMax() {
return CameraServerJNI.getPropertyMax(m_handle);
}
public int getStep() {
return CameraServerJNI.getPropertyStep(m_handle);
}
public int getDefault() {
return CameraServerJNI.getPropertyDefault(m_handle);
}
// String-specific functions
public String getString() {
return CameraServerJNI.getStringProperty(m_handle);
}
public void setString(String value) {
CameraServerJNI.setStringProperty(m_handle, value);
}
// Enum-specific functions
public String[] getChoices() {
return CameraServerJNI.getEnumPropertyChoices(m_handle);
}
VideoProperty(int handle) {
m_handle = handle;
m_kind = getKindFromInt(CameraServerJNI.getPropertyKind(handle));
}
VideoProperty(int handle, Kind kind) {
m_handle = handle;
m_kind = kind;
}
int m_handle;
private Kind m_kind;
}

View File

@@ -0,0 +1,139 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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.cscore;
/**
* A source for video that provides a sequence of frames. Each frame may
* consist of multiple images (e.g. from a stereo or depth camera); these
* are called channels.
*/
public class VideoSink {
public enum Kind {
kUnknown(0), kMjpeg(2), kCv(4);
private int value;
private Kind(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public static Kind getKindFromInt(int kind) {
switch (kind) {
case 2: return Kind.kMjpeg;
case 4: return Kind.kCv;
default: return Kind.kUnknown;
}
}
protected VideoSink(int handle) {
m_handle = handle;
}
public synchronized void free() {
if (m_handle != 0) {
CameraServerJNI.releaseSink(m_handle);
}
m_handle = 0;
}
public boolean isValid() {
return m_handle != 0;
}
public int getHandle() {
return m_handle;
}
public boolean equals(Object other) {
if (this == other) return true;
if (other == null) return false;
if (getClass() != other.getClass()) return false;
VideoSink sink = (VideoSink) other;
return m_handle == sink.m_handle;
}
public int hashCode() {
return m_handle;
}
/**
* Get the kind of the sink.
*/
public Kind getKind() {
return getKindFromInt(CameraServerJNI.getSinkKind(m_handle));
}
/**
* Get the name of the sink. The name is an arbitrary identifier
* provided when the sink is created, and should be unique.
*/
public String getName() {
return CameraServerJNI.getSinkName(m_handle);
}
/**
* Get the sink description. This is sink-kind specific.
*/
public String getDescription() {
return CameraServerJNI.getSinkDescription(m_handle);
}
/**
* Configure which source should provide frames to this sink. Each sink
* can accept frames from only a single source, but a single source can
* provide frames to multiple clients.
* @param source Source
*/
public void setSource(VideoSource source) {
if (source == null) {
CameraServerJNI.setSinkSource(m_handle, 0);
} else {
CameraServerJNI.setSinkSource(m_handle, source.m_handle);
}
}
/**
* Get the connected source.
* @return Connected source; nullptr if no source connected.
*/
public VideoSource getSource() {
// While VideoSource.free() will call releaseSource(), getSinkSource()
// increments the internal reference count so this is okay to do.
return new VideoSource(CameraServerJNI.getSinkSource(m_handle));
}
/**
* Get a property of the associated source.
* @param name Property name
* @return Property (kind Property::kNone if no property with
* the given name exists or no source connected)
*/
public VideoProperty getSourceProperty(String name) {
return new VideoProperty(
CameraServerJNI.getSinkSourceProperty(m_handle, name));
}
/**
* Enumerate all existing sinks.
* @return Vector of sinks.
*/
public static VideoSink[] enumerateSinks() {
int[] handles = CameraServerJNI.enumerateSinks();
VideoSink[] rv = new VideoSink[handles.length];
for (int i=0; i<handles.length; i++) {
rv[i] = new VideoSink(handles[i]);
}
return rv;
}
protected int m_handle;
}

View File

@@ -0,0 +1,237 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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.cscore;
/**
* A source for video that provides a sequence of frames. Each frame may
* consist of multiple images (e.g. from a stereo or depth camera); these
* are called channels.
*/
public class VideoSource {
public enum Kind {
kUnknown(0), kUsb(1), kHttp(2), kCv(4);
private int value;
private Kind(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public static Kind getKindFromInt(int kind) {
switch (kind) {
case 1: return Kind.kUsb;
case 2: return Kind.kHttp;
case 4: return Kind.kCv;
default: return Kind.kUnknown;
}
}
protected VideoSource(int handle) {
m_handle = handle;
}
public synchronized void free() {
if (m_handle != 0) {
CameraServerJNI.releaseSource(m_handle);
}
m_handle = 0;
}
public boolean isValid() {
return m_handle != 0;
}
public int getHandle() {
return m_handle;
}
public boolean equals(Object other) {
if (this == other) return true;
if (other == null) return false;
if (getClass() != other.getClass()) return false;
VideoSource source = (VideoSource) other;
return m_handle == source.m_handle;
}
public int hashCode() {
return m_handle;
}
/**
* Get the kind of the source.
*/
public Kind getKind() {
return getKindFromInt(CameraServerJNI.getSourceKind(m_handle));
}
/**
* Get the name of the source. The name is an arbitrary identifier
* provided when the source is created, and should be unique.
*/
public String getName() {
return CameraServerJNI.getSourceName(m_handle);
}
/**
* Get the source description. This is source-kind specific.
*/
public String getDescription() {
return CameraServerJNI.getSourceDescription(m_handle);
}
/**
* Get the last time a frame was captured.
* @return Time in 1 us increments.
*/
public long getLastFrameTime() {
return CameraServerJNI.getSourceLastFrameTime(m_handle);
}
/**
* Is the source currently connected to whatever is providing the images?
*/
public boolean isConnected() {
return CameraServerJNI.isSourceConnected(m_handle);
}
/**
* Get a property.
* @param name Property name
* @return Property contents (of kind Property::kNone if no property with
* the given name exists)
*/
public VideoProperty getProperty(String name) {
return new VideoProperty(CameraServerJNI.getSourceProperty(m_handle, name));
}
/**
* Enumerate all properties of this source.
*/
public VideoProperty[] enumerateProperties() {
int[] handles = CameraServerJNI.enumerateSourceProperties(m_handle);
VideoProperty[] rv = new VideoProperty[handles.length];
for (int i=0; i<handles.length; i++) {
rv[i] = new VideoProperty(handles[i]);
}
return rv;
}
/**
* Get the current video mode.
*/
public VideoMode getVideoMode() {
return CameraServerJNI.getSourceVideoMode(m_handle);
}
/**
* Set the video mode.
* @param mode Video mode
*/
public boolean setVideoMode(VideoMode mode) {
return CameraServerJNI.setSourceVideoMode(m_handle, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps);
}
/**
* Set the video mode.
* @param pixelFormat desired pixel format
* @param width desired width
* @param height desired height
* @param fps desired FPS
* @return True if set successfully
*/
public boolean setVideoMode(VideoMode.PixelFormat pixelFormat, int width, int height, int fps) {
return CameraServerJNI.setSourceVideoMode(m_handle, pixelFormat.getValue(), width, height, fps);
}
/**
* Set the pixel format.
* @param pixelFormat desired pixel format
* @return True if set successfully
*/
public boolean setPixelFormat(VideoMode.PixelFormat pixelFormat) {
return CameraServerJNI.setSourcePixelFormat(m_handle, pixelFormat.getValue());
}
/**
* Set the resolution.
* @param width desired width
* @param height desired height
* @return True if set successfully
*/
public boolean setResolution(int width, int height) {
return CameraServerJNI.setSourceResolution(m_handle, width, height);
}
/**
* Set the frames per second (FPS).
* @param fps desired FPS
* @return True if set successfully
*/
public boolean setFPS(int fps) {
return CameraServerJNI.setSourceFPS(m_handle, fps);
}
/**
* Get the actual FPS.
* CameraServerJNI#setTelemetryPeriod() must be called for this to be valid
* (throws VisionException if telemetry is not enabled).
* @return Actual FPS averaged over the telemetry period.
*/
public double getActualFPS() {
return CameraServerJNI.getTelemetryAverageValue(m_handle, CameraServerJNI.TelemetryKind.kSourceFramesReceived);
}
/**
* Get the data rate (in bytes per second).
* CameraServerJNI#setTelemetryPeriod() must be called for this to be valid
* (throws VisionException if telemetry is not enabled).
* @return Data rate averaged over the telemetry period.
*/
public double getActualDataRate() {
return CameraServerJNI.getTelemetryAverageValue(m_handle, CameraServerJNI.TelemetryKind.kSourceBytesReceived);
}
/**
* Enumerate all known video modes for this source.
*/
public VideoMode[] enumerateVideoModes() {
return CameraServerJNI.enumerateSourceVideoModes(m_handle);
}
/**
* Enumerate all sinks connected to this source.
* @return Vector of sinks.
*/
public VideoSink[] enumerateSinks() {
int[] handles = CameraServerJNI.enumerateSourceSinks(m_handle);
VideoSink[] rv = new VideoSink[handles.length];
for (int i=0; i<handles.length; i++) {
rv[i] = new VideoSink(handles[i]);
}
return rv;
}
/**
* Enumerate all existing sources.
* @return Vector of sources.
*/
public static VideoSource[] enumerateSources() {
int[] handles = CameraServerJNI.enumerateSources();
VideoSource[] rv = new VideoSource[handles.length];
for (int i=0; i<handles.length; i++) {
rv[i] = new VideoSource(handles[i]);
}
return rv;
}
protected int m_handle;
}

View File

@@ -0,0 +1,246 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "CvSinkImpl.h"
#include <llvm/SmallString.h>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include "Handle.h"
#include "Log.h"
#include "Notifier.h"
#include "c_util.h"
#include "cscore_cpp.h"
using namespace cs;
CvSinkImpl::CvSinkImpl(llvm::StringRef name) : SinkImpl{name} {
m_active = true;
// m_thread = std::thread(&CvSinkImpl::ThreadMain, this);
}
CvSinkImpl::CvSinkImpl(llvm::StringRef name,
std::function<void(uint64_t time)> processFrame)
: SinkImpl{name} {}
CvSinkImpl::~CvSinkImpl() { Stop(); }
void CvSinkImpl::Stop() {
m_active = false;
// wake up any waiters by forcing an empty frame to be sent
if (auto source = GetSource()) source->Wakeup();
// join thread
if (m_thread.joinable()) m_thread.join();
}
uint64_t CvSinkImpl::GrabFrame(cv::Mat& image) {
SetEnabled(true);
auto source = GetSource();
if (!source) {
// Source disconnected; sleep for one second
std::this_thread::sleep_for(std::chrono::seconds(1));
return 0;
}
auto frame = source->GetNextFrame(); // blocks
if (!frame) {
// Bad frame; sleep for 20 ms so we don't consume all processor time.
std::this_thread::sleep_for(std::chrono::milliseconds(20));
return 0; // signal error
}
if (!frame.GetCv(image)) {
// Shouldn't happen, but just in case...
std::this_thread::sleep_for(std::chrono::milliseconds(20));
return 0;
}
return frame.GetTime();
}
uint64_t CvSinkImpl::GrabFrame(cv::Mat& image, double timeout) {
SetEnabled(true);
auto source = GetSource();
if (!source) {
// Source disconnected; sleep for one second
std::this_thread::sleep_for(std::chrono::seconds(1));
return 0;
}
auto frame = source->GetNextFrame(timeout); // blocks
if (!frame) {
// Bad frame; sleep for 20 ms so we don't consume all processor time.
std::this_thread::sleep_for(std::chrono::milliseconds(20));
return 0; // signal error
}
if (!frame.GetCv(image)) {
// Shouldn't happen, but just in case...
std::this_thread::sleep_for(std::chrono::milliseconds(20));
return 0;
}
return frame.GetTime();
}
// Send HTTP response and a stream of JPG-frames
void CvSinkImpl::ThreadMain() {
Enable();
while (m_active) {
auto source = GetSource();
if (!source) {
// Source disconnected; sleep for one second
std::this_thread::sleep_for(std::chrono::seconds(1));
continue;
}
SDEBUG4("waiting for frame");
Frame frame = source->GetNextFrame(); // blocks
if (!m_active) break;
if (!frame) {
// Bad frame; sleep for 10 ms so we don't consume all processor time.
std::this_thread::sleep_for(std::chrono::milliseconds(10));
continue;
}
// TODO m_processFrame();
}
Disable();
}
namespace cs {
CS_Sink CreateCvSink(llvm::StringRef name, CS_Status* status) {
auto sink = std::make_shared<CvSinkImpl>(name);
auto handle = Sinks::GetInstance().Allocate(CS_SINK_CV, sink);
Notifier::GetInstance().NotifySink(name, handle, CS_SINK_CREATED);
return handle;
}
CS_Sink CreateCvSinkCallback(llvm::StringRef name,
std::function<void(uint64_t time)> processFrame,
CS_Status* status) {
auto sink = std::make_shared<CvSinkImpl>(name, processFrame);
auto handle = Sinks::GetInstance().Allocate(CS_SINK_CV, sink);
Notifier::GetInstance().NotifySink(name, handle, CS_SINK_CREATED);
return handle;
}
void SetSinkDescription(CS_Sink sink, llvm::StringRef description,
CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data || data->kind != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<CvSinkImpl&>(*data->sink).SetDescription(description);
}
uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data || data->kind != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return 0;
}
return static_cast<CvSinkImpl&>(*data->sink).GrabFrame(image);
}
uint64_t GrabSinkFrameTimeout(CS_Sink sink, cv::Mat& image, double timeout,
CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data || data->kind != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return 0;
}
return static_cast<CvSinkImpl&>(*data->sink).GrabFrame(image, timeout);
}
std::string GetSinkError(CS_Sink sink, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data || data->kind != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return std::string{};
}
return static_cast<CvSinkImpl&>(*data->sink).GetError();
}
llvm::StringRef GetSinkError(CS_Sink sink, llvm::SmallVectorImpl<char>& buf,
CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data || data->kind != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return llvm::StringRef{};
}
return static_cast<CvSinkImpl&>(*data->sink).GetError(buf);
}
void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data || data->kind != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<CvSinkImpl&>(*data->sink).SetEnabled(enabled);
}
} // namespace cs
extern "C" {
CS_Sink CS_CreateCvSink(const char* name, CS_Status* status) {
return cs::CreateCvSink(name, status);
}
CS_Sink CS_CreateCvSinkCallback(const char* name, void* data,
void (*processFrame)(void* data, uint64_t time),
CS_Status* status) {
return cs::CreateCvSinkCallback(
name, [=](uint64_t time) { processFrame(data, time); }, status);
}
void CS_SetSinkDescription(CS_Sink sink, const char* description,
CS_Status* status) {
return cs::SetSinkDescription(sink, description, status);
}
uint64_t CS_GrabSinkFrame(CS_Sink sink, struct CvMat* image,
CS_Status* status) {
auto mat = cv::cvarrToMat(image);
return cs::GrabSinkFrame(sink, mat, status);
}
uint64_t CS_GrabSinkFrameTimeout(CS_Sink sink, struct CvMat* image,
double timeout, CS_Status* status) {
auto mat = cv::cvarrToMat(image);
return cs::GrabSinkFrameTimeout(sink, mat, timeout, status);
}
uint64_t CS_GrabSinkFrameCpp(CS_Sink sink, cv::Mat* image, CS_Status* status) {
return cs::GrabSinkFrame(sink, *image, status);
}
uint64_t CS_GrabSinkFrameTimeoutCpp(CS_Sink sink, cv::Mat* image,
double timeout, CS_Status* status) {
return cs::GrabSinkFrameTimeout(sink, *image, timeout, status);
}
char* CS_GetSinkError(CS_Sink sink, CS_Status* status) {
llvm::SmallString<128> buf;
auto str = cs::GetSinkError(sink, buf, status);
if (*status != 0) return nullptr;
return cs::ConvertToC(str);
}
void CS_SetSinkEnabled(CS_Sink sink, CS_Bool enabled, CS_Status* status) {
return cs::SetSinkEnabled(sink, enabled, status);
}
} // extern "C"

View File

@@ -0,0 +1,52 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_CVSINKIMPL_H_
#define CSCORE_CVSINKIMPL_H_
#include <atomic>
#include <memory>
#include <thread>
#include <vector>
#include <llvm/SmallVector.h>
#include <llvm/StringRef.h>
#include <llvm/raw_ostream.h>
#include <support/raw_istream.h>
#include <support/raw_socket_ostream.h>
#include <tcpsockets/NetworkAcceptor.h>
#include <tcpsockets/NetworkStream.h>
#include "SinkImpl.h"
namespace cs {
class SourceImpl;
class CvSinkImpl : public SinkImpl {
public:
explicit CvSinkImpl(llvm::StringRef name);
CvSinkImpl(llvm::StringRef name,
std::function<void(uint64_t time)> processFrame);
~CvSinkImpl() override;
void Stop();
uint64_t GrabFrame(cv::Mat& image);
uint64_t GrabFrame(cv::Mat& image, double timeout);
private:
void ThreadMain();
std::atomic_bool m_active; // set to false to terminate threads
std::thread m_thread;
std::function<void(uint64_t time)> m_processFrame;
};
} // namespace cs
#endif // CSCORE_CVSINKIMPL_H_

View File

@@ -0,0 +1,399 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "CvSourceImpl.h"
#include <llvm/STLExtras.h>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <support/timestamp.h>
#include "Handle.h"
#include "Log.h"
#include "Notifier.h"
#include "c_util.h"
#include "cscore_cpp.h"
using namespace cs;
CvSourceImpl::CvSourceImpl(llvm::StringRef name, const VideoMode& mode)
: SourceImpl{name} {
m_mode = mode;
m_videoModes.push_back(m_mode);
}
CvSourceImpl::~CvSourceImpl() {}
void CvSourceImpl::Start() {}
std::unique_ptr<PropertyImpl> CvSourceImpl::CreateEmptyProperty(
llvm::StringRef name) const {
return llvm::make_unique<PropertyData>(name);
}
bool CvSourceImpl::CacheProperties(CS_Status* status) const {
// Doesn't need to do anything.
m_properties_cached = true;
return true;
}
void CvSourceImpl::SetProperty(int property, int value, CS_Status* status) {
std::lock_guard<wpi::mutex> lock(m_mutex);
auto prop = static_cast<PropertyData*>(GetProperty(property));
if (!prop) {
*status = CS_INVALID_PROPERTY;
return;
}
// Guess it's integer if we've set before get
if (prop->propKind == CS_PROP_NONE) prop->propKind = CS_PROP_INTEGER;
if ((prop->propKind & (CS_PROP_BOOLEAN | CS_PROP_INTEGER | CS_PROP_ENUM)) ==
0) {
*status = CS_WRONG_PROPERTY_TYPE;
return;
}
UpdatePropertyValue(property, false, value, llvm::StringRef{});
}
void CvSourceImpl::SetStringProperty(int property, llvm::StringRef value,
CS_Status* status) {
std::lock_guard<wpi::mutex> lock(m_mutex);
auto prop = static_cast<PropertyData*>(GetProperty(property));
if (!prop) {
*status = CS_INVALID_PROPERTY;
return;
}
// Guess it's string if we've set before get
if (prop->propKind == CS_PROP_NONE) prop->propKind = CS_PROP_STRING;
if (prop->propKind != CS_PROP_STRING) {
*status = CS_WRONG_PROPERTY_TYPE;
return;
}
UpdatePropertyValue(property, true, 0, value);
}
// These are only valid for cameras (should never get called)
void CvSourceImpl::SetBrightness(int brightness, CS_Status* status) {
*status = CS_INVALID_HANDLE;
}
int CvSourceImpl::GetBrightness(CS_Status* status) const {
*status = CS_INVALID_HANDLE;
return 0;
}
void CvSourceImpl::SetWhiteBalanceAuto(CS_Status* status) {
*status = CS_INVALID_HANDLE;
}
void CvSourceImpl::SetWhiteBalanceHoldCurrent(CS_Status* status) {
*status = CS_INVALID_HANDLE;
}
void CvSourceImpl::SetWhiteBalanceManual(int value, CS_Status* status) {
*status = CS_INVALID_HANDLE;
}
void CvSourceImpl::SetExposureAuto(CS_Status* status) {
*status = CS_INVALID_HANDLE;
}
void CvSourceImpl::SetExposureHoldCurrent(CS_Status* status) {
*status = CS_INVALID_HANDLE;
}
void CvSourceImpl::SetExposureManual(int value, CS_Status* status) {
*status = CS_INVALID_HANDLE;
}
bool CvSourceImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
{
std::lock_guard<wpi::mutex> lock(m_mutex);
m_mode = mode;
m_videoModes[0] = mode;
}
Notifier::GetInstance().NotifySourceVideoMode(*this, mode);
return true;
}
void CvSourceImpl::NumSinksChanged() {
// ignore
}
void CvSourceImpl::NumSinksEnabledChanged() {
// ignore
}
void CvSourceImpl::PutFrame(cv::Mat& image) {
// We only support 8-bit images; convert if necessary.
cv::Mat finalImage;
if (image.depth() == CV_8U)
finalImage = image;
else
image.convertTo(finalImage, CV_8U);
std::unique_ptr<Image> dest;
switch (image.channels()) {
case 1:
dest =
AllocImage(VideoMode::kGray, image.cols, image.rows, image.total());
finalImage.copyTo(dest->AsMat());
break;
case 3:
dest = AllocImage(VideoMode::kBGR, image.cols, image.rows,
image.total() * 3);
finalImage.copyTo(dest->AsMat());
break;
case 4:
dest = AllocImage(VideoMode::kBGR, image.cols, image.rows,
image.total() * 3);
cv::cvtColor(finalImage, dest->AsMat(), cv::COLOR_BGRA2BGR);
break;
default:
SERROR("PutFrame: " << image.channels()
<< "-channel images not supported");
return;
}
SourceImpl::PutFrame(std::move(dest), wpi::Now());
}
void CvSourceImpl::NotifyError(llvm::StringRef msg) {
PutError(msg, wpi::Now());
}
int CvSourceImpl::CreateProperty(llvm::StringRef name, CS_PropertyKind kind,
int minimum, int maximum, int step,
int defaultValue, int value) {
std::lock_guard<wpi::mutex> lock(m_mutex);
int& ndx = m_properties[name];
if (ndx == 0) {
// create a new index
ndx = m_propertyData.size() + 1;
m_propertyData.emplace_back(llvm::make_unique<PropertyData>(
name, kind, minimum, maximum, step, defaultValue, value));
} else {
// update all but value
auto prop = GetProperty(ndx);
prop->propKind = kind;
prop->minimum = minimum;
prop->maximum = maximum;
prop->step = step;
prop->defaultValue = defaultValue;
value = prop->value;
}
Notifier::GetInstance().NotifySourceProperty(
*this, CS_SOURCE_PROPERTY_CREATED, name, ndx, kind, value,
llvm::StringRef{});
return ndx;
}
int CvSourceImpl::CreateProperty(
llvm::StringRef name, CS_PropertyKind kind, int minimum, int maximum,
int step, int defaultValue, int value,
std::function<void(CS_Property property)> onChange) {
// TODO
return 0;
}
void CvSourceImpl::SetEnumPropertyChoices(int property,
llvm::ArrayRef<std::string> choices,
CS_Status* status) {
std::lock_guard<wpi::mutex> lock(m_mutex);
auto prop = GetProperty(property);
if (!prop) {
*status = CS_INVALID_PROPERTY;
return;
}
if (prop->propKind != CS_PROP_ENUM) {
*status = CS_WRONG_PROPERTY_TYPE;
return;
}
prop->enumChoices = choices;
Notifier::GetInstance().NotifySourceProperty(
*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED, prop->name, property,
CS_PROP_ENUM, prop->value, llvm::StringRef{});
}
namespace cs {
CS_Source CreateCvSource(llvm::StringRef name, const VideoMode& mode,
CS_Status* status) {
auto source = std::make_shared<CvSourceImpl>(name, mode);
auto handle = Sources::GetInstance().Allocate(CS_SOURCE_CV, source);
auto& notifier = Notifier::GetInstance();
notifier.NotifySource(name, handle, CS_SOURCE_CREATED);
// Generate initial events here so they come after the source created event
source->Start(); // causes a property event
notifier.NotifySource(name, handle, CS_SOURCE_CONNECTED);
notifier.NotifySource(name, handle, CS_SOURCE_VIDEOMODES_UPDATED);
notifier.NotifySourceVideoMode(*source, mode);
return handle;
}
void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data || data->kind != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<CvSourceImpl&>(*data->source).PutFrame(image);
}
void NotifySourceError(CS_Source source, llvm::StringRef msg,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data || data->kind != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<CvSourceImpl&>(*data->source).NotifyError(msg);
}
void SetSourceConnected(CS_Source source, bool connected, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data || data->kind != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<CvSourceImpl&>(*data->source).SetConnected(connected);
}
void SetSourceDescription(CS_Source source, llvm::StringRef description,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data || data->kind != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<CvSourceImpl&>(*data->source).SetDescription(description);
}
CS_Property CreateSourceProperty(CS_Source source, llvm::StringRef name,
CS_PropertyKind kind, int minimum, int maximum,
int step, int defaultValue, int value,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data || data->kind != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return -1;
}
int property = static_cast<CvSourceImpl&>(*data->source)
.CreateProperty(name, kind, minimum, maximum, step,
defaultValue, value);
return Handle{source, property, Handle::kProperty};
}
CS_Property CreateSourcePropertyCallback(
CS_Source source, llvm::StringRef name, CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue, int value,
std::function<void(CS_Property property)> onChange, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data || data->kind != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return -1;
}
int property = static_cast<CvSourceImpl&>(*data->source)
.CreateProperty(name, kind, minimum, maximum, step,
defaultValue, value, onChange);
return Handle{source, property, Handle::kProperty};
}
void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
llvm::ArrayRef<std::string> choices,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data || data->kind != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return;
}
// Get property index; also validate the source owns this property
Handle handle{property};
int i = handle.GetParentIndex();
if (i < 0) {
*status = CS_INVALID_HANDLE;
return;
}
auto data2 = Sources::GetInstance().Get(Handle{i, Handle::kSource});
if (!data2 || data->source.get() != data2->source.get()) {
*status = CS_INVALID_HANDLE;
return;
}
int propertyIndex = handle.GetIndex();
static_cast<CvSourceImpl&>(*data->source)
.SetEnumPropertyChoices(propertyIndex, choices, status);
}
} // namespace cs
extern "C" {
CS_Source CS_CreateCvSource(const char* name, const CS_VideoMode* mode,
CS_Status* status) {
return cs::CreateCvSource(name, static_cast<const cs::VideoMode&>(*mode),
status);
}
void CS_PutSourceFrame(CS_Source source, struct CvMat* image,
CS_Status* status) {
auto mat = cv::cvarrToMat(image);
return cs::PutSourceFrame(source, mat, status);
}
void CS_PutSourceFrameCpp(CS_Source source, cv::Mat* image, CS_Status* status) {
return cs::PutSourceFrame(source, *image, status);
}
void CS_NotifySourceError(CS_Source source, const char* msg,
CS_Status* status) {
return cs::NotifySourceError(source, msg, status);
}
void CS_SetSourceConnected(CS_Source source, CS_Bool connected,
CS_Status* status) {
return cs::SetSourceConnected(source, connected, status);
}
void CS_SetSourceDescription(CS_Source source, const char* description,
CS_Status* status) {
return cs::SetSourceDescription(source, description, status);
}
CS_Property CS_CreateSourceProperty(CS_Source source, const char* name,
enum CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue,
int value, CS_Status* status) {
return cs::CreateSourceProperty(source, name, kind, minimum, maximum, step,
defaultValue, value, status);
}
CS_Property CS_CreateSourcePropertyCallback(
CS_Source source, const char* name, enum CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue, int value, void* data,
void (*onChange)(void* data, CS_Property property), CS_Status* status) {
return cs::CreateSourcePropertyCallback(
source, name, kind, minimum, maximum, step, defaultValue, value,
[=](CS_Property property) { onChange(data, property); }, status);
}
void CS_SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
const char** choices, int count,
CS_Status* status) {
llvm::SmallVector<std::string, 8> vec;
vec.reserve(count);
for (int i = 0; i < count; ++i) vec.push_back(choices[i]);
return cs::SetSourceEnumPropertyChoices(source, property, vec, status);
}
} // extern "C"

View File

@@ -0,0 +1,89 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_CVSOURCEIMPL_H_
#define CSCORE_CVSOURCEIMPL_H_
#include <atomic>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include "SourceImpl.h"
namespace cs {
class CvSourceImpl : public SourceImpl {
public:
CvSourceImpl(llvm::StringRef name, const VideoMode& mode);
~CvSourceImpl() override;
void Start();
// Property functions
void SetProperty(int property, int value, CS_Status* status) override;
void SetStringProperty(int property, llvm::StringRef value,
CS_Status* status) override;
// Standard common camera properties
void SetBrightness(int brightness, CS_Status* status) override;
int GetBrightness(CS_Status* status) const override;
void SetWhiteBalanceAuto(CS_Status* status) override;
void SetWhiteBalanceHoldCurrent(CS_Status* status) override;
void SetWhiteBalanceManual(int value, CS_Status* status) override;
void SetExposureAuto(CS_Status* status) override;
void SetExposureHoldCurrent(CS_Status* status) override;
void SetExposureManual(int value, CS_Status* status) override;
bool SetVideoMode(const VideoMode& mode, CS_Status* status) override;
void NumSinksChanged() override;
void NumSinksEnabledChanged() override;
// OpenCV-specific functions
void PutFrame(cv::Mat& image);
void NotifyError(llvm::StringRef msg);
int CreateProperty(llvm::StringRef name, CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue, int value);
int CreateProperty(llvm::StringRef name, CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue, int value,
std::function<void(CS_Property property)> onChange);
void SetEnumPropertyChoices(int property, llvm::ArrayRef<std::string> choices,
CS_Status* status);
// Property data
class PropertyData : public PropertyImpl {
public:
PropertyData() = default;
explicit PropertyData(llvm::StringRef name_) : PropertyImpl{name_} {}
PropertyData(llvm::StringRef name_, CS_PropertyKind kind_, int minimum_,
int maximum_, int step_, int defaultValue_, int value_)
: PropertyImpl{name_, kind_, step_, defaultValue_, value_} {
hasMinimum = true;
minimum = minimum_;
hasMaximum = true;
maximum = maximum_;
}
~PropertyData() override = default;
std::function<void(CS_Property property)> onChange;
};
protected:
std::unique_ptr<PropertyImpl> CreateEmptyProperty(
llvm::StringRef name) const override;
bool CacheProperties(CS_Status* status) const override;
private:
std::atomic_bool m_connected{true};
};
} // namespace cs
#endif // CSCORE_CVSOURCEIMPL_H_

View File

@@ -0,0 +1,480 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "Frame.h"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include "Log.h"
#include "SourceImpl.h"
using namespace cs;
Frame::Frame(SourceImpl& source, llvm::StringRef error, Time time)
: m_impl{source.AllocFrameImpl().release()} {
m_impl->refcount = 1;
m_impl->error = error;
m_impl->time = time;
}
Frame::Frame(SourceImpl& source, std::unique_ptr<Image> image, Time time)
: m_impl{source.AllocFrameImpl().release()} {
m_impl->refcount = 1;
m_impl->error.resize(0);
m_impl->time = time;
m_impl->images.push_back(image.release());
}
Image* Frame::GetNearestImage(int width, int height) const {
if (!m_impl) return nullptr;
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
Image* found = nullptr;
// Ideally we want the smallest image at least width/height in size
for (auto i : m_impl->images) {
if (i->IsLarger(width, height) && (!found || (i->IsSmaller(*found))))
found = i;
}
if (found) return found;
// Find the largest image (will be less than width/height)
for (auto i : m_impl->images) {
if (!found || (i->IsLarger(*found))) found = i;
}
if (found) return found;
// Shouldn't reach this, but just in case...
return m_impl->images.empty() ? nullptr : m_impl->images[0];
}
Image* Frame::GetNearestImage(int width, int height,
VideoMode::PixelFormat pixelFormat) const {
if (!m_impl) return nullptr;
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
Image* found = nullptr;
// We want the smallest image at least width/height (or the next largest),
// but the primary search order is in order of conversion cost.
// If we don't find exactly what we want, we prefer non-JPEG source images
// (because JPEG source images require a decompression step).
// While the searching takes a little time, it pales in comparison to the
// image processing to come, so it's worth spending a little extra time
// looking for the most efficient conversion.
// 1) Same width, height, and pixelFormat (e.g. exactly what we want)
for (auto i : m_impl->images) {
if (i->Is(width, height, pixelFormat)) return i;
}
// 2) Same width, height, different (but non-JPEG) pixelFormat (color conv)
// 2a) If we want JPEG output, prefer BGR over other pixel formats
if (pixelFormat == VideoMode::kMJPEG) {
for (auto i : m_impl->images) {
if (i->Is(width, height, VideoMode::kBGR)) return i;
}
}
for (auto i : m_impl->images) {
if (i->Is(width, height) && i->pixelFormat != VideoMode::kMJPEG) return i;
}
// 3) Different width, height, same pixelFormat (only if non-JPEG) (resample)
if (pixelFormat != VideoMode::kMJPEG) {
// 3a) Smallest image at least width/height in size
for (auto i : m_impl->images) {
if (i->IsLarger(width, height) && i->pixelFormat == pixelFormat &&
(!found || (i->IsSmaller(*found))))
found = i;
}
if (found) return found;
// 3b) Largest image (less than width/height)
for (auto i : m_impl->images) {
if (i->pixelFormat == pixelFormat && (!found || (i->IsLarger(*found))))
found = i;
}
if (found) return found;
}
// 4) Different width, height, different (but non-JPEG) pixelFormat
// (color conversion + resample)
// 4a) Smallest image at least width/height in size
for (auto i : m_impl->images) {
if (i->IsLarger(width, height) && i->pixelFormat != VideoMode::kMJPEG &&
(!found || (i->IsSmaller(*found))))
found = i;
}
if (found) return found;
// 4b) Largest image (less than width/height)
for (auto i : m_impl->images) {
if (i->pixelFormat != VideoMode::kMJPEG &&
(!found || (i->IsLarger(*found))))
found = i;
}
if (found) return found;
// 5) Same width, height, JPEG pixelFormat (decompression)
for (auto i : m_impl->images) {
if (i->Is(width, height, VideoMode::kMJPEG)) return i;
}
// 6) Different width, height, JPEG pixelFormat (decompression)
// 6a) Smallest image at least width/height in size
for (auto i : m_impl->images) {
if (i->IsLarger(width, height) && i->pixelFormat == VideoMode::kMJPEG &&
(!found || (i->IsSmaller(*found))))
found = i;
}
if (found) return found;
// 6b) Largest image (less than width/height)
for (auto i : m_impl->images) {
if (i->pixelFormat != VideoMode::kMJPEG &&
(!found || (i->IsLarger(*found))))
found = i;
}
if (found) return found;
// Shouldn't reach this, but just in case...
return m_impl->images.empty() ? nullptr : m_impl->images[0];
}
Image* Frame::Convert(Image* image, VideoMode::PixelFormat pixelFormat,
int jpegQuality) {
if (!image || image->pixelFormat == pixelFormat) return image;
Image* cur = image;
// If the source image is a JPEG, we need to decode it before we can do
// anything else with it. Note that if the destination format is JPEG, we
// still need to do this (unless it was already a JPEG, in which case we
// would have returned above).
if (cur->pixelFormat == VideoMode::kMJPEG) {
cur = ConvertMJPEGToBGR(cur);
if (pixelFormat == VideoMode::kBGR) return cur;
}
// Color convert; if ultimate destination is JPEG, we need to convert to BGR
switch (pixelFormat) {
case VideoMode::kRGB565:
// If source is YUYV or Gray, need to convert to BGR first
if (cur->pixelFormat == VideoMode::kYUYV) {
// Check to see if BGR version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR))
cur = newImage;
else
cur = ConvertYUYVToBGR(cur);
} else if (cur->pixelFormat == VideoMode::kGray) {
// Check to see if BGR version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR))
cur = newImage;
else
cur = ConvertGrayToBGR(cur);
}
return ConvertBGRToRGB565(cur);
case VideoMode::kGray:
// If source is YUYV or RGB565, need to convert to BGR first
if (cur->pixelFormat == VideoMode::kYUYV) {
// Check to see if BGR version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR))
cur = newImage;
else
cur = ConvertYUYVToBGR(cur);
} else if (cur->pixelFormat == VideoMode::kRGB565) {
// Check to see if BGR version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR))
cur = newImage;
else
cur = ConvertRGB565ToBGR(cur);
}
return ConvertBGRToGray(cur);
case VideoMode::kBGR:
case VideoMode::kMJPEG:
if (cur->pixelFormat == VideoMode::kYUYV) {
cur = ConvertYUYVToBGR(cur);
} else if (cur->pixelFormat == VideoMode::kRGB565) {
cur = ConvertRGB565ToBGR(cur);
} else if (cur->pixelFormat == VideoMode::kGray) {
if (pixelFormat == VideoMode::kBGR)
return ConvertGrayToBGR(cur);
else
return ConvertGrayToMJPEG(cur, jpegQuality);
}
break;
case VideoMode::kYUYV:
default:
return nullptr; // Unsupported
}
// Compress if destination is JPEG
if (pixelFormat == VideoMode::kMJPEG)
cur = ConvertBGRToMJPEG(cur, jpegQuality);
return cur;
}
Image* Frame::ConvertMJPEGToBGR(Image* image) {
if (!image || image->pixelFormat != VideoMode::kMJPEG) return nullptr;
// Allocate an BGR image
auto newImage =
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
image->width * image->height * 3);
// Decode
cv::Mat newMat = newImage->AsMat();
cv::imdecode(image->AsInputArray(), cv::IMREAD_COLOR, &newMat);
// Save the result
Image* rv = newImage.release();
if (m_impl) {
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
m_impl->images.push_back(rv);
}
return rv;
}
Image* Frame::ConvertMJPEGToGray(Image* image) {
if (!image || image->pixelFormat != VideoMode::kMJPEG) return nullptr;
// Allocate an grayscale image
auto newImage =
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
image->width * image->height);
// Decode
cv::Mat newMat = newImage->AsMat();
cv::imdecode(image->AsInputArray(), cv::IMREAD_GRAYSCALE, &newMat);
// Save the result
Image* rv = newImage.release();
if (m_impl) {
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
m_impl->images.push_back(rv);
}
return rv;
}
Image* Frame::ConvertYUYVToBGR(Image* image) {
if (!image || image->pixelFormat != VideoMode::kYUYV) return nullptr;
// Allocate a BGR image
auto newImage =
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
image->width * image->height * 3);
// Convert
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_YUV2BGR_YUYV);
// Save the result
Image* rv = newImage.release();
if (m_impl) {
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
m_impl->images.push_back(rv);
}
return rv;
}
Image* Frame::ConvertBGRToRGB565(Image* image) {
if (!image || image->pixelFormat != VideoMode::kBGR) return nullptr;
// Allocate a RGB565 image
auto newImage =
m_impl->source.AllocImage(VideoMode::kRGB565, image->width, image->height,
image->width * image->height * 2);
// Convert
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_RGB2BGR565);
// Save the result
Image* rv = newImage.release();
if (m_impl) {
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
m_impl->images.push_back(rv);
}
return rv;
}
Image* Frame::ConvertRGB565ToBGR(Image* image) {
if (!image || image->pixelFormat != VideoMode::kRGB565) return nullptr;
// Allocate a BGR image
auto newImage =
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
image->width * image->height * 3);
// Convert
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_BGR5652RGB);
// Save the result
Image* rv = newImage.release();
if (m_impl) {
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
m_impl->images.push_back(rv);
}
return rv;
}
Image* Frame::ConvertBGRToGray(Image* image) {
if (!image || image->pixelFormat != VideoMode::kBGR) return nullptr;
// Allocate a Grayscale image
auto newImage =
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
image->width * image->height);
// Convert
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_BGR2GRAY);
// Save the result
Image* rv = newImage.release();
if (m_impl) {
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
m_impl->images.push_back(rv);
}
return rv;
}
Image* Frame::ConvertGrayToBGR(Image* image) {
if (!image || image->pixelFormat != VideoMode::kBGR) return nullptr;
// Allocate a BGR image
auto newImage =
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
image->width * image->height * 3);
// Convert
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_GRAY2BGR);
// Save the result
Image* rv = newImage.release();
if (m_impl) {
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
m_impl->images.push_back(rv);
}
return rv;
}
Image* Frame::ConvertBGRToMJPEG(Image* image, int quality) {
if (!image || image->pixelFormat != VideoMode::kBGR) return nullptr;
if (!m_impl) return nullptr;
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
// Allocate a JPEG image. We don't actually know what the resulting size
// will be; while the destination will automatically grow, doing so will
// cause an extra malloc, so we don't want to be too conservative here.
// Per Wikipedia, Q=100 on a sample image results in 8.25 bits per pixel,
// this is a little bit more conservative in assuming 50% space savings over
// the equivalent BGR image.
auto newImage =
m_impl->source.AllocImage(VideoMode::kMJPEG, image->width, image->height,
image->width * image->height * 1.5);
// Compress
if (m_impl->compressionParams.empty()) {
m_impl->compressionParams.push_back(CV_IMWRITE_JPEG_QUALITY);
m_impl->compressionParams.push_back(quality);
} else {
m_impl->compressionParams[1] = quality;
}
cv::imencode(".jpg", image->AsMat(), newImage->vec(),
m_impl->compressionParams);
// Save the result
Image* rv = newImage.release();
m_impl->images.push_back(rv);
return rv;
}
Image* Frame::ConvertGrayToMJPEG(Image* image, int quality) {
if (!image || image->pixelFormat != VideoMode::kGray) return nullptr;
if (!m_impl) return nullptr;
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
// Allocate a JPEG image. We don't actually know what the resulting size
// will be; while the destination will automatically grow, doing so will
// cause an extra malloc, so we don't want to be too conservative here.
// Per Wikipedia, Q=100 on a sample image results in 8.25 bits per pixel,
// this is a little bit more conservative in assuming 25% space savings over
// the equivalent grayscale image.
auto newImage =
m_impl->source.AllocImage(VideoMode::kMJPEG, image->width, image->height,
image->width * image->height * 0.75);
// Compress
if (m_impl->compressionParams.empty()) {
m_impl->compressionParams.push_back(CV_IMWRITE_JPEG_QUALITY);
m_impl->compressionParams.push_back(quality);
} else {
m_impl->compressionParams[1] = quality;
}
cv::imencode(".jpg", image->AsMat(), newImage->vec(),
m_impl->compressionParams);
// Save the result
Image* rv = newImage.release();
m_impl->images.push_back(rv);
return rv;
}
Image* Frame::GetImage(int width, int height,
VideoMode::PixelFormat pixelFormat, int jpegQuality) {
if (!m_impl) return nullptr;
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
Image* cur = GetNearestImage(width, height, pixelFormat);
if (!cur || cur->Is(width, height, pixelFormat)) return cur;
DEBUG4("converting image from "
<< cur->width << "x" << cur->height << " type " << cur->pixelFormat
<< " to " << width << "x" << height << " type " << pixelFormat);
// If the source image is a JPEG, we need to decode it before we can do
// anything else with it. Note that if the destination format is JPEG, we
// still need to do this (unless the width/height were the same, in which
// case we already returned the existing JPEG above).
if (cur->pixelFormat == VideoMode::kMJPEG) cur = ConvertMJPEGToBGR(cur);
// Resize
if (!cur->Is(width, height)) {
// Allocate an image.
auto newImage = m_impl->source.AllocImage(
cur->pixelFormat, width, height,
width * height * (cur->size() / (cur->width * cur->height)));
// Resize
cv::Mat newMat = newImage->AsMat();
cv::resize(cur->AsMat(), newMat, newMat.size(), 0, 0);
// Save the result
cur = newImage.release();
m_impl->images.push_back(cur);
}
// Convert to output format
return Convert(cur, pixelFormat, jpegQuality);
}
bool Frame::GetCv(cv::Mat& image, int width, int height) {
Image* rawImage = GetImage(width, height, VideoMode::kBGR);
if (!rawImage) return false;
rawImage->AsMat().copyTo(image);
return true;
}
void Frame::ReleaseFrame() {
for (auto image : m_impl->images)
m_impl->source.ReleaseImage(std::unique_ptr<Image>(image));
m_impl->images.clear();
m_impl->source.ReleaseFrameImpl(std::unique_ptr<Impl>(m_impl));
m_impl = nullptr;
}

View File

@@ -0,0 +1,162 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_FRAME_H_
#define CSCORE_FRAME_H_
#include <atomic>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <llvm/SmallVector.h>
#include <support/mutex.h>
#include "Image.h"
#include "cscore_cpp.h"
namespace cs {
class SourceImpl;
class Frame {
friend class SourceImpl;
public:
typedef uint64_t Time;
private:
struct Impl {
explicit Impl(SourceImpl& source_) : source(source_) {}
wpi::recursive_mutex mutex;
std::atomic_int refcount{0};
Time time{0};
SourceImpl& source;
std::string error;
llvm::SmallVector<Image*, 4> images;
std::vector<int> compressionParams;
};
public:
Frame() noexcept : m_impl{nullptr} {}
Frame(SourceImpl& source, llvm::StringRef error, Time time);
Frame(SourceImpl& source, std::unique_ptr<Image> image, Time time);
Frame(const Frame& frame) noexcept : m_impl{frame.m_impl} {
if (m_impl) ++m_impl->refcount;
}
Frame(Frame&& other) noexcept : Frame() { swap(*this, other); }
~Frame() { DecRef(); }
Frame& operator=(Frame other) noexcept {
swap(*this, other);
return *this;
}
explicit operator bool() const { return m_impl && m_impl->error.empty(); }
friend void swap(Frame& first, Frame& second) noexcept {
using std::swap;
swap(first.m_impl, second.m_impl);
}
Time GetTime() const { return m_impl ? m_impl->time : 0; }
llvm::StringRef GetError() const {
if (!m_impl) return llvm::StringRef{};
return m_impl->error;
}
int GetOriginalWidth() const {
if (!m_impl) return 0;
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
if (m_impl->images.empty()) return 0;
return m_impl->images[0]->width;
}
int GetOriginalHeight() const {
if (!m_impl) return 0;
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
if (m_impl->images.empty()) return 0;
return m_impl->images[0]->height;
}
int GetOriginalPixelFormat() const {
if (!m_impl) return 0;
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
if (m_impl->images.empty()) return 0;
return m_impl->images[0]->pixelFormat;
}
Image* GetExistingImage(size_t i = 0) const {
if (!m_impl) return nullptr;
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
if (i >= m_impl->images.size()) return nullptr;
return m_impl->images[i];
}
Image* GetExistingImage(int width, int height) const {
if (!m_impl) return nullptr;
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
for (auto i : m_impl->images) {
if (i->Is(width, height)) return i;
}
return nullptr;
}
Image* GetExistingImage(int width, int height,
VideoMode::PixelFormat pixelFormat) const {
if (!m_impl) return nullptr;
std::lock_guard<wpi::recursive_mutex> lock(m_impl->mutex);
for (auto i : m_impl->images) {
if (i->Is(width, height, pixelFormat)) return i;
}
return nullptr;
}
Image* GetNearestImage(int width, int height) const;
Image* GetNearestImage(int width, int height,
VideoMode::PixelFormat pixelFormat) const;
Image* Convert(Image* image, VideoMode::PixelFormat pixelFormat,
int jpegQuality = 80);
Image* ConvertMJPEGToBGR(Image* image);
Image* ConvertMJPEGToGray(Image* image);
Image* ConvertYUYVToBGR(Image* image);
Image* ConvertBGRToRGB565(Image* image);
Image* ConvertRGB565ToBGR(Image* image);
Image* ConvertBGRToGray(Image* image);
Image* ConvertGrayToBGR(Image* image);
Image* ConvertBGRToMJPEG(Image* image, int quality);
Image* ConvertGrayToMJPEG(Image* image, int quality);
Image* GetImage(int width, int height, VideoMode::PixelFormat pixelFormat,
int jpegQuality = 80);
bool GetCv(cv::Mat& image) {
return GetCv(image, GetOriginalWidth(), GetOriginalHeight());
}
bool GetCv(cv::Mat& image, int width, int height);
private:
void DecRef() {
if (m_impl && --(m_impl->refcount) == 0) ReleaseFrame();
}
void ReleaseFrame();
Impl* m_impl;
};
} // namespace cs
#endif // CSCORE_FRAME_H_

View File

@@ -0,0 +1,124 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_HANDLE_H_
#define CSCORE_HANDLE_H_
#include <atomic>
#include <memory>
#include <utility>
#include <llvm/StringRef.h>
#include "UnlimitedHandleResource.h"
#include "cscore_c.h"
namespace cs {
class SinkImpl;
class SourceImpl;
// Handle data layout:
// Bits 0-15: Handle index
// Bits 16-23: Parent index (property only)
// Bits 24-30: Type
class Handle {
public:
enum Type { kUndefined = 0, kProperty = 0x40, kSource, kSink, kListener };
enum { kIndexMax = 0xffff };
Handle(CS_Handle handle) : m_handle(handle) {} // NOLINT
operator CS_Handle() const { return m_handle; }
Handle(int index, Type type) {
if (index < 0) {
m_handle = 0;
return;
}
m_handle = ((static_cast<int>(type) & 0x7f) << 24) | (index & 0xffff);
}
Handle(int index, int property, Type type) {
if (index < 0 || property < 0) {
m_handle = 0;
return;
}
m_handle = ((static_cast<int>(type) & 0x7f) << 24) |
((index & 0xff) << 16) | (property & 0xffff);
}
int GetIndex() const { return static_cast<int>(m_handle) & 0xffff; }
Type GetType() const {
return static_cast<Type>((static_cast<int>(m_handle) >> 24) & 0xff);
}
bool IsType(Type type) const { return type == GetType(); }
int GetTypedIndex(Type type) const { return IsType(type) ? GetIndex() : -1; }
int GetParentIndex() const {
return IsType(Handle::kProperty) ? (static_cast<int>(m_handle) >> 16) & 0xff
: -1;
}
private:
CS_Handle m_handle;
};
struct SourceData {
SourceData(CS_SourceKind kind_, std::shared_ptr<SourceImpl> source_)
: kind{kind_}, refCount{0}, source{source_} {}
CS_SourceKind kind;
std::atomic_int refCount;
std::shared_ptr<SourceImpl> source;
};
class Sources
: public UnlimitedHandleResource<Handle, SourceData, Handle::kSource> {
public:
static Sources& GetInstance() {
static Sources instance;
return instance;
}
std::pair<CS_Source, std::shared_ptr<SourceData>> Find(
const SourceImpl& source) {
return FindIf(
[&](const SourceData& data) { return data.source.get() == &source; });
}
private:
Sources() = default;
};
struct SinkData {
explicit SinkData(CS_SinkKind kind_, std::shared_ptr<SinkImpl> sink_)
: kind{kind_}, refCount{0}, sourceHandle{0}, sink{sink_} {}
CS_SinkKind kind;
std::atomic_int refCount;
std::atomic<CS_Source> sourceHandle;
std::shared_ptr<SinkImpl> sink;
};
class Sinks : public UnlimitedHandleResource<Handle, SinkData, Handle::kSink> {
public:
static Sinks& GetInstance() {
static Sinks instance;
return instance;
}
std::pair<CS_Sink, std::shared_ptr<SinkData>> Find(const SinkImpl& sink) {
return FindIf(
[&](const SinkData& data) { return data.sink.get() == &sink; });
}
private:
Sinks() = default;
};
} // namespace cs
#endif // CSCORE_HANDLE_H_

View File

@@ -0,0 +1,589 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "HttpCameraImpl.h"
#include <llvm/STLExtras.h>
#include <support/timestamp.h>
#include <tcpsockets/TCPConnector.h>
#include "Handle.h"
#include "JpegUtil.h"
#include "Log.h"
#include "Notifier.h"
#include "Telemetry.h"
#include "c_util.h"
using namespace cs;
HttpCameraImpl::HttpCameraImpl(llvm::StringRef name, CS_HttpCameraKind kind)
: SourceImpl{name}, m_kind{kind} {}
HttpCameraImpl::~HttpCameraImpl() {
m_active = false;
// Close file if it's open
{
std::lock_guard<wpi::mutex> lock(m_mutex);
if (m_streamConn) m_streamConn->stream->close();
if (m_settingsConn) m_settingsConn->stream->close();
}
// force wakeup of camera thread in case it's waiting on cv
m_sinkEnabledCond.notify_one();
// join camera thread
if (m_streamThread.joinable()) m_streamThread.join();
// force wakeup of settings thread
m_settingsCond.notify_one();
// join settings thread
if (m_settingsThread.joinable()) m_settingsThread.join();
}
void HttpCameraImpl::Start() {
// Kick off the stream and settings threads
m_streamThread = std::thread(&HttpCameraImpl::StreamThreadMain, this);
m_settingsThread = std::thread(&HttpCameraImpl::SettingsThreadMain, this);
}
#ifdef __linux__
static inline void DoFdSet(int fd, fd_set* set, int* nfds) {
if (fd >= 0) {
FD_SET(fd, set);
if ((fd + 1) > *nfds) *nfds = fd + 1;
}
}
#endif
void HttpCameraImpl::StreamThreadMain() {
while (m_active) {
SetConnected(false);
// sleep between retries
std::this_thread::sleep_for(std::chrono::milliseconds(250));
// disconnect if no one is listening
if (m_numSinksEnabled == 0) {
std::unique_lock<wpi::mutex> lock(m_mutex);
if (m_streamConn) m_streamConn->stream->close();
// Wait for a sink to enable
m_sinkEnabledCond.wait(
lock, [=] { return !m_active || m_numSinksEnabled != 0; });
if (!m_active) return;
}
// connect
llvm::SmallString<64> boundary;
wpi::HttpConnection* conn = DeviceStreamConnect(boundary);
if (!m_active) break;
// keep retrying
if (!conn) continue;
// update connected since we're actually connected
SetConnected(true);
// stream
DeviceStream(conn->is, boundary);
}
SDEBUG("Camera Thread exiting");
SetConnected(false);
}
wpi::HttpConnection* HttpCameraImpl::DeviceStreamConnect(
llvm::SmallVectorImpl<char>& boundary) {
// Build the request
wpi::HttpRequest req;
{
std::lock_guard<wpi::mutex> lock(m_mutex);
if (m_locations.empty()) {
SERROR("locations array is empty!?");
std::this_thread::sleep_for(std::chrono::seconds(1));
return nullptr;
}
if (m_nextLocation >= m_locations.size()) m_nextLocation = 0;
req = wpi::HttpRequest{m_locations[m_nextLocation++], m_streamSettings};
m_streamSettingsUpdated = false;
}
// Try to connect
auto stream = wpi::TCPConnector::connect(req.host.c_str(), req.port,
Logger::GetInstance(), 1);
if (!m_active || !stream) return nullptr;
auto connPtr = llvm::make_unique<wpi::HttpConnection>(std::move(stream), 1);
wpi::HttpConnection* conn = connPtr.get();
// update m_streamConn
{
std::lock_guard<wpi::mutex> lock(m_mutex);
m_streamConn = std::move(connPtr);
}
std::string warn;
if (!conn->Handshake(req, &warn)) {
SWARNING(GetName() << ": " << warn);
std::lock_guard<wpi::mutex> lock(m_mutex);
m_streamConn = nullptr;
return nullptr;
}
// Parse Content-Type header to get the boundary
llvm::StringRef mediaType, contentType;
std::tie(mediaType, contentType) = conn->contentType.str().split(';');
mediaType = mediaType.trim();
if (mediaType != "multipart/x-mixed-replace") {
SWARNING("\"" << req.host << "\": unrecognized Content-Type \"" << mediaType
<< "\"");
std::lock_guard<wpi::mutex> lock(m_mutex);
m_streamConn = nullptr;
return nullptr;
}
// media parameters
boundary.clear();
while (!contentType.empty()) {
llvm::StringRef keyvalue;
std::tie(keyvalue, contentType) = contentType.split(';');
contentType = contentType.ltrim();
llvm::StringRef key, value;
std::tie(key, value) = keyvalue.split('=');
if (key.trim() == "boundary") {
value = value.trim().trim('"'); // value may be quoted
boundary.append(value.begin(), value.end());
}
}
if (boundary.empty()) {
SWARNING("\"" << req.host
<< "\": empty multi-part boundary or no Content-Type");
std::lock_guard<wpi::mutex> lock(m_mutex);
m_streamConn = nullptr;
return nullptr;
}
return conn;
}
void HttpCameraImpl::DeviceStream(wpi::raw_istream& is,
llvm::StringRef boundary) {
// Stored here so we reuse it from frame to frame
std::string imageBuf;
// keep track of number of bad images received; if we receive 3 bad images
// in a row, we reconnect
int numErrors = 0;
// streaming loop
while (m_active && !is.has_error() && m_numSinksEnabled > 0 &&
numErrors < 3 && !m_streamSettingsUpdated) {
if (!FindMultipartBoundary(is, boundary, nullptr)) break;
// Read the next two characters after the boundary (normally \r\n)
char eol[2];
is.read(eol, 2);
if (!m_active || is.has_error()) break;
// End-of-stream is indicated with trailing --
if (eol[0] == '-' && eol[1] == '-') break;
if (!DeviceStreamFrame(is, imageBuf))
++numErrors;
else
numErrors = 0;
}
}
bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
std::string& imageBuf) {
// Read the headers
llvm::SmallString<64> contentTypeBuf;
llvm::SmallString<64> contentLengthBuf;
if (!ParseHttpHeaders(is, &contentTypeBuf, &contentLengthBuf)) {
SWARNING("disconnected during headers");
PutError("disconnected during headers", wpi::Now());
return false;
}
// Check the content type (if present)
if (!contentTypeBuf.str().empty() &&
!contentTypeBuf.str().startswith("image/jpeg")) {
llvm::SmallString<64> errBuf;
llvm::raw_svector_ostream errMsg{errBuf};
errMsg << "received unknown Content-Type \"" << contentTypeBuf << "\"";
SWARNING(errMsg.str());
PutError(errMsg.str(), wpi::Now());
return false;
}
unsigned int contentLength = 0;
if (contentLengthBuf.str().getAsInteger(10, contentLength)) {
// Ugh, no Content-Length? Read the blocks of the JPEG file.
int width, height;
if (!ReadJpeg(is, imageBuf, &width, &height)) {
SWARNING("did not receive a JPEG image");
PutError("did not receive a JPEG image", wpi::Now());
return false;
}
PutFrame(VideoMode::PixelFormat::kMJPEG, width, height, imageBuf,
wpi::Now());
return true;
}
// We know how big it is! Just get a frame of the right size and read
// the data directly into it.
auto image = AllocImage(VideoMode::PixelFormat::kMJPEG, 0, 0, contentLength);
is.read(image->data(), contentLength);
if (!m_active || is.has_error()) return false;
int width, height;
if (!GetJpegSize(image->str(), &width, &height)) {
SWARNING("did not receive a JPEG image");
PutError("did not receive a JPEG image", wpi::Now());
return false;
}
image->width = width;
image->height = height;
PutFrame(std::move(image), wpi::Now());
return true;
}
void HttpCameraImpl::SettingsThreadMain() {
for (;;) {
wpi::HttpRequest req;
{
std::unique_lock<wpi::mutex> lock(m_mutex);
m_settingsCond.wait(lock, [=] {
return !m_active || (m_prefLocation != -1 && !m_settings.empty());
});
if (!m_active) break;
// Build the request
req = wpi::HttpRequest{m_locations[m_prefLocation], m_settings};
}
DeviceSendSettings(req);
}
SDEBUG("Settings Thread exiting");
}
void HttpCameraImpl::DeviceSendSettings(wpi::HttpRequest& req) {
// Try to connect
auto stream = wpi::TCPConnector::connect(req.host.c_str(), req.port,
Logger::GetInstance(), 1);
if (!m_active || !stream) return;
auto connPtr = llvm::make_unique<wpi::HttpConnection>(std::move(stream), 1);
wpi::HttpConnection* conn = connPtr.get();
// update m_settingsConn
{
std::lock_guard<wpi::mutex> lock(m_mutex);
m_settingsConn = std::move(connPtr);
}
// Just need a handshake as settings are sent via GET parameters
std::string warn;
if (!conn->Handshake(req, &warn)) SWARNING(GetName() << ": " << warn);
conn->stream->close();
}
CS_HttpCameraKind HttpCameraImpl::GetKind() const {
std::lock_guard<wpi::mutex> lock(m_mutex);
return m_kind;
}
bool HttpCameraImpl::SetUrls(llvm::ArrayRef<std::string> urls,
CS_Status* status) {
std::vector<wpi::HttpLocation> locations;
for (const auto& url : urls) {
bool error = false;
std::string errorMsg;
locations.emplace_back(url, &error, &errorMsg);
if (error) {
SERROR(GetName() << ": " << errorMsg);
*status = CS_BAD_URL;
return false;
}
}
std::lock_guard<wpi::mutex> lock(m_mutex);
m_locations.swap(locations);
m_nextLocation = 0;
m_streamSettingsUpdated = true;
return true;
}
std::vector<std::string> HttpCameraImpl::GetUrls() const {
std::lock_guard<wpi::mutex> lock(m_mutex);
std::vector<std::string> urls;
for (const auto& loc : m_locations) urls.push_back(loc.url);
return urls;
}
void HttpCameraImpl::CreateProperty(llvm::StringRef name,
llvm::StringRef httpParam, bool viaSettings,
CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue,
int value) const {
std::lock_guard<wpi::mutex> lock(m_mutex);
m_propertyData.emplace_back(llvm::make_unique<PropertyData>(
name, httpParam, viaSettings, kind, minimum, maximum, step, defaultValue,
value));
Notifier::GetInstance().NotifySourceProperty(
*this, CS_SOURCE_PROPERTY_CREATED, name, m_propertyData.size() + 1, kind,
value, llvm::StringRef{});
}
template <typename T>
void HttpCameraImpl::CreateEnumProperty(
llvm::StringRef name, llvm::StringRef httpParam, bool viaSettings,
int defaultValue, int value, std::initializer_list<T> choices) const {
std::lock_guard<wpi::mutex> lock(m_mutex);
m_propertyData.emplace_back(llvm::make_unique<PropertyData>(
name, httpParam, viaSettings, CS_PROP_ENUM, 0, choices.size() - 1, 1,
defaultValue, value));
auto& enumChoices = m_propertyData.back()->enumChoices;
enumChoices.clear();
for (const auto& choice : choices) enumChoices.emplace_back(choice);
Notifier::GetInstance().NotifySourceProperty(
*this, CS_SOURCE_PROPERTY_CREATED, name, m_propertyData.size() + 1,
CS_PROP_ENUM, value, llvm::StringRef{});
Notifier::GetInstance().NotifySourceProperty(
*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED, name,
m_propertyData.size() + 1, CS_PROP_ENUM, value, llvm::StringRef{});
}
std::unique_ptr<PropertyImpl> HttpCameraImpl::CreateEmptyProperty(
llvm::StringRef name) const {
return llvm::make_unique<PropertyData>(name);
}
bool HttpCameraImpl::CacheProperties(CS_Status* status) const {
std::lock_guard<wpi::mutex> lock(m_mutex);
// Pretty typical set of video modes
m_videoModes.clear();
m_videoModes.emplace_back(VideoMode::kMJPEG, 640, 480, 30);
m_videoModes.emplace_back(VideoMode::kMJPEG, 320, 240, 30);
m_videoModes.emplace_back(VideoMode::kMJPEG, 160, 120, 30);
m_properties_cached = true;
return true;
}
void HttpCameraImpl::SetProperty(int property, int value, CS_Status* status) {
// TODO
}
void HttpCameraImpl::SetStringProperty(int property, llvm::StringRef value,
CS_Status* status) {
// TODO
}
void HttpCameraImpl::SetBrightness(int brightness, CS_Status* status) {
// TODO
}
int HttpCameraImpl::GetBrightness(CS_Status* status) const {
// TODO
return 0;
}
void HttpCameraImpl::SetWhiteBalanceAuto(CS_Status* status) {
// TODO
}
void HttpCameraImpl::SetWhiteBalanceHoldCurrent(CS_Status* status) {
// TODO
}
void HttpCameraImpl::SetWhiteBalanceManual(int value, CS_Status* status) {
// TODO
}
void HttpCameraImpl::SetExposureAuto(CS_Status* status) {
// TODO
}
void HttpCameraImpl::SetExposureHoldCurrent(CS_Status* status) {
// TODO
}
void HttpCameraImpl::SetExposureManual(int value, CS_Status* status) {
// TODO
}
bool HttpCameraImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
if (mode.pixelFormat != VideoMode::kMJPEG) return false;
std::lock_guard<wpi::mutex> lock(m_mutex);
m_mode = mode;
m_streamSettingsUpdated = true;
return true;
}
void HttpCameraImpl::NumSinksChanged() {
// ignore
}
void HttpCameraImpl::NumSinksEnabledChanged() {
m_sinkEnabledCond.notify_one();
}
bool AxisCameraImpl::CacheProperties(CS_Status* status) const {
CreateProperty("brightness", "ImageSource.I0.Sensor.Brightness", true,
CS_PROP_INTEGER, 0, 100, 1, 50, 50);
CreateEnumProperty("white_balance", "ImageSource.I0.Sensor.WhiteBalance",
true, 0, 0,
{"auto", "hold", "fixed_outdoor1", "fixed_outdoor2",
"fixed_indoor", "fixed_fluor1", "fixed_fluor2"});
CreateProperty("color_level", "ImageSource.I0.Sensor.ColorLevel", true,
CS_PROP_INTEGER, 0, 100, 1, 50, 50);
CreateEnumProperty("exposure", "ImageSource.I0.Sensor.Exposure", true, 0, 0,
{"auto", "hold", "flickerfree50", "flickerfree60"});
CreateProperty("exposure_priority", "ImageSource.I0.Sensor.ExposurePriority",
true, CS_PROP_INTEGER, 0, 100, 1, 50, 50);
// TODO: get video modes from device
std::lock_guard<wpi::mutex> lock(m_mutex);
m_videoModes.clear();
m_videoModes.emplace_back(VideoMode::kMJPEG, 640, 480, 30);
m_videoModes.emplace_back(VideoMode::kMJPEG, 480, 360, 30);
m_videoModes.emplace_back(VideoMode::kMJPEG, 320, 240, 30);
m_videoModes.emplace_back(VideoMode::kMJPEG, 240, 180, 30);
m_videoModes.emplace_back(VideoMode::kMJPEG, 176, 144, 30);
m_videoModes.emplace_back(VideoMode::kMJPEG, 160, 120, 30);
m_properties_cached = true;
return true;
}
namespace cs {
CS_Source CreateHttpCamera(llvm::StringRef name, llvm::StringRef url,
CS_HttpCameraKind kind, CS_Status* status) {
std::shared_ptr<HttpCameraImpl> source;
switch (kind) {
case CS_HTTP_AXIS:
source = std::make_shared<AxisCameraImpl>(name);
break;
default:
source = std::make_shared<HttpCameraImpl>(name, kind);
break;
}
std::string urlCopy{url};
if (!source->SetUrls(urlCopy, status)) return 0;
auto handle = Sources::GetInstance().Allocate(CS_SOURCE_HTTP, source);
auto& notifier = Notifier::GetInstance();
notifier.NotifySource(name, handle, CS_SOURCE_CREATED);
source->Start();
return handle;
}
CS_Source CreateHttpCamera(llvm::StringRef name,
llvm::ArrayRef<std::string> urls,
CS_HttpCameraKind kind, CS_Status* status) {
if (urls.empty()) {
*status = CS_EMPTY_VALUE;
return 0;
}
auto source = std::make_shared<HttpCameraImpl>(name, kind);
if (!source->SetUrls(urls, status)) return 0;
auto handle = Sources::GetInstance().Allocate(CS_SOURCE_HTTP, source);
auto& notifier = Notifier::GetInstance();
notifier.NotifySource(name, handle, CS_SOURCE_CREATED);
source->Start();
return handle;
}
CS_HttpCameraKind GetHttpCameraKind(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data || data->kind != CS_SOURCE_HTTP) {
*status = CS_INVALID_HANDLE;
return CS_HTTP_UNKNOWN;
}
return static_cast<HttpCameraImpl&>(*data->source).GetKind();
}
void SetHttpCameraUrls(CS_Source source, llvm::ArrayRef<std::string> urls,
CS_Status* status) {
if (urls.empty()) {
*status = CS_EMPTY_VALUE;
return;
}
auto data = Sources::GetInstance().Get(source);
if (!data || data->kind != CS_SOURCE_HTTP) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<HttpCameraImpl&>(*data->source).SetUrls(urls, status);
}
std::vector<std::string> GetHttpCameraUrls(CS_Source source,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data || data->kind != CS_SOURCE_HTTP) {
*status = CS_INVALID_HANDLE;
return std::vector<std::string>{};
}
return static_cast<HttpCameraImpl&>(*data->source).GetUrls();
}
} // namespace cs
extern "C" {
CS_Source CS_CreateHttpCamera(const char* name, const char* url,
CS_HttpCameraKind kind, CS_Status* status) {
return cs::CreateHttpCamera(name, url, kind, status);
}
CS_Source CS_CreateHttpCameraMulti(const char* name, const char** urls,
int count, CS_HttpCameraKind kind,
CS_Status* status) {
llvm::SmallVector<std::string, 4> vec;
vec.reserve(count);
for (int i = 0; i < count; ++i) vec.push_back(urls[i]);
return cs::CreateHttpCamera(name, vec, kind, status);
}
CS_HttpCameraKind CS_GetHttpCameraKind(CS_Source source, CS_Status* status) {
return cs::GetHttpCameraKind(source, status);
}
void CS_SetHttpCameraUrls(CS_Source source, const char** urls, int count,
CS_Status* status) {
llvm::SmallVector<std::string, 4> vec;
vec.reserve(count);
for (int i = 0; i < count; ++i) vec.push_back(urls[i]);
cs::SetHttpCameraUrls(source, vec, status);
}
char** CS_GetHttpCameraUrls(CS_Source source, int* count, CS_Status* status) {
auto urls = cs::GetHttpCameraUrls(source, status);
char** out = static_cast<char**>(std::malloc(urls.size() * sizeof(char*)));
*count = urls.size();
for (size_t i = 0; i < urls.size(); ++i) out[i] = cs::ConvertToC(urls[i]);
return out;
}
void CS_FreeHttpCameraUrls(char** urls, int count) {
if (!urls) return;
for (int i = 0; i < count; ++i) std::free(urls[i]);
std::free(urls);
}
} // extern "C"

View File

@@ -0,0 +1,155 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_HTTPCAMERAIMPL_H_
#define CSCORE_HTTPCAMERAIMPL_H_
#include <atomic>
#include <functional>
#include <initializer_list>
#include <memory>
#include <string>
#include <thread>
#include <vector>
#include <llvm/SmallString.h>
#include <llvm/StringMap.h>
#include <support/HttpUtil.h>
#include <support/condition_variable.h>
#include <support/raw_istream.h>
#include "SourceImpl.h"
#include "cscore_cpp.h"
namespace cs {
class HttpCameraImpl : public SourceImpl {
public:
HttpCameraImpl(llvm::StringRef name, CS_HttpCameraKind kind);
~HttpCameraImpl() override;
void Start();
// Property functions
void SetProperty(int property, int value, CS_Status* status) override;
void SetStringProperty(int property, llvm::StringRef value,
CS_Status* status) override;
// Standard common camera properties
void SetBrightness(int brightness, CS_Status* status) override;
int GetBrightness(CS_Status* status) const override;
void SetWhiteBalanceAuto(CS_Status* status) override;
void SetWhiteBalanceHoldCurrent(CS_Status* status) override;
void SetWhiteBalanceManual(int value, CS_Status* status) override;
void SetExposureAuto(CS_Status* status) override;
void SetExposureHoldCurrent(CS_Status* status) override;
void SetExposureManual(int value, CS_Status* status) override;
bool SetVideoMode(const VideoMode& mode, CS_Status* status) override;
void NumSinksChanged() override;
void NumSinksEnabledChanged() override;
CS_HttpCameraKind GetKind() const;
bool SetUrls(llvm::ArrayRef<std::string> urls, CS_Status* status);
std::vector<std::string> GetUrls() const;
// Property data
class PropertyData : public PropertyImpl {
public:
PropertyData() = default;
explicit PropertyData(llvm::StringRef name_) : PropertyImpl{name_} {}
PropertyData(llvm::StringRef name_, llvm::StringRef httpParam_,
bool viaSettings_, CS_PropertyKind kind_, int minimum_,
int maximum_, int step_, int defaultValue_, int value_)
: PropertyImpl(name_, kind_, step_, defaultValue_, value_),
viaSettings(viaSettings_),
httpParam(httpParam_) {
hasMinimum = true;
minimum = minimum_;
hasMaximum = true;
maximum = maximum_;
}
~PropertyData() override = default;
bool viaSettings{false};
std::string httpParam;
};
protected:
std::unique_ptr<PropertyImpl> CreateEmptyProperty(
llvm::StringRef name) const override;
bool CacheProperties(CS_Status* status) const override;
void CreateProperty(llvm::StringRef name, llvm::StringRef httpParam,
bool viaSettings, CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue, int value) const;
template <typename T>
void CreateEnumProperty(llvm::StringRef name, llvm::StringRef httpParam,
bool viaSettings, int defaultValue, int value,
std::initializer_list<T> choices) const;
private:
// The camera streaming thread
void StreamThreadMain();
// Functions used by StreamThreadMain()
wpi::HttpConnection* DeviceStreamConnect(
llvm::SmallVectorImpl<char>& boundary);
void DeviceStream(wpi::raw_istream& is, llvm::StringRef boundary);
bool DeviceStreamFrame(wpi::raw_istream& is, std::string& imageBuf);
// The camera settings thread
void SettingsThreadMain();
void DeviceSendSettings(wpi::HttpRequest& req);
std::atomic_bool m_connected{false};
std::atomic_bool m_active{true}; // set to false to terminate thread
std::thread m_streamThread;
std::thread m_settingsThread;
//
// Variables protected by m_mutex
//
// The camera connections
std::unique_ptr<wpi::HttpConnection> m_streamConn;
std::unique_ptr<wpi::HttpConnection> m_settingsConn;
CS_HttpCameraKind m_kind;
std::vector<wpi::HttpLocation> m_locations;
size_t m_nextLocation{0};
int m_prefLocation{-1}; // preferred location
wpi::condition_variable m_sinkEnabledCond;
llvm::StringMap<llvm::SmallString<16>> m_settings;
wpi::condition_variable m_settingsCond;
llvm::StringMap<llvm::SmallString<16>> m_streamSettings;
std::atomic_bool m_streamSettingsUpdated{false};
};
class AxisCameraImpl : public HttpCameraImpl {
public:
explicit AxisCameraImpl(llvm::StringRef name)
: HttpCameraImpl{name, CS_HTTP_AXIS} {}
#if 0
void SetProperty(int property, int value, CS_Status* status) override;
void SetStringProperty(int property, llvm::StringRef value,
CS_Status* status) override;
#endif
protected:
bool CacheProperties(CS_Status* status) const override;
};
} // namespace cs
#endif // CSCORE_HTTPCAMERAIMPL_H_

View File

@@ -0,0 +1,102 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_IMAGE_H_
#define CSCORE_IMAGE_H_
#include <vector>
#include <llvm/StringRef.h>
#include <opencv2/core/core.hpp>
#include "cscore_cpp.h"
#include "default_init_allocator.h"
namespace cs {
class Frame;
class Image {
friend class Frame;
public:
#ifndef __linux__
explicit Image(size_t capacity) { m_data.reserve(capacity); }
#else
explicit Image(size_t capacity)
: m_data{capacity, default_init_allocator<uchar>{}} {
m_data.resize(0);
}
#endif
Image(const Image&) = delete;
Image& operator=(const Image&) = delete;
// Getters
operator llvm::StringRef() const { return str(); }
llvm::StringRef str() const { return llvm::StringRef(data(), size()); }
size_t capacity() const { return m_data.capacity(); }
const char* data() const {
return reinterpret_cast<const char*>(m_data.data());
}
char* data() { return reinterpret_cast<char*>(m_data.data()); }
size_t size() const { return m_data.size(); }
const std::vector<uchar>& vec() const { return m_data; }
std::vector<uchar>& vec() { return m_data; }
void resize(size_t size) { m_data.resize(size); }
void SetSize(size_t size) { m_data.resize(size); }
cv::Mat AsMat() {
int type;
switch (pixelFormat) {
case VideoMode::kYUYV:
case VideoMode::kRGB565:
type = CV_8UC2;
break;
case VideoMode::kBGR:
type = CV_8UC3;
break;
case VideoMode::kGray:
case VideoMode::kMJPEG:
default:
type = CV_8UC1;
break;
}
return cv::Mat{height, width, type, m_data.data()};
}
cv::_InputArray AsInputArray() { return cv::_InputArray{m_data}; }
bool Is(int width_, int height_) {
return width == width_ && height == height_;
}
bool Is(int width_, int height_, VideoMode::PixelFormat pixelFormat_) {
return width == width_ && height == height_ && pixelFormat == pixelFormat_;
}
bool IsLarger(int width_, int height_) {
return width >= width_ && height >= height_;
}
bool IsLarger(const Image& oth) {
return width >= oth.width && height >= oth.height;
}
bool IsSmaller(int width_, int height_) { return !IsLarger(width_, height_); }
bool IsSmaller(const Image& oth) { return !IsLarger(oth); }
private:
std::vector<uchar> m_data;
public:
VideoMode::PixelFormat pixelFormat{VideoMode::kUnknown};
int width{0};
int height{0};
};
} // namespace cs
#endif // CSCORE_IMAGE_H_

View File

@@ -0,0 +1,197 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "JpegUtil.h"
#include <support/raw_istream.h>
namespace cs {
// DHT data for MJPEG images that don't have it.
static const unsigned char dhtData[] = {
0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02,
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x03,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05,
0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04,
0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22,
0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15,
0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36,
0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95,
0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2,
0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5,
0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
0xfa, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05,
0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04,
0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22,
0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33,
0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25,
0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36,
0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94,
0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba,
0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa};
bool IsJpeg(llvm::StringRef data) {
if (data.size() < 11) return false;
// Check for valid SOI
auto bytes = data.bytes_begin();
if (bytes[0] != 0xff || bytes[1] != 0xd8) return false;
return true;
}
bool GetJpegSize(llvm::StringRef data, int* width, int* height) {
if (!IsJpeg(data)) return false;
data = data.substr(2); // Get to the first block
auto bytes = data.bytes_begin();
for (;;) {
if (data.size() < 4) return false; // EOF
bytes = data.bytes_begin();
if (bytes[0] != 0xff) return false; // not a tag
if (bytes[1] == 0xd9) return false; // EOI without finding SOF?
if (bytes[1] == 0xda) return false; // SOS without finding SOF?
if (bytes[1] == 0xc0) {
// SOF contains the file size
if (data.size() < 9) return false;
*height = bytes[5] * 256 + bytes[6];
*width = bytes[7] * 256 + bytes[8];
return true;
}
// Go to the next block
data = data.substr(bytes[2] * 256 + bytes[3] + 2);
}
}
bool JpegNeedsDHT(const char* data, size_t* size, size_t* locSOF) {
llvm::StringRef sdata(data, *size);
if (!IsJpeg(sdata)) return false;
*locSOF = *size;
// Search until SOS for DHT tag
sdata = sdata.substr(2); // Get to the first block
auto bytes = sdata.bytes_begin();
for (;;) {
if (sdata.size() < 4) return false; // EOF
bytes = sdata.bytes_begin();
if (bytes[0] != 0xff) return false; // not a tag
if (bytes[1] == 0xda) break; // SOS
if (bytes[1] == 0xc4) return false; // DHT
if (bytes[1] == 0xc0) *locSOF = sdata.data() - data; // SOF
// Go to the next block
sdata = sdata.substr(bytes[2] * 256 + bytes[3] + 2);
}
// Only add DHT if we also found SOF (insertion point)
if (*locSOF != *size) {
*size += sizeof(dhtData);
return true;
}
return false;
}
llvm::StringRef JpegGetDHT() {
return llvm::StringRef(reinterpret_cast<const char*>(dhtData),
sizeof(dhtData));
}
static inline void ReadInto(wpi::raw_istream& is, std::string& buf,
size_t len) {
size_t oldSize = buf.size();
buf.resize(oldSize + len);
is.read(&(*buf.begin()) + oldSize, len);
}
bool ReadJpeg(wpi::raw_istream& is, std::string& buf, int* width, int* height) {
// in case we don't get a SOF
*width = 0;
*height = 0;
// read SOI and first marker
buf.resize(4);
is.read(&(*buf.begin()), 4);
if (is.has_error()) return false;
// Check for valid SOI
auto bytes = reinterpret_cast<const unsigned char*>(buf.data());
if (bytes[0] != 0xff || bytes[1] != 0xd8) return false;
size_t pos = 2; // point to first marker
for (;;) {
bytes = reinterpret_cast<const unsigned char*>(buf.data() + pos);
if (bytes[0] != 0xff) return false; // not a marker
unsigned char marker = bytes[1];
if (marker == 0xd9) return true; // EOI, we're done
if (marker == 0xda) {
// SOS: need to keep reading until we reach a normal marker.
// Byte stuffing ensures we don't get false markers.
// Have to read a byte at a time as we don't want to overread.
pos += 2; // point after SOS marker
bool maybeMarker = false;
for (;;) {
ReadInto(is, buf, 1);
if (is.has_error()) return false;
bytes = reinterpret_cast<const unsigned char*>(buf.data() + pos);
if (maybeMarker) {
if (bytes[0] != 0x00 && bytes[0] != 0xff &&
(bytes[0] < 0xd0 || bytes[0] > 0xd7))
break;
maybeMarker = false;
} else if (bytes[0] == 0xff) {
maybeMarker = true;
}
++pos; // point after byte we finished reading
}
--pos; // point back to start of marker
continue;
}
// A normal block. Read the length
ReadInto(is, buf, 2); // read length
if (is.has_error()) return false;
// Point to length
pos += 2;
bytes = reinterpret_cast<const unsigned char*>(buf.data() + pos);
// Read the block and the next marker
size_t blockLength = bytes[0] * 256 + bytes[1];
ReadInto(is, buf, blockLength);
if (is.has_error()) return false;
bytes = reinterpret_cast<const unsigned char*>(buf.data() + pos);
// Special block processing
if (marker == 0xc0) {
// SOF: contains the file size; make sure we actually read enough bytes
if (blockLength >= 7) {
*height = bytes[3] * 256 + bytes[4];
*width = bytes[5] * 256 + bytes[6];
}
}
// Point to next marker
pos += blockLength;
}
}
} // namespace cs

View File

@@ -0,0 +1,33 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_JPEGUTIL_H_
#define CSCORE_JPEGUTIL_H_
#include <string>
#include <llvm/StringRef.h>
namespace wpi {
class raw_istream;
} // namespace wpi
namespace cs {
bool IsJpeg(llvm::StringRef data);
bool GetJpegSize(llvm::StringRef data, int* width, int* height);
bool JpegNeedsDHT(const char* data, size_t* size, size_t* locSOF);
llvm::StringRef JpegGetDHT();
bool ReadJpeg(wpi::raw_istream& is, std::string& buf, int* width, int* height);
} // namespace cs
#endif // CSCORE_JPEGUTIL_H_

View File

@@ -0,0 +1,45 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "Log.h"
#include <llvm/Path.h>
#include <llvm/SmallString.h>
#include <llvm/StringRef.h>
#include <llvm/raw_ostream.h>
using namespace cs;
static void def_log_func(unsigned int level, const char* file,
unsigned int line, const char* msg) {
llvm::SmallString<128> buf;
llvm::raw_svector_ostream oss(buf);
if (level == 20) {
oss << "CS: " << msg << '\n';
llvm::errs() << oss.str();
return;
}
llvm::StringRef levelmsg;
if (level >= 50)
levelmsg = "CRITICAL: ";
else if (level >= 40)
levelmsg = "ERROR: ";
else if (level >= 30)
levelmsg = "WARNING: ";
else
return;
oss << "CS: " << levelmsg << msg << " (" << llvm::sys::path::filename(file)
<< ':' << line << ")\n";
llvm::errs() << oss.str();
}
Logger::Logger() { SetDefaultLogger(); }
Logger::~Logger() {}
void Logger::SetDefaultLogger() { SetLogger(def_log_func); }

View File

@@ -0,0 +1,54 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_LOG_H_
#define CSCORE_LOG_H_
#include <support/Logger.h>
namespace cs {
class Logger : public wpi::Logger {
public:
static Logger& GetInstance() {
static Logger instance;
return instance;
}
~Logger();
void SetDefaultLogger();
private:
Logger();
};
#define LOG(level, x) WPI_LOG(cs::Logger::GetInstance(), level, x)
#undef ERROR
#define ERROR(x) WPI_ERROR(cs::Logger::GetInstance(), x)
#define WARNING(x) WPI_WARNING(cs::Logger::GetInstance(), x)
#define INFO(x) WPI_INFO(cs::Logger::GetInstance(), x)
#define DEBUG(x) WPI_DEBUG(cs::Logger::GetInstance(), x)
#define DEBUG1(x) WPI_DEBUG1(cs::Logger::GetInstance(), x)
#define DEBUG2(x) WPI_DEBUG2(cs::Logger::GetInstance(), x)
#define DEBUG3(x) WPI_DEBUG3(cs::Logger::GetInstance(), x)
#define DEBUG4(x) WPI_DEBUG4(cs::Logger::GetInstance(), x)
#define SERROR(x) ERROR(GetName() << ": " << x)
#define SWARNING(x) WARNING(GetName() << ": " << x)
#define SINFO(x) INFO(GetName() << ": " << x)
#define SDEBUG(x) DEBUG(GetName() << ": " << x)
#define SDEBUG1(x) DEBUG1(GetName() << ": " << x)
#define SDEBUG2(x) DEBUG2(GetName() << ": " << x)
#define SDEBUG3(x) DEBUG3(GetName() << ": " << x)
#define SDEBUG4(x) DEBUG4(GetName() << ": " << x)
} // namespace cs
#endif // CSCORE_LOG_H_

View File

@@ -0,0 +1,938 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "MjpegServerImpl.h"
#include <chrono>
#include <llvm/SmallString.h>
#include <support/HttpUtil.h>
#include <support/raw_socket_istream.h>
#include <support/raw_socket_ostream.h>
#include <tcpsockets/TCPAcceptor.h>
#include "Handle.h"
#include "JpegUtil.h"
#include "Log.h"
#include "Notifier.h"
#include "SourceImpl.h"
#include "c_util.h"
#include "cscore_cpp.h"
using namespace cs;
// The boundary used for the M-JPEG stream.
// It separates the multipart stream of pictures
#define BOUNDARY "boundarydonotcross"
// A bare-bones HTML webpage for user friendliness.
static const char* emptyRootPage =
"</head><body>"
"<img src=\"/stream.mjpg\" /><p />"
"<a href=\"/settings.json\">Settings JSON</a>"
"</body></html>";
// An HTML page to be sent when a source exists
static const char* startRootPage =
"<script>\n"
"function httpGetAsync(name, val)\n"
"{\n"
" var host = location.protocol + '//' + location.host + "
"'/?action=command&' + name + '=' + val;\n"
" var xmlHttp = new XMLHttpRequest();\n"
" xmlHttp.open(\"GET\", host, true);\n"
" xmlHttp.send(null);\n"
"}\n"
"function updateInt(prop, name, val) {\n"
" document.querySelector(prop).value = val;\n"
" httpGetAsync(name, val);\n"
"}\n"
"function update(name, val) {\n"
" httpGetAsync(name, val);\n"
"}\n"
"</script>\n"
"<style>\n"
"table, th, td {\n"
" border: 1px solid black;\n"
" border-collapse: collapse;\n"
"}\n"
".settings { float: left; }\n"
".stream { display: inline-block; margin-left: 10px; }\n"
"</style>\n"
"</head><body>\n"
"<div class=\"stream\">\n"
"<img src=\"/stream.mjpg\" /><p />\n"
"<a href=\"/settings.json\">Settings JSON</a>\n"
"</div>\n"
"<div class=\"settings\">\n";
static const char* endRootPage = "</div></body></html>";
class MjpegServerImpl::ConnThread : public wpi::SafeThread {
public:
explicit ConnThread(llvm::StringRef name) : m_name(name) {}
void Main();
bool ProcessCommand(llvm::raw_ostream& os, SourceImpl& source,
llvm::StringRef parameters, bool respond);
void SendJSON(llvm::raw_ostream& os, SourceImpl& source, bool header);
void SendHTMLHeadTitle(llvm::raw_ostream& os) const;
void SendHTML(llvm::raw_ostream& os, SourceImpl& source, bool header);
void SendStream(wpi::raw_socket_ostream& os);
void ProcessRequest();
std::unique_ptr<wpi::NetworkStream> m_stream;
std::shared_ptr<SourceImpl> m_source;
bool m_streaming = false;
bool m_noStreaming = false;
private:
std::string m_name;
llvm::StringRef GetName() { return m_name; }
std::shared_ptr<SourceImpl> GetSource() {
std::lock_guard<wpi::mutex> lock(m_mutex);
return m_source;
}
void StartStream() {
std::lock_guard<wpi::mutex> lock(m_mutex);
m_source->EnableSink();
m_streaming = true;
}
void StopStream() {
std::lock_guard<wpi::mutex> lock(m_mutex);
m_source->DisableSink();
m_streaming = false;
}
int m_width{0};
int m_height{0};
int m_compression{80};
int m_fps{0};
};
// Standard header to send along with other header information like mimetype.
//
// The parameters should ensure the browser does not cache our answer.
// A browser should connect for each file and not serve files from its cache.
// Using cached pictures would lead to showing old/outdated pictures.
// Many browsers seem to ignore, or at least not always obey, those headers.
static void SendHeader(llvm::raw_ostream& os, int code,
llvm::StringRef codeText, llvm::StringRef contentType,
llvm::StringRef extra = llvm::StringRef{}) {
os << "HTTP/1.0 " << code << ' ' << codeText << "\r\n";
os << "Connection: close\r\n"
"Server: CameraServer/1.0\r\n"
"Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, "
"post-check=0, max-age=0\r\n"
"Pragma: no-cache\r\n"
"Expires: Mon, 3 Jan 2000 12:34:56 GMT\r\n";
os << "Content-Type: " << contentType << "\r\n";
os << "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: *\r\n";
if (!extra.empty()) os << extra << "\r\n";
os << "\r\n"; // header ends with a blank line
}
// Send error header and message
// @param code HTTP error code (e.g. 404)
// @param message Additional message text
static void SendError(llvm::raw_ostream& os, int code,
llvm::StringRef message) {
llvm::StringRef codeText, extra, baseMessage;
switch (code) {
case 401:
codeText = "Unauthorized";
extra = "WWW-Authenticate: Basic realm=\"CameraServer\"";
baseMessage = "401: Not Authenticated!";
break;
case 404:
codeText = "Not Found";
baseMessage = "404: Not Found!";
break;
case 500:
codeText = "Internal Server Error";
baseMessage = "500: Internal Server Error!";
break;
case 400:
codeText = "Bad Request";
baseMessage = "400: Not Found!";
break;
case 403:
codeText = "Forbidden";
baseMessage = "403: Forbidden!";
break;
case 503:
codeText = "Service Unavailable";
baseMessage = "503: Service Unavailable";
break;
default:
code = 501;
codeText = "Not Implemented";
baseMessage = "501: Not Implemented!";
break;
}
SendHeader(os, code, codeText, "text/plain", extra);
os << baseMessage << "\r\n" << message;
}
// Perform a command specified by HTTP GET parameters.
bool MjpegServerImpl::ConnThread::ProcessCommand(llvm::raw_ostream& os,
SourceImpl& source,
llvm::StringRef parameters,
bool respond) {
llvm::SmallString<256> responseBuf;
llvm::raw_svector_ostream response{responseBuf};
// command format: param1=value1&param2=value2...
while (!parameters.empty()) {
// split out next param and value
llvm::StringRef rawParam, rawValue;
std::tie(rawParam, parameters) = parameters.split('&');
if (rawParam.empty()) continue; // ignore "&&"
std::tie(rawParam, rawValue) = rawParam.split('=');
if (rawParam.empty() || rawValue.empty()) continue; // ignore "param="
SDEBUG4("HTTP parameter \"" << rawParam << "\" value \"" << rawValue
<< "\"");
// unescape param
bool error = false;
llvm::SmallString<64> paramBuf;
llvm::StringRef param = wpi::UnescapeURI(rawParam, paramBuf, &error);
if (error) {
llvm::SmallString<128> error;
llvm::raw_svector_ostream oss{error};
oss << "could not unescape parameter \"" << rawParam << "\"";
SendError(os, 500, error.str());
SDEBUG(error.str());
return false;
}
// unescape value
llvm::SmallString<64> valueBuf;
llvm::StringRef value = wpi::UnescapeURI(rawValue, valueBuf, &error);
if (error) {
llvm::SmallString<128> error;
llvm::raw_svector_ostream oss{error};
oss << "could not unescape value \"" << rawValue << "\"";
SendError(os, 500, error.str());
SDEBUG(error.str());
return false;
}
// Handle resolution, compression, and FPS. These are handled locally
// rather than passed to the source.
if (param == "resolution") {
llvm::StringRef widthStr, heightStr;
std::tie(widthStr, heightStr) = value.split('x');
int width, height;
if (widthStr.getAsInteger(10, width)) {
response << param << ": \"width is not an integer\"\r\n";
SWARNING("HTTP parameter \"" << param << "\" width \"" << widthStr
<< "\" is not an integer");
continue;
}
if (heightStr.getAsInteger(10, height)) {
response << param << ": \"height is not an integer\"\r\n";
SWARNING("HTTP parameter \"" << param << "\" height \"" << heightStr
<< "\" is not an integer");
continue;
}
m_width = width;
m_height = height;
response << param << ": \"ok\"\r\n";
continue;
}
if (param == "fps") {
int fps;
if (value.getAsInteger(10, fps)) {
response << param << ": \"invalid integer\"\r\n";
SWARNING("HTTP parameter \"" << param << "\" value \"" << value
<< "\" is not an integer");
continue;
} else {
m_fps = fps;
response << param << ": \"ok\"\r\n";
}
continue;
}
if (param == "compression") {
int compression;
if (value.getAsInteger(10, compression)) {
response << param << ": \"invalid integer\"\r\n";
SWARNING("HTTP parameter \"" << param << "\" value \"" << value
<< "\" is not an integer");
continue;
} else {
m_compression = compression;
response << param << ": \"ok\"\r\n";
}
continue;
}
// ignore name parameter
if (param == "name") continue;
// try to assign parameter
auto prop = source.GetPropertyIndex(param);
if (!prop) {
response << param << ": \"ignored\"\r\n";
SWARNING("ignoring HTTP parameter \"" << param << "\"");
continue;
}
CS_Status status = 0;
auto kind = source.GetPropertyKind(prop);
switch (kind) {
case CS_PROP_BOOLEAN:
case CS_PROP_INTEGER:
case CS_PROP_ENUM: {
int val;
if (value.getAsInteger(10, val)) {
response << param << ": \"invalid integer\"\r\n";
SWARNING("HTTP parameter \"" << param << "\" value \"" << value
<< "\" is not an integer");
} else {
response << param << ": " << val << "\r\n";
SDEBUG4("HTTP parameter \"" << param << "\" value " << value);
source.SetProperty(prop, val, &status);
}
break;
}
case CS_PROP_STRING: {
response << param << ": \"ok\"\r\n";
SDEBUG4("HTTP parameter \"" << param << "\" value \"" << value << "\"");
source.SetStringProperty(prop, value, &status);
break;
}
default:
break;
}
}
// Send HTTP response
if (respond) {
SendHeader(os, 200, "OK", "text/plain");
os << response.str() << "\r\n";
}
return true;
}
void MjpegServerImpl::ConnThread::SendHTMLHeadTitle(
llvm::raw_ostream& os) const {
os << "<html><head><title>" << m_name << " CameraServer</title>";
}
// Send the root html file with controls for all the settable properties.
void MjpegServerImpl::ConnThread::SendHTML(llvm::raw_ostream& os,
SourceImpl& source, bool header) {
if (header) SendHeader(os, 200, "OK", "application/x-javascript");
SendHTMLHeadTitle(os);
os << startRootPage;
llvm::SmallVector<int, 32> properties_vec;
CS_Status status = 0;
for (auto prop : source.EnumerateProperties(properties_vec, &status)) {
llvm::SmallString<128> name_buf;
auto name = source.GetPropertyName(prop, name_buf, &status);
if (name.startswith("raw_")) continue;
auto kind = source.GetPropertyKind(prop);
os << "<p />"
<< "<label for=\"" << name << "\">" << name << "</label>\n";
switch (kind) {
case CS_PROP_BOOLEAN:
os << "<input id=\"" << name
<< "\" type=\"checkbox\" onclick=\"update('" << name
<< "', this.checked ? 1 : 0)\" ";
if (source.GetProperty(prop, &status) != 0)
os << "checked />\n";
else
os << " />\n";
break;
case CS_PROP_INTEGER: {
auto valI = source.GetProperty(prop, &status);
auto min = source.GetPropertyMin(prop, &status);
auto max = source.GetPropertyMax(prop, &status);
auto step = source.GetPropertyStep(prop, &status);
os << "<input type=\"range\" min=\"" << min << "\" max=\"" << max
<< "\" value=\"" << valI << "\" id=\"" << name << "\" step=\""
<< step << "\" oninput=\"updateInt('#" << name << "op', '" << name
<< "', value)\" />\n";
os << "<output for=\"" << name << "\" id=\"" << name << "op\">" << valI
<< "</output>\n";
break;
}
case CS_PROP_ENUM: {
auto valE = source.GetProperty(prop, &status);
auto choices = source.GetEnumPropertyChoices(prop, &status);
int j = 0;
for (auto choice = choices.begin(), end = choices.end(); choice != end;
++j, ++choice) {
if (choice->empty()) continue; // skip empty choices
// replace any non-printable characters in name with spaces
llvm::SmallString<128> ch_name;
for (char ch : *choice)
ch_name.push_back(std::isprint(ch) ? ch : ' ');
os << "<input id=\"" << name << j << "\" type=\"radio\" name=\""
<< name << "\" value=\"" << ch_name << "\" onclick=\"update('"
<< name << "', " << j << ")\"";
if (j == valE) {
os << " checked";
}
os << " /><label for=\"" << name << j << "\">" << ch_name
<< "</label>\n";
}
break;
}
case CS_PROP_STRING: {
llvm::SmallString<128> strval_buf;
os << "<input type=\"text\" id=\"" << name << "box\" name=\"" << name
<< "\" value=\""
<< source.GetStringProperty(prop, strval_buf, &status) << "\" />\n";
os << "<input type=\"button\" value =\"Submit\" onclick=\"update('"
<< name << "', " << name << "box.value)\" />\n";
break;
}
default:
break;
}
}
os << "<p>Supported Video Modes:</p>\n";
os << "<table cols=\"4\" style=\"border: 1px solid black\">\n";
os << "<tr><th>Pixel Format</th>"
<< "<th>Width</th>"
<< "<th>Height</th>"
<< "<th>FPS</th></tr>";
for (auto mode : source.EnumerateVideoModes(&status)) {
os << "<tr><td>";
switch (mode.pixelFormat) {
case VideoMode::kMJPEG:
os << "MJPEG";
break;
case VideoMode::kYUYV:
os << "YUYV";
break;
case VideoMode::kRGB565:
os << "RGB565";
break;
case VideoMode::kBGR:
os << "BGR";
break;
case VideoMode::kGray:
os << "gray";
break;
default:
os << "unknown";
break;
}
os << "</td><td>" << mode.width;
os << "</td><td>" << mode.height;
os << "</td><td>" << mode.fps;
os << "</td></tr>";
}
os << "</table>\n";
os << endRootPage << "\r\n";
os.flush();
}
// Send a JSON file which is contains information about the source parameters.
void MjpegServerImpl::ConnThread::SendJSON(llvm::raw_ostream& os,
SourceImpl& source, bool header) {
if (header) SendHeader(os, 200, "OK", "application/x-javascript");
os << "{\n\"controls\": [\n";
llvm::SmallVector<int, 32> properties_vec;
bool first = true;
CS_Status status = 0;
for (auto prop : source.EnumerateProperties(properties_vec, &status)) {
if (first)
first = false;
else
os << ",\n";
os << '{';
llvm::SmallString<128> name_buf;
auto name = source.GetPropertyName(prop, name_buf, &status);
auto kind = source.GetPropertyKind(prop);
os << "\n\"name\": \"" << name << '"';
os << ",\n\"id\": \"" << prop << '"';
os << ",\n\"type\": \"" << kind << '"';
os << ",\n\"min\": \"" << source.GetPropertyMin(prop, &status) << '"';
os << ",\n\"max\": \"" << source.GetPropertyMax(prop, &status) << '"';
os << ",\n\"step\": \"" << source.GetPropertyStep(prop, &status) << '"';
os << ",\n\"default\": \"" << source.GetPropertyDefault(prop, &status)
<< '"';
os << ",\n\"value\": \"";
switch (kind) {
case CS_PROP_BOOLEAN:
case CS_PROP_INTEGER:
case CS_PROP_ENUM:
os << source.GetProperty(prop, &status);
break;
case CS_PROP_STRING: {
llvm::SmallString<128> strval_buf;
os << source.GetStringProperty(prop, strval_buf, &status);
break;
}
default:
break;
}
os << '"';
// os << ",\n\"dest\": \"0\"";
// os << ",\n\"flags\": \"" << param->flags << '"';
// os << ",\n\"group\": \"" << param->group << '"';
// append the menu object to the menu typecontrols
if (source.GetPropertyKind(prop) == CS_PROP_ENUM) {
os << ",\n\"menu\": {";
auto choices = source.GetEnumPropertyChoices(prop, &status);
int j = 0;
for (auto choice = choices.begin(), end = choices.end(); choice != end;
++j, ++choice) {
if (j != 0) os << ", ";
// replace any non-printable characters in name with spaces
llvm::SmallString<128> ch_name;
for (char ch : *choice) ch_name.push_back(std::isprint(ch) ? ch : ' ');
os << '"' << j << "\": \"" << ch_name << '"';
}
os << "}\n";
}
os << '}';
}
os << "\n],\n\"modes\": [\n";
first = true;
for (auto mode : source.EnumerateVideoModes(&status)) {
if (first)
first = false;
else
os << ",\n";
os << '{';
os << "\n\"pixelFormat\": \"";
switch (mode.pixelFormat) {
case VideoMode::kMJPEG:
os << "MJPEG";
break;
case VideoMode::kYUYV:
os << "YUYV";
break;
case VideoMode::kRGB565:
os << "RGB565";
break;
case VideoMode::kBGR:
os << "BGR";
break;
case VideoMode::kGray:
os << "gray";
break;
default:
os << "unknown";
break;
}
os << "\",\n\"width\": \"" << mode.width << '"';
os << ",\n\"height\": \"" << mode.height << '"';
os << ",\n\"fps\": \"" << mode.fps << '"';
os << '}';
}
os << "\n]\n}\n";
os.flush();
}
MjpegServerImpl::MjpegServerImpl(llvm::StringRef name,
llvm::StringRef listenAddress, int port,
std::unique_ptr<wpi::NetworkAcceptor> acceptor)
: SinkImpl{name},
m_listenAddress(listenAddress),
m_port(port),
m_acceptor{std::move(acceptor)} {
m_active = true;
llvm::SmallString<128> descBuf;
llvm::raw_svector_ostream desc{descBuf};
desc << "HTTP Server on port " << port;
SetDescription(desc.str());
m_serverThread = std::thread(&MjpegServerImpl::ServerThreadMain, this);
}
MjpegServerImpl::~MjpegServerImpl() { Stop(); }
void MjpegServerImpl::Stop() {
m_active = false;
// wake up server thread by shutting down the socket
m_acceptor->shutdown();
// join server thread
if (m_serverThread.joinable()) m_serverThread.join();
// close streams
for (auto& connThread : m_connThreads) {
if (auto thr = connThread.GetThread()) {
if (thr->m_stream) thr->m_stream->close();
}
connThread.Stop();
}
// wake up connection threads by forcing an empty frame to be sent
if (auto source = GetSource()) source->Wakeup();
}
// Send HTTP response and a stream of JPG-frames
void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
if (m_noStreaming) {
SERROR("Too many simultaneous client streams");
SendError(os, 503, "Too many simultaneous streams");
return;
}
os.SetUnbuffered();
llvm::SmallString<256> header;
llvm::raw_svector_ostream oss{header};
SendHeader(oss, 200, "OK", "multipart/x-mixed-replace;boundary=" BOUNDARY);
os << oss.str();
SDEBUG("Headers send, sending stream now");
Frame::Time lastFrameTime = 0;
Frame::Time timePerFrame = 0;
if (m_fps != 0) timePerFrame = 1000000.0 / m_fps;
// Allow fudge factor of 1 ms in frame rate
if (timePerFrame >= 1000) timePerFrame -= 1000;
StartStream();
while (m_active && !os.has_error()) {
auto source = GetSource();
if (!source) {
// Source disconnected; sleep so we don't consume all processor time.
os << "\r\n"; // Keep connection alive
std::this_thread::sleep_for(std::chrono::milliseconds(200));
continue;
}
SDEBUG4("waiting for frame");
Frame frame = source->GetNextFrame(0.225); // blocks
if (!m_active) break;
if (!frame) {
// Bad frame; sleep for 20 ms so we don't consume all processor time.
os << "\r\n"; // Keep connection alive
std::this_thread::sleep_for(std::chrono::milliseconds(20));
continue;
}
if (frame.GetTime() < (lastFrameTime + timePerFrame)) {
// Limit FPS; sleep for 10 ms so we don't consume all processor time
std::this_thread::sleep_for(std::chrono::milliseconds(10));
continue;
}
int width = m_width != 0 ? m_width : frame.GetOriginalWidth();
int height = m_height != 0 ? m_height : frame.GetOriginalHeight();
Image* image =
frame.GetImage(width, height, VideoMode::kMJPEG, m_compression);
if (!image) {
// Shouldn't happen, but just in case...
std::this_thread::sleep_for(std::chrono::milliseconds(20));
continue;
}
const char* data = image->data();
size_t size = image->size();
bool addDHT = false;
size_t locSOF = size;
switch (image->pixelFormat) {
case VideoMode::kMJPEG:
// Determine if we need to add DHT to it, and allocate enough space
// for adding it if required.
addDHT = JpegNeedsDHT(data, &size, &locSOF);
break;
case VideoMode::kYUYV:
case VideoMode::kRGB565:
default:
// Bad frame; sleep for 10 ms so we don't consume all processor time.
std::this_thread::sleep_for(std::chrono::milliseconds(10));
continue;
}
SDEBUG4("sending frame size=" << size << " addDHT=" << addDHT);
// print the individual mimetype and the length
// sending the content-length fixes random stream disruption observed
// with firefox
lastFrameTime = frame.GetTime();
double timestamp = lastFrameTime / 1000000.0;
header.clear();
oss << "\r\n--" BOUNDARY "\r\n"
<< "Content-Type: image/jpeg\r\n"
<< "Content-Length: " << size << "\r\n"
<< "X-Timestamp: " << timestamp << "\r\n"
<< "\r\n";
os << oss.str();
if (addDHT) {
// Insert DHT data immediately before SOF
os << llvm::StringRef(data, locSOF);
os << JpegGetDHT();
os << llvm::StringRef(data + locSOF, image->size() - locSOF);
} else {
os << llvm::StringRef(data, size);
}
// os.flush();
}
StopStream();
}
void MjpegServerImpl::ConnThread::ProcessRequest() {
wpi::raw_socket_istream is{*m_stream};
wpi::raw_socket_ostream os{*m_stream, true};
// Reset per-request settings
m_width = 0;
m_height = 0;
m_compression = 80;
m_fps = 0;
// Read the request string from the stream
llvm::SmallString<128> reqBuf;
llvm::StringRef req = is.getline(reqBuf, 4096);
if (is.has_error()) {
SDEBUG("error getting request string");
return;
}
enum { kCommand, kStream, kGetSettings, kRootPage } kind;
llvm::StringRef parameters;
size_t pos;
SDEBUG("HTTP request: '" << req << "'\n");
// Determine request kind. Most of these are for mjpgstreamer
// compatibility, others are for Axis camera compatibility.
if ((pos = req.find("POST /stream")) != llvm::StringRef::npos) {
kind = kStream;
parameters = req.substr(req.find('?', pos + 12)).substr(1);
} else if ((pos = req.find("GET /?action=stream")) != llvm::StringRef::npos) {
kind = kStream;
parameters = req.substr(req.find('&', pos + 19)).substr(1);
} else if ((pos = req.find("GET /stream.mjpg")) != llvm::StringRef::npos) {
kind = kStream;
parameters = req.substr(req.find('?', pos + 16)).substr(1);
} else if (req.find("GET /settings") != llvm::StringRef::npos &&
req.find(".json") != llvm::StringRef::npos) {
kind = kGetSettings;
} else if (req.find("GET /input") != llvm::StringRef::npos &&
req.find(".json") != llvm::StringRef::npos) {
kind = kGetSettings;
} else if (req.find("GET /output") != llvm::StringRef::npos &&
req.find(".json") != llvm::StringRef::npos) {
kind = kGetSettings;
} else if ((pos = req.find("GET /?action=command")) !=
llvm::StringRef::npos) {
kind = kCommand;
parameters = req.substr(req.find('&', pos + 20)).substr(1);
} else if (req.find("GET / ") != llvm::StringRef::npos || req == "GET /\n") {
kind = kRootPage;
} else {
SDEBUG("HTTP request resource not found");
SendError(os, 404, "Resource not found");
return;
}
// Parameter can only be certain characters. This also strips the EOL.
pos = parameters.find_first_not_of(
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"
"-=&1234567890%./");
parameters = parameters.substr(0, pos);
SDEBUG("command parameters: \"" << parameters << "\"");
// Read the rest of the HTTP request.
// The end of the request is marked by a single, empty line
llvm::SmallString<128> lineBuf;
for (;;) {
if (is.getline(lineBuf, 4096).startswith("\n")) break;
if (is.has_error()) return;
}
// Send response
switch (kind) {
case kStream:
if (auto source = GetSource()) {
SDEBUG("request for stream " << source->GetName());
if (!ProcessCommand(os, *source, parameters, false)) return;
}
SendStream(os);
break;
case kCommand:
if (auto source = GetSource()) {
ProcessCommand(os, *source, parameters, true);
} else {
SendHeader(os, 200, "OK", "text/plain");
os << "Ignored due to no connected source."
<< "\r\n";
SDEBUG("Ignored due to no connected source.");
}
break;
case kGetSettings:
SDEBUG("request for JSON file");
if (auto source = GetSource())
SendJSON(os, *source, true);
else
SendError(os, 404, "Resource not found");
break;
case kRootPage:
SDEBUG("request for root page");
SendHeader(os, 200, "OK", "text/html");
if (auto source = GetSource()) {
SendHTML(os, *source, false);
} else {
SendHTMLHeadTitle(os);
os << emptyRootPage << "\r\n";
}
break;
}
SDEBUG("leaving HTTP client thread");
}
// worker thread for clients that connected to this server
void MjpegServerImpl::ConnThread::Main() {
std::unique_lock<wpi::mutex> lock(m_mutex);
while (m_active) {
while (!m_stream) {
m_cond.wait(lock);
if (!m_active) return;
}
lock.unlock();
ProcessRequest();
lock.lock();
m_stream = nullptr;
}
}
// Main server thread
void MjpegServerImpl::ServerThreadMain() {
if (m_acceptor->start() != 0) {
m_active = false;
return;
}
SDEBUG("waiting for clients to connect");
while (m_active) {
auto stream = m_acceptor->accept();
if (!stream) {
m_active = false;
return;
}
if (!m_active) return;
SDEBUG("client connection from " << stream->getPeerIP());
auto source = GetSource();
std::lock_guard<wpi::mutex> lock(m_mutex);
// Find unoccupied worker thread, or create one if necessary
auto it = std::find_if(m_connThreads.begin(), m_connThreads.end(),
[](const wpi::SafeThreadOwner<ConnThread>& owner) {
auto thr = owner.GetThread();
return !thr || !thr->m_stream;
});
if (it == m_connThreads.end()) {
m_connThreads.emplace_back();
it = std::prev(m_connThreads.end());
}
// Start it if not already started
{
auto thr = it->GetThread();
if (!thr) it->Start(new ConnThread{GetName()});
}
auto nstreams =
std::count_if(m_connThreads.begin(), m_connThreads.end(),
[](const wpi::SafeThreadOwner<ConnThread>& owner) {
auto thr = owner.GetThread();
return thr && thr->m_streaming;
});
// Hand off connection to it
auto thr = it->GetThread();
thr->m_stream = std::move(stream);
thr->m_source = source;
thr->m_noStreaming = nstreams >= 10;
thr->m_cond.notify_one();
}
SDEBUG("leaving server thread");
}
void MjpegServerImpl::SetSourceImpl(std::shared_ptr<SourceImpl> source) {
std::lock_guard<wpi::mutex> lock(m_mutex);
for (auto& connThread : m_connThreads) {
if (auto thr = connThread.GetThread()) {
if (thr->m_source != source) {
bool streaming = thr->m_streaming;
if (thr->m_source && streaming) thr->m_source->DisableSink();
thr->m_source = source;
if (source && streaming) thr->m_source->EnableSink();
}
}
}
}
namespace cs {
CS_Sink CreateMjpegServer(llvm::StringRef name, llvm::StringRef listenAddress,
int port, CS_Status* status) {
llvm::SmallString<128> str{listenAddress};
auto sink = std::make_shared<MjpegServerImpl>(
name, listenAddress, port,
std::unique_ptr<wpi::NetworkAcceptor>(
new wpi::TCPAcceptor(port, str.c_str(), Logger::GetInstance())));
auto handle = Sinks::GetInstance().Allocate(CS_SINK_MJPEG, sink);
Notifier::GetInstance().NotifySink(name, handle, CS_SINK_CREATED);
return handle;
}
std::string GetMjpegServerListenAddress(CS_Sink sink, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data || data->kind != CS_SINK_MJPEG) {
*status = CS_INVALID_HANDLE;
return std::string{};
}
return static_cast<MjpegServerImpl&>(*data->sink).GetListenAddress();
}
int GetMjpegServerPort(CS_Sink sink, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data || data->kind != CS_SINK_MJPEG) {
*status = CS_INVALID_HANDLE;
return 0;
}
return static_cast<MjpegServerImpl&>(*data->sink).GetPort();
}
} // namespace cs
extern "C" {
CS_Sink CS_CreateMjpegServer(const char* name, const char* listenAddress,
int port, CS_Status* status) {
return cs::CreateMjpegServer(name, listenAddress, port, status);
}
char* CS_GetMjpegServerListenAddress(CS_Sink sink, CS_Status* status) {
return ConvertToC(cs::GetMjpegServerListenAddress(sink, status));
}
int CS_GetMjpegServerPort(CS_Sink sink, CS_Status* status) {
return cs::GetMjpegServerPort(sink, status);
}
} // extern "C"

View File

@@ -0,0 +1,62 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_MJPEGSERVERIMPL_H_
#define CSCORE_MJPEGSERVERIMPL_H_
#include <atomic>
#include <memory>
#include <string>
#include <thread>
#include <vector>
#include <llvm/SmallVector.h>
#include <llvm/StringRef.h>
#include <llvm/raw_ostream.h>
#include <support/SafeThread.h>
#include <support/raw_istream.h>
#include <support/raw_socket_ostream.h>
#include <tcpsockets/NetworkAcceptor.h>
#include <tcpsockets/NetworkStream.h>
#include "SinkImpl.h"
namespace cs {
class SourceImpl;
class MjpegServerImpl : public SinkImpl {
public:
MjpegServerImpl(llvm::StringRef name, llvm::StringRef listenAddress, int port,
std::unique_ptr<wpi::NetworkAcceptor> acceptor);
~MjpegServerImpl() override;
void Stop();
std::string GetListenAddress() { return m_listenAddress; }
int GetPort() { return m_port; }
private:
void SetSourceImpl(std::shared_ptr<SourceImpl> source) override;
void ServerThreadMain();
class ConnThread;
// Never changed, so not protected by mutex
std::string m_listenAddress;
int m_port;
std::unique_ptr<wpi::NetworkAcceptor> m_acceptor;
std::atomic_bool m_active; // set to false to terminate threads
std::thread m_serverThread;
std::vector<wpi::SafeThreadOwner<ConnThread>> m_connThreads;
};
} // namespace cs
#endif // CSCORE_MJPEGSERVERIMPL_H_

View File

@@ -0,0 +1,136 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2015-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "NetworkListener.h"
#ifdef __linux__
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <sys/eventfd.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <cerrno>
#endif
#include "Log.h"
#include "Notifier.h"
using namespace cs;
class NetworkListener::Thread : public wpi::SafeThread {
public:
void Main();
#ifdef __linux__
int m_command_fd = -1;
#endif
};
NetworkListener::~NetworkListener() { Stop(); }
void NetworkListener::Start() {
auto thr = m_owner.GetThread();
if (!thr) m_owner.Start();
}
void NetworkListener::Stop() {
// Wake up thread
if (auto thr = m_owner.GetThread()) {
thr->m_active = false;
#ifdef __linux__
if (thr->m_command_fd >= 0) eventfd_write(thr->m_command_fd, 1);
#endif
}
m_owner.Stop();
}
void NetworkListener::Thread::Main() {
#ifdef __linux__
// Create event socket so we can be shut down
m_command_fd = ::eventfd(0, 0);
if (m_command_fd < 0) {
ERROR(
"NetworkListener: could not create eventfd: " << std::strerror(errno));
return;
}
// Create netlink socket
int sd = ::socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (sd < 0) {
ERROR("NetworkListener: could not create socket: " << std::strerror(errno));
::close(m_command_fd);
m_command_fd = -1;
return;
}
// Bind to netlink socket
struct sockaddr_nl addr;
std::memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR;
if (bind(sd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) < 0) {
ERROR("NetworkListener: could not create socket: " << std::strerror(errno));
::close(sd);
::close(m_command_fd);
m_command_fd = -1;
return;
}
char buf[4096];
while (m_active) {
// select timeout
struct timeval tv;
tv.tv_sec = 10;
tv.tv_usec = 0;
// select on applicable read descriptors
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(m_command_fd, &readfds);
FD_SET(sd, &readfds);
int nfds = std::max(m_command_fd, sd) + 1;
if (::select(nfds, &readfds, nullptr, nullptr, &tv) < 0) {
ERROR("NetworkListener: select(): " << std::strerror(errno));
break; // XXX: is this the right thing to do here?
}
// Double-check to see if we're shutting down
if (!m_active) break;
if (!FD_ISSET(sd, &readfds)) continue;
std::memset(&addr, 0, sizeof(addr));
struct iovec iov = {buf, sizeof(buf)};
struct msghdr msg = {&addr, sizeof(addr), &iov, 1, nullptr, 0, 0};
int len = ::recvmsg(sd, &msg, 0);
if (len < 0) {
if (errno == EWOULDBLOCK || errno == EAGAIN) continue;
ERROR(
"NetworkListener: could not read netlink: " << std::strerror(errno));
break; // XXX: is this the right thing to do here?
}
if (len == 0) continue; // EOF?
for (struct nlmsghdr* nh = reinterpret_cast<struct nlmsghdr*>(buf);
NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) {
if (nh->nlmsg_type == NLMSG_DONE) break;
if (nh->nlmsg_type == RTM_NEWLINK || nh->nlmsg_type == RTM_DELLINK ||
nh->nlmsg_type == RTM_NEWADDR || nh->nlmsg_type == RTM_DELADDR) {
Notifier::GetInstance().NotifyNetworkInterfacesChanged();
}
}
}
::close(sd);
::close(m_command_fd);
m_command_fd = -1;
#endif
}

View File

@@ -0,0 +1,35 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2015-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_NETWORKLISTENER_H_
#define CSCORE_NETWORKLISTENER_H_
#include <support/SafeThread.h>
namespace cs {
class NetworkListener {
public:
static NetworkListener& GetInstance() {
static NetworkListener instance;
return instance;
}
~NetworkListener();
void Start();
void Stop();
private:
NetworkListener() = default;
class Thread;
wpi::SafeThreadOwner<Thread> m_owner;
};
} // namespace cs
#endif // CSCORE_NETWORKLISTENER_H_

View File

@@ -0,0 +1,234 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2015-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "Notifier.h"
#include <queue>
#include <vector>
#include "Handle.h"
#include "SinkImpl.h"
#include "SourceImpl.h"
using namespace cs;
bool Notifier::s_destroyed = false;
namespace {
// Vector which provides an integrated freelist for removal and reuse of
// individual elements.
template <typename T>
class UidVector {
public:
typedef typename std::vector<T>::size_type size_type;
size_type size() const { return m_vector.size(); }
T& operator[](size_type i) { return m_vector[i]; }
const T& operator[](size_type i) const { return m_vector[i]; }
// Add a new T to the vector. If there are elements on the freelist,
// reuses the last one; otherwise adds to the end of the vector.
// Returns the resulting element index (+1).
template <class... Args>
unsigned int emplace_back(Args&&... args) {
unsigned int uid;
if (m_free.empty()) {
uid = m_vector.size();
m_vector.emplace_back(std::forward<Args>(args)...);
} else {
uid = m_free.back();
m_free.pop_back();
m_vector[uid] = T(std::forward<Args>(args)...);
}
return uid + 1;
}
// Removes the identified element by replacing it with a default-constructed
// one. The element is added to the freelist for later reuse.
void erase(unsigned int uid) {
--uid;
if (uid >= m_vector.size() || !m_vector[uid]) return;
m_free.push_back(uid);
m_vector[uid] = T();
}
private:
std::vector<T> m_vector;
std::vector<unsigned int> m_free;
};
} // namespace
class Notifier::Thread : public wpi::SafeThread {
public:
Thread(std::function<void()> on_start, std::function<void()> on_exit)
: m_on_start(on_start), m_on_exit(on_exit) {}
void Main();
struct Listener {
Listener() = default;
Listener(std::function<void(const RawEvent& event)> callback_,
int eventMask_)
: callback(callback_), eventMask(eventMask_) {}
explicit operator bool() const { return static_cast<bool>(callback); }
std::string prefix;
std::function<void(const RawEvent& event)> callback;
int eventMask;
};
UidVector<Listener> m_listeners;
std::queue<RawEvent> m_notifications;
std::function<void()> m_on_start;
std::function<void()> m_on_exit;
};
Notifier::Notifier() { s_destroyed = false; }
Notifier::~Notifier() { s_destroyed = true; }
void Notifier::Start() {
auto thr = m_owner.GetThread();
if (!thr) m_owner.Start(new Thread(m_on_start, m_on_exit));
}
void Notifier::Stop() { m_owner.Stop(); }
void Notifier::Thread::Main() {
if (m_on_start) m_on_start();
std::unique_lock<wpi::mutex> lock(m_mutex);
while (m_active) {
while (m_notifications.empty()) {
m_cond.wait(lock);
if (!m_active) goto done;
}
while (!m_notifications.empty()) {
if (!m_active) goto done;
auto item = std::move(m_notifications.front());
m_notifications.pop();
// Use index because iterator might get invalidated.
for (size_t i = 0; i < m_listeners.size(); ++i) {
if (!m_listeners[i]) continue; // removed
// Event type must be within requested set for this listener.
if ((item.kind & m_listeners[i].eventMask) == 0) continue;
// make a copy of the callback so we can safely release the mutex
auto callback = m_listeners[i].callback;
// Don't hold mutex during callback execution!
lock.unlock();
callback(item);
lock.lock();
}
}
}
done:
if (m_on_exit) m_on_exit();
}
int Notifier::AddListener(std::function<void(const RawEvent& event)> callback,
int eventMask) {
Start();
auto thr = m_owner.GetThread();
return thr->m_listeners.emplace_back(callback, eventMask);
}
void Notifier::RemoveListener(int uid) {
auto thr = m_owner.GetThread();
if (!thr) return;
thr->m_listeners.erase(uid);
}
void Notifier::NotifySource(llvm::StringRef name, CS_Source source,
CS_EventKind kind) {
auto thr = m_owner.GetThread();
if (!thr) return;
thr->m_notifications.emplace(name, source, static_cast<RawEvent::Kind>(kind));
thr->m_cond.notify_one();
}
void Notifier::NotifySource(const SourceImpl& source, CS_EventKind kind) {
auto handleData = Sources::GetInstance().Find(source);
NotifySource(source.GetName(), handleData.first, kind);
}
void Notifier::NotifySourceVideoMode(const SourceImpl& source,
const VideoMode& mode) {
auto thr = m_owner.GetThread();
if (!thr) return;
auto handleData = Sources::GetInstance().Find(source);
thr->m_notifications.emplace(source.GetName(), handleData.first, mode);
thr->m_cond.notify_one();
}
void Notifier::NotifySourceProperty(const SourceImpl& source, CS_EventKind kind,
llvm::StringRef propertyName, int property,
CS_PropertyKind propertyKind, int value,
llvm::StringRef valueStr) {
auto thr = m_owner.GetThread();
if (!thr) return;
auto handleData = Sources::GetInstance().Find(source);
thr->m_notifications.emplace(
propertyName, handleData.first, static_cast<RawEvent::Kind>(kind),
Handle{handleData.first, property, Handle::kProperty}, propertyKind,
value, valueStr);
thr->m_cond.notify_one();
}
void Notifier::NotifySink(llvm::StringRef name, CS_Sink sink,
CS_EventKind kind) {
auto thr = m_owner.GetThread();
if (!thr) return;
thr->m_notifications.emplace(name, sink, static_cast<RawEvent::Kind>(kind));
thr->m_cond.notify_one();
}
void Notifier::NotifySink(const SinkImpl& sink, CS_EventKind kind) {
auto handleData = Sinks::GetInstance().Find(sink);
NotifySink(sink.GetName(), handleData.first, kind);
}
void Notifier::NotifySinkSourceChanged(llvm::StringRef name, CS_Sink sink,
CS_Source source) {
auto thr = m_owner.GetThread();
if (!thr) return;
RawEvent event{name, sink, RawEvent::kSinkSourceChanged};
event.sourceHandle = source;
thr->m_notifications.emplace(std::move(event));
thr->m_cond.notify_one();
}
void Notifier::NotifyNetworkInterfacesChanged() {
auto thr = m_owner.GetThread();
if (!thr) return;
thr->m_notifications.emplace(RawEvent::kNetworkInterfacesChanged);
thr->m_cond.notify_one();
}
void Notifier::NotifyTelemetryUpdated() {
auto thr = m_owner.GetThread();
if (!thr) return;
thr->m_notifications.emplace(RawEvent::kTelemetryUpdated);
thr->m_cond.notify_one();
}

View File

@@ -0,0 +1,72 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2015-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_NOTIFIER_H_
#define CSCORE_NOTIFIER_H_
#include <functional>
#include <support/SafeThread.h>
#include "cscore_cpp.h"
namespace cs {
class SinkImpl;
class SourceImpl;
class Notifier {
friend class NotifierTest;
public:
static Notifier& GetInstance() {
static Notifier instance;
return instance;
}
~Notifier();
void Start();
void Stop();
static bool destroyed() { return s_destroyed; }
void SetOnStart(std::function<void()> on_start) { m_on_start = on_start; }
void SetOnExit(std::function<void()> on_exit) { m_on_exit = on_exit; }
int AddListener(std::function<void(const RawEvent& event)> callback,
int eventMask);
void RemoveListener(int uid);
// Notification events
void NotifySource(llvm::StringRef name, CS_Source source, CS_EventKind kind);
void NotifySource(const SourceImpl& source, CS_EventKind kind);
void NotifySourceVideoMode(const SourceImpl& source, const VideoMode& mode);
void NotifySourceProperty(const SourceImpl& source, CS_EventKind kind,
llvm::StringRef propertyName, int property,
CS_PropertyKind propertyKind, int value,
llvm::StringRef valueStr);
void NotifySink(llvm::StringRef name, CS_Sink sink, CS_EventKind kind);
void NotifySink(const SinkImpl& sink, CS_EventKind kind);
void NotifySinkSourceChanged(llvm::StringRef name, CS_Sink sink,
CS_Source source);
void NotifyNetworkInterfacesChanged();
void NotifyTelemetryUpdated();
private:
Notifier();
class Thread;
wpi::SafeThreadOwner<Thread> m_owner;
std::function<void()> m_on_start;
std::function<void()> m_on_exit;
static bool s_destroyed;
};
} // namespace cs
#endif // CSCORE_NOTIFIER_H_

View File

@@ -0,0 +1,76 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_PROPERTYIMPL_H_
#define CSCORE_PROPERTYIMPL_H_
#include <string>
#include <vector>
#include <llvm/StringRef.h>
#include "cscore_c.h"
namespace cs {
// Property data
class PropertyImpl {
public:
PropertyImpl() = default;
explicit PropertyImpl(llvm::StringRef name_) : name{name_} {}
PropertyImpl(llvm::StringRef name_, CS_PropertyKind kind_, int step_,
int defaultValue_, int value_)
: name{name_},
propKind{kind_},
step{step_},
defaultValue{defaultValue_},
value{value_} {}
virtual ~PropertyImpl() = default;
PropertyImpl(const PropertyImpl& oth) = delete;
PropertyImpl& operator=(const PropertyImpl& oth) = delete;
void SetValue(int v) {
if (hasMinimum && v < minimum)
value = minimum;
else if (hasMaximum && v > maximum)
value = maximum;
else
value = v;
valueSet = true;
}
void SetValue(llvm::StringRef v) {
valueStr = v;
valueSet = true;
}
void SetDefaultValue(int v) {
if (hasMinimum && v < minimum)
defaultValue = minimum;
else if (hasMaximum && v > maximum)
defaultValue = maximum;
else
defaultValue = v;
}
std::string name;
CS_PropertyKind propKind{CS_PROP_NONE};
bool hasMinimum{false};
bool hasMaximum{false};
int minimum{0};
int maximum{100};
int step{1};
int defaultValue{0};
int value{0};
std::string valueStr;
std::vector<std::string> enumChoices;
bool valueSet{false};
};
} // namespace cs
#endif // CSCORE_PROPERTYIMPL_H_

View File

@@ -0,0 +1,100 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "SinkImpl.h"
#include "Notifier.h"
#include "SourceImpl.h"
using namespace cs;
SinkImpl::SinkImpl(llvm::StringRef name) : m_name{name} {}
SinkImpl::~SinkImpl() {
if (m_source) {
if (m_enabledCount > 0) m_source->DisableSink();
m_source->RemoveSink();
}
}
void SinkImpl::SetDescription(llvm::StringRef description) {
std::lock_guard<wpi::mutex> lock(m_mutex);
m_description = description;
}
llvm::StringRef SinkImpl::GetDescription(
llvm::SmallVectorImpl<char>& buf) const {
std::lock_guard<wpi::mutex> lock(m_mutex);
buf.append(m_description.begin(), m_description.end());
return llvm::StringRef{buf.data(), buf.size()};
}
void SinkImpl::Enable() {
std::lock_guard<wpi::mutex> lock(m_mutex);
++m_enabledCount;
if (m_enabledCount == 1) {
if (m_source) m_source->EnableSink();
Notifier::GetInstance().NotifySink(*this, CS_SINK_ENABLED);
}
}
void SinkImpl::Disable() {
std::lock_guard<wpi::mutex> lock(m_mutex);
--m_enabledCount;
if (m_enabledCount == 0) {
if (m_source) m_source->DisableSink();
Notifier::GetInstance().NotifySink(*this, CS_SINK_DISABLED);
}
}
void SinkImpl::SetEnabled(bool enabled) {
std::lock_guard<wpi::mutex> lock(m_mutex);
if (enabled && m_enabledCount == 0) {
if (m_source) m_source->EnableSink();
m_enabledCount = 1;
Notifier::GetInstance().NotifySink(*this, CS_SINK_ENABLED);
} else if (!enabled && m_enabledCount > 0) {
if (m_source) m_source->DisableSink();
m_enabledCount = 0;
Notifier::GetInstance().NotifySink(*this, CS_SINK_DISABLED);
}
}
void SinkImpl::SetSource(std::shared_ptr<SourceImpl> source) {
{
std::lock_guard<wpi::mutex> lock(m_mutex);
if (m_source == source) return;
if (m_source) {
if (m_enabledCount > 0) m_source->DisableSink();
m_source->RemoveSink();
}
m_source = source;
if (m_source) {
m_source->AddSink();
if (m_enabledCount > 0) m_source->EnableSink();
}
}
SetSourceImpl(source);
}
std::string SinkImpl::GetError() const {
std::lock_guard<wpi::mutex> lock(m_mutex);
if (!m_source) return "no source connected";
return m_source->GetCurFrame().GetError();
}
llvm::StringRef SinkImpl::GetError(llvm::SmallVectorImpl<char>& buf) const {
std::lock_guard<wpi::mutex> lock(m_mutex);
if (!m_source) return "no source connected";
// Make a copy as it's shared data
llvm::StringRef error = m_source->GetCurFrame().GetError();
buf.clear();
buf.append(error.data(), error.data() + error.size());
return llvm::StringRef{buf.data(), buf.size()};
}
void SinkImpl::SetSourceImpl(std::shared_ptr<SourceImpl> source) {}

View File

@@ -0,0 +1,63 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_SINKIMPL_H_
#define CSCORE_SINKIMPL_H_
#include <memory>
#include <string>
#include <llvm/StringRef.h>
#include <support/mutex.h>
#include "SourceImpl.h"
namespace cs {
class Frame;
class SinkImpl {
public:
explicit SinkImpl(llvm::StringRef name);
virtual ~SinkImpl();
SinkImpl(const SinkImpl& queue) = delete;
SinkImpl& operator=(const SinkImpl& queue) = delete;
llvm::StringRef GetName() const { return m_name; }
void SetDescription(llvm::StringRef description);
llvm::StringRef GetDescription(llvm::SmallVectorImpl<char>& buf) const;
void Enable();
void Disable();
void SetEnabled(bool enabled);
void SetSource(std::shared_ptr<SourceImpl> source);
std::shared_ptr<SourceImpl> GetSource() const {
std::lock_guard<wpi::mutex> lock(m_mutex);
return m_source;
}
std::string GetError() const;
llvm::StringRef GetError(llvm::SmallVectorImpl<char>& buf) const;
protected:
virtual void SetSourceImpl(std::shared_ptr<SourceImpl> source);
mutable wpi::mutex m_mutex;
private:
std::string m_name;
std::string m_description;
std::shared_ptr<SourceImpl> m_source;
int m_enabledCount{0};
};
} // namespace cs
#endif // CSCORE_SINKIMPL_H_

View File

@@ -0,0 +1,421 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "SourceImpl.h"
#include <algorithm>
#include <cstring>
#include <llvm/STLExtras.h>
#include <support/timestamp.h>
#include "Log.h"
#include "Notifier.h"
#include "Telemetry.h"
using namespace cs;
static constexpr size_t kMaxImagesAvail = 32;
SourceImpl::SourceImpl(llvm::StringRef name) : m_name{name} {
m_frame = Frame{*this, llvm::StringRef{}, 0};
}
SourceImpl::~SourceImpl() {
// Wake up anyone who is waiting. This also clears the current frame,
// which is good because its destructor will call back into the class.
Wakeup();
// Set a flag so ReleaseFrame() doesn't re-add them to m_framesAvail.
// Put in a block so we destroy before the destructor ends.
{
m_destroyFrames = true;
auto frames = std::move(m_framesAvail);
}
// Everything else can clean up itself.
}
void SourceImpl::SetDescription(llvm::StringRef description) {
std::lock_guard<wpi::mutex> lock(m_mutex);
m_description = description;
}
llvm::StringRef SourceImpl::GetDescription(
llvm::SmallVectorImpl<char>& buf) const {
std::lock_guard<wpi::mutex> lock(m_mutex);
buf.append(m_description.begin(), m_description.end());
return llvm::StringRef{buf.data(), buf.size()};
}
void SourceImpl::SetConnected(bool connected) {
bool wasConnected = m_connected.exchange(connected);
if (wasConnected && !connected)
Notifier::GetInstance().NotifySource(*this, CS_SOURCE_DISCONNECTED);
else if (!wasConnected && connected)
Notifier::GetInstance().NotifySource(*this, CS_SOURCE_CONNECTED);
}
uint64_t SourceImpl::GetCurFrameTime() {
std::unique_lock<wpi::mutex> lock{m_frameMutex};
return m_frame.GetTime();
}
Frame SourceImpl::GetCurFrame() {
std::unique_lock<wpi::mutex> lock{m_frameMutex};
return m_frame;
}
Frame SourceImpl::GetNextFrame() {
std::unique_lock<wpi::mutex> lock{m_frameMutex};
auto oldTime = m_frame.GetTime();
m_frameCv.wait(lock, [=] { return m_frame.GetTime() != oldTime; });
return m_frame;
}
Frame SourceImpl::GetNextFrame(double timeout) {
std::unique_lock<wpi::mutex> lock{m_frameMutex};
auto oldTime = m_frame.GetTime();
if (!m_frameCv.wait_for(
lock, std::chrono::milliseconds(static_cast<int>(timeout * 1000)),
[=] { return m_frame.GetTime() != oldTime; })) {
m_frame = Frame{*this, "timed out getting frame", wpi::Now()};
}
return m_frame;
}
void SourceImpl::Wakeup() {
{
std::lock_guard<wpi::mutex> lock{m_frameMutex};
m_frame = Frame{*this, llvm::StringRef{}, 0};
}
m_frameCv.notify_all();
}
int SourceImpl::GetPropertyIndex(llvm::StringRef name) const {
// We can't fail, so instead we create a new index if caching fails.
CS_Status status = 0;
if (!m_properties_cached) CacheProperties(&status);
std::lock_guard<wpi::mutex> lock(m_mutex);
int& ndx = m_properties[name];
if (ndx == 0) {
// create a new index
ndx = m_propertyData.size() + 1;
m_propertyData.emplace_back(CreateEmptyProperty(name));
}
return ndx;
}
llvm::ArrayRef<int> SourceImpl::EnumerateProperties(
llvm::SmallVectorImpl<int>& vec, CS_Status* status) const {
if (!m_properties_cached && !CacheProperties(status))
return llvm::ArrayRef<int>{};
std::lock_guard<wpi::mutex> lock(m_mutex);
for (int i = 0; i < static_cast<int>(m_propertyData.size()); ++i) {
if (m_propertyData[i]) vec.push_back(i + 1);
}
return vec;
}
CS_PropertyKind SourceImpl::GetPropertyKind(int property) const {
CS_Status status = 0;
if (!m_properties_cached && !CacheProperties(&status)) return CS_PROP_NONE;
std::lock_guard<wpi::mutex> lock(m_mutex);
auto prop = GetProperty(property);
if (!prop) return CS_PROP_NONE;
return prop->propKind;
}
llvm::StringRef SourceImpl::GetPropertyName(int property,
llvm::SmallVectorImpl<char>& buf,
CS_Status* status) const {
if (!m_properties_cached && !CacheProperties(status))
return llvm::StringRef{};
std::lock_guard<wpi::mutex> lock(m_mutex);
auto prop = GetProperty(property);
if (!prop) {
*status = CS_INVALID_PROPERTY;
return llvm::StringRef{};
}
// safe to not copy because we never modify it after caching
return prop->name;
}
int SourceImpl::GetProperty(int property, CS_Status* status) const {
if (!m_properties_cached && !CacheProperties(status)) return 0;
std::lock_guard<wpi::mutex> lock(m_mutex);
auto prop = GetProperty(property);
if (!prop) {
*status = CS_INVALID_PROPERTY;
return 0;
}
if ((prop->propKind & (CS_PROP_BOOLEAN | CS_PROP_INTEGER | CS_PROP_ENUM)) ==
0) {
*status = CS_WRONG_PROPERTY_TYPE;
return 0;
}
return prop->value;
}
int SourceImpl::GetPropertyMin(int property, CS_Status* status) const {
if (!m_properties_cached && !CacheProperties(status)) return 0;
std::lock_guard<wpi::mutex> lock(m_mutex);
auto prop = GetProperty(property);
if (!prop) {
*status = CS_INVALID_PROPERTY;
return 0;
}
return prop->minimum;
}
int SourceImpl::GetPropertyMax(int property, CS_Status* status) const {
if (!m_properties_cached && !CacheProperties(status)) return 0;
std::lock_guard<wpi::mutex> lock(m_mutex);
auto prop = GetProperty(property);
if (!prop) {
*status = CS_INVALID_PROPERTY;
return 0;
}
return prop->maximum;
}
int SourceImpl::GetPropertyStep(int property, CS_Status* status) const {
if (!m_properties_cached && !CacheProperties(status)) return 0;
std::lock_guard<wpi::mutex> lock(m_mutex);
auto prop = GetProperty(property);
if (!prop) {
*status = CS_INVALID_PROPERTY;
return 0;
}
return prop->step;
}
int SourceImpl::GetPropertyDefault(int property, CS_Status* status) const {
if (!m_properties_cached && !CacheProperties(status)) return 0;
std::lock_guard<wpi::mutex> lock(m_mutex);
auto prop = GetProperty(property);
if (!prop) {
*status = CS_INVALID_PROPERTY;
return 0;
}
return prop->defaultValue;
}
llvm::StringRef SourceImpl::GetStringProperty(int property,
llvm::SmallVectorImpl<char>& buf,
CS_Status* status) const {
if (!m_properties_cached && !CacheProperties(status))
return llvm::StringRef{};
std::lock_guard<wpi::mutex> lock(m_mutex);
auto prop = GetProperty(property);
if (!prop) {
*status = CS_INVALID_PROPERTY;
return llvm::StringRef{};
}
if (prop->propKind != CS_PROP_STRING) {
*status = CS_WRONG_PROPERTY_TYPE;
return llvm::StringRef{};
}
buf.clear();
buf.append(prop->valueStr.begin(), prop->valueStr.end());
return llvm::StringRef(buf.data(), buf.size());
}
std::vector<std::string> SourceImpl::GetEnumPropertyChoices(
int property, CS_Status* status) const {
if (!m_properties_cached && !CacheProperties(status))
return std::vector<std::string>{};
std::lock_guard<wpi::mutex> lock(m_mutex);
auto prop = GetProperty(property);
if (!prop) {
*status = CS_INVALID_PROPERTY;
return std::vector<std::string>{};
}
if (prop->propKind != CS_PROP_ENUM) {
*status = CS_WRONG_PROPERTY_TYPE;
return std::vector<std::string>{};
}
return prop->enumChoices;
}
VideoMode SourceImpl::GetVideoMode(CS_Status* status) const {
if (!m_properties_cached && !CacheProperties(status)) return VideoMode{};
std::lock_guard<wpi::mutex> lock(m_mutex);
return m_mode;
}
bool SourceImpl::SetPixelFormat(VideoMode::PixelFormat pixelFormat,
CS_Status* status) {
auto mode = GetVideoMode(status);
if (!mode) return false;
mode.pixelFormat = pixelFormat;
return SetVideoMode(mode, status);
}
bool SourceImpl::SetResolution(int width, int height, CS_Status* status) {
auto mode = GetVideoMode(status);
if (!mode) return false;
mode.width = width;
mode.height = height;
return SetVideoMode(mode, status);
}
bool SourceImpl::SetFPS(int fps, CS_Status* status) {
auto mode = GetVideoMode(status);
if (!mode) return false;
mode.fps = fps;
return SetVideoMode(mode, status);
}
std::vector<VideoMode> SourceImpl::EnumerateVideoModes(
CS_Status* status) const {
if (!m_properties_cached && !CacheProperties(status))
return std::vector<VideoMode>{};
std::lock_guard<wpi::mutex> lock(m_mutex);
return m_videoModes;
}
std::unique_ptr<Image> SourceImpl::AllocImage(
VideoMode::PixelFormat pixelFormat, int width, int height, size_t size) {
std::unique_ptr<Image> image;
{
std::lock_guard<wpi::mutex> lock{m_poolMutex};
// find the smallest existing frame that is at least big enough.
int found = -1;
for (size_t i = 0; i < m_imagesAvail.size(); ++i) {
// is it big enough?
if (m_imagesAvail[i] && m_imagesAvail[i]->capacity() >= size) {
// is it smaller than the last found?
if (found < 0 ||
m_imagesAvail[i]->capacity() < m_imagesAvail[found]->capacity()) {
// yes, update
found = i;
}
}
}
// if nothing found, allocate a new buffer
if (found < 0)
image.reset(new Image{size});
else
image = std::move(m_imagesAvail[found]);
}
// Initialize image
image->SetSize(size);
image->pixelFormat = pixelFormat;
image->width = width;
image->height = height;
return image;
}
void SourceImpl::PutFrame(VideoMode::PixelFormat pixelFormat, int width,
int height, llvm::StringRef data, Frame::Time time) {
auto image = AllocImage(pixelFormat, width, height, data.size());
// Copy in image data
SDEBUG4("Copying data to "
<< reinterpret_cast<const void*>(image->data()) << " from "
<< reinterpret_cast<const void*>(data.data()) << " (" << data.size()
<< " bytes)");
std::memcpy(image->data(), data.data(), data.size());
PutFrame(std::move(image), time);
}
void SourceImpl::PutFrame(std::unique_ptr<Image> image, Frame::Time time) {
// Update telemetry
Telemetry::GetInstance().RecordSourceFrames(*this, 1);
Telemetry::GetInstance().RecordSourceBytes(*this,
static_cast<int>(image->size()));
// Update frame
{
std::lock_guard<wpi::mutex> lock{m_frameMutex};
m_frame = Frame{*this, std::move(image), time};
}
// Signal listeners
m_frameCv.notify_all();
}
void SourceImpl::PutError(llvm::StringRef msg, Frame::Time time) {
// Update frame
{
std::lock_guard<wpi::mutex> lock{m_frameMutex};
m_frame = Frame{*this, msg, time};
}
// Signal listeners
m_frameCv.notify_all();
}
void SourceImpl::NotifyPropertyCreated(int propIndex, PropertyImpl& prop) {
auto& notifier = Notifier::GetInstance();
notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, prop.name,
propIndex, prop.propKind, prop.value,
prop.valueStr);
// also notify choices updated event for enum types
if (prop.propKind == CS_PROP_ENUM)
notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED,
prop.name, propIndex, prop.propKind,
prop.value, llvm::StringRef{});
}
void SourceImpl::UpdatePropertyValue(int property, bool setString, int value,
llvm::StringRef valueStr) {
auto prop = GetProperty(property);
if (!prop) return;
if (setString)
prop->SetValue(valueStr);
else
prop->SetValue(value);
// Only notify updates after we've notified created
if (m_properties_cached)
Notifier::GetInstance().NotifySourceProperty(
*this, CS_SOURCE_PROPERTY_VALUE_UPDATED, prop->name, property,
prop->propKind, prop->value, prop->valueStr);
}
void SourceImpl::ReleaseImage(std::unique_ptr<Image> image) {
std::lock_guard<wpi::mutex> lock{m_poolMutex};
if (m_destroyFrames) return;
// Return the frame to the pool. First try to find an empty slot, otherwise
// add it to the end.
auto it = std::find(m_imagesAvail.begin(), m_imagesAvail.end(), nullptr);
if (it != m_imagesAvail.end()) {
*it = std::move(image);
} else if (m_imagesAvail.size() > kMaxImagesAvail) {
// Replace smallest buffer; don't need to check for null because the above
// find would have found it.
auto it2 = std::min_element(
m_imagesAvail.begin(), m_imagesAvail.end(),
[](const std::unique_ptr<Image>& a, const std::unique_ptr<Image>& b) {
return a->capacity() < b->capacity();
});
if ((*it2)->capacity() < image->capacity()) *it2 = std::move(image);
} else {
m_imagesAvail.emplace_back(std::move(image));
}
}
std::unique_ptr<Frame::Impl> SourceImpl::AllocFrameImpl() {
std::lock_guard<wpi::mutex> lock{m_poolMutex};
if (m_framesAvail.empty()) return llvm::make_unique<Frame::Impl>(*this);
auto impl = std::move(m_framesAvail.back());
m_framesAvail.pop_back();
return impl;
}
void SourceImpl::ReleaseFrameImpl(std::unique_ptr<Frame::Impl> impl) {
std::lock_guard<wpi::mutex> lock{m_poolMutex};
if (m_destroyFrames) return;
m_framesAvail.push_back(std::move(impl));
}

View File

@@ -0,0 +1,223 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_SOURCEIMPL_H_
#define CSCORE_SOURCEIMPL_H_
#include <atomic>
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
#include <llvm/ArrayRef.h>
#include <llvm/StringMap.h>
#include <llvm/StringRef.h>
#include <support/condition_variable.h>
#include <support/mutex.h>
#include "Frame.h"
#include "Image.h"
#include "PropertyImpl.h"
#include "cscore_cpp.h"
namespace cs {
class SourceImpl {
friend class Frame;
public:
explicit SourceImpl(llvm::StringRef name);
virtual ~SourceImpl();
SourceImpl(const SourceImpl& oth) = delete;
SourceImpl& operator=(const SourceImpl& oth) = delete;
llvm::StringRef GetName() const { return m_name; }
void SetDescription(llvm::StringRef description);
llvm::StringRef GetDescription(llvm::SmallVectorImpl<char>& buf) const;
void SetConnected(bool connected);
bool IsConnected() const { return m_connected; }
// Functions to keep track of the overall number of sinks connected to this
// source. Primarily used by sinks to determine if other sinks are using
// the same source.
int GetNumSinks() const { return m_numSinks; }
void AddSink() {
++m_numSinks;
NumSinksChanged();
}
void RemoveSink() {
--m_numSinks;
NumSinksChanged();
}
// Functions to keep track of the number of sinks connected to this source
// that are "enabled", in other words, listening for new images. Primarily
// used by sources to determine whether they should actually bother trying
// to get source frames.
int GetNumSinksEnabled() const { return m_numSinksEnabled; }
void EnableSink() {
++m_numSinksEnabled;
NumSinksEnabledChanged();
}
void DisableSink() {
--m_numSinksEnabled;
NumSinksEnabledChanged();
}
// Gets the current frame time (without waiting for a new one).
uint64_t GetCurFrameTime();
// Gets the current frame (without waiting for a new one).
Frame GetCurFrame();
// Blocking function that waits for the next frame and returns it.
Frame GetNextFrame();
// Blocking function that waits for the next frame and returns it (with
// timeout in seconds). If timeout expires, returns empty frame.
Frame GetNextFrame(double timeout);
// Force a wakeup of all GetNextFrame() callers by sending an empty frame.
void Wakeup();
// Property functions
int GetPropertyIndex(llvm::StringRef name) const;
llvm::ArrayRef<int> EnumerateProperties(llvm::SmallVectorImpl<int>& vec,
CS_Status* status) const;
CS_PropertyKind GetPropertyKind(int property) const;
llvm::StringRef GetPropertyName(int property,
llvm::SmallVectorImpl<char>& buf,
CS_Status* status) const;
int GetProperty(int property, CS_Status* status) const;
virtual void SetProperty(int property, int value, CS_Status* status) = 0;
int GetPropertyMin(int property, CS_Status* status) const;
int GetPropertyMax(int property, CS_Status* status) const;
int GetPropertyStep(int property, CS_Status* status) const;
int GetPropertyDefault(int property, CS_Status* status) const;
llvm::StringRef GetStringProperty(int property,
llvm::SmallVectorImpl<char>& buf,
CS_Status* status) const;
virtual void SetStringProperty(int property, llvm::StringRef value,
CS_Status* status) = 0;
std::vector<std::string> GetEnumPropertyChoices(int property,
CS_Status* status) const;
// Standard common camera properties
virtual void SetBrightness(int brightness, CS_Status* status) = 0;
virtual int GetBrightness(CS_Status* status) const = 0;
virtual void SetWhiteBalanceAuto(CS_Status* status) = 0;
virtual void SetWhiteBalanceHoldCurrent(CS_Status* status) = 0;
virtual void SetWhiteBalanceManual(int value, CS_Status* status) = 0;
virtual void SetExposureAuto(CS_Status* status) = 0;
virtual void SetExposureHoldCurrent(CS_Status* status) = 0;
virtual void SetExposureManual(int value, CS_Status* status) = 0;
// Video mode functions
VideoMode GetVideoMode(CS_Status* status) const;
virtual bool SetVideoMode(const VideoMode& mode, CS_Status* status) = 0;
// These have default implementations but can be overridden for custom
// or optimized behavior.
virtual bool SetPixelFormat(VideoMode::PixelFormat pixelFormat,
CS_Status* status);
virtual bool SetResolution(int width, int height, CS_Status* status);
virtual bool SetFPS(int fps, CS_Status* status);
std::vector<VideoMode> EnumerateVideoModes(CS_Status* status) const;
std::unique_ptr<Image> AllocImage(VideoMode::PixelFormat pixelFormat,
int width, int height, size_t size);
protected:
void PutFrame(VideoMode::PixelFormat pixelFormat, int width, int height,
llvm::StringRef data, Frame::Time time);
void PutFrame(std::unique_ptr<Image> image, Frame::Time time);
void PutError(llvm::StringRef msg, Frame::Time time);
// Notification functions for corresponding atomics
virtual void NumSinksChanged() = 0;
virtual void NumSinksEnabledChanged() = 0;
std::atomic_int m_numSinks{0};
std::atomic_int m_numSinksEnabled{0};
protected:
// Get a property; must be called with m_mutex held.
PropertyImpl* GetProperty(int property) {
if (property <= 0 || static_cast<size_t>(property) > m_propertyData.size())
return nullptr;
return m_propertyData[property - 1].get();
}
const PropertyImpl* GetProperty(int property) const {
if (property <= 0 || static_cast<size_t>(property) > m_propertyData.size())
return nullptr;
return m_propertyData[property - 1].get();
}
// Create an "empty" property. This is called by GetPropertyIndex to create
// properties that don't exist (as GetPropertyIndex can't fail).
// Note: called with m_mutex held.
virtual std::unique_ptr<PropertyImpl> CreateEmptyProperty(
llvm::StringRef name) const = 0;
// Cache properties. Implementations must return false and set status to
// CS_SOURCE_IS_DISCONNECTED if not possible to cache.
virtual bool CacheProperties(CS_Status* status) const = 0;
void NotifyPropertyCreated(int propIndex, PropertyImpl& prop);
// Update property value; must be called with m_mutex held.
void UpdatePropertyValue(int property, bool setString, int value,
llvm::StringRef valueStr);
// Cached properties and video modes (protected with m_mutex)
mutable std::vector<std::unique_ptr<PropertyImpl>> m_propertyData;
mutable llvm::StringMap<int> m_properties;
mutable std::vector<VideoMode> m_videoModes;
// Current video mode
mutable VideoMode m_mode;
// Whether CacheProperties() has been successful at least once (and thus
// should not be called again)
mutable std::atomic_bool m_properties_cached{false};
mutable wpi::mutex m_mutex;
private:
void ReleaseImage(std::unique_ptr<Image> image);
std::unique_ptr<Frame::Impl> AllocFrameImpl();
void ReleaseFrameImpl(std::unique_ptr<Frame::Impl> data);
std::string m_name;
std::string m_description;
wpi::mutex m_frameMutex;
wpi::condition_variable m_frameCv;
bool m_destroyFrames{false};
// Pool of frames/images to reduce malloc traffic.
wpi::mutex m_poolMutex;
std::vector<std::unique_ptr<Frame::Impl>> m_framesAvail;
std::vector<std::unique_ptr<Image>> m_imagesAvail;
std::atomic_bool m_connected{false};
// Most recent frame (returned to callers of GetNextFrame)
// Access protected by m_frameMutex.
// MUST be located below m_poolMutex as the Frame destructor calls back
// into SourceImpl::ReleaseImage, which locks m_poolMutex.
Frame m_frame;
};
} // namespace cs
#endif // CSCORE_SOURCEIMPL_H_

View File

@@ -0,0 +1,136 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2015-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "Telemetry.h"
#include <chrono>
#include <limits>
#include <llvm/DenseMap.h>
#include <support/timestamp.h>
#include "Handle.h"
#include "Notifier.h"
#include "cscore_cpp.h"
using namespace cs;
class Telemetry::Thread : public wpi::SafeThread {
public:
void Main();
llvm::DenseMap<std::pair<CS_Handle, int>, int64_t> m_user;
llvm::DenseMap<std::pair<CS_Handle, int>, int64_t> m_current;
double m_period = 0.0;
double m_elapsed = 0.0;
bool m_updated = false;
int64_t GetValue(CS_Handle handle, CS_TelemetryKind kind, CS_Status* status);
};
int64_t Telemetry::Thread::GetValue(CS_Handle handle, CS_TelemetryKind kind,
CS_Status* status) {
auto it = m_user.find(std::make_pair(handle, static_cast<int>(kind)));
if (it == m_user.end()) {
*status = CS_EMPTY_VALUE;
return 0;
}
return it->getSecond();
}
Telemetry::Telemetry() {}
Telemetry::~Telemetry() {}
void Telemetry::Start() {
auto thr = m_owner.GetThread();
if (!thr) m_owner.Start(new Thread);
}
void Telemetry::Stop() { m_owner.Stop(); }
void Telemetry::Thread::Main() {
std::unique_lock<wpi::mutex> lock(m_mutex);
auto prevTime = std::chrono::steady_clock::now();
while (m_active) {
double period = m_period;
if (period == 0) period = 1000.0;
auto timeoutTime = prevTime + std::chrono::duration<double>(period);
while (m_active && !m_updated) {
if (m_cond.wait_until(lock, timeoutTime) == std::cv_status::timeout)
break;
}
if (!m_active) break;
if (m_updated) {
m_updated = false;
continue;
}
// move to user and clear current, as we don't keep around old values
m_user = std::move(m_current);
m_current.clear();
auto curTime = std::chrono::steady_clock::now();
m_elapsed = std::chrono::duration<double>(curTime - prevTime).count();
prevTime = curTime;
// notify
Notifier::GetInstance().NotifyTelemetryUpdated();
}
}
void Telemetry::SetPeriod(double seconds) {
auto thr = m_owner.GetThread();
if (!thr) return;
if (thr->m_period == seconds) return; // no change
thr->m_period = seconds;
thr->m_updated = true;
thr->m_cond.notify_one();
}
double Telemetry::GetElapsedTime() {
auto thr = m_owner.GetThread();
if (!thr) return 0;
return thr->m_elapsed;
}
int64_t Telemetry::GetValue(CS_Handle handle, CS_TelemetryKind kind,
CS_Status* status) {
auto thr = m_owner.GetThread();
if (!thr) {
*status = CS_TELEMETRY_NOT_ENABLED;
return 0;
}
return thr->GetValue(handle, kind, status);
}
double Telemetry::GetAverageValue(CS_Handle handle, CS_TelemetryKind kind,
CS_Status* status) {
auto thr = m_owner.GetThread();
if (!thr) {
*status = CS_TELEMETRY_NOT_ENABLED;
return 0;
}
if (thr->m_elapsed == 0) return 0.0;
return thr->GetValue(handle, kind, status) / thr->m_elapsed;
}
void Telemetry::RecordSourceBytes(const SourceImpl& source, int quantity) {
auto thr = m_owner.GetThread();
if (!thr) return;
auto handleData = Sources::GetInstance().Find(source);
thr->m_current[std::make_pair(Handle{handleData.first, Handle::kSource},
static_cast<int>(CS_SOURCE_BYTES_RECEIVED))] +=
quantity;
}
void Telemetry::RecordSourceFrames(const SourceImpl& source, int quantity) {
auto thr = m_owner.GetThread();
if (!thr) return;
auto handleData = Sources::GetInstance().Find(source);
thr->m_current[std::make_pair(Handle{handleData.first, Handle::kSource},
static_cast<int>(CS_SOURCE_FRAMES_RECEIVED))] +=
quantity;
}

View File

@@ -0,0 +1,52 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2015-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_TELEMETRY_H_
#define CSCORE_TELEMETRY_H_
#include <support/SafeThread.h>
#include "cscore_cpp.h"
namespace cs {
class SourceImpl;
class Telemetry {
friend class TelemetryTest;
public:
static Telemetry& GetInstance() {
static Telemetry instance;
return instance;
}
~Telemetry();
void Start();
void Stop();
// User interface
void SetPeriod(double seconds);
double GetElapsedTime();
int64_t GetValue(CS_Handle handle, CS_TelemetryKind kind, CS_Status* status);
double GetAverageValue(CS_Handle handle, CS_TelemetryKind kind,
CS_Status* status);
// Telemetry events
void RecordSourceBytes(const SourceImpl& source, int quantity);
void RecordSourceFrames(const SourceImpl& source, int quantity);
private:
Telemetry();
class Thread;
wpi::SafeThreadOwner<Thread> m_owner;
};
} // namespace cs
#endif // CSCORE_TELEMETRY_H_

View File

@@ -0,0 +1,182 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_UNLIMITEDHANDLERESOURCE_H_
#define CSCORE_UNLIMITEDHANDLERESOURCE_H_
#include <memory>
#include <utility>
#include <vector>
#include <llvm/ArrayRef.h>
#include <llvm/SmallVector.h>
#include <support/mutex.h>
namespace cs {
// The UnlimitedHandleResource class is a way to track handles. This version
// allows an unlimted number of handles that are allocated sequentially. When
// possible, indices are reused to save memory usage and keep the array length
// down.
// However, automatic array management has not been implemented, but might be in
// the future.
// Because we have to loop through the allocator, we must use a global mutex.
//
// THandle needs to have the following attributes:
// Type : enum or typedef
// kIndexMax : static, constexpr, or enum value for the maximum index value
// int GetTypedIndex() const : function that returns the index of the handle
// THandle(int index, HandleType[int] type) : constructor for index and type
//
// @tparam THandle The Handle Type
// @tparam TStruct The struct type held by this resource
// @tparam typeValue The type value stored in the handle
// @tparam TMutex The mutex type to use
template <typename THandle, typename TStruct, int typeValue,
typename TMutex = wpi::mutex>
class UnlimitedHandleResource {
public:
UnlimitedHandleResource(const UnlimitedHandleResource&) = delete;
UnlimitedHandleResource operator=(const UnlimitedHandleResource&) = delete;
UnlimitedHandleResource() = default;
template <typename... Args>
THandle Allocate(Args&&... args);
THandle Allocate(std::shared_ptr<THandle> structure);
std::shared_ptr<TStruct> Get(THandle handle);
void Free(THandle handle);
template <typename T>
llvm::ArrayRef<T> GetAll(llvm::SmallVectorImpl<T>& vec);
// @param func functor with (THandle, const TStruct&) parameters
template <typename F>
void ForEach(F func);
// @pram func functor with (const TStruct&) parameter and bool return value
template <typename F>
std::pair<THandle, std::shared_ptr<TStruct>> FindIf(F func);
private:
THandle MakeHandle(size_t i) {
return THandle{static_cast<int>(i),
static_cast<typename THandle::Type>(typeValue)};
}
std::vector<std::shared_ptr<TStruct>> m_structures;
TMutex m_handleMutex;
};
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
template <typename... Args>
THandle UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Allocate(
Args&&... args) {
std::lock_guard<TMutex> sync(m_handleMutex);
size_t i;
for (i = 0; i < m_structures.size(); i++) {
if (m_structures[i] == nullptr) {
m_structures[i] = std::make_shared<TStruct>(std::forward<Args>(args)...);
return MakeHandle(i);
}
}
if (i >= THandle::kIndexMax) return 0;
m_structures.emplace_back(
std::make_shared<TStruct>(std::forward<Args>(args)...));
return MakeHandle(i);
}
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
THandle UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Allocate(
std::shared_ptr<THandle> structure) {
std::lock_guard<TMutex> sync(m_handleMutex);
size_t i;
for (i = 0; i < m_structures.size(); i++) {
if (m_structures[i] == nullptr) {
m_structures[i] = structure;
return MakeHandle(i);
}
}
if (i >= THandle::kIndexMax) return 0;
m_structures.push_back(structure);
return MakeHandle(i);
}
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
inline std::shared_ptr<TStruct>
UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Get(
THandle handle) {
auto index =
handle.GetTypedIndex(static_cast<typename THandle::Type>(typeValue));
if (index < 0) return nullptr;
std::lock_guard<TMutex> sync(m_handleMutex);
if (index >= static_cast<int>(m_structures.size())) return nullptr;
return m_structures[index];
}
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
inline void UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Free(
THandle handle) {
auto index =
handle.GetTypedIndex(static_cast<typename THandle::Type>(typeValue));
if (index < 0) return;
std::lock_guard<TMutex> sync(m_handleMutex);
if (index >= static_cast<int>(m_structures.size())) return;
m_structures[index].reset();
}
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
template <typename T>
inline llvm::ArrayRef<T>
UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::GetAll(
llvm::SmallVectorImpl<T>& vec) {
ForEach([&](THandle handle, const TStruct& data) { vec.push_back(handle); });
return vec;
}
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
template <typename F>
inline void
UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::ForEach(F func) {
std::lock_guard<TMutex> sync(m_handleMutex);
for (size_t i = 0; i < m_structures.size(); i++) {
if (m_structures[i] != nullptr) func(MakeHandle(i), *(m_structures[i]));
}
}
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
template <typename F>
inline std::pair<THandle, std::shared_ptr<TStruct>>
UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::FindIf(F func) {
std::lock_guard<TMutex> sync(m_handleMutex);
for (size_t i = 0; i < m_structures.size(); i++) {
auto& structure = m_structures[i];
if (structure != nullptr && func(*structure))
return std::make_pair(MakeHandle(i), structure);
}
return std::make_pair(0, nullptr);
}
template <typename THandle, typename TStruct, int typeValue,
typename TMutex = wpi::mutex>
class StaticUnlimitedHandleResource
: public UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex> {
public:
static StaticUnlimitedHandleResource& GetInstance() {
static StaticUnlimitedHandleResource instance;
return instance;
}
private:
StaticUnlimitedHandleResource() = default;
};
} // namespace cs
#endif // CSCORE_UNLIMITEDHANDLERESOURCE_H_

View File

@@ -0,0 +1,60 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_USBCAMERABUFFER_H_
#define CSCORE_USBCAMERABUFFER_H_
#ifdef __linux__
#include <sys/mman.h>
#endif
#include <utility>
namespace cs {
class UsbCameraBuffer {
public:
UsbCameraBuffer() noexcept : m_data{nullptr}, m_length{0} {}
UsbCameraBuffer(UsbCameraBuffer&& other) noexcept : UsbCameraBuffer() {
swap(*this, other);
}
UsbCameraBuffer& operator=(UsbCameraBuffer&& other) noexcept {
swap(*this, other);
return *this;
}
UsbCameraBuffer(const UsbCameraBuffer&) = delete;
UsbCameraBuffer& operator=(const UsbCameraBuffer&) = delete;
#ifdef __linux__
UsbCameraBuffer(int fd, size_t length, off_t offset) noexcept
: m_length{length} {
m_data =
mmap(nullptr, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset);
if (m_data == MAP_FAILED) {
m_data = nullptr;
m_length = 0;
}
}
~UsbCameraBuffer() {
if (m_data) munmap(m_data, m_length);
}
#endif
friend void swap(UsbCameraBuffer& first, UsbCameraBuffer& second) noexcept {
using std::swap;
swap(first.m_data, second.m_data);
swap(first.m_length, second.m_length);
}
void* m_data;
size_t m_length;
};
} // namespace cs
#endif // CSCORE_USBCAMERABUFFER_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,183 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_USBCAMERAIMPL_H_
#define CSCORE_USBCAMERAIMPL_H_
#ifdef __linux__
#include <linux/videodev2.h>
#endif
#include <atomic>
#include <memory>
#include <string>
#include <thread>
#include <utility>
#include <vector>
#include <llvm/STLExtras.h>
#include <llvm/SmallVector.h>
#include <llvm/raw_ostream.h>
#include <support/condition_variable.h>
#include <support/mutex.h>
#include <support/raw_istream.h>
#include "SourceImpl.h"
#include "UsbCameraBuffer.h"
#include "UsbCameraProperty.h"
namespace cs {
class UsbCameraImpl : public SourceImpl {
public:
UsbCameraImpl(llvm::StringRef name, llvm::StringRef path);
~UsbCameraImpl() override;
void Start();
// Property functions
void SetProperty(int property, int value, CS_Status* status) override;
void SetStringProperty(int property, llvm::StringRef value,
CS_Status* status) override;
// Standard common camera properties
void SetBrightness(int brightness, CS_Status* status) override;
int GetBrightness(CS_Status* status) const override;
void SetWhiteBalanceAuto(CS_Status* status) override;
void SetWhiteBalanceHoldCurrent(CS_Status* status) override;
void SetWhiteBalanceManual(int value, CS_Status* status) override;
void SetExposureAuto(CS_Status* status) override;
void SetExposureHoldCurrent(CS_Status* status) override;
void SetExposureManual(int value, CS_Status* status) override;
bool SetVideoMode(const VideoMode& mode, CS_Status* status) override;
bool SetPixelFormat(VideoMode::PixelFormat pixelFormat,
CS_Status* status) override;
bool SetResolution(int width, int height, CS_Status* status) override;
bool SetFPS(int fps, CS_Status* status) override;
void NumSinksChanged() override;
void NumSinksEnabledChanged() override;
std::string GetPath() { return m_path; }
// Messages passed to/from camera thread
struct Message {
enum Kind {
kNone = 0,
kCmdSetMode,
kCmdSetPixelFormat,
kCmdSetResolution,
kCmdSetFPS,
kCmdSetProperty,
kCmdSetPropertyStr,
kNumSinksChanged, // no response
kNumSinksEnabledChanged, // no response
// Responses
kOk,
kError
};
explicit Message(Kind kind_)
: kind(kind_), from(std::this_thread::get_id()) {}
Kind kind;
int data[4];
std::string dataStr;
std::thread::id from;
};
protected:
std::unique_ptr<PropertyImpl> CreateEmptyProperty(
llvm::StringRef name) const override;
// Cache properties. Immediately successful if properties are already cached.
// If they are not, tries to connect to the camera to do so; returns false and
// sets status to CS_SOURCE_IS_DISCONNECTED if that too fails.
bool CacheProperties(CS_Status* status) const override;
private:
// Send a message to the camera thread and wait for a response (generic)
CS_StatusValue SendAndWait(Message&& msg) const;
// Send a message to the camera thread with no response
void Send(Message&& msg) const;
// The camera processing thread
void CameraThreadMain();
// Functions used by CameraThreadMain()
void DeviceDisconnect();
void DeviceConnect();
bool DeviceStreamOn();
bool DeviceStreamOff();
void DeviceProcessCommands();
void DeviceSetMode();
void DeviceSetFPS();
void DeviceCacheMode();
void DeviceCacheProperty(std::unique_ptr<UsbCameraProperty> rawProp);
void DeviceCacheProperties();
void DeviceCacheVideoModes();
// Command helper functions
CS_StatusValue DeviceProcessCommand(std::unique_lock<wpi::mutex>& lock,
const Message& msg);
CS_StatusValue DeviceCmdSetMode(std::unique_lock<wpi::mutex>& lock,
const Message& msg);
CS_StatusValue DeviceCmdSetProperty(std::unique_lock<wpi::mutex>& lock,
const Message& msg);
// Property helper functions
int RawToPercentage(const UsbCameraProperty& rawProp, int rawValue);
int PercentageToRaw(const UsbCameraProperty& rawProp, int percentValue);
void SetQuirks();
//
// Variables only used within camera thread
//
bool m_streaming;
bool m_modeSetPixelFormat{false};
bool m_modeSetResolution{false};
bool m_modeSetFPS{false};
#ifdef __linux__
unsigned m_capabilities = 0;
#endif
// Number of buffers to ask OS for
static constexpr int kNumBuffers = 4;
#ifdef __linux__
std::array<UsbCameraBuffer, kNumBuffers> m_buffers;
#endif
//
// Path never changes, so not protected by mutex.
//
std::string m_path;
#ifdef __linux__
std::atomic_int m_fd;
std::atomic_int m_command_fd; // for command eventfd
#endif
std::atomic_bool m_active; // set to false to terminate thread
std::thread m_cameraThread;
// Quirks
bool m_lifecam_exposure{false}; // Microsoft LifeCam exposure
//
// Variables protected by m_mutex
//
// Message queues
mutable std::vector<Message> m_commands;
mutable std::vector<std::pair<std::thread::id, CS_StatusValue>> m_responses;
mutable wpi::condition_variable m_responseCv;
};
} // namespace cs
#endif // CSCORE_USBCAMERAIMPL_H_

View File

@@ -0,0 +1,320 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "UsbCameraProperty.h"
#include <llvm/STLExtras.h>
#include <llvm/SmallString.h>
#include "UsbUtil.h"
using namespace cs;
#ifdef __linux__
static int GetIntCtrlIoctl(int fd, unsigned id, int type, int64_t* value) {
unsigned ctrl_class = V4L2_CTRL_ID2CLASS(id);
if (type == V4L2_CTRL_TYPE_INTEGER64 || V4L2_CTRL_DRIVER_PRIV(id) ||
(ctrl_class != V4L2_CTRL_CLASS_USER &&
ctrl_class != V4L2_CID_PRIVATE_BASE)) {
// Use extended control
struct v4l2_ext_control ctrl;
struct v4l2_ext_controls ctrls;
std::memset(&ctrl, 0, sizeof(ctrl));
std::memset(&ctrls, 0, sizeof(ctrls));
ctrl.id = id;
ctrls.ctrl_class = ctrl_class;
ctrls.count = 1;
ctrls.controls = &ctrl;
int rc = DoIoctl(fd, VIDIOC_G_EXT_CTRLS, &ctrls);
if (rc < 0) return rc;
*value = ctrl.value;
} else {
// Use normal control
struct v4l2_control ctrl;
std::memset(&ctrl, 0, sizeof(ctrl));
ctrl.id = id;
int rc = DoIoctl(fd, VIDIOC_G_CTRL, &ctrl);
if (rc < 0) return rc;
*value = ctrl.value;
}
return 0;
}
static int SetIntCtrlIoctl(int fd, unsigned id, int type, int64_t value) {
unsigned ctrl_class = V4L2_CTRL_ID2CLASS(id);
if (type == V4L2_CTRL_TYPE_INTEGER64 || V4L2_CTRL_DRIVER_PRIV(id) ||
(ctrl_class != V4L2_CTRL_CLASS_USER &&
ctrl_class != V4L2_CID_PRIVATE_BASE)) {
// Use extended control
struct v4l2_ext_control ctrl;
struct v4l2_ext_controls ctrls;
std::memset(&ctrl, 0, sizeof(ctrl));
std::memset(&ctrls, 0, sizeof(ctrls));
ctrl.id = id;
if (type == V4L2_CTRL_TYPE_INTEGER64)
ctrl.value64 = value;
else
ctrl.value = static_cast<__s32>(value);
ctrls.ctrl_class = ctrl_class;
ctrls.count = 1;
ctrls.controls = &ctrl;
return DoIoctl(fd, VIDIOC_S_EXT_CTRLS, &ctrls);
} else {
// Use normal control
struct v4l2_control ctrl;
ctrl.id = id;
ctrl.value = static_cast<__s32>(value);
return DoIoctl(fd, VIDIOC_S_CTRL, &ctrl);
}
}
static int GetStringCtrlIoctl(int fd, int id, int maximum, std::string* value) {
struct v4l2_ext_control ctrl;
struct v4l2_ext_controls ctrls;
std::memset(&ctrl, 0, sizeof(ctrl));
std::memset(&ctrls, 0, sizeof(ctrls));
ctrl.id = id;
ctrls.ctrl_class = V4L2_CTRL_ID2CLASS(id);
ctrls.count = 1;
ctrls.controls = &ctrl;
int rc = DoIoctl(fd, VIDIOC_G_EXT_CTRLS, &ctrls);
if (rc < 0) {
value->clear();
return rc;
}
value->assign(ctrl.string, std::strlen(ctrl.string));
return 0;
}
static int SetStringCtrlIoctl(int fd, int id, int maximum,
llvm::StringRef value) {
llvm::SmallString<64> str{
value.substr(0, std::min(value.size(), static_cast<size_t>(maximum)))};
struct v4l2_ext_control ctrl;
struct v4l2_ext_controls ctrls;
std::memset(&ctrl, 0, sizeof(ctrl));
std::memset(&ctrls, 0, sizeof(ctrls));
ctrl.id = id;
ctrl.size = str.size();
ctrl.string = const_cast<char*>(str.c_str());
ctrls.ctrl_class = V4L2_CTRL_ID2CLASS(id);
ctrls.count = 1;
ctrls.controls = &ctrl;
return DoIoctl(fd, VIDIOC_S_EXT_CTRLS, &ctrls);
}
// Removes non-alphanumeric characters and replaces spaces with underscores.
// e.g. "Zoom, Absolute" -> "zoom_absolute", "Pan (Absolute)" -> "pan_absolute"
static llvm::StringRef NormalizeName(llvm::StringRef name,
llvm::SmallVectorImpl<char>& buf) {
bool newWord = false;
for (auto ch : name) {
if (std::isalnum(ch)) {
if (newWord) buf.push_back('_');
newWord = false;
buf.push_back(std::tolower(ch));
} else if (!buf.empty()) {
newWord = true;
}
}
return llvm::StringRef(buf.data(), buf.size());
}
#ifdef VIDIOC_QUERY_EXT_CTRL
UsbCameraProperty::UsbCameraProperty(const struct v4l2_query_ext_ctrl& ctrl)
: PropertyImpl(llvm::StringRef{}, CS_PROP_NONE, ctrl.step,
ctrl.default_value, 0),
id(ctrl.id & V4L2_CTRL_ID_MASK),
type(ctrl.type) {
hasMinimum = true;
minimum = ctrl.minimum;
hasMaximum = true;
maximum = ctrl.maximum;
// propKind
switch (ctrl.type) {
case V4L2_CTRL_TYPE_INTEGER:
case V4L2_CTRL_TYPE_INTEGER64:
propKind = CS_PROP_INTEGER;
break;
case V4L2_CTRL_TYPE_BOOLEAN:
propKind = CS_PROP_BOOLEAN;
break;
case V4L2_CTRL_TYPE_INTEGER_MENU:
case V4L2_CTRL_TYPE_MENU:
propKind = CS_PROP_ENUM;
break;
case V4L2_CTRL_TYPE_STRING:
propKind = CS_PROP_STRING;
break;
default:
return; // others unsupported
}
// name
size_t len = 0;
while (len < sizeof(ctrl.name) && ctrl.name[len] != '\0') ++len;
llvm::SmallString<64> name_buf;
name = NormalizeName(llvm::StringRef(ctrl.name, len), name_buf);
}
#endif
UsbCameraProperty::UsbCameraProperty(const struct v4l2_queryctrl& ctrl)
: PropertyImpl(llvm::StringRef{}, CS_PROP_NONE, ctrl.step,
ctrl.default_value, 0),
id(ctrl.id & V4L2_CTRL_ID_MASK),
type(ctrl.type) {
hasMinimum = true;
minimum = ctrl.minimum;
hasMaximum = true;
maximum = ctrl.maximum;
// propKind
switch (ctrl.type) {
case V4L2_CTRL_TYPE_INTEGER:
case V4L2_CTRL_TYPE_INTEGER64:
propKind = CS_PROP_INTEGER;
break;
case V4L2_CTRL_TYPE_BOOLEAN:
propKind = CS_PROP_BOOLEAN;
break;
case V4L2_CTRL_TYPE_INTEGER_MENU:
case V4L2_CTRL_TYPE_MENU:
propKind = CS_PROP_ENUM;
break;
case V4L2_CTRL_TYPE_STRING:
propKind = CS_PROP_STRING;
break;
default:
return; // others unsupported
}
// name
size_t len = 0;
while (len < sizeof(ctrl.name) && ctrl.name[len] != '\0') ++len;
llvm::SmallString<64> name_buf;
name = NormalizeName(
llvm::StringRef(reinterpret_cast<const char*>(ctrl.name), len), name_buf);
}
std::unique_ptr<UsbCameraProperty> UsbCameraProperty::DeviceQuery(int fd,
__u32* id) {
int rc;
std::unique_ptr<UsbCameraProperty> prop;
#ifdef VIDIOC_QUERY_EXT_CTRL
v4l2_query_ext_ctrl qc_ext;
std::memset(&qc_ext, 0, sizeof(qc_ext));
qc_ext.id = *id;
rc = TryIoctl(fd, VIDIOC_QUERY_EXT_CTRL, &qc_ext);
if (rc == 0) {
*id = qc_ext.id; // copy back
// We don't support array types
if (qc_ext.elems > 1 || qc_ext.nr_of_dims > 0) return nullptr;
prop = llvm::make_unique<UsbCameraProperty>(qc_ext);
}
#endif
if (!prop) {
// Fall back to normal QUERYCTRL
struct v4l2_queryctrl qc;
std::memset(&qc, 0, sizeof(qc));
qc.id = *id;
rc = TryIoctl(fd, VIDIOC_QUERYCTRL, &qc);
*id = qc.id; // copy back
if (rc != 0) return nullptr;
prop = llvm::make_unique<UsbCameraProperty>(qc);
}
// Cache enum property choices
if (prop->propKind == CS_PROP_ENUM) {
prop->enumChoices.resize(prop->maximum + 1);
v4l2_querymenu qmenu;
std::memset(&qmenu, 0, sizeof(qmenu));
qmenu.id = *id;
for (int i = prop->minimum; i <= prop->maximum; ++i) {
qmenu.index = static_cast<__u32>(i);
if (TryIoctl(fd, VIDIOC_QUERYMENU, &qmenu) != 0) continue;
prop->enumChoices[i] = reinterpret_cast<const char*>(qmenu.name);
}
}
return prop;
}
bool UsbCameraProperty::DeviceGet(std::unique_lock<wpi::mutex>& lock, int fd) {
if (fd < 0) return true;
unsigned idCopy = id;
int rv = 0;
switch (propKind) {
case CS_PROP_BOOLEAN:
case CS_PROP_INTEGER:
case CS_PROP_ENUM: {
int typeCopy = type;
int64_t newValue = 0;
lock.unlock();
rv = GetIntCtrlIoctl(fd, idCopy, typeCopy, &newValue);
lock.lock();
if (rv >= 0) value = newValue;
break;
}
case CS_PROP_STRING: {
int maximumCopy = maximum;
std::string newValueStr;
lock.unlock();
rv = GetStringCtrlIoctl(fd, idCopy, maximumCopy, &newValueStr);
lock.lock();
if (rv >= 0) valueStr = std::move(newValueStr);
break;
}
default:
break;
}
return rv >= 0;
}
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
int fd) const {
// Make a copy of the string as we're about to release the lock
llvm::SmallString<128> valueStrCopy{valueStr};
return DeviceSet(lock, fd, value, valueStrCopy);
}
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock, int fd,
int newValue,
llvm::StringRef newValueStr) const {
if (fd < 0) return true;
unsigned idCopy = id;
int rv = 0;
switch (propKind) {
case CS_PROP_BOOLEAN:
case CS_PROP_INTEGER:
case CS_PROP_ENUM: {
int typeCopy = type;
lock.unlock();
rv = SetIntCtrlIoctl(fd, idCopy, typeCopy, newValue);
lock.lock();
break;
}
case CS_PROP_STRING: {
int maximumCopy = maximum;
lock.unlock();
rv = SetStringCtrlIoctl(fd, idCopy, maximumCopy, newValueStr);
lock.lock();
break;
}
default:
break;
}
return rv >= 0;
}
#endif // __linux__

View File

@@ -0,0 +1,70 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_USBCAMERAPROPERTY_H_
#define CSCORE_USBCAMERAPROPERTY_H_
#ifdef __linux__
#include <linux/videodev2.h>
#endif
#include <memory>
#include <support/mutex.h>
#include "PropertyImpl.h"
namespace cs {
// Property data
class UsbCameraProperty : public PropertyImpl {
public:
UsbCameraProperty() = default;
explicit UsbCameraProperty(llvm::StringRef name_) : PropertyImpl{name_} {}
// Normalized property constructor
UsbCameraProperty(llvm::StringRef name_, int rawIndex_,
const UsbCameraProperty& rawProp, int defaultValue_,
int value_)
: PropertyImpl(name_, rawProp.propKind, 1, defaultValue_, value_),
percentage{true},
propPair{rawIndex_},
id{rawProp.id},
type{rawProp.type} {
hasMinimum = true;
minimum = 0;
hasMaximum = true;
maximum = 100;
}
#ifdef __linux__
#ifdef VIDIOC_QUERY_EXT_CTRL
explicit UsbCameraProperty(const struct v4l2_query_ext_ctrl& ctrl);
#endif
explicit UsbCameraProperty(const struct v4l2_queryctrl& ctrl);
static std::unique_ptr<UsbCameraProperty> DeviceQuery(int fd, __u32* id);
bool DeviceGet(std::unique_lock<wpi::mutex>& lock, int fd);
bool DeviceSet(std::unique_lock<wpi::mutex>& lock, int fd) const;
bool DeviceSet(std::unique_lock<wpi::mutex>& lock, int fd, int newValue,
llvm::StringRef newValueStr) const;
#endif
// If this is a percentage (rather than raw) property
bool percentage{false};
// If not 0, index of corresponding raw/percentage property
int propPair{0};
unsigned id{0}; // implementation-level id
int type{0}; // implementation type, not CS_PropertyKind!
};
} // namespace cs
#endif // CSCORE_USBCAMERAPROPERTY_H_

View File

@@ -0,0 +1,166 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "UsbUtil.h"
#include <fcntl.h>
#ifdef __linux__
#include <libgen.h>
#include <sys/ioctl.h>
#endif
#include <llvm/Format.h>
#include <llvm/SmallString.h>
#include <llvm/raw_ostream.h>
#include <support/raw_istream.h>
#include "Log.h"
namespace cs {
#ifdef __linux__
static llvm::StringRef GetUsbNameFromFile(int vendor, int product,
llvm::SmallVectorImpl<char>& buf) {
int fd = open("/var/lib/usbutils/usb.ids", O_RDONLY);
if (fd < 0) return llvm::StringRef{};
llvm::raw_svector_ostream os{buf};
wpi::raw_fd_istream is{fd, true};
// build vendor and product 4-char hex strings
llvm::SmallString<16> vendorStr, productStr;
llvm::raw_svector_ostream vendorOs{vendorStr}, productOs{productStr};
vendorOs << llvm::format_hex_no_prefix(vendor, 4);
productOs << llvm::format_hex_no_prefix(product, 4);
// scan file
llvm::SmallString<128> lineBuf;
bool foundVendor = false;
for (;;) {
auto line = is.getline(lineBuf, 4096);
if (is.has_error()) break;
if (line.empty()) continue;
// look for vendor at start of line
if (line.startswith(vendorStr)) {
foundVendor = true;
os << line.substr(5).trim() << ' ';
continue;
}
if (foundVendor) {
// next vendor, but didn't match product?
if (line[0] != '\t') {
os << "Unknown";
return os.str();
}
// look for product
if (line.substr(1).startswith(productStr)) {
os << line.substr(6).trim();
return os.str();
}
}
}
return llvm::StringRef{};
}
llvm::StringRef GetUsbNameFromId(int vendor, int product,
llvm::SmallVectorImpl<char>& buf) {
// try reading usb.ids
llvm::StringRef rv = GetUsbNameFromFile(vendor, product, buf);
if (!rv.empty()) return rv;
// Fall back to internal database
llvm::raw_svector_ostream os{buf};
switch (vendor) {
case 0x046d:
os << "Logitech, Inc. ";
switch (product) {
case 0x0802:
os << "Webcam C200";
break;
case 0x0804:
os << "Webcam C250";
break;
case 0x0805:
os << "Webcam C300";
break;
case 0x0807:
os << "Webcam B500";
break;
case 0x0808:
os << "Webcam C600";
break;
case 0x0809:
os << "Webcam Pro 9000";
break;
case 0x080a:
os << "Portable Webcam C905";
break;
case 0x080f:
os << "Webcam C120";
break;
case 0x0819:
os << "Webcam C210";
break;
case 0x081b:
os << "Webcam C310";
break;
case 0x081d:
os << "HD Webcam C510";
break;
case 0x0821:
os << "HD Webcam C910";
break;
case 0x0825:
os << "Webcam C270";
break;
case 0x0826:
os << "HD Webcam C525";
break;
case 0x0828:
os << "HD Webcam B990";
break;
case 0x082b:
os << "Webcam C170";
break;
case 0x082d:
os << "HD Pro Webcam C920";
break;
case 0x0836:
os << "B525 HD Webcam";
break;
case 0x0843:
os << "Webcam C930e";
break;
}
break;
}
return os.str();
}
int CheckedIoctl(int fd, unsigned long req, void* data, // NOLINT(runtime/int)
const char* name, const char* file, int line, bool quiet) {
int retval = ioctl(fd, req, data);
if (!quiet && retval < 0) {
llvm::SmallString<64> localfile{file};
localfile.push_back('\0');
ERROR("ioctl " << name << " failed at " << basename(localfile.data()) << ":"
<< line << ": " << std::strerror(errno));
}
return retval;
}
#endif // __linux__
} // namespace cs

View File

@@ -0,0 +1,35 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_USBUTIL_H_
#define CSCORE_USBUTIL_H_
#include <stdint.h>
#include <llvm/SmallVector.h>
#include <llvm/StringRef.h>
namespace cs {
#ifdef __linux__
llvm::StringRef GetUsbNameFromId(int vendor, int product,
llvm::SmallVectorImpl<char>& buf);
int CheckedIoctl(int fd, unsigned long req, void* data, // NOLINT(runtime/int)
const char* name, const char* file, int line, bool quiet);
#define DoIoctl(fd, req, data) \
CheckedIoctl(fd, req, data, #req, __FILE__, __LINE__, false)
#define TryIoctl(fd, req, data) \
CheckedIoctl(fd, req, data, #req, __FILE__, __LINE__, true)
#endif // __linux__
} // namespace cs
#endif // CSCORE_USBUTIL_H_

View File

@@ -0,0 +1,27 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_C_UTIL_H_
#define CSCORE_C_UTIL_H_
#include <cstdlib>
#include <cstring>
#include <llvm/StringRef.h>
namespace cs {
inline char* ConvertToC(llvm::StringRef in) {
char* out = static_cast<char*>(std::malloc(in.size() + 1));
std::memmove(out, in.data(), in.size());
out[in.size()] = '\0';
return out;
}
} // namespace cs
#endif // CSCORE_C_UTIL_H_

View File

@@ -0,0 +1,395 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "cscore_c.h"
#include <cstddef>
#include <cstdlib>
#include <llvm/SmallString.h>
#include <opencv2/core/core.hpp>
#include "c_util.h"
#include "cscore_cpp.h"
extern "C" {
CS_PropertyKind CS_GetPropertyKind(CS_Property property, CS_Status* status) {
return cs::GetPropertyKind(property, status);
}
char* CS_GetPropertyName(CS_Property property, CS_Status* status) {
llvm::SmallString<128> buf;
auto str = cs::GetPropertyName(property, buf, status);
if (*status != 0) return nullptr;
return cs::ConvertToC(str);
}
int CS_GetProperty(CS_Property property, CS_Status* status) {
return cs::GetProperty(property, status);
}
void CS_SetProperty(CS_Property property, int value, CS_Status* status) {
return cs::SetProperty(property, value, status);
}
int CS_GetPropertyMin(CS_Property property, CS_Status* status) {
return cs::GetPropertyMin(property, status);
}
int CS_GetPropertyMax(CS_Property property, CS_Status* status) {
return cs::GetPropertyMax(property, status);
}
int CS_GetPropertyStep(CS_Property property, CS_Status* status) {
return cs::GetPropertyStep(property, status);
}
int CS_GetPropertyDefault(CS_Property property, CS_Status* status) {
return cs::GetPropertyDefault(property, status);
}
char* CS_GetStringProperty(CS_Property property, CS_Status* status) {
llvm::SmallString<128> buf;
auto str = cs::GetStringProperty(property, buf, status);
if (*status != 0) return nullptr;
return cs::ConvertToC(str);
}
void CS_SetStringProperty(CS_Property property, const char* value,
CS_Status* status) {
return cs::SetStringProperty(property, value, status);
}
char** CS_GetEnumPropertyChoices(CS_Property property, int* count,
CS_Status* status) {
auto choices = cs::GetEnumPropertyChoices(property, status);
char** out = static_cast<char**>(std::malloc(choices.size() * sizeof(char*)));
*count = choices.size();
for (size_t i = 0; i < choices.size(); ++i)
out[i] = cs::ConvertToC(choices[i]);
return out;
}
CS_SourceKind CS_GetSourceKind(CS_Source source, CS_Status* status) {
return cs::GetSourceKind(source, status);
}
char* CS_GetSourceName(CS_Source source, CS_Status* status) {
llvm::SmallString<128> buf;
auto str = cs::GetSourceName(source, buf, status);
if (*status != 0) return nullptr;
return cs::ConvertToC(str);
}
char* CS_GetSourceDescription(CS_Source source, CS_Status* status) {
llvm::SmallString<128> buf;
auto str = cs::GetSourceDescription(source, buf, status);
if (*status != 0) return nullptr;
return cs::ConvertToC(str);
}
uint64_t CS_GetSourceLastFrameTime(CS_Source source, CS_Status* status) {
return cs::GetSourceLastFrameTime(source, status);
}
CS_Bool CS_IsSourceConnected(CS_Source source, CS_Status* status) {
return cs::IsSourceConnected(source, status);
}
CS_Property CS_GetSourceProperty(CS_Source source, const char* name,
CS_Status* status) {
return cs::GetSourceProperty(source, name, status);
}
CS_Property* CS_EnumerateSourceProperties(CS_Source source, int* count,
CS_Status* status) {
llvm::SmallVector<CS_Property, 32> buf;
auto vec = cs::EnumerateSourceProperties(source, buf, status);
CS_Property* out =
static_cast<CS_Property*>(std::malloc(vec.size() * sizeof(CS_Property)));
*count = vec.size();
std::copy(vec.begin(), vec.end(), out);
return out;
}
void CS_GetSourceVideoMode(CS_Source source, CS_VideoMode* mode,
CS_Status* status) {
*mode = cs::GetSourceVideoMode(source, status);
}
CS_Bool CS_SetSourceVideoMode(CS_Source source, const CS_VideoMode* mode,
CS_Status* status) {
return cs::SetSourceVideoMode(
source, static_cast<const cs::VideoMode&>(*mode), status);
}
CS_Bool CS_SetSourceVideoModeDiscrete(CS_Source source,
enum CS_PixelFormat pixelFormat,
int width, int height, int fps,
CS_Status* status) {
return cs::SetSourceVideoMode(
source,
cs::VideoMode{static_cast<cs::VideoMode::PixelFormat>(
static_cast<int>(pixelFormat)),
width, height, fps},
status);
}
CS_Bool CS_SetSourcePixelFormat(CS_Source source,
enum CS_PixelFormat pixelFormat,
CS_Status* status) {
return cs::SetSourcePixelFormat(
source,
static_cast<cs::VideoMode::PixelFormat>(static_cast<int>(pixelFormat)),
status);
}
CS_Bool CS_SetSourceResolution(CS_Source source, int width, int height,
CS_Status* status) {
return cs::SetSourceResolution(source, width, height, status);
}
CS_Bool CS_SetSourceFPS(CS_Source source, int fps, CS_Status* status) {
return cs::SetSourceFPS(source, fps, status);
}
CS_VideoMode* CS_EnumerateSourceVideoModes(CS_Source source, int* count,
CS_Status* status) {
auto vec = cs::EnumerateSourceVideoModes(source, status);
CS_VideoMode* out = static_cast<CS_VideoMode*>(
std::malloc(vec.size() * sizeof(CS_VideoMode)));
*count = vec.size();
std::copy(vec.begin(), vec.end(), out);
return out;
}
CS_Sink* CS_EnumerateSourceSinks(CS_Source source, int* count,
CS_Status* status) {
llvm::SmallVector<CS_Sink, 32> buf;
auto handles = cs::EnumerateSourceSinks(source, buf, status);
CS_Sink* sinks =
static_cast<CS_Sink*>(std::malloc(handles.size() * sizeof(CS_Sink)));
*count = handles.size();
std::copy(handles.begin(), handles.end(), sinks);
return sinks;
}
CS_Source CS_CopySource(CS_Source source, CS_Status* status) {
return cs::CopySource(source, status);
}
void CS_ReleaseSource(CS_Source source, CS_Status* status) {
return cs::ReleaseSource(source, status);
}
void CS_SetCameraBrightness(CS_Source source, int brightness,
CS_Status* status) {
return cs::SetCameraBrightness(source, brightness, status);
}
int CS_GetCameraBrightness(CS_Source source, CS_Status* status) {
return cs::GetCameraBrightness(source, status);
}
void CS_SetCameraWhiteBalanceAuto(CS_Source source, CS_Status* status) {
return cs::SetCameraWhiteBalanceAuto(source, status);
}
void CS_SetCameraWhiteBalanceHoldCurrent(CS_Source source, CS_Status* status) {
return cs::SetCameraWhiteBalanceHoldCurrent(source, status);
}
void CS_SetCameraWhiteBalanceManual(CS_Source source, int value,
CS_Status* status) {
return cs::SetCameraWhiteBalanceManual(source, value, status);
}
void CS_SetCameraExposureAuto(CS_Source source, CS_Status* status) {
return cs::SetCameraExposureAuto(source, status);
}
void CS_SetCameraExposureHoldCurrent(CS_Source source, CS_Status* status) {
return cs::SetCameraExposureHoldCurrent(source, status);
}
void CS_SetCameraExposureManual(CS_Source source, int value,
CS_Status* status) {
return cs::SetCameraExposureManual(source, value, status);
}
CS_SinkKind CS_GetSinkKind(CS_Sink sink, CS_Status* status) {
return cs::GetSinkKind(sink, status);
}
char* CS_GetSinkName(CS_Sink sink, CS_Status* status) {
llvm::SmallString<128> buf;
auto str = cs::GetSinkName(sink, buf, status);
if (*status != 0) return nullptr;
return cs::ConvertToC(str);
}
char* CS_GetSinkDescription(CS_Sink sink, CS_Status* status) {
llvm::SmallString<128> buf;
auto str = cs::GetSinkDescription(sink, buf, status);
if (*status != 0) return nullptr;
return cs::ConvertToC(str);
}
void CS_SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) {
return cs::SetSinkSource(sink, source, status);
}
CS_Source CS_GetSinkSource(CS_Sink sink, CS_Status* status) {
return cs::GetSinkSource(sink, status);
}
CS_Property CS_GetSinkSourceProperty(CS_Sink sink, const char* name,
CS_Status* status) {
return cs::GetSinkSourceProperty(sink, name, status);
}
CS_Sink CS_CopySink(CS_Sink sink, CS_Status* status) {
return cs::CopySink(sink, status);
}
void CS_ReleaseSink(CS_Sink sink, CS_Status* status) {
return cs::ReleaseSink(sink, status);
}
void CS_SetListenerOnStart(void (*onStart)(void* data), void* data) {
cs::SetListenerOnStart([=]() { onStart(data); });
}
void CS_SetListenerOnExit(void (*onExit)(void* data), void* data) {
cs::SetListenerOnExit([=]() { onExit(data); });
}
CS_Listener CS_AddListener(void* data,
void (*callback)(void* data, const CS_Event* event),
int eventMask, int immediateNotify,
CS_Status* status) {
return cs::AddListener(
[=](const cs::RawEvent& rawEvent) {
CS_Event event;
event.kind = static_cast<CS_EventKind>(static_cast<int>(rawEvent.kind));
event.source = rawEvent.sourceHandle;
event.sink = rawEvent.sinkHandle;
event.name = rawEvent.name.c_str();
event.mode = rawEvent.mode;
event.property = rawEvent.propertyHandle;
event.propertyKind = rawEvent.propertyKind;
event.value = rawEvent.value;
event.valueStr = rawEvent.valueStr.c_str();
callback(data, &event);
},
eventMask, immediateNotify, status);
}
void CS_RemoveListener(CS_Listener handle, CS_Status* status) {
return cs::RemoveListener(handle, status);
}
int CS_NotifierDestroyed(void) { return cs::NotifierDestroyed(); }
void CS_SetTelemetryPeriod(double seconds) { cs::SetTelemetryPeriod(seconds); }
double CS_GetTelemetryElapsedTime(void) {
return cs::GetTelemetryElapsedTime();
}
int64_t CS_GetTelemetryValue(CS_Handle handle, CS_TelemetryKind kind,
CS_Status* status) {
return cs::GetTelemetryValue(handle, kind, status);
}
double CS_GetTelemetryAverageValue(CS_Handle handle, CS_TelemetryKind kind,
CS_Status* status) {
return cs::GetTelemetryAverageValue(handle, kind, status);
}
void CS_SetLogger(CS_LogFunc func, unsigned int min_level) {
cs::SetLogger(func, min_level);
}
void CS_SetDefaultLogger(unsigned int min_level) {
cs::SetDefaultLogger(min_level);
}
CS_Source* CS_EnumerateSources(int* count, CS_Status* status) {
llvm::SmallVector<CS_Source, 32> buf;
auto handles = cs::EnumerateSourceHandles(buf, status);
CS_Source* sources =
static_cast<CS_Source*>(std::malloc(handles.size() * sizeof(CS_Source)));
*count = handles.size();
std::copy(handles.begin(), handles.end(), sources);
return sources;
}
void CS_ReleaseEnumeratedSources(CS_Source* sources, int count) {
if (!sources) return;
for (int i = 0; i < count; ++i) {
CS_Status status = 0;
if (sources[i] != 0) cs::ReleaseSource(sources[i], &status);
}
std::free(sources);
}
CS_Sink* CS_EnumerateSinks(int* count, CS_Status* status) {
llvm::SmallVector<CS_Sink, 32> buf;
auto handles = cs::EnumerateSinkHandles(buf, status);
CS_Sink* sinks =
static_cast<CS_Sink*>(std::malloc(handles.size() * sizeof(CS_Sink)));
*count = handles.size();
std::copy(handles.begin(), handles.end(), sinks);
return sinks;
}
void CS_ReleaseEnumeratedSinks(CS_Sink* sinks, int count) {
if (!sinks) return;
for (int i = 0; i < count; ++i) {
CS_Status status = 0;
if (sinks[i] != 0) cs::ReleaseSink(sinks[i], &status);
}
std::free(sinks);
}
void CS_FreeString(char* str) { std::free(str); }
void CS_FreeEnumPropertyChoices(char** choices, int count) {
if (!choices) return;
for (int i = 0; i < count; ++i) std::free(choices[i]);
std::free(choices);
}
void CS_FreeEnumeratedProperties(CS_Property* properties, int count) {
std::free(properties);
}
void CS_FreeEnumeratedVideoModes(CS_VideoMode* modes, int count) {
std::free(modes);
}
char* CS_GetHostname() { return cs::ConvertToC(cs::GetHostname()); }
char** CS_GetNetworkInterfaces(int* count) {
auto interfaces = cs::GetNetworkInterfaces();
char** out =
static_cast<char**>(std::malloc(interfaces.size() * sizeof(char*)));
*count = interfaces.size();
for (size_t i = 0; i < interfaces.size(); ++i)
out[i] = cs::ConvertToC(interfaces[i]);
return out;
}
void CS_FreeNetworkInterfaces(char** interfaces, int count) {
if (!interfaces) return;
for (int i = 0; i < count; ++i) std::free(interfaces[i]);
std::free(interfaces);
}
} // extern "C"

View File

@@ -0,0 +1,673 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "cscore_cpp.h"
#if defined(__linux__)
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#include <llvm/SmallString.h>
#include "Handle.h"
#include "Log.h"
#include "NetworkListener.h"
#include "Notifier.h"
#include "SinkImpl.h"
#include "SourceImpl.h"
#include "Telemetry.h"
using namespace cs;
static std::shared_ptr<SourceImpl> GetPropertySource(CS_Property propertyHandle,
int* propertyIndex,
CS_Status* status) {
Handle handle{propertyHandle};
int i = handle.GetParentIndex();
if (i < 0) {
*status = CS_INVALID_HANDLE;
return nullptr;
}
auto data = Sources::GetInstance().Get(Handle{i, Handle::kSource});
if (!data) {
*status = CS_INVALID_HANDLE;
return nullptr;
}
*propertyIndex = handle.GetIndex();
return data->source;
}
namespace cs {
//
// Property Functions
//
CS_PropertyKind GetPropertyKind(CS_Property property, CS_Status* status) {
int propertyIndex;
auto source = GetPropertySource(property, &propertyIndex, status);
if (!source) return CS_PROP_NONE;
return source->GetPropertyKind(propertyIndex);
}
std::string GetPropertyName(CS_Property property, CS_Status* status) {
llvm::SmallString<128> buf;
int propertyIndex;
auto source = GetPropertySource(property, &propertyIndex, status);
if (!source) return std::string{};
return source->GetPropertyName(propertyIndex, buf, status);
}
llvm::StringRef GetPropertyName(CS_Property property,
llvm::SmallVectorImpl<char>& buf,
CS_Status* status) {
int propertyIndex;
auto source = GetPropertySource(property, &propertyIndex, status);
if (!source) return llvm::StringRef{};
return source->GetPropertyName(propertyIndex, buf, status);
}
int GetProperty(CS_Property property, CS_Status* status) {
int propertyIndex;
auto source = GetPropertySource(property, &propertyIndex, status);
if (!source) return false;
return source->GetProperty(propertyIndex, status);
}
void SetProperty(CS_Property property, int value, CS_Status* status) {
int propertyIndex;
auto source = GetPropertySource(property, &propertyIndex, status);
if (!source) return;
source->SetProperty(propertyIndex, value, status);
}
int GetPropertyMin(CS_Property property, CS_Status* status) {
int propertyIndex;
auto source = GetPropertySource(property, &propertyIndex, status);
if (!source) return 0.0;
return source->GetPropertyMin(propertyIndex, status);
}
int GetPropertyMax(CS_Property property, CS_Status* status) {
int propertyIndex;
auto source = GetPropertySource(property, &propertyIndex, status);
if (!source) return 0.0;
return source->GetPropertyMax(propertyIndex, status);
}
int GetPropertyStep(CS_Property property, CS_Status* status) {
int propertyIndex;
auto source = GetPropertySource(property, &propertyIndex, status);
if (!source) return 0.0;
return source->GetPropertyStep(propertyIndex, status);
}
int GetPropertyDefault(CS_Property property, CS_Status* status) {
int propertyIndex;
auto source = GetPropertySource(property, &propertyIndex, status);
if (!source) return 0.0;
return source->GetPropertyDefault(propertyIndex, status);
}
std::string GetStringProperty(CS_Property property, CS_Status* status) {
llvm::SmallString<128> buf;
int propertyIndex;
auto source = GetPropertySource(property, &propertyIndex, status);
if (!source) return std::string{};
return source->GetStringProperty(propertyIndex, buf, status);
}
llvm::StringRef GetStringProperty(CS_Property property,
llvm::SmallVectorImpl<char>& buf,
CS_Status* status) {
int propertyIndex;
auto source = GetPropertySource(property, &propertyIndex, status);
if (!source) return llvm::StringRef{};
return source->GetStringProperty(propertyIndex, buf, status);
}
void SetStringProperty(CS_Property property, llvm::StringRef value,
CS_Status* status) {
int propertyIndex;
auto source = GetPropertySource(property, &propertyIndex, status);
if (!source) return;
source->SetStringProperty(propertyIndex, value, status);
}
std::vector<std::string> GetEnumPropertyChoices(CS_Property property,
CS_Status* status) {
int propertyIndex;
auto source = GetPropertySource(property, &propertyIndex, status);
if (!source) return std::vector<std::string>{};
return source->GetEnumPropertyChoices(propertyIndex, status);
}
//
// Source Functions
//
CS_SourceKind GetSourceKind(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return CS_SOURCE_UNKNOWN;
}
return data->kind;
}
std::string GetSourceName(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return std::string{};
}
return data->source->GetName();
}
llvm::StringRef GetSourceName(CS_Source source,
llvm::SmallVectorImpl<char>& buf,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return llvm::StringRef{};
}
return data->source->GetName();
}
std::string GetSourceDescription(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return std::string{};
}
llvm::SmallString<128> buf;
return data->source->GetDescription(buf);
}
llvm::StringRef GetSourceDescription(CS_Source source,
llvm::SmallVectorImpl<char>& buf,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return llvm::StringRef{};
}
return data->source->GetDescription(buf);
}
uint64_t GetSourceLastFrameTime(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return 0;
}
return data->source->GetCurFrameTime();
}
bool IsSourceConnected(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
}
return data->source->IsConnected();
}
CS_Property GetSourceProperty(CS_Source source, llvm::StringRef name,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return 0;
}
int property = data->source->GetPropertyIndex(name);
if (property < 0) {
*status = CS_INVALID_HANDLE;
return 0;
}
return Handle{source, property, Handle::kProperty};
}
llvm::ArrayRef<CS_Property> EnumerateSourceProperties(
CS_Source source, llvm::SmallVectorImpl<CS_Property>& vec,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return 0;
}
llvm::SmallVector<int, 32> properties_buf;
for (auto property :
data->source->EnumerateProperties(properties_buf, status))
vec.push_back(Handle{source, property, Handle::kProperty});
return vec;
}
VideoMode GetSourceVideoMode(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return VideoMode{};
}
return data->source->GetVideoMode(status);
}
bool SetSourceVideoMode(CS_Source source, const VideoMode& mode,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
}
return data->source->SetVideoMode(mode, status);
}
bool SetSourcePixelFormat(CS_Source source, VideoMode::PixelFormat pixelFormat,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
}
return data->source->SetPixelFormat(pixelFormat, status);
}
bool SetSourceResolution(CS_Source source, int width, int height,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
}
return data->source->SetResolution(width, height, status);
}
bool SetSourceFPS(CS_Source source, int fps, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
}
return data->source->SetFPS(fps, status);
}
std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return std::vector<VideoMode>{};
}
return data->source->EnumerateVideoModes(status);
}
llvm::ArrayRef<CS_Sink> EnumerateSourceSinks(
CS_Source source, llvm::SmallVectorImpl<CS_Sink>& vec, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return llvm::ArrayRef<CS_Sink>{};
}
vec.clear();
Sinks::GetInstance().ForEach([&](CS_Sink sinkHandle, const SinkData& data) {
if (source == data.sourceHandle.load()) vec.push_back(sinkHandle);
});
return vec;
}
CS_Source CopySource(CS_Source source, CS_Status* status) {
if (source == 0) return 0;
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return 0;
}
data->refCount++;
return source;
}
void ReleaseSource(CS_Source source, CS_Status* status) {
if (source == 0) return;
auto& inst = Sources::GetInstance();
auto data = inst.Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
}
if (data->refCount-- == 0) {
Notifier::GetInstance().NotifySource(data->source->GetName(), source,
CS_SOURCE_DESTROYED);
inst.Free(source);
}
}
//
// Camera Source Common Property Fuctions
//
void SetCameraBrightness(CS_Source source, int brightness, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
}
data->source->SetBrightness(brightness, status);
}
int GetCameraBrightness(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return 0;
}
return data->source->GetBrightness(status);
}
void SetCameraWhiteBalanceAuto(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
}
data->source->SetWhiteBalanceAuto(status);
}
void SetCameraWhiteBalanceHoldCurrent(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
}
data->source->SetWhiteBalanceHoldCurrent(status);
}
void SetCameraWhiteBalanceManual(CS_Source source, int value,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
}
data->source->SetWhiteBalanceManual(value, status);
}
void SetCameraExposureAuto(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
}
data->source->SetExposureAuto(status);
}
void SetCameraExposureHoldCurrent(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
}
data->source->SetExposureHoldCurrent(status);
}
void SetCameraExposureManual(CS_Source source, int value, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
}
data->source->SetExposureManual(value, status);
}
//
// Sink Functions
//
CS_SinkKind GetSinkKind(CS_Sink sink, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return CS_SINK_UNKNOWN;
}
return data->kind;
}
std::string GetSinkName(CS_Sink sink, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return std::string{};
}
return data->sink->GetName();
}
llvm::StringRef GetSinkName(CS_Sink sink, llvm::SmallVectorImpl<char>& buf,
CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return llvm::StringRef{};
}
return data->sink->GetName();
}
std::string GetSinkDescription(CS_Sink sink, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return std::string{};
}
llvm::SmallString<128> buf;
return data->sink->GetDescription(buf);
}
llvm::StringRef GetSinkDescription(CS_Sink sink,
llvm::SmallVectorImpl<char>& buf,
CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return llvm::StringRef{};
}
return data->sink->GetDescription(buf);
}
void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
}
if (source == 0) {
data->sink->SetSource(nullptr);
} else {
auto sourceData = Sources::GetInstance().Get(source);
if (!sourceData) {
*status = CS_INVALID_HANDLE;
return;
}
data->sink->SetSource(sourceData->source);
}
data->sourceHandle.store(source);
Notifier::GetInstance().NotifySinkSourceChanged(data->sink->GetName(), sink,
source);
}
CS_Source GetSinkSource(CS_Sink sink, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return 0;
}
return data->sourceHandle.load();
}
CS_Property GetSinkSourceProperty(CS_Sink sink, llvm::StringRef name,
CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return 0;
}
return GetSourceProperty(data->sourceHandle.load(), name, status);
}
CS_Sink CopySink(CS_Sink sink, CS_Status* status) {
if (sink == 0) return 0;
auto data = Sinks::GetInstance().Get(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return 0;
}
data->refCount++;
return sink;
}
void ReleaseSink(CS_Sink sink, CS_Status* status) {
if (sink == 0) return;
auto& inst = Sinks::GetInstance();
auto data = inst.Get(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
}
if (data->refCount-- == 0) {
Notifier::GetInstance().NotifySink(data->sink->GetName(), sink,
CS_SINK_DESTROYED);
inst.Free(sink);
}
}
//
// Listener Functions
//
void SetListenerOnStart(std::function<void()> onStart) {
Notifier::GetInstance().SetOnStart(onStart);
}
void SetListenerOnExit(std::function<void()> onExit) {
Notifier::GetInstance().SetOnExit(onExit);
}
CS_Listener AddListener(std::function<void(const RawEvent& event)> callback,
int eventMask, bool immediateNotify,
CS_Status* status) {
int uid = Notifier::GetInstance().AddListener(callback, eventMask);
if ((eventMask & CS_NETWORK_INTERFACES_CHANGED) != 0) {
// start network interface event listener
NetworkListener::GetInstance().Start();
if (immediateNotify)
Notifier::GetInstance().NotifyNetworkInterfacesChanged();
}
if (immediateNotify) {
// TODO
}
return Handle{uid, Handle::kListener};
}
void RemoveListener(CS_Listener handle, CS_Status* status) {
int uid = Handle{handle}.GetTypedIndex(Handle::kListener);
if (uid < 0) {
*status = CS_INVALID_HANDLE;
return;
}
Notifier::GetInstance().RemoveListener(uid);
}
bool NotifierDestroyed() { return Notifier::destroyed(); }
//
// Telemetry Functions
//
void SetTelemetryPeriod(double seconds) {
Telemetry::GetInstance().Start();
Telemetry::GetInstance().SetPeriod(seconds);
}
double GetTelemetryElapsedTime() {
return Telemetry::GetInstance().GetElapsedTime();
}
int64_t GetTelemetryValue(CS_Handle handle, CS_TelemetryKind kind,
CS_Status* status) {
return Telemetry::GetInstance().GetValue(handle, kind, status);
}
double GetTelemetryAverageValue(CS_Handle handle, CS_TelemetryKind kind,
CS_Status* status) {
return Telemetry::GetInstance().GetAverageValue(handle, kind, status);
}
//
// Logging Functions
//
void SetLogger(LogFunc func, unsigned int min_level) {
Logger& logger = Logger::GetInstance();
logger.SetLogger(func);
logger.set_min_level(min_level);
}
void SetDefaultLogger(unsigned int min_level) {
Logger& logger = Logger::GetInstance();
logger.SetDefaultLogger();
logger.set_min_level(min_level);
}
//
// Utility Functions
//
llvm::ArrayRef<CS_Source> EnumerateSourceHandles(
llvm::SmallVectorImpl<CS_Source>& vec, CS_Status* status) {
return Sources::GetInstance().GetAll(vec);
}
llvm::ArrayRef<CS_Sink> EnumerateSinkHandles(
llvm::SmallVectorImpl<CS_Sink>& vec, CS_Status* status) {
return Sinks::GetInstance().GetAll(vec);
}
std::string GetHostname() {
#ifdef __linux__
char name[256];
if (::gethostname(name, sizeof(name)) != 0) return "";
name[255] = '\0'; // Per POSIX, may not be null terminated if too long
return name;
#else
return ""; // TODO
#endif
}
std::vector<std::string> GetNetworkInterfaces() {
#ifdef __linux__
struct ifaddrs* ifa;
if (::getifaddrs(&ifa) != 0) return std::vector<std::string>{};
std::vector<std::string> rv;
char buf[256];
for (struct ifaddrs* i = ifa; i; i = i->ifa_next) {
if (!i->ifa_addr) continue; // no address
if (i->ifa_addr->sa_family != AF_INET) continue; // only return IPv4
struct sockaddr_in* addr_in = reinterpret_cast<sockaddr_in*>(i->ifa_addr);
const char* addr =
::inet_ntop(addr_in->sin_family, &addr_in->sin_addr, buf, sizeof(buf));
if (!addr) continue; // error converting address
rv.emplace_back(addr);
}
::freeifaddrs(ifa);
return rv;
#else
return std::vector<std::string>{}; // TODO
#endif
}
} // namespace cs

View File

@@ -0,0 +1,55 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "cscore_oo.h"
using namespace cs;
std::vector<VideoProperty> VideoSource::EnumerateProperties() const {
llvm::SmallVector<CS_Property, 32> handles_buf;
CS_Status status = 0;
auto handles = EnumerateSourceProperties(m_handle, handles_buf, &status);
std::vector<VideoProperty> properties;
properties.reserve(handles.size());
for (CS_Property handle : handles)
properties.emplace_back(VideoProperty{handle});
return properties;
}
std::vector<VideoSink> VideoSource::EnumerateSinks() {
llvm::SmallVector<CS_Sink, 16> handles_buf;
CS_Status status = 0;
auto handles = EnumerateSourceSinks(m_handle, handles_buf, &status);
std::vector<VideoSink> sinks;
sinks.reserve(handles.size());
for (int handle : handles) sinks.emplace_back(VideoSink{handle});
return sinks;
}
std::vector<VideoSource> VideoSource::EnumerateSources() {
llvm::SmallVector<CS_Source, 16> handles_buf;
CS_Status status = 0;
auto handles = ::cs::EnumerateSourceHandles(handles_buf, &status);
std::vector<VideoSource> sources;
sources.reserve(handles.size());
for (int handle : handles) sources.emplace_back(VideoSource{handle});
return sources;
}
std::vector<VideoSink> VideoSink::EnumerateSinks() {
llvm::SmallVector<CS_Sink, 16> handles_buf;
CS_Status status = 0;
auto handles = ::cs::EnumerateSinkHandles(handles_buf, &status);
std::vector<VideoSink> sinks;
sinks.reserve(handles.size());
for (int handle : handles) sinks.emplace_back(VideoSink{handle});
return sinks;
}

View File

@@ -0,0 +1,41 @@
// From:
// http://stackoverflow.com/questions/21028299/is-this-behavior-of-vectorresizesize-type-n-under-c11-and-boost-container
// Credits: Casey and Howard Hinnant
#ifndef CSCORE_DEFAULT_INIT_ALLOCATOR_H_
#define CSCORE_DEFAULT_INIT_ALLOCATOR_H_
#include <memory>
#include <utility>
namespace cs {
// Allocator adaptor that interposes construct() calls to
// convert value initialization into default initialization.
template <typename T, typename A = std::allocator<T>>
class default_init_allocator : public A {
typedef std::allocator_traits<A> a_t;
public:
template <typename U>
struct rebind {
using other =
default_init_allocator<U, typename a_t::template rebind_alloc<U>>;
};
using A::A;
template <typename U>
void construct(U* ptr) noexcept(
std::is_nothrow_default_constructible<U>::value) {
::new (static_cast<void*>(ptr)) U;
}
template <typename U, typename... Args>
void construct(U* ptr, Args&&... args) {
a_t::construct(static_cast<A&>(*this), ptr, std::forward<Args>(args)...);
}
};
} // namespace cs
#endif // CSCORE_DEFAULT_INIT_ALLOCATOR_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_CSCORE_H_
#define CSCORE_CSCORE_H_
/* C API */
#include "cscore_c.h"
#ifdef __cplusplus
/* C++ API */
#include "cscore_cpp.h"
#include "cscore_oo.h"
#endif /* __cplusplus */
#endif // CSCORE_CSCORE_H_

View File

@@ -0,0 +1,403 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_CSCORE_C_H_
#define CSCORE_CSCORE_C_H_
#include <stdint.h>
#include <cstddef>
#ifdef __cplusplus
extern "C" {
#endif
struct CvMat;
//
// The C API is handle-based. Sources and sinks are reference counted
// internally to the library. Any time a source or sink handle is returned
// or provided to a callback, the reference count is incremented.
// Calling CS_ReleaseSource() or CS_ReleaseSink() decrements the reference
// count, and when the reference count reaches zero, the object is destroyed.
// Connecting a source to a sink increments the reference count of the source,
// and when the sink is destroyed (its reference count reaches zero), the
// source reference count is decremented.
//
//
// Typedefs
//
typedef int CS_Bool;
typedef int CS_Status;
typedef int CS_Handle;
typedef CS_Handle CS_Property;
typedef CS_Handle CS_Listener;
typedef CS_Handle CS_Sink;
typedef CS_Handle CS_Source;
//
// Status values
//
enum CS_StatusValue {
CS_PROPERTY_WRITE_FAILED = 2000,
CS_OK = 0,
CS_INVALID_HANDLE = -2000, // handle was invalid (does not exist)
CS_WRONG_HANDLE_SUBTYPE = -2001,
CS_INVALID_PROPERTY = -2002,
CS_WRONG_PROPERTY_TYPE = -2003,
CS_READ_FAILED = -2004,
CS_SOURCE_IS_DISCONNECTED = -2005,
CS_EMPTY_VALUE = -2006,
CS_BAD_URL = -2007,
CS_TELEMETRY_NOT_ENABLED = -2008
};
//
// Logging levels
//
enum CS_LogLevel {
CS_LOG_CRITICAL = 50,
CS_LOG_ERROR = 40,
CS_LOG_WARNING = 30,
CS_LOG_INFO = 20,
CS_LOG_DEBUG = 10,
CS_LOG_DEBUG1 = 9,
CS_LOG_DEBUG2 = 8,
CS_LOG_DEBUG3 = 7,
CS_LOG_DEBUG4 = 6
};
//
// Pixel formats
//
enum CS_PixelFormat {
CS_PIXFMT_UNKNOWN = 0,
CS_PIXFMT_MJPEG,
CS_PIXFMT_YUYV,
CS_PIXFMT_RGB565,
CS_PIXFMT_BGR,
CS_PIXFMT_GRAY
};
//
// Frame formats
//
typedef struct CS_VideoMode {
int pixelFormat;
int width;
int height;
int fps;
} CS_VideoMode;
//
// Property kinds
//
enum CS_PropertyKind {
CS_PROP_NONE = 0,
CS_PROP_BOOLEAN = 1,
CS_PROP_INTEGER = 2,
CS_PROP_STRING = 4,
CS_PROP_ENUM = 8
};
//
// Source kinds
//
enum CS_SourceKind {
CS_SOURCE_UNKNOWN = 0,
CS_SOURCE_USB = 1,
CS_SOURCE_HTTP = 2,
CS_SOURCE_CV = 4
};
//
// HTTP Camera kinds
//
enum CS_HttpCameraKind {
CS_HTTP_UNKNOWN = 0,
CS_HTTP_MJPGSTREAMER = 1,
CS_HTTP_CSCORE = 2,
CS_HTTP_AXIS = 3
};
//
// Sink kinds
//
enum CS_SinkKind { CS_SINK_UNKNOWN = 0, CS_SINK_MJPEG = 2, CS_SINK_CV = 4 };
//
// Listener event kinds
//
enum CS_EventKind {
CS_SOURCE_CREATED = 0x0001,
CS_SOURCE_DESTROYED = 0x0002,
CS_SOURCE_CONNECTED = 0x0004,
CS_SOURCE_DISCONNECTED = 0x0008,
CS_SOURCE_VIDEOMODES_UPDATED = 0x0010,
CS_SOURCE_VIDEOMODE_CHANGED = 0x0020,
CS_SOURCE_PROPERTY_CREATED = 0x0040,
CS_SOURCE_PROPERTY_VALUE_UPDATED = 0x0080,
CS_SOURCE_PROPERTY_CHOICES_UPDATED = 0x0100,
CS_SINK_SOURCE_CHANGED = 0x0200,
CS_SINK_CREATED = 0x0400,
CS_SINK_DESTROYED = 0x0800,
CS_SINK_ENABLED = 0x1000,
CS_SINK_DISABLED = 0x2000,
CS_NETWORK_INTERFACES_CHANGED = 0x4000,
CS_TELEMETRY_UPDATED = 0x8000
};
//
// Telemetry kinds
//
enum CS_TelemetryKind {
CS_SOURCE_BYTES_RECEIVED = 1,
CS_SOURCE_FRAMES_RECEIVED = 2
};
//
// Listener event
//
struct CS_Event {
CS_EventKind kind;
// Valid for CS_SOURCE_* and CS_SINK_* respectively
CS_Source source;
CS_Sink sink;
// Source/sink/property name
const char* name;
// Fields for CS_SOURCE_VIDEOMODE_CHANGED event
CS_VideoMode mode;
// Fields for CS_SOURCE_PROPERTY_* events
CS_Property property;
CS_PropertyKind propertyKind;
int value;
const char* valueStr;
};
//
// Property Functions
//
enum CS_PropertyKind CS_GetPropertyKind(CS_Property property,
CS_Status* status);
char* CS_GetPropertyName(CS_Property property, CS_Status* status);
int CS_GetProperty(CS_Property property, CS_Status* status);
void CS_SetProperty(CS_Property property, int value, CS_Status* status);
int CS_GetPropertyMin(CS_Property property, CS_Status* status);
int CS_GetPropertyMax(CS_Property property, CS_Status* status);
int CS_GetPropertyStep(CS_Property property, CS_Status* status);
int CS_GetPropertyDefault(CS_Property property, CS_Status* status);
char* CS_GetStringProperty(CS_Property property, CS_Status* status);
void CS_SetStringProperty(CS_Property property, const char* value,
CS_Status* status);
char** CS_GetEnumPropertyChoices(CS_Property property, int* count,
CS_Status* status);
//
// Source Creation Functions
//
CS_Source CS_CreateUsbCameraDev(const char* name, int dev, CS_Status* status);
CS_Source CS_CreateUsbCameraPath(const char* name, const char* path,
CS_Status* status);
CS_Source CS_CreateHttpCamera(const char* name, const char* url,
enum CS_HttpCameraKind kind, CS_Status* status);
CS_Source CS_CreateHttpCameraMulti(const char* name, const char** urls,
int count, enum CS_HttpCameraKind kind,
CS_Status* status);
CS_Source CS_CreateCvSource(const char* name, const CS_VideoMode* mode,
CS_Status* status);
//
// Source Functions
//
CS_SourceKind CS_GetSourceKind(CS_Source source, CS_Status* status);
char* CS_GetSourceName(CS_Source source, CS_Status* status);
char* CS_GetSourceDescription(CS_Source source, CS_Status* status);
uint64_t CS_GetSourceLastFrameTime(CS_Source source, CS_Status* status);
CS_Bool CS_IsSourceConnected(CS_Source source, CS_Status* status);
CS_Property CS_GetSourceProperty(CS_Source source, const char* name,
CS_Status* status);
CS_Property* CS_EnumerateSourceProperties(CS_Source source, int* count,
CS_Status* status);
void CS_GetSourceVideoMode(CS_Source source, CS_VideoMode* mode,
CS_Status* status);
CS_Bool CS_SetSourceVideoMode(CS_Source source, const CS_VideoMode* mode,
CS_Status* status);
CS_Bool CS_SetSourceVideoModeDiscrete(CS_Source source,
enum CS_PixelFormat pixelFormat,
int width, int height, int fps,
CS_Status* status);
CS_Bool CS_SetSourcePixelFormat(CS_Source source,
enum CS_PixelFormat pixelFormat,
CS_Status* status);
CS_Bool CS_SetSourceResolution(CS_Source source, int width, int height,
CS_Status* status);
CS_Bool CS_SetSourceFPS(CS_Source source, int fps, CS_Status* status);
CS_VideoMode* CS_EnumerateSourceVideoModes(CS_Source source, int* count,
CS_Status* status);
CS_Sink* CS_EnumerateSourceSinks(CS_Source source, int* count,
CS_Status* status);
CS_Source CS_CopySource(CS_Source source, CS_Status* status);
void CS_ReleaseSource(CS_Source source, CS_Status* status);
//
// Camera Source Common Property Fuctions
//
void CS_SetCameraBrightness(CS_Source source, int brightness,
CS_Status* status);
int CS_GetCameraBrightness(CS_Source source, CS_Status* status);
void CS_SetCameraWhiteBalanceAuto(CS_Source source, CS_Status* status);
void CS_SetCameraWhiteBalanceHoldCurrent(CS_Source source, CS_Status* status);
void CS_SetCameraWhiteBalanceManual(CS_Source source, int value,
CS_Status* status);
void CS_SetCameraExposureAuto(CS_Source source, CS_Status* status);
void CS_SetCameraExposureHoldCurrent(CS_Source source, CS_Status* status);
void CS_SetCameraExposureManual(CS_Source source, int value, CS_Status* status);
//
// UsbCamera Source Functions
//
char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status);
//
// HttpCamera Source Functions
//
CS_HttpCameraKind CS_GetHttpCameraKind(CS_Source source, CS_Status* status);
void CS_SetHttpCameraUrls(CS_Source source, const char** urls, int count,
CS_Status* status);
char** CS_GetHttpCameraUrls(CS_Source source, int* count, CS_Status* status);
//
// OpenCV Source Functions
//
void CS_PutSourceFrame(CS_Source source, struct CvMat* image,
CS_Status* status);
void CS_NotifySourceError(CS_Source source, const char* msg, CS_Status* status);
void CS_SetSourceConnected(CS_Source source, CS_Bool connected,
CS_Status* status);
void CS_SetSourceDescription(CS_Source source, const char* description,
CS_Status* status);
CS_Property CS_CreateSourceProperty(CS_Source source, const char* name,
enum CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue,
int value, CS_Status* status);
void CS_SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
const char** choices, int count,
CS_Status* status);
//
// Sink Creation Functions
//
CS_Sink CS_CreateMjpegServer(const char* name, const char* listenAddress,
int port, CS_Status* status);
CS_Sink CS_CreateCvSink(const char* name, CS_Status* status);
CS_Sink CS_CreateCvSinkCallback(const char* name, void* data,
void (*processFrame)(void* data, uint64_t time),
CS_Status* status);
//
// Sink Functions
//
CS_SinkKind CS_GetSinkKind(CS_Sink sink, CS_Status* status);
char* CS_GetSinkName(CS_Sink sink, CS_Status* status);
char* CS_GetSinkDescription(CS_Sink sink, CS_Status* status);
void CS_SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status);
CS_Property CS_GetSinkSourceProperty(CS_Sink sink, const char* name,
CS_Status* status);
CS_Source CS_GetSinkSource(CS_Sink sink, CS_Status* status);
CS_Sink CS_CopySink(CS_Sink sink, CS_Status* status);
void CS_ReleaseSink(CS_Sink sink, CS_Status* status);
//
// MjpegServer Sink Functions
//
char* CS_GetMjpegServerListenAddress(CS_Sink sink, CS_Status* status);
int CS_GetMjpegServerPort(CS_Sink sink, CS_Status* status);
//
// OpenCV Sink Functions
//
void CS_SetSinkDescription(CS_Sink sink, const char* description,
CS_Status* status);
uint64_t CS_GrabSinkFrame(CS_Sink sink, struct CvMat* image, CS_Status* status);
uint64_t CS_GrabSinkFrameTimeout(CS_Sink sink, struct CvMat* image,
double timeout, CS_Status* status);
char* CS_GetSinkError(CS_Sink sink, CS_Status* status);
void CS_SetSinkEnabled(CS_Sink sink, CS_Bool enabled, CS_Status* status);
//
// Listener Functions
//
void CS_SetListenerOnStart(void (*onStart)(void* data), void* data);
void CS_SetListenerOnExit(void (*onExit)(void* data), void* data);
CS_Listener CS_AddListener(void* data,
void (*callback)(void* data, const CS_Event* event),
int eventMask, int immediateNotify,
CS_Status* status);
void CS_RemoveListener(CS_Listener handle, CS_Status* status);
int CS_NotifierDestroyed(void);
//
// Telemetry Functions
//
void CS_SetTelemetryPeriod(double seconds);
double CS_GetTelemetryElapsedTime(void);
int64_t CS_GetTelemetryValue(CS_Handle handle, CS_TelemetryKind kind,
CS_Status* status);
double CS_GetTelemetryAverageValue(CS_Handle handle, CS_TelemetryKind kind,
CS_Status* status);
//
// Logging Functions
//
typedef void (*CS_LogFunc)(unsigned int level, const char* file,
unsigned int line, const char* msg);
void CS_SetLogger(CS_LogFunc func, unsigned int min_level);
void CS_SetDefaultLogger(unsigned int min_level);
//
// Utility Functions
//
typedef struct CS_UsbCameraInfo {
int dev;
char* path;
char* name;
} CS_UsbCameraInfo;
CS_UsbCameraInfo* CS_EnumerateUsbCameras(int* count, CS_Status* status);
void CS_FreeEnumeratedUsbCameras(CS_UsbCameraInfo* cameras, int count);
CS_Source* CS_EnumerateSources(int* count, CS_Status* status);
void CS_ReleaseEnumeratedSources(CS_Source* sources, int count);
CS_Sink* CS_EnumerateSinks(int* count, CS_Status* status);
void CS_ReleaseEnumeratedSinks(CS_Sink* sinks, int count);
void CS_FreeString(char* str);
void CS_FreeEnumPropertyChoices(char** choices, int count);
void CS_FreeHttpCameraUrls(char** urls, int count);
void CS_FreeEnumeratedProperties(CS_Property* properties, int count);
void CS_FreeEnumeratedVideoModes(CS_VideoMode* modes, int count);
char* CS_GetHostname();
char** CS_GetNetworkInterfaces(int* count);
void CS_FreeNetworkInterfaces(char** interfaces, int count);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // CSCORE_CSCORE_C_H_

View File

@@ -0,0 +1,353 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2015-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_CSCORE_CPP_H_
#define CSCORE_CSCORE_CPP_H_
#include <stdint.h>
#include <functional>
#include <string>
#include <vector>
#include <llvm/ArrayRef.h>
#include <llvm/SmallVector.h>
#include <llvm/StringRef.h>
#include "cscore_c.h"
namespace cv {
class Mat;
} // namespace cv
namespace cs {
//
// Handle-based interface for C++. Users are encouraged to use the
// object oriented interface instead; this interface is intended for use
// in applications such as JNI which require handle-based access.
//
/// USB camera information
struct UsbCameraInfo {
/// Device number (e.g. N in '/dev/videoN' on Linux)
int dev;
/// Path to device if available (e.g. '/dev/video0' on Linux)
std::string path;
/// Vendor/model name of the camera as provided by the USB driver
std::string name;
};
/// Video mode
struct VideoMode : public CS_VideoMode {
enum PixelFormat {
kUnknown = CS_PIXFMT_UNKNOWN,
kMJPEG = CS_PIXFMT_MJPEG,
kYUYV = CS_PIXFMT_YUYV,
kRGB565 = CS_PIXFMT_RGB565,
kBGR = CS_PIXFMT_BGR,
kGray = CS_PIXFMT_GRAY
};
VideoMode() {
pixelFormat = 0;
width = 0;
height = 0;
fps = 0;
}
VideoMode(PixelFormat pixelFormat_, int width_, int height_, int fps_) {
pixelFormat = pixelFormat_;
width = width_;
height = height_;
fps = fps_;
}
explicit operator bool() const { return pixelFormat == kUnknown; }
};
/// Listener event
struct RawEvent {
enum Kind {
kSourceCreated = CS_SOURCE_CREATED,
kSourceDestroyed = CS_SOURCE_DESTROYED,
kSourceConnected = CS_SOURCE_CONNECTED,
kSourceDisconnected = CS_SOURCE_DISCONNECTED,
kSourceVideoModesUpdated = CS_SOURCE_VIDEOMODES_UPDATED,
kSourceVideoModeChanged = CS_SOURCE_VIDEOMODE_CHANGED,
kSourcePropertyCreated = CS_SOURCE_PROPERTY_CREATED,
kSourcePropertyValueUpdated = CS_SOURCE_PROPERTY_VALUE_UPDATED,
kSourcePropertyChoicesUpdated = CS_SOURCE_PROPERTY_CHOICES_UPDATED,
kSinkSourceChanged = CS_SINK_SOURCE_CHANGED,
kSinkCreated = CS_SINK_CREATED,
kSinkDestroyed = CS_SINK_DESTROYED,
kSinkEnabled = CS_SINK_ENABLED,
kSinkDisabled = CS_SINK_DISABLED,
kNetworkInterfacesChanged = CS_NETWORK_INTERFACES_CHANGED,
kTelemetryUpdated = CS_TELEMETRY_UPDATED
};
RawEvent() = default;
explicit RawEvent(RawEvent::Kind kind_) : kind{kind_} {}
RawEvent(llvm::StringRef name_, CS_Handle handle_, RawEvent::Kind kind_)
: kind{kind_}, name{name_} {
if (kind_ == kSinkCreated || kind_ == kSinkDestroyed ||
kind_ == kSinkEnabled || kind_ == kSinkDisabled)
sinkHandle = handle_;
else
sourceHandle = handle_;
}
RawEvent(llvm::StringRef name_, CS_Source source_, const VideoMode& mode_)
: kind{kSourceVideoModeChanged},
sourceHandle{source_},
name{name_},
mode{mode_} {}
RawEvent(llvm::StringRef name_, CS_Source source_, RawEvent::Kind kind_,
CS_Property property_, CS_PropertyKind propertyKind_, int value_,
llvm::StringRef valueStr_)
: kind{kind_},
sourceHandle{source_},
name{name_},
propertyHandle{property_},
propertyKind{propertyKind_},
value{value_},
valueStr{valueStr_} {}
Kind kind;
// Valid for kSource* and kSink* respectively
CS_Source sourceHandle = CS_INVALID_HANDLE;
CS_Sink sinkHandle = CS_INVALID_HANDLE;
// Source/sink/property name
std::string name;
// Fields for kSourceVideoModeChanged event
VideoMode mode;
// Fields for kSourceProperty* events
CS_Property propertyHandle;
CS_PropertyKind propertyKind;
int value;
std::string valueStr;
};
//
// Property Functions
//
CS_PropertyKind GetPropertyKind(CS_Property property, CS_Status* status);
std::string GetPropertyName(CS_Property property, CS_Status* status);
llvm::StringRef GetPropertyName(CS_Property property,
llvm::SmallVectorImpl<char>& buf,
CS_Status* status);
int GetProperty(CS_Property property, CS_Status* status);
void SetProperty(CS_Property property, int value, CS_Status* status);
int GetPropertyMin(CS_Property property, CS_Status* status);
int GetPropertyMax(CS_Property property, CS_Status* status);
int GetPropertyStep(CS_Property property, CS_Status* status);
int GetPropertyDefault(CS_Property property, CS_Status* status);
std::string GetStringProperty(CS_Property property, CS_Status* status);
llvm::StringRef GetStringProperty(CS_Property property,
llvm::SmallVectorImpl<char>& buf,
CS_Status* status);
void SetStringProperty(CS_Property property, llvm::StringRef value,
CS_Status* status);
std::vector<std::string> GetEnumPropertyChoices(CS_Property property,
CS_Status* status);
//
// Source Creation Functions
//
CS_Source CreateUsbCameraDev(llvm::StringRef name, int dev, CS_Status* status);
CS_Source CreateUsbCameraPath(llvm::StringRef name, llvm::StringRef path,
CS_Status* status);
CS_Source CreateHttpCamera(llvm::StringRef name, llvm::StringRef url,
CS_HttpCameraKind kind, CS_Status* status);
CS_Source CreateHttpCamera(llvm::StringRef name,
llvm::ArrayRef<std::string> urls,
CS_HttpCameraKind kind, CS_Status* status);
CS_Source CreateCvSource(llvm::StringRef name, const VideoMode& mode,
CS_Status* status);
//
// Source Functions
//
CS_SourceKind GetSourceKind(CS_Source source, CS_Status* status);
std::string GetSourceName(CS_Source source, CS_Status* status);
llvm::StringRef GetSourceName(CS_Source source,
llvm::SmallVectorImpl<char>& buf,
CS_Status* status);
std::string GetSourceDescription(CS_Source source, CS_Status* status);
llvm::StringRef GetSourceDescription(CS_Source source,
llvm::SmallVectorImpl<char>& buf,
CS_Status* status);
uint64_t GetSourceLastFrameTime(CS_Source source, CS_Status* status);
bool IsSourceConnected(CS_Source source, CS_Status* status);
CS_Property GetSourceProperty(CS_Source source, llvm::StringRef name,
CS_Status* status);
llvm::ArrayRef<CS_Property> EnumerateSourceProperties(
CS_Source source, llvm::SmallVectorImpl<CS_Property>& vec,
CS_Status* status);
VideoMode GetSourceVideoMode(CS_Source source, CS_Status* status);
bool SetSourceVideoMode(CS_Source source, const VideoMode& mode,
CS_Status* status);
bool SetSourcePixelFormat(CS_Source source, VideoMode::PixelFormat pixelFormat,
CS_Status* status);
bool SetSourceResolution(CS_Source source, int width, int height,
CS_Status* status);
bool SetSourceFPS(CS_Source source, int fps, CS_Status* status);
std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
CS_Status* status);
llvm::ArrayRef<CS_Sink> EnumerateSourceSinks(
CS_Source source, llvm::SmallVectorImpl<CS_Sink>& vec, CS_Status* status);
CS_Source CopySource(CS_Source source, CS_Status* status);
void ReleaseSource(CS_Source source, CS_Status* status);
//
// Camera Source Common Property Fuctions
//
void SetCameraBrightness(CS_Source source, int brightness, CS_Status* status);
int GetCameraBrightness(CS_Source source, CS_Status* status);
void SetCameraWhiteBalanceAuto(CS_Source source, CS_Status* status);
void SetCameraWhiteBalanceHoldCurrent(CS_Source source, CS_Status* status);
void SetCameraWhiteBalanceManual(CS_Source source, int value,
CS_Status* status);
void SetCameraExposureAuto(CS_Source source, CS_Status* status);
void SetCameraExposureHoldCurrent(CS_Source source, CS_Status* status);
void SetCameraExposureManual(CS_Source source, int value, CS_Status* status);
//
// UsbCamera Source Functions
//
std::string GetUsbCameraPath(CS_Source source, CS_Status* status);
//
// HttpCamera Source Functions
//
CS_HttpCameraKind GetHttpCameraKind(CS_Source source, CS_Status* status);
void SetHttpCameraUrls(CS_Source source, llvm::ArrayRef<std::string> urls,
CS_Status* status);
std::vector<std::string> GetHttpCameraUrls(CS_Source source, CS_Status* status);
//
// OpenCV Source Functions
//
void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status);
void NotifySourceError(CS_Source source, llvm::StringRef msg,
CS_Status* status);
void SetSourceConnected(CS_Source source, bool connected, CS_Status* status);
void SetSourceDescription(CS_Source source, llvm::StringRef description,
CS_Status* status);
CS_Property CreateSourceProperty(CS_Source source, llvm::StringRef name,
CS_PropertyKind kind, int minimum, int maximum,
int step, int defaultValue, int value,
CS_Status* status);
void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
llvm::ArrayRef<std::string> choices,
CS_Status* status);
//
// Sink Creation Functions
//
CS_Sink CreateMjpegServer(llvm::StringRef name, llvm::StringRef listenAddress,
int port, CS_Status* status);
CS_Sink CreateCvSink(llvm::StringRef name, CS_Status* status);
CS_Sink CreateCvSinkCallback(llvm::StringRef name,
std::function<void(uint64_t time)> processFrame,
CS_Status* status);
//
// Sink Functions
//
CS_SinkKind GetSinkKind(CS_Sink sink, CS_Status* status);
std::string GetSinkName(CS_Sink sink, CS_Status* status);
llvm::StringRef GetSinkName(CS_Sink sink, llvm::SmallVectorImpl<char>& buf,
CS_Status* status);
std::string GetSinkDescription(CS_Sink sink, CS_Status* status);
llvm::StringRef GetSinkDescription(CS_Sink sink,
llvm::SmallVectorImpl<char>& buf,
CS_Status* status);
void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status);
CS_Property GetSinkSourceProperty(CS_Sink sink, llvm::StringRef name,
CS_Status* status);
CS_Source GetSinkSource(CS_Sink sink, CS_Status* status);
CS_Sink CopySink(CS_Sink sink, CS_Status* status);
void ReleaseSink(CS_Sink sink, CS_Status* status);
//
// MjpegServer Sink Functions
//
std::string GetMjpegServerListenAddress(CS_Sink sink, CS_Status* status);
int GetMjpegServerPort(CS_Sink sink, CS_Status* status);
//
// OpenCV Sink Functions
//
void SetSinkDescription(CS_Sink sink, llvm::StringRef description,
CS_Status* status);
uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status);
uint64_t GrabSinkFrameTimeout(CS_Sink sink, cv::Mat& image, double timeout,
CS_Status* status);
std::string GetSinkError(CS_Sink sink, CS_Status* status);
llvm::StringRef GetSinkError(CS_Sink sink, llvm::SmallVectorImpl<char>& buf,
CS_Status* status);
void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status);
//
// Listener Functions
//
void SetListenerOnStart(std::function<void()> onStart);
void SetListenerOnExit(std::function<void()> onExit);
CS_Listener AddListener(std::function<void(const RawEvent& event)> callback,
int eventMask, bool immediateNotify, CS_Status* status);
void RemoveListener(CS_Listener handle, CS_Status* status);
bool NotifierDestroyed();
//
// Telemetry Functions
//
void SetTelemetryPeriod(double seconds);
double GetTelemetryElapsedTime();
int64_t GetTelemetryValue(CS_Handle handle, CS_TelemetryKind kind,
CS_Status* status);
double GetTelemetryAverageValue(CS_Handle handle, CS_TelemetryKind kind,
CS_Status* status);
//
// Logging Functions
//
typedef std::function<void(unsigned int level, const char* file,
unsigned int line, const char* msg)>
LogFunc;
void SetLogger(LogFunc func, unsigned int min_level);
void SetDefaultLogger(unsigned int min_level);
//
// Utility Functions
//
std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status);
llvm::ArrayRef<CS_Source> EnumerateSourceHandles(
llvm::SmallVectorImpl<CS_Source>& vec, CS_Status* status);
llvm::ArrayRef<CS_Sink> EnumerateSinkHandles(
llvm::SmallVectorImpl<CS_Sink>& vec, CS_Status* status);
std::string GetHostname();
std::vector<std::string> GetNetworkInterfaces();
} // namespace cs
// C functions taking a cv::Mat* for specific interop implementations
extern "C" {
uint64_t CS_GrabSinkFrameCpp(CS_Sink sink, cv::Mat* image, CS_Status* status);
uint64_t CS_GrabSinkFrameTimeoutCpp(CS_Sink sink, cv::Mat* image,
double timeout, CS_Status* status);
void CS_PutSourceFrameCpp(CS_Source source, cv::Mat* image, CS_Status* status);
} // extern "C"
#endif // CSCORE_CSCORE_CPP_H_

View File

@@ -0,0 +1,661 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2015-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_CSCORE_OO_H_
#define CSCORE_CSCORE_OO_H_
#include <initializer_list>
#include <string>
#include <utility>
#include <vector>
#include "cscore_cpp.h"
namespace cs {
//
// Object-oriented interface
//
// Forward declarations so friend declarations work correctly
class CvSource;
class VideoEvent;
class VideoSink;
class VideoSource;
class VideoProperty {
friend class CvSource;
friend class VideoEvent;
friend class VideoSink;
friend class VideoSource;
public:
enum Kind {
kNone = CS_PROP_NONE,
kBoolean = CS_PROP_BOOLEAN,
kInteger = CS_PROP_INTEGER,
kString = CS_PROP_STRING,
kEnum = CS_PROP_ENUM
};
VideoProperty() : m_handle(0), m_kind(kNone) {}
std::string GetName() const;
Kind GetKind() const { return m_kind; }
explicit operator bool() const { return m_kind != kNone; }
// Kind checkers
bool IsBoolean() const { return m_kind == kBoolean; }
bool IsInteger() const { return m_kind == kInteger; }
bool IsString() const { return m_kind == kString; }
bool IsEnum() const { return m_kind == kEnum; }
int Get() const;
void Set(int value);
int GetMin() const;
int GetMax() const;
int GetStep() const;
int GetDefault() const;
// String-specific functions
std::string GetString() const;
llvm::StringRef GetString(llvm::SmallVectorImpl<char>& buf) const;
void SetString(llvm::StringRef value);
// Enum-specific functions
std::vector<std::string> GetChoices() const;
CS_Status GetLastStatus() const { return m_status; }
private:
explicit VideoProperty(CS_Property handle);
VideoProperty(CS_Property handle, Kind kind);
mutable CS_Status m_status;
CS_Property m_handle;
Kind m_kind;
};
/// A source for video that provides a sequence of frames.
class VideoSource {
friend class VideoEvent;
friend class VideoSink;
public:
enum Kind {
kUnknown = CS_SOURCE_UNKNOWN,
kUsb = CS_SOURCE_USB,
kHttp = CS_SOURCE_HTTP,
kCv = CS_SOURCE_CV
};
VideoSource() noexcept : m_handle(0) {}
VideoSource(const VideoSource& source);
VideoSource(VideoSource&& other) noexcept;
VideoSource& operator=(VideoSource other) noexcept;
~VideoSource();
explicit operator bool() const { return m_handle != 0; }
int GetHandle() const { return m_handle; }
bool operator==(const VideoSource& other) const {
return m_handle == other.m_handle;
}
bool operator!=(const VideoSource& other) const { return !(*this == other); }
/// Get the kind of the source.
Kind GetKind() const;
/// Get the name of the source. The name is an arbitrary identifier
/// provided when the source is created, and should be unique.
std::string GetName() const;
/// Get the source description. This is source-kind specific.
std::string GetDescription() const;
/// Get the last time a frame was captured.
/// This uses the same time base as wpi::Now().
/// @return Time in 1 us increments.
uint64_t GetLastFrameTime() const;
/// Is the source currently connected to whatever is providing the images?
bool IsConnected() const;
/// Get a property.
/// @param name Property name
/// @return Property contents (of kind Property::kNone if no property with
/// the given name exists)
VideoProperty GetProperty(llvm::StringRef name);
/// Enumerate all properties of this source.
std::vector<VideoProperty> EnumerateProperties() const;
/// Get the current video mode.
VideoMode GetVideoMode() const;
/// Set the video mode.
/// @param mode Video mode
bool SetVideoMode(const VideoMode& mode);
/// Set the video mode.
/// @param pixelFormat desired pixel format
/// @param width desired width
/// @param height desired height
/// @param fps desired FPS
/// @return True if set successfully
bool SetVideoMode(VideoMode::PixelFormat pixelFormat, int width, int height,
int fps);
/// Set the pixel format.
/// @param pixelFormat desired pixel format
/// @return True if set successfully
bool SetPixelFormat(VideoMode::PixelFormat pixelFormat);
/// Set the resolution.
/// @param width desired width
/// @param height desired height
/// @return True if set successfully
bool SetResolution(int width, int height);
/// Set the frames per second (FPS).
/// @param fps desired FPS
/// @return True if set successfully
bool SetFPS(int fps);
/// Get the actual FPS.
/// SetTelemetryPeriod() must be called for this to be valid.
/// @return Actual FPS averaged over the telemetry period.
double GetActualFPS() const;
/// Get the data rate (in bytes per second).
/// SetTelemetryPeriod() must be called for this to be valid.
/// @return Data rate averaged over the telemetry period.
double GetActualDataRate() const;
/// Enumerate all known video modes for this source.
std::vector<VideoMode> EnumerateVideoModes() const;
CS_Status GetLastStatus() const { return m_status; }
/// Enumerate all sinks connected to this source.
/// @return Vector of sinks.
std::vector<VideoSink> EnumerateSinks();
/// Enumerate all existing sources.
/// @return Vector of sources.
static std::vector<VideoSource> EnumerateSources();
friend void swap(VideoSource& first, VideoSource& second) noexcept {
using std::swap;
swap(first.m_status, second.m_status);
swap(first.m_handle, second.m_handle);
}
protected:
explicit VideoSource(CS_Source handle) : m_handle(handle) {}
mutable CS_Status m_status = 0;
CS_Source m_handle;
};
/// A source that represents a video camera.
class VideoCamera : public VideoSource {
public:
enum WhiteBalance {
kFixedIndoor = 3000,
kFixedOutdoor1 = 4000,
kFixedOutdoor2 = 5000,
kFixedFluorescent1 = 5100,
kFixedFlourescent2 = 5200
};
VideoCamera() = default;
/// Set the brightness, as a percentage (0-100).
void SetBrightness(int brightness);
/// Get the brightness, as a percentage (0-100).
int GetBrightness();
/// Set the white balance to auto.
void SetWhiteBalanceAuto();
/// Set the white balance to hold current.
void SetWhiteBalanceHoldCurrent();
/// Set the white balance to manual, with specified color temperature.
void SetWhiteBalanceManual(int value);
/// Set the exposure to auto aperature.
void SetExposureAuto();
/// Set the exposure to hold current.
void SetExposureHoldCurrent();
/// Set the exposure to manual, as a percentage (0-100).
void SetExposureManual(int value);
protected:
explicit VideoCamera(CS_Source handle) : VideoSource(handle) {}
};
/// A source that represents a USB camera.
class UsbCamera : public VideoCamera {
public:
UsbCamera() = default;
/// Create a source for a USB camera based on device number.
/// @param name Source name (arbitrary unique identifier)
/// @param dev Device number (e.g. 0 for /dev/video0)
UsbCamera(llvm::StringRef name, int dev);
/// Create a source for a USB camera based on device path.
/// @param name Source name (arbitrary unique identifier)
/// @param path Path to device (e.g. "/dev/video0" on Linux)
UsbCamera(llvm::StringRef name, llvm::StringRef path);
/// Enumerate USB cameras on the local system.
/// @return Vector of USB camera information (one for each camera)
static std::vector<UsbCameraInfo> EnumerateUsbCameras();
/// Get the path to the device.
std::string GetPath() const;
};
/// A source that represents a MJPEG-over-HTTP (IP) camera.
class HttpCamera : public VideoCamera {
public:
enum HttpCameraKind {
kUnknown = CS_HTTP_UNKNOWN,
kMJPGStreamer = CS_HTTP_MJPGSTREAMER,
kCSCore = CS_HTTP_CSCORE,
kAxis = CS_HTTP_AXIS
};
/// Create a source for a MJPEG-over-HTTP (IP) camera.
/// @param name Source name (arbitrary unique identifier)
/// @param url Camera URL (e.g. "http://10.x.y.11/video/stream.mjpg")
/// @param kind Camera kind (e.g. kAxis)
HttpCamera(llvm::StringRef name, llvm::StringRef url,
HttpCameraKind kind = kUnknown);
/// Create a source for a MJPEG-over-HTTP (IP) camera.
/// @param name Source name (arbitrary unique identifier)
/// @param url Camera URL (e.g. "http://10.x.y.11/video/stream.mjpg")
/// @param kind Camera kind (e.g. kAxis)
HttpCamera(llvm::StringRef name, const char* url,
HttpCameraKind kind = kUnknown);
/// Create a source for a MJPEG-over-HTTP (IP) camera.
/// @param name Source name (arbitrary unique identifier)
/// @param url Camera URL (e.g. "http://10.x.y.11/video/stream.mjpg")
/// @param kind Camera kind (e.g. kAxis)
HttpCamera(llvm::StringRef name, const std::string& url,
HttpCameraKind kind = kUnknown);
/// Create a source for a MJPEG-over-HTTP (IP) camera.
/// @param name Source name (arbitrary unique identifier)
/// @param urls Array of Camera URLs
/// @param kind Camera kind (e.g. kAxis)
HttpCamera(llvm::StringRef name, llvm::ArrayRef<std::string> urls,
HttpCameraKind kind = kUnknown);
/// Create a source for a MJPEG-over-HTTP (IP) camera.
/// @param name Source name (arbitrary unique identifier)
/// @param urls Array of Camera URLs
/// @param kind Camera kind (e.g. kAxis)
template <typename T>
HttpCamera(llvm::StringRef name, std::initializer_list<T> urls,
HttpCameraKind kind = kUnknown);
/// Get the kind of HTTP camera.
/// Autodetection can result in returning a different value than the camera
/// was created with.
HttpCameraKind GetHttpCameraKind() const;
/// Change the URLs used to connect to the camera.
void SetUrls(llvm::ArrayRef<std::string> urls);
/// Change the URLs used to connect to the camera.
template <typename T>
void SetUrls(std::initializer_list<T> urls);
/// Get the URLs used to connect to the camera.
std::vector<std::string> GetUrls() const;
};
/// A source that represents an Axis IP camera.
class AxisCamera : public HttpCamera {
static std::string HostToUrl(llvm::StringRef host);
static std::vector<std::string> HostToUrl(llvm::ArrayRef<std::string> hosts);
template <typename T>
static std::vector<std::string> HostToUrl(std::initializer_list<T> hosts);
public:
/// Create a source for an Axis IP camera.
/// @param name Source name (arbitrary unique identifier)
/// @param host Camera host IP or DNS name (e.g. "10.x.y.11")
/// @param kind Camera kind (e.g. kAxis)
AxisCamera(llvm::StringRef name, llvm::StringRef host);
/// Create a source for an Axis IP camera.
/// @param name Source name (arbitrary unique identifier)
/// @param host Camera host IP or DNS name (e.g. "10.x.y.11")
/// @param kind Camera kind (e.g. kAxis)
AxisCamera(llvm::StringRef name, const char* host);
/// Create a source for an Axis IP camera.
/// @param name Source name (arbitrary unique identifier)
/// @param host Camera host IP or DNS name (e.g. "10.x.y.11")
/// @param kind Camera kind (e.g. kAxis)
AxisCamera(llvm::StringRef name, const std::string& host);
/// Create a source for an Axis IP camera.
/// @param name Source name (arbitrary unique identifier)
/// @param hosts Array of Camera host IPs/DNS names
/// @param kind Camera kind (e.g. kAxis)
AxisCamera(llvm::StringRef name, llvm::ArrayRef<std::string> hosts);
/// Create a source for an Axis IP camera.
/// @param name Source name (arbitrary unique identifier)
/// @param hosts Array of Camera host IPs/DNS names
/// @param kind Camera kind (e.g. kAxis)
template <typename T>
AxisCamera(llvm::StringRef name, std::initializer_list<T> hosts);
};
/// A source for user code to provide OpenCV images as video frames.
class CvSource : public VideoSource {
public:
CvSource() = default;
/// Create an OpenCV source.
/// @param name Source name (arbitrary unique identifier)
/// @param mode Video mode being generated
CvSource(llvm::StringRef name, const VideoMode& mode);
/// Create an OpenCV source.
/// @param name Source name (arbitrary unique identifier)
/// @param pixelFormat Pixel format
/// @param width width
/// @param height height
/// @param fps fps
CvSource(llvm::StringRef name, VideoMode::PixelFormat pixelFormat, int width,
int height, int fps);
/// Put an OpenCV image and notify sinks.
/// Only 8-bit single-channel or 3-channel (with BGR channel order) images
/// are supported. If the format, depth or channel order is different, use
/// cv::Mat::convertTo() and/or cv::cvtColor() to convert it first.
/// @param image OpenCV image
void PutFrame(cv::Mat& image);
/// Signal sinks that an error has occurred. This should be called instead
/// of NotifyFrame when an error occurs.
void NotifyError(llvm::StringRef msg);
/// Set source connection status. Defaults to true.
/// @param connected True for connected, false for disconnected
void SetConnected(bool connected);
/// Set source description.
/// @param description Description
void SetDescription(llvm::StringRef description);
/// Create a property.
/// @param name Property name
/// @param kind Property kind
/// @param minimum Minimum value
/// @param maximum Maximum value
/// @param step Step value
/// @param defaultValue Default value
/// @param value Current value
/// @return Property
VideoProperty CreateProperty(llvm::StringRef name, VideoProperty::Kind kind,
int minimum, int maximum, int step,
int defaultValue, int value);
/// Create an integer property.
/// @param name Property name
/// @param minimum Minimum value
/// @param maximum Maximum value
/// @param step Step value
/// @param defaultValue Default value
/// @param value Current value
/// @return Property
VideoProperty CreateIntegerProperty(llvm::StringRef name, int minimum,
int maximum, int step, int defaultValue,
int value);
/// Create a boolean property.
/// @param name Property name
/// @param defaultValue Default value
/// @param value Current value
/// @return Property
VideoProperty CreateBooleanProperty(llvm::StringRef name, bool defaultValue,
bool value);
/// Create a string property.
/// @param name Property name
/// @param defaultValue Default value
/// @param value Current value
/// @return Property
VideoProperty CreateStringProperty(llvm::StringRef name,
llvm::StringRef value);
/// Configure enum property choices.
/// @param property Property
/// @param choices Choices
void SetEnumPropertyChoices(const VideoProperty& property,
llvm::ArrayRef<std::string> choices);
/// Configure enum property choices.
/// @param property Property
/// @param choices Choices
template <typename T>
void SetEnumPropertyChoices(const VideoProperty& property,
std::initializer_list<T> choices);
};
/// A sink for video that accepts a sequence of frames.
class VideoSink {
friend class VideoEvent;
friend class VideoSource;
public:
enum Kind {
kUnknown = CS_SINK_UNKNOWN,
kMjpeg = CS_SINK_MJPEG,
kCv = CS_SINK_CV
};
VideoSink() noexcept : m_handle(0) {}
VideoSink(const VideoSink& sink);
VideoSink(VideoSink&& sink) noexcept;
VideoSink& operator=(VideoSink other) noexcept;
~VideoSink();
explicit operator bool() const { return m_handle != 0; }
int GetHandle() const { return m_handle; }
bool operator==(const VideoSink& other) const {
return m_handle == other.m_handle;
}
bool operator!=(const VideoSink& other) const { return !(*this == other); }
/// Get the kind of the sink.
Kind GetKind() const;
/// Get the name of the sink. The name is an arbitrary identifier
/// provided when the sink is created, and should be unique.
std::string GetName() const;
/// Get the sink description. This is sink-kind specific.
std::string GetDescription() const;
/// Configure which source should provide frames to this sink. Each sink
/// can accept frames from only a single source, but a single source can
/// provide frames to multiple clients.
/// @param source Source
void SetSource(VideoSource source);
/// Get the connected source.
/// @return Connected source (empty if none connected).
VideoSource GetSource() const;
/// Get a property of the associated source.
/// @param name Property name
/// @return Property (kind Property::kNone if no property with
/// the given name exists or no source connected)
VideoProperty GetSourceProperty(llvm::StringRef name);
CS_Status GetLastStatus() const { return m_status; }
/// Enumerate all existing sinks.
/// @return Vector of sinks.
static std::vector<VideoSink> EnumerateSinks();
friend void swap(VideoSink& first, VideoSink& second) noexcept {
using std::swap;
swap(first.m_status, second.m_status);
swap(first.m_handle, second.m_handle);
}
protected:
explicit VideoSink(CS_Sink handle) : m_handle(handle) {}
mutable CS_Status m_status = 0;
CS_Sink m_handle;
};
/// A sink that acts as a MJPEG-over-HTTP network server.
class MjpegServer : public VideoSink {
public:
MjpegServer() = default;
/// Create a MJPEG-over-HTTP server sink.
/// @param name Sink name (arbitrary unique identifier)
/// @param listenAddress TCP listen address (empty string for all addresses)
/// @param port TCP port number
MjpegServer(llvm::StringRef name, llvm::StringRef listenAddress, int port);
/// Create a MJPEG-over-HTTP server sink.
/// @param name Sink name (arbitrary unique identifier)
/// @param port TCP port number
MjpegServer(llvm::StringRef name, int port) : MjpegServer(name, "", port) {}
/// Get the listen address of the server.
std::string GetListenAddress() const;
/// Get the port number of the server.
int GetPort() const;
};
/// A sink for user code to accept video frames as OpenCV images.
class CvSink : public VideoSink {
public:
CvSink() = default;
/// Create a sink for accepting OpenCV images.
/// WaitForFrame() must be called on the created sink to get each new
/// image.
/// @param name Source name (arbitrary unique identifier)
explicit CvSink(llvm::StringRef name);
/// Create a sink for accepting OpenCV images in a separate thread.
/// A thread will be created that calls WaitForFrame() and calls the
/// processFrame() callback each time a new frame arrives.
/// @param name Source name (arbitrary unique identifier)
/// @param processFrame Frame processing function; will be called with a
/// time=0 if an error occurred. processFrame should call GetImage()
/// or GetError() as needed, but should not call (except in very
/// unusual circumstances) WaitForImage().
CvSink(llvm::StringRef name, std::function<void(uint64_t time)> processFrame);
/// Set sink description.
/// @param description Description
void SetDescription(llvm::StringRef description);
/// Wait for the next frame and get the image.
/// Times out (returning 0) after timeout seconds.
/// The provided image will have three 8-bit channels stored in BGR order.
/// @return Frame time, or 0 on error (call GetError() to obtain the error
/// message); the frame time is in the same time base as wpi::Now(),
/// and is in 1 us increments.
uint64_t GrabFrame(cv::Mat& image, double timeout = 0.225) const;
/// Wait for the next frame and get the image. May block forever.
/// The provided image will have three 8-bit channels stored in BGR order.
/// @return Frame time, or 0 on error (call GetError() to obtain the error
/// message); the frame time is in the same time base as wpi::Now(),
/// and is in 1 us increments.
uint64_t GrabFrameNoTimeout(cv::Mat& image) const;
/// Get error string. Call this if WaitForFrame() returns 0 to determine
/// what the error is.
std::string GetError() const;
/// Enable or disable getting new frames.
/// Disabling will cause processFrame (for callback-based CvSinks) to not
/// be called and WaitForFrame() to not return. This can be used to save
/// processor resources when frames are not needed.
void SetEnabled(bool enabled);
};
/// An event generated by the library and provided to event listeners.
class VideoEvent : public RawEvent {
public:
/// Get the source associated with the event (if any).
VideoSource GetSource() const;
/// Get the sink associated with the event (if any).
VideoSink GetSink() const;
/// Get the property associated with the event (if any).
VideoProperty GetProperty() const;
};
/// An event listener. This calls back to a desigated callback function when
/// an event matching the specified mask is generated by the library.
class VideoListener {
public:
VideoListener() : m_handle(0) {}
/// Create an event listener.
/// @param callback Callback function
/// @param eventMask Bitmask of VideoEvent::Kind values
/// @param immediateNotify Whether callback should be immediately called with
/// a representative set of events for the current library state.
VideoListener(std::function<void(const VideoEvent& event)> callback,
int eventMask, bool immediateNotify);
VideoListener(const VideoListener&) = delete;
VideoListener& operator=(const VideoListener&) = delete;
VideoListener(VideoListener&& other) noexcept;
VideoListener& operator=(VideoListener&& other) noexcept;
~VideoListener();
friend void swap(VideoListener& first, VideoListener& second) noexcept {
using std::swap;
swap(first.m_handle, second.m_handle);
}
private:
CS_Listener m_handle;
};
} // namespace cs
#include "cscore_oo.inl"
#endif // CSCORE_CSCORE_OO_H_

View File

@@ -0,0 +1,573 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2015. 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. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_OO_INL_
#define CSCORE_OO_INL_
namespace cs {
inline std::string VideoProperty::GetName() const {
m_status = 0;
return GetPropertyName(m_handle, &m_status);
}
inline int VideoProperty::Get() const {
m_status = 0;
return GetProperty(m_handle, &m_status);
}
inline void VideoProperty::Set(int value) {
m_status = 0;
SetProperty(m_handle, value, &m_status);
}
inline int VideoProperty::GetMin() const {
m_status = 0;
return GetPropertyMin(m_handle, &m_status);
}
inline int VideoProperty::GetMax() const {
m_status = 0;
return GetPropertyMax(m_handle, &m_status);
}
inline int VideoProperty::GetStep() const {
m_status = 0;
return GetPropertyStep(m_handle, &m_status);
}
inline int VideoProperty::GetDefault() const {
m_status = 0;
return GetPropertyDefault(m_handle, &m_status);
}
inline std::string VideoProperty::GetString() const {
m_status = 0;
return GetStringProperty(m_handle, &m_status);
}
inline llvm::StringRef VideoProperty::GetString(
llvm::SmallVectorImpl<char>& buf) const {
m_status = 0;
return GetStringProperty(m_handle, buf, &m_status);
}
inline void VideoProperty::SetString(llvm::StringRef value) {
m_status = 0;
SetStringProperty(m_handle, value, &m_status);
}
inline std::vector<std::string> VideoProperty::GetChoices() const {
m_status = 0;
return GetEnumPropertyChoices(m_handle, &m_status);
}
inline VideoProperty::VideoProperty(CS_Property handle) : m_handle(handle) {
m_status = 0;
if (handle == 0)
m_kind = kNone;
else
m_kind =
static_cast<Kind>(static_cast<int>(GetPropertyKind(handle, &m_status)));
}
inline VideoProperty::VideoProperty(CS_Property handle, Kind kind)
: m_handle(handle), m_kind(kind) {}
inline VideoSource::VideoSource(const VideoSource& source)
: m_handle(source.m_handle == 0 ? 0
: CopySource(source.m_handle, &m_status)) {}
inline VideoSource::VideoSource(VideoSource&& other) noexcept : VideoSource() {
swap(*this, other);
}
inline VideoSource& VideoSource::operator=(VideoSource other) noexcept {
swap(*this, other);
return *this;
}
inline VideoSource::~VideoSource() {
m_status = 0;
if (m_handle != 0) ReleaseSource(m_handle, &m_status);
}
inline VideoSource::Kind VideoSource::GetKind() const {
m_status = 0;
return static_cast<VideoSource::Kind>(GetSourceKind(m_handle, &m_status));
}
inline std::string VideoSource::GetName() const {
m_status = 0;
return GetSourceName(m_handle, &m_status);
}
inline std::string VideoSource::GetDescription() const {
m_status = 0;
return GetSourceDescription(m_handle, &m_status);
}
inline uint64_t VideoSource::GetLastFrameTime() const {
m_status = 0;
return GetSourceLastFrameTime(m_handle, &m_status);
}
inline bool VideoSource::IsConnected() const {
m_status = 0;
return IsSourceConnected(m_handle, &m_status);
}
inline VideoProperty VideoSource::GetProperty(llvm::StringRef name) {
m_status = 0;
return VideoProperty{GetSourceProperty(m_handle, name, &m_status)};
}
inline VideoMode VideoSource::GetVideoMode() const {
m_status = 0;
return GetSourceVideoMode(m_handle, &m_status);
}
inline bool VideoSource::SetVideoMode(const VideoMode& mode) {
m_status = 0;
return SetSourceVideoMode(m_handle, mode, &m_status);
}
inline bool VideoSource::SetVideoMode(VideoMode::PixelFormat pixelFormat,
int width, int height, int fps) {
m_status = 0;
return SetSourceVideoMode(
m_handle, VideoMode{pixelFormat, width, height, fps}, &m_status);
}
inline bool VideoSource::SetPixelFormat(VideoMode::PixelFormat pixelFormat) {
m_status = 0;
return SetSourcePixelFormat(m_handle, pixelFormat, &m_status);
}
inline bool VideoSource::SetResolution(int width, int height) {
m_status = 0;
return SetSourceResolution(m_handle, width, height, &m_status);
}
inline bool VideoSource::SetFPS(int fps) {
m_status = 0;
return SetSourceFPS(m_handle, fps, &m_status);
}
inline double VideoSource::GetActualFPS() const {
m_status = 0;
return cs::GetTelemetryAverageValue(m_handle, CS_SOURCE_FRAMES_RECEIVED,
&m_status);
}
inline double VideoSource::GetActualDataRate() const {
m_status = 0;
return cs::GetTelemetryAverageValue(m_handle, CS_SOURCE_BYTES_RECEIVED,
&m_status);
}
inline std::vector<VideoMode> VideoSource::EnumerateVideoModes() const {
CS_Status status = 0;
return EnumerateSourceVideoModes(m_handle, &status);
}
inline void VideoCamera::SetBrightness(int brightness) {
m_status = 0;
SetCameraBrightness(m_handle, brightness, &m_status);
}
inline int VideoCamera::GetBrightness() {
m_status = 0;
return GetCameraBrightness(m_handle, &m_status);
}
inline void VideoCamera::SetWhiteBalanceAuto() {
m_status = 0;
SetCameraWhiteBalanceAuto(m_handle, &m_status);
}
inline void VideoCamera::SetWhiteBalanceHoldCurrent() {
m_status = 0;
SetCameraWhiteBalanceHoldCurrent(m_handle, &m_status);
}
inline void VideoCamera::SetWhiteBalanceManual(int value) {
m_status = 0;
SetCameraWhiteBalanceManual(m_handle, value, &m_status);
}
inline void VideoCamera::SetExposureAuto() {
m_status = 0;
SetCameraExposureAuto(m_handle, &m_status);
}
inline void VideoCamera::SetExposureHoldCurrent() {
m_status = 0;
SetCameraExposureHoldCurrent(m_handle, &m_status);
}
inline void VideoCamera::SetExposureManual(int value) {
m_status = 0;
SetCameraExposureManual(m_handle, value, &m_status);
}
inline UsbCamera::UsbCamera(llvm::StringRef name, int dev) {
m_handle = CreateUsbCameraDev(name, dev, &m_status);
}
inline UsbCamera::UsbCamera(llvm::StringRef name, llvm::StringRef path) {
m_handle = CreateUsbCameraPath(name, path, &m_status);
}
inline std::vector<UsbCameraInfo> UsbCamera::EnumerateUsbCameras() {
CS_Status status = 0;
return ::cs::EnumerateUsbCameras(&status);
}
inline std::string UsbCamera::GetPath() const {
m_status = 0;
return ::cs::GetUsbCameraPath(m_handle, &m_status);
}
inline HttpCamera::HttpCamera(llvm::StringRef name, llvm::StringRef url,
HttpCameraKind kind) {
m_handle = CreateHttpCamera(
name, url, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
&m_status);
}
inline HttpCamera::HttpCamera(llvm::StringRef name, const char* url,
HttpCameraKind kind) {
m_handle = CreateHttpCamera(
name, url, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
&m_status);
}
inline HttpCamera::HttpCamera(llvm::StringRef name, const std::string& url,
HttpCameraKind kind)
: HttpCamera(name, llvm::StringRef{url}, kind) {}
inline HttpCamera::HttpCamera(llvm::StringRef name,
llvm::ArrayRef<std::string> urls,
HttpCameraKind kind) {
m_handle = CreateHttpCamera(
name, urls, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
&m_status);
}
template <typename T>
inline HttpCamera::HttpCamera(llvm::StringRef name,
std::initializer_list<T> urls,
HttpCameraKind kind) {
std::vector<std::string> vec;
vec.reserve(urls.size());
for (const auto& url : urls) vec.emplace_back(url);
m_handle = CreateHttpCamera(
name, vec, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
&m_status);
}
inline HttpCamera::HttpCameraKind HttpCamera::GetHttpCameraKind() const {
m_status = 0;
return static_cast<HttpCameraKind>(
static_cast<int>(::cs::GetHttpCameraKind(m_handle, &m_status)));
}
inline void HttpCamera::SetUrls(llvm::ArrayRef<std::string> urls) {
m_status = 0;
::cs::SetHttpCameraUrls(m_handle, urls, &m_status);
}
template <typename T>
inline void HttpCamera::SetUrls(std::initializer_list<T> urls) {
std::vector<std::string> vec;
vec.reserve(urls.size());
for (const auto& url : urls) vec.emplace_back(url);
m_status = 0;
::cs::SetHttpCameraUrls(m_handle, vec, &m_status);
}
inline std::vector<std::string> HttpCamera::GetUrls() const {
m_status = 0;
return ::cs::GetHttpCameraUrls(m_handle, &m_status);
}
inline std::string AxisCamera::HostToUrl(llvm::StringRef host) {
std::string rv{"http://"};
rv += host;
rv += "/mjpg/video.mjpg";
return rv;
}
inline std::vector<std::string> AxisCamera::HostToUrl(
llvm::ArrayRef<std::string> hosts) {
std::vector<std::string> rv;
rv.reserve(hosts.size());
for (const auto& host : hosts)
rv.emplace_back(HostToUrl(llvm::StringRef{host}));
return rv;
}
template <typename T>
inline std::vector<std::string> AxisCamera::HostToUrl(
std::initializer_list<T> hosts) {
std::vector<std::string> rv;
rv.reserve(hosts.size());
for (const auto& host : hosts)
rv.emplace_back(HostToUrl(llvm::StringRef{host}));
return rv;
}
inline AxisCamera::AxisCamera(llvm::StringRef name, llvm::StringRef host)
: HttpCamera(name, HostToUrl(host), kAxis) {}
inline AxisCamera::AxisCamera(llvm::StringRef name, const char* host)
: HttpCamera(name, HostToUrl(host), kAxis) {}
inline AxisCamera::AxisCamera(llvm::StringRef name, const std::string& host)
: HttpCamera(name, HostToUrl(llvm::StringRef{host}), kAxis) {}
inline AxisCamera::AxisCamera(llvm::StringRef name,
llvm::ArrayRef<std::string> hosts)
: HttpCamera(name, HostToUrl(hosts), kAxis) {}
template <typename T>
inline AxisCamera::AxisCamera(llvm::StringRef name,
std::initializer_list<T> hosts)
: HttpCamera(name, HostToUrl(hosts), kAxis) {}
inline CvSource::CvSource(llvm::StringRef name, const VideoMode& mode) {
m_handle = CreateCvSource(name, mode, &m_status);
}
inline CvSource::CvSource(llvm::StringRef name, VideoMode::PixelFormat format,
int width, int height, int fps) {
m_handle =
CreateCvSource(name, VideoMode{format, width, height, fps}, &m_status);
}
inline void CvSource::PutFrame(cv::Mat& image) {
m_status = 0;
PutSourceFrame(m_handle, image, &m_status);
}
inline void CvSource::NotifyError(llvm::StringRef msg) {
m_status = 0;
NotifySourceError(m_handle, msg, &m_status);
}
inline void CvSource::SetConnected(bool connected) {
m_status = 0;
SetSourceConnected(m_handle, connected, &m_status);
}
inline void CvSource::SetDescription(llvm::StringRef description) {
m_status = 0;
SetSourceDescription(m_handle, description, &m_status);
}
inline VideoProperty CvSource::CreateProperty(llvm::StringRef name,
VideoProperty::Kind kind,
int minimum, int maximum,
int step, int defaultValue,
int value) {
m_status = 0;
return VideoProperty{CreateSourceProperty(
m_handle, name, static_cast<CS_PropertyKind>(static_cast<int>(kind)),
minimum, maximum, step, defaultValue, value, &m_status)};
}
inline VideoProperty CvSource::CreateIntegerProperty(llvm::StringRef name,
int minimum, int maximum,
int step, int defaultValue,
int value) {
m_status = 0;
return VideoProperty{CreateSourceProperty(
m_handle, name, static_cast<CS_PropertyKind>(static_cast<int>(VideoProperty::Kind::kInteger)),
minimum, maximum, step, defaultValue, value, &m_status)};
}
inline VideoProperty CvSource::CreateBooleanProperty(llvm::StringRef name,
bool defaultValue,
bool value) {
m_status = 0;
return VideoProperty{CreateSourceProperty(
m_handle, name, static_cast<CS_PropertyKind>(static_cast<int>(VideoProperty::Kind::kBoolean)),
0, 1, 1, defaultValue ? 1 : 0, value ? 1 : 0, &m_status)};
}
inline VideoProperty CvSource::CreateStringProperty(llvm::StringRef name,
llvm::StringRef value) {
m_status = 0;
auto prop = VideoProperty{CreateSourceProperty(
m_handle, name, static_cast<CS_PropertyKind>(static_cast<int>(VideoProperty::Kind::kString)),
0, 0, 0, 0, 0, &m_status)};
prop.SetString(value);
return prop;
}
inline void CvSource::SetEnumPropertyChoices(
const VideoProperty& property, llvm::ArrayRef<std::string> choices) {
m_status = 0;
SetSourceEnumPropertyChoices(m_handle, property.m_handle, choices, &m_status);
}
template <typename T>
inline void CvSource::SetEnumPropertyChoices(const VideoProperty& property,
std::initializer_list<T> choices) {
std::vector<std::string> vec;
vec.reserve(choices.size());
for (const auto& choice : choices) vec.emplace_back(choice);
m_status = 0;
SetSourceEnumPropertyChoices(m_handle, property.m_handle, vec, &m_status);
}
inline VideoSink::VideoSink(const VideoSink& sink)
: m_handle(sink.m_handle == 0 ? 0 : CopySink(sink.m_handle, &m_status)) {}
inline VideoSink::VideoSink(VideoSink&& other) noexcept : VideoSink() {
swap(*this, other);
}
inline VideoSink& VideoSink::operator=(VideoSink other) noexcept {
swap(*this, other);
return *this;
}
inline VideoSink::~VideoSink() {
m_status = 0;
if (m_handle != 0) ReleaseSink(m_handle, &m_status);
}
inline VideoSink::Kind VideoSink::GetKind() const {
m_status = 0;
return static_cast<VideoSink::Kind>(GetSinkKind(m_handle, &m_status));
}
inline std::string VideoSink::GetName() const {
m_status = 0;
return GetSinkName(m_handle, &m_status);
}
inline std::string VideoSink::GetDescription() const {
m_status = 0;
return GetSinkDescription(m_handle, &m_status);
}
inline void VideoSink::SetSource(VideoSource source) {
m_status = 0;
if (!source)
SetSinkSource(m_handle, 0, &m_status);
else
SetSinkSource(m_handle, source.m_handle, &m_status);
}
inline VideoSource VideoSink::GetSource() const {
m_status = 0;
auto handle = GetSinkSource(m_handle, &m_status);
return VideoSource{handle == 0 ? 0 : CopySource(handle, &m_status)};
}
inline VideoProperty VideoSink::GetSourceProperty(llvm::StringRef name) {
m_status = 0;
return VideoProperty{GetSinkSourceProperty(m_handle, name, &m_status)};
}
inline MjpegServer::MjpegServer(llvm::StringRef name,
llvm::StringRef listenAddress, int port) {
m_handle = CreateMjpegServer(name, listenAddress, port, &m_status);
}
inline std::string MjpegServer::GetListenAddress() const {
m_status = 0;
return cs::GetMjpegServerListenAddress(m_handle, &m_status);
}
inline int MjpegServer::GetPort() const {
m_status = 0;
return cs::GetMjpegServerPort(m_handle, &m_status);
}
inline CvSink::CvSink(llvm::StringRef name) {
m_handle = CreateCvSink(name, &m_status);
}
inline CvSink::CvSink(llvm::StringRef name,
std::function<void(uint64_t time)> processFrame) {
m_handle = CreateCvSinkCallback(name, processFrame, &m_status);
}
inline void CvSink::SetDescription(llvm::StringRef description) {
m_status = 0;
SetSinkDescription(m_handle, description, &m_status);
}
inline uint64_t CvSink::GrabFrame(cv::Mat& image, double timeout) const {
m_status = 0;
return GrabSinkFrameTimeout(m_handle, image, timeout, &m_status);
}
inline uint64_t CvSink::GrabFrameNoTimeout(cv::Mat& image) const {
m_status = 0;
return GrabSinkFrame(m_handle, image, &m_status);
}
inline std::string CvSink::GetError() const {
m_status = 0;
return GetSinkError(m_handle, &m_status);
}
inline void CvSink::SetEnabled(bool enabled) {
m_status = 0;
SetSinkEnabled(m_handle, enabled, &m_status);
}
inline VideoSource VideoEvent::GetSource() const {
CS_Status status = 0;
return VideoSource{sourceHandle == 0 ? 0 : CopySource(sourceHandle, &status)};
}
inline VideoSink VideoEvent::GetSink() const {
CS_Status status = 0;
return VideoSink{sinkHandle == 0 ? 0 : CopySink(sinkHandle, &status)};
}
inline VideoProperty VideoEvent::GetProperty() const {
return VideoProperty{propertyHandle,
static_cast<VideoProperty::Kind>(propertyKind)};
}
inline VideoListener::VideoListener(
std::function<void(const VideoEvent& event)> callback, int eventMask,
bool immediateNotify) {
CS_Status status = 0;
m_handle = AddListener(
[=](const RawEvent& event) {
callback(static_cast<const VideoEvent&>(event));
},
eventMask, immediateNotify, &status);
}
inline VideoListener::VideoListener(VideoListener&& other) noexcept
: VideoListener() {
swap(*this, other);
}
inline VideoListener& VideoListener::operator=(VideoListener&& other) noexcept {
swap(*this, other);
return *this;
}
inline VideoListener::~VideoListener() {
CS_Status status = 0;
if (m_handle != 0) RemoveListener(m_handle, &status);
}
} // namespace cs
#endif /* CSCORE_OO_INL_ */

View File

@@ -0,0 +1,18 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2017-2018 FIRST. 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.cscore;
import org.junit.Test;
public class JNITest {
@Test
public void jniLinkTest() {
// Test to verify that the JNI test link works correctly.
edu.wpi.cscore.CameraServerJNI.getHostname();
}
}

View File

@@ -0,0 +1,22 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2015-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "cscore.h"
#include "gtest/gtest.h"
namespace cs {
class CameraSourceTest : public ::testing::Test {
protected:
CameraSourceTest() {}
};
TEST_F(CameraSourceTest, HTTPCamera) {
auto source = HttpCamera("axis", "http://localhost:8000");
}
} // namespace cs

View File

@@ -0,0 +1,14 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2015-2018 FIRST. 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. */
/*----------------------------------------------------------------------------*/
#include "gtest/gtest.h"
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
int ret = RUN_ALL_TESTS();
return ret;
}