Merge remote-tracking branch 'origin/master' into config

This commit is contained in:
LordGrey
2024-08-01 23:08:14 +02:00
58 changed files with 1964 additions and 232 deletions

View File

@@ -36,7 +36,12 @@ jobs:
if: ${{ matrix.language == 'cpp' }} if: ${{ matrix.language == 'cpp' }}
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install --yes git cmake build-essential qtbase5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5x11extras5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-image0-dev libxcb-util0-dev libxcb-shm0-dev libxcb-render0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev sudo apt-get install --yes git build-essential qtbase5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5x11extras5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-image0-dev libxcb-util0-dev libxcb-shm0-dev libxcb-render0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev libftdi1-dev
- name: Temporarily downgrade CMake to 3.28.3 # Please remove if GitHub has updated Cmake (greater than 3.30.0)
uses: jwlawson/actions-setup-cmake@v2
with:
cmake-version: '3.28.3'
- name: 🔁 Initialize CodeQL - name: 🔁 Initialize CodeQL
uses: github/codeql-action/init@v3 uses: github/codeql-action/init@v3

View File

@@ -117,9 +117,14 @@ jobs:
echo '::group::Update/Install dependencies' echo '::group::Update/Install dependencies'
brew untap --force homebrew/core homebrew/cask brew untap --force homebrew/core homebrew/cask
brew update || true brew update || true
brew install qt${{ inputs.qt_version }} vulkan-headers ninja || true brew install qt@${{ inputs.qt_version }} vulkan-headers ninja libftdi || true
echo '::endgroup::' echo '::endgroup::'
- name: Temporarily downgrade CMake to 3.28.3 # Please remove if GitHub has updated Cmake (greater than 3.30.0)
uses: jwlawson/actions-setup-cmake@v2
with:
cmake-version: '3.28.3'
- name: 👷 Build - name: 👷 Build
shell: bash shell: bash
run: ./.github/scripts/build.sh run: ./.github/scripts/build.sh
@@ -163,23 +168,30 @@ jobs:
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: C:\Users\runneradmin\AppData\Local\Temp\chocolatey path: C:\Users\runneradmin\AppData\Local\Temp\chocolatey
key: ${{ runner.os }}${{ inputs.qt_version == '6' && '-chocolatey-qt6' || '-chocolatey' }} key: ${{ runner.os }}${{ '-chocolatey' }}
- name: 📥 Install DirectX SDK, OpenSSL, libjpeg-turbo ${{ inputs.qt_version == '6' && 'and Vulkan-SDK' || '' }} - name: 📥 Install DirectX SDK, OpenSSL, libjpeg-turbo
shell: powershell shell: powershell
run: | run: |
choco install --no-progress directx-sdk ${{env.VULKAN_SDK}} -y choco install --no-progress directx-sdk -y
choco install --no-progress ${{env.OPENSSL}} -y choco install --no-progress ${{env.OPENSSL}} -y
Invoke-WebRequest https://netcologne.dl.sourceforge.net/project/libjpeg-turbo/3.0.1/libjpeg-turbo-3.0.1-vc64.exe -OutFile libjpeg-turbo.exe -UserAgent NativeHost Invoke-WebRequest https://netcologne.dl.sourceforge.net/project/libjpeg-turbo/3.0.1/libjpeg-turbo-3.0.1-vc64.exe -OutFile libjpeg-turbo.exe -UserAgent NativeHost
.\libjpeg-turbo /S .\libjpeg-turbo /S
env: env:
VULKAN_SDK: ${{ inputs.qt_version == '6' && 'vulkan-sdk' || '' }}
OPENSSL: ${{ inputs.qt_version == '6' && 'openssl' || 'openssl --version=1.1.1.2100' }} OPENSSL: ${{ inputs.qt_version == '6' && 'openssl' || 'openssl --version=1.1.1.2100' }}
- name: 📥 Install Qt - name: Install Vulkan SDK
uses: jurplel/install-qt-action@v3 if: ${{ inputs.qt_version == '6' }}
uses: jakoch/install-vulkan-sdk-action@v1.0.4
with: with:
version: ${{ inputs.qt_version == '6' && '6.5.2' || '5.15.2' }} install_runtime: false
cache: true
stripdown: true
- name: 📥 Install Qt
uses: jurplel/install-qt-action@v4
with:
version: ${{ inputs.qt_version == '6' && '6.7' || '5.15.*' }}
target: 'desktop' target: 'desktop'
modules: ${{ inputs.qt_version == '6' && 'qtserialport' || '' }} modules: ${{ inputs.qt_version == '6' && 'qtserialport' || '' }}
arch: 'win64_msvc2019_64' arch: 'win64_msvc2019_64'
@@ -190,6 +202,11 @@ jobs:
shell: cmd shell: cmd
run: call "${{env.VCINSTALLDIR}}\Auxiliary\Build\vcvars64.bat" run: call "${{env.VCINSTALLDIR}}\Auxiliary\Build\vcvars64.bat"
- name: Temporarily downgrade CMake to 3.28.3 # Please remove if GitHub has updated Cmake (greater than 3.30.0)
uses: jwlawson/actions-setup-cmake@v2
with:
cmake-version: '3.28.3'
- name: 👷 Build - name: 👷 Build
shell: bash shell: bash
run: ./.github/scripts/build.sh run: ./.github/scripts/build.sh

View File

