Added image flipping ability to MF Grabber

This commit is contained in:
Paulchen Panther 2021-01-24 12:16:16 +01:00
parent d5717af2df
commit 4254f36bba
18 changed files with 255 additions and 164 deletions

2
.vscode/launch.json vendored
View File

@ -32,7 +32,7 @@
"stopAtEntry": false, "stopAtEntry": false,
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"environment": [], "environment": [],
"externalConsole": false "console": "internalConsole"
} }
] ]
} }

View File

@ -258,6 +258,9 @@
"edt_conf_enum_NTSC": "NTSC", "edt_conf_enum_NTSC": "NTSC",
"edt_conf_enum_PAL": "PAL", "edt_conf_enum_PAL": "PAL",
"edt_conf_enum_SECAM": "SECAM", "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_automatic": "Automatisch",
"edt_conf_enum_bbclassic": "Klassisch", "edt_conf_enum_bbclassic": "Klassisch",
"edt_conf_enum_bbdefault": "Standard", "edt_conf_enum_bbdefault": "Standard",
@ -424,6 +427,8 @@
"edt_conf_v4l2_sizeDecimation_title": "Bildverkleinerung Faktor", "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_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_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_expl": "Pfad zur Zertifikats-Datei (Format sollte PEM sein)",
"edt_conf_webc_crtPath_title": "Zertifikats-Pfad", "edt_conf_webc_crtPath_title": "Zertifikats-Pfad",
"edt_conf_webc_docroot_expl": "Lokaler Pfad zum WebUI Wurzelverzeichnis (Nur für WebUI Entwickler)", "edt_conf_webc_docroot_expl": "Lokaler Pfad zum WebUI Wurzelverzeichnis (Nur für WebUI Entwickler)",

View File

@ -258,6 +258,9 @@
"edt_conf_enum_NTSC": "NTSC", "edt_conf_enum_NTSC": "NTSC",
"edt_conf_enum_PAL": "PAL", "edt_conf_enum_PAL": "PAL",
"edt_conf_enum_SECAM": "SECAM", "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_automatic": "Automatic",
"edt_conf_enum_bbclassic": "Classic", "edt_conf_enum_bbclassic": "Classic",
"edt_conf_enum_bbdefault": "Default", "edt_conf_enum_bbdefault": "Default",
@ -424,6 +427,8 @@
"edt_conf_v4l2_sizeDecimation_title": "Size decimation", "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_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_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_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_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", "edt_conf_v4l2_encoding_title" : "Encoding format",

View File

@ -13,35 +13,40 @@ $(document).ready(function () {
"type": "string", "type": "string",
"title": "edt_conf_v4l2_device_title", "title": "edt_conf_v4l2_device_title",
"propertyOrder": 1, "propertyOrder": 1,
"required": true "required": true,
"custom": true
}, },
"device_inputs": "device_inputs":
{ {
"type": "string", "type": "string",
"title": "edt_conf_v4l2_input_title", "title": "edt_conf_v4l2_input_title",
"propertyOrder": 3, "propertyOrder": 3,
"required": true "required": true,
"custom": false
}, },
"encoding_format": "encoding_format":
{ {
"type": "string", "type": "string",
"title": "edt_conf_v4l2_encoding_title", "title": "edt_conf_v4l2_encoding_title",
"propertyOrder": 5, "propertyOrder": 5,
"required": true "required": true,
"custom": false
}, },
"resolutions": "resolutions":
{ {
"type": "string", "type": "string",
"title": "edt_conf_v4l2_resolution_title", "title": "edt_conf_v4l2_resolution_title",
"propertyOrder": 8, "propertyOrder": 9,
"required": true "required": true,
"custom": true
}, },
"framerates": "framerates":
{ {
"type": "string", "type": "string",
"title": "edt_conf_v4l2_framerate_title", "title": "edt_conf_v4l2_framerate_title",
"propertyOrder": 11, "propertyOrder": 12,
"required": true "required": true,
"custom": true
} }
}; };
@ -52,7 +57,7 @@ $(document).ready(function () {
var enumTitelVals = []; var enumTitelVals = [];
var v4l2_properties = JSON.parse(JSON.stringify(window.serverInfo.grabbers.v4l2_properties)); 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++) { for (var i = 0; i < v4l2_properties.length; i++) {
enumVals.push(v4l2_properties[i]['device']); enumVals.push(v4l2_properties[i]['device']);
@ -79,17 +84,20 @@ $(document).ready(function () {
} }
} }
window.schema.grabberV4L2.properties[key] = { if (key != 'device_inputs' || enumVals.length > 0) {
"type": schema[key].type, window.schema.grabberV4L2.properties[key] = {
"title": schema[key].title, "type": schema[key].type,
"enum": [].concat(["auto"], enumVals, ["custom"]), "title": schema[key].title,
"options": ...(schema[key].custom ? {"enum": [].concat(["auto"], enumVals, ["custom"]),} : {"enum": [].concat(["auto"], enumVals),}),
{ // "enum": [].concat(["auto"], enumVals, ["custom"]),
"enum_titles": [].concat(["edt_conf_enum_automatic"], enumTitelVals, ["edt_conf_enum_custom"]), "options":
}, {
"propertyOrder": schema[key].propertyOrder, "enum_titles": [].concat(["edt_conf_enum_automatic"], enumTitelVals, ["edt_conf_enum_custom"]),
"required": schema[key].required },
}; "propertyOrder": schema[key].propertyOrder,
"required": schema[key].required
};
}
} }
}; };

