2016-11-28 00:22:15 -08:00
|
|
|
/*----------------------------------------------------------------------------*/
|
2017-08-25 17:48:06 -07:00
|
|
|
/* Copyright (c) 2016-2017 FIRST. All Rights Reserved. */
|
2016-11-28 00:22:15 -08:00
|
|
|
/* 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"
|
|
|
|
|
|
2017-08-25 17:48:06 -07:00
|
|
|
#include <llvm/STLExtras.h>
|
|
|
|
|
#include <support/timestamp.h>
|
|
|
|
|
#include <tcpsockets/TCPConnector.h>
|
2016-11-28 00:22:15 -08:00
|
|
|
|
|
|
|
|
#include "Handle.h"
|
|
|
|
|
#include "JpegUtil.h"
|
|
|
|
|
#include "Log.h"
|
|
|
|
|
#include "Notifier.h"
|
2017-08-25 17:48:06 -07:00
|
|
|
#include "c_util.h"
|
2016-11-28 00:22:15 -08:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
{
|
2017-11-13 09:51:26 -08:00
|
|
|
std::lock_guard<wpi::mutex> lock(m_mutex);
|
2016-11-28 00:22:15 -08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-14 22:27:07 -07:00
|
|
|
#ifdef __linux__
|
2016-11-28 00:22:15 -08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-01-02 23:16:35 -08:00
|
|
|
#endif
|
2016-11-28 00:22:15 -08:00
|
|
|
|
|
|
|
|
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) {
|
2017-11-13 09:51:26 -08:00
|
|
|
std::unique_lock<wpi::mutex> lock(m_mutex);
|
2016-11-28 00:22:15 -08:00
|
|
|
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;
|
2017-08-25 18:10:47 -07:00
|
|
|
wpi::HttpConnection* conn = DeviceStreamConnect(boundary);
|
2016-11-28 00:22:15 -08:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-25 18:10:47 -07:00
|
|
|
wpi::HttpConnection* HttpCameraImpl::DeviceStreamConnect(
|
2016-11-28 00:22:15 -08:00
|
|
|
llvm::SmallVectorImpl<char>& boundary) {
|
|
|
|
|
// Build the request
|
2017-08-25 18:10:47 -07:00
|
|
|
wpi::HttpRequest req;
|
2016-11-28 00:22:15 -08:00
|
|
|
{
|
2017-11-13 09:51:26 -08:00
|
|
|
std::lock_guard<wpi::mutex> lock(m_mutex);
|
2016-11-28 00:22:15 -08:00
|
|
|
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;
|
2017-08-25 18:10:47 -07:00
|
|
|
req = wpi::HttpRequest{m_locations[m_nextLocation++], m_streamSettings};
|
2016-11-28 00:22:15 -08:00
|
|
|
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;
|
|
|
|
|
|
2017-08-25 18:10:47 -07:00
|
|
|
auto connPtr = llvm::make_unique<wpi::HttpConnection>(std::move(stream), 1);
|
|
|
|
|
wpi::HttpConnection* conn = connPtr.get();
|
2016-11-28 00:22:15 -08:00
|
|
|
|
|
|
|
|
// update m_streamConn
|
|
|
|
|
{
|
2017-11-13 09:51:26 -08:00
|
|
|
std::lock_guard<wpi::mutex> lock(m_mutex);
|
2016-11-28 00:22:15 -08:00
|
|
|
m_streamConn = std::move(connPtr);
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-25 18:10:47 -07:00
|
|
|
std::string warn;
|
|
|
|
|
if (!conn->Handshake(req, &warn)) {
|
|
|
|
|
SWARNING(GetName() << ": " << warn);
|
2017-11-13 09:51:26 -08:00
|
|
|
std::lock_guard<wpi::mutex> lock(m_mutex);
|
2016-11-28 00:22:15 -08:00
|
|
|
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
|
|
|
|
|
<< "\"");
|
2017-11-13 09:51:26 -08:00
|
|
|
std::lock_guard<wpi::mutex> lock(m_mutex);
|
2016-11-28 00:22:15 -08:00
|
|
|
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");
|
2017-11-13 09:51:26 -08:00
|
|
|
std::lock_guard<wpi::mutex> lock(m_mutex);
|
2016-11-28 00:22:15 -08:00
|
|
|
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;
|
|
|
|
|
}
|
2017-08-14 22:27:07 -07:00
|
|
|
|
2016-11-28 00:22:15 -08:00
|
|
|
// 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 (;;) {
|
2017-08-25 18:10:47 -07:00
|
|
|
wpi::HttpRequest req;
|
2016-11-28 00:22:15 -08:00
|
|
|
{
|
2017-11-13 09:51:26 -08:00
|
|
|
std::unique_lock<wpi::mutex> lock(m_mutex);
|
2016-11-28 00:22:15 -08:00
|
|
|
m_settingsCond.wait(lock, [=] {
|
|
|
|
|
return !m_active || (m_prefLocation != -1 && !m_settings.empty());
|
|
|
|
|
});
|
|
|
|
|
if (!m_active) break;
|
|
|
|
|
|
|
|
|
|
// Build the request
|
2017-08-25 18:10:47 -07:00
|
|
|
req = wpi::HttpRequest{m_locations[m_prefLocation], m_settings};
|
2016-11-28 00:22:15 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DeviceSendSettings(req);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SDEBUG("Settings Thread exiting");
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-25 18:10:47 -07:00
|
|
|
void HttpCameraImpl::DeviceSendSettings(wpi::HttpRequest& req) {
|
2016-11-28 00:22:15 -08:00
|
|
|
// Try to connect
|
|
|
|
|
auto stream = wpi::TCPConnector::connect(req.host.c_str(), req.port,
|
|
|
|
|
Logger::GetInstance(), 1);
|
|
|
|
|
|
|
|
|
|
if (!m_active || !stream) return;
|
|
|
|
|
|
2017-08-25 18:10:47 -07:00
|
|
|
auto connPtr = llvm::make_unique<wpi::HttpConnection>(std::move(stream), 1);
|
|
|
|
|
wpi::HttpConnection* conn = connPtr.get();
|
2016-11-28 00:22:15 -08:00
|
|
|
|
|
|
|
|
// update m_settingsConn
|
|
|
|
|
{
|
2017-11-13 09:51:26 -08:00
|
|
|
std::lock_guard<wpi::mutex> lock(m_mutex);
|
2016-11-28 00:22:15 -08:00
|
|
|
m_settingsConn = std::move(connPtr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Just need a handshake as settings are sent via GET parameters
|
2017-08-25 18:10:47 -07:00
|
|
|
std::string warn;
|
|
|
|
|
if (!conn->Handshake(req, &warn)) SWARNING(GetName() << ": " << warn);
|
2016-11-28 00:22:15 -08:00
|
|
|
|
|
|
|
|
conn->stream->close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CS_HttpCameraKind HttpCameraImpl::GetKind() const {
|
2017-11-13 09:51:26 -08:00
|
|
|
std::lock_guard<wpi::mutex> lock(m_mutex);
|
2016-11-28 00:22:15 -08:00
|
|
|
return m_kind;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool HttpCameraImpl::SetUrls(llvm::ArrayRef<std::string> urls,
|
|
|
|
|
CS_Status* status) {
|
2017-08-25 18:10:47 -07:00
|
|
|
std::vector<wpi::HttpLocation> locations;
|
2016-11-28 00:22:15 -08:00
|
|
|
for (const auto& url : urls) {
|
|
|
|
|
bool error = false;
|
2017-08-25 18:10:47 -07:00
|
|
|
std::string errorMsg;
|
|
|
|
|
locations.emplace_back(url, &error, &errorMsg);
|
2016-11-28 00:22:15 -08:00
|
|
|
if (error) {
|
2017-08-25 18:10:47 -07:00
|
|
|
SERROR(GetName() << ": " << errorMsg);
|
2016-11-28 00:22:15 -08:00
|
|
|
*status = CS_BAD_URL;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-13 09:51:26 -08:00
|
|
|
std::lock_guard<wpi::mutex> lock(m_mutex);
|
2016-11-28 00:22:15 -08:00
|
|
|
m_locations.swap(locations);
|
|
|
|
|
m_nextLocation = 0;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> HttpCameraImpl::GetUrls() const {
|
2017-11-13 09:51:26 -08:00
|
|
|
std::lock_guard<wpi::mutex> lock(m_mutex);
|
2016-11-28 00:22:15 -08:00
|
|
|
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 {
|
2017-11-13 09:51:26 -08:00
|
|
|
std::lock_guard<wpi::mutex> lock(m_mutex);
|
2016-11-28 00:22:15 -08:00
|
|
|
m_propertyData.emplace_back(llvm::make_unique<PropertyData>(
|
|
|
|
|
name, httpParam, viaSettings, kind, minimum, maximum, step, defaultValue,
|
|
|
|
|
value));
|
|
|
|
|
|
|
|
|
|
Notifier::GetInstance().NotifySourceProperty(
|
2017-01-02 19:43:04 -08:00
|
|
|
*this, CS_SOURCE_PROPERTY_CREATED, name, m_propertyData.size() + 1, kind,
|
|
|
|
|
value, llvm::StringRef{});
|
2016-11-28 00:22:15 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
void HttpCameraImpl::CreateEnumProperty(
|
|
|
|
|
llvm::StringRef name, llvm::StringRef httpParam, bool viaSettings,
|
|
|
|
|
int defaultValue, int value, std::initializer_list<T> choices) const {
|
2017-11-13 09:51:26 -08:00
|
|
|
std::lock_guard<wpi::mutex> lock(m_mutex);
|
2016-11-28 00:22:15 -08:00
|
|
|
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(
|
2017-01-02 19:43:04 -08:00
|
|
|
*this, CS_SOURCE_PROPERTY_CREATED, name, m_propertyData.size() + 1,
|
2016-11-28 00:22:15 -08:00
|
|
|
CS_PROP_ENUM, value, llvm::StringRef{});
|
|
|
|
|
Notifier::GetInstance().NotifySourceProperty(
|
2017-01-02 19:43:04 -08:00
|
|
|
*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED, name,
|
|
|
|
|
m_propertyData.size() + 1, CS_PROP_ENUM, value, llvm::StringRef{});
|
2016-11-28 00:22:15 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<PropertyImpl> HttpCameraImpl::CreateEmptyProperty(
|
|
|
|
|
llvm::StringRef name) const {
|
|
|
|
|
return llvm::make_unique<PropertyData>(name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool HttpCameraImpl::CacheProperties(CS_Status* status) const {
|
2017-11-13 09:51:26 -08:00
|
|
|
std::lock_guard<wpi::mutex> lock(m_mutex);
|
2016-11-28 00:22:15 -08:00
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-01 14:24:13 -08:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-28 00:22:15 -08:00
|
|
|
bool HttpCameraImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
|
|
|
|
|
if (mode.pixelFormat != VideoMode::kMJPEG) return false;
|
2017-11-13 09:51:26 -08:00
|
|
|
std::lock_guard<wpi::mutex> lock(m_mutex);
|
2016-11-28 00:22:15 -08:00
|
|
|
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
|
2017-11-13 09:51:26 -08:00
|
|
|
std::lock_guard<wpi::mutex> lock(m_mutex);
|
2016-11-28 00:22:15 -08:00
|
|
|
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();
|
2017-08-25 17:48:06 -07:00
|
|
|
for (size_t i = 0; i < urls.size(); ++i) out[i] = cs::ConvertToC(urls[i]);
|
2016-11-28 00:22:15 -08:00
|
|
|
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"
|