feat: SchemaChecker & V4L2 enhancement (#734)

* libjpeg-turbo, QJsonSchemaChecker, V4L2 width/height/fps

Signed-off-by: Paulchen-Panther <Paulchen-Panter@protonmail.com>

* Implement hyperion-v4l cli args

* Apply v4l2 settings during runtime

* feat: Provide minimum values for input restriction

* fix: merge mess

Co-authored-by: brindosch <edeltraud70@gmx.de>
This commit is contained in:
Paulchen Panther 2020-03-27 23:13:58 +01:00 committed by GitHub
parent 20a5e5dc06
commit 662872dafe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 363 additions and 121 deletions

View File

@ -298,22 +298,31 @@ find_package(libusb-1.0 REQUIRED)
find_package(Threads REQUIRED)
add_definitions(${QT_DEFINITIONS})
# Add jpeg library
# Add JPEG library
if (ENABLE_V4L2)
# Turbo JPEG
find_package(TurboJPEG)
if (TURBOJPEG_FOUND)
add_definitions(-DHAVE_TURBO_JPEG)
message( STATUS "Using Turbo JPEG library: ${TurboJPEG_LIBRARY}")
include_directories(${TurboJPEG_INCLUDE_DIRS})
else()
# System JPEG
find_package(JPEG)
if (JPEG_FOUND)
add_definitions(-DHAVE_JPEG)
message( STATUS "Using JPEG library: ${JPEG_LIBRARIES}")
message( STATUS "Using system JPEG library: ${JPEG_LIBRARIES}")
include_directories(${JPEG_INCLUDE_DIR})
else()
message( STATUS "JPEG library not found, MJPEG camera format won't work in V4L2 grabber.")
endif()
endif()
endif (TurboJPEG_FOUND)
# TODO[TvdZ]: This linking directory should only be added if we are cross compiling
#if(NOT APPLE)
# link_directories(${CMAKE_FIND_ROOT_PATH}/lib/arm-linux-gnueabihf)
#endif()
if (TURBOJPEG_FOUND OR JPEG_FOUND)
add_definitions(-DHAVE_JPEG_DECODER)
endif()
endif()
if(APPLE)
set(CMAKE_EXE_LINKER_FLAGS "-framework CoreGraphics")

View File

@ -40,7 +40,7 @@ wget -qN https://raw.github.com/hyperion-project/hyperion.ng/master/bin/scripts/
```
sudo apt-get update
sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libqt5sql5-sqlite libssl-dev
sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libqt5sql5-sqlite libssl-dev
```
**on RPI you need the videocore IV headers**

View File

@ -4,14 +4,14 @@ Use a clean Raspbian Stretch Lite (on target) and Ubuntu 18/19 (on host) to exec
## On the Target system (here Raspberry Pi)
Install required additional packages.
```
sudo apt-get install qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libqt5sql5-sqlite aptitude show qt5-default rsync
sudo apt-get install qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libqt5sql5-sqlite aptitude show qt5-default rsync
```
## On the Host system (here Ubuntu)
Update the Ubuntu environment to the latest stage and install required additional packages.
```
sudo apt-get update
sudo apt-get upgrade
sudo apt-get -qq -y install git rsync cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libqt5sql5-sqlite
sudo apt-get -qq -y install git rsync cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libqt5sql5-sqlite
```
Refine the target IP or hostname, plus userID as required and set-up cross-compilation environment:

View File

@ -5,7 +5,7 @@ CFG="${2:-Release}"
INST="$( [ "${3:-}" = "install" ] && echo true || echo false )"
sudo apt-get update
sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libssl-dev || exit 1
sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev libturbojpeg0-dev python3-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libssl-dev || exit 1
if [ -e /dev/vc-cma -a -e /dev/vc-mem ]
then

33
cmake/FindTurboJPEG.cmake Normal file
View File

@ -0,0 +1,33 @@
# FindTurboJPEG.cmake
# TURBOJPEG_FOUND
# TurboJPEG_INCLUDE_DIRS
# TurboJPEG_LIBRARY
find_path(TurboJPEG_INCLUDE_DIRS
NAMES turbojpeg.h
PATH_SUFFIXES include
)
find_library(TurboJPEG_LIBRARY
NAMES turbojpeg turbojpeg-static
PATH_SUFFIXES bin lib
)
if(TurboJPEG_INCLUDE_DIRS AND TurboJPEG_LIBRARY)
include(CheckCSourceCompiles)
include(CMakePushCheckState)
cmake_push_check_state(RESET)
list(APPEND CMAKE_REQUIRED_INCLUDES ${TurboJPEG_INCLUDE_DIRS})
list(APPEND CMAKE_REQUIRED_LIBRARIES ${TurboJPEG_LIBRARY})
check_c_source_compiles("#include <turbojpeg.h>\nint main(void) { tjhandle h=tjInitCompress(); return 0; }" TURBOJPEG_WORKS)
cmake_pop_check_state()
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(TurboJpeg
FOUND_VAR TURBOJPEG_FOUND
REQUIRED_VARS TurboJPEG_LIBRARY TurboJPEG_INCLUDE_DIRS TURBOJPEG_WORKS
TurboJPEG_INCLUDE_DIRS TurboJPEG_LIBRARY
)

View File

@ -101,6 +101,8 @@
/// Configuration for the embedded V4L2 grabber
/// * device : V4L2 Device to use [default="auto"] (Auto detection)
/// * width : The width of the grabbed frames (pixels) [default=0]
/// * height : The height of the grabbed frames (pixels) [default=0]
/// * standard : Video standard (PAL/NTSC/SECAM/NO_CHANGE) [default="NO_CHANGE"]
/// * sizeDecimation : Size decimation factor [default=8]
/// * cropLeft : Cropping from the left [default=0]
@ -118,6 +120,8 @@
"grabberV4L2" :
{
"device" : "auto",
"width" : 0,
"height" : 0,
"standard" : "NO_CHANGE",
"sizeDecimation" : 8,
"priority" : 240,

View File

@ -60,6 +60,9 @@
"grabberV4L2" :
{
"device" : "auto",
"width" : 0,
"height" : 0,
"fps" : 15,
"standard" : "NO_CHANGE",
"sizeDecimation" : 8,
"cropLeft" : 0,

View File

@ -15,13 +15,23 @@
#include <grabber/VideoStandard.h>
#include <utils/Components.h>
#ifdef HAVE_JPEG
// general JPEG decoder includes
#ifdef HAVE_JPEG_DECODER
#include <QImage>
#include <QColor>
#endif
// System JPEG decoder
#ifdef HAVE_JPEG
#include <jpeglib.h>
#include <csetjmp>
#endif
// TurboJPEG decoder
#ifdef HAVE_TURBO_JPEG
#include <turbojpeg.h>
#endif
/// Capture class for V4L2 devices
///
/// @see http://linuxtv.org/downloads/v4l-dvb-apis/capture-example.html
@ -31,6 +41,9 @@ class V4L2Grabber : public Grabber
public:
V4L2Grabber(const QString & device,
const unsigned width,
const unsigned height,
const unsigned fps,
VideoStandard videoStandard,
PixelFormat pixelFormat,
int pixelDecimation
@ -46,11 +59,6 @@ public:
int grabFrame(Image<ColorRgb> &);
///
/// @brief overwrite Grabber.h implementation, as v4l doesn't use width/height
///
virtual void setWidthHeight(){};
///
/// @brief set new PixelDecimation value to ImageResampler
/// @param pixelDecimation The new pixelDecimation value
@ -84,6 +92,16 @@ public:
///
virtual void setDeviceVideoStandard(QString device, VideoStandard videoStandard);
///
/// @brief overwrite Grabber.h implementation
///
virtual bool setFramerate(int fps);
///
/// @brief overwrite Grabber.h implementation
///
virtual bool setWidthHeight(int width, int height);
public slots:
bool start();
@ -173,6 +191,11 @@ private:
errorManager* _error;
#endif
#ifdef HAVE_TURBO_JPEG
tjhandle _decompress = nullptr;
int _subsamp;
#endif
private:
QString _deviceName;
std::map<QString,QString> _v4lDevices;

View File

@ -9,6 +9,9 @@ class V4L2Wrapper : public GrabberWrapper
public:
V4L2Wrapper(const QString & device,
const unsigned grabWidth,
const unsigned grabHeight,
const unsigned fps,
VideoStandard videoStandard,
PixelFormat pixelFormat,
int pixelDecimation );

View File

@ -40,6 +40,12 @@ public:
///
virtual bool setWidthHeight(int width, int height);
///
/// @brief Apply new framerate (used from v4l)
/// @param fps framesPerSecond
///
virtual bool setFramerate(int fps);
///
/// @brief Apply new pixelDecimation (used from x11 and qt)
///
@ -111,6 +117,8 @@ protected:
/// Height of the captured snapshot [pixels]
int _height;
int _fps;
// number of pixels to crop after capturing
int _cropLeft, _cropRight, _cropTop, _cropBottom;

View File

@ -12,7 +12,7 @@ enum PixelFormat {
PIXELFORMAT_BGR24,
PIXELFORMAT_RGB32,
PIXELFORMAT_BGR32,
#ifdef HAVE_JPEG
#ifdef HAVE_JPEG_DECODER
PIXELFORMAT_MJPEG,
#endif
PIXELFORMAT_NO_CHANGE
@ -47,7 +47,7 @@ inline PixelFormat parsePixelFormat(QString pixelFormat)
{
return PIXELFORMAT_BGR32;
}
#ifdef HAVE_JPEG
#ifdef HAVE_JPEG_DECODER
else if (pixelFormat == "mjpeg")
{
return PIXELFORMAT_MJPEG;

View File

@ -187,6 +187,14 @@ private:
///
void checkEnum(const QJsonValue & value, const QJsonValue & schema, const QJsonValue & defaultValue);
///
/// @brief Return the "default" value as string. If not found, an empty string is output
///
/// @param value The JSON value to search
/// @return The "default" value as string
///
QString getDefaultValue(const QJsonValue & value);
private:
/// The schema of the entire json-configuration
QJsonObject _qSchema;

View File

@ -41,6 +41,37 @@ public:
return createValue(schema, ignoreRequired);
}
static QString getDefaultValue(const QJsonValue & value)
{
QString ret;
switch (value.type())
{
case QJsonValue::Array:
{
for (const QJsonValue &v : value.toArray())
{
ret = getDefaultValue(v);
if (!ret.isEmpty())
break;
}
break;
}
case QJsonValue::Object:
ret = getDefaultValue(value.toObject().find("default").value());
break;
case QJsonValue::Bool:
return value.toBool() ? "True" : "False";
case QJsonValue::Double:
return QString::number(value.toDouble());
case QJsonValue::String:
return value.toString();
case QJsonValue::Null:
case QJsonValue::Undefined:
break;
}
return ret;
}
private:
static QJsonValue createValue(QJsonValue schema, bool ignoreRequired)

View File

@ -11,6 +11,8 @@ target_link_libraries(v4l2-grabber
${QT_LIBRARIES}
)
if (JPEG_FOUND)
if(TURBOJPEG_FOUND)
target_link_libraries(v4l2-grabber ${TurboJPEG_LIBRARY})
elseif (JPEG_FOUND)
target_link_libraries(v4l2-grabber ${JPEG_LIBRARY})
endif()
endif(TURBOJPEG_FOUND)

View File

@ -27,6 +27,9 @@
#define CLEAR(x) memset(&(x), 0, sizeof(x))
V4L2Grabber::V4L2Grabber(const QString & device
, const unsigned width
, const unsigned height
, const unsigned fps
, VideoStandard videoStandard
, PixelFormat pixelFormat
, int pixelDecimation
@ -59,6 +62,8 @@ V4L2Grabber::V4L2Grabber(const QString & device
getV4Ldevices();
// init
setWidthHeight(width, height);
setFramerate(fps);
setDeviceVideoStandard(device, videoStandard);
}
@ -561,7 +566,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32;
break;
#ifdef HAVE_JPEG
#ifdef HAVE_JPEG_DECODER
case PIXELFORMAT_MJPEG:
{
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
@ -576,53 +581,41 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
break;
}
// get maximum video devices resolution
__u32 max_width = 0, max_height = 0;
struct v4l2_fmtdesc fmtdesc;
CLEAR(fmtdesc);
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmtdesc.index = 0;
while (xioctl(VIDIOC_ENUM_FMT, &fmtdesc) >= 0)
{
// collect available device resolutions
QString v4lDevice_res;
v4l2_frmsizeenum frmsizeenum;
CLEAR(frmsizeenum);
frmsizeenum.pixel_format = fmtdesc.pixelformat;
frmsizeenum.index = 0;
frmsizeenum.pixel_format = fmt.fmt.pix.pixelformat;
while (xioctl(VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) >= 0)
{
switch (frmsizeenum.type)
{
case V4L2_FRMSIZE_TYPE_DISCRETE:
{
max_width = std::max(max_width, frmsizeenum.discrete.width);
max_height = std::max(max_height, frmsizeenum.discrete.height);
}
v4lDevice_res += "\t"+ QString::number(frmsizeenum.discrete.width) + "x" + QString::number(frmsizeenum.discrete.height) + "\n";
break;
case V4L2_FRMSIZE_TYPE_CONTINUOUS:
case V4L2_FRMSIZE_TYPE_STEPWISE:
{
max_width = std::max(max_width, frmsizeenum.stepwise.max_width);
max_height = std::max(max_height, frmsizeenum.stepwise.max_height);
for(unsigned int y = frmsizeenum.stepwise.min_height; y <= frmsizeenum.stepwise.max_height; y += frmsizeenum.stepwise.step_height)
{
for(unsigned int x = frmsizeenum.stepwise.min_width; x <= frmsizeenum.stepwise.max_width; x += frmsizeenum.stepwise.step_width)
{
v4lDevice_res += "\t"+ QString::number(x) + "x" + QString::number(y) + "\n";
}
}
}
}
frmsizeenum.index++;
}
fmtdesc.index++;
}
// print available device resolutions in debug mode
if (!v4lDevice_res.isEmpty())
Debug(_log, "available V4L2 resolutions:\n%s", QSTRING_CSTR(v4lDevice_res));
// set the settings
if (max_width != 0 || max_height != 0)
{
fmt.fmt.pix.width = max_width;
fmt.fmt.pix.height = max_height;
}
else
{
fmt.fmt.pix.width = _width;
fmt.fmt.pix.height = _height;
}
if (-1 == xioctl(VIDIOC_S_FMT, &fmt))
{
@ -652,14 +645,18 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
if (streamparms.parm.capture.capability == V4L2_CAP_TIMEPERFRAME)
{
// Driver supports the feature. Set required framerate
streamparms.parm.capture.capturemode = V4L2_MODE_HIGHQUALITY;
CLEAR(streamparms);
streamparms.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
streamparms.parm.capture.timeperframe.numerator = 1;
streamparms.parm.capture.timeperframe.denominator = 30;
streamparms.parm.capture.timeperframe.denominator = _fps;
if(-1 == xioctl(VIDIOC_S_PARM, &streamparms))
{
throw_errno_exception("VIDIOC_S_PARM");
// continue
}
else
// display the used framerate
Debug(_log, "Set framerate to %d fps", _fps);
}
}
@ -693,7 +690,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
}
break;
#ifdef HAVE_JPEG
#ifdef HAVE_JPEG_DECODER
case V4L2_PIX_FMT_MJPEG:
{
_pixelFormat = PIXELFORMAT_MJPEG;
@ -703,7 +700,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
#endif
default:
#ifdef HAVE_JPEG
#ifdef HAVE_JPEG_DECODER
throw_exception("Only pixel formats UYVY, YUYV, RGB32 and MJPEG are supported");
#else
throw_exception("Only pixel formats UYVY, YUYV, and RGB32 are supported");
@ -955,7 +952,7 @@ int V4L2Grabber::read_frame()
bool V4L2Grabber::process_image(const void *p, int size)
{
// We do want a new frame...
#ifdef HAVE_JPEG
#ifdef HAVE_JPEG_DECODER
if (size != _frameByteSize && _pixelFormat != PIXELFORMAT_MJPEG)
#else
if (size != _frameByteSize)
@ -976,9 +973,15 @@ void V4L2Grabber::process_image(const uint8_t * data, int size)
{
Image<ColorRgb> image(_width, _height);
#ifdef HAVE_JPEG
/* ----------------------------------------------------------
* ----------- BEGIN of JPEG decoder related code -----------
* --------------------------------------------------------*/
#ifdef HAVE_JPEG_DECODER
if (_pixelFormat == PIXELFORMAT_MJPEG)
{
#endif
#ifdef HAVE_JPEG
_decompress = new jpeg_decompress_struct;
_error = new errorManager;
@ -1048,7 +1051,31 @@ void V4L2Grabber::process_image(const uint8_t * data, int size)
if (imageFrame.isNull() || _error->pub.num_warnings > 0)
return;
#endif
#ifdef HAVE_TURBO_JPEG
_decompress = tjInitDecompress();
if (_decompress == nullptr)
return;
if (tjDecompressHeader2(_decompress, const_cast<uint8_t*>(data), size, &_width, &_height, &_subsamp) != 0)
{
tjDestroy(_decompress);
return;
}
QImage imageFrame = QImage(_width, _height, QImage::Format_RGB888);
if (tjDecompress2(_decompress, const_cast<uint8_t*>(data), size, imageFrame.bits(), _width, 0, _height, TJPF_RGB, TJFLAG_FASTDCT | TJFLAG_FASTUPSAMPLE) != 0)
{
tjDestroy(_decompress);
return;
}
tjDestroy(_decompress);
if (imageFrame.isNull())
return;
#endif
#ifdef HAVE_JPEG_DECODER
QRect rect(_cropLeft, _cropTop, imageFrame.width() - _cropLeft - _cropRight, imageFrame.height() - _cropTop - _cropBottom);
imageFrame = imageFrame.copy(rect);
imageFrame = imageFrame.scaled(imageFrame.width() / _pixelDecimation, imageFrame.height() / _pixelDecimation,Qt::KeepAspectRatio);
@ -1068,6 +1095,11 @@ void V4L2Grabber::process_image(const uint8_t * data, int size)
}
else
#endif
/* ----------------------------------------------------------
* ------------ END of JPEG decoder related code ------------
* --------------------------------------------------------*/
_imageResampler.processImage(data, _width, _height, _lineLength, _pixelFormat, image);
if (_signalDetectionEnabled)
@ -1171,3 +1203,27 @@ void V4L2Grabber::setDeviceVideoStandard(QString device, VideoStandard videoStan
if(started) start();
}
}
bool V4L2Grabber::setFramerate(int fps)
{
if(Grabber::setFramerate(fps))
{
bool started = _initialized;
uninit();
if(started) start();
return true;
}
return false;
}
bool V4L2Grabber::setWidthHeight(int width, int height)
{
if(Grabber::setWidthHeight(width,height))
{
bool started = _initialized;
uninit();
if(started) start();
return true;
}
return false;
}

View File

@ -6,11 +6,17 @@
#include <QTimer>
V4L2Wrapper::V4L2Wrapper(const QString &device,
const unsigned grabWidth,
const unsigned grabHeight,
const unsigned fps,
VideoStandard videoStandard,
PixelFormat pixelFormat,
int pixelDecimation )
: GrabberWrapper("V4L2:"+device, &_grabber, 0, 0, 10)
: GrabberWrapper("V4L2:"+device, &_grabber, grabWidth, grabHeight, 10)
, _grabber(device,
grabWidth,
grabHeight,
fps,
videoStandard,
pixelFormat,
pixelDecimation)

View File

@ -7,6 +7,7 @@ Grabber::Grabber(QString grabberName, int width, int height, int cropLeft, int c
, _videoMode(VIDEO_2D)
, _width(width)
, _height(height)
, _fps(15)
, _cropLeft(0)
, _cropRight(0)
, _cropTop(0)
@ -86,3 +87,11 @@ bool Grabber::setWidthHeight(int width, int height)
}
return false;
}
bool Grabber::setFramerate(int fps)
{
if(fps > 0)
_fps = fps;
return fps > 0;
}

View File

@ -157,6 +157,12 @@ void GrabberWrapper::handleSettingsUpdate(const settings::type& type, const QJso
obj["cropTop"].toInt(0),
obj["cropBottom"].toInt(0));
// device resolution
_ggrabber->setWidthHeight(obj["width"].toInt(0), obj["height"].toInt(0));
// device framerate
_ggrabber->setFramerate(obj["fps"].toInt(15));
_ggrabber->setSignalDetectionEnable(obj["signalDetection"].toBool(true));
_ggrabber->setSignalDetectionOffset(
obj["sDHOffsetMin"].toDouble(0.25),
@ -170,7 +176,6 @@ void GrabberWrapper::handleSettingsUpdate(const settings::type& type, const QJso
_ggrabber->setDeviceVideoStandard(
obj["device"].toString("auto"),
parseVideoStandard(obj["standard"].toString("no-change")));
}
}
}

View File

@ -30,7 +30,8 @@
"items" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 255
"maximum" : 255,
"default" : 0
},
"minItems" : 3,
"maxItems" : 3,

View File

@ -30,7 +30,8 @@
"items" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 255
"maximum" : 255,
"default" : 0
},
"minItems" : 3,
"maxItems" : 3,

View File

@ -9,7 +9,6 @@
"type" : "string",
"title" : "edt_conf_v4l2_device_title",
"default" : "auto",
"minLength" : 4,
"required" : true,
"propertyOrder" : 1
},
@ -17,14 +16,44 @@
{
"type" : "string",
"title" : "edt_conf_v4l2_standard_title",
"enum" : ["PAL","NTSC","SECAM","NO_CHANGE"],
"enum" : ["NO_CHANGE", "PAL","NTSC","SECAM"],
"default" : "NO_CHANGE",
"options" : {
"enum_titles" : ["edt_conf_enum_PAL", "edt_conf_enum_NTSC", "edt_conf_enum_SECAM", "edt_conf_enum_NO_CHANGE"]
"enum_titles" : ["edt_conf_enum_NO_CHANGE", "edt_conf_enum_PAL", "edt_conf_enum_NTSC", "edt_conf_enum_SECAM"]
},
"required" : true,
"propertyOrder" : 2
},
"width" :
{
"type" : "integer",
"title" : "edt_conf_fg_width_title",
"default" : 0,
"minimum" : 0,
"required" : true,
"access" : "expert",
"propertyOrder" : 3
},
"height" :
{
"type" : "integer",
"title" : "edt_conf_fg_height_title",
"default" : 0,
"minimum" : 0,
"required" : true,
"access" : "expert",
"propertyOrder" : 4
},
"fps" :
{
"type" : "integer",
"title" : "Framerate",
"default" : 15,
"minimum" : 1,
"required" : true,
"access" : "expert",
"propertyOrder" : 5
},
"sizeDecimation" :
{
"type" : "integer",
@ -33,7 +62,7 @@
"maximum" : 30,
"default" : 6,
"required" : true,
"propertyOrder" : 3
"propertyOrder" : 6
},
"cropLeft" :
{
@ -43,7 +72,7 @@
"default" : 0,
"append" : "edt_append_pixel",
"required" : true,
"propertyOrder" : 4
"propertyOrder" : 7
},
"cropRight" :
{
@ -53,7 +82,7 @@
"default" : 0,
"append" : "edt_append_pixel",
"required" : true,
"propertyOrder" : 5
"propertyOrder" : 8
},
"cropTop" :
{
@ -63,7 +92,7 @@
"default" : 0,
"append" : "edt_append_pixel",
"required" : true,
"propertyOrder" : 6
"propertyOrder" : 9
},
"cropBottom" :
{
@ -73,7 +102,7 @@
"default" : 0,
"append" : "edt_append_pixel",
"required" : true,
"propertyOrder" : 7
"propertyOrder" : 10
},
"signalDetection" :
{
@ -81,7 +110,7 @@
"title" : "edt_conf_v4l2_signalDetection_title",
"default" : false,
"required" : true,
"propertyOrder" : 8
"propertyOrder" : 11
},
"redSignalThreshold" :
{
@ -97,7 +126,7 @@
}
},
"required" : true,
"propertyOrder" : 9
"propertyOrder" : 12
},
"greenSignalThreshold" :
{
@ -113,7 +142,7 @@
}
},
"required" : true,
"propertyOrder" : 10
"propertyOrder" : 13
},
"blueSignalThreshold" :
{
@ -129,7 +158,7 @@
}
},
"required" : true,
"propertyOrder" : 11
"propertyOrder" : 14
},
"sDVOffsetMin" :
{
@ -145,7 +174,7 @@
}
},
"required" : true,
"propertyOrder" : 12
"propertyOrder" : 15
},
"sDVOffsetMax" :
{
@ -161,7 +190,7 @@
}
},
"required" : true,
"propertyOrder" : 13
"propertyOrder" : 16
},
"sDHOffsetMin" :
{
@ -177,7 +206,7 @@
}
},
"required" : true,
"propertyOrder" : 14
"propertyOrder" : 17
},
"sDHOffsetMax" :
{
@ -193,8 +222,8 @@
}
},
"required" : true,
"propertyOrder" : 15
"propertyOrder" : 18
}
},
"additionalProperties" : false
"additionalProperties" : true
}