View File

@ -69,6 +69,7 @@
"height" : 0, "height" : 0,
"fps" : 15, "fps" : 15,
"standard" : "NO_CHANGE", "standard" : "NO_CHANGE",
"flip" : "NO_CHANGE",
"fpsSoftwareDecimation" : 1, "fpsSoftwareDecimation" : 1,
"sizeDecimation" : 8, "sizeDecimation" : 8,
"cropLeft" : 0, "cropLeft" : 0,

View File

@ -56,7 +56,7 @@ public:
QList<DevicePropertiesItem> valid = QList<DevicePropertiesItem>(); QList<DevicePropertiesItem> valid = QList<DevicePropertiesItem>();
}; };
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; ~MFGrabber() override;
void receive_image(const void *frameImageBuffer, int size); void receive_image(const void *frameImageBuffer, int size);
@ -80,6 +80,7 @@ public:
bool setFramerate(int fps) override; bool setFramerate(int fps) override;
void setFpsSoftwareDecimation(int decimation); void setFpsSoftwareDecimation(int decimation);
void setEncoding(QString enc); void setEncoding(QString enc);
void setFlipMode(QString flipMode);
void setBrightnessContrastSaturationHue(int brightness, int contrast, int saturation, int hue); void setBrightnessContrastSaturationHue(int brightness, int contrast, int saturation, int hue);
public slots: public slots:

View File

@ -32,7 +32,7 @@ public:
unsigned int threadIndex, PixelFormat pixelFormat, uint8_t* sharedData, unsigned int threadIndex, PixelFormat pixelFormat, uint8_t* sharedData,
int size, int width, int height, int lineLength, int size, int width, int height, int lineLength,
int subsamp, unsigned cropLeft, unsigned cropTop, unsigned cropBottom, unsigned cropRight, 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(); void run();
bool isBusy(); bool isBusy();
@ -45,30 +45,33 @@ private:
void processImageMjpeg(); void processImageMjpeg();
#ifdef HAVE_TURBO_JPEG #ifdef HAVE_TURBO_JPEG
tjhandle _decompress; tjhandle _transform,
tjscalingfactor* _scalingFactors; _decompress;
tjscalingfactor* _scalingFactors;
tjtransform* _xform;
#endif #endif
static volatile bool _isActive; static volatile bool _isActive;
volatile bool _isBusy; volatile bool _isBusy;
QSemaphore _semaphore; QSemaphore _semaphore;
unsigned int _workerIndex; unsigned int _threadIndex;
PixelFormat _pixelFormat; PixelFormat _pixelFormat;
uint8_t* _localData; uint8_t* _localData;
int _localDataSize; int _localDataSize,
int _scalingFactorsCount; _scalingFactorsCount,
int _size; _size,
int _width; _width,
int _height; _height,
int _lineLength; _lineLength,
int _subsamp; _subsamp,
unsigned _cropLeft; _currentFrame,
unsigned _cropTop; _pixelDecimation;
unsigned _cropBottom; unsigned _cropLeft,
unsigned _cropRight; _cropTop,
int _currentFrame; _cropBottom,
int _pixelDecimation; _cropRight;
ImageResampler _imageResampler; FlipMode _flipMode;
ImageResampler _imageResampler;
}; };
class MFThreadManager : public QObject class MFThreadManager : public QObject
@ -116,7 +119,7 @@ public:
if (_threads != nullptr) if (_threads != nullptr)
{ {
for(unsigned i=0; i < _maxThreads; i++) for(unsigned i = 0; i < _maxThreads; i++)
if (_threads[i] != nullptr) if (_threads[i] != nullptr)
{ {
_threads[i]->quit(); _threads[i]->quit();
@ -125,6 +128,6 @@ public:
} }
} }
unsigned int _maxThreads; unsigned int _maxThreads;
MFThread** _threads; MFThread** _threads;
}; };

