From 4800c201e4f42e63707d76965bb4dbceeaa1f5b9 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Sat, 24 Dec 2016 21:05:08 -0600 Subject: [PATCH] Add Axis camera creation functions and Http camera NT publishing. (#420) --- wpilibc/athena/include/CameraServer.h | 97 +++++++++++ wpilibc/athena/include/CameraServer.inc | 30 ++++ wpilibc/athena/src/CameraServer.cpp | 133 ++++++++++++--- .../edu/wpi/first/wpilibj/CameraServer.java | 155 ++++++++++++++---- 4 files changed, 359 insertions(+), 56 deletions(-) create mode 100644 wpilibc/athena/include/CameraServer.inc diff --git a/wpilibc/athena/include/CameraServer.h b/wpilibc/athena/include/CameraServer.h index d5dcb2f88c..1d60db2a8c 100644 --- a/wpilibc/athena/include/CameraServer.h +++ b/wpilibc/athena/include/CameraServer.h @@ -84,6 +84,100 @@ class CameraServer : public ErrorBase { */ void StartAutomaticCapture(const cs::VideoSource& camera); + /** + * Adds an Axis IP camera. + * + *

This overload calls {@link #AddAxisCamera(String, String)} with + * name "Axis Camera". + * + * @param host Camera host IP or DNS name (e.g. "10.x.y.11") + */ + cs::AxisCamera AddAxisCamera(llvm::StringRef host); + + /** + * Adds an Axis IP camera. + * + *

This overload calls {@link #AddAxisCamera(String, String)} with + * name "Axis Camera". + * + * @param host Camera host IP or DNS name (e.g. "10.x.y.11") + */ + cs::AxisCamera AddAxisCamera(const char* host); + + /** + * Adds an Axis IP camera. + * + *

This overload calls {@link #AddAxisCamera(String, String)} with + * name "Axis Camera". + * + * @param host Camera host IP or DNS name (e.g. "10.x.y.11") + */ + cs::AxisCamera AddAxisCamera(const std::string& host); + + /** + * Adds an Axis IP camera. + * + *

This overload calls {@link #AddAxisCamera(String, String[])} with + * name "Axis Camera". + * + * @param hosts Array of Camera host IPs/DNS names + */ + cs::AxisCamera AddAxisCamera(llvm::ArrayRef hosts); + + /** + * Adds an Axis IP camera. + * + *

This overload calls {@link #AddAxisCamera(String, String[])} with + * name "Axis Camera". + * + * @param hosts Array of Camera host IPs/DNS names + */ + template + cs::AxisCamera AddAxisCamera(std::initializer_list hosts); + + /** + * Adds an Axis IP camera. + * + * @param name The name to give the camera + * @param host Camera host IP or DNS name (e.g. "10.x.y.11") + */ + cs::AxisCamera AddAxisCamera(llvm::StringRef name, llvm::StringRef host); + + /** + * Adds an Axis IP camera. + * + * @param name The name to give the camera + * @param host Camera host IP or DNS name (e.g. "10.x.y.11") + */ + cs::AxisCamera AddAxisCamera(llvm::StringRef name, const char* host); + + /** + * Adds an Axis IP camera. + * + * @param name The name to give the camera + * @param host Camera host IP or DNS name (e.g. "10.x.y.11") + */ + cs::AxisCamera AddAxisCamera(llvm::StringRef name, const std::string& host); + + /** + * Adds an Axis IP camera. + * + * @param name The name to give the camera + * @param hosts Array of Camera host IPs/DNS names + */ + cs::AxisCamera AddAxisCamera(llvm::StringRef name, + llvm::ArrayRef hosts); + + /** + * Adds an Axis IP camera. + * + * @param name The name to give the camera + * @param hosts Array of Camera host IPs/DNS names + */ + template + cs::AxisCamera AddAxisCamera(llvm::StringRef name, + std::initializer_list hosts); + /** * Get OpenCV access to the primary camera feed. This allows you to * get images from the camera for image processing on the roboRIO. @@ -176,6 +270,7 @@ class CameraServer : public ErrorBase { CameraServer(); std::shared_ptr GetSourceTable(CS_Source source); + std::vector GetSinkStreamValues(CS_Sink sink); void UpdateStreamValues(); static constexpr char const* kPublishName = "/CameraPublisher"; @@ -193,3 +288,5 @@ class CameraServer : public ErrorBase { }; } // namespace frc + +#include "CameraServer.inc" diff --git a/wpilibc/athena/include/CameraServer.inc b/wpilibc/athena/include/CameraServer.inc new file mode 100644 index 0000000000..54e3639194 --- /dev/null +++ b/wpilibc/athena/include/CameraServer.inc @@ -0,0 +1,30 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2016. 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. */ +/*----------------------------------------------------------------------------*/ + +#pragma once + +#include +#include + +namespace frc { + +template +inline cs::AxisCamera CameraServer::AddAxisCamera( + std::initializer_list hosts) { + return AddAxisCamera("Axis Camera", hosts); +} + +template +inline cs::AxisCamera CameraServer::AddAxisCamera( + llvm::StringRef name, std::initializer_list hosts) { + std::vector vec; + vec.reserve(hosts.size()); + for (const auto& host : hosts) vec.emplace_back(host); + return AddAxisCamera(name, vec); +} + +} // namespace frc diff --git a/wpilibc/athena/src/CameraServer.cpp b/wpilibc/athena/src/CameraServer.cpp index 02539781bf..f82ded3db6 100644 --- a/wpilibc/athena/src/CameraServer.cpp +++ b/wpilibc/athena/src/CameraServer.cpp @@ -34,7 +34,8 @@ static llvm::StringRef MakeSourceValue(CS_Source source, case cs::VideoSource::kHttp: { llvm::StringRef prefix{"ip:"}; buf.append(prefix.begin(), prefix.end()); - // TODO + auto urls = cs::GetHttpCameraUrls(source, &status); + if (!urls.empty()) buf.append(urls[0].begin(), urls[0].end()); break; } case cs::VideoSource::kCv: @@ -61,41 +62,78 @@ std::shared_ptr CameraServer::GetSourceTable(CS_Source source) { return m_tables.lookup(source); } +std::vector CameraServer::GetSinkStreamValues(CS_Sink sink) { + CS_Status status = 0; + + // Ignore all but MjpegServer + if (cs::GetSinkKind(sink, &status) != CS_SINK_MJPEG) + return std::vector{}; + + // Get port + int port = cs::GetMjpegServerPort(sink, &status); + + // Generate values + std::vector values; + auto listenAddress = cs::GetMjpegServerListenAddress(sink, &status); + if (!listenAddress.empty()) { + // If a listen address is specified, only use that + values.emplace_back(MakeStreamValue(listenAddress, port)); + } else { + // Otherwise generate for hostname and all interface addresses + values.emplace_back(MakeStreamValue(cs::GetHostname() + ".local", port)); + + for (const auto& addr : m_addresses) { + if (addr == "127.0.0.1") continue; // ignore localhost + values.emplace_back(MakeStreamValue(addr, port)); + } + } + + return values; +} + +static std::vector GetSourceStreamValues(CS_Source source) { + CS_Status status = 0; + + // Ignore all but HttpCamera + if (cs::GetSourceKind(source, &status) != CS_SOURCE_HTTP) + return std::vector{}; + + // Generate values + auto values = cs::GetHttpCameraUrls(source, &status); + for (auto& value : values) value = "mjpg:" + value; + + // Set table value + return values; +} + void CameraServer::UpdateStreamValues() { std::lock_guard lock(m_mutex); // Over all the sinks... for (const auto& i : m_sinks) { CS_Status status = 0; - // Ignore all but MjpegServer - if (i.second.GetKind() != cs::VideoSink::kMjpeg) continue; CS_Sink sink = i.second.GetHandle(); // Get the source's subtable (if none exists, we're done) CS_Source source = cs::GetSinkSource(sink, &status); auto table = m_tables.lookup(source); - if (!table) continue; - - // Get port - int port = cs::GetMjpegServerPort(sink, &status); - - // Generate values - std::vector values; - auto listenAddress = cs::GetMjpegServerListenAddress(sink, &status); - if (!listenAddress.empty()) { - // If a listen address is specified, only use that - values.emplace_back(MakeStreamValue(listenAddress, port)); - } else { - // Otherwise generate for hostname and all interface addresses - values.emplace_back(MakeStreamValue(cs::GetHostname() + ".local", port)); - - for (const auto& addr : m_addresses) { - if (addr == "127.0.0.1") continue; // ignore localhost - values.emplace_back(MakeStreamValue(addr, port)); - } + if (table) { + // Set table value + auto values = GetSinkStreamValues(sink); + if (!values.empty()) table->PutStringArray("streams", values); } + } - // Set table value - table->PutStringArray("streams", values); + // Over all the sources... + for (const auto& i : m_sources) { + CS_Source source = i.second.GetHandle(); + + // Get the source's subtable (if none exists, we're done) + auto table = m_tables.lookup(source); + if (table) { + // Set table value + auto values = GetSourceStreamValues(source); + if (!values.empty()) table->PutStringArray("streams", values); + } } } @@ -129,7 +167,8 @@ CameraServer::CameraServer() cs::GetSourceDescription(event.sourceHandle, descBuf, &status)); table->PutBoolean("connected", cs::IsSourceConnected( event.sourceHandle, &status)); - table->PutStringArray("streams", std::vector{}); + table->PutStringArray("streams", + GetSourceStreamValues(event.sourceHandle)); break; } case cs::VideoEvent::kSourceDestroyed: { @@ -227,6 +266,50 @@ cs::UsbCamera CameraServer::StartAutomaticCapture(llvm::StringRef name, return camera; } +cs::AxisCamera CameraServer::AddAxisCamera(llvm::StringRef host) { + return AddAxisCamera("Axis Camera", host); +} + +cs::AxisCamera CameraServer::AddAxisCamera(const char* host) { + return AddAxisCamera("Axis Camera", host); +} + +cs::AxisCamera CameraServer::AddAxisCamera(const std::string& host) { + return AddAxisCamera("Axis Camera", host); +} + +cs::AxisCamera CameraServer::AddAxisCamera(llvm::ArrayRef hosts) { + return AddAxisCamera("Axis Camera", hosts); +} + +cs::AxisCamera CameraServer::AddAxisCamera(llvm::StringRef name, + llvm::StringRef host) { + cs::AxisCamera camera{name, host}; + AddCamera(camera); + return camera; +} + +cs::AxisCamera CameraServer::AddAxisCamera(llvm::StringRef name, + const char* host) { + cs::AxisCamera camera{name, host}; + AddCamera(camera); + return camera; +} + +cs::AxisCamera CameraServer::AddAxisCamera(llvm::StringRef name, + const std::string& host) { + cs::AxisCamera camera{name, host}; + AddCamera(camera); + return camera; +} + +cs::AxisCamera CameraServer::AddAxisCamera(llvm::StringRef name, + llvm::ArrayRef hosts) { + cs::AxisCamera camera{name, hosts}; + AddCamera(camera); + return camera; +} + void CameraServer::StartAutomaticCapture(const cs::VideoSource& camera) { llvm::SmallString<64> name{"serve_"}; name += camera.GetName(); diff --git a/wpilibj/src/athena/java/edu/wpi/first/wpilibj/CameraServer.java b/wpilibj/src/athena/java/edu/wpi/first/wpilibj/CameraServer.java index 8f60d3c300..bbf5a9fa93 100644 --- a/wpilibj/src/athena/java/edu/wpi/first/wpilibj/CameraServer.java +++ b/wpilibj/src/athena/java/edu/wpi/first/wpilibj/CameraServer.java @@ -7,6 +7,7 @@ package edu.wpi.first.wpilibj; +import edu.wpi.cscore.AxisCamera; import edu.wpi.cscore.CameraServerJNI; import edu.wpi.cscore.CvSink; import edu.wpi.cscore.CvSource; @@ -65,9 +66,14 @@ public class CameraServer { switch (VideoSource.getKindFromInt(CameraServerJNI.getSourceKind(source))) { case kUsb: return "usb:" + CameraServerJNI.getUsbCameraPath(source); - case kHttp: - // TODO - return "ip:"; + case kHttp: { + String[] urls = CameraServerJNI.getHttpCameraUrls(source); + if (urls.length > 0) { + return "ip:" + urls[0]; + } else { + return "ip:"; + } + } case kCv: // FIXME: Should be "cv:", but LabVIEW dashboard requires "usb:". // https://github.com/wpilibsuite/allwpilib/issues/407 @@ -87,45 +93,84 @@ public class CameraServer { return m_tables.get(source); } + @SuppressWarnings({"JavadocMethod", "PMD.AvoidUsingHardCodedIP"}) + private synchronized String[] getSinkStreamValues(int sink) { + // Ignore all but MjpegServer + if (VideoSink.getKindFromInt(CameraServerJNI.getSinkKind(sink)) != VideoSink.Kind.kMjpeg) { + return new String[0]; + } + + // Get port + int port = CameraServerJNI.getMjpegServerPort(sink); + + // Generate values + ArrayList values = new ArrayList(m_addresses.length + 1); + String listenAddress = CameraServerJNI.getMjpegServerListenAddress(sink); + if (!listenAddress.isEmpty()) { + // If a listen address is specified, only use that + values.add(makeStreamValue(listenAddress, port)); + } else { + // Otherwise generate for hostname and all interface addresses + values.add(makeStreamValue(CameraServerJNI.getHostname() + ".local", port)); + for (String addr : m_addresses) { + if (addr.equals("127.0.0.1")) { + continue; // ignore localhost + } + values.add(makeStreamValue(addr, port)); + } + } + + return values.toArray(new String[0]); + } + + @SuppressWarnings("JavadocMethod") + private static String[] getSourceStreamValues(int source) { + // Ignore all but HttpCamera + if (VideoSource.getKindFromInt(CameraServerJNI.getSourceKind(source)) + != VideoSource.Kind.kHttp) { + return new String[0]; + } + + // Generate values + String[] values = CameraServerJNI.getHttpCameraUrls(source); + for (int j = 0; j < values.length; j++) { + values[j] = "mjpg:" + values[j]; + } + + return values; + } + @SuppressWarnings({"JavadocMethod", "PMD.AvoidUsingHardCodedIP"}) private synchronized void updateStreamValues() { // Over all the sinks... for (VideoSink i : m_sinks.values()) { - // Ignore all but MjpegServer - if (i.getKind() != VideoSink.Kind.kMjpeg) { - continue; - } int sink = i.getHandle(); // Get the source's subtable (if none exists, we're done) int source = CameraServerJNI.getSinkSource(sink); ITable table = m_tables.get(source); - if (table == null) { - continue; - } - - // Get port - int port = CameraServerJNI.getMjpegServerPort(sink); - - // Generate values - ArrayList values = new ArrayList(m_addresses.length + 1); - String listenAddress = CameraServerJNI.getMjpegServerListenAddress(sink); - if (!listenAddress.isEmpty()) { - // If a listen address is specified, only use that - values.add(makeStreamValue(listenAddress, port)); - } else { - // Otherwise generate for hostname and all interface addresses - values.add(makeStreamValue(CameraServerJNI.getHostname() + ".local", port)); - for (String addr : m_addresses) { - if (addr.equals("127.0.0.1")) { - continue; // ignore localhost - } - values.add(makeStreamValue(addr, port)); + if (table != null) { + // Set table value + String[] values = getSinkStreamValues(sink); + if (values.length > 0) { + table.putStringArray("streams", values); } } + } - // Set table value - table.putStringArray("streams", values.toArray(new String[0])); + // Over all the sources... + for (VideoSource i : m_sources.values()) { + int source = i.getHandle(); + + // Get the source's subtable (if none exists, we're done) + ITable table = m_tables.get(source); + if (table != null) { + // Set table value + String[] values = getSourceStreamValues(source); + if (values.length > 0) { + table.putStringArray("streams", values); + } + } } } @@ -157,7 +202,7 @@ public class CameraServer { table.putString("description", CameraServerJNI.getSourceDescription(event.sourceHandle)); table.putBoolean("connected", CameraServerJNI.isSourceConnected(event.sourceHandle)); - table.putStringArray("streams", new String[0]); + table.putStringArray("streams", getSourceStreamValues(event.sourceHandle)); break; } case kSourceDestroyed: { @@ -300,6 +345,54 @@ public class CameraServer { server.setSource(camera); } + /** + * Adds an Axis IP camera. + * + *

This overload calls {@link #addAxisCamera(String, String)} with + * name "Axis Camera". + * + * @param host Camera host IP or DNS name (e.g. "10.x.y.11") + */ + public AxisCamera addAxisCamera(String host) { + return addAxisCamera("Axis Camera", host); + } + + /** + * Adds an Axis IP camera. + * + *

This overload calls {@link #addAxisCamera(String, String[])} with + * name "Axis Camera". + * + * @param hosts Array of Camera host IPs/DNS names + */ + public AxisCamera addAxisCamera(String[] hosts) { + return addAxisCamera("Axis Camera", hosts); + } + + /** + * Adds an Axis IP camera. + * + * @param name The name to give the camera + * @param host Camera host IP or DNS name (e.g. "10.x.y.11") + */ + public AxisCamera addAxisCamera(String name, String host) { + AxisCamera camera = new AxisCamera(name, host); + addCamera(camera); + return camera; + } + + /** + * Adds an Axis IP camera. + * + * @param name The name to give the camera + * @param hosts Array of Camera host IPs/DNS names + */ + public AxisCamera addAxisCamera(String name, String[] hosts) { + AxisCamera camera = new AxisCamera(name, hosts); + addCamera(camera); + return camera; + } + /** * Get OpenCV access to the primary camera feed. This allows you to * get images from the camera for image processing on the roboRIO.