From e6714b21f92650b484666b36c124b54d0d321a57 Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Tue, 21 May 2024 19:37:43 +0200 Subject: [PATCH 1/6] Fix Philip Hue APIv2 support without Entertainment group defined (#1743) --- CHANGELOG.md | 1 + .../js/wizards/LedDevice_philipshue.js | 20 +++++++++----- libsrc/leddevice/LedDeviceWrapper.cpp | 2 +- .../leddevice/dev_net/LedDevicePhilipsHue.cpp | 26 +++++++++---------- libsrc/leddevice/dev_net/ProviderRestApi.cpp | 12 ++++++++- 5 files changed, 40 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75577551..4ea84fc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Workaround to address Web UI keeps forcing browser to download the html instead (#1692) - Fixed: Kodi Color Calibration, Refactor Wizards (#1674) - Fixed: Token Dialog not closing +- Fixed: Philip Hue APIv2 support without Entertainment group defined (#1742) **JSON-API** - Refactored JSON-API to ensure consistent authorization behaviour across sessions and single requests with token authorization. diff --git a/assets/webconfig/js/wizards/LedDevice_philipshue.js b/assets/webconfig/js/wizards/LedDevice_philipshue.js index 92f1c173..bfc33bd8 100644 --- a/assets/webconfig/js/wizards/LedDevice_philipshue.js +++ b/assets/webconfig/js/wizards/LedDevice_philipshue.js @@ -502,7 +502,9 @@ const philipshueWizard = (() => { let serviceID; if (isAPIv2Ready) { - serviceID = lightLocation.service.rid; + if (lightLocation) { + serviceID = lightLocation.service.rid; + } } if (position.startsWith("entertainment")) { @@ -531,7 +533,7 @@ const philipshueWizard = (() => { // Layout per manual settings let maxSegments = 1; - if (isAPIv2Ready) { + if (isAPIv2Ready && serviceID) { const service = hueEntertainmentServices.find(service => service.id === serviceID); maxSegments = service.segments.max_segments; } @@ -593,10 +595,10 @@ const philipshueWizard = (() => { d.enableAttempts = parseInt(conf_editor.getEditor("root.generalOptions.enableAttempts").getValue()); d.enableAttemptsInterval = parseInt(conf_editor.getEditor("root.generalOptions.enableAttemptsInterval").getValue()); - d.useEntertainmentAPI = isEntertainmentReady; + d.useEntertainmentAPI = isEntertainmentReady && (d.groupId !== ""); d.useAPIv2 = isAPIv2Ready; - if (isEntertainmentReady) { + if (d.useEntertainmentAPI) { d.hardwareLedCount = channelNumber; if (window.serverConfig.device.type !== d.type) { //smoothing on, if new device @@ -803,12 +805,18 @@ const philipshueWizard = (() => { "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121" ]; - if (isEntertainmentReady) { + if (isEntertainmentReady && hueEntertainmentConfigs.length > 0) { lightOptions.unshift("entertainment_center"); lightOptions.unshift("entertainment"); } else { lightOptions.unshift("disabled"); - groupLights = Object.keys(hueLights); + if (isAPIv2Ready) { + for (const light in hueLights) { + groupLights.push(hueLights[light].id); + } + } else { + groupLights = Object.keys(hueLights); + } } $('.lidsb').html(""); diff --git a/libsrc/leddevice/LedDeviceWrapper.cpp b/libsrc/leddevice/LedDeviceWrapper.cpp index 838a5b37..53532074 100644 --- a/libsrc/leddevice/LedDeviceWrapper.cpp +++ b/libsrc/leddevice/LedDeviceWrapper.cpp @@ -65,7 +65,7 @@ void LedDeviceWrapper::createLedDevice(const QJsonObject& config) connect(thread, &QThread::started, _ledDevice, &LedDevice::start); // further signals - connect(this, &LedDeviceWrapper::updateLeds, _ledDevice, &LedDevice::updateLeds, Qt::QueuedConnection); + connect(this, &LedDeviceWrapper::updateLeds, _ledDevice, &LedDevice::updateLeds, Qt::BlockingQueuedConnection); connect(this, &LedDeviceWrapper::switchOn, _ledDevice, &LedDevice::switchOn, Qt::BlockingQueuedConnection); connect(this, &LedDeviceWrapper::switchOff, _ledDevice, &LedDevice::switchOff, Qt::BlockingQueuedConnection); diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp index f5f8d24b..e3df5c7d 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp @@ -895,6 +895,7 @@ void LedDevicePhilipsHueBridge::setBridgeDetails(const QJsonDocument &doc, bool log( "API-Version", "%u.%u.%u", _api_major, _api_minor, _api_patch ); log( "API v2 ready", "%s", _isAPIv2Ready ? "Yes" : "No" ); log( "Entertainment ready", "%s", _isHueEntertainmentReady ? "Yes" : "No" ); + log( "Use Entertainment API", "%s", _useEntertainmentAPI ? "Yes" : "No" ); log( "DIYHue", "%s", _isDiyHue ? "Yes" : "No" ); } } @@ -1799,11 +1800,11 @@ bool LedDevicePhilipsHue::init(const QJsonObject &deviceConfig) if (LedDevicePhilipsHueBridge::init(_devConfig)) { - log( "Off on Black", "%s", _switchOffOnBlack ? "Yes" : "No" ); - log( "Brightness Factor", "%f", _brightnessFactor ); - log( "Transition Time", "%d", _transitionTime ); - log( "Restore Original State", "%s", _isRestoreOrigState ? "Yes" : "No" ); - log( "Use Hue Entertainment API", "%s", _useEntertainmentAPI ? "Yes" : "No" ); + log("Off on Black", "%s", _switchOffOnBlack ? "Yes" : "No" ); + log("Brightness Factor", "%f", _brightnessFactor ); + log("Transition Time", "%d", _transitionTime ); + log("Restore Original State", "%s", _isRestoreOrigState ? "Yes" : "No" ); + log("Use Hue Entertainment API", "%s", _useEntertainmentAPI ? "Yes" : "No" ); log("Brightness Threshold", "%f", _blackLevel); log("CandyGamma", "%s", _candyGamma ? "Yes" : "No" ); log("Time powering off when black", "%s", _onBlackTimeToPowerOff ? "Yes" : "No" ); @@ -1864,7 +1865,7 @@ bool LedDevicePhilipsHue::setLights() Debug(_log, "Lights configured: %d", configuredLightsCount ); if (updateLights( getLightMap())) { - if (_useApiV2) + if (_useApiV2 && _useEntertainmentAPI) { _channelsCount = getGroupChannelsCount (_groupId); @@ -2208,15 +2209,14 @@ int LedDevicePhilipsHue::write(const std::vector & ledValues) int rc {0}; if (_isOn) { - if (!_useApiV2) - { - rc = writeSingleLights( ledValues ); - } - if (_useEntertainmentAPI && _isInitLeds) { rc= writeStreamData(ledValues); } + else + { + rc = writeSingleLights( ledValues ); + } } return rc; } @@ -2482,7 +2482,7 @@ void LedDevicePhilipsHue::setColor(PhilipsHueLight& light, CiColor& color) QJsonObject colorXY; colorXY[API_X_COORDINATE] = color.x; colorXY[API_Y_COORDINATE] = color.y; - cmd.insert(API_COLOR, QJsonObject {{API_DURATION, colorXY }}); + cmd.insert(API_COLOR, QJsonObject {{API_XY_COORDINATES, colorXY }}); cmd.insert(API_DIMMING, QJsonObject {{API_BRIGHTNESS, bri }}); } else @@ -2556,7 +2556,7 @@ void LedDevicePhilipsHue::setState(PhilipsHueLight& light, bool on, const CiColo QJsonObject colorXY; colorXY[API_X_COORDINATE] = color.x; colorXY[API_Y_COORDINATE] = color.y; - cmd.insert(API_COLOR, QJsonObject {{API_DURATION, colorXY }}); + cmd.insert(API_COLOR, QJsonObject {{API_XY_COORDINATES, colorXY }}); cmd.insert(API_DIMMING, QJsonObject {{API_BRIGHTNESS, bri }}); } else diff --git a/libsrc/leddevice/dev_net/ProviderRestApi.cpp b/libsrc/leddevice/dev_net/ProviderRestApi.cpp index 7321810f..e981d00a 100644 --- a/libsrc/leddevice/dev_net/ProviderRestApi.cpp +++ b/libsrc/leddevice/dev_net/ProviderRestApi.cpp @@ -30,7 +30,8 @@ enum HttpStatusCode { BadRequest = 400, UnAuthorized = 401, Forbidden = 403, - NotFound = 404 + NotFound = 404, + TooManyRequests = 429 }; } //End of constants @@ -336,6 +337,15 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply) case HttpStatusCode::NotFound: advise = "Check Resource given"; break; + case HttpStatusCode::TooManyRequests: + { + QString retryAfterTime = response.getHeader("Retry-After"); + if (!retryAfterTime.isEmpty()) + { + advise = "Retry-After: " + response.getHeader("Retry-After"); + } + } + break; default: advise = httpReason; break; From bde5d156a99fd0f6342fb1d462e8a635ba913895 Mon Sep 17 00:00:00 2001 From: Thinner77 <59623671+Thinner77@users.noreply.github.com> Date: Fri, 24 May 2024 09:13:33 +0200 Subject: [PATCH 2/6] faster imageresampler (#1744) --- include/grabber/video/v4l2/V4L2Grabber.h | 9 + libsrc/grabber/video/v4l2/V4L2Grabber.cpp | 16 ++ libsrc/utils/ImageResampler.cpp | 190 ++++++++++++++-------- 3 files changed, 147 insertions(+), 68 deletions(-) diff --git a/include/grabber/video/v4l2/V4L2Grabber.h b/include/grabber/video/v4l2/V4L2Grabber.h index 5ac00738..a9c1e687 100644 --- a/include/grabber/video/v4l2/V4L2Grabber.h +++ b/include/grabber/video/v4l2/V4L2Grabber.h @@ -1,5 +1,11 @@ #pragma once +#define FRAME_BENCH + +#ifdef FRAME_BENCH + #include +#endif + // stl includes #include #include @@ -166,6 +172,9 @@ private: double _x_frac_max; double _y_frac_max; +#ifdef FRAME_BENCH + QElapsedTimer _frameTimer; +#endif QSocketNotifier *_streamNotifier; bool _initialized, _reload; diff --git a/libsrc/grabber/video/v4l2/V4L2Grabber.cpp b/libsrc/grabber/video/v4l2/V4L2Grabber.cpp index d4a73ab9..081b978b 100644 --- a/libsrc/grabber/video/v4l2/V4L2Grabber.cpp +++ b/libsrc/grabber/video/v4l2/V4L2Grabber.cpp @@ -1079,6 +1079,22 @@ void V4L2Grabber::newThreadFrame(Image image) } else emit newFrame(image); + +#ifdef FRAME_BENCH + // calculate average frametime + if (_currentFrame > 1) + { + if (_currentFrame % 100 == 0) + { + Debug(_log, "%d: avg. frametime=%.02fms / %.02fms", int(_currentFrame), _frameTimer.restart()/100.0, 1000.0/_fps); + } + } + else + { + Debug(_log, "%d: frametimer started", int(_currentFrame)); + _frameTimer.start(); + } +#endif } int V4L2Grabber::xioctl(int request, void *arg) diff --git a/libsrc/utils/ImageResampler.cpp b/libsrc/utils/ImageResampler.cpp index 94b606da..483e35a9 100644 --- a/libsrc/utils/ImageResampler.cpp +++ b/libsrc/utils/ImageResampler.cpp @@ -29,9 +29,6 @@ void ImageResampler::processImage(const uint8_t * data, int width, int height, i int cropTop = _cropTop; int cropBottom = _cropBottom; - int xDestFlip = 0, yDestFlip = 0; - int uOffset = 0, vOffset = 0; - // handle 3D mode switch (_videoMode) { @@ -53,118 +50,175 @@ void ImageResampler::processImage(const uint8_t * data, int width, int height, i outputImage.resize(outputWidth, outputHeight); - for (int yDest = 0, ySource = cropTop + (_verticalDecimation >> 1); yDest < outputHeight; ySource += _verticalDecimation, ++yDest) + int xDestStart, xDestEnd; + int yDestStart, yDestEnd; + + switch (_flipMode) { - int yOffset = lineLength * ySource; - if (pixelFormat == PixelFormat::NV12) - { - uOffset = (height + ySource / 2) * lineLength; - } - else if (pixelFormat == PixelFormat::I420) - { - uOffset = width * height + (ySource/2) * width/2; - vOffset = width * height * 1.25 + (ySource/2) * width/2; - } + case FlipMode::NO_CHANGE: + xDestStart = 0; + xDestEnd = outputWidth-1; + yDestStart = 0; + yDestEnd = outputHeight-1; + break; + case FlipMode::HORIZONTAL: + xDestStart = 0; + xDestEnd = outputWidth-1; + yDestStart = -(outputHeight-1); + yDestEnd = 0; + break; + case FlipMode::VERTICAL: + xDestStart = -(outputWidth-1); + xDestEnd = 0; + yDestStart = 0; + yDestEnd = outputHeight-1; + break; + case FlipMode::BOTH: + xDestStart = -(outputWidth-1); + xDestEnd = 0; + yDestStart = -(outputHeight-1); + yDestEnd = 0; + break; + } - for (int xDest = 0, xSource = cropLeft + (_horizontalDecimation >> 1); xDest < outputWidth; xSource += _horizontalDecimation, ++xDest) + switch (pixelFormat) + { + case PixelFormat::UYVY: { - switch (_flipMode) + for (int yDest = yDestStart, ySource = cropTop + (_verticalDecimation >> 1); yDest <= yDestEnd; ySource += _verticalDecimation, ++yDest) { - 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: + for (int xDest = xDestStart, xSource = cropLeft + (_horizontalDecimation >> 1); xDest <= xDestEnd; xSource += _horizontalDecimation, ++xDest) { - int index = yOffset + (xSource << 1); + ColorRgb & rgb = outputImage(abs(xDest), abs(yDest)); + int index = lineLength * ySource + (xSource << 1); uint8_t y = data[index+1]; uint8_t u = ((xSource&1) == 0) ? data[index ] : data[index-2]; uint8_t v = ((xSource&1) == 0) ? data[index+2] : data[index ]; ColorSys::yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue); } - break; - case PixelFormat::YUYV: + } + break; + } + + case PixelFormat::YUYV: + { + for (int yDest = yDestStart, ySource = cropTop + (_verticalDecimation >> 1); yDest <= yDestEnd; ySource += _verticalDecimation, ++yDest) + { + for (int xDest = xDestStart, xSource = cropLeft + (_horizontalDecimation >> 1); xDest <= xDestEnd; xSource += _horizontalDecimation, ++xDest) { - int index = yOffset + (xSource << 1); + ColorRgb & rgb = outputImage(abs(xDest), abs(yDest)); + int index = lineLength * ySource + (xSource << 1); uint8_t y = data[index]; uint8_t u = ((xSource&1) == 0) ? data[index+1] : data[index-1]; uint8_t v = ((xSource&1) == 0) ? data[index+3] : data[index+1]; ColorSys::yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue); } - break; - case PixelFormat::BGR16: + } + break; + } + + case PixelFormat::BGR16: + { + for (int yDest = yDestStart, ySource = cropTop + (_verticalDecimation >> 1); yDest <= yDestEnd; ySource += _verticalDecimation, ++yDest) + { + for (int xDest = xDestStart, xSource = cropLeft + (_horizontalDecimation >> 1); xDest <= xDestEnd; xSource += _horizontalDecimation, ++xDest) { - int index = yOffset + (xSource << 1); + ColorRgb & rgb = outputImage(abs(xDest), abs(yDest)); + int index = lineLength * ySource + (xSource << 1); rgb.blue = (data[index] & 0x1f) << 3; rgb.green = (((data[index+1] & 0x7) << 3) | (data[index] & 0xE0) >> 5) << 2; rgb.red = (data[index+1] & 0xF8); } - break; - case PixelFormat::BGR24: + } + break; + } + + case PixelFormat::BGR24: + { + for (int yDest = yDestStart, ySource = cropTop + (_verticalDecimation >> 1); yDest <= yDestEnd; ySource += _verticalDecimation, ++yDest) + { + for (int xDest = xDestStart, xSource = cropLeft + (_horizontalDecimation >> 1); xDest <= xDestEnd; xSource += _horizontalDecimation, ++xDest) { - int index = yOffset + (xSource << 1) + xSource; + ColorRgb & rgb = outputImage(abs(xDest), abs(yDest)); + int index = lineLength * ySource + (xSource << 1) + xSource; rgb.blue = data[index ]; rgb.green = data[index+1]; rgb.red = data[index+2]; } - break; - case PixelFormat::RGB32: + } + break; + } + + case PixelFormat::RGB32: + { + for (int yDest = yDestStart, ySource = cropTop + (_verticalDecimation >> 1); yDest <= yDestEnd; ySource += _verticalDecimation, ++yDest) + { + for (int xDest = xDestStart, xSource = cropLeft + (_horizontalDecimation >> 1); xDest <= xDestEnd; xSource += _horizontalDecimation, ++xDest) { - int index = yOffset + (xSource << 2); + ColorRgb & rgb = outputImage(abs(xDest), abs(yDest)); + int index = lineLength * ySource + (xSource << 2); rgb.red = data[index ]; rgb.green = data[index+1]; rgb.blue = data[index+2]; } - break; - case PixelFormat::BGR32: + } + break; + } + + case PixelFormat::BGR32: + { + for (int yDest = yDestStart, ySource = cropTop + (_verticalDecimation >> 1); yDest <= yDestEnd; ySource += _verticalDecimation, ++yDest) + { + for (int xDest = xDestStart, xSource = cropLeft + (_horizontalDecimation >> 1); xDest <= xDestEnd; xSource += _horizontalDecimation, ++xDest) { - int index = yOffset + (xSource << 2); + ColorRgb & rgb = outputImage(abs(xDest), abs(yDest)); + int index = lineLength * ySource + (xSource << 2); rgb.blue = data[index ]; rgb.green = data[index+1]; rgb.red = data[index+2]; } - break; - case PixelFormat::NV12: + } + break; + } + + case PixelFormat::NV12: + { + for (int yDest = yDestStart, ySource = cropTop + (_verticalDecimation >> 1); yDest <= yDestEnd; ySource += _verticalDecimation, ++yDest) + { + int uOffset = (height + ySource / 2) * lineLength; + for (int xDest = xDestStart, xSource = cropLeft + (_horizontalDecimation >> 1); xDest <= xDestEnd; xSource += _horizontalDecimation, ++xDest) { - uint8_t y = data[yOffset + xSource]; + ColorRgb & rgb = outputImage(abs(xDest), abs(yDest)); + uint8_t y = data[lineLength * ySource + xSource]; 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: + } + break; + } + + case PixelFormat::I420: + { + for (int yDest = yDestStart, ySource = cropTop + (_verticalDecimation >> 1); yDest <= yDestEnd; ySource += _verticalDecimation, ++yDest) + { + int uOffset = width * height + (ySource/2) * width/2; + int vOffset = width * height * 1.25 + (ySource/2) * width/2; + for (int xDest = xDestStart, xSource = cropLeft + (_horizontalDecimation >> 1); xDest <= xDestEnd; xSource += _horizontalDecimation, ++xDest) { - int y = data[yOffset + xSource]; + ColorRgb & rgb = outputImage(abs(xDest), abs(yDest)); + int y = data[lineLength * ySource + xSource]; int u = data[uOffset + (xSource >> 1)]; int v = data[vOffset + (xSource >> 1)]; ColorSys::yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue); - break; } - break; - case PixelFormat::MJPEG: - break; - case PixelFormat::NO_CHANGE: - Error(Logger::getInstance("ImageResampler"), "Invalid pixel format given"); - break; } + break; } + case PixelFormat::MJPEG: + break; + case PixelFormat::NO_CHANGE: + Error(Logger::getInstance("ImageResampler"), "Invalid pixel format given"); + break; } } From 5bcbe599d3c8b564c6fd4336d06ad6698bb0c369 Mon Sep 17 00:00:00 2001 From: Thinner77 <59623671+Thinner77@users.noreply.github.com> Date: Fri, 24 May 2024 17:57:10 +0200 Subject: [PATCH 3/6] Fix flipmode for BGR24 special handling (#1739) --- libsrc/grabber/video/EncoderThread.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/libsrc/grabber/video/EncoderThread.cpp b/libsrc/grabber/video/EncoderThread.cpp index 254472c0..e891c821 100644 --- a/libsrc/grabber/video/EncoderThread.cpp +++ b/libsrc/grabber/video/EncoderThread.cpp @@ -122,18 +122,6 @@ void EncoderThread::process() else #endif { - if (_pixelFormat == PixelFormat::BGR24) - { - if (_flipMode == FlipMode::NO_CHANGE) - _imageResampler.setFlipMode(FlipMode::HORIZONTAL); - else if (_flipMode == FlipMode::HORIZONTAL) - _imageResampler.setFlipMode(FlipMode::NO_CHANGE); - else if (_flipMode == FlipMode::VERTICAL) - _imageResampler.setFlipMode(FlipMode::BOTH); - else if (_flipMode == FlipMode::BOTH) - _imageResampler.setFlipMode(FlipMode::VERTICAL); - } - Image image = Image(); _imageResampler.processImage( _localData, From b390d16b0c47ea96a15a18335b73332bb673e564 Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Fri, 24 May 2024 18:24:26 +0200 Subject: [PATCH 4/6] Disable V42L Benchmark Code --- include/grabber/video/v4l2/V4L2Grabber.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/grabber/video/v4l2/V4L2Grabber.h b/include/grabber/video/v4l2/V4L2Grabber.h index a9c1e687..3db42711 100644 --- a/include/grabber/video/v4l2/V4L2Grabber.h +++ b/include/grabber/video/v4l2/V4L2Grabber.h @@ -1,6 +1,6 @@ #pragma once -#define FRAME_BENCH +#define NOFRAME_BENCH #ifdef FRAME_BENCH #include From c0ddca3c5bca0bea96ebe111889a80df066daa09 Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Sun, 26 May 2024 17:59:50 +0200 Subject: [PATCH 5/6] Fix Formula qt5 was renamed to qt@5 --- .github/workflows/qt5_6.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/qt5_6.yml b/.github/workflows/qt5_6.yml index 9cd59df8..fe5fa41d 100644 --- a/.github/workflows/qt5_6.yml +++ b/.github/workflows/qt5_6.yml @@ -117,7 +117,7 @@ jobs: echo '::group::Update/Install dependencies' brew untap --force homebrew/core homebrew/cask brew update || true - brew install qt${{ inputs.qt_version }} vulkan-headers ninja || true + brew install qt@${{ inputs.qt_version }} vulkan-headers ninja || true echo '::endgroup::' - name: 👷 Build From 8c303c8b9cf396546a1ea6da8d00a93cdf948fdd Mon Sep 17 00:00:00 2001 From: David Sansome Date: Thu, 30 May 2024 21:39:46 +1000 Subject: [PATCH 6/6] Windows: Add a DDA grabber for much better performance (#1745) * Add a DXDI DDA grabber * Change all names to camel case * Handle cropping and pixel decimation * Try more persistently to restart capture after an error occurred. These can happen when changing resolution, or resuming from sleep. --- CHANGELOG.md | 7 +- CMakeLists.txt | 6 + HyperionConfig.h.in | 3 + doc/development/CompileHowto.md | 2 +- include/grabber/GrabberConfig.h | 8 +- include/grabber/dda/DDAGrabber.h | 75 ++++++ include/grabber/dda/DDAWrapper.h | 24 ++ libsrc/api/JsonInfo.cpp | 4 + libsrc/grabber/CMakeLists.txt | 4 + libsrc/grabber/dda/CMakeLists.txt | 12 + libsrc/grabber/dda/DDAGrabber.cpp | 357 +++++++++++++++++++++++++++++ libsrc/grabber/dda/DDAWrapper.cpp | 20 ++ libsrc/hyperion/GrabberWrapper.cpp | 4 + src/hyperiond/CMakeLists.txt | 4 + src/hyperiond/hyperiond.cpp | 8 +- src/hyperiond/hyperiond.h | 6 + 16 files changed, 535 insertions(+), 9 deletions(-) create mode 100644 include/grabber/dda/DDAGrabber.h create mode 100644 include/grabber/dda/DDAWrapper.h create mode 100644 libsrc/grabber/dda/CMakeLists.txt create mode 100644 libsrc/grabber/dda/DDAGrabber.cpp create mode 100644 libsrc/grabber/dda/DDAWrapper.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ea84fc1..4e770924 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,12 +14,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Support gaps on Matrix Layout (#1696) +- Windows: Added a new grabber that uses the DXGI DDA (Desktop Duplication API). This has much better performance than the DX grabber as it does more of its work on the GPU. **JSON-API** - New subscription support for event updates, i.e. `Suspend, Resume, Idle, idleResume, Restart, Quit`. - Support direct or multiple instance addressing via single requests (#809) - Support of `serverinfo` subcommands: `getInfo, subscribe, unsubscribe, getSubscriptions, getSubscriptionCommands` -- [Overview](https://github.com/hyperion-project/hyperion.ng/blob/API_Auth/doc/development/JSON-API%20_Commands_Overview.md) of API commands and subscription updates +- [Overview](https://github.com/hyperion-project/hyperion.ng/blob/API_Auth/doc/development/JSON-API%20_Commands_Overview.md) of API commands and subscription updates ### Changed @@ -35,7 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Refactored JSON-API to ensure consistent authorization behaviour across sessions and single requests with token authorization. - Provide additional error details with API responses, esp. on JSON parsing, validation or token errors. - Generate random TANs for every API request from the Hyperion UI -- Fixed: Handling of IP4 addresses wrapped in IPv6 for external network connections- +- Fixed: Handling of IP4 addresses wrapped in IPv6 for external network connections- ### Removed @@ -135,7 +136,7 @@ Note: The wizard will configure an APIv2 capable bridge always with Entertainmen - Support streaming to individual WLED segments (requires WLED 0.13.3+). To allow segment streaming, enable "Realtime - Use main segment only" in WLED's Sync Interfaces setup screen - Allow to keep WLED powered on after streaming and restoring state -- Allow to Disable / Enable all instances (#970) by +- Allow to Disable / Enable all instances (#970) by - Suspend/Resume support for Linux and Windows (#1493,#1282, #978). Suspend/Resume/Restart is supported via API, UI, Systray and hyperion-remote - Idle scenario via Screen Locking (Linux/Windows), Screensaver invokation (Linux), hyperion-remote or API diff --git a/CMakeLists.txt b/CMakeLists.txt index 7818a6f1..7cb7366a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,7 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(DEFAULT_AMLOGIC OFF) set(DEFAULT_DISPMANX OFF) set(DEFAULT_DX OFF) +set(DEFAULT_DDA OFF) set(DEFAULT_MF OFF) set(DEFAULT_OSX OFF) set(DEFAULT_QT ON ) @@ -121,6 +122,7 @@ if(${CMAKE_SYSTEM} MATCHES "Linux") set(DEFAULT_CEC ON) elseif (WIN32) set(DEFAULT_DX ON) + set(DEFAULT_DDA ON) set(DEFAULT_MF ON) else() set(DEFAULT_FB OFF) @@ -227,6 +229,7 @@ if(HYPERION_LIGHT) SET ( DEFAULT_AMLOGIC OFF ) SET ( DEFAULT_DISPMANX OFF ) SET ( DEFAULT_DX OFF ) + SET ( DEFAULT_DDA OFF ) SET ( DEFAULT_FB OFF ) SET ( DEFAULT_MF OFF ) SET ( DEFAULT_OSX OFF ) @@ -264,6 +267,9 @@ message(STATUS "ENABLE_DISPMANX = ${ENABLE_DISPMANX}") option(ENABLE_DX "Enable the DirectX grabber" ${DEFAULT_DX}) message(STATUS "ENABLE_DX = ${ENABLE_DX}") +option(ENABLE_DDA "Enable the DXGI DDA grabber" ${DEFAULT_DDA}) +message(STATUS "ENABLE_DDA = ${ENABLE_DDA}") + if(ENABLE_AMLOGIC) set(ENABLE_FB ON) else() diff --git a/HyperionConfig.h.in b/HyperionConfig.h.in index 324b703b..086c4ef0 100644 --- a/HyperionConfig.h.in +++ b/HyperionConfig.h.in @@ -9,6 +9,9 @@ // Define to enable the DirectX grabber #cmakedefine ENABLE_DX +// Define to enable the DXGI DDA (Desktop Duplication API) grabber +#cmakedefine ENABLE_DDA + // Define to enable the framebuffer grabber // Define to enable the Audio grabber #cmakedefine ENABLE_AUDIO diff --git a/doc/development/CompileHowto.md b/doc/development/CompileHowto.md index dcef8093..2de4a770 100644 --- a/doc/development/CompileHowto.md +++ b/doc/development/CompileHowto.md @@ -128,7 +128,7 @@ We assume a 64bit Windows 10. Install the following; - [CMake (Windows win64-x64 installer)](https://cmake.org/download/) (Check: Add to PATH) - [Visual Studio 2022 Community Edition](https://visualstudio.microsoft.com/downloads/#visual-studio-community-2022) - Select 'Desktop development with C++' - - On the right, just select `MSVC v143 VS 2022 C++ x64/x86-Buildtools` and latest `Windows 10 SDK`. Everything else is not needed. + - On the right, just select `MSVC v143 VS 2022 C++ x64/x86-Buildtools`, `C++ ATL for latest v143 build tools (x86 & x64)` and latest `Windows 10 SDK`. Everything else is not needed. - [Win64 OpenSSL v1.1.1w](https://slproweb.com/products/Win32OpenSSL.html) ([direct link](https://slproweb.com/download/Win64OpenSSL-1_1_1w.exe)) - [Python 3 (Windows x86-64 executable installer)](https://www.python.org/downloads/windows/) (Check: Add to PATH and Debug Symbols) - Open a console window and execute `pip install aqtinstall`. diff --git a/include/grabber/GrabberConfig.h b/include/grabber/GrabberConfig.h index f3a575c2..0b14132c 100644 --- a/include/grabber/GrabberConfig.h +++ b/include/grabber/GrabberConfig.h @@ -27,6 +27,10 @@ #include #endif +#ifdef ENABLE_DDA +#include +#endif + #if defined(ENABLE_X11) #include #endif @@ -35,10 +39,6 @@ #include #endif -#if defined(ENABLE_DX) -#include -#endif - #if defined(ENABLE_FB) #include #endif diff --git a/include/grabber/dda/DDAGrabber.h b/include/grabber/dda/DDAGrabber.h new file mode 100644 index 00000000..561434f4 --- /dev/null +++ b/include/grabber/dda/DDAGrabber.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +#include +#include + +class DDAGrabberImpl; + +class DDAGrabber : public Grabber +{ +public: + DDAGrabber(int display = 0, int cropLeft = 0, int cropRight = 0, int cropTop = 0, int cropBottom = 0); + + virtual ~DDAGrabber(); + + /// + /// Captures a single snapshot of the display and writes the data to the given image. The + /// provided image should have the same dimensions as the configured values (_width and _height) + /// + /// @param[out] image The snapped screenshot + /// + int grabFrame(Image &image); + + /// + /// @brief Set a new video mode + /// + void setVideoMode(VideoMode mode) override; + + /// + /// @brief Apply new width/height values, overwrite Grabber.h implementation + /// + bool setWidthHeight(int /* width */, int /*height*/) override + { + return true; + } + + /// + /// @brief Apply new pixelDecimation + /// + bool setPixelDecimation(int pixelDecimation) override; + + /// + /// Set the crop values + /// @param cropLeft Left pixel crop + /// @param cropRight Right pixel crop + /// @param cropTop Top pixel crop + /// @param cropBottom Bottom pixel crop + /// + void setCropping(int cropLeft, int cropRight, int cropTop, int cropBottom); + + /// + /// @brief Apply display index + /// + bool setDisplayIndex(int index) override; + + /// @brief Discover QT screens available (for configuration). + /// + /// @param[in] params Parameters used to overwrite discovery default behaviour + /// + /// @return A JSON structure holding a list of devices found + /// + QJsonObject discover(const QJsonObject ¶ms); + +private: + /// + /// @brief Setup a new capture display, will free the previous one + /// @return True on success, false if no display is found + /// + bool restartCapture(); + +private: + std::unique_ptr d; +}; diff --git a/include/grabber/dda/DDAWrapper.h b/include/grabber/dda/DDAWrapper.h new file mode 100644 index 00000000..8b8266a3 --- /dev/null +++ b/include/grabber/dda/DDAWrapper.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +class DDAWrapper : public GrabberWrapper +{ +public: + static constexpr const char *GRABBERTYPE = "DDA"; + + DDAWrapper(int updateRate_Hz = GrabberWrapper::DEFAULT_RATE_HZ, int display = 0, + int pixelDecimation = GrabberWrapper::DEFAULT_PIXELDECIMATION, int cropLeft = 0, int cropRight = 0, + int cropTop = 0, int cropBottom = 0); + + DDAWrapper(const QJsonDocument &grabberConfig = QJsonDocument()); + + virtual ~DDAWrapper(){}; + +public slots: + virtual void action(); + +private: + DDAGrabber _grabber; +}; diff --git a/libsrc/api/JsonInfo.cpp b/libsrc/api/JsonInfo.cpp index e2a73ffe..7c3f8129 100644 --- a/libsrc/api/JsonInfo.cpp +++ b/libsrc/api/JsonInfo.cpp @@ -592,6 +592,10 @@ QJsonArray JsonInfo::discoverScreenInputs(const QJsonObject& params) const discoverGrabber(screenInputs, params); #endif +#ifdef ENABLE_DDA + discoverGrabber(screenInputs, params); +#endif + #ifdef ENABLE_X11 discoverGrabber(screenInputs, params); #endif diff --git a/libsrc/grabber/CMakeLists.txt b/libsrc/grabber/CMakeLists.txt index 75f8c529..daf0bc57 100644 --- a/libsrc/grabber/CMakeLists.txt +++ b/libsrc/grabber/CMakeLists.txt @@ -34,6 +34,10 @@ if(ENABLE_DX) add_subdirectory(directx) endif(ENABLE_DX) +if(ENABLE_DDA) + add_subdirectory(dda) +endif(ENABLE_DDA) + if(ENABLE_AUDIO) add_subdirectory(audio) endif() diff --git a/libsrc/grabber/dda/CMakeLists.txt b/libsrc/grabber/dda/CMakeLists.txt new file mode 100644 index 00000000..e78ecacc --- /dev/null +++ b/libsrc/grabber/dda/CMakeLists.txt @@ -0,0 +1,12 @@ +add_library(dda-grabber + ${CMAKE_SOURCE_DIR}/include/grabber/dda/DDAGrabber.h + ${CMAKE_SOURCE_DIR}/include/grabber/dda/DDAWrapper.h + ${CMAKE_SOURCE_DIR}/libsrc/grabber/dda/DDAGrabber.cpp + ${CMAKE_SOURCE_DIR}/libsrc/grabber/dda/DDAWrapper.cpp +) + +target_link_libraries(dda-grabber + hyperion + d3d11.lib + dxgi.lib +) diff --git a/libsrc/grabber/dda/DDAGrabber.cpp b/libsrc/grabber/dda/DDAGrabber.cpp new file mode 100644 index 00000000..aea46046 --- /dev/null +++ b/libsrc/grabber/dda/DDAGrabber.cpp @@ -0,0 +1,357 @@ +#include "grabber/dda/DDAGrabber.h" + +#include +#include +#include +#include +#include + +#pragma comment(lib, "d3d9.lib") +#pragma comment(lib, "dxva2.lib") + +namespace +{ +// Driver types supported. +constexpr D3D_DRIVER_TYPE kDriverTypes[] = { + D3D_DRIVER_TYPE_HARDWARE, + D3D_DRIVER_TYPE_WARP, + D3D_DRIVER_TYPE_REFERENCE, +}; + +// Feature levels supported. +D3D_FEATURE_LEVEL kFeatureLevels[] = {D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, + D3D_FEATURE_LEVEL_9_1}; + +// Returns true if the two texture descriptors are compatible for copying. +bool areTextureDescriptionsCompatible(D3D11_TEXTURE2D_DESC a, D3D11_TEXTURE2D_DESC b) +{ + return a.Width == b.Width && a.Height == b.Height && a.MipLevels == b.MipLevels && a.ArraySize == b.ArraySize && + a.Format == b.Format; +} + +} // namespace + +// Logs a message along with the hex error HRESULT. +#define LOG_ERROR(hr, msg) Error(_log, msg ": 0x%x", hr) + +// Checks if the HRESULT is an error, and if so, logs it and returns from the +// current function. +#define RETURN_IF_ERROR(hr, msg, returnValue) \ + if (FAILED(hr)) \ + { \ + LOG_ERROR(hr, msg); \ + return returnValue; \ + } + +// Checks if the condition is false, and if so, logs an error and returns from +// the current function. +#define RET_CHECK(cond, returnValue) \ + if (!(cond)) \ + { \ + Error(_log, "Assertion failed: " #cond); \ + return returnValue; \ + } + +// Private implementation. These member variables are here and not in the .h +// so we don't have to include in the header and pollute everything +// else that includes it. +class DDAGrabberImpl +{ +public: + int display = 0; + int desktopWidth = 0; + int desktopHeight = 0; + + // Created in the constructor. + CComPtr device; + CComPtr deviceContext; + CComPtr dxgiDevice; + CComPtr dxgiAdapter; + + // Created in restartCapture - only valid while desktop capture is in + // progress. + CComPtr desktopDuplication; + CComPtr intermediateTexture; + D3D11_TEXTURE2D_DESC intermediateTextureDesc; +}; + +DDAGrabber::DDAGrabber(int display, int cropLeft, int cropRight, int cropTop, int cropBottom) + : Grabber("GRABBER-DDA", cropLeft, cropRight, cropTop, cropBottom), d(new DDAGrabberImpl) +{ + d->display = display; + + HRESULT hr = S_OK; + + // Iterate through driver types until we find one that succeeds. + D3D_FEATURE_LEVEL featureLevel; + for (D3D_DRIVER_TYPE driverType : kDriverTypes) + { + hr = D3D11CreateDevice(nullptr, driverType, nullptr, 0, kFeatureLevels, std::size(kFeatureLevels), + D3D11_SDK_VERSION, &d->device, &featureLevel, &d->deviceContext); + if (SUCCEEDED(hr)) + { + break; + } + } + RETURN_IF_ERROR(hr, "CreateDevice failed", ); + + // Get the DXGI factory. + hr = d->device.QueryInterface(&d->dxgiDevice); + RETURN_IF_ERROR(hr, "Failed to get DXGI device", ); + + // Get the factory's adapter. + hr = d->dxgiDevice->GetAdapter(&d->dxgiAdapter); + RETURN_IF_ERROR(hr, "Failed to get DXGI Adapter", ); +} + +DDAGrabber::~DDAGrabber() +{ +} + +bool DDAGrabber::restartCapture() +{ + if (!d->dxgiAdapter) + { + return false; + } + + HRESULT hr = S_OK; + + d->desktopDuplication.Release(); + + // Get the output that was selected. + CComPtr output; + hr = d->dxgiAdapter->EnumOutputs(d->display, &output); + RETURN_IF_ERROR(hr, "Failed to get output", false); + + // Get the descriptor which has the size of the display. + DXGI_OUTPUT_DESC desc; + hr = output->GetDesc(&desc); + RETURN_IF_ERROR(hr, "Failed to get output description", false); + + d->desktopWidth = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left; + d->desktopHeight = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top; + _width = (d->desktopWidth - _cropLeft - _cropRight) / _pixelDecimation; + _height = (d->desktopHeight - _cropTop - _cropBottom) / _pixelDecimation; + Info(_log, "Desktop size: %dx%d, cropping=%d,%d,%d,%d, decimation=%d, final image size=%dx%d", d->desktopWidth, + d->desktopHeight, _cropLeft, _cropTop, _cropRight, _cropBottom, _pixelDecimation, _width, _height); + + // Get the DXGIOutput1 interface. + CComPtr output1; + hr = output.QueryInterface(&output1); + RETURN_IF_ERROR(hr, "Failed to get output1", false); + + // Create the desktop duplication interface. + hr = output1->DuplicateOutput(d->device, &d->desktopDuplication); + RETURN_IF_ERROR(hr, "Failed to create desktop duplication interface", false); + + return true; +} + +int DDAGrabber::grabFrame(Image &image) +{ + // Do nothing if we're disabled. + if (!_isEnabled) + { + return 0; + } + + // Start the capture if it's not already running. + if (!d->desktopDuplication && !restartCapture()) + { + return -1; + } + + HRESULT hr = S_OK; + + // Release the last frame, if any. + hr = d->desktopDuplication->ReleaseFrame(); + if (FAILED(hr) && hr != DXGI_ERROR_INVALID_CALL) + { + LOG_ERROR(hr, "Failed to release frame"); + } + + // Acquire the next frame. + CComPtr desktopResource; + DXGI_OUTDUPL_FRAME_INFO frameInfo; + hr = d->desktopDuplication->AcquireNextFrame(INFINITE, &frameInfo, &desktopResource); + if (hr == DXGI_ERROR_ACCESS_LOST || hr == DXGI_ERROR_INVALID_CALL) + { + if (!restartCapture()) + { + return -1; + } + return 0; + } + if (hr == DXGI_ERROR_WAIT_TIMEOUT) + { + // This shouldn't happen since we specified an INFINITE timeout. + return 0; + } + RETURN_IF_ERROR(hr, "Failed to acquire next frame", 0); + + // Get the 2D texture. + CComPtr texture; + hr = desktopResource.QueryInterface(&texture); + RETURN_IF_ERROR(hr, "Failed to get 2D texture", 0); + + // The texture we acquired is on the GPU and can't be accessed from the CPU, + // so we have to copy it into another texture that can. + D3D11_TEXTURE2D_DESC textureDesc; + texture->GetDesc(&textureDesc); + + // Create a new intermediate texture if we haven't done so already, or the + // existing one is incompatible with the acquired texture (i.e. it has + // different dimensions). + if (!d->intermediateTexture || !areTextureDescriptionsCompatible(d->intermediateTextureDesc, textureDesc)) + { + Info(_log, "Creating intermediate texture"); + d->intermediateTexture.Release(); + + d->intermediateTextureDesc = textureDesc; + d->intermediateTextureDesc.Usage = D3D11_USAGE_STAGING; + d->intermediateTextureDesc.BindFlags = 0; + d->intermediateTextureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + d->intermediateTextureDesc.MiscFlags = 0; + + hr = d->device->CreateTexture2D(&d->intermediateTextureDesc, nullptr, &d->intermediateTexture); + RETURN_IF_ERROR(hr, "Failed to create intermediate texture", 0); + } + + // Copy the texture to the intermediate texture. + d->deviceContext->CopyResource(d->intermediateTexture, texture); + RETURN_IF_ERROR(hr, "Failed to copy texture", 0); + + // Map the texture so we can access its pixels. + D3D11_MAPPED_SUBRESOURCE resource; + hr = d->deviceContext->Map(d->intermediateTexture, 0, D3D11_MAP_READ, 0, &resource); + RETURN_IF_ERROR(hr, "Failed to map texture", 0); + + // Copy the texture to the output image. + RET_CHECK(textureDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM, 0); + + ColorRgb *dest = image.memptr(); + for (size_t destY = 0, srcY = _cropTop; destY < image.height(); destY++, srcY += _pixelDecimation) + { + uint32_t *src = + reinterpret_cast(reinterpret_cast(resource.pData) + srcY * resource.RowPitch) + + _cropLeft; + for (size_t destX = 0; destX < image.width(); destX++, src += _pixelDecimation, dest++) + { + *dest = ColorRgb{static_cast(((*src) >> 16) & 0xff), static_cast(((*src) >> 8) & 0xff), + static_cast(((*src) >> 0) & 0xff)}; + } + } + + return 0; +} + +void DDAGrabber::setVideoMode(VideoMode mode) +{ + Grabber::setVideoMode(mode); + restartCapture(); +} + +bool DDAGrabber::setPixelDecimation(int pixelDecimation) +{ + if (Grabber::setPixelDecimation(pixelDecimation)) + return restartCapture(); + + return false; +} + +void DDAGrabber::setCropping(int cropLeft, int cropRight, int cropTop, int cropBottom) +{ + // Grabber::setCropping rejects the cropped size if it is larger than _width + // and _height, so temporarily set those back to the original pre-cropped full + // desktop sizes first. They'll be set back to the cropped sizes by + // restartCapture. + _width = d->desktopWidth; + _height = d->desktopHeight; + Grabber::setCropping(cropLeft, cropRight, cropTop, cropBottom); + restartCapture(); +} + +bool DDAGrabber::setDisplayIndex(int index) +{ + bool rc = true; + if (d->display != index) + { + d->display = index; + rc = restartCapture(); + } + return rc; +} + +QJsonObject DDAGrabber::discover(const QJsonObject ¶ms) +{ + QJsonObject ret; + if (!d->dxgiAdapter) + { + return ret; + } + + HRESULT hr = S_OK; + + // Enumerate through the outputs. + QJsonArray videoInputs; + for (int i = 0;; ++i) + { + CComPtr output; + hr = d->dxgiAdapter->EnumOutputs(i, &output); + if (!output || !SUCCEEDED(hr)) + { + break; + } + + // Get the output description. + DXGI_OUTPUT_DESC desc; + hr = output->GetDesc(&desc); + if (FAILED(hr)) + { + Error(_log, "Failed to get output description"); + continue; + } + + // Add it to the JSON. + const int width = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left; + const int height = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top; + videoInputs.append(QJsonObject{ + {"inputIdx", i}, + {"name", QString::fromWCharArray(desc.DeviceName)}, + {"formats", + QJsonArray{ + QJsonObject{ + {"resolutions", + QJsonArray{ + QJsonObject{ + {"width", width}, + {"height", height}, + {"fps", QJsonArray{1, 5, 10, 15, 20, 25, 30, 40, 50, 60, 120, 144}}, + }, + }}, + }, + }}, + }); + } + + ret["video_inputs"] = videoInputs; + if (!videoInputs.isEmpty()) + { + ret["device"] = "dda"; + ret["device_name"] = "DXGI DDA"; + ret["type"] = "screen"; + ret["default"] = QJsonObject{ + {"video_input", + QJsonObject{ + {"inputIdx", 0}, + {"resolution", + QJsonObject{ + {"fps", 60}, + }}, + }}, + }; + } + + return ret; +} diff --git a/libsrc/grabber/dda/DDAWrapper.cpp b/libsrc/grabber/dda/DDAWrapper.cpp new file mode 100644 index 00000000..e0a8147f --- /dev/null +++ b/libsrc/grabber/dda/DDAWrapper.cpp @@ -0,0 +1,20 @@ +#include "grabber/dda/DDAWrapper.h" + +DDAWrapper::DDAWrapper(int updateRate_Hz, int display, int pixelDecimation, int cropLeft, int cropRight, int cropTop, + int cropBottom) + : GrabberWrapper(GRABBERTYPE, &_grabber, updateRate_Hz), _grabber(display, cropLeft, cropRight, cropTop, cropBottom) + +{ + _grabber.setPixelDecimation(pixelDecimation); +} + +DDAWrapper::DDAWrapper(const QJsonDocument &grabberConfig) + : DDAWrapper(GrabberWrapper::DEFAULT_RATE_HZ, 0, GrabberWrapper::DEFAULT_PIXELDECIMATION, 0, 0, 0, 0) +{ + this->handleSettingsUpdate(settings::SYSTEMCAPTURE, grabberConfig); +} + +void DDAWrapper::action() +{ + transferFrame(_grabber); +} diff --git a/libsrc/hyperion/GrabberWrapper.cpp b/libsrc/hyperion/GrabberWrapper.cpp index d7cc4a8f..a5f48b62 100644 --- a/libsrc/hyperion/GrabberWrapper.cpp +++ b/libsrc/hyperion/GrabberWrapper.cpp @@ -173,6 +173,10 @@ QStringList GrabberWrapper::availableGrabbers(GrabberTypeFilter type) #ifdef ENABLE_DX grabbers << "dx"; #endif + + #ifdef ENABLE_DDA + grabbers << "dda"; + #endif } if (type == GrabberTypeFilter::VIDEO || type == GrabberTypeFilter::ALL) diff --git a/src/hyperiond/CMakeLists.txt b/src/hyperiond/CMakeLists.txt index 8ecbce4c..092260ad 100644 --- a/src/hyperiond/CMakeLists.txt +++ b/src/hyperiond/CMakeLists.txt @@ -118,6 +118,10 @@ if(ENABLE_DX) target_link_libraries(${PROJECT_NAME} directx-grabber) endif (ENABLE_DX) +if(ENABLE_DDA) + target_link_libraries(${PROJECT_NAME} dda-grabber) +endif (ENABLE_DDA) + if(ENABLE_CEC) target_link_libraries(${PROJECT_NAME} cechandler) endif (ENABLE_CEC) diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index 6c71475d..1b1bb739 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -425,7 +425,7 @@ void HyperionDaemon::handleSettingsUpdate(settings::type settingsType, const QJs void HyperionDaemon::updateScreenGrabbers(const QJsonDocument& grabberConfig) { -#if !defined(ENABLE_DISPMANX) && !defined(ENABLE_OSX) && !defined(ENABLE_FB) && !defined(ENABLE_X11) && !defined(ENABLE_XCB) && !defined(ENABLE_AMLOGIC) && !defined(ENABLE_QT) && !defined(ENABLE_DX) +#if !defined(ENABLE_DISPMANX) && !defined(ENABLE_OSX) && !defined(ENABLE_FB) && !defined(ENABLE_X11) && !defined(ENABLE_XCB) && !defined(ENABLE_AMLOGIC) && !defined(ENABLE_QT) && !defined(ENABLE_DX) && !defined(ENABLE_DDA) Info(_log, "No screen capture supported on this platform"); return; #endif @@ -469,6 +469,12 @@ void HyperionDaemon::updateScreenGrabbers(const QJsonDocument& grabberConfig) startGrabber(_screenGrabber, grabberConfig); } #endif +#ifdef ENABLE_DDA + else if (type == "dda") + { + startGrabber(_screenGrabber, grabberConfig); + } +#endif #ifdef ENABLE_FB else if (type == "framebuffer") { diff --git a/src/hyperiond/hyperiond.h b/src/hyperiond/hyperiond.h index 70c5b74d..cf64c134 100644 --- a/src/hyperiond/hyperiond.h +++ b/src/hyperiond/hyperiond.h @@ -64,6 +64,12 @@ typedef QObject DirectXWrapper; #endif +#ifdef ENABLE_DDA + #include +#else + typedef QObject DDAWrapper; +#endif + #include #ifdef ENABLE_AUDIO #include