View File

@ -8,7 +8,7 @@ class MFWrapper : public GrabberWrapper
Q_OBJECT Q_OBJECT
public: 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; ~MFWrapper() override;
bool getSignalDetectionEnable() const; bool getSignalDetectionEnable() const;

View File

@ -86,8 +86,8 @@ private:
unsigned _display; unsigned _display;
int _pixelDecimation; int _pixelDecimation;
unsigned _screenWidth; unsigned _calculatedWidth;
unsigned _screenHeight; unsigned _calculatedHeight;
unsigned _src_x; unsigned _src_x;
unsigned _src_y; unsigned _src_y;
unsigned _src_x_max; unsigned _src_x_max;

View File

@ -30,6 +30,12 @@ public:
/// ///
virtual void setVideoMode(VideoMode mode); 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 /// @brief Apply new crop values, on errors reject the values
/// ///
@ -161,8 +167,11 @@ protected:
bool _useImageResampler; bool _useImageResampler;
/// the selected VideoMode /// The selected VideoMode
VideoMode _videoMode; VideoMode _videoMode;
/// The used Flip Mode
FlipMode _flipMode;
/// With of the captured snapshot [pixels] /// With of the captured snapshot [pixels]
int _width; int _width;

View File

@ -120,10 +120,10 @@ inline QString pixelFormatToString(const PixelFormat& pixelFormat)
enum class FlipMode enum class FlipMode
{ {
HORIZONTAL = 1, HORIZONTAL,
VERTICAL = 2, VERTICAL,
BOTH = HORIZONTAL | VERTICAL, BOTH,
NO_CHANGE = 4 NO_CHANGE
}; };
inline FlipMode parseFlipMode(const QString& flipMode) inline FlipMode parseFlipMode(const QString& flipMode)
@ -131,15 +131,15 @@ inline FlipMode parseFlipMode(const QString& flipMode)
// convert to lower case // convert to lower case
QString mode = flipMode.toLower(); QString mode = flipMode.toLower();
if (flipMode.compare("horizontal") == 0) if (mode.compare("horizontal") == 0)
{ {
return FlipMode::HORIZONTAL; return FlipMode::HORIZONTAL;
} }
else if (flipMode.compare("vertical") == 0) else if (mode.compare("vertical") == 0)
{ {
return FlipMode::VERTICAL; return FlipMode::VERTICAL;
} }
else if (flipMode.compare("both") == 0) else if (mode.compare("both") == 0)
{ {
return FlipMode::BOTH; return FlipMode::BOTH;
} }
@ -150,7 +150,6 @@ inline FlipMode parseFlipMode(const QString& flipMode)
inline QString flipModeToString(const FlipMode& flipMode) inline QString flipModeToString(const FlipMode& flipMode)
{ {
if ( flipMode == FlipMode::HORIZONTAL) if ( flipMode == FlipMode::HORIZONTAL)
{ {
return "horizontal"; return "horizontal";

View File

@ -1,7 +1,10 @@
#include "MFSourceReaderCB.h" #include "MFSourceReaderCB.h"
#include "grabber/MFGrabber.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) : Grabber("V4L2:"+device)
, _deviceName(device) , _deviceName(device)
, _hr(S_FALSE) , _hr(S_FALSE)
@ -30,6 +33,7 @@ MFGrabber::MFGrabber(const QString & device, unsigned width, unsigned height, un
setInput(input); setInput(input);
setWidthHeight(width, height); setWidthHeight(width, height);
setFramerate(fps); setFramerate(fps);
setFlipMode(flipMode);
// setDeviceVideoStandard(device, videoStandard); // TODO // setDeviceVideoStandard(device, videoStandard); // TODO
CoInitializeEx(0, COINIT_MULTITHREADED); CoInitializeEx(0, COINIT_MULTITHREADED);
@ -467,8 +471,8 @@ void MFGrabber::enumVideoCaptureDevices()
if (pixelformat != PixelFormat::NO_CHANGE) if (pixelformat != PixelFormat::NO_CHANGE)
{ {
int framerate = fr1/fr2; int framerate = fr1/fr2;
QString sFrame = QString::number(framerate).rightJustified(2,' '); QString sFrame = QString::number(framerate).rightJustified(2,' ').trimmed();
QString displayResolutions = QString::number(w).rightJustified(4,' ') +"x"+ QString::number(h).rightJustified(4,' '); QString displayResolutions = QString::number(w).rightJustified(4,' ').trimmed() +"x"+ QString::number(h).rightJustified(4,' ').trimmed();
if (!properties.displayResolutions.contains(displayResolutions)) if (!properties.displayResolutions.contains(displayResolutions))
properties.displayResolutions << displayResolutions; properties.displayResolutions << displayResolutions;
@ -486,7 +490,7 @@ void MFGrabber::enumVideoCaptureDevices()
di.guid = format; di.guid = format;
properties.valid.append(di); 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++) 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<ColorRgb> &,unsigned int)), this , SLOT(newThreadFrame(unsigned int, const Image<ColorRgb> &, unsigned int))); connect(_thread, SIGNAL(newFrame(unsigned int, const Image<ColorRgb> &,unsigned int)), this , SLOT(newThreadFrame(unsigned int, const Image<ColorRgb> &, 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())) 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) if ( _threadManager._threads[i]->isBusy() == false)
{ {
MFThread* _thread = _threadManager._threads[i]; MFThread* _thread = _threadManager._threads[i];
_thread->setup(i, _pixelFormat, (uint8_t *)frameImageBuffer, size, _width, _height, _lineLength, _subsamp, _cropLeft, _cropTop, _cropBottom, _cropRight, _videoMode, processFrameIndex, _pixelDecimation); _thread->setup(i, _pixelFormat, (uint8_t *)frameImageBuffer, size, _width, _height, _lineLength, _subsamp, _cropLeft, _cropTop, _cropBottom, _cropRight, _videoMode, _flipMode, processFrameIndex, _pixelDecimation);
_threadManager._threads[i]->start();
if (_threadManager._maxThreads > 1)
_threadManager._threads[i]->start();
break; break;
} }
} }
@ -586,7 +587,8 @@ void MFGrabber::setSignalThreshold(double redSignalThreshold, double greenSignal
_noSignalThresholdColor.blue = uint8_t(255*blueSignalThreshold); _noSignalThresholdColor.blue = uint8_t(255*blueSignalThreshold);
_noSignalCounterThreshold = qMax(1, noSignalCounterThreshold); _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) 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; _x_frac_max = horizontalMax;
_y_frac_max = verticalMax; _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() bool MFGrabber::start()
@ -744,6 +747,12 @@ void MFGrabber::setPixelDecimation(int pixelDecimation)
_pixelDecimation = pixelDecimation; _pixelDecimation = pixelDecimation;
} }
void MFGrabber::setFlipMode(QString flipMode)
{
if(_flipMode != parseFlipMode(flipMode))
Grabber::setFlipMode(parseFlipMode(flipMode));
}
void MFGrabber::setDeviceVideoStandard(QString device, VideoStandard videoStandard) void MFGrabber::setDeviceVideoStandard(QString device, VideoStandard videoStandard)
{ {
if(_deviceName != device) if(_deviceName != device)

View File

@ -3,26 +3,31 @@
volatile bool MFThread::_isActive = false; volatile bool MFThread::_isActive = false;
MFThread::MFThread() MFThread::MFThread()
: _localData(nullptr) : _isBusy(false)
, _semaphore(1)
, _localData(nullptr)
, _localDataSize(0) , _localDataSize(0)
, _decompress(nullptr)
, _scalingFactorsCount(0) , _scalingFactorsCount(0)
, _scalingFactors(nullptr) , _scalingFactors(nullptr)
, _isBusy(false) , _transform(nullptr)
, _semaphore(1) , _decompress(nullptr)
, _xform(nullptr)
, _imageResampler() , _imageResampler()
{ {
} }
MFThread::~MFThread() MFThread::~MFThread()
{ {
if (_decompress == nullptr) if (_transform)
tjDestroy(_transform);
if (_decompress)
tjDestroy(_decompress); tjDestroy(_decompress);
if (_localData != NULL) if (_localData)
{ {
free(_localData); free(_localData);
_localData = NULL; _localData = nullptr;
_localDataSize = 0; _localDataSize = 0;
} }
} }
@ -31,9 +36,9 @@ void MFThread::setup(
unsigned int threadIndex, PixelFormat pixelFormat, uint8_t* sharedData, unsigned int threadIndex, PixelFormat pixelFormat, uint8_t* sharedData,
int size, int width, int height, int lineLength, int size, int width, int height, int lineLength,
int subsamp, unsigned cropLeft, unsigned cropTop, unsigned cropBottom, unsigned cropRight, 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; _lineLength = lineLength;
_pixelFormat = pixelFormat; _pixelFormat = pixelFormat;
_size = size; _size = size;
@ -44,26 +49,25 @@ void MFThread::setup(
_cropTop = cropTop; _cropTop = cropTop;
_cropBottom = cropBottom; _cropBottom = cropBottom;
_cropRight = cropRight; _cropRight = cropRight;
_flipMode = flipMode;
_currentFrame = currentFrame; _currentFrame = currentFrame;
_pixelDecimation = pixelDecimation; _pixelDecimation = pixelDecimation;
_imageResampler.setVideoMode(videoMode); _imageResampler.setVideoMode(videoMode);
_imageResampler.setFlipMode(_flipMode);
_imageResampler.setCropping(cropLeft, cropRight, cropTop, cropBottom); _imageResampler.setCropping(cropLeft, cropRight, cropTop, cropBottom);
_imageResampler.setHorizontalPixelDecimation(_pixelDecimation); _imageResampler.setHorizontalPixelDecimation(_pixelDecimation);
_imageResampler.setVerticalPixelDecimation(_pixelDecimation); _imageResampler.setVerticalPixelDecimation(_pixelDecimation);
_imageResampler.setFlipMode(FlipMode::NO_CHANGE);
if (size > _localDataSize) if (size > _localDataSize)
{ {
if (_localData != NULL) if (_localData)
{ tjFree(_localData);
free(_localData);
_localData = NULL; _localData = (uint8_t*)tjAlloc(size + 1);
_localDataSize = 0;
}
_localData = (uint8_t *) malloc(size+1);
_localDataSize = size; _localDataSize = size;
} }
memcpy(_localData, sharedData, size); memcpy(_localData, sharedData, size);
} }
@ -79,7 +83,7 @@ void MFThread::run()
{ {
Image<ColorRgb> image = Image<ColorRgb>(); Image<ColorRgb> image = Image<ColorRgb>();
_imageResampler.processImage(_localData, _width, _height, _lineLength, PixelFormat::BGR24, 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() 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(); _decompress = tjInitDecompress();
_scalingFactors = tjGetScalingFactors (&_scalingFactorsCount); _scalingFactors = tjGetScalingFactors(&_scalingFactorsCount);
tjhandle handle=NULL;
} }
if (tjDecompressHeader2(_decompress, _localData, _size, &_width, &_height, &_subsamp) != 0) if (tjDecompressHeader2(_decompress, _localData, _size, &_width, &_height, &_subsamp) != 0)
@ -153,10 +182,10 @@ void MFThread::processImageMjpeg()
// got image, process it // got image, process it
if ( !(_cropLeft > 0 || _cropTop > 0 || _cropBottom > 0 || _cropRight > 0)) if ( !(_cropLeft > 0 || _cropTop > 0 || _cropBottom > 0 || _cropRight > 0))
emit newFrame(_workerIndex, srcImage, _currentFrame); emit newFrame(_threadIndex, srcImage, _currentFrame);
else else
{ {
// calculate the output size // calculate the output size
int outputWidth = (_width - _cropLeft - _cropRight); int outputWidth = (_width - _cropLeft - _cropRight);
int outputHeight = (_height - _cropTop - _cropBottom); int outputHeight = (_height - _cropTop - _cropBottom);
@ -171,10 +200,12 @@ void MFThread::processImageMjpeg()
unsigned char* dest = (unsigned char*)destImage.memptr() + y*destImage.width()*3; unsigned char* dest = (unsigned char*)destImage.memptr() + y*destImage.width()*3;
memcpy(dest, source, destImage.width()*3); memcpy(dest, source, destImage.width()*3);
free(source); free(source);
source = nullptr;
free(dest); free(dest);
dest = nullptr;
} }
// emit // emit
emit newFrame(_workerIndex, destImage, _currentFrame); emit newFrame(_threadIndex, destImage, _currentFrame);
} }
} }

