From 662872dafebc271a1d6dbff7dd850fea9c3c1527 Mon Sep 17 00:00:00 2001 From: Paulchen Panther <16664240+Paulchen-Panther@users.noreply.github.com> Date: Fri, 27 Mar 2020 23:13:58 +0100 Subject: [PATCH] feat: SchemaChecker & V4L2 enhancement (#734) * libjpeg-turbo, QJsonSchemaChecker, V4L2 width/height/fps Signed-off-by: Paulchen-Panther * Implement hyperion-v4l cli args * Apply v4l2 settings during runtime * feat: Provide minimum values for input restriction * fix: merge mess Co-authored-by: brindosch --- CMakeLists.txt | 33 ++-- CompileHowto.md | 2 +- CrossCompileHowto.md | 4 +- bin/compile.sh | 2 +- cmake/FindTurboJPEG.cmake | 33 ++++ config/hyperion.config.json.commented | 4 + config/hyperion.config.json.default | 33 ++-- include/grabber/V4L2Grabber.h | 35 ++++- include/grabber/V4L2Wrapper.h | 3 + include/hyperion/Grabber.h | 8 + include/utils/PixelFormat.h | 4 +- include/utils/jsonschema/QJsonSchemaChecker.h | 8 + include/utils/jsonschema/QJsonUtils.h | 31 ++++ libsrc/grabber/v4l2/CMakeLists.txt | 8 +- libsrc/grabber/v4l2/V4L2Grabber.cpp | 144 ++++++++++++------ libsrc/grabber/v4l2/V4L2Wrapper.cpp | 8 +- libsrc/hyperion/Grabber.cpp | 9 ++ libsrc/hyperion/GrabberWrapper.cpp | 7 +- .../schema/schema-backgroundEffect.json | 3 +- .../schema/schema-foregroundEffect.json | 3 +- .../hyperion/schema/schema-grabberV4L2.json | 63 +++++--- libsrc/hyperion/schema/schema-network.json | 3 +- libsrc/utils/ImageResampler.cpp | 2 +- .../utils/jsonschema/QJsonSchemaChecker.cpp | 23 +-- src/hyperion-v4l2/hyperion-v4l2.cpp | 6 + src/hyperiond/hyperiond.cpp | 5 +- 26 files changed, 363 insertions(+), 121 deletions(-) create mode 100644 cmake/FindTurboJPEG.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index e3c4bd47e..4e5d75dca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -298,23 +298,32 @@ find_package(libusb-1.0 REQUIRED) find_package(Threads REQUIRED) add_definitions(${QT_DEFINITIONS}) -# Add jpeg library +# Add JPEG library if (ENABLE_V4L2) - find_package(JPEG) - if (JPEG_FOUND) - add_definitions(-DHAVE_JPEG) - message( STATUS "Using JPEG library: ${JPEG_LIBRARIES}") - include_directories(${JPEG_INCLUDE_DIR}) + # 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() - message( STATUS "JPEG library not found, MJPEG camera format won't work in V4L2 grabber.") + # System JPEG + find_package(JPEG) + if (JPEG_FOUND) + add_definitions(-DHAVE_JPEG) + 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 (TurboJPEG_FOUND) + + + if (TURBOJPEG_FOUND OR JPEG_FOUND) + add_definitions(-DHAVE_JPEG_DECODER) endif() endif() -# 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(APPLE) set(CMAKE_EXE_LINKER_FLAGS "-framework CoreGraphics") endif() diff --git a/CompileHowto.md b/CompileHowto.md index d8810b6a8..ff4101ecb 100644 --- a/CompileHowto.md +++ b/CompileHowto.md @@ -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** diff --git a/CrossCompileHowto.md b/CrossCompileHowto.md index 365af84c2..8fd96a180 100644 --- a/CrossCompileHowto.md +++ b/CrossCompileHowto.md @@ -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: diff --git a/bin/compile.sh b/bin/compile.sh index 9d8cdd06c..a399b8a79 100755 --- a/bin/compile.sh +++ b/bin/compile.sh @@ -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 diff --git a/cmake/FindTurboJPEG.cmake b/cmake/FindTurboJPEG.cmake new file mode 100644 index 000000000..d6a1c9af9 --- /dev/null +++ b/cmake/FindTurboJPEG.cmake @@ -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 \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 +) diff --git a/config/hyperion.config.json.commented b/config/hyperion.config.json.commented index 2f776a6c2..052844e4b 100644 --- a/config/hyperion.config.json.commented +++ b/config/hyperion.config.json.commented @@ -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, diff --git a/config/hyperion.config.json.default b/config/hyperion.config.json.default index 6c8ed8ed2..8b43dd008 100644 --- a/config/hyperion.config.json.default +++ b/config/hyperion.config.json.default @@ -59,21 +59,24 @@ "grabberV4L2" : { - "device" : "auto", - "standard" : "NO_CHANGE", - "sizeDecimation" : 8, - "cropLeft" : 0, - "cropRight" : 0, - "cropTop" : 0, - "cropBottom" : 0, - "redSignalThreshold" : 5, - "greenSignalThreshold" : 5, - "blueSignalThreshold" : 5, - "signalDetection" : false, - "sDVOffsetMin" : 0.25, - "sDHOffsetMin" : 0.25, - "sDVOffsetMax" : 0.75, - "sDHOffsetMax" : 0.75 + "device" : "auto", + "width" : 0, + "height" : 0, + "fps" : 15, + "standard" : "NO_CHANGE", + "sizeDecimation" : 8, + "cropLeft" : 0, + "cropRight" : 0, + "cropTop" : 0, + "cropBottom" : 0, + "redSignalThreshold" : 5, + "greenSignalThreshold" : 5, + "blueSignalThreshold" : 5, + "signalDetection" : false, + "sDVOffsetMin" : 0.25, + "sDHOffsetMin" : 0.25, + "sDVOffsetMax" : 0.75, + "sDHOffsetMax" : 0.75 }, "framegrabber" : diff --git a/include/grabber/V4L2Grabber.h b/include/grabber/V4L2Grabber.h index 109293b04..11fef1d5f 100644 --- a/include/grabber/V4L2Grabber.h +++ b/include/grabber/V4L2Grabber.h @@ -15,13 +15,23 @@ #include #include -#ifdef HAVE_JPEG +// general JPEG decoder includes +#ifdef HAVE_JPEG_DECODER #include #include +#endif + +// System JPEG decoder +#ifdef HAVE_JPEG #include #include #endif +// TurboJPEG decoder +#ifdef HAVE_TURBO_JPEG + #include +#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 @@ class V4L2Grabber : public Grabber int grabFrame(Image &); - /// - /// @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 @@ class V4L2Grabber : public Grabber /// 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 slots: errorManager* _error; #endif +#ifdef HAVE_TURBO_JPEG + tjhandle _decompress = nullptr; + int _subsamp; +#endif + private: QString _deviceName; std::map _v4lDevices; diff --git a/include/grabber/V4L2Wrapper.h b/include/grabber/V4L2Wrapper.h index e4a307168..beb1a5bc8 100644 --- a/include/grabber/V4L2Wrapper.h +++ b/include/grabber/V4L2Wrapper.h @@ -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 ); diff --git a/include/hyperion/Grabber.h b/include/hyperion/Grabber.h index 629d539df..06a4e2df3 100644 --- a/include/hyperion/Grabber.h +++ b/include/hyperion/Grabber.h @@ -40,6 +40,12 @@ class Grabber : public QObject /// 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 @@ class Grabber : public QObject /// Height of the captured snapshot [pixels] int _height; + int _fps; + // number of pixels to crop after capturing int _cropLeft, _cropRight, _cropTop, _cropBottom; diff --git a/include/utils/PixelFormat.h b/include/utils/PixelFormat.h index a3b1fca3b..4af71b780 100644 --- a/include/utils/PixelFormat.h +++ b/include/utils/PixelFormat.h @@ -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; diff --git a/include/utils/jsonschema/QJsonSchemaChecker.h b/include/utils/jsonschema/QJsonSchemaChecker.h index 895cdaa1b..57191b676 100644 --- a/include/utils/jsonschema/QJsonSchemaChecker.h +++ b/include/utils/jsonschema/QJsonSchemaChecker.h @@ -187,6 +187,14 @@ class QJsonSchemaChecker /// 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; diff --git a/include/utils/jsonschema/QJsonUtils.h b/include/utils/jsonschema/QJsonUtils.h index 60381dbee..6c4f56bda 100644 --- a/include/utils/jsonschema/QJsonUtils.h +++ b/include/utils/jsonschema/QJsonUtils.h @@ -41,6 +41,37 @@ class QJsonUtils 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) diff --git a/libsrc/grabber/v4l2/CMakeLists.txt b/libsrc/grabber/v4l2/CMakeLists.txt index 078012998..b7a2fdbd0 100644 --- a/libsrc/grabber/v4l2/CMakeLists.txt +++ b/libsrc/grabber/v4l2/CMakeLists.txt @@ -11,6 +11,8 @@ target_link_libraries(v4l2-grabber ${QT_LIBRARIES} ) -if (JPEG_FOUND) - target_link_libraries(v4l2-grabber ${JPEG_LIBRARY}) -endif() +if(TURBOJPEG_FOUND) + target_link_libraries(v4l2-grabber ${TurboJPEG_LIBRARY}) +elseif (JPEG_FOUND) + target_link_libraries(v4l2-grabber ${JPEG_LIBRARY}) +endif(TURBOJPEG_FOUND) diff --git a/libsrc/grabber/v4l2/V4L2Grabber.cpp b/libsrc/grabber/v4l2/V4L2Grabber.cpp index 0e08f4b8e..a19363d0a 100644 --- a/libsrc/grabber/v4l2/V4L2Grabber.cpp +++ b/libsrc/grabber/v4l2/V4L2Grabber.cpp @@ -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.index = 0; + frmsizeenum.pixel_format = fmt.fmt.pix.pixelformat; + while (xioctl(VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) >= 0) { - v4l2_frmsizeenum frmsizeenum; - CLEAR(frmsizeenum); - frmsizeenum.pixel_format = fmtdesc.pixelformat; - frmsizeenum.index = 0; - while (xioctl(VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) >= 0) + switch (frmsizeenum.type) { - switch (frmsizeenum.type) + case V4L2_FRMSIZE_TYPE_DISCRETE: + 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: { - case V4L2_FRMSIZE_TYPE_DISCRETE: - { - max_width = std::max(max_width, frmsizeenum.discrete.width); - max_height = std::max(max_height, frmsizeenum.discrete.height); - } - break; - case V4L2_FRMSIZE_TYPE_CONTINUOUS: - case V4L2_FRMSIZE_TYPE_STEPWISE: + for(unsigned int y = frmsizeenum.stepwise.min_height; y <= frmsizeenum.stepwise.max_height; y += frmsizeenum.stepwise.step_height) { - max_width = std::max(max_width, frmsizeenum.stepwise.max_width); - 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++; } - - fmtdesc.index++; + frmsizeenum.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; - } + 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 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(data), size, &_width, &_height, &_subsamp) != 0) + { + tjDestroy(_decompress); + return; + } + + QImage imageFrame = QImage(_width, _height, QImage::Format_RGB888); + if (tjDecompress2(_decompress, const_cast(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; +} diff --git a/libsrc/grabber/v4l2/V4L2Wrapper.cpp b/libsrc/grabber/v4l2/V4L2Wrapper.cpp index 2fcdbbc22..7fa526835 100644 --- a/libsrc/grabber/v4l2/V4L2Wrapper.cpp +++ b/libsrc/grabber/v4l2/V4L2Wrapper.cpp @@ -6,11 +6,17 @@ #include 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) diff --git a/libsrc/hyperion/Grabber.cpp b/libsrc/hyperion/Grabber.cpp index 904f567ba..5678d5dbb 100644 --- a/libsrc/hyperion/Grabber.cpp +++ b/libsrc/hyperion/Grabber.cpp @@ -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; +} \ No newline at end of file diff --git a/libsrc/hyperion/GrabberWrapper.cpp b/libsrc/hyperion/GrabberWrapper.cpp index 6304d096a..07f168e37 100644 --- a/libsrc/hyperion/GrabberWrapper.cpp +++ b/libsrc/hyperion/GrabberWrapper.cpp @@ -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"))); - } } } diff --git a/libsrc/hyperion/schema/schema-backgroundEffect.json b/libsrc/hyperion/schema/schema-backgroundEffect.json index 486951500..78edd8d37 100644 --- a/libsrc/hyperion/schema/schema-backgroundEffect.json +++ b/libsrc/hyperion/schema/schema-backgroundEffect.json @@ -30,7 +30,8 @@ "items" : { "type" : "integer", "minimum" : 0, - "maximum" : 255 + "maximum" : 255, + "default" : 0 }, "minItems" : 3, "maxItems" : 3, diff --git a/libsrc/hyperion/schema/schema-foregroundEffect.json b/libsrc/hyperion/schema/schema-foregroundEffect.json index 2435d45d3..71cf5d154 100644 --- a/libsrc/hyperion/schema/schema-foregroundEffect.json +++ b/libsrc/hyperion/schema/schema-foregroundEffect.json @@ -30,7 +30,8 @@ "items" : { "type" : "integer", "minimum" : 0, - "maximum" : 255 + "maximum" : 255, + "default" : 0 }, "minItems" : 3, "maxItems" : 3, diff --git a/libsrc/hyperion/schema/schema-grabberV4L2.json b/libsrc/hyperion/schema/schema-grabberV4L2.json index 1808f04d0..87e68ba1b 100644 --- a/libsrc/hyperion/schema/schema-grabberV4L2.json +++ b/libsrc/hyperion/schema/schema-grabberV4L2.json @@ -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 } diff --git a/libsrc/hyperion/schema/schema-network.json b/libsrc/hyperion/schema/schema-network.json index 9b85b1f47..e159c95fe 100644 --- a/libsrc/hyperion/schema/schema-network.json +++ b/libsrc/hyperion/schema/schema-network.json @@ -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": { diff --git a/libsrc/utils/ImageResampler.cpp b/libsrc/utils/ImageResampler.cpp index 017712fb4..8f02ac926 100644 --- a/libsrc/utils/ImageResampler.cpp +++ b/libsrc/utils/ImageResampler.cpp @@ -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 diff --git a/libsrc/utils/jsonschema/QJsonSchemaChecker.cpp b/libsrc/utils/jsonschema/QJsonSchemaChecker.cpp index 60a02e558..bff2c75d2 100644 --- a/libsrc/utils/jsonschema/QJsonSchemaChecker.cpp +++ b/libsrc/utils/jsonschema/QJsonSchemaChecker.cpp @@ -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"); diff --git a/src/hyperion-v4l2/hyperion-v4l2.cpp b/src/hyperion-v4l2/hyperion-v4l2.cpp index 3ac46354d..59afa9d93 100644 --- a/src/hyperion-v4l2/hyperion-v4l2.cpp +++ b/src/hyperion-v4l2/hyperion-v4l2.cpp @@ -53,6 +53,9 @@ int main(int argc, char** argv) Option & argDevice = parser.add