View File

@ -45,7 +45,8 @@
"required" : true,
"items" : {
"type": "string",
"title" : "edt_conf_net_ip_itemtitle"
"title" : "edt_conf_net_ip_itemtitle",
"allowEmptyArray" : true
},
"options": {
"dependencies": {

View File

@ -121,7 +121,7 @@ void ImageResampler::processImage(const uint8_t * data, int width, int height, i
rgb.red = data[index+2];
}
break;
#ifdef HAVE_JPEG
#ifdef HAVE_JPEG_DECODER
case PIXELFORMAT_MJPEG:
break;
#endif

View File

@ -73,7 +73,7 @@ void QJsonSchemaChecker::validate(const QJsonValue & value, const QJsonObject &s
QJsonObject::const_iterator defaultValue = schema.find("default");
if (attribute == "type")
checkType(value, attributeValue, (defaultValue != schema.end() ? defaultValue.value() : QJsonValue::Null));
checkType(value, attributeValue, (defaultValue != schema.end() ? *defaultValue : QJsonValue::Null));
else if (attribute == "properties")
{
if (value.isObject())
@ -106,13 +106,13 @@ void QJsonSchemaChecker::validate(const QJsonValue & value, const QJsonObject &s
}
}
else if (attribute == "minimum")
checkMinimum(value, attributeValue, (defaultValue != schema.end() ? defaultValue.value() : QJsonValue::Null));
checkMinimum(value, attributeValue, (defaultValue != schema.end() ? *defaultValue : QJsonValue::Null));
else if (attribute == "maximum")
checkMaximum(value, attributeValue, (defaultValue != schema.end() ? defaultValue.value() : QJsonValue::Null));
checkMaximum(value, attributeValue, (defaultValue != schema.end() ? *defaultValue : QJsonValue::Null));
else if (attribute == "minLength")
checkMinLength(value, attributeValue, (defaultValue != schema.end() ? defaultValue.value() : QJsonValue::Null));
checkMinLength(value, attributeValue, (defaultValue != schema.end() ? *defaultValue : QJsonValue::Null));
else if (attribute == "maxLength")
checkMaxLength(value, attributeValue, (defaultValue != schema.end() ? defaultValue.value() : QJsonValue::Null));
checkMaxLength(value, attributeValue, (defaultValue != schema.end() ? *defaultValue : QJsonValue::Null));
else if (attribute == "items")
{
if (value.isArray())
@ -125,19 +125,20 @@ void QJsonSchemaChecker::validate(const QJsonValue & value, const QJsonObject &s
}
}
else if (attribute == "minItems")
checkMinItems(value, attributeValue, (defaultValue != schema.end() ? defaultValue.value() : QJsonValue::Null));
checkMinItems(value, attributeValue, (defaultValue != schema.end() ? *defaultValue : QJsonValue::Null));
else if (attribute == "maxItems")
checkMaxItems(value, attributeValue, (defaultValue != schema.end() ? defaultValue.value() : QJsonValue::Null));
checkMaxItems(value, attributeValue, (defaultValue != schema.end() ? *defaultValue : QJsonValue::Null));
else if (attribute == "uniqueItems")
checkUniqueItems(value, attributeValue);
else if (attribute == "enum")
checkEnum(value, attributeValue, (defaultValue != schema.end() ? defaultValue.value() : QJsonValue::Null));
checkEnum(value, attributeValue, (defaultValue != schema.end() ? *defaultValue : QJsonValue::Null));
else if (attribute == "required")
; // nothing to do. value is present so always oke
else if (attribute == "id")
; // references have already been collected
else if (attribute == "title" || attribute == "description" || attribute == "default" || attribute == "format"
|| attribute == "defaultProperties" || attribute == "propertyOrder" || attribute == "append" || attribute == "step" || attribute == "access" || attribute == "options" || attribute == "script")
|| attribute == "defaultProperties" || attribute == "propertyOrder" || attribute == "append" || attribute == "step"
|| attribute == "access" || attribute == "options" || attribute == "script" || attribute == "allowEmptyArray")
; // nothing to do.
else
{
@ -225,7 +226,7 @@ void QJsonSchemaChecker::checkProperties(const QJsonObject & value, const QJsonO
if (_correct == "create")
{
QJsonUtils::modify(_autoCorrected, _currentPath, QJsonUtils::create(propertyValue, _ignoreRequired), property);
setMessage("Create property: "+property+" with value: "+propertyValue.toObject().find("default").value().toString());
setMessage("Create property: "+property+" with value: "+QJsonUtils::getDefaultValue(propertyValue));
}
if (_correct == "")
@ -391,7 +392,7 @@ void QJsonSchemaChecker::checkItems(const QJsonValue & value, const QJsonObject
QJsonArray jArray = value.toArray();
if (_correct == "remove")
if (jArray.isEmpty())
if (jArray.isEmpty() && !schema.contains("allowEmptyArray"))
{
QJsonUtils::modify(_autoCorrected, _currentPath);
setMessage("Remove empty array");

View File

@ -53,6 +53,9 @@ int main(int argc, char** argv)
Option & argDevice = parser.add<Option> ('d', "device", "The device to use, can be /dev/video0 [default: %1 (auto detected)]", "auto");
SwitchOption<VideoStandard> & argVideoStandard= parser.add<SwitchOption<VideoStandard>>('v', "video-standard", "The used video standard. Valid values are PAL, NTSC, SECAM or no-change. [default: %1]", "no-change");
SwitchOption<PixelFormat> & argPixelFormat = parser.add<SwitchOption<PixelFormat>> (0x0, "pixel-format", "The use pixel format. Valid values are YUYV, UYVY, RGB32, MJPEG or no-change. [default: %1]", "no-change");
IntOption & argFps = parser.add<IntOption> ('f', "framerate", "Capture frame rate [default: %1]", "15", 1, 25);
IntOption & argWidth = parser.add<IntOption> (0x0, "width", "Width of the captured image [default: %1]", "160", 160);
IntOption & argHeight = parser.add<IntOption> (0x0, "height", "Height of the captured image [default: %1]", "160", 160);
IntOption & argCropWidth = parser.add<IntOption> (0x0, "crop-width", "Number of pixels to crop from the left and right sides of the picture before decimation [default: %1]", "0");
IntOption & argCropHeight = parser.add<IntOption> (0x0, "crop-height", "Number of pixels to crop from the top and the bottom of the picture before decimation [default: %1]", "0");
IntOption & argCropLeft = parser.add<IntOption> (0x0, "crop-left", "Number of pixels to crop from the left of the picture before decimation (overrides --crop-width)");
@ -105,6 +108,9 @@ int main(int argc, char** argv)
// initialize the grabber
V4L2Grabber grabber(
argDevice.value(parser),
argWidth.getInt(parser),
argHeight.getInt(parser),
1000 / argFps.getInt(parser),
argVideoStandard.switchValue(parser),
argPixelFormat.switchValue(parser),
std::max(1, argSizeDecimation.getInt(parser)));

View File

@ -443,9 +443,12 @@ void HyperionDaemon::handleSettingsUpdate(const settings::type& settingsType, co
_v4l2Grabber = new V4L2Wrapper(
grabberConfig["device"].toString("auto"),
grabberConfig["width"].toInt(0),
grabberConfig["height"].toInt(0),
grabberConfig["fps"].toInt(15),
parseVideoStandard(grabberConfig["standard"].toString("no-change")),
parsePixelFormat(grabberConfig["pixelFormat"].toString("no-change")),
grabberConfig["sizeDecimation"].toInt(8) );
grabberConfig["sizeDecimation"].toInt(8));
_v4l2Grabber->setSignalThreshold(
grabberConfig["redSignalThreshold"].toDouble(0.0)/100.0,
grabberConfig["greenSignalThreshold"].toDouble(0.0)/100.0,