diff --git a/.github/workflows/push-master.yml b/.github/workflows/push-master.yml index 647e5f99..6b2b9fcf 100644 --- a/.github/workflows/push-master.yml +++ b/.github/workflows/push-master.yml @@ -134,6 +134,11 @@ jobs: run: | choco install --no-progress python nsis openssl directx-sdk -y + - name: Install libjpeg-turbo + run: | + Invoke-WebRequest https://netcologne.dl.sourceforge.net/project/libjpeg-turbo/2.0.6/libjpeg-turbo-2.0.6-vc64.exe -OutFile libjpeg-turbo.exe + .\libjpeg-turbo /S + - name: Set up x64 build architecture environment shell: cmd run: call "${{env.VCINSTALLDIR}}\Auxiliary\Build\vcvars64.bat" diff --git a/.vscode/launch.json b/.vscode/launch.json index ad6f92cc..36fde755 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,7 @@ "name": "(Linux) hyperiond", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/build/bin/hyperiond", + "program": "${command:cmake.launchTargetDirectory}/hyperiond", "args": ["-d"], "stopAtEntry": false, "cwd": "${workspaceFolder}", @@ -27,7 +27,7 @@ "name": "(Windows) hyperiond", "type": "cppvsdbg", "request": "launch", - "program": "${workspaceFolder}/build/bin/Debug/hyperiond.exe", + "program": "${command:cmake.launchTargetDirectory}/hyperiond", "args": ["-d"], "stopAtEntry": false, "cwd": "${workspaceFolder}", diff --git a/include/grabber/MFThread.h b/include/grabber/MFThread.h index 1faaefb0..04281554 100644 --- a/include/grabber/MFThread.h +++ b/include/grabber/MFThread.h @@ -46,8 +46,7 @@ private: #ifdef HAVE_TURBO_JPEG tjhandle _decompress; - int _scalingFactorsCount = 0; - tjscalingfactor* _scalingFactors = nullptr; + tjscalingfactor* _scalingFactors; #endif static volatile bool _isActive; @@ -57,6 +56,7 @@ private: PixelFormat _pixelFormat; uint8_t* _localData; int _localDataSize; + int _scalingFactorsCount; int _size; int _width; int _height; @@ -78,16 +78,7 @@ class MFThreadManager : public QObject public: MFThreadManager() : _threads(nullptr) { - int select = QThread::idealThreadCount(); - - if (select >= 2 && select <= 3) - select = 2; - else if (select > 3 && select <= 5) - select = 3; - else if (select > 5) - select = 4; - - _maxThreads = qMax(select, 1); + _maxThreads = qBound(1, ((QThread::idealThreadCount() * 3) / 2), 12); } ~MFThreadManager() @@ -100,6 +91,7 @@ public: _threads[i]->deleteLater(); _threads[i] = nullptr; } + delete[] _threads; _threads = nullptr; } diff --git a/include/utils/ImageResampler.h b/include/utils/ImageResampler.h index 62adb2b9..7aba7a40 100644 --- a/include/utils/ImageResampler.h +++ b/include/utils/ImageResampler.h @@ -9,12 +9,13 @@ class ImageResampler { public: ImageResampler(); - ~ImageResampler(); + ~ImageResampler() {} - void setHorizontalPixelDecimation(int decimator); - void setVerticalPixelDecimation(int decimator); + void setHorizontalPixelDecimation(int decimator) { _horizontalDecimation = decimator; } + void setVerticalPixelDecimation(int decimator) { _verticalDecimation = decimator; } void setCropping(int cropLeft, int cropRight, int cropTop, int cropBottom); - void setVideoMode(VideoMode mode); + void setVideoMode(VideoMode mode) { _videoMode = mode; } + void setFlipMode(FlipMode mode) { _flipMode = mode; } void processImage(const uint8_t * data, int width, int height, int lineLength, PixelFormat pixelFormat, Image & outputImage) const; private: @@ -25,5 +26,6 @@ private: int _cropTop; int _cropBottom; VideoMode _videoMode; + FlipMode _flipMode; }; diff --git a/include/utils/PixelFormat.h b/include/utils/PixelFormat.h index c9b911c2..7bd2769d 100644 --- a/include/utils/PixelFormat.h +++ b/include/utils/PixelFormat.h @@ -12,6 +12,8 @@ enum class PixelFormat { BGR24, RGB32, BGR32, + NV12, + I420, #ifdef HAVE_JPEG_DECODER MJPEG, #endif @@ -47,6 +49,14 @@ inline PixelFormat parsePixelFormat(const QString& pixelFormat) { return PixelFormat::BGR32; } + else if (format.compare("i420") == 0) + { + return PixelFormat::I420; + } + else if (format.compare("nv12") == 0) + { + return PixelFormat::NV12; + } #ifdef HAVE_JPEG_DECODER else if (format.compare("mjpeg") == 0) { @@ -63,35 +73,97 @@ inline QString pixelFormatToString(const PixelFormat& pixelFormat) if ( pixelFormat == PixelFormat::YUYV) { - return "yuyv"; + return "YUYV"; } else if (pixelFormat == PixelFormat::UYVY) { - return "uyvy"; + return "UYVY"; } else if (pixelFormat == PixelFormat::BGR16) { - return "bgr16"; + return "BGR16"; } else if (pixelFormat == PixelFormat::BGR24) { - return "bgr24"; + return "BGR24"; } else if (pixelFormat == PixelFormat::RGB32) { - return "rgb32"; + return "RGB32"; } else if (pixelFormat == PixelFormat::BGR32) { - return "bgr32"; + return "BGR32"; + } + else if (pixelFormat == PixelFormat::I420) + { + return "I420"; + } + else if (pixelFormat == PixelFormat::NV12) + { + return "NV12"; } #ifdef HAVE_JPEG_DECODER else if (pixelFormat == PixelFormat::MJPEG) { - return "mjpeg"; + return "MJPEG"; } #endif // return the default NO_CHANGE return "NO_CHANGE"; } + +/** + * Enumeration of the possible flip modes + */ + +enum class FlipMode +{ + HORIZONTAL = 1, + VERTICAL = 2, + BOTH = HORIZONTAL | VERTICAL, + NO_CHANGE = 4 +}; + +inline FlipMode parseFlipMode(const QString& flipMode) +{ + // convert to lower case + QString mode = flipMode.toLower(); + + if (flipMode.compare("horizontal") == 0) + { + return FlipMode::HORIZONTAL; + } + else if (flipMode.compare("vertical") == 0) + { + return FlipMode::VERTICAL; + } + else if (flipMode.compare("both") == 0) + { + return FlipMode::BOTH; + } + + // return the default NO_CHANGE + return FlipMode::NO_CHANGE; +} + +inline QString flipModeToString(const FlipMode& flipMode) +{ + + if ( flipMode == FlipMode::HORIZONTAL) + { + return "horizontal"; + } + else if (flipMode == FlipMode::VERTICAL) + { + return "vertical"; + } + else if (flipMode == FlipMode::BOTH) + { + return "both"; + } + + // return the default NO_CHANGE + return "NO_CHANGE"; +} diff --git a/libsrc/grabber/mediafoundation/MFGrabber.cpp b/libsrc/grabber/mediafoundation/MFGrabber.cpp index 62633330..b749fbd7 100644 --- a/libsrc/grabber/mediafoundation/MFGrabber.cpp +++ b/libsrc/grabber/mediafoundation/MFGrabber.cpp @@ -4,9 +4,12 @@ static PixelFormat GetPixelFormatForGuid(const GUID guid) { if (IsEqualGUID(guid, MFVideoFormat_RGB32)) return PixelFormat::RGB32; + if (IsEqualGUID(guid, MFVideoFormat_RGB24)) return PixelFormat::BGR24; if (IsEqualGUID(guid, MFVideoFormat_YUY2)) return PixelFormat::YUYV; if (IsEqualGUID(guid, MFVideoFormat_UYVY)) return PixelFormat::UYVY; if (IsEqualGUID(guid, MFVideoFormat_MJPG)) return PixelFormat::MJPEG; + if (IsEqualGUID(guid, MFVideoFormat_NV12)) return PixelFormat::NV12; + if (IsEqualGUID(guid, MFVideoFormat_I420)) return PixelFormat::I420; return PixelFormat::NO_CHANGE; }; @@ -40,7 +43,7 @@ MFGrabber::MFGrabber(const QString & device, unsigned width, unsigned height, un setInput(input); setWidthHeight(width, height); setFramerate(fps); - // setDeviceVideoStandard(device, videoStandard); + // setDeviceVideoStandard(device, videoStandard); // TODO CoInitializeEx(0, COINIT_MULTITHREADED); _hr = MFStartup(MF_VERSION, MFSTARTUP_NOSOCKET); @@ -134,7 +137,7 @@ bool MFGrabber::init() continue; } - if (strict) + if (strict && (val.fps <= 60 || _fps != 15)) foundIndex = i; } @@ -394,16 +397,32 @@ bool MFGrabber::init_device(QString deviceName, DevicePropertiesItem props) } break; - case PixelFormat::RGB32: + case PixelFormat::BGR24: + case PixelFormat::MJPEG: { - _frameByteSize = props.x * props.y * 4; + _frameByteSize = props.x * props.y * 3; _lineLength = props.x * 3; } break; - case PixelFormat::MJPEG: + case PixelFormat::RGB32: { - _lineLength = props.x * 3; + _frameByteSize = props.x * props.y * 4; + _lineLength = props.x * 4; + } + break; + + case PixelFormat::NV12: + { + _frameByteSize = (6 * props.x * props.y) / 4; + _lineLength = props.x; + } + break; + + case PixelFormat::I420: + { + _frameByteSize = (6 * props.x * props.y) / 4; + _lineLength = props.x; } break; } @@ -499,6 +518,8 @@ void MFGrabber::enumVideoCaptureDevices() di.pf = pixelformat; 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))); } } diff --git a/libsrc/grabber/mediafoundation/MFSourceReaderCB.h b/libsrc/grabber/mediafoundation/MFSourceReaderCB.h index 5ff330b6..e15ff81c 100644 --- a/libsrc/grabber/mediafoundation/MFSourceReaderCB.h +++ b/libsrc/grabber/mediafoundation/MFSourceReaderCB.h @@ -74,7 +74,6 @@ public: hrStatus = pSample->ConvertToContiguousBuffer(&buffer); if (SUCCEEDED(hrStatus)) { - BYTE* data = nullptr; DWORD maxLength = 0, currentLength = 0; @@ -89,11 +88,11 @@ public: else error = QString("buffer->Lock failed => %1").arg(hrStatus); - SAFE_RELEASE(buffer); } else error = QString("pSample->ConvertToContiguousBuffer failed => %1").arg(hrStatus); + SAFE_RELEASE(buffer); } else error = "pSample is NULL"; diff --git a/libsrc/grabber/mediafoundation/MFThread.cpp b/libsrc/grabber/mediafoundation/MFThread.cpp index dba7da6b..6d817476 100644 --- a/libsrc/grabber/mediafoundation/MFThread.cpp +++ b/libsrc/grabber/mediafoundation/MFThread.cpp @@ -3,13 +3,15 @@ volatile bool MFThread::_isActive = false; -MFThread::MFThread(): - _localData(nullptr), - _localDataSize(0), - _decompress(nullptr), - _isBusy(false), - _semaphore(1), - _imageResampler() +MFThread::MFThread() + : _localData(nullptr) + , _localDataSize(0) + , _decompress(nullptr) + , _scalingFactorsCount(0) + , _scalingFactors(nullptr) + , _isBusy(false) + , _semaphore(1) + , _imageResampler() { } @@ -50,6 +52,7 @@ void MFThread::setup( _imageResampler.setCropping(cropLeft, cropRight, cropTop, cropBottom); _imageResampler.setHorizontalPixelDecimation(_pixelDecimation); _imageResampler.setVerticalPixelDecimation(_pixelDecimation); + _imageResampler.setFlipMode(FlipMode::HORIZONTAL); if (size > _localDataSize) { @@ -110,6 +113,7 @@ void MFThread::processImageMjpeg() { _decompress = tjInitDecompress(); _scalingFactors = tjGetScalingFactors (&_scalingFactorsCount); + tjhandle handle=NULL; } if (tjDecompressHeader2(_decompress, _localData, _size, &_width, &_height, &_subsamp) != 0) @@ -167,6 +171,8 @@ void MFThread::processImageMjpeg() unsigned char* source = (unsigned char*)srcImage.memptr() + (y + _cropTop)*srcImage.width()*3 + _cropLeft*3; unsigned char* dest = (unsigned char*)destImage.memptr() + y*destImage.width()*3; memcpy(dest, source, destImage.width()*3); + free(source); + free(dest); } // emit diff --git a/libsrc/utils/ImageResampler.cpp b/libsrc/utils/ImageResampler.cpp index faa5f575..1d0002fc 100644 --- a/libsrc/utils/ImageResampler.cpp +++ b/libsrc/utils/ImageResampler.cpp @@ -10,23 +10,10 @@ ImageResampler::ImageResampler() , _cropTop(0) , _cropBottom(0) , _videoMode(VideoMode::VIDEO_2D) + , _flipMode(FlipMode::NO_CHANGE) { } -ImageResampler::~ImageResampler() -{ -} - -void ImageResampler::setHorizontalPixelDecimation(int decimator) -{ - _horizontalDecimation = decimator; -} - -void ImageResampler::setVerticalPixelDecimation(int decimator) -{ - _verticalDecimation = decimator; -} - void ImageResampler::setCropping(int cropLeft, int cropRight, int cropTop, int cropBottom) { _cropLeft = cropLeft; @@ -35,15 +22,12 @@ void ImageResampler::setCropping(int cropLeft, int cropRight, int cropTop, int c _cropBottom = cropBottom; } -void ImageResampler::setVideoMode(VideoMode mode) -{ - _videoMode = mode; -} - void ImageResampler::processImage(const uint8_t * data, int width, int height, int lineLength, PixelFormat pixelFormat, Image &outputImage) const { int cropRight = _cropRight; int cropBottom = _cropBottom; + int xDestFlip = 0, yDestFlip = 0; + int uOffset, vOffset; // handle 3D mode switch (_videoMode) @@ -67,11 +51,40 @@ void ImageResampler::processImage(const uint8_t * data, int width, int height, i for (int yDest = 0, ySource = _cropTop + (_verticalDecimation >> 1); yDest < outputHeight; ySource += _verticalDecimation, ++yDest) { int yOffset = lineLength * ySource; + if (pixelFormat == PixelFormat::NV12) + { + uOffset = (height + ySource / 2) * lineLength; + } + else if (pixelFormat == PixelFormat::I420) + { + uOffset = height * lineLength + ((ySource * lineLength) / 4); + vOffset = ((5 * height * lineLength) * 4) + ((ySource * lineLength) / 4); + } for (int xDest = 0, xSource = _cropLeft + (_horizontalDecimation >> 1); xDest < outputWidth; xSource += _horizontalDecimation, ++xDest) { - ColorRgb & rgb = outputImage(xDest, yDest); + switch (_flipMode) + { + case FlipMode::HORIZONTAL: + xDestFlip = xDest; + yDestFlip = outputHeight-yDest-1; + break; + case FlipMode::VERTICAL: + xDestFlip = outputWidth-xDest-1; + yDestFlip = yDest; + break; + case FlipMode::BOTH: + xDestFlip = outputWidth-xDest-1; + yDestFlip = outputHeight-yDest-1; + break; + case FlipMode::NO_CHANGE: + xDestFlip = xDest; + yDestFlip = yDest; + break; + } + + ColorRgb &rgb = outputImage(xDestFlip, yDestFlip); switch (pixelFormat) { case PixelFormat::UYVY: @@ -124,6 +137,25 @@ void ImageResampler::processImage(const uint8_t * data, int width, int height, i rgb.red = data[index+2]; } break; + case PixelFormat::NV12: + { + int index = yOffset + xSource; + uint8_t y = data[index]; + uint8_t u = data[uOffset + ((xSource >> 1) << 1)]; + uint8_t v = data[uOffset + ((xSource >> 1) << 1) + 1]; + ColorSys::yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue); + } + break; + case PixelFormat::I420: + { + int index = yOffset + xSource; + uint8_t y = data[index]; + uint8_t u = data[uOffset + (xSource >> 1)]; + uint8_t v = data[vOffset + (xSource >> 1)]; + ColorSys::yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue); + break; + } + break; #ifdef HAVE_JPEG_DECODER case PixelFormat::MJPEG: break;