@@ -13,7 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Support for ftdi chip based LED-devices with ws2812, sk6812 apa102 LED types (Many thanks to @nurikk) (#1746)
- Support for Skydimo devices (being an Adalight variant)
- Support gaps on Matrix Layout (#1696) - 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** **JSON-API**
- New subscription support for event updates, i.e. `Suspend, Resume, Idle, idleResume, Restart, Quit`. - New subscription support for event updates, i.e. `Suspend, Resume, Idle, idleResume, Restart, Quit`.
@@ -29,6 +32,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) - Workaround to address Web UI keeps forcing browser to download the html instead (#1692)
- Fixed: Kodi Color Calibration, Refactor Wizards (#1674) - Fixed: Kodi Color Calibration, Refactor Wizards (#1674)
- Fixed: Token Dialog not closing - Fixed: Token Dialog not closing
- Fixed: Philip Hue APIv2 support without Entertainment group defined (#1742)
**JSON-API** **JSON-API**
- Refactored JSON-API to ensure consistent authorization behaviour across sessions and single requests with token authorization. - Refactored JSON-API to ensure consistent authorization behaviour across sessions and single requests with token authorization.

View File

@@ -68,6 +68,7 @@ set(CMAKE_CXX_EXTENSIONS OFF)
set(DEFAULT_AMLOGIC OFF) set(DEFAULT_AMLOGIC OFF)
set(DEFAULT_DISPMANX OFF) set(DEFAULT_DISPMANX OFF)
set(DEFAULT_DX OFF) set(DEFAULT_DX OFF)
set(DEFAULT_DDA OFF)
set(DEFAULT_MF OFF) set(DEFAULT_MF OFF)
set(DEFAULT_OSX OFF) set(DEFAULT_OSX OFF)
set(DEFAULT_QT ON ) set(DEFAULT_QT ON )
@@ -93,6 +94,7 @@ set(DEFAULT_DEV_SPI OFF)
set(DEFAULT_DEV_TINKERFORGE OFF) set(DEFAULT_DEV_TINKERFORGE OFF)
set(DEFAULT_DEV_USB_HID OFF) set(DEFAULT_DEV_USB_HID OFF)
set(DEFAULT_DEV_WS281XPWM OFF) set(DEFAULT_DEV_WS281XPWM OFF)
set(DEFAULT_DEV_FTDI ON )
# Services # Services
set(DEFAULT_EFFECTENGINE ON ) set(DEFAULT_EFFECTENGINE ON )
@@ -120,8 +122,10 @@ if(${CMAKE_SYSTEM} MATCHES "Linux")
set(DEFAULT_DEV_USB_HID ON) set(DEFAULT_DEV_USB_HID ON)
set(DEFAULT_CEC ON) set(DEFAULT_CEC ON)
elseif (WIN32) elseif (WIN32)
set(DEFAULT_DX ON) set(DEFAULT_DX ON )
set(DEFAULT_MF ON) set(DEFAULT_DDA ON )
set(DEFAULT_MF ON )
set(DEFAULT_DEV_FTDI OFF)
else() else()
set(DEFAULT_FB OFF) set(DEFAULT_FB OFF)
set(DEFAULT_V4L2 OFF) set(DEFAULT_V4L2 OFF)
@@ -227,6 +231,7 @@ if(HYPERION_LIGHT)
SET ( DEFAULT_AMLOGIC OFF ) SET ( DEFAULT_AMLOGIC OFF )
SET ( DEFAULT_DISPMANX OFF ) SET ( DEFAULT_DISPMANX OFF )
SET ( DEFAULT_DX OFF ) SET ( DEFAULT_DX OFF )
SET ( DEFAULT_DDA OFF )
SET ( DEFAULT_FB OFF ) SET ( DEFAULT_FB OFF )
SET ( DEFAULT_MF OFF ) SET ( DEFAULT_MF OFF )
SET ( DEFAULT_OSX OFF ) SET ( DEFAULT_OSX OFF )
@@ -264,6 +269,9 @@ message(STATUS "ENABLE_DISPMANX = ${ENABLE_DISPMANX}")
option(ENABLE_DX "Enable the DirectX grabber" ${DEFAULT_DX}) option(ENABLE_DX "Enable the DirectX grabber" ${DEFAULT_DX})
message(STATUS "ENABLE_DX = ${ENABLE_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) if(ENABLE_AMLOGIC)
set(ENABLE_FB ON) set(ENABLE_FB ON)
else() else()
@@ -347,6 +355,9 @@ message(STATUS "ENABLE_DEV_USB_HID = ${ENABLE_DEV_USB_HID}")
option(ENABLE_DEV_WS281XPWM "Enable the WS281x-PWM device" ${DEFAULT_DEV_WS281XPWM}) option(ENABLE_DEV_WS281XPWM "Enable the WS281x-PWM device" ${DEFAULT_DEV_WS281XPWM})
message(STATUS "ENABLE_DEV_WS281XPWM = ${ENABLE_DEV_WS281XPWM}") message(STATUS "ENABLE_DEV_WS281XPWM = ${ENABLE_DEV_WS281XPWM}")
option(ENABLE_DEV_FTDI "Enable the FTDI devices" ${DEFAULT_DEV_FTDI} )
message(STATUS "ENABLE_DEV_FTDI = ${ENABLE_DEV_FTDI}")
removeIndent() removeIndent()
message(STATUS "Services options:") message(STATUS "Services options:")

View File

@@ -9,6 +9,9 @@
// Define to enable the DirectX grabber // Define to enable the DirectX grabber
#cmakedefine ENABLE_DX #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 framebuffer grabber
// Define to enable the Audio grabber // Define to enable the Audio grabber
#cmakedefine ENABLE_AUDIO #cmakedefine ENABLE_AUDIO

View File

@@ -159,8 +159,9 @@
"conf_leds_note_layout_overwrite": "Achtung: Überschreiben erzeugt ein Standardlayout für {{plural:$1| eine LED| alle $1 LEDs}} gemäß der gegebenen Hardware LED-Anzahl", "conf_leds_note_layout_overwrite": "Achtung: Überschreiben erzeugt ein Standardlayout für {{plural:$1| eine LED| alle $1 LEDs}} gemäß der gegebenen Hardware LED-Anzahl",
"conf_leds_optgroup_RPiGPIO": "RPi GPIO", "conf_leds_optgroup_RPiGPIO": "RPi GPIO",
"conf_leds_optgroup_RPiPWM": "RPi PWM", "conf_leds_optgroup_RPiPWM": "RPi PWM",
"conf_leds_optgroup_RPiSPI": "RPi SPI", "conf_leds_optgroup_SPI": "SPI",
"conf_leds_optgroup_debug": "Debug", "conf_leds_optgroup_debug": "Debug",
"conf_leds_optgroup_ftdi": "USB/Ftdi",
"conf_leds_optgroup_network": "Netzwerk", "conf_leds_optgroup_network": "Netzwerk",
"conf_leds_optgroup_other": "Andere", "conf_leds_optgroup_other": "Andere",
"conf_leds_optgroup_usb": "USB/Seriell", "conf_leds_optgroup_usb": "USB/Seriell",
@@ -191,6 +192,7 @@
"conf_network_tok_diaTitle": "Neues Token erstellt!", "conf_network_tok_diaTitle": "Neues Token erstellt!",
"conf_network_tok_grantMsg": "Eine App fordert Zugriff auf die Hyperion API durch ein Token. Möchtest du dies zulassen? Bitte überprüfe die angegebenen Informationen!", "conf_network_tok_grantMsg": "Eine App fordert Zugriff auf die Hyperion API durch ein Token. Möchtest du dies zulassen? Bitte überprüfe die angegebenen Informationen!",
"conf_network_tok_grantT": "App Token angefordert", "conf_network_tok_grantT": "App Token angefordert",
"conf_network_tok_idhead": "ID",
"conf_network_tok_intro": "Hier kannst du Token zur API-Authentifizierung erstellen oder löschen. Neu erstellte Token werden einmalig angezeigt.", "conf_network_tok_intro": "Hier kannst du Token zur API-Authentifizierung erstellen oder löschen. Neu erstellte Token werden einmalig angezeigt.",
"conf_network_tok_lastuse": "Zuletzt genutzt", "conf_network_tok_lastuse": "Zuletzt genutzt",
"conf_network_tok_title": "Token Management", "conf_network_tok_title": "Token Management",
@@ -611,6 +613,11 @@
"edt_conf_webc_sslport_title": "HTTPS Port", "edt_conf_webc_sslport_title": "HTTPS Port",
"edt_dev_auth_key_title": "Autorisierungs-Token", "edt_dev_auth_key_title": "Autorisierungs-Token",
"edt_dev_auth_key_title_info": "Autorisierungs-Token für den Zugriff auf das Gerät erforderlich", "edt_dev_auth_key_title_info": "Autorisierungs-Token für den Zugriff auf das Gerät erforderlich",
"edt_dev_enum_auto": "Auto",
"edt_dev_enum_auto_accurate": "Auto genau",
"edt_dev_enum_auto_max": "Auto maximal",
"edt_dev_enum_cold_white": "Kaltweiß",
"edt_dev_enum_neutral_white": "Neutralweiß",
"edt_dev_enum_sub_min_cool_adjust": "Minimale Anpassung: cool", "edt_dev_enum_sub_min_cool_adjust": "Minimale Anpassung: cool",
"edt_dev_enum_sub_min_warm_adjust": "Minimale Anpassung: warm", "edt_dev_enum_sub_min_warm_adjust": "Minimale Anpassung: warm",
"edt_dev_enum_subtract_minimum": "Subtrahiere Minimum", "edt_dev_enum_subtract_minimum": "Subtrahiere Minimum",
@@ -735,7 +742,7 @@
"edt_dev_spec_username_title": "Benutzername", "edt_dev_spec_username_title": "Benutzername",
"edt_dev_spec_verbose_title": "Protokollierung der HUE-Kommandos", "edt_dev_spec_verbose_title": "Protokollierung der HUE-Kommandos",
"edt_dev_spec_vid_title": "VID", "edt_dev_spec_vid_title": "VID",
"edt_dev_spec_whiteLedAlgor_title": "Weiß Algorithmus", "edt_dev_spec_whiteLedAlgor_title": "Weißabgleich Algorithmus",
"edt_dev_spec_whitepoint_title": "Weißpunkt", "edt_dev_spec_whitepoint_title": "Weißpunkt",
"edt_eff_alarmcolor": "Alarm Farbe", "edt_eff_alarmcolor": "Alarm Farbe",
"edt_eff_backgroundColor": "Hintergrundfarbe", "edt_eff_backgroundColor": "Hintergrundfarbe",
@@ -962,6 +969,7 @@
"general_country_us": "Amerika", "general_country_us": "Amerika",
"general_disabled": "deaktiviert", "general_disabled": "deaktiviert",
"general_enabled": "aktiviert", "general_enabled": "aktiviert",
"general_speech_bg": "Bulgarisch",
"general_speech_ca": "Katalanisch", "general_speech_ca": "Katalanisch",
"general_speech_cs": "Tschechisch", "general_speech_cs": "Tschechisch",
"general_speech_da": "Dänisch", "general_speech_da": "Dänisch",

View File

@@ -161,11 +161,12 @@
"conf_leds_note_layout_overwrite": "Note: Overwrite creates a default layout for {{plural:$1| one LED| all $1 LEDs}} given by the hardware LED count", "conf_leds_note_layout_overwrite": "Note: Overwrite creates a default layout for {{plural:$1| one LED| all $1 LEDs}} given by the hardware LED count",
"conf_leds_optgroup_RPiGPIO": "RPi GPIO", "conf_leds_optgroup_RPiGPIO": "RPi GPIO",
"conf_leds_optgroup_RPiPWM": "RPi PWM", "conf_leds_optgroup_RPiPWM": "RPi PWM",
"conf_leds_optgroup_RPiSPI": "RPi SPI", "conf_leds_optgroup_SPI": "SPI",
"conf_leds_optgroup_debug": "Debug", "conf_leds_optgroup_debug": "Debug",
"conf_leds_optgroup_network": "Network", "conf_leds_optgroup_network": "Network",
"conf_leds_optgroup_other": "Other", "conf_leds_optgroup_other": "Other",
"conf_leds_optgroup_usb": "USB/Serial", "conf_leds_optgroup_usb": "USB/Serial",
"conf_leds_optgroup_ftdi": "USB/Ftdi",
"conf_logging_btn_autoscroll": "Auto scrolling", "conf_logging_btn_autoscroll": "Auto scrolling",
"conf_logging_btn_clipboard": "Copy Log to Clipboard", "conf_logging_btn_clipboard": "Copy Log to Clipboard",
"conf_logging_btn_pbupload": "Upload a report for support requests", "conf_logging_btn_pbupload": "Upload a report for support requests",
@@ -619,6 +620,11 @@
"edt_dev_enum_sub_min_cool_adjust": "Subtract cool white", "edt_dev_enum_sub_min_cool_adjust": "Subtract cool white",
"edt_dev_enum_sub_min_warm_adjust": "Subtract warm white", "edt_dev_enum_sub_min_warm_adjust": "Subtract warm white",
"edt_dev_enum_subtract_minimum": "Subtract minimum", "edt_dev_enum_subtract_minimum": "Subtract minimum",
"edt_dev_enum_cold_white": "Cold white",
"edt_dev_enum_neutral_white": "Neutral white",
"edt_dev_enum_auto": "Auto",
"edt_dev_enum_auto_max": "Auto max",
"edt_dev_enum_auto_accurate": "Auto accurate",
"edt_dev_enum_white_off": "White off", "edt_dev_enum_white_off": "White off",
"edt_dev_general_autostart_title": "Autostart", "edt_dev_general_autostart_title": "Autostart",
"edt_dev_general_autostart_title_info": "The LED device is switched-on during startup or not", "edt_dev_general_autostart_title_info": "The LED device is switched-on during startup or not",
@@ -700,6 +706,7 @@
"edt_dev_spec_port_expl": "Service Port [1-65535]", "edt_dev_spec_port_expl": "Service Port [1-65535]",
"edt_dev_spec_port_title": "Port", "edt_dev_spec_port_title": "Port",
"edt_dev_spec_printTimeStamp_title": "Add timestamp", "edt_dev_spec_printTimeStamp_title": "Add timestamp",
"edt_dev_spec_skydimo_mode_title": "Skydimo Mode",
"edt_dev_spec_stream_protocol_title": "Streaming protocol", "edt_dev_spec_stream_protocol_title": "Streaming protocol",
"edt_dev_spec_pwmChannel_title": "PWM channel", "edt_dev_spec_pwmChannel_title": "PWM channel",
"edt_dev_spec_razer_device_title": "Razer Chroma Device", "edt_dev_spec_razer_device_title": "Razer Chroma Device",

View File

@@ -159,8 +159,9 @@
"conf_leds_note_layout_overwrite": "Varning: Åsidosättande skapar en standardlayout för {{plural:$1| en LED| varje $1 lysdioder}} enligt det givna antalet lysdioder för hårdvara", "conf_leds_note_layout_overwrite": "Varning: Åsidosättande skapar en standardlayout för {{plural:$1| en LED| varje $1 lysdioder}} enligt det givna antalet lysdioder för hårdvara",
"conf_leds_optgroup_RPiGPIO": "RPi GPIO", "conf_leds_optgroup_RPiGPIO": "RPi GPIO",
"conf_leds_optgroup_RPiPWM": "RPi PWM", "conf_leds_optgroup_RPiPWM": "RPi PWM",
"conf_leds_optgroup_RPiSPI": "RPi SPI", "conf_leds_optgroup_SPI": "SPI",
"conf_leds_optgroup_debug": "Felsöka", "conf_leds_optgroup_debug": "Felsöka",
"conf_leds_optgroup_ftdi": "USB/Ftdi",
"conf_leds_optgroup_network": "Nätverk", "conf_leds_optgroup_network": "Nätverk",
"conf_leds_optgroup_other": "Annat", "conf_leds_optgroup_other": "Annat",
"conf_leds_optgroup_usb": "USB/Seriell", "conf_leds_optgroup_usb": "USB/Seriell",
@@ -612,6 +613,11 @@
"edt_conf_webc_sslport_title": "HTTPS-Port", "edt_conf_webc_sslport_title": "HTTPS-Port",
"edt_dev_auth_key_title": "Auktorisationsnyckel", "edt_dev_auth_key_title": "Auktorisationsnyckel",
"edt_dev_auth_key_title_info": "Auktorisationsnyckel krävs för att få åtkomst till enheten", "edt_dev_auth_key_title_info": "Auktorisationsnyckel krävs för att få åtkomst till enheten",
"edt_dev_enum_auto": "Auto",
"edt_dev_enum_auto_accurate": "Auto noggrann",
"edt_dev_enum_auto_max": "Auto max",
"edt_dev_enum_cold_white": "Kallvitt",
"edt_dev_enum_neutral_white": "Neutralvitt",
"edt_dev_enum_sub_min_cool_adjust": "Minsta justering: kall", "edt_dev_enum_sub_min_cool_adjust": "Minsta justering: kall",
"edt_dev_enum_sub_min_warm_adjust": "Minsta justering: varm", "edt_dev_enum_sub_min_warm_adjust": "Minsta justering: varm",
"edt_dev_enum_subtract_minimum": "Subtrahera minimum", "edt_dev_enum_subtract_minimum": "Subtrahera minimum",
@@ -963,6 +969,7 @@
"general_country_us": "USA", "general_country_us": "USA",
"general_disabled": "Inaktiverad", "general_disabled": "Inaktiverad",
"general_enabled": "Aktiverad", "general_enabled": "Aktiverad",
"general_speech_bg": "Bulgariska",
"general_speech_ca": "Katalanska", "general_speech_ca": "Katalanska",
"general_speech_cs": "Tjeckiska", "general_speech_cs": "Tjeckiska",
"general_speech_da": "Danska", "general_speech_da": "Danska",

View File

@@ -18,7 +18,8 @@ var bottomRight2bottomLeft = null;
var bottomLeft2topLeft = null; var bottomLeft2topLeft = null;
var toggleKeystoneCorrectionArea = false; var toggleKeystoneCorrectionArea = false;
var devRPiSPI = ['apa102', 'apa104', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'sk9822', 'ws2812spi']; var devSPI = ['apa102', 'apa104', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'sk9822', 'ws2812spi'];
var devFTDI = ['apa102_ftdi', 'sk6812_ftdi', 'ws2812_ftdi'];
var devRPiPWM = ['ws281x']; var devRPiPWM = ['ws281x'];
var devRPiGPIO = ['piblaster']; var devRPiGPIO = ['piblaster'];
var devNET = ['atmoorb', 'cololight', 'fadecandy', 'philipshue', 'nanoleaf', 'razer', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udpddp', 'udph801', 'udpraw', 'wled', 'yeelight']; var devNET = ['atmoorb', 'cololight', 'fadecandy', 'philipshue', 'nanoleaf', 'razer', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udpddp', 'udph801', 'udpraw', 'wled', 'yeelight'];
@@ -1121,6 +1122,12 @@ $(document).ready(function () {
case "karate": case "karate":
case "sedu": case "sedu":
case "tpm2": case "tpm2":
//FTDI devices
case "apa102_ftdi":
case "sk6812_ftdi":
case "ws2812_ftdi":
if (storedAccess === 'expert') { if (storedAccess === 'expert') {
filter.discoverAll = true; filter.discoverAll = true;
} }
@@ -1139,6 +1146,7 @@ $(document).ready(function () {
.catch(error => { .catch(error => {
showNotification('danger', "Device discovery for " + ledType + " failed with error:" + error); showNotification('danger', "Device discovery for " + ledType + " failed with error:" + error);
}); });
break; break;
case "philipshue": { case "philipshue": {
@@ -1441,6 +1449,9 @@ $(document).ready(function () {
case "sk9822": case "sk9822":
case "ws2812spi": case "ws2812spi":
case "piblaster": case "piblaster":
case "apa102_ftdi":
case "sk6812_ftdi":
case "ws2812_ftdi":
default: default:
} }
@@ -1657,9 +1668,10 @@ $(document).ready(function () {
optArr[3] = []; optArr[3] = [];
optArr[4] = []; optArr[4] = [];
optArr[5] = []; optArr[5] = [];
optArr[6] = [];
for (var idx = 0; idx < ledDevices.length; idx++) { for (var idx = 0; idx < ledDevices.length; idx++) {
if ($.inArray(ledDevices[idx], devRPiSPI) != -1) if ($.inArray(ledDevices[idx], devSPI) != -1)
optArr[0].push(ledDevices[idx]); optArr[0].push(ledDevices[idx]);
else if ($.inArray(ledDevices[idx], devRPiPWM) != -1) else if ($.inArray(ledDevices[idx], devRPiPWM) != -1)
optArr[1].push(ledDevices[idx]); optArr[1].push(ledDevices[idx]);
@@ -1671,8 +1683,12 @@ $(document).ready(function () {
optArr[4].push(ledDevices[idx]); optArr[4].push(ledDevices[idx]);
else if ($.inArray(ledDevices[idx], devHID) != -1) else if ($.inArray(ledDevices[idx], devHID) != -1)
optArr[4].push(ledDevices[idx]); optArr[4].push(ledDevices[idx]);
else if (ledDevices[idx].endsWith("_ftdi")) {
var title = ledDevices[idx].replace('_ftdi','');
optArr[5].push(ledDevices[idx] + ":" + title);
}
else else
optArr[5].push(ledDevices[idx]); optArr[6].push(ledDevices[idx]);
} }
$("#leddevices").append(createSel(optArr[0], $.i18n('conf_leds_optgroup_RPiSPI'))); $("#leddevices").append(createSel(optArr[0], $.i18n('conf_leds_optgroup_RPiSPI')));
@@ -1680,9 +1696,10 @@ $(document).ready(function () {
$("#leddevices").append(createSel(optArr[2], $.i18n('conf_leds_optgroup_RPiGPIO'))); $("#leddevices").append(createSel(optArr[2], $.i18n('conf_leds_optgroup_RPiGPIO')));
$("#leddevices").append(createSel(optArr[3], $.i18n('conf_leds_optgroup_network'))); $("#leddevices").append(createSel(optArr[3], $.i18n('conf_leds_optgroup_network')));
$("#leddevices").append(createSel(optArr[4], $.i18n('conf_leds_optgroup_usb'))); $("#leddevices").append(createSel(optArr[4], $.i18n('conf_leds_optgroup_usb')));
$("#leddevices").append(createSel(optArr[5], $.i18n('conf_leds_optgroup_ftdi'), true));
if (storedAccess === 'expert' || window.serverConfig.device.type === "file") { if (storedAccess === 'expert' || window.serverConfig.device.type === "file") {
$("#leddevices").append(createSel(optArr[5], $.i18n('conf_leds_optgroup_other'))); $("#leddevices").append(createSel(optArr[6], $.i18n('conf_leds_optgroup_other')));
} }
$("#leddevices").val(window.serverConfig.device.type); $("#leddevices").val(window.serverConfig.device.type);
@@ -1886,6 +1903,9 @@ function saveLedConfig(genDefLayout = false) {
case "sk9822": case "sk9822":
case "ws2812spi": case "ws2812spi":
case "piblaster": case "piblaster":
case "apa102_ftdi":
case "sk6812_ftdi":
case "ws2812_ftdi":
default: default:
if (genDefLayout === true) { if (genDefLayout === true) {
ledConfig = { ledConfig = {
@@ -1938,8 +1958,10 @@ var updateOutputSelectList = function (ledType, discoveryInfo) {
ledTypeGroup = "devNET"; ledTypeGroup = "devNET";
} else if ($.inArray(ledType, devSerial) != -1) { } else if ($.inArray(ledType, devSerial) != -1) {
ledTypeGroup = "devSerial"; ledTypeGroup = "devSerial";
} else if ($.inArray(ledType, devRPiSPI) != -1) { } else if ($.inArray(ledType, devSPI) != -1) {
ledTypeGroup = "devRPiSPI"; ledTypeGroup = "devSPI";
} else if ($.inArray(ledType, devFTDI) != -1) {
ledTypeGroup = "devFTDI";
} else if ($.inArray(ledType, devRPiGPIO) != -1) { } else if ($.inArray(ledType, devRPiGPIO) != -1) {
ledTypeGroup = "devRPiGPIO"; ledTypeGroup = "devRPiGPIO";
} else if ($.inArray(ledType, devRPiPWM) != -1) { } else if ($.inArray(ledType, devRPiPWM) != -1) {
@@ -2062,7 +2084,63 @@ var updateOutputSelectList = function (ledType, discoveryInfo) {
} }
} }
break; break;
case "devRPiSPI":
case "devFTDI":
key = "output";
if (discoveryInfo.devices.length == 0) {
enumVals.push("NONE");
enumTitleVals.push($.i18n('edt_dev_spec_devices_discovered_none'));
$('#btn_submit_controller').prop('disabled', true);
showAllDeviceInputOptions(key, false);
}
else {
switch (ledType) {
case "ws2812_ftdi":
case "sk6812_ftdi":
case "apa102_ftdi":
for (const device of discoveryInfo.devices) {
enumVals.push(device.ftdiOpenString);
var title = "FTDI";
if (device.manufacturer) {
title = device.manufacturer;
}
if (device.serialNumber) {
title += " - " + device.serialNumber;
}
title += " (" + device.vendorIdentifier + "|" + device.productIdentifier + ")";
if (device.description) {
title += " " + device.description;
}
enumTitleVals.push(title);
}
// Select configured device
var configuredDeviceType = window.serverConfig.device.type;
var configuredOutput = window.serverConfig.device.output;
if (ledType === configuredDeviceType) {
if ($.inArray(configuredOutput, enumVals) != -1) {
enumDefaultVal = configuredOutput;
} else {
enumVals.push(window.serverConfig.device.output);
enumDefaultVal = configuredOutput;
}
}
else {
addSelect = true;
}
break;
default:
}
}
break;
case "devSPI":
case "devRPiGPIO": case "devRPiGPIO":
key = "output"; key = "output";
@@ -2128,7 +2206,6 @@ var updateOutputSelectList = function (ledType, discoveryInfo) {
async function discover_device(ledType, params) { async function discover_device(ledType, params) {
const result = await requestLedDeviceDiscovery(ledType, params); const result = await requestLedDeviceDiscovery(ledType, params);
var discoveryResult = {}; var discoveryResult = {};
if (result) { if (result) {
if (result.error) { if (result.error) {

View File

@@ -502,7 +502,9 @@ const philipshueWizard = (() => {
let serviceID; let serviceID;
if (isAPIv2Ready) { if (isAPIv2Ready) {
serviceID = lightLocation.service.rid; if (lightLocation) {
serviceID = lightLocation.service.rid;
}
} }
if (position.startsWith("entertainment")) { if (position.startsWith("entertainment")) {
@@ -531,7 +533,7 @@ const philipshueWizard = (() => {
// Layout per manual settings // Layout per manual settings
let maxSegments = 1; let maxSegments = 1;
if (isAPIv2Ready) { if (isAPIv2Ready && serviceID) {
const service = hueEntertainmentServices.find(service => service.id === serviceID); const service = hueEntertainmentServices.find(service => service.id === serviceID);
maxSegments = service.segments.max_segments; maxSegments = service.segments.max_segments;
} }
@@ -593,10 +595,10 @@ const philipshueWizard = (() => {
d.enableAttempts = parseInt(conf_editor.getEditor("root.generalOptions.enableAttempts").getValue()); d.enableAttempts = parseInt(conf_editor.getEditor("root.generalOptions.enableAttempts").getValue());
d.enableAttemptsInterval = parseInt(conf_editor.getEditor("root.generalOptions.enableAttemptsInterval").getValue()); d.enableAttemptsInterval = parseInt(conf_editor.getEditor("root.generalOptions.enableAttemptsInterval").getValue());
d.useEntertainmentAPI = isEntertainmentReady; d.useEntertainmentAPI = isEntertainmentReady && (d.groupId !== "");
d.useAPIv2 = isAPIv2Ready; d.useAPIv2 = isAPIv2Ready;
if (isEntertainmentReady) { if (d.useEntertainmentAPI) {
d.hardwareLedCount = channelNumber; d.hardwareLedCount = channelNumber;
if (window.serverConfig.device.type !== d.type) { if (window.serverConfig.device.type !== d.type) {
//smoothing on, if new device //smoothing on, if new device
@@ -803,12 +805,18 @@ const philipshueWizard = (() => {
"lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121" "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121"
]; ];
if (isEntertainmentReady) { if (isEntertainmentReady && hueEntertainmentConfigs.length > 0) {
lightOptions.unshift("entertainment_center"); lightOptions.unshift("entertainment_center");
lightOptions.unshift("entertainment"); lightOptions.unshift("entertainment");
} else { } else {
lightOptions.unshift("disabled"); 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(""); $('.lidsb').html("");

View File

@@ -24,36 +24,19 @@ if (ENABLE_MDNS)
if(USE_SYSTEM_QMDNS_LIBS) if(USE_SYSTEM_QMDNS_LIBS)
find_package(qmdnsengine REQUIRED) find_package(qmdnsengine REQUIRED)
else() else()
include(ExternalProject) # Build QMdnsEngine as static library
ExternalProject_Add(qmdns set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build statically version of QMdnsEngine")
PREFIX ${CMAKE_CURRENT_BINARY_DIR}/external/qmdnsengine
BUILD_ALWAYS OFF
DOWNLOAD_COMMAND ""
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/qmdnsengine
BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/external/qmdnsengine/bin
CMAKE_ARGS -DBUILD_SHARED_LIBS:BOOL=OFF
-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_BINARY_DIR}
-DBIN_INSTALL_DIR:STRING=lib
-DLIB_INSTALL_DIR:STRING=lib
-DINCLUDE_INSTALL_DIR:STRING=include
-DCMAKE_PREFIX_PATH:PATH=${CMAKE_PREFIX_PATH}
-DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER}
-DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER}
-DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS}
-DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS}
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-Wno-dev # We don't want to be warned over unused variables
INSTALL_DIR ${CMAKE_BINARY_DIR}
BUILD_BYPRODUCTS <INSTALL_DIR>/lib/${CMAKE_STATIC_LIBRARY_PREFIX}qmdnsengine${CMAKE_STATIC_LIBRARY_SUFFIX}
)
add_library(qmdnsengine STATIC IMPORTED GLOBAL) # Suppress warnings about "Compatibility with CMake < 3.5 will be removed from a future version of CMake"
add_dependencies(qmdnsengine qmdns) set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "" FORCE)
ExternalProject_Get_Property(qmdns INSTALL_DIR)
set_target_properties(qmdnsengine PROPERTIES # Add QMdnsEngine directory to the build
IMPORTED_LOCATION "${INSTALL_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}qmdnsengine${CMAKE_STATIC_LIBRARY_SUFFIX}" add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/external/qmdnsengine")
INTERFACE_INCLUDE_DIRECTORIES "${INSTALL_DIR}/include" endif()
)
if(TARGET qmdnsengine AND NOT TARGET qmdns)
add_library(qmdns INTERFACE IMPORTED GLOBAL)
target_link_libraries(qmdns INTERFACE qmdnsengine)
endif() endif()
endif() endif()
@@ -149,6 +132,7 @@ if(ENABLE_PROTOBUF_SERVER)
set(protobuf_BUILD_TESTS OFF CACHE BOOL "Build protobuf with tests") set(protobuf_BUILD_TESTS OFF CACHE BOOL "Build protobuf with tests")
set(protobuf_BUILD_SHARED_LIBS OFF CACHE BOOL "Build protobuf shared") set(protobuf_BUILD_SHARED_LIBS OFF CACHE BOOL "Build protobuf shared")
set(protobuf_WITH_ZLIB OFF CACHE BOOL "Build protobuf with zlib support") set(protobuf_WITH_ZLIB OFF CACHE BOOL "Build protobuf with zlib support")
set(protobuf_BUILD_LIBUPB OFF CACHE BOOL "Build libupb")
if (WIN32) if (WIN32)
set(protobuf_MSVC_STATIC_RUNTIME OFF CACHE BOOL "Build protobuf static") set(protobuf_MSVC_STATIC_RUNTIME OFF CACHE BOOL "Build protobuf static")

View File

@@ -61,14 +61,14 @@ cd $HYPERION_HOME
```console ```console
sudo apt-get update sudo apt-get update
sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5x11extras5-dev libusb-1.0-0-dev python3-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5x11extras5-dev libusb-1.0-0-dev python3-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev libftdi1-dev
``` ```
**Ubuntu (22.04+) - Qt6 based** **Ubuntu (22.04+) - Qt6 based**
```console ```console
sudo apt-get update sudo apt-get update
sudo apt-get install git cmake build-essential qt6-base-dev libqt6serialport6-dev libxkbcommon-dev libvulkan-dev libgl1-mesa-dev libusb-1.0-0-dev python3-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev pkg-config sudo apt-get install git cmake build-essential qt6-base-dev libqt6serialport6-dev libxkbcommon-dev libvulkan-dev libgl1-mesa-dev libusb-1.0-0-dev python3-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev pkg-config libftdi1-dev
``` ```
**For Linux X11/XCB grabber support** **For Linux X11/XCB grabber support**
@@ -110,16 +110,21 @@ See [AUR](https://aur.archlinux.org/packages/?O=0&SeB=nd&K=hyperion&outdated=&SB
The following dependencies are needed to build hyperion.ng on fedora. The following dependencies are needed to build hyperion.ng on fedora.
```console ```console
sudo dnf -y groupinstall "Development Tools" sudo dnf -y groupinstall "Development Tools"
sudo dnf install python3-devel qt-devel qt5-qtbase-devel qt5-qtserialport-devel xrandr xcb-util-image-devel qt5-qtx11extras-devel alsa-lib-devel turbojpeg-devel libusb-devel xcb-util-devel dbus-devel openssl-devel fedora-packager rpmdevtools gcc libcec-devel sudo dnf install python3-devel qt-devel qt5-qtbase-devel qt5-qtserialport-devel xrandr xcb-util-image-devel qt5-qtx11extras-devel alsa-lib-devel turbojpeg-devel libusb-devel xcb-util-devel dbus-devel openssl-devel fedora-packager rpmdevtools gcc libcec-devel libftdi1-dev
``` ```
After installing the dependencies, you can continue with the compile instructions later on this page (the more detailed way..). After installing the dependencies, you can continue with the compile instructions later on this page (the more detailed way..).
## OSX ## macOS
To install on OS X you either need [Homebrew](https://brew.sh/) or [Macport](https://www.macports.org/) but Homebrew is the recommended way to install the packages. To use Homebrew, XCode is required as well, use `brew doctor` to check your install. To install on OS X you either need [Homebrew](https://brew.sh/) or [Macport](https://www.macports.org/) but Homebrew is the recommended way to install the packages. To use Homebrew, XCode is required as well, use `brew doctor` to check your install.
First you need to install the dependencies: First you need to install the dependencies for either the QT5 or QT6 build:
####QT5
```console ```console
brew install git qt@5 python3 cmake libusb openssl@1.1 brew install git qt@5 python3 cmake libusb openssl@1.1 libftdi pkg-config
```
####QT6
```console
brew install git qt python3 cmake libusb openssl@1.1 libftdi pkg-config
``` ```
## Windows ## Windows
@@ -128,7 +133,7 @@ We assume a 64bit Windows 10. Install the following;
- [CMake (Windows win64-x64 installer)](https://cmake.org/download/) (Check: Add to PATH) - [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) - [Visual Studio 2022 Community Edition](https://visualstudio.microsoft.com/downloads/#visual-studio-community-2022)
- Select 'Desktop development with C++' - 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)) - [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) - [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`. - Open a console window and execute `pip install aqtinstall`.
@@ -147,7 +152,7 @@ We assume a 64bit Windows 10. Install the following;
## The general quick way (without big comments) ## The general quick way (without big comments)
**complete automated process for Mac/Linux:** **complete automated process (Linux only):**
```console ```console
wget -qO- https://raw.githubusercontent.com/hyperion-project/hyperion.ng/master/bin/compile.sh | sh wget -qO- https://raw.githubusercontent.com/hyperion-project/hyperion.ng/master/bin/compile.sh | sh
``` ```

View File

@@ -27,6 +27,10 @@
#include <grabber/directx/directXGrabber.h> #include <grabber/directx/directXGrabber.h>
#endif #endif
#ifdef ENABLE_DDA
#include <grabber/dda/DDAGrabber.h>
#endif
#if defined(ENABLE_X11) #if defined(ENABLE_X11)
#include <grabber/x11/X11Grabber.h> #include <grabber/x11/X11Grabber.h>
#endif #endif
@@ -35,10 +39,6 @@
#include <grabber/xcb/XcbGrabber.h> #include <grabber/xcb/XcbGrabber.h>
#endif #endif
#if defined(ENABLE_DX)
#include <grabber/directx/DirectXGrabber.h>
#endif
#if defined(ENABLE_FB) #if defined(ENABLE_FB)
#include <grabber/framebuffer/FramebufferFrameGrabber.h> #include <grabber/framebuffer/FramebufferFrameGrabber.h>
#endif #endif

View File

@@ -0,0 +1,75 @@
#pragma once
#include <QObject>
#include <QJsonObject>
#include <hyperion/Grabber.h>
#include <utils/ColorRgb.h>
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<ColorRgb> &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 &params);
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<DDAGrabberImpl> d;
};

View File

@@ -0,0 +1,24 @@
#pragma once
#include <grabber/dda/DDAGrabber.h>
#include <hyperion/GrabberWrapper.h>
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;
};

View File

@@ -46,6 +46,7 @@ public:
int numerator = 0; int numerator = 0;
int denominator = 0; int denominator = 0;
PixelFormat pf = PixelFormat::NO_CHANGE; PixelFormat pf = PixelFormat::NO_CHANGE;
long defstride = 0;
GUID guid = GUID_NULL; GUID guid = GUID_NULL;
}; };

View File

@@ -1,5 +1,11 @@
#pragma once #pragma once
#define NOFRAME_BENCH
#ifdef FRAME_BENCH
#include <QElapsedTimer>
#endif
// stl includes // stl includes
#include <vector> #include <vector>
#include <map> #include <map>
@@ -166,6 +172,9 @@ private:
double _x_frac_max; double _x_frac_max;
double _y_frac_max; double _y_frac_max;
#ifdef FRAME_BENCH
QElapsedTimer _frameTimer;
#endif
QSocketNotifier *_streamNotifier; QSocketNotifier *_streamNotifier;
bool _initialized, _reload; bool _initialized, _reload;

View File

@@ -10,6 +10,7 @@ enum class PixelFormat {
YUYV, YUYV,
UYVY, UYVY,
BGR16, BGR16,
RGB24,
BGR24, BGR24,
RGB32, RGB32,
BGR32, BGR32,
@@ -36,6 +37,10 @@ inline PixelFormat parsePixelFormat(const QString& pixelFormat)
{ {
return PixelFormat::BGR16; return PixelFormat::BGR16;
} }
else if (format.compare("rgb24") == 0)
{
return PixelFormat::RGB24;
}
else if (format.compare("bgr24") == 0) else if (format.compare("bgr24") == 0)
{ {
return PixelFormat::BGR24; return PixelFormat::BGR24;
@@ -80,6 +85,10 @@ inline QString pixelFormatToString(const PixelFormat& pixelFormat)
{ {
return "BGR16"; return "BGR16";
} }
else if (pixelFormat == PixelFormat::RGB24)
{
return "RGB24";
}
else if (pixelFormat == PixelFormat::BGR24) else if (pixelFormat == PixelFormat::BGR24)
{ {
return "BGR24"; return "BGR24";
@@ -115,10 +124,10 @@ inline QString pixelFormatToString(const PixelFormat& pixelFormat)
enum class FlipMode enum class FlipMode
{ {
NO_CHANGE,
HORIZONTAL, HORIZONTAL,
VERTICAL, VERTICAL,
BOTH, BOTH
NO_CHANGE
}; };
inline FlipMode parseFlipMode(const QString& flipMode) inline FlipMode parseFlipMode(const QString& flipMode)

View File

@@ -11,7 +11,12 @@ namespace RGBW {
SUBTRACT_MINIMUM, SUBTRACT_MINIMUM,
SUB_MIN_WARM_ADJUST, SUB_MIN_WARM_ADJUST,
SUB_MIN_COOL_ADJUST, SUB_MIN_COOL_ADJUST,
WHITE_OFF WHITE_OFF,
COLD_WHITE,
NEUTRAL_WHITE,
AUTO,
AUTO_MAX,
AUTO_ACCURATE
}; };
WhiteAlgorithm stringToWhiteAlgorithm(const QString& str); WhiteAlgorithm stringToWhiteAlgorithm(const QString& str);

View File

@@ -593,6 +593,10 @@ QJsonArray JsonInfo::discoverScreenInputs(const QJsonObject& params) const
discoverGrabber<DirectXGrabber>(screenInputs, params); discoverGrabber<DirectXGrabber>(screenInputs, params);
#endif #endif
#ifdef ENABLE_DDA
discoverGrabber<DDAGrabber>(screenInputs, params);
#endif
#ifdef ENABLE_X11 #ifdef ENABLE_X11
discoverGrabber<X11Grabber>(screenInputs, params); discoverGrabber<X11Grabber>(screenInputs, params);
#endif #endif

View File

@@ -34,6 +34,10 @@ if(ENABLE_DX)
add_subdirectory(directx) add_subdirectory(directx)
endif(ENABLE_DX) endif(ENABLE_DX)
if(ENABLE_DDA)
add_subdirectory(dda)
endif(ENABLE_DDA)
if(ENABLE_AUDIO) if(ENABLE_AUDIO)
add_subdirectory(audio) add_subdirectory(audio)
endif() endif()

View File

@@ -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
)

View File

@@ -0,0 +1,357 @@
#include "grabber/dda/DDAGrabber.h"
#include <atlbase.h>
#include <d3d11.h>
#include <dxgi1_2.h>
#include <physicalmonitorenumerationapi.h>
#include <windows.h>
#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 <atlbase.h> 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<ID3D11Device> device;
CComPtr<ID3D11DeviceContext> deviceContext;
CComPtr<IDXGIDevice> dxgiDevice;
CComPtr<IDXGIAdapter> dxgiAdapter;
// Created in restartCapture - only valid while desktop capture is in
// progress.
CComPtr<IDXGIOutputDuplication> desktopDuplication;
CComPtr<ID3D11Texture2D> 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<IDXGIOutput> 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<IDXGIOutput1> 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<ColorRgb> &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<IDXGIResource> desktopResource;
DXGI_OUTDUPL_FRAME_INFO frameInfo;
hr = d->desktopDuplication->AcquireNextFrame(500, &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)
{
// Nothing changed on the screen in the 500ms we waited.
return 0;
}
RETURN_IF_ERROR(hr, "Failed to acquire next frame", 0);
// Get the 2D texture.
CComPtr<ID3D11Texture2D> 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<uint32_t *>(reinterpret_cast<unsigned char *>(resource.pData) + srcY * resource.RowPitch) +
_cropLeft;
for (size_t destX = 0; destX < image.width(); destX++, src += _pixelDecimation, dest++)
{
*dest = ColorRgb{static_cast<uint8_t>(((*src) >> 16) & 0xff), static_cast<uint8_t>(((*src) >> 8) & 0xff),
static_cast<uint8_t>(((*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 &params)
{
QJsonObject ret;
if (!d->dxgiAdapter)
{
return ret;
}
HRESULT hr = S_OK;
// Enumerate through the outputs.
QJsonArray videoInputs;
for (int i = 0;; ++i)
{
CComPtr<IDXGIOutput> 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;
}

View File

@@ -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);
}

View File

@@ -122,18 +122,6 @@ void EncoderThread::process()
else else
#endif #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<ColorRgb> image = Image<ColorRgb>(); Image<ColorRgb> image = Image<ColorRgb>();
_imageResampler.processImage( _imageResampler.processImage(
_localData, _localData,
@@ -143,7 +131,7 @@ void EncoderThread::process()
#if defined(ENABLE_V4L2) #if defined(ENABLE_V4L2)
_pixelFormat, _pixelFormat,
#else #else
PixelFormat::BGR24, PixelFormat::BGR24, // MF-Grabber always sends RGB24, but memory layout is RGBTRIPLE (b,g,r) -> process as BGR24
#endif #endif
image image
); );

View File

@@ -363,6 +363,18 @@ done:
_height = props.height; _height = props.height;
_frameByteSize = _width * _height * 3; _frameByteSize = _width * _height * 3;
_lineLength = _width * 3; _lineLength = _width * 3;
// adjust flipMode for bottom-up images
if (props.defstride < 0)
{
if (_flipMode == FlipMode::NO_CHANGE)
_flipMode = FlipMode::HORIZONTAL;
else if (_flipMode == FlipMode::HORIZONTAL)
_flipMode = FlipMode::NO_CHANGE;
else if (_flipMode == FlipMode::VERTICAL)
_flipMode = FlipMode::BOTH;
else if (_flipMode == FlipMode::BOTH)
_flipMode = FlipMode::VERTICAL;
}
} }
// Cleanup // Cleanup
@@ -436,6 +448,14 @@ void MFGrabber::enumVideoCaptureDevices()
properties.denominator = denominator; properties.denominator = denominator;
properties.pf = pixelformat; properties.pf = pixelformat;
properties.guid = format; properties.guid = format;
HRESULT hr = pType->GetUINT32(MF_MT_DEFAULT_STRIDE, (UINT32*)&properties.defstride);
if (FAILED(hr))
{
hr = MFGetStrideForBitmapInfoHeader(format.Data1, width, &properties.defstride);
if (FAILED(hr))
DebugIf (verbose, _log, "failed to get default stride");
}
devicePropertyList.append(properties); devicePropertyList.append(properties);
DebugIf (verbose, _log, "%s %d x %d @ %d fps (%s)", QSTRING_CSTR(dev), properties.width, properties.height, properties.fps, QSTRING_CSTR(pixelFormatToString(properties.pf))); DebugIf (verbose, _log, "%s %d x %d @ %d fps (%s)", QSTRING_CSTR(dev), properties.width, properties.height, properties.fps, QSTRING_CSTR(pixelFormatToString(properties.pf)));
@@ -797,7 +817,7 @@ QJsonArray MFGrabber::discover(const QJsonObject& params)
resolution_default["width"] = 640; resolution_default["width"] = 640;
resolution_default["height"] = 480; resolution_default["height"] = 480;
resolution_default["fps"] = 25; resolution_default["fps"] = 25;
format_default["format"] = "bgr24"; format_default["format"] = "rgb24";
format_default["resolution"] = resolution_default; format_default["resolution"] = resolution_default;
video_inputs_default["inputIdx"] = 0; video_inputs_default["inputIdx"] = 0;
video_inputs_default["standards"] = "PAL"; video_inputs_default["standards"] = "PAL";

View File

@@ -27,7 +27,7 @@
static PixelFormat GetPixelFormatForGuid(const GUID guid) static PixelFormat GetPixelFormatForGuid(const GUID guid)
{ {
if (IsEqualGUID(guid, MFVideoFormat_RGB32)) return PixelFormat::RGB32; if (IsEqualGUID(guid, MFVideoFormat_RGB32)) return PixelFormat::RGB32;
if (IsEqualGUID(guid, MFVideoFormat_RGB24)) return PixelFormat::BGR24; if (IsEqualGUID(guid, MFVideoFormat_RGB24)) return PixelFormat::RGB24;
if (IsEqualGUID(guid, MFVideoFormat_YUY2)) return PixelFormat::YUYV; if (IsEqualGUID(guid, MFVideoFormat_YUY2)) return PixelFormat::YUYV;
if (IsEqualGUID(guid, MFVideoFormat_UYVY)) return PixelFormat::UYVY; if (IsEqualGUID(guid, MFVideoFormat_UYVY)) return PixelFormat::UYVY;
#ifdef HAVE_TURBO_JPEG #ifdef HAVE_TURBO_JPEG
@@ -145,11 +145,11 @@ public:
} }
#ifdef HAVE_TURBO_JPEG #ifdef HAVE_TURBO_JPEG
if (_pixelformat != PixelFormat::MJPEG && _pixelformat != PixelFormat::BGR24 && _pixelformat != PixelFormat::NO_CHANGE) if (_pixelformat != PixelFormat::MJPEG && _pixelformat != PixelFormat::RGB24 && _pixelformat != PixelFormat::NO_CHANGE)
#else #else
if (_pixelformat != PixelFormat::BGR24 && _pixelformat != PixelFormat::NO_CHANGE) if (_pixelformat != PixelFormat::RGB24 && _pixelformat != PixelFormat::NO_CHANGE)
#endif #endif
pSample = TransformSample(_transform, pSample); pSample = TransformSample(_transform, pSample); // forced conversion to RGB24, but memory layout is RGBTRIPLE (b,g,r) -> process as BGR24
_hrStatus = pSample->ConvertToContiguousBuffer(&buffer); _hrStatus = pSample->ConvertToContiguousBuffer(&buffer);
if (FAILED(_hrStatus)) if (FAILED(_hrStatus))
@@ -181,9 +181,9 @@ public:
_bEOS = TRUE; // Reached the end of the stream. _bEOS = TRUE; // Reached the end of the stream.
#ifdef HAVE_TURBO_JPEG #ifdef HAVE_TURBO_JPEG
if (_pixelformat != PixelFormat::MJPEG && _pixelformat != PixelFormat::BGR24 && _pixelformat != PixelFormat::NO_CHANGE) if (_pixelformat != PixelFormat::MJPEG && _pixelformat != PixelFormat::RGB24 && _pixelformat != PixelFormat::NO_CHANGE)
#else #else
if (_pixelformat != PixelFormat::BGR24 && _pixelformat != PixelFormat::NO_CHANGE) if (_pixelformat != PixelFormat::RGB24 && _pixelformat != PixelFormat::NO_CHANGE)
#endif #endif
SAFE_RELEASE(pSample); SAFE_RELEASE(pSample);
@@ -196,9 +196,9 @@ public:
{ {
_pixelformat = format; _pixelformat = format;
#ifdef HAVE_TURBO_JPEG #ifdef HAVE_TURBO_JPEG
if (format == PixelFormat::MJPEG || format == PixelFormat::BGR24 || format == PixelFormat::NO_CHANGE) if (format == PixelFormat::MJPEG || format == PixelFormat::RGB24 || format == PixelFormat::NO_CHANGE)
#else #else
if (format == PixelFormat::BGR24 || format == PixelFormat::NO_CHANGE) if (format == PixelFormat::RGB24 || format == PixelFormat::NO_CHANGE)
#endif #endif
return S_OK; return S_OK;
@@ -392,10 +392,10 @@ private:
private: private:
long _nRefCount; long _nRefCount;
CRITICAL_SECTION _critsec; CRITICAL_SECTION _critsec;
MFGrabber* _grabber; MFGrabber* _grabber;
BOOL _bEOS; BOOL _bEOS;
HRESULT _hrStatus; HRESULT _hrStatus;
IMFTransform* _transform; IMFTransform* _transform;
PixelFormat _pixelformat; PixelFormat _pixelformat;
std::atomic<bool> _isBusy; std::atomic<bool> _isBusy;
}; };

View File

@@ -54,7 +54,9 @@ Q_GLOBAL_STATIC_WITH_ARGS(ControlIDPropertyMap, _controlIDPropertyMap, (initCont
static PixelFormat GetPixelFormat(const unsigned int format) static PixelFormat GetPixelFormat(const unsigned int format)
{ {
if (format == V4L2_PIX_FMT_RGB32) return PixelFormat::RGB32; if (format == V4L2_PIX_FMT_RGB32) return PixelFormat::RGB32;
if (format == V4L2_PIX_FMT_RGB24) return PixelFormat::BGR24; if (format == V4L2_PIX_FMT_BGR32) return PixelFormat::BGR32;
if (format == V4L2_PIX_FMT_RGB24) return PixelFormat::RGB24;
if (format == V4L2_PIX_FMT_BGR24) return PixelFormat::BGR24;
if (format == V4L2_PIX_FMT_YUYV) return PixelFormat::YUYV; if (format == V4L2_PIX_FMT_YUYV) return PixelFormat::YUYV;
if (format == V4L2_PIX_FMT_UYVY) return PixelFormat::UYVY; if (format == V4L2_PIX_FMT_UYVY) return PixelFormat::UYVY;
if (format == V4L2_PIX_FMT_NV12) return PixelFormat::NV12; if (format == V4L2_PIX_FMT_NV12) return PixelFormat::NV12;
@@ -557,10 +559,18 @@ void V4L2Grabber::init_device(VideoStandard videoStandard)
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32;
break; break;
case PixelFormat::BGR24: case PixelFormat::BGR32:
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_BGR32;
break;
case PixelFormat::RGB24:
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24;
break; break;
case PixelFormat::BGR24:
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_BGR24;
break;
case PixelFormat::YUYV: case PixelFormat::YUYV:
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
break; break;
@@ -691,7 +701,23 @@ void V4L2Grabber::init_device(VideoStandard videoStandard)
} }
break; break;
case V4L2_PIX_FMT_BGR32:
{
_pixelFormat = PixelFormat::BGR32;
_frameByteSize = _width * _height * 4;
Debug(_log, "Pixel format=BGR32");
}
break;
case V4L2_PIX_FMT_RGB24: case V4L2_PIX_FMT_RGB24:
{
_pixelFormat = PixelFormat::RGB24;
_frameByteSize = _width * _height * 3;
Debug(_log, "Pixel format=RGB24");
}
break;
case V4L2_PIX_FMT_BGR24:
{ {
_pixelFormat = PixelFormat::BGR24; _pixelFormat = PixelFormat::BGR24;
_frameByteSize = _width * _height * 3; _frameByteSize = _width * _height * 3;
@@ -699,7 +725,6 @@ void V4L2Grabber::init_device(VideoStandard videoStandard)
} }
break; break;
case V4L2_PIX_FMT_YUYV: case V4L2_PIX_FMT_YUYV:
{ {
_pixelFormat = PixelFormat::YUYV; _pixelFormat = PixelFormat::YUYV;
@@ -743,9 +768,9 @@ void V4L2Grabber::init_device(VideoStandard videoStandard)
default: default:
#ifdef HAVE_TURBO_JPEG #ifdef HAVE_TURBO_JPEG
throw_exception("Only pixel formats RGB32, BGR24, YUYV, UYVY, NV12, I420 and MJPEG are supported"); throw_exception("Only pixel formats RGB32, BGR32, RGB24, BGR24, YUYV, UYVY, NV12, I420 and MJPEG are supported");
#else #else
throw_exception("Only pixel formats RGB32, BGR24, YUYV, UYVY, NV12 and I420 are supported"); throw_exception("Only pixel formats RGB32, BGR32, RGB24, BGR24, YUYV, UYVY, NV12 and I420 are supported");
#endif #endif
return; return;
} }
@@ -1079,6 +1104,22 @@ void V4L2Grabber::newThreadFrame(Image<ColorRgb> image)
} }
else else
emit newFrame(image); 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) int V4L2Grabber::xioctl(int request, void *arg)

View File

@@ -173,6 +173,10 @@ QStringList GrabberWrapper::availableGrabbers(GrabberTypeFilter type)
#ifdef ENABLE_DX #ifdef ENABLE_DX
grabbers << "dx"; grabbers << "dx";
#endif #endif
#ifdef ENABLE_DDA
grabbers << "dda";
#endif
} }
if (type == GrabberTypeFilter::VIDEO || type == GrabberTypeFilter::ALL) if (type == GrabberTypeFilter::VIDEO || type == GrabberTypeFilter::ALL)

View File

@@ -19,6 +19,7 @@ include_directories(
dev_spi dev_spi
dev_rpi_pwm dev_rpi_pwm
dev_tinker dev_tinker
dev_ftdi
) )
file (GLOB Leddevice_SOURCES file (GLOB Leddevice_SOURCES
@@ -63,7 +64,11 @@ if(ENABLE_DEV_WS281XPWM)
file (GLOB Leddevice_PWM_SOURCES "${CURRENT_SOURCE_DIR}/dev_rpi_pwm/*.h" "${CURRENT_SOURCE_DIR}/dev_rpi_pwm/*.cpp") file (GLOB Leddevice_PWM_SOURCES "${CURRENT_SOURCE_DIR}/dev_rpi_pwm/*.h" "${CURRENT_SOURCE_DIR}/dev_rpi_pwm/*.cpp")
endif() endif()
set(LedDevice_RESOURCES ${CURRENT_SOURCE_DIR}/LedDeviceSchemas.qrc) if (ENABLE_DEV_FTDI)
FILE ( GLOB Leddevice_FTDI_SOURCES "${CURRENT_SOURCE_DIR}/dev_ftdi/*.h" "${CURRENT_SOURCE_DIR}/dev_ftdi/*.cpp")
endif()
set(LedDevice_RESOURCES ${CURRENT_SOURCE_DIR}/LedDeviceSchemas.qrc )
set(Leddevice_SOURCES set(Leddevice_SOURCES
${Leddevice_SOURCES} ${Leddevice_SOURCES}
@@ -74,6 +79,7 @@ set(Leddevice_SOURCES
${Leddevice_SPI_SOURCES} ${Leddevice_SPI_SOURCES}
${Leddevice_TINKER_SOURCES} ${Leddevice_TINKER_SOURCES}
${Leddevice_USB_HID_SOURCES} ${Leddevice_USB_HID_SOURCES}
${Leddevice_FTDI_SOURCES}
) )
# auto generate header file that include all available leddevice headers # auto generate header file that include all available leddevice headers
@@ -165,3 +171,10 @@ if(ENABLE_MDNS)
target_link_libraries(leddevice mdns) target_link_libraries(leddevice mdns)
endif() endif()
if( ENABLE_DEV_FTDI )
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIB_FTDI REQUIRED IMPORTED_TARGET libftdi1 )
target_include_directories(leddevice PRIVATE PkgConfig::LIB_FTDI)
target_link_libraries(leddevice PkgConfig::LIB_FTDI)
endif()

View File

@@ -38,5 +38,8 @@
<file alias="schema-yeelight">schemas/schema-yeelight.json</file> <file alias="schema-yeelight">schemas/schema-yeelight.json</file>
<file alias="schema-razer">schemas/schema-razer.json</file> <file alias="schema-razer">schemas/schema-razer.json</file>
<file alias="schema-cololight">schemas/schema-cololight.json</file> <file alias="schema-cololight">schemas/schema-cololight.json</file>
<file alias="schema-ws2812_ftdi">schemas/schema-ws2812_ftdi.json</file>
<file alias="schema-apa102_ftdi">schemas/schema-apa102_ftdi.json</file>
<file alias="schema-sk6812_ftdi">schemas/schema-sk6812_ftdi.json</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@@ -65,7 +65,7 @@ void LedDeviceWrapper::createLedDevice(const QJsonObject& config)
connect(thread, &QThread::started, _ledDevice, &LedDevice::start); connect(thread, &QThread::started, _ledDevice, &LedDevice::start);
// further signals // 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::switchOn, _ledDevice, &LedDevice::switchOn, Qt::BlockingQueuedConnection);
connect(this, &LedDeviceWrapper::switchOff, _ledDevice, &LedDevice::switchOff, Qt::BlockingQueuedConnection); connect(this, &LedDeviceWrapper::switchOff, _ledDevice, &LedDevice::switchOff, Qt::BlockingQueuedConnection);

View File

@@ -0,0 +1,52 @@
#include "LedDeviceAPA102_ftdi.h"
#define LED_HEADER 0b11100000
#define LED_BRIGHTNESS_FULL 31
LedDeviceAPA102_ftdi::LedDeviceAPA102_ftdi(const QJsonObject &deviceConfig) : ProviderFtdi(deviceConfig)
{
}
LedDevice *LedDeviceAPA102_ftdi::construct(const QJsonObject &deviceConfig)
{
return new LedDeviceAPA102_ftdi(deviceConfig);
}
bool LedDeviceAPA102_ftdi::init(const QJsonObject &deviceConfig)
{
bool isInitOK = false;
// Initialise sub-class
if (ProviderFtdi::init(deviceConfig))
{
_brightnessControlMaxLevel = deviceConfig["brightnessControlMaxLevel"].toInt(LED_BRIGHTNESS_FULL);
Info(_log, "[%s] Setting maximum brightness to [%d] = %d%%", QSTRING_CSTR(_activeDeviceType), _brightnessControlMaxLevel, _brightnessControlMaxLevel * 100 / LED_BRIGHTNESS_FULL);
CreateHeader();
isInitOK = true;
}
return isInitOK;
}
void LedDeviceAPA102_ftdi::CreateHeader()
{
const unsigned int startFrameSize = 4;
// Endframe, add additional 4 bytes to cover SK9922 Reset frame (in case SK9922 were sold as AP102) - has no effect on APA102
const unsigned int endFrameSize = (_ledCount / 32) * 4 + 4;
const unsigned int APAbufferSize = (_ledCount * 4) + startFrameSize + endFrameSize;
_ledBuffer.resize(APAbufferSize, 0);
Debug(_log, "APA102 buffer created for %d LEDs", _ledCount);
}
int LedDeviceAPA102_ftdi::write(const std::vector<ColorRgb> &ledValues)
{
for (signed iLed = 0; iLed < static_cast<int>(_ledCount); ++iLed)
{
const ColorRgb &rgb = ledValues[iLed];
_ledBuffer[4 + iLed * 4 + 0] = LED_HEADER | _brightnessControlMaxLevel;
_ledBuffer[4 + iLed * 4 + 1] = rgb.red;
_ledBuffer[4 + iLed * 4 + 2] = rgb.green;
_ledBuffer[4 + iLed * 4 + 3] = rgb.blue;
}
return writeBytes(_ledBuffer.size(), _ledBuffer.data());
}

View File

@@ -0,0 +1,50 @@
#ifndef LEDEVICET_APA102_H
#define LEDEVICET_APA102_H
#include "ProviderFtdi.h"
class LedDeviceAPA102_ftdi : public ProviderFtdi
{
Q_OBJECT
public:
///
/// @brief Constructs an APA102 LED-device
///
/// @param deviceConfig Device's configuration as JSON-Object
///
explicit LedDeviceAPA102_ftdi(const QJsonObject& deviceConfig);
///
/// @brief Constructs the LED-device
///
/// @param[in] deviceConfig Device's configuration as JSON-Object
/// @return LedDevice constructed
static LedDevice* construct(const QJsonObject& deviceConfig);
private:
///
/// @brief Initialise the device's configuration
///
/// @param[in] deviceConfig the JSON device configuration
/// @return True, if success
///
bool init(const QJsonObject& deviceConfig) override;
void CreateHeader();
///
/// @brief Writes the RGB-Color values to the LEDs.
///
/// @param[in] ledValues The RGB-color per LED
/// @return Zero on success, else negative
///
int write(const std::vector<ColorRgb>& ledValues) override;
/// The brighness level. Possibile values 1 .. 31.
int _brightnessControlMaxLevel;
};
#endif // LEDEVICET_APA102_H

View File

@@ -0,0 +1,96 @@
#include "LedDeviceSk6812_ftdi.h"
LedDeviceSk6812_ftdi::LedDeviceSk6812_ftdi(const QJsonObject &deviceConfig)
: ProviderFtdi(deviceConfig),
_whiteAlgorithm(RGBW::WhiteAlgorithm::INVALID),
SPI_BYTES_PER_COLOUR(4),
bitpair_to_byte{
0b10001000,
0b10001100,
0b11001000,
0b11001100}
{
}
LedDevice *LedDeviceSk6812_ftdi::construct(const QJsonObject &deviceConfig)
{
return new LedDeviceSk6812_ftdi(deviceConfig);
}
bool LedDeviceSk6812_ftdi::init(const QJsonObject &deviceConfig)
{
bool isInitOK = false;
// Initialise sub-class
if (ProviderFtdi::init(deviceConfig))
{
_brightnessControlMaxLevel = deviceConfig["brightnessControlMaxLevel"].toInt(255);
Info(_log, "[%s] Setting maximum brightness to [%d]", QSTRING_CSTR(_activeDeviceType), _brightnessControlMaxLevel);
QString whiteAlgorithm = deviceConfig["whiteAlgorithm"].toString("white_off");
_whiteAlgorithm = RGBW::stringToWhiteAlgorithm(whiteAlgorithm);
if (_whiteAlgorithm == RGBW::WhiteAlgorithm::INVALID)
{
QString errortext = QString ("unknown whiteAlgorithm: %1").arg(whiteAlgorithm);
this->setInError(errortext);
isInitOK = false;
}
else
{
Debug(_log, "whiteAlgorithm : %s", QSTRING_CSTR(whiteAlgorithm));
WarningIf((_baudRate_Hz < 2050000 || _baudRate_Hz > 3750000), _log, "Baud rate %d outside recommended range (2050000 -> 3750000)", _baudRate_Hz);
const int SPI_FRAME_END_LATCH_BYTES = 3;
_ledBuffer.resize(_ledRGBWCount * SPI_BYTES_PER_COLOUR + SPI_FRAME_END_LATCH_BYTES, 0x00);
isInitOK = true;
}
}
return isInitOK;
}
inline __attribute__((always_inline)) uint8_t LedDeviceSk6812_ftdi::scale(uint8_t i, uint8_t scale) {
return (((uint16_t)i) * (1+(uint16_t)(scale))) >> 8;
}
int LedDeviceSk6812_ftdi::write(const std::vector<ColorRgb> &ledValues)
{
unsigned spi_ptr = 0;
const int SPI_BYTES_PER_LED = sizeof(ColorRgbw) * SPI_BYTES_PER_COLOUR;
ColorRgbw temp_rgbw;
ColorRgb scaled_color;
for (const ColorRgb &color : ledValues)
{
scaled_color.red = scale(color.red, _brightnessControlMaxLevel);
scaled_color.green = scale(color.green, _brightnessControlMaxLevel);
scaled_color.blue = scale(color.blue, _brightnessControlMaxLevel);
RGBW::Rgb_to_Rgbw(scaled_color, &temp_rgbw, _whiteAlgorithm);
uint32_t colorBits =
((uint32_t)temp_rgbw.red << 24) +
((uint32_t)temp_rgbw.green << 16) +
((uint32_t)temp_rgbw.blue << 8) +
temp_rgbw.white;
for (int j = SPI_BYTES_PER_LED - 1; j >= 0; j--)
{
_ledBuffer[spi_ptr + j] = bitpair_to_byte[colorBits & 0x3];
colorBits >>= 2;
}
spi_ptr += SPI_BYTES_PER_LED;
}
_ledBuffer[spi_ptr++] = 0;
_ledBuffer[spi_ptr++] = 0;
_ledBuffer[spi_ptr++] = 0;
return writeBytes(_ledBuffer.size(), _ledBuffer.data());
}

View File

@@ -0,0 +1,52 @@
#ifndef LEDEVICESK6812ftdi_H
#define LEDEVICESK6812ftdi_H
#include "ProviderFtdi.h"
class LedDeviceSk6812_ftdi : public ProviderFtdi
{
public:
///
/// @brief Constructs a Sk6801 LED-device
///
/// @param deviceConfig Device's configuration as JSON-Object
///
explicit LedDeviceSk6812_ftdi(const QJsonObject& deviceConfig);
///
/// @brief Constructs the LED-device
///
/// @param[in] deviceConfig Device's configuration as JSON-Object
/// @return LedDevice constructed
static LedDevice* construct(const QJsonObject& deviceConfig);
private:
///
/// @brief Initialise the device's configuration
///
/// @param[in] deviceConfig the JSON device configuration
/// @return True, if success
///
bool init(const QJsonObject& deviceConfig) override;
///
/// @brief Writes the RGB-Color values to the LEDs.
///
/// @param[in] ledValues The RGB-color per LED
/// @return Zero on success, else negative
///
int write(const std::vector<ColorRgb>& ledValues) override;
inline __attribute__((always_inline)) uint8_t scale(uint8_t i, uint8_t scale);
RGBW::WhiteAlgorithm _whiteAlgorithm;
const int SPI_BYTES_PER_COLOUR;
uint8_t bitpair_to_byte[4];
int _brightnessControlMaxLevel;
};
#endif // LEDEVICESK6812ftdi_H

View File

@@ -0,0 +1,93 @@
#include "LedDeviceWs2812_ftdi.h"
/*
From the data sheet:
(TH+TL=1.25μs±600ns)
T0H, 0 code, high level time, 0.40µs ±0.150ns
T0L, 0 code, low level time, 0.85µs ±0.150ns
T1H, 1 code, high level time, 0.80µs ±0.150ns
T1L, 1 code, low level time, 0.45µs ±0.150ns
WT, Wait for the processing time, NA
Trst, Reset code,low level time, 50µs (not anymore... need 300uS for latest revision)
To normalise the pulse times so they fit in 4 SPI bits:
On the assumption that the "low" time doesnt matter much
A SPI bit time of 0.40uS = 2.5 Mbit/sec
T0 is sent as 1000
T1 is sent as 1100
With a bit of excel testing, we can work out the maximum and minimum speeds:
2106000 MIN
2590500 AVG
3075000 MAX
Wait time:
Not Applicable for WS2812
Reset time:
using the max of 3075000, the bit time is 0.325
Reset time is 300uS = 923 bits = 116 bytes
*/
LedDeviceWs2812_ftdi::LedDeviceWs2812_ftdi(const QJsonObject &deviceConfig)
: ProviderFtdi(deviceConfig),
SPI_BYTES_PER_COLOUR(4),
SPI_FRAME_END_LATCH_BYTES(116),
bitpair_to_byte{
0b10001000,
0b10001100,
0b11001000,
0b11001100,
}
{
}
LedDevice *LedDeviceWs2812_ftdi::construct(const QJsonObject &deviceConfig)
{
return new LedDeviceWs2812_ftdi(deviceConfig);
}
bool LedDeviceWs2812_ftdi::init(const QJsonObject &deviceConfig)
{
bool isInitOK = false;
// Initialise sub-class
if (ProviderFtdi::init(deviceConfig))
{
WarningIf((_baudRate_Hz < 2106000 || _baudRate_Hz > 3075000), _log, "Baud rate %d outside recommended range (2106000 -> 3075000)", _baudRate_Hz);
_ledBuffer.resize(_ledRGBCount * SPI_BYTES_PER_COLOUR + SPI_FRAME_END_LATCH_BYTES, 0x00);
isInitOK = true;
}
return isInitOK;
}
int LedDeviceWs2812_ftdi::write(const std::vector<ColorRgb> &ledValues)
{
unsigned spi_ptr = 0;
const int SPI_BYTES_PER_LED = sizeof(ColorRgb) * SPI_BYTES_PER_COLOUR;
for (const ColorRgb &color : ledValues)
{
uint32_t colorBits = ((unsigned int)color.red << 16) | ((unsigned int)color.green << 8) | color.blue;
for (int j = SPI_BYTES_PER_LED - 1; j >= 0; j--)
{
_ledBuffer[spi_ptr + j] = bitpair_to_byte[colorBits & 0x3];
colorBits >>= 2;
}
spi_ptr += SPI_BYTES_PER_LED;
}
for (int j = 0; j < SPI_FRAME_END_LATCH_BYTES; j++)
{
_ledBuffer[spi_ptr++] = 0;
}
return writeBytes(_ledBuffer.size(), _ledBuffer.data());
}

View File

@@ -0,0 +1,49 @@
#ifndef LEDEVICEWS2812_ftdi_H
#define LEDEVICEWS2812_ftdi_H
#include "ProviderFtdi.h"
class LedDeviceWs2812_ftdi : public ProviderFtdi
{
public:
///
/// @brief Constructs a Ws2812 LED-device
///
/// @param deviceConfig Device's configuration as JSON-Object
///
explicit LedDeviceWs2812_ftdi(const QJsonObject& deviceConfig);
///
/// @brief Constructs the LED-device
///
/// @param[in] deviceConfig Device's configuration as JSON-Object
/// @return LedDevice constructed
static LedDevice* construct(const QJsonObject& deviceConfig);
private:
///
/// @brief Initialise the device's configuration
///
/// @param[in] deviceConfig the JSON device configuration
/// @return True, if success
///
bool init(const QJsonObject& deviceConfig) override;
///
/// @brief Writes the RGB-Color values to the LEDs.
///
/// @param[in] ledValues The RGB-color per LED
/// @return Zero on success, else negative
///
int write(const std::vector<ColorRgb>& ledValues) override;
const int SPI_BYTES_PER_COLOUR;
const int SPI_FRAME_END_LATCH_BYTES;
uint8_t bitpair_to_byte[4];
};
#endif // LEDEVICEWS2812_ftdi_H

View File

@@ -0,0 +1,208 @@
// LedDevice includes
#include <leddevice/LedDevice.h>
#include "ProviderFtdi.h"
#include <utils/WaitTime.h>
#include <ftdi.h>
#include <libusb.h>
#define ANY_FTDI_VENDOR 0x0
#define ANY_FTDI_PRODUCT 0x0
#define FTDI_CHECK_RESULT(statement) if (statement) {setInError(ftdi_get_error_string(_ftdic)); return rc;}
namespace Pin
{
// enumerate the AD bus for convenience.
enum bus_t
{
SK = 0x01, // ADBUS0, SPI data clock
DO = 0x02, // ADBUS1, SPI data out
CS = 0x08, // ADBUS3, SPI chip select, active low
};
}
const uint8_t pinInitialState = Pin::CS;
// Use these pins as outputs
const uint8_t pinDirection = Pin::SK | Pin::DO | Pin::CS;
const QString ProviderFtdi::AUTO_SETTING = QString("auto");
ProviderFtdi::ProviderFtdi(const QJsonObject &deviceConfig)
: LedDevice(deviceConfig),
_ftdic(nullptr),
_baudRate_Hz(1000000)
{
}
bool ProviderFtdi::init(const QJsonObject &deviceConfig)
{
bool isInitOK = false;
if (LedDevice::init(deviceConfig))
{
_baudRate_Hz = deviceConfig["rate"].toInt(_baudRate_Hz);
_deviceName = deviceConfig["output"].toString(AUTO_SETTING);
Debug(_log, "_baudRate_Hz [%d]", _baudRate_Hz);
Debug(_log, "_deviceName [%s]", QSTRING_CSTR(_deviceName));
isInitOK = true;
}
return isInitOK;
}
int ProviderFtdi::open()
{
int rc = 0;
_ftdic = ftdi_new();
if (ftdi_init(_ftdic) < 0)
{
_ftdic = nullptr;
setInError("Could not initialize the ftdi library");
return -1;
}
Debug(_log, "Opening FTDI device=%s", QSTRING_CSTR(_deviceName));
FTDI_CHECK_RESULT((rc = ftdi_usb_open_string(_ftdic, QSTRING_CSTR(_deviceName))) < 0);
/* doing this disable resets things if they were in a bad state */
FTDI_CHECK_RESULT((rc = ftdi_disable_bitbang(_ftdic)) < 0);
FTDI_CHECK_RESULT((rc = ftdi_setflowctrl(_ftdic, SIO_DISABLE_FLOW_CTRL)) < 0);
FTDI_CHECK_RESULT((rc = ftdi_set_bitmode(_ftdic, 0x00, BITMODE_RESET)) < 0);
FTDI_CHECK_RESULT((rc = ftdi_set_bitmode(_ftdic, 0xff, BITMODE_MPSSE)) < 0);
double reference_clock = 60e6;
int divisor = (reference_clock / 2 / _baudRate_Hz) - 1;
std::vector<uint8_t> buf = {
DIS_DIV_5,
TCK_DIVISOR,
static_cast<unsigned char>(divisor),
static_cast<unsigned char>(divisor >> 8),
SET_BITS_LOW, // opcode: set low bits (ADBUS[0-7]
pinInitialState, // argument: inital pin state
pinDirection
};
FTDI_CHECK_RESULT((rc = ftdi_write_data(_ftdic, buf.data(), buf.size())) != buf.size());
_isDeviceReady = true;
return rc;
}
int ProviderFtdi::close()
{
LedDevice::close();
if (_ftdic != nullptr) {
Debug(_log, "Closing FTDI device");
// Delay to give time to push color black from writeBlack() into the led,
// otherwise frame transmission will be terminated half way through
wait(30);
ftdi_set_bitmode(_ftdic, 0x00, BITMODE_RESET);
ftdi_usb_close(_ftdic);
ftdi_free(_ftdic);
_ftdic = nullptr;
}
return 0;
}
void ProviderFtdi::setInError(const QString &errorMsg, bool isRecoverable)
{
close();
LedDevice::setInError(errorMsg, isRecoverable);
}
int ProviderFtdi::writeBytes(const qint64 size, const uint8_t *data)
{
int rc;
int count_arg = size - 1;
std::vector<uint8_t> buf = {
SET_BITS_LOW,
pinInitialState & ~Pin::CS,
pinDirection,
MPSSE_DO_WRITE | MPSSE_WRITE_NEG,
static_cast<unsigned char>(count_arg),
static_cast<unsigned char>(count_arg >> 8),
SET_BITS_LOW,
pinInitialState | Pin::CS,
pinDirection
};
// insert before last SET_BITS_LOW command
// SET_BITS_LOW takes 2 arguments, so we're inserting data in -3 position from the end
buf.insert(buf.end() - 3, &data[0], &data[size]);
FTDI_CHECK_RESULT((rc = ftdi_write_data(_ftdic, buf.data(), buf.size())) != buf.size());
return rc;
}
QJsonObject ProviderFtdi::discover(const QJsonObject & /*params*/)
{
QJsonObject devicesDiscovered;
QJsonArray deviceList;
struct ftdi_device_list *devlist;
struct ftdi_context *ftdic;
ftdic = ftdi_new();
if (ftdi_usb_find_all(ftdic, &devlist, ANY_FTDI_VENDOR, ANY_FTDI_PRODUCT) > 0)
{
struct ftdi_device_list *curdev = devlist;
QMap<QString, uint8_t> deviceIndexes;
while (curdev)
{
libusb_device_descriptor desc;
int rc = libusb_get_device_descriptor(curdev->dev, &desc);
if (rc == 0)
{
QString vendorIdentifier = QString("0x%1").arg(desc.idVendor, 4, 16, QChar{'0'});
QString productIdentifier = QString("0x%1").arg(desc.idProduct, 4, 16, QChar{'0'});
QString vendorAndProduct = QString("%1:%2")
.arg(vendorIdentifier)
.arg(productIdentifier);
uint8_t deviceIndex = deviceIndexes.value(vendorAndProduct, 0);
char serial_string[128] = {0};
char manufacturer_string[128] = {0};
char description_string[128] = {0};
ftdi_usb_get_strings2(ftdic, curdev->dev, manufacturer_string, 128, description_string, 128, serial_string, 128);
QString serialNumber {serial_string};
QString ftdiOpenString;
if(!serialNumber.isEmpty())
{
ftdiOpenString = QString("s:%1:%2").arg(vendorAndProduct).arg(serialNumber);
}
else
{
ftdiOpenString = QString("i:%1:%2").arg(vendorAndProduct).arg(deviceIndex);
}
deviceList.push_back(QJsonObject{
{"ftdiOpenString", ftdiOpenString},
{"vendorIdentifier", vendorIdentifier},
{"productIdentifier", productIdentifier},
{"deviceIndex", deviceIndex},
{"serialNumber", serialNumber},
{"manufacturer", manufacturer_string},
{"description", description_string}
});
deviceIndexes.insert(vendorAndProduct, deviceIndex + 1);
}
curdev = curdev->next;
}
}
ftdi_list_free(&devlist);
ftdi_free(ftdic);
devicesDiscovered.insert("ledDeviceType", _activeDeviceType);
devicesDiscovered.insert("devices", deviceList);
Debug(_log, "FTDI devices discovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
return devicesDiscovered;
}

View File

@@ -0,0 +1,76 @@
#ifndef PROVIDERFtdi_H
#define PROVIDERFtdi_H
// LedDevice includes
#include <leddevice/LedDevice.h>
#include <ftdi.h>
///
/// The ProviderFtdi implements an abstract base-class for LedDevices using a Ftdi-device.
///
class ProviderFtdi : public LedDevice
{
Q_OBJECT
public:
///
/// @brief Constructs a Ftdi LED-device
///
ProviderFtdi(const QJsonObject& deviceConfig);
static const QString AUTO_SETTING;
protected:
///
/// @brief Opens the output device.
///
/// @return Zero on success (i.e. device is ready), else negative
///
int open() override;
///
/// Sets configuration
///
/// @param deviceConfig the json device config
/// @return true if success
bool init(const QJsonObject& deviceConfig) override;
///
/// @brief Closes the UDP device.
///
/// @return Zero on success (i.e. device is closed), else negative
///
int close() override;
/// @brief Write the given bytes to the Ftdi-device
///
/// @param[in[ size The length of the data
/// @param[in] data The data
/// @return Zero on success, else negative
///
int writeBytes(const qint64 size, const uint8_t* data);
QJsonObject discover(const QJsonObject& params) override;
/// The Ftdi serial-device
struct ftdi_context *_ftdic;
/// The used baud-rate of the output device
qint32 _baudRate_Hz;
QString _deviceName;
protected slots:
///
/// @brief Set device in error state
///
/// @param errorMsg The error message to be logged
///
void setInError(const QString& errorMsg, bool isRecoverable=true) override;
};
#endif // PROVIDERFtdi_H

View File

@@ -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-Version", "%u.%u.%u", _api_major, _api_minor, _api_patch );
log( "API v2 ready", "%s", _isAPIv2Ready ? "Yes" : "No" ); log( "API v2 ready", "%s", _isAPIv2Ready ? "Yes" : "No" );
log( "Entertainment ready", "%s", _isHueEntertainmentReady ? "Yes" : "No" ); log( "Entertainment ready", "%s", _isHueEntertainmentReady ? "Yes" : "No" );
log( "Use Entertainment API", "%s", _useEntertainmentAPI ? "Yes" : "No" );
log( "DIYHue", "%s", _isDiyHue ? "Yes" : "No" ); log( "DIYHue", "%s", _isDiyHue ? "Yes" : "No" );
} }
} }
@@ -1799,11 +1800,11 @@ bool LedDevicePhilipsHue::init(const QJsonObject &deviceConfig)
if (LedDevicePhilipsHueBridge::init(_devConfig)) if (LedDevicePhilipsHueBridge::init(_devConfig))
{ {
log( "Off on Black", "%s", _switchOffOnBlack ? "Yes" : "No" ); log("Off on Black", "%s", _switchOffOnBlack ? "Yes" : "No" );
log( "Brightness Factor", "%f", _brightnessFactor ); log("Brightness Factor", "%f", _brightnessFactor );
log( "Transition Time", "%d", _transitionTime ); log("Transition Time", "%d", _transitionTime );
log( "Restore Original State", "%s", _isRestoreOrigState ? "Yes" : "No" ); log("Restore Original State", "%s", _isRestoreOrigState ? "Yes" : "No" );
log( "Use Hue Entertainment API", "%s", _useEntertainmentAPI ? "Yes" : "No" ); log("Use Hue Entertainment API", "%s", _useEntertainmentAPI ? "Yes" : "No" );
log("Brightness Threshold", "%f", _blackLevel); log("Brightness Threshold", "%f", _blackLevel);
log("CandyGamma", "%s", _candyGamma ? "Yes" : "No" ); log("CandyGamma", "%s", _candyGamma ? "Yes" : "No" );
log("Time powering off when black", "%s", _onBlackTimeToPowerOff ? "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 ); Debug(_log, "Lights configured: %d", configuredLightsCount );
if (updateLights( getLightMap())) if (updateLights( getLightMap()))
{ {
if (_useApiV2) if (_useApiV2 && _useEntertainmentAPI)
{ {
_channelsCount = getGroupChannelsCount (_groupId); _channelsCount = getGroupChannelsCount (_groupId);
@@ -2208,15 +2209,14 @@ int LedDevicePhilipsHue::write(const std::vector<ColorRgb> & ledValues)
int rc {0}; int rc {0};
if (_isOn) if (_isOn)
{ {
if (!_useApiV2)
{
rc = writeSingleLights( ledValues );
}
if (_useEntertainmentAPI && _isInitLeds) if (_useEntertainmentAPI && _isInitLeds)
{ {
rc= writeStreamData(ledValues); rc= writeStreamData(ledValues);
} }
else
{
rc = writeSingleLights( ledValues );
}
} }
return rc; return rc;
} }
@@ -2482,7 +2482,7 @@ void LedDevicePhilipsHue::setColor(PhilipsHueLight& light, CiColor& color)
QJsonObject colorXY; QJsonObject colorXY;
colorXY[API_X_COORDINATE] = color.x; colorXY[API_X_COORDINATE] = color.x;
colorXY[API_Y_COORDINATE] = color.y; 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 }}); cmd.insert(API_DIMMING, QJsonObject {{API_BRIGHTNESS, bri }});
} }
else else
@@ -2556,7 +2556,7 @@ void LedDevicePhilipsHue::setState(PhilipsHueLight& light, bool on, const CiColo
QJsonObject colorXY; QJsonObject colorXY;
colorXY[API_X_COORDINATE] = color.x; colorXY[API_X_COORDINATE] = color.x;
colorXY[API_Y_COORDINATE] = color.y; 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 }}); cmd.insert(API_DIMMING, QJsonObject {{API_BRIGHTNESS, bri }});
} }
else else

View File

@@ -30,7 +30,8 @@ enum HttpStatusCode {
BadRequest = 400, BadRequest = 400,
UnAuthorized = 401, UnAuthorized = 401,
Forbidden = 403, Forbidden = 403,
NotFound = 404 NotFound = 404,
TooManyRequests = 429
}; };
} //End of constants } //End of constants
@@ -336,6 +337,15 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply)
case HttpStatusCode::NotFound: case HttpStatusCode::NotFound:
advise = "Check Resource given"; advise = "Check Resource given";
break; break;
case HttpStatusCode::TooManyRequests:
{
QString retryAfterTime = response.getHeader("Retry-After");
if (!retryAfterTime.isEmpty())
{
advise = "Retry-After: " + response.getHeader("Retry-After");
}
}
break;
default: default:
advise = httpReason; advise = httpReason;
break; break;

View File

@@ -58,6 +58,11 @@ bool LedDeviceAdalight::init(const QJsonObject &deviceConfig)
case Adalight::ADA: case Adalight::ADA:
Debug( _log, "Adalight driver uses standard Adalight protocol"); Debug( _log, "Adalight driver uses standard Adalight protocol");
break; break;
case Adalight::SKYDIMO:
Debug( _log, "Adalight driver uses Skydimo protocol");
break;
default: default:
Error( _log, "Adalight driver - unsupported protocol"); Error( _log, "Adalight driver - unsupported protocol");
return false; return false;
@@ -71,10 +76,6 @@ bool LedDeviceAdalight::init(const QJsonObject &deviceConfig)
void LedDeviceAdalight::prepareHeader() void LedDeviceAdalight::prepareHeader()
{ {
// create ledBuffer
uint totalLedCount = _ledCount;
_bufferLength = static_cast<qint64>(HEADER_SIZE + _ledRGBCount);
switch (_streamProtocol) { switch (_streamProtocol) {
case Adalight::LBAPA: case Adalight::LBAPA:
{ {
@@ -82,7 +83,6 @@ void LedDeviceAdalight::prepareHeader()
const unsigned int bytesPerRGBLed = 4; const unsigned int bytesPerRGBLed = 4;
const unsigned int endFrameSize = qMax<unsigned int>(((_ledCount + 15) / 16), bytesPerRGBLed); const unsigned int endFrameSize = qMax<unsigned int>(((_ledCount + 15) / 16), bytesPerRGBLed);
_bufferLength = HEADER_SIZE + (_ledCount * bytesPerRGBLed) + startFrameSize + endFrameSize; _bufferLength = HEADER_SIZE + (_ledCount * bytesPerRGBLed) + startFrameSize + endFrameSize;
_ledBuffer.resize(static_cast<size_t>(_bufferLength), 0x00); _ledBuffer.resize(static_cast<size_t>(_bufferLength), 0x00);
// init constant data values // init constant data values
@@ -91,39 +91,47 @@ void LedDeviceAdalight::prepareHeader()
_ledBuffer[iLed*4+HEADER_SIZE] = 0xFF; _ledBuffer[iLed*4+HEADER_SIZE] = 0xFF;
} }
} }
break; break;
case Adalight::SKYDIMO:
{
_bufferLength = static_cast<qint64>(HEADER_SIZE + _ledRGBCount);
_ledBuffer.resize(static_cast<size_t>(_bufferLength), 0x00);
_ledBuffer[0] = 'A';
_ledBuffer[1] = 'd';
_ledBuffer[2] = 'a';
_ledBuffer[3] = 0;
_ledBuffer[4] = 0;
_ledBuffer[5] = static_cast<quint8>(_ledCount);
}
break;
case Adalight::AWA: case Adalight::AWA:
_bufferLength += 8; {
[[fallthrough]]; _bufferLength = static_cast<qint64>(HEADER_SIZE + _ledRGBCount + 8);
_ledBuffer.resize(static_cast<size_t>(_bufferLength), 0x00);
_ledBuffer[0] = 'A';
_ledBuffer[1] = 'w';
_ledBuffer[2] = _white_channel_calibration ? 'A' : 'a';
qToBigEndian<quint16>(static_cast<quint16>(_ledCount-1), &_ledBuffer[3]);
_ledBuffer[5] = _ledBuffer[3] ^ _ledBuffer[4] ^ 0x55; // Checksum
}
break;
case Adalight::ADA: case Adalight::ADA:
[[fallthrough]]; [[fallthrough]];
default: default:
totalLedCount -= 1; _bufferLength = static_cast<qint64>(HEADER_SIZE + _ledRGBCount);
_ledBuffer.resize(static_cast<size_t>(_bufferLength), 0x00); _ledBuffer.resize(static_cast<size_t>(_bufferLength), 0x00);
break; _ledBuffer[0] = 'A';
}
_ledBuffer[0] = 'A';
if (_streamProtocol == Adalight::AWA )
{
_ledBuffer[1] = 'w';
_ledBuffer[2] = _white_channel_calibration ? 'A' : 'a';
}
else
{
_ledBuffer[1] = 'd'; _ledBuffer[1] = 'd';
_ledBuffer[2] = 'a'; _ledBuffer[2] = 'a';
qToBigEndian<quint16>(static_cast<quint16>(_ledCount-1), &_ledBuffer[3]);
_ledBuffer[5] = _ledBuffer[3] ^ _ledBuffer[4] ^ 0x55; // Checksum
break;
} }
qToBigEndian<quint16>(static_cast<quint16>(totalLedCount), &_ledBuffer[3]);
_ledBuffer[5] = _ledBuffer[3] ^ _ledBuffer[4] ^ 0x55; // Checksum
Debug( _log, "Adalight header for %d leds (size: %d): %c%c%c 0x%02x 0x%02x 0x%02x", _ledCount, _ledBuffer.size(), Debug( _log, "Adalight header for %d leds (size: %d): %c%c%c 0x%02x 0x%02x 0x%02x", _ledCount, _ledBuffer.size(),
_ledBuffer[0], _ledBuffer[1], _ledBuffer[2], _ledBuffer[3], _ledBuffer[4], _ledBuffer[5] ); _ledBuffer[0], _ledBuffer[1], _ledBuffer[2], _ledBuffer[3], _ledBuffer[4], _ledBuffer[5] );
} }
int LedDeviceAdalight::write(const std::vector<ColorRgb> & ledValues) int LedDeviceAdalight::write(const std::vector<ColorRgb> & ledValues)
{ {
if (_ledCount != ledValues.size()) if (_ledCount != ledValues.size())

View File

@@ -10,7 +10,8 @@ typedef enum ProtocolType
{ {
ADA = 0, ADA = 0,
LBAPA, LBAPA,
AWA AWA,
SKYDIMO
} PROTOCOLTYPE; } PROTOCOLTYPE;
} }

View File

@@ -11,10 +11,10 @@
"streamProtocol": { "streamProtocol": {
"type": "string", "type": "string",
"title": "edt_dev_spec_stream_protocol_title", "title": "edt_dev_spec_stream_protocol_title",
"enum": [ "0", "1", "2" ], "enum": [ "0", "1", "2", "3" ],
"default": "0", "default": "0",
"options": { "options": {
"enum_titles": [ "edt_dev_spec_ada_mode_title", "edt_dev_spec_LBap102Mode_title","edt_dev_spec_awa_mode_title" ] "enum_titles": [ "edt_dev_spec_ada_mode_title", "edt_dev_spec_LBap102Mode_title","edt_dev_spec_awa_mode_title", "edt_dev_spec_skydimo_mode_title" ]
}, },
"propertyOrder": 2 "propertyOrder": 2
}, },

View File

@@ -0,0 +1,27 @@
{
"type": "object",
"required": true,
"properties": {
"output": {
"type": "string",
"title":"edt_dev_spec_outputPath_title",
"propertyOrder": 1
},
"rate": {
"type": "integer",
"title": "edt_dev_spec_baudrate_title",
"default": 5000000,
"propertyOrder": 2
},
"brightnessControlMaxLevel": {
"type": "integer",
"title": "edt_conf_color_brightness_title",
"default": 31,
"minimum": 1,
"maximum": 31,
"propertyOrder": 3
}
},
"additionalProperties": true
}

View File

@@ -0,0 +1,60 @@
{
"type": "object",
"required": true,
"properties": {
"output": {
"type": "string",
"title": "edt_dev_spec_outputPath_title",
"required": true,
"propertyOrder": 1
},
"rate": {
"type": "integer",
"step": 100000,
"title": "edt_dev_spec_baudrate_title",
"default": 3200000,
"minimum": 2050000,
"maximum": 3750000,
"propertyOrder": 2
},
"brightnessControlMaxLevel": {
"type": "integer",
"title": "edt_conf_color_brightness_title",
"default": 255,
"minimum": 1,
"maximum": 255,
"propertyOrder": 3
},
"whiteAlgorithm": {
"type": "string",
"title": "edt_dev_spec_whiteLedAlgor_title",
"enum": [
"subtract_minimum",
"sub_min_cool_adjust",
"sub_min_warm_adjust",
"cold_white",
"neutral_white",
"auto",
"auto_max",
"auto_accurate",
"white_off"
],
"default": "white_off",
"options": {
"enum_titles": [
"edt_dev_enum_subtract_minimum",
"edt_dev_enum_sub_min_cool_adjust",
"edt_dev_enum_sub_min_warm_adjust",
"edt_dev_enum_cold_white",
"edt_dev_enum_neutral_white",
"edt_dev_enum_auto",
"edt_dev_enum_auto_max",
"edt_dev_enum_auto_accurate",
"edt_dev_enum_white_off"
]
},
"propertyOrder": 4
}
},
"additionalProperties": true
}

View File

@@ -22,10 +22,30 @@
"whiteAlgorithm": { "whiteAlgorithm": {
"type": "string", "type": "string",
"title":"edt_dev_spec_whiteLedAlgor_title", "title":"edt_dev_spec_whiteLedAlgor_title",
"enum" : ["subtract_minimum","sub_min_cool_adjust","sub_min_warm_adjust","white_off"], "enum" : [
"subtract_minimum",
"sub_min_cool_adjust",
"sub_min_warm_adjust",
"cold_white",
"neutral_white",
"auto",
"auto_max",
"auto_accurate",
"white_off"
],
"default": "subtract_minimum", "default": "subtract_minimum",
"options" : { "options" : {
"enum_titles" : ["edt_dev_enum_subtract_minimum", "edt_dev_enum_sub_min_cool_adjust","edt_dev_enum_sub_min_warm_adjust", "edt_dev_enum_white_off"] "enum_titles" : [
"edt_dev_enum_subtract_minimum",
"edt_dev_enum_sub_min_cool_adjust",
"edt_dev_enum_sub_min_warm_adjust",
"edt_dev_enum_cold_white",
"edt_dev_enum_neutral_white",
"edt_dev_enum_auto",
"edt_dev_enum_auto_max",
"edt_dev_enum_auto_accurate",
"edt_dev_enum_white_off"
]
}, },
"propertyOrder" : 4 "propertyOrder" : 4
}, },

View File

@@ -0,0 +1,20 @@
{
"type": "object",
"required": true,
"properties": {
"output": {
"type": "string",
"title": "edt_dev_spec_outputPath_title",
"propertyOrder": 1
},
"rate": {
"type": "integer",
"title": "edt_dev_spec_baudrate_title",
"default": 3075000,
"minimum": 2106000,
"maximum": 3075000,
"propertyOrder": 2
}
},
"additionalProperties": true
}

View File

@@ -43,10 +43,30 @@
"whiteAlgorithm": { "whiteAlgorithm": {
"type": "string", "type": "string",
"title":"edt_dev_spec_whiteLedAlgor_title", "title":"edt_dev_spec_whiteLedAlgor_title",
"enum" : ["subtract_minimum","sub_min_cool_adjust","sub_min_warm_adjust","white_off"], "enum" : [
"subtract_minimum",
"sub_min_cool_adjust",
"sub_min_warm_adjust",
"cold_white",
"neutral_white",
"auto",
"auto_max",
"auto_accurate",
"white_off"
],
"default": "subtract_minimum", "default": "subtract_minimum",
"options" : { "options" : {
"enum_titles" : ["edt_dev_enum_subtract_minimum", "edt_dev_enum_sub_min_cool_adjust","edt_dev_enum_sub_min_warm_adjust", "edt_dev_enum_white_off"] "enum_titles" : [
"edt_dev_enum_subtract_minimum",
"edt_dev_enum_sub_min_cool_adjust",
"edt_dev_enum_sub_min_warm_adjust",
"edt_dev_enum_cold_white",
"edt_dev_enum_neutral_white",
"edt_dev_enum_auto",
"edt_dev_enum_auto_max",
"edt_dev_enum_auto_accurate",
"edt_dev_enum_white_off"
]
}, },
"propertyOrder" : 7 "propertyOrder" : 7
}, },

View File

@@ -29,9 +29,6 @@ void ImageResampler::processImage(const uint8_t * data, int width, int height, i
int cropTop = _cropTop; int cropTop = _cropTop;
int cropBottom = _cropBottom; int cropBottom = _cropBottom;
int xDestFlip = 0, yDestFlip = 0;
int uOffset = 0, vOffset = 0;
// handle 3D mode // handle 3D mode
switch (_videoMode) switch (_videoMode)
{ {
@@ -53,118 +50,191 @@ void ImageResampler::processImage(const uint8_t * data, int width, int height, i
outputImage.resize(outputWidth, outputHeight); 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; case FlipMode::NO_CHANGE:
if (pixelFormat == PixelFormat::NV12) xDestStart = 0;
{ xDestEnd = outputWidth-1;
uOffset = (height + ySource / 2) * lineLength; yDestStart = 0;
} yDestEnd = outputHeight-1;
else if (pixelFormat == PixelFormat::I420) break;
{ case FlipMode::HORIZONTAL:
uOffset = width * height + (ySource/2) * width/2; xDestStart = 0;
vOffset = width * height * 1.25 + (ySource/2) * width/2; 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: for (int xDest = xDestStart, xSource = cropLeft + (_horizontalDecimation >> 1); xDest <= xDestEnd; xSource += _horizontalDecimation, ++xDest)
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:
{ {
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 y = data[index+1];
uint8_t u = ((xSource&1) == 0) ? data[index ] : data[index-2]; uint8_t u = ((xSource&1) == 0) ? data[index ] : data[index-2];
uint8_t v = ((xSource&1) == 0) ? data[index+2] : data[index ]; uint8_t v = ((xSource&1) == 0) ? data[index+2] : data[index ];
ColorSys::yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue); 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 y = data[index];
uint8_t u = ((xSource&1) == 0) ? data[index+1] : data[index-1]; uint8_t u = ((xSource&1) == 0) ? data[index+1] : data[index-1];
uint8_t v = ((xSource&1) == 0) ? data[index+3] : 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); 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.blue = (data[index] & 0x1f) << 3;
rgb.green = (((data[index+1] & 0x7) << 3) | (data[index] & 0xE0) >> 5) << 2; rgb.green = (((data[index+1] & 0x7) << 3) | (data[index] & 0xE0) >> 5) << 2;
rgb.red = (data[index+1] & 0xF8); rgb.red = (data[index+1] & 0xF8);
} }
break; }
case PixelFormat::BGR24: break;
}
case PixelFormat::RGB24:
{
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));
rgb.blue = data[index ]; int index = lineLength * ySource + (xSource << 1) + xSource;
rgb.green = data[index+1];
rgb.red = data[index+2];
}
break;
case PixelFormat::RGB32:
{
int index = yOffset + (xSource << 2);
rgb.red = data[index ]; rgb.red = data[index ];
rgb.green = data[index+1]; rgb.green = data[index+1];
rgb.blue = data[index+2]; rgb.blue = data[index+2];
} }
break; }
case PixelFormat::BGR32: 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 << 2); ColorRgb & rgb = outputImage(abs(xDest), abs(yDest));
int index = lineLength * ySource + (xSource << 1) + xSource;
rgb.blue = data[index ]; rgb.blue = data[index ];
rgb.green = data[index+1]; rgb.green = data[index+1];
rgb.red = data[index+2]; rgb.red = data[index+2];
} }
break; }
case PixelFormat::NV12: 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)
{ {
uint8_t y = data[yOffset + xSource]; 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:
{
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)
{
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:
{
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)
{
ColorRgb & rgb = outputImage(abs(xDest), abs(yDest));
uint8_t y = data[lineLength * ySource + xSource];
uint8_t u = data[uOffset + ((xSource >> 1) << 1)]; uint8_t u = data[uOffset + ((xSource >> 1) << 1)];
uint8_t v = data[uOffset + ((xSource >> 1) << 1) + 1]; uint8_t v = data[uOffset + ((xSource >> 1) << 1) + 1];
ColorSys::yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue); 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 u = data[uOffset + (xSource >> 1)];
int v = data[vOffset + (xSource >> 1)]; int v = data[vOffset + (xSource >> 1)];
ColorSys::yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue); 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;
} }
} }

View File

@@ -3,6 +3,8 @@
#include <utils/RgbToRgbw.h> #include <utils/RgbToRgbw.h>
#include <utils/Logger.h> #include <utils/Logger.h>
#define ROUND_DIVIDE(number, denom) (((number) + (denom) / 2) / (denom))
namespace RGBW { namespace RGBW {
WhiteAlgorithm stringToWhiteAlgorithm(const QString& str) WhiteAlgorithm stringToWhiteAlgorithm(const QString& str)
@@ -19,7 +21,27 @@ WhiteAlgorithm stringToWhiteAlgorithm(const QString& str)
{ {
return WhiteAlgorithm::SUB_MIN_COOL_ADJUST; return WhiteAlgorithm::SUB_MIN_COOL_ADJUST;
} }
if (str.isEmpty() || str == "white_off") if (str == "cold_white")
{
return WhiteAlgorithm::COLD_WHITE;
}
if (str == "neutral_white")
{
return WhiteAlgorithm::NEUTRAL_WHITE;
}
if (str == "auto")
{
return WhiteAlgorithm::AUTO;
}
if (str == "auto_max")
{
return WhiteAlgorithm::AUTO_MAX;
}
if (str == "auto_accurate")
{
return WhiteAlgorithm::AUTO_ACCURATE;
}
if (str.isEmpty() || str == "white_off")
{ {
return WhiteAlgorithm::WHITE_OFF; return WhiteAlgorithm::WHITE_OFF;
} }
@@ -77,6 +99,63 @@ void Rgb_to_Rgbw(ColorRgb input, ColorRgbw * output, WhiteAlgorithm algorithm)
output->white = 0; output->white = 0;
break; break;
} }
case WhiteAlgorithm::AUTO_MAX:
{
output->red = input.red;
output->green = input.green;
output->blue = input.blue;
output->white = input.red > input.green ? (input.red > input.blue ? input.red : input.blue) : (input.green > input.blue ? input.green : input.blue);
break;
}
case WhiteAlgorithm::AUTO_ACCURATE:
{
output->white = input.red < input.green ? (input.red < input.blue ? input.red : input.blue) : (input.green < input.blue ? input.green : input.blue);
output->red = input.red - output->white;
output->green = input.green - output->white;
output->blue = input.blue - output->white;
break;
}
case WhiteAlgorithm::AUTO:
{
output->red = input.red;
output->green = input.green;
output->blue = input.blue;
output->white = input.red < input.green ? (input.red < input.blue ? input.red : input.blue) : (input.green < input.blue ? input.green : input.blue);
break;
}
case WhiteAlgorithm::NEUTRAL_WHITE:
case WhiteAlgorithm::COLD_WHITE:
{
//cold white config
uint8_t gain = 0xFF;
uint8_t red = 0xA0;
uint8_t green = 0xA0;
uint8_t blue = 0xA0;
if (algorithm == WhiteAlgorithm::NEUTRAL_WHITE) {
gain = 0xFF;
red = 0xB0;
green = 0xB0;
blue = 0x70;
}
uint8_t _r = qMin((uint32_t)(ROUND_DIVIDE(red * input.red, 0xFF)), (uint32_t)0xFF);
uint8_t _g = qMin((uint32_t)(ROUND_DIVIDE(green * input.green, 0xFF)), (uint32_t)0xFF);
uint8_t _b = qMin((uint32_t)(ROUND_DIVIDE(blue * input.blue, 0xFF)), (uint32_t)0xFF);
output->white = qMin(_r, qMin(_g, _b));
output->red = input.red - _r;
output->green = input.green - _g;
output->blue = input.blue - _b;
uint8_t _w = qMin((uint32_t)(ROUND_DIVIDE(gain * output->white, 0xFF)), (uint32_t)0xFF);
output->white = _w;
break;
}
default: default:
break; break;
} }

View File

@@ -118,6 +118,10 @@ if(ENABLE_DX)
target_link_libraries(${PROJECT_NAME} directx-grabber) target_link_libraries(${PROJECT_NAME} directx-grabber)
endif (ENABLE_DX) endif (ENABLE_DX)
if(ENABLE_DDA)
target_link_libraries(${PROJECT_NAME} dda-grabber)
endif (ENABLE_DDA)
if(ENABLE_CEC) if(ENABLE_CEC)
target_link_libraries(${PROJECT_NAME} cechandler) target_link_libraries(${PROJECT_NAME} cechandler)
endif (ENABLE_CEC) endif (ENABLE_CEC)

View File

@@ -425,7 +425,7 @@ void HyperionDaemon::handleSettingsUpdate(settings::type settingsType, const QJs
void HyperionDaemon::updateScreenGrabbers(const QJsonDocument& grabberConfig) 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"); Info(_log, "No screen capture supported on this platform");
return; return;
#endif #endif
@@ -469,6 +469,12 @@ void HyperionDaemon::updateScreenGrabbers(const QJsonDocument& grabberConfig)
startGrabber<DirectXWrapper>(_screenGrabber, grabberConfig); startGrabber<DirectXWrapper>(_screenGrabber, grabberConfig);
} }
#endif #endif
#ifdef ENABLE_DDA
else if (type == "dda")
{
startGrabber<DDAWrapper>(_screenGrabber, grabberConfig);
}
#endif
#ifdef ENABLE_FB #ifdef ENABLE_FB
else if (type == "framebuffer") else if (type == "framebuffer")
{ {

View File

@@ -64,6 +64,12 @@
typedef QObject DirectXWrapper; typedef QObject DirectXWrapper;
#endif #endif
#ifdef ENABLE_DDA
#include <grabber/dda/DDAWrapper.h>
#else
typedef QObject DDAWrapper;
#endif
#include <hyperion/GrabberWrapper.h> #include <hyperion/GrabberWrapper.h>
#ifdef ENABLE_AUDIO #ifdef ENABLE_AUDIO
#include <grabber/audio/AudioWrapper.h> #include <grabber/audio/AudioWrapper.h>