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) find_package(Threads REQUIRED)
add_definitions(${QT_DEFINITIONS}) add_definitions(${QT_DEFINITIONS})
# Add jpeg library # Add JPEG library
if (ENABLE_V4L2) 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) find_package(JPEG)
if (JPEG_FOUND) if (JPEG_FOUND)
add_definitions(-DHAVE_JPEG) add_definitions(-DHAVE_JPEG)
message( STATUS "Using JPEG library: ${JPEG_LIBRARIES}") message( STATUS "Using system JPEG library: ${JPEG_LIBRARIES}")
include_directories(${JPEG_INCLUDE_DIR}) include_directories(${JPEG_INCLUDE_DIR})
else() else()
message( STATUS "JPEG library not found, MJPEG camera format won't work in V4L2 grabber.") message( STATUS "JPEG library not found, MJPEG camera format won't work in V4L2 grabber.")
endif() endif()
endif() endif (TurboJPEG_FOUND)
# TODO[TvdZ]: This linking directory should only be added if we are cross compiling
#if(NOT APPLE) if (TURBOJPEG_FOUND OR JPEG_FOUND)
# link_directories(${CMAKE_FIND_ROOT_PATH}/lib/arm-linux-gnueabihf) add_definitions(-DHAVE_JPEG_DECODER)
#endif() endif()
endif()
if(APPLE) if(APPLE)
set(CMAKE_EXE_LINKER_FLAGS "-framework CoreGraphics") 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 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** **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) ## On the Target system (here Raspberry Pi)
Install required additional packages. 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) ## On the Host system (here Ubuntu)
Update the Ubuntu environment to the latest stage and install required additional packages. Update the Ubuntu environment to the latest stage and install required additional packages.
``` ```
sudo apt-get update sudo apt-get update
sudo apt-get upgrade 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: 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 )" INST="$( [ "${3:-}" = "install" ] && echo true || echo false )"
sudo apt-get update 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 ] if [ -e /dev/vc-cma -a -e /dev/vc-mem ]
then 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 /// Configuration for the embedded V4L2 grabber
/// * device : V4L2 Device to use [default="auto"] (Auto detection) /// * 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"] /// * standard : Video standard (PAL/NTSC/SECAM/NO_CHANGE) [default="NO_CHANGE"]
/// * sizeDecimation : Size decimation factor [default=8] /// * sizeDecimation : Size decimation factor [default=8]
/// * cropLeft : Cropping from the left [default=0] /// * cropLeft : Cropping from the left [default=0]
@ -118,6 +120,8 @@
"grabberV4L2" : "grabberV4L2" :
{ {
"device" : "auto", "device" : "auto",
"width" : 0,
"height" : 0,
"standard" : "NO_CHANGE", "standard" : "NO_CHANGE",
"sizeDecimation" : 8, "sizeDecimation" : 8,
"priority" : 240, "priority" : 240,

View File

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

View File

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

View File

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

View File

@ -40,6 +40,12 @@ public:
/// ///
virtual bool setWidthHeight(int width, int height); 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) /// @brief Apply new pixelDecimation (used from x11 and qt)
/// ///
@ -111,6 +117,8 @@ protected:
/// Height of the captured snapshot [pixels] /// Height of the captured snapshot [pixels]
int _height; int _height;
int _fps;
// number of pixels to crop after capturing // number of pixels to crop after capturing
int _cropLeft, _cropRight, _cropTop, _cropBottom; int _cropLeft, _cropRight, _cropTop, _cropBottom;

View File

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

View File

@ -187,6 +187,14 @@ private:
/// ///
void checkEnum(const QJsonValue & value, const QJsonValue & schema, const QJsonValue & defaultValue); 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: private:
/// The schema of the entire json-configuration /// The schema of the entire json-configuration
QJsonObject _qSchema; QJsonObject _qSchema;

View File

@ -41,6 +41,37 @@ public:
return createValue(schema, ignoreRequired); 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: private:
static QJsonValue createValue(QJsonValue schema, bool ignoreRequired) static QJsonValue createValue(QJsonValue schema, bool ignoreRequired)

View File

@ -11,6 +11,8 @@ target_link_libraries(v4l2-grabber
${QT_LIBRARIES} ${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}) 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)) #define CLEAR(x) memset(&(x), 0, sizeof(x))
V4L2Grabber::V4L2Grabber(const QString & device V4L2Grabber::V4L2Grabber(const QString & device
, const unsigned width
, const unsigned height
, const unsigned fps
, VideoStandard videoStandard , VideoStandard videoStandard
, PixelFormat pixelFormat , PixelFormat pixelFormat
, int pixelDecimation , int pixelDecimation
@ -59,6 +62,8 @@ V4L2Grabber::V4L2Grabber(const QString & device
getV4Ldevices(); getV4Ldevices();
// init // init
setWidthHeight(width, height);
setFramerate(fps);
setDeviceVideoStandard(device, videoStandard); setDeviceVideoStandard(device, videoStandard);
} }
@ -561,7 +566,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32;
break; break;
#ifdef HAVE_JPEG #ifdef HAVE_JPEG_DECODER
case PIXELFORMAT_MJPEG: case PIXELFORMAT_MJPEG:
{ {
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
@ -576,53 +581,41 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
break; break;
} }
// get maximum video devices resolution // collect available device resolutions
__u32 max_width = 0, max_height = 0; QString v4lDevice_res;
struct v4l2_fmtdesc fmtdesc;
CLEAR(fmtdesc);
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmtdesc.index = 0;
while (xioctl(VIDIOC_ENUM_FMT, &fmtdesc) >= 0)
{
v4l2_frmsizeenum frmsizeenum; v4l2_frmsizeenum frmsizeenum;
CLEAR(frmsizeenum); CLEAR(frmsizeenum);
frmsizeenum.pixel_format = fmtdesc.pixelformat;
frmsizeenum.index = 0; frmsizeenum.index = 0;
frmsizeenum.pixel_format = fmt.fmt.pix.pixelformat;
while (xioctl(VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) >= 0) while (xioctl(VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) >= 0)
{ {
switch (frmsizeenum.type) switch (frmsizeenum.type)
{ {
case V4L2_FRMSIZE_TYPE_DISCRETE: case V4L2_FRMSIZE_TYPE_DISCRETE:
{ v4lDevice_res += "\t"+ QString::number(frmsizeenum.discrete.width) + "x" + QString::number(frmsizeenum.discrete.height) + "\n";
max_width = std::max(max_width, frmsizeenum.discrete.width);
max_height = std::max(max_height, frmsizeenum.discrete.height);
}
break; break;
case V4L2_FRMSIZE_TYPE_CONTINUOUS: case V4L2_FRMSIZE_TYPE_CONTINUOUS:
case V4L2_FRMSIZE_TYPE_STEPWISE: case V4L2_FRMSIZE_TYPE_STEPWISE:
{ {
max_width = std::max(max_width, frmsizeenum.stepwise.max_width); for(unsigned int y = frmsizeenum.stepwise.min_height; y <= frmsizeenum.stepwise.max_height; y += frmsizeenum.stepwise.step_height)
max_height = std::max(max_height, frmsizeenum.stepwise.max_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++; 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 // 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.width = _width;
fmt.fmt.pix.height = _height; fmt.fmt.pix.height = _height;
}
if (-1 == xioctl(VIDIOC_S_FMT, &fmt)) 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) if (streamparms.parm.capture.capability == V4L2_CAP_TIMEPERFRAME)
{ {
// Driver supports the feature. Set required framerate // 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.numerator = 1;
streamparms.parm.capture.timeperframe.denominator = 30; streamparms.parm.capture.timeperframe.denominator = _fps;
if(-1 == xioctl(VIDIOC_S_PARM, &streamparms)) if(-1 == xioctl(VIDIOC_S_PARM, &streamparms))
{ {
throw_errno_exception("VIDIOC_S_PARM"); throw_errno_exception("VIDIOC_S_PARM");
// continue // 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; break;
#ifdef HAVE_JPEG #ifdef HAVE_JPEG_DECODER
case V4L2_PIX_FMT_MJPEG: case V4L2_PIX_FMT_MJPEG:
{ {
_pixelFormat = PIXELFORMAT_MJPEG; _pixelFormat = PIXELFORMAT_MJPEG;
@ -703,7 +700,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
#endif #endif
default: default:
#ifdef HAVE_JPEG #ifdef HAVE_JPEG_DECODER
throw_exception("Only pixel formats UYVY, YUYV, RGB32 and MJPEG are supported"); throw_exception("Only pixel formats UYVY, YUYV, RGB32 and MJPEG are supported");
#else #else
throw_exception("Only pixel formats UYVY, YUYV, and RGB32 are supported"); 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) bool V4L2Grabber::process_image(const void *p, int size)
{ {
// We do want a new frame... // We do want a new frame...
#ifdef HAVE_JPEG #ifdef HAVE_JPEG_DECODER
if (size != _frameByteSize && _pixelFormat != PIXELFORMAT_MJPEG) if (size != _frameByteSize && _pixelFormat != PIXELFORMAT_MJPEG)
#else #else
if (size != _frameByteSize) if (size != _frameByteSize)
@ -976,9 +973,15 @@ void V4L2Grabber::process_image(const uint8_t * data, int size)
{ {
Image<ColorRgb> image(_width, _height); Image<ColorRgb> image(_width, _height);
#ifdef HAVE_JPEG /* ----------------------------------------------------------
* ----------- BEGIN of JPEG decoder related code -----------
* --------------------------------------------------------*/
#ifdef HAVE_JPEG_DECODER
if (_pixelFormat == PIXELFORMAT_MJPEG) if (_pixelFormat == PIXELFORMAT_MJPEG)
{ {
#endif
#ifdef HAVE_JPEG
_decompress = new jpeg_decompress_struct; _decompress = new jpeg_decompress_struct;
_error = new errorManager; _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) if (imageFrame.isNull() || _error->pub.num_warnings > 0)
return; 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); QRect rect(_cropLeft, _cropTop, imageFrame.width() - _cropLeft - _cropRight, imageFrame.height() - _cropTop - _cropBottom);
imageFrame = imageFrame.copy(rect); imageFrame = imageFrame.copy(rect);
imageFrame = imageFrame.scaled(imageFrame.width() / _pixelDecimation, imageFrame.height() / _pixelDecimation,Qt::KeepAspectRatio); 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 else
#endif #endif
/* ----------------------------------------------------------
* ------------ END of JPEG decoder related code ------------
* --------------------------------------------------------*/
_imageResampler.processImage(data, _width, _height, _lineLength, _pixelFormat, image); _imageResampler.processImage(data, _width, _height, _lineLength, _pixelFormat, image);
if (_signalDetectionEnabled) if (_signalDetectionEnabled)
@ -1171,3 +1203,27 @@ void V4L2Grabber::setDeviceVideoStandard(QString device, VideoStandard videoStan
if(started) start(); 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> #include <QTimer>
V4L2Wrapper::V4L2Wrapper(const QString &device, V4L2Wrapper::V4L2Wrapper(const QString &device,
const unsigned grabWidth,
const unsigned grabHeight,
const unsigned fps,
VideoStandard videoStandard, VideoStandard videoStandard,
PixelFormat pixelFormat, PixelFormat pixelFormat,
int pixelDecimation ) int pixelDecimation )
: GrabberWrapper("V4L2:"+device, &_grabber, 0, 0, 10) : GrabberWrapper("V4L2:"+device, &_grabber, grabWidth, grabHeight, 10)
, _grabber(device, , _grabber(device,
grabWidth,
grabHeight,
fps,
videoStandard, videoStandard,
pixelFormat, pixelFormat,
pixelDecimation) pixelDecimation)

View File

@ -7,6 +7,7 @@ Grabber::Grabber(QString grabberName, int width, int height, int cropLeft, int c
, _videoMode(VIDEO_2D) , _videoMode(VIDEO_2D)
, _width(width) , _width(width)
, _height(height) , _height(height)
, _fps(15)
, _cropLeft(0) , _cropLeft(0)
, _cropRight(0) , _cropRight(0)
, _cropTop(0) , _cropTop(0)
@ -86,3 +87,11 @@ bool Grabber::setWidthHeight(int width, int height)
} }
return false; 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["cropTop"].toInt(0),
obj["cropBottom"].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->setSignalDetectionEnable(obj["signalDetection"].toBool(true));
_ggrabber->setSignalDetectionOffset( _ggrabber->setSignalDetectionOffset(
obj["sDHOffsetMin"].toDouble(0.25), obj["sDHOffsetMin"].toDouble(0.25),
@ -170,7 +176,6 @@ void GrabberWrapper::handleSettingsUpdate(const settings::type& type, const QJso
_ggrabber->setDeviceVideoStandard( _ggrabber->setDeviceVideoStandard(
obj["device"].toString("auto"), obj["device"].toString("auto"),
parseVideoStandard(obj["standard"].toString("no-change"))); parseVideoStandard(obj["standard"].toString("no-change")));
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -73,7 +73,7 @@ void QJsonSchemaChecker::validate(const QJsonValue & value, const QJsonObject &s
QJsonObject::const_iterator defaultValue = schema.find("default"); QJsonObject::const_iterator defaultValue = schema.find("default");
if (attribute == "type") 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") else if (attribute == "properties")
{ {
if (value.isObject()) if (value.isObject())
@ -106,13 +106,13 @@ void QJsonSchemaChecker::validate(const QJsonValue & value, const QJsonObject &s
} }
} }
else if (attribute == "minimum") 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") 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") 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") 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") else if (attribute == "items")
{ {
if (value.isArray()) if (value.isArray())
@ -125,19 +125,20 @@ void QJsonSchemaChecker::validate(const QJsonValue & value, const QJsonObject &s
} }
} }
else if (attribute == "minItems") 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") 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") else if (attribute == "uniqueItems")
checkUniqueItems(value, attributeValue); checkUniqueItems(value, attributeValue);
else if (attribute == "enum") 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") else if (attribute == "required")
; // nothing to do. value is present so always oke ; // nothing to do. value is present so always oke
else if (attribute == "id") else if (attribute == "id")
; // references have already been collected ; // references have already been collected
else if (attribute == "title" || attribute == "description" || attribute == "default" || attribute == "format" 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. ; // nothing to do.
else else
{ {
@ -225,7 +226,7 @@ void QJsonSchemaChecker::checkProperties(const QJsonObject & value, const QJsonO
if (_correct == "create") if (_correct == "create")
{ {
QJsonUtils::modify(_autoCorrected, _currentPath, QJsonUtils::create(propertyValue, _ignoreRequired), property); 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 == "") if (_correct == "")
@ -391,7 +392,7 @@ void QJsonSchemaChecker::checkItems(const QJsonValue & value, const QJsonObject
QJsonArray jArray = value.toArray(); QJsonArray jArray = value.toArray();
if (_correct == "remove") if (_correct == "remove")
if (jArray.isEmpty()) if (jArray.isEmpty() && !schema.contains("allowEmptyArray"))
{ {
QJsonUtils::modify(_autoCorrected, _currentPath); QJsonUtils::modify(_autoCorrected, _currentPath);
setMessage("Remove empty array"); 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"); 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<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"); 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 & 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 & 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)"); 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 // initialize the grabber
V4L2Grabber grabber( V4L2Grabber grabber(
argDevice.value(parser), argDevice.value(parser),
argWidth.getInt(parser),
argHeight.getInt(parser),
1000 / argFps.getInt(parser),
argVideoStandard.switchValue(parser), argVideoStandard.switchValue(parser),
argPixelFormat.switchValue(parser), argPixelFormat.switchValue(parser),
std::max(1, argSizeDecimation.getInt(parser))); std::max(1, argSizeDecimation.getInt(parser)));

View File

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