View File

@ -5,9 +5,9 @@
// qt // qt
#include <QTimer> #include <QTimer>
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) : GrabberWrapper("V4L2:"+device, &_grabber, grabWidth, grabHeight, 10)
, _grabber(device, grabWidth, grabHeight, fps, input, pixelDecimation) , _grabber(device, grabWidth, grabHeight, fps, input, pixelDecimation, flipMode)
{ {
_ggrabber = &_grabber; _ggrabber = &_grabber;
@ -123,6 +123,9 @@ void MFWrapper::handleSettingsUpdate(settings::type type, const QJsonDocument& c
// image size decimation // image size decimation
_grabber.setPixelDecimation(obj["sizeDecimation"].toInt(8)); _grabber.setPixelDecimation(obj["sizeDecimation"].toInt(8));
// flip mode
_grabber.setFlipMode(obj["flip"].toString("no-change"));
// image cropping // image cropping
_grabber.setCropping( _grabber.setCropping(
obj["cropLeft"].toInt(0), obj["cropLeft"].toInt(0),

View File

@ -12,8 +12,8 @@ QtGrabber::QtGrabber(int cropLeft, int cropRight, int cropTop, int cropBottom, i
: Grabber("QTGRABBER", 0, 0, cropLeft, cropRight, cropTop, cropBottom) : Grabber("QTGRABBER", 0, 0, cropLeft, cropRight, cropTop, cropBottom)
, _display(unsigned(display)) , _display(unsigned(display))
, _pixelDecimation(pixelDecimation) , _pixelDecimation(pixelDecimation)
, _screenWidth(0) , _calculatedWidth(0)
, _screenHeight(0) , _calculatedHeight(0)
, _src_x(0) , _src_x(0)
, _src_y(0) , _src_y(0)
, _src_x_max(0) , _src_x_max(0)
@ -98,20 +98,13 @@ int QtGrabber::grabFrame(Image<ColorRgb> & image)
setEnabled(setupDisplay()); setEnabled(setupDisplay());
return -1; 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; y<imageFrame.height(); ++y) QPixmap originalPixmap = _screen->grabWindow(0, _src_x, _src_y, _src_x_max, _src_y_max);
for (int x=0; x<imageFrame.width(); ++x) QImage imageFrame = originalPixmap.toImage().scaled(_calculatedWidth, _calculatedHeight).convertToFormat( QImage::Format_RGB888);
{ image.resize(_calculatedWidth, _calculatedHeight);
QColor inPixel(imageFrame.pixel(x,y));
ColorRgb & outPixel = image(x,y); for (int y = 0; y < imageFrame.height(); y++)
outPixel.red = inPixel.red(); memcpy((unsigned char*)image.memptr() + y * image.width() * 3, (unsigned char*)imageFrame.scanLine(y), imageFrame.width() * 3);
outPixel.green = inPixel.green();
outPixel.blue = inPixel.blue();
}
return 0; return 0;
} }
@ -122,59 +115,59 @@ int QtGrabber::updateScreenDimensions(bool force)
return -1; return -1;
const QRect& geo = _screen->geometry(); 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 // No update required
return 0; return 0;
} }
Info(_log, "Update of screen resolution: [%dx%d] to [%dx%d]", _screenWidth, _screenHeight, geo.right(), geo.bottom()); Info(_log, "Update of screen resolution: [%dx%d] to [%dx%d]", _width, _height, geo.right(), geo.bottom());
_screenWidth = geo.right() - geo.left(); _width = geo.right() - geo.left();
_screenHeight = geo.bottom() - geo.top(); _height = geo.bottom() - geo.top();
int width=0, height=0; int width=0, height=0;
// Image scaling is performed by Qt // Image scaling is performed by Qt
width = (_screenWidth > unsigned(_cropLeft + _cropRight)) width = (_width > (_cropLeft + _cropRight))
? ((_screenWidth - _cropLeft - _cropRight) / _pixelDecimation) ? ((_width - _cropLeft - _cropRight) / _pixelDecimation)
: (_screenWidth / _pixelDecimation); : (_width / _pixelDecimation);
height = (_screenHeight > unsigned(_cropTop + _cropBottom)) height = (_height > (_cropTop + _cropBottom))
? ((_screenHeight - _cropTop - _cropBottom) / _pixelDecimation) ? ((_height - _cropTop - _cropBottom) / _pixelDecimation)
: (_screenHeight / _pixelDecimation); : (_height / _pixelDecimation);
// calculate final image dimensions and adjust top/left cropping in 3D modes // calculate final image dimensions and adjust top/left cropping in 3D modes
switch (_videoMode) switch (_videoMode)
{ {
case VideoMode::VIDEO_3DSBS: case VideoMode::VIDEO_3DSBS:
_width = width /2; _calculatedWidth = width /2;
_height = height; _calculatedHeight = height;
_src_x = _cropLeft / 2; _src_x = _cropLeft / 2;
_src_y = _cropTop; _src_y = _cropTop;
_src_x_max = (_screenWidth / 2) - _cropRight; _src_x_max = (_width / 2) - _cropRight - _cropLeft;
_src_y_max = _screenHeight - _cropBottom; _src_y_max = _height - _cropBottom - _cropTop;
break; break;
case VideoMode::VIDEO_3DTAB: case VideoMode::VIDEO_3DTAB:
_width = width; _calculatedWidth = width;
_height = height / 2; _calculatedHeight = height / 2;
_src_x = _cropLeft; _src_x = _cropLeft;
_src_y = _cropTop / 2; _src_y = _cropTop / 2;
_src_x_max = _screenWidth - _cropRight; _src_x_max = _width - _cropRight - _cropLeft;
_src_y_max = (_screenHeight / 2) - _cropBottom; _src_y_max = (_height / 2) - _cropBottom - _cropTop;
break; break;
case VideoMode::VIDEO_2D: case VideoMode::VIDEO_2D:
default: default:
_width = width; _calculatedWidth = width;
_height = height; _calculatedHeight = height;
_src_x = _cropLeft; _src_x = _cropLeft;
_src_y = _cropTop; _src_y = _cropTop;
_src_x_max = _screenWidth - _cropRight; _src_x_max = _width - _cropRight - _cropLeft;
_src_y_max = _screenHeight - _cropBottom; _src_y_max = _height - _cropBottom - _cropTop;
break; 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; return 1;
} }

View File

@ -4,6 +4,7 @@ Grabber::Grabber(const QString& grabberName, int width, int height, int cropLeft
: _imageResampler() : _imageResampler()
, _useImageResampler(true) , _useImageResampler(true)
, _videoMode(VideoMode::VIDEO_2D) , _videoMode(VideoMode::VIDEO_2D)
, _flipMode(FlipMode::NO_CHANGE)
, _width(width) , _width(width)
, _height(height) , _height(height)
, _fps(15) , _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) void Grabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom)
{ {
if (_width>0 && _height>0) if (_width>0 && _height>0)

View File

@ -52,6 +52,18 @@
"required" : true, "required" : true,
"propertyOrder" : 7 "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" : "width" :
{ {
"type" : "integer", "type" : "integer",
@ -63,8 +75,8 @@
"hidden":true "hidden":true
}, },
"required" : true, "required" : true,
"propertyOrder" : 9, "propertyOrder" : 10,
"comment" : "The 'resolutions' settings are dynamically inserted into the WebUI under PropertyOrder '8'." "comment" : "The 'resolutions' settings are dynamically inserted into the WebUI under PropertyOrder '9'."
}, },
"height" : "height" :
{ {
@ -77,8 +89,8 @@
"hidden":true "hidden":true
}, },
"required" : true, "required" : true,
"propertyOrder" : 10, "propertyOrder" : 11,
"comment" : "The 'resolutions' settings are dynamically inserted into the WebUI under PropertyOrder '6'." "comment" : "The 'resolutions' settings are dynamically inserted into the WebUI under PropertyOrder '9'."
}, },
"fps" : "fps" :
{ {
@ -91,8 +103,8 @@
"hidden":true "hidden":true
}, },
"required" : true, "required" : true,
"propertyOrder" : 12, "propertyOrder" : 13,
"comment" : "The 'framerates' setting is dynamically inserted into the WebUI under PropertyOrder '11'." "comment" : "The 'framerates' setting is dynamically inserted into the WebUI under PropertyOrder '12'."
}, },
"fpsSoftwareDecimation" : "fpsSoftwareDecimation" :
{ {
@ -102,7 +114,7 @@
"maximum" : 60, "maximum" : 60,
"default" : 1, "default" : 1,
"required" : true, "required" : true,
"propertyOrder" : 13 "propertyOrder" : 14
}, },
"sizeDecimation" : "sizeDecimation" :
{ {
@ -112,7 +124,7 @@
"maximum" : 30, "maximum" : 30,
"default" : 6, "default" : 6,
"required" : true, "required" : true,
"propertyOrder" : 14 "propertyOrder" : 15
}, },
"cropLeft" : "cropLeft" :
{ {
@ -122,7 +134,7 @@
"default" : 0, "default" : 0,
"append" : "edt_append_pixel", "append" : "edt_append_pixel",
"required" : true, "required" : true,
"propertyOrder" : 15 "propertyOrder" : 16
}, },
"cropRight" : "cropRight" :
{ {
@ -132,7 +144,7 @@
"default" : 0, "default" : 0,
"append" : "edt_append_pixel", "append" : "edt_append_pixel",
"required" : true, "required" : true,
"propertyOrder" : 16 "propertyOrder" : 17
}, },
"cropTop" : "cropTop" :
{ {
@ -142,7 +154,7 @@
"default" : 0, "default" : 0,
"append" : "edt_append_pixel", "append" : "edt_append_pixel",
"required" : true, "required" : true,
"propertyOrder" : 17 "propertyOrder" : 18
}, },
"cropBottom" : "cropBottom" :
{ {
@ -152,7 +164,7 @@
"default" : 0, "default" : 0,
"append" : "edt_append_pixel", "append" : "edt_append_pixel",
"required" : true, "required" : true,
"propertyOrder" : 18 "propertyOrder" : 19
}, },
"cecDetection" : "cecDetection" :
{ {
@ -160,7 +172,7 @@
"title" : "edt_conf_v4l2_cecDetection_title", "title" : "edt_conf_v4l2_cecDetection_title",
"default" : false, "default" : false,
"required" : true, "required" : true,
"propertyOrder" : 19 "propertyOrder" : 20
}, },
"signalDetection" : "signalDetection" :
{ {
@ -168,7 +180,7 @@
"title" : "edt_conf_v4l2_signalDetection_title", "title" : "edt_conf_v4l2_signalDetection_title",
"default" : false, "default" : false,
"required" : true, "required" : true,
"propertyOrder" : 20 "propertyOrder" : 21
}, },
"redSignalThreshold" : "redSignalThreshold" :
{ {
@ -184,7 +196,7 @@
} }
}, },
"required" : true, "required" : true,
"propertyOrder" : 21 "propertyOrder" : 22
}, },
"greenSignalThreshold" : "greenSignalThreshold" :
{ {
@ -200,7 +212,7 @@
} }
}, },
"required" : true, "required" : true,
"propertyOrder" : 22 "propertyOrder" : 23
}, },
"blueSignalThreshold" : "blueSignalThreshold" :
{ {
@ -216,7 +228,7 @@
} }
}, },
"required" : true, "required" : true,
"propertyOrder" : 23 "propertyOrder" : 24
}, },
"noSignalCounterThreshold" : "noSignalCounterThreshold" :
{ {
@ -231,7 +243,7 @@
} }
}, },
"required" : true, "required" : true,
"propertyOrder" : 24 "propertyOrder" : 25
}, },
"sDVOffsetMin" : "sDVOffsetMin" :
{ {
@ -247,7 +259,7 @@
} }
}, },
"required" : true, "required" : true,
"propertyOrder" : 25 "propertyOrder" : 26
}, },
"sDVOffsetMax" : "sDVOffsetMax" :
{ {
@ -263,7 +275,7 @@
} }
}, },
"required" : true, "required" : true,
"propertyOrder" : 26 "propertyOrder" : 27
}, },
"sDHOffsetMin" : "sDHOffsetMin" :
{ {
@ -279,7 +291,7 @@
} }
}, },
"required" : true, "required" : true,
"propertyOrder" : 27 "propertyOrder" : 28
}, },
"sDHOffsetMax" : "sDHOffsetMax" :
{ {
@ -295,7 +307,7 @@
} }
}, },
"required" : true, "required" : true,
"propertyOrder" : 28 "propertyOrder" : 29
}, },
"hardware_brightness" : "hardware_brightness" :
{ {
@ -303,7 +315,7 @@
"title" : "edt_conf_v4l2_hardware_brightness_title", "title" : "edt_conf_v4l2_hardware_brightness_title",
"default" : 0, "default" : 0,
"required" : true, "required" : true,
"propertyOrder" : 29 "propertyOrder" : 30
}, },
"hardware_contrast" : "hardware_contrast" :
{ {
@ -311,7 +323,7 @@
"title" : "edt_conf_v4l2_hardware_contrast_title", "title" : "edt_conf_v4l2_hardware_contrast_title",
"default" : 0, "default" : 0,
"required" : true, "required" : true,
"propertyOrder" : 30 "propertyOrder" : 31
}, },
"hardware_saturation" : "hardware_saturation" :
{ {
@ -319,7 +331,7 @@
"title" : "edt_conf_v4l2_hardware_saturation_title", "title" : "edt_conf_v4l2_hardware_saturation_title",
"default" : 0, "default" : 0,
"required" : true, "required" : true,
"propertyOrder" : 31 "propertyOrder" : 32
}, },
"hardware_hue" : "hardware_hue" :
{ {
@ -327,7 +339,7 @@
"title" : "edt_conf_v4l2_hardware_hue_title", "title" : "edt_conf_v4l2_hardware_hue_title",
"default" : 0, "default" : 0,
"required" : true, "required" : true,
"propertyOrder" : 32 "propertyOrder" : 33
} }
}, },
"additionalProperties" : true "additionalProperties" : true

View File

@ -605,7 +605,8 @@ void HyperionDaemon::handleSettingsUpdate(settings::type settingsType, const QJs
grabberConfig["height"].toInt(0), grabberConfig["height"].toInt(0),
grabberConfig["fps"].toInt(15), grabberConfig["fps"].toInt(15),
grabberConfig["input"].toInt(-1), grabberConfig["input"].toInt(-1),
grabberConfig["sizeDecimation"].toInt(8)); grabberConfig["sizeDecimation"].toInt(8),
grabberConfig["flip"].toString("auto"));
// Image cropping // Image cropping
_mfGrabber->setCropping( _mfGrabber->setCropping(