diff --git a/.vscode/launch.json b/.vscode/launch.json index 36fde755..a2ec8824 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -32,7 +32,7 @@ "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], - "externalConsole": false + "console": "internalConsole" } ] } diff --git a/assets/webconfig/i18n/de.json b/assets/webconfig/i18n/de.json index f2c1bca4..875b3646 100644 --- a/assets/webconfig/i18n/de.json +++ b/assets/webconfig/i18n/de.json @@ -258,6 +258,9 @@ "edt_conf_enum_NTSC": "NTSC", "edt_conf_enum_PAL": "PAL", "edt_conf_enum_SECAM": "SECAM", + "edt_conf_enum_HORIZONTAL": "Horizontal", + "edt_conf_enum_VERTICAL": "Vertikal", + "edt_conf_enum_BOTH": "Horizontal & Vertikal", "edt_conf_enum_automatic": "Automatisch", "edt_conf_enum_bbclassic": "Klassisch", "edt_conf_enum_bbdefault": "Standard", @@ -424,6 +427,8 @@ "edt_conf_v4l2_sizeDecimation_title": "Bildverkleinerung Faktor", "edt_conf_v4l2_standard_expl": "Wähle das passende Videoformat deiner Region. Auf 'Automatisch' wird der gewählte Modus vom v4l interface beibehalten.", "edt_conf_v4l2_standard_title": "Videoformat", + "edt_conf_v4l2_flip_expl": "Hiermit kannst du das Bild in horizontaler, vertikaler oder beiden Richtung spiegeln.", + "edt_conf_v4l2_flip_title": "Spiegelung", "edt_conf_webc_crtPath_expl": "Pfad zur Zertifikats-Datei (Format sollte PEM sein)", "edt_conf_webc_crtPath_title": "Zertifikats-Pfad", "edt_conf_webc_docroot_expl": "Lokaler Pfad zum WebUI Wurzelverzeichnis (Nur für WebUI Entwickler)", @@ -935,4 +940,4 @@ "wiz_yeelight_intro1": "Dieser Assistent hilft dir bei der Konfiguration von Hyperion für Yeelight. Zu den Funktionen zählen ein automatisches Finden der Yeelights, die einzelnen Lampen unterschiedlichen Bereichen im Bild zuzuordnen und weitere Einstellungen von Hyperion automatisch anzupassen. Kurz gesagt: Komplette Einrichtung mit ein paar Klicks.", "wiz_yeelight_title": "Yeelight Einrichtungsassistent", "wiz_yeelight_unsupported": "Nicht unterstützt" -} \ No newline at end of file +} diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index 10575d45..f2568a31 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -258,6 +258,9 @@ "edt_conf_enum_NTSC": "NTSC", "edt_conf_enum_PAL": "PAL", "edt_conf_enum_SECAM": "SECAM", + "edt_conf_enum_HORIZONTAL": "Horizontal", + "edt_conf_enum_VERTICAL": "Vertical", + "edt_conf_enum_BOTH": "Horizontal & Vertical", "edt_conf_enum_automatic": "Automatic", "edt_conf_enum_bbclassic": "Classic", "edt_conf_enum_bbdefault": "Default", @@ -424,6 +427,8 @@ "edt_conf_v4l2_sizeDecimation_title": "Size decimation", "edt_conf_v4l2_standard_expl": "Select the video standard for your region. 'Automatic' keeps the value chosen by the v4l2 interface.", "edt_conf_v4l2_standard_title": "Video standard", + "edt_conf_v4l2_flip_expl": "This allows you to flip the image horizontally, vertically, or both.", + "edt_conf_v4l2_flip_title": "Image flip", "edt_conf_v4l2_fpsSoftwareDecimation_title" : "Software frame skipping", "edt_conf_v4l2_fpsSoftwareDecimation_expl" : "To save resources every n'th frame will be processed only. For ex. if grabber is set to 30FPS with this option set to 5 the final result will be around 6FPS (1 - disabled)", "edt_conf_v4l2_encoding_title" : "Encoding format", diff --git a/assets/webconfig/js/content_grabber.js b/assets/webconfig/js/content_grabber.js index 767f1cfa..329c6c55 100755 --- a/assets/webconfig/js/content_grabber.js +++ b/assets/webconfig/js/content_grabber.js @@ -13,35 +13,40 @@ $(document).ready(function () { "type": "string", "title": "edt_conf_v4l2_device_title", "propertyOrder": 1, - "required": true + "required": true, + "custom": true }, "device_inputs": { "type": "string", "title": "edt_conf_v4l2_input_title", "propertyOrder": 3, - "required": true + "required": true, + "custom": false }, "encoding_format": { "type": "string", "title": "edt_conf_v4l2_encoding_title", "propertyOrder": 5, - "required": true + "required": true, + "custom": false }, "resolutions": { "type": "string", "title": "edt_conf_v4l2_resolution_title", - "propertyOrder": 8, - "required": true + "propertyOrder": 9, + "required": true, + "custom": true }, "framerates": { "type": "string", "title": "edt_conf_v4l2_framerate_title", - "propertyOrder": 11, - "required": true + "propertyOrder": 12, + "required": true, + "custom": true } }; @@ -52,7 +57,7 @@ $(document).ready(function () { var enumTitelVals = []; var v4l2_properties = JSON.parse(JSON.stringify(window.serverInfo.grabbers.v4l2_properties)); - if (key === 'available_devices') { + if (key == 'available_devices') { for (var i = 0; i < v4l2_properties.length; i++) { enumVals.push(v4l2_properties[i]['device']); @@ -79,17 +84,20 @@ $(document).ready(function () { } } - window.schema.grabberV4L2.properties[key] = { - "type": schema[key].type, - "title": schema[key].title, - "enum": [].concat(["auto"], enumVals, ["custom"]), - "options": - { - "enum_titles": [].concat(["edt_conf_enum_automatic"], enumTitelVals, ["edt_conf_enum_custom"]), - }, - "propertyOrder": schema[key].propertyOrder, - "required": schema[key].required - }; + if (key != 'device_inputs' || enumVals.length > 0) { + window.schema.grabberV4L2.properties[key] = { + "type": schema[key].type, + "title": schema[key].title, + ...(schema[key].custom ? {"enum": [].concat(["auto"], enumVals, ["custom"]),} : {"enum": [].concat(["auto"], enumVals),}), + // "enum": [].concat(["auto"], enumVals, ["custom"]), + "options": + { + "enum_titles": [].concat(["edt_conf_enum_automatic"], enumTitelVals, ["edt_conf_enum_custom"]), + }, + "propertyOrder": schema[key].propertyOrder, + "required": schema[key].required + }; + } } }; diff --git a/config/hyperion.config.json.default b/config/hyperion.config.json.default index 6cc7e6b9..795c1a2c 100644 --- a/config/hyperion.config.json.default +++ b/config/hyperion.config.json.default @@ -69,6 +69,7 @@ "height" : 0, "fps" : 15, "standard" : "NO_CHANGE", + "flip" : "NO_CHANGE", "fpsSoftwareDecimation" : 1, "sizeDecimation" : 8, "cropLeft" : 0, diff --git a/include/grabber/MFGrabber.h b/include/grabber/MFGrabber.h index fa887174..4644ca66 100644 --- a/include/grabber/MFGrabber.h +++ b/include/grabber/MFGrabber.h @@ -56,7 +56,7 @@ public: QList valid = QList(); }; - MFGrabber(const QString & device, const unsigned width, const unsigned height, const unsigned fps, const unsigned input, int pixelDecimation); + MFGrabber(const QString & device, const unsigned width, const unsigned height, const unsigned fps, const unsigned input, int pixelDecimation, QString flipMode); ~MFGrabber() override; void receive_image(const void *frameImageBuffer, int size); @@ -80,6 +80,7 @@ public: bool setFramerate(int fps) override; void setFpsSoftwareDecimation(int decimation); void setEncoding(QString enc); + void setFlipMode(QString flipMode); void setBrightnessContrastSaturationHue(int brightness, int contrast, int saturation, int hue); public slots: diff --git a/include/grabber/MFThread.h b/include/grabber/MFThread.h index 7eb4e9a6..52f93776 100644 --- a/include/grabber/MFThread.h +++ b/include/grabber/MFThread.h @@ -32,7 +32,7 @@ public: unsigned int threadIndex, PixelFormat pixelFormat, uint8_t* sharedData, int size, int width, int height, int lineLength, int subsamp, unsigned cropLeft, unsigned cropTop, unsigned cropBottom, unsigned cropRight, - VideoMode videoMode, int currentFrame, int pixelDecimation); + VideoMode videoMode, FlipMode flipMode, int currentFrame, int pixelDecimation); void run(); bool isBusy(); @@ -45,30 +45,33 @@ private: void processImageMjpeg(); #ifdef HAVE_TURBO_JPEG - tjhandle _decompress; - tjscalingfactor* _scalingFactors; + tjhandle _transform, + _decompress; + tjscalingfactor* _scalingFactors; + tjtransform* _xform; #endif static volatile bool _isActive; - volatile bool _isBusy; - QSemaphore _semaphore; - unsigned int _workerIndex; - PixelFormat _pixelFormat; - uint8_t* _localData; - int _localDataSize; - int _scalingFactorsCount; - int _size; - int _width; - int _height; - int _lineLength; - int _subsamp; - unsigned _cropLeft; - unsigned _cropTop; - unsigned _cropBottom; - unsigned _cropRight; - int _currentFrame; - int _pixelDecimation; - ImageResampler _imageResampler; + volatile bool _isBusy; + QSemaphore _semaphore; + unsigned int _threadIndex; + PixelFormat _pixelFormat; + uint8_t* _localData; + int _localDataSize, + _scalingFactorsCount, + _size, + _width, + _height, + _lineLength, + _subsamp, + _currentFrame, + _pixelDecimation; + unsigned _cropLeft, + _cropTop, + _cropBottom, + _cropRight; + FlipMode _flipMode; + ImageResampler _imageResampler; }; class MFThreadManager : public QObject @@ -116,7 +119,7 @@ public: if (_threads != nullptr) { - for(unsigned i=0; i < _maxThreads; i++) + for(unsigned i = 0; i < _maxThreads; i++) if (_threads[i] != nullptr) { _threads[i]->quit(); @@ -125,6 +128,6 @@ public: } } - unsigned int _maxThreads; - MFThread** _threads; + unsigned int _maxThreads; + MFThread** _threads; }; diff --git a/include/grabber/MFWrapper.h b/include/grabber/MFWrapper.h index e726a666..d373f557 100644 --- a/include/grabber/MFWrapper.h +++ b/include/grabber/MFWrapper.h @@ -8,7 +8,7 @@ class MFWrapper : public GrabberWrapper Q_OBJECT public: - MFWrapper(const QString & device, const unsigned grabWidth, const unsigned grabHeight, const unsigned fps, const unsigned input, int pixelDecimation); + MFWrapper(const QString & device, const unsigned grabWidth, const unsigned grabHeight, const unsigned fps, const unsigned input, int pixelDecimation, QString flipMode); ~MFWrapper() override; bool getSignalDetectionEnable() const; diff --git a/include/grabber/QtGrabber.h b/include/grabber/QtGrabber.h index 9c5903b6..94db630c 100644 --- a/include/grabber/QtGrabber.h +++ b/include/grabber/QtGrabber.h @@ -86,8 +86,8 @@ private: unsigned _display; int _pixelDecimation; - unsigned _screenWidth; - unsigned _screenHeight; + unsigned _calculatedWidth; + unsigned _calculatedHeight; unsigned _src_x; unsigned _src_y; unsigned _src_x_max; diff --git a/include/hyperion/Grabber.h b/include/hyperion/Grabber.h index 3cb02746..93dd1d47 100644 --- a/include/hyperion/Grabber.h +++ b/include/hyperion/Grabber.h @@ -30,6 +30,12 @@ public: /// virtual void setVideoMode(VideoMode mode); + /// + /// Apply new flip mode (vertical/horizontal/both) + /// @param[in] mode The new flip mode + /// + virtual void setFlipMode(FlipMode mode); + /// /// @brief Apply new crop values, on errors reject the values /// @@ -161,8 +167,11 @@ protected: bool _useImageResampler; - /// the selected VideoMode - VideoMode _videoMode; + /// The selected VideoMode + VideoMode _videoMode; + + /// The used Flip Mode + FlipMode _flipMode; /// With of the captured snapshot [pixels] int _width; diff --git a/include/utils/PixelFormat.h b/include/utils/PixelFormat.h index 7bd2769d..c77836a0 100644 --- a/include/utils/PixelFormat.h +++ b/include/utils/PixelFormat.h @@ -120,10 +120,10 @@ inline QString pixelFormatToString(const PixelFormat& pixelFormat) enum class FlipMode { - HORIZONTAL = 1, - VERTICAL = 2, - BOTH = HORIZONTAL | VERTICAL, - NO_CHANGE = 4 + HORIZONTAL, + VERTICAL, + BOTH, + NO_CHANGE }; inline FlipMode parseFlipMode(const QString& flipMode) @@ -131,15 +131,15 @@ inline FlipMode parseFlipMode(const QString& flipMode) // convert to lower case QString mode = flipMode.toLower(); - if (flipMode.compare("horizontal") == 0) + if (mode.compare("horizontal") == 0) { return FlipMode::HORIZONTAL; } - else if (flipMode.compare("vertical") == 0) + else if (mode.compare("vertical") == 0) { return FlipMode::VERTICAL; } - else if (flipMode.compare("both") == 0) + else if (mode.compare("both") == 0) { return FlipMode::BOTH; } @@ -150,7 +150,6 @@ inline FlipMode parseFlipMode(const QString& flipMode) inline QString flipModeToString(const FlipMode& flipMode) { - if ( flipMode == FlipMode::HORIZONTAL) { return "horizontal"; diff --git a/libsrc/grabber/mediafoundation/MFGrabber.cpp b/libsrc/grabber/mediafoundation/MFGrabber.cpp index 4a024b74..18f59996 100644 --- a/libsrc/grabber/mediafoundation/MFGrabber.cpp +++ b/libsrc/grabber/mediafoundation/MFGrabber.cpp @@ -1,7 +1,10 @@ #include "MFSourceReaderCB.h" #include "grabber/MFGrabber.h" -MFGrabber::MFGrabber(const QString & device, unsigned width, unsigned height, unsigned fps, unsigned input, int pixelDecimation) +// Constants +namespace { const bool verbose = false; } + +MFGrabber::MFGrabber(const QString & device, unsigned width, unsigned height, unsigned fps, unsigned input, int pixelDecimation, QString flipMode) : Grabber("V4L2:"+device) , _deviceName(device) , _hr(S_FALSE) @@ -30,6 +33,7 @@ MFGrabber::MFGrabber(const QString & device, unsigned width, unsigned height, un setInput(input); setWidthHeight(width, height); setFramerate(fps); + setFlipMode(flipMode); // setDeviceVideoStandard(device, videoStandard); // TODO CoInitializeEx(0, COINIT_MULTITHREADED); @@ -467,8 +471,8 @@ void MFGrabber::enumVideoCaptureDevices() if (pixelformat != PixelFormat::NO_CHANGE) { int framerate = fr1/fr2; - QString sFrame = QString::number(framerate).rightJustified(2,' '); - QString displayResolutions = QString::number(w).rightJustified(4,' ') +"x"+ QString::number(h).rightJustified(4,' '); + QString sFrame = QString::number(framerate).rightJustified(2,' ').trimmed(); + QString displayResolutions = QString::number(w).rightJustified(4,' ').trimmed() +"x"+ QString::number(h).rightJustified(4,' ').trimmed(); if (!properties.displayResolutions.contains(displayResolutions)) properties.displayResolutions << displayResolutions; @@ -486,7 +490,7 @@ void MFGrabber::enumVideoCaptureDevices() di.guid = format; properties.valid.append(di); - Debug(_log, "%s %d x %d @ %d fps (%s)", QSTRING_CSTR(dev), di.x, di.y, di.fps, QSTRING_CSTR(pixelFormatToString(di.pf))); + DebugIf(verbose, _log, "%s %d x %d @ %d fps (%s)", QSTRING_CSTR(dev), di.x, di.y, di.fps, QSTRING_CSTR(pixelFormatToString(di.pf))); } } @@ -553,12 +557,12 @@ void MFGrabber::process_image(const void *frameImageBuffer, int size) for (unsigned int i=0; i < _threadManager._maxThreads && _threadManager._threads != nullptr; i++) { - MFThread* _thread=_threadManager._threads[i]; + MFThread* _thread = _threadManager._threads[i]; connect(_thread, SIGNAL(newFrame(unsigned int, const Image &,unsigned int)), this , SLOT(newThreadFrame(unsigned int, const Image &, unsigned int))); } } - for (unsigned int i=0;_threadManager.isActive() && i < _threadManager._maxThreads && _threadManager._threads != nullptr; i++) + for (unsigned int i = 0;_threadManager.isActive() && i < _threadManager._maxThreads && _threadManager._threads != nullptr; i++) { if ((_threadManager._threads[i]->isFinished() || !_threadManager._threads[i]->isRunning())) { @@ -566,11 +570,8 @@ void MFGrabber::process_image(const void *frameImageBuffer, int size) if ( _threadManager._threads[i]->isBusy() == false) { MFThread* _thread = _threadManager._threads[i]; - _thread->setup(i, _pixelFormat, (uint8_t *)frameImageBuffer, size, _width, _height, _lineLength, _subsamp, _cropLeft, _cropTop, _cropBottom, _cropRight, _videoMode, processFrameIndex, _pixelDecimation); - - if (_threadManager._maxThreads > 1) - _threadManager._threads[i]->start(); - + _thread->setup(i, _pixelFormat, (uint8_t *)frameImageBuffer, size, _width, _height, _lineLength, _subsamp, _cropLeft, _cropTop, _cropBottom, _cropRight, _videoMode, _flipMode, processFrameIndex, _pixelDecimation); + _threadManager._threads[i]->start(); break; } } @@ -586,7 +587,8 @@ void MFGrabber::setSignalThreshold(double redSignalThreshold, double greenSignal _noSignalThresholdColor.blue = uint8_t(255*blueSignalThreshold); _noSignalCounterThreshold = qMax(1, noSignalCounterThreshold); - Info(_log, "Signal threshold set to: {%d, %d, %d} and frames: %d", _noSignalThresholdColor.red, _noSignalThresholdColor.green, _noSignalThresholdColor.blue, _noSignalCounterThreshold ); + if(_signalDetectionEnabled) + Info(_log, "Signal threshold set to: {%d, %d, %d} and frames: %d", _noSignalThresholdColor.red, _noSignalThresholdColor.green, _noSignalThresholdColor.blue, _noSignalCounterThreshold ); } void MFGrabber::setSignalDetectionOffset(double horizontalMin, double verticalMin, double horizontalMax, double verticalMax) @@ -599,7 +601,8 @@ void MFGrabber::setSignalDetectionOffset(double horizontalMin, double verticalMi _x_frac_max = horizontalMax; _y_frac_max = verticalMax; - Info(_log, "Signal detection area set to: %f,%f x %f,%f", _x_frac_min, _y_frac_min, _x_frac_max, _y_frac_max ); + if(_signalDetectionEnabled) + Info(_log, "Signal detection area set to: %f,%f x %f,%f", _x_frac_min, _y_frac_min, _x_frac_max, _y_frac_max ); } bool MFGrabber::start() @@ -744,6 +747,12 @@ void MFGrabber::setPixelDecimation(int pixelDecimation) _pixelDecimation = pixelDecimation; } +void MFGrabber::setFlipMode(QString flipMode) +{ + if(_flipMode != parseFlipMode(flipMode)) + Grabber::setFlipMode(parseFlipMode(flipMode)); +} + void MFGrabber::setDeviceVideoStandard(QString device, VideoStandard videoStandard) { if(_deviceName != device) diff --git a/libsrc/grabber/mediafoundation/MFThread.cpp b/libsrc/grabber/mediafoundation/MFThread.cpp index 965d0c36..44d28ab3 100644 --- a/libsrc/grabber/mediafoundation/MFThread.cpp +++ b/libsrc/grabber/mediafoundation/MFThread.cpp @@ -3,26 +3,31 @@ volatile bool MFThread::_isActive = false; MFThread::MFThread() - : _localData(nullptr) + : _isBusy(false) + , _semaphore(1) + , _localData(nullptr) , _localDataSize(0) - , _decompress(nullptr) , _scalingFactorsCount(0) , _scalingFactors(nullptr) - , _isBusy(false) - , _semaphore(1) + , _transform(nullptr) + , _decompress(nullptr) + , _xform(nullptr) , _imageResampler() { } MFThread::~MFThread() { - if (_decompress == nullptr) + if (_transform) + tjDestroy(_transform); + + if (_decompress) tjDestroy(_decompress); - if (_localData != NULL) + if (_localData) { free(_localData); - _localData = NULL; + _localData = nullptr; _localDataSize = 0; } } @@ -31,9 +36,9 @@ void MFThread::setup( unsigned int threadIndex, PixelFormat pixelFormat, uint8_t* sharedData, int size, int width, int height, int lineLength, int subsamp, unsigned cropLeft, unsigned cropTop, unsigned cropBottom, unsigned cropRight, - VideoMode videoMode, int currentFrame, int pixelDecimation) + VideoMode videoMode, FlipMode flipMode, int currentFrame, int pixelDecimation) { - _workerIndex = threadIndex; + _threadIndex = threadIndex; _lineLength = lineLength; _pixelFormat = pixelFormat; _size = size; @@ -44,26 +49,25 @@ void MFThread::setup( _cropTop = cropTop; _cropBottom = cropBottom; _cropRight = cropRight; + _flipMode = flipMode; _currentFrame = currentFrame; _pixelDecimation = pixelDecimation; _imageResampler.setVideoMode(videoMode); + _imageResampler.setFlipMode(_flipMode); _imageResampler.setCropping(cropLeft, cropRight, cropTop, cropBottom); _imageResampler.setHorizontalPixelDecimation(_pixelDecimation); _imageResampler.setVerticalPixelDecimation(_pixelDecimation); - _imageResampler.setFlipMode(FlipMode::NO_CHANGE); if (size > _localDataSize) { - if (_localData != NULL) - { - free(_localData); - _localData = NULL; - _localDataSize = 0; - } - _localData = (uint8_t *) malloc(size+1); + if (_localData) + tjFree(_localData); + + _localData = (uint8_t*)tjAlloc(size + 1); _localDataSize = size; } + memcpy(_localData, sharedData, size); } @@ -79,7 +83,7 @@ void MFThread::run() { Image image = Image(); _imageResampler.processImage(_localData, _width, _height, _lineLength, PixelFormat::BGR24, image); - emit newFrame(_workerIndex, image, _currentFrame); + emit newFrame(_threadIndex, image, _currentFrame); } } } @@ -108,11 +112,36 @@ void MFThread::noBusy() void MFThread::processImageMjpeg() { - if (_decompress == nullptr) + if (!_transform && _flipMode != FlipMode::NO_CHANGE) + { + _transform = tjInitTransform(); + _xform = new tjtransform(); + } + + if (_flipMode == FlipMode::BOTH || _flipMode == FlipMode::HORIZONTAL) + { + _xform->op = TJXOP_HFLIP; + uint8_t* dstBuf = nullptr; + unsigned long dstSize = 0; + tjTransform(_transform, _localData, _size, 1, &dstBuf, &dstSize, _xform, TJFLAG_FASTDCT | TJFLAG_FASTUPSAMPLE); + _localData = dstBuf; + _size = dstSize; + } + + if (_flipMode == FlipMode::BOTH || _flipMode == FlipMode::VERTICAL) + { + _xform->op = TJXOP_VFLIP; + uint8_t *dstBuf = nullptr; + unsigned long dstSize = 0; + tjTransform(_transform, _localData, _size, 1, &dstBuf, &dstSize, _xform, TJFLAG_FASTDCT | TJFLAG_FASTUPSAMPLE); + _localData = dstBuf; + _size = dstSize; + } + + if (!_decompress) { _decompress = tjInitDecompress(); - _scalingFactors = tjGetScalingFactors (&_scalingFactorsCount); - tjhandle handle=NULL; + _scalingFactors = tjGetScalingFactors(&_scalingFactorsCount); } if (tjDecompressHeader2(_decompress, _localData, _size, &_width, &_height, &_subsamp) != 0) @@ -153,10 +182,10 @@ void MFThread::processImageMjpeg() // got image, process it if ( !(_cropLeft > 0 || _cropTop > 0 || _cropBottom > 0 || _cropRight > 0)) - emit newFrame(_workerIndex, srcImage, _currentFrame); + emit newFrame(_threadIndex, srcImage, _currentFrame); else { - // calculate the output size + // calculate the output size int outputWidth = (_width - _cropLeft - _cropRight); int outputHeight = (_height - _cropTop - _cropBottom); @@ -171,10 +200,12 @@ void MFThread::processImageMjpeg() unsigned char* dest = (unsigned char*)destImage.memptr() + y*destImage.width()*3; memcpy(dest, source, destImage.width()*3); free(source); + source = nullptr; free(dest); + dest = nullptr; } // emit - emit newFrame(_workerIndex, destImage, _currentFrame); + emit newFrame(_threadIndex, destImage, _currentFrame); } } diff --git a/libsrc/grabber/mediafoundation/MFWrapper.cpp b/libsrc/grabber/mediafoundation/MFWrapper.cpp index 34cba000..f4801b91 100644 --- a/libsrc/grabber/mediafoundation/MFWrapper.cpp +++ b/libsrc/grabber/mediafoundation/MFWrapper.cpp @@ -5,9 +5,9 @@ // qt #include -MFWrapper::MFWrapper(const QString &device, unsigned grabWidth, unsigned grabHeight, unsigned fps, unsigned input, int pixelDecimation ) +MFWrapper::MFWrapper(const QString &device, unsigned grabWidth, unsigned grabHeight, unsigned fps, unsigned input, int pixelDecimation, QString flipMode) : GrabberWrapper("V4L2:"+device, &_grabber, grabWidth, grabHeight, 10) - , _grabber(device, grabWidth, grabHeight, fps, input, pixelDecimation) + , _grabber(device, grabWidth, grabHeight, fps, input, pixelDecimation, flipMode) { _ggrabber = &_grabber; @@ -123,6 +123,9 @@ void MFWrapper::handleSettingsUpdate(settings::type type, const QJsonDocument& c // image size decimation _grabber.setPixelDecimation(obj["sizeDecimation"].toInt(8)); + // flip mode + _grabber.setFlipMode(obj["flip"].toString("no-change")); + // image cropping _grabber.setCropping( obj["cropLeft"].toInt(0), diff --git a/libsrc/grabber/qt/QtGrabber.cpp b/libsrc/grabber/qt/QtGrabber.cpp index aca6092d..2dfe198b 100644 --- a/libsrc/grabber/qt/QtGrabber.cpp +++ b/libsrc/grabber/qt/QtGrabber.cpp @@ -12,8 +12,8 @@ QtGrabber::QtGrabber(int cropLeft, int cropRight, int cropTop, int cropBottom, i : Grabber("QTGRABBER", 0, 0, cropLeft, cropRight, cropTop, cropBottom) , _display(unsigned(display)) , _pixelDecimation(pixelDecimation) - , _screenWidth(0) - , _screenHeight(0) + , _calculatedWidth(0) + , _calculatedHeight(0) , _src_x(0) , _src_y(0) , _src_x_max(0) @@ -98,20 +98,13 @@ int QtGrabber::grabFrame(Image & image) setEnabled(setupDisplay()); return -1; } - QPixmap originalPixmap = _screen->grabWindow(0, _src_x, _src_y, _src_x_max, _src_y_max); - QPixmap resizedPixmap = originalPixmap.scaled(_width,_height); - QImage imageFrame = resizedPixmap.toImage().convertToFormat( QImage::Format_RGB888); - image.resize(imageFrame.width(), imageFrame.height()); - for (int y=0; ygrabWindow(0, _src_x, _src_y, _src_x_max, _src_y_max); + QImage imageFrame = originalPixmap.toImage().scaled(_calculatedWidth, _calculatedHeight).convertToFormat( QImage::Format_RGB888); + image.resize(_calculatedWidth, _calculatedHeight); + + for (int y = 0; y < imageFrame.height(); y++) + memcpy((unsigned char*)image.memptr() + y * image.width() * 3, (unsigned char*)imageFrame.scanLine(y), imageFrame.width() * 3); return 0; } @@ -122,59 +115,59 @@ int QtGrabber::updateScreenDimensions(bool force) return -1; const QRect& geo = _screen->geometry(); - if (!force && _screenWidth == unsigned(geo.right()) && _screenHeight == unsigned(geo.bottom())) + if (!force && _width == unsigned(geo.right()) && _height == unsigned(geo.bottom())) { // No update required return 0; } - Info(_log, "Update of screen resolution: [%dx%d] to [%dx%d]", _screenWidth, _screenHeight, geo.right(), geo.bottom()); - _screenWidth = geo.right() - geo.left(); - _screenHeight = geo.bottom() - geo.top(); + Info(_log, "Update of screen resolution: [%dx%d] to [%dx%d]", _width, _height, geo.right(), geo.bottom()); + _width = geo.right() - geo.left(); + _height = geo.bottom() - geo.top(); int width=0, height=0; // Image scaling is performed by Qt - width = (_screenWidth > unsigned(_cropLeft + _cropRight)) - ? ((_screenWidth - _cropLeft - _cropRight) / _pixelDecimation) - : (_screenWidth / _pixelDecimation); + width = (_width > (_cropLeft + _cropRight)) + ? ((_width - _cropLeft - _cropRight) / _pixelDecimation) + : (_width / _pixelDecimation); - height = (_screenHeight > unsigned(_cropTop + _cropBottom)) - ? ((_screenHeight - _cropTop - _cropBottom) / _pixelDecimation) - : (_screenHeight / _pixelDecimation); + height = (_height > (_cropTop + _cropBottom)) + ? ((_height - _cropTop - _cropBottom) / _pixelDecimation) + : (_height / _pixelDecimation); // calculate final image dimensions and adjust top/left cropping in 3D modes switch (_videoMode) { case VideoMode::VIDEO_3DSBS: - _width = width /2; - _height = height; + _calculatedWidth = width /2; + _calculatedHeight = height; _src_x = _cropLeft / 2; _src_y = _cropTop; - _src_x_max = (_screenWidth / 2) - _cropRight; - _src_y_max = _screenHeight - _cropBottom; + _src_x_max = (_width / 2) - _cropRight - _cropLeft; + _src_y_max = _height - _cropBottom - _cropTop; break; case VideoMode::VIDEO_3DTAB: - _width = width; - _height = height / 2; + _calculatedWidth = width; + _calculatedHeight = height / 2; _src_x = _cropLeft; _src_y = _cropTop / 2; - _src_x_max = _screenWidth - _cropRight; - _src_y_max = (_screenHeight / 2) - _cropBottom; + _src_x_max = _width - _cropRight - _cropLeft; + _src_y_max = (_height / 2) - _cropBottom - _cropTop; break; case VideoMode::VIDEO_2D: default: - _width = width; - _height = height; + _calculatedWidth = width; + _calculatedHeight = height; _src_x = _cropLeft; _src_y = _cropTop; - _src_x_max = _screenWidth - _cropRight; - _src_y_max = _screenHeight - _cropBottom; + _src_x_max = _width - _cropRight - _cropLeft; + _src_y_max = _height - _cropBottom - _cropTop; break; } - Info(_log, "Update output image resolution to [%dx%d]", _width, _height); + Info(_log, "Update output image resolution to [%dx%d]", _calculatedWidth, _calculatedHeight); return 1; } diff --git a/libsrc/hyperion/Grabber.cpp b/libsrc/hyperion/Grabber.cpp index 85633ff1..ba33e85f 100644 --- a/libsrc/hyperion/Grabber.cpp +++ b/libsrc/hyperion/Grabber.cpp @@ -4,6 +4,7 @@ Grabber::Grabber(const QString& grabberName, int width, int height, int cropLeft : _imageResampler() , _useImageResampler(true) , _videoMode(VideoMode::VIDEO_2D) + , _flipMode(FlipMode::NO_CHANGE) , _width(width) , _height(height) , _fps(15) @@ -35,6 +36,16 @@ void Grabber::setVideoMode(VideoMode mode) } } +void Grabber::setFlipMode(FlipMode mode) +{ + Debug(_log,"Set flipmode to %d", mode); + _flipMode = mode; + if ( _useImageResampler ) + { + _imageResampler.setFlipMode(_flipMode); + } +} + void Grabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom) { if (_width>0 && _height>0) diff --git a/libsrc/hyperion/schema/schema-grabberV4L2.json b/libsrc/hyperion/schema/schema-grabberV4L2.json index 32641139..3b13d79d 100644 --- a/libsrc/hyperion/schema/schema-grabberV4L2.json +++ b/libsrc/hyperion/schema/schema-grabberV4L2.json @@ -52,6 +52,18 @@ "required" : true, "propertyOrder" : 7 }, + "flip" : + { + "type" : "string", + "title" : "edt_conf_v4l2_flip_title", + "enum" : ["NO_CHANGE", "HORIZONTAL","VERTICAL","BOTH"], + "default" : "NO_CHANGE", + "options" : { + "enum_titles" : ["edt_conf_enum_NO_CHANGE", "edt_conf_enum_HORIZONTAL", "edt_conf_enum_VERTICAL", "edt_conf_enum_BOTH"] + }, + "required" : true, + "propertyOrder" : 8 + }, "width" : { "type" : "integer", @@ -63,8 +75,8 @@ "hidden":true }, "required" : true, - "propertyOrder" : 9, - "comment" : "The 'resolutions' settings are dynamically inserted into the WebUI under PropertyOrder '8'." + "propertyOrder" : 10, + "comment" : "The 'resolutions' settings are dynamically inserted into the WebUI under PropertyOrder '9'." }, "height" : { @@ -77,8 +89,8 @@ "hidden":true }, "required" : true, - "propertyOrder" : 10, - "comment" : "The 'resolutions' settings are dynamically inserted into the WebUI under PropertyOrder '6'." + "propertyOrder" : 11, + "comment" : "The 'resolutions' settings are dynamically inserted into the WebUI under PropertyOrder '9'." }, "fps" : { @@ -91,8 +103,8 @@ "hidden":true }, "required" : true, - "propertyOrder" : 12, - "comment" : "The 'framerates' setting is dynamically inserted into the WebUI under PropertyOrder '11'." + "propertyOrder" : 13, + "comment" : "The 'framerates' setting is dynamically inserted into the WebUI under PropertyOrder '12'." }, "fpsSoftwareDecimation" : { @@ -102,7 +114,7 @@ "maximum" : 60, "default" : 1, "required" : true, - "propertyOrder" : 13 + "propertyOrder" : 14 }, "sizeDecimation" : { @@ -112,7 +124,7 @@ "maximum" : 30, "default" : 6, "required" : true, - "propertyOrder" : 14 + "propertyOrder" : 15 }, "cropLeft" : { @@ -122,7 +134,7 @@ "default" : 0, "append" : "edt_append_pixel", "required" : true, - "propertyOrder" : 15 + "propertyOrder" : 16 }, "cropRight" : { @@ -132,7 +144,7 @@ "default" : 0, "append" : "edt_append_pixel", "required" : true, - "propertyOrder" : 16 + "propertyOrder" : 17 }, "cropTop" : { @@ -142,7 +154,7 @@ "default" : 0, "append" : "edt_append_pixel", "required" : true, - "propertyOrder" : 17 + "propertyOrder" : 18 }, "cropBottom" : { @@ -152,7 +164,7 @@ "default" : 0, "append" : "edt_append_pixel", "required" : true, - "propertyOrder" : 18 + "propertyOrder" : 19 }, "cecDetection" : { @@ -160,7 +172,7 @@ "title" : "edt_conf_v4l2_cecDetection_title", "default" : false, "required" : true, - "propertyOrder" : 19 + "propertyOrder" : 20 }, "signalDetection" : { @@ -168,7 +180,7 @@ "title" : "edt_conf_v4l2_signalDetection_title", "default" : false, "required" : true, - "propertyOrder" : 20 + "propertyOrder" : 21 }, "redSignalThreshold" : { @@ -184,7 +196,7 @@ } }, "required" : true, - "propertyOrder" : 21 + "propertyOrder" : 22 }, "greenSignalThreshold" : { @@ -200,7 +212,7 @@ } }, "required" : true, - "propertyOrder" : 22 + "propertyOrder" : 23 }, "blueSignalThreshold" : { @@ -216,7 +228,7 @@ } }, "required" : true, - "propertyOrder" : 23 + "propertyOrder" : 24 }, "noSignalCounterThreshold" : { @@ -231,7 +243,7 @@ } }, "required" : true, - "propertyOrder" : 24 + "propertyOrder" : 25 }, "sDVOffsetMin" : { @@ -247,7 +259,7 @@ } }, "required" : true, - "propertyOrder" : 25 + "propertyOrder" : 26 }, "sDVOffsetMax" : { @@ -263,7 +275,7 @@ } }, "required" : true, - "propertyOrder" : 26 + "propertyOrder" : 27 }, "sDHOffsetMin" : { @@ -279,7 +291,7 @@ } }, "required" : true, - "propertyOrder" : 27 + "propertyOrder" : 28 }, "sDHOffsetMax" : { @@ -295,7 +307,7 @@ } }, "required" : true, - "propertyOrder" : 28 + "propertyOrder" : 29 }, "hardware_brightness" : { @@ -303,7 +315,7 @@ "title" : "edt_conf_v4l2_hardware_brightness_title", "default" : 0, "required" : true, - "propertyOrder" : 29 + "propertyOrder" : 30 }, "hardware_contrast" : { @@ -311,7 +323,7 @@ "title" : "edt_conf_v4l2_hardware_contrast_title", "default" : 0, "required" : true, - "propertyOrder" : 30 + "propertyOrder" : 31 }, "hardware_saturation" : { @@ -319,7 +331,7 @@ "title" : "edt_conf_v4l2_hardware_saturation_title", "default" : 0, "required" : true, - "propertyOrder" : 31 + "propertyOrder" : 32 }, "hardware_hue" : { @@ -327,7 +339,7 @@ "title" : "edt_conf_v4l2_hardware_hue_title", "default" : 0, "required" : true, - "propertyOrder" : 32 + "propertyOrder" : 33 } }, "additionalProperties" : true diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index 78d9b414..a2efe55c 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -605,7 +605,8 @@ void HyperionDaemon::handleSettingsUpdate(settings::type settingsType, const QJs grabberConfig["height"].toInt(0), grabberConfig["fps"].toInt(15), grabberConfig["input"].toInt(-1), - grabberConfig["sizeDecimation"].toInt(8)); + grabberConfig["sizeDecimation"].toInt(8), + grabberConfig["flip"].toString("auto")); // Image cropping _mfGrabber->setCropping(