diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index 953a77b7b..bad34af35 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -1144,5 +1144,7 @@ "perf_no" : "No", "perf_decoding_time": "time", "perf_frames" : "frames", - "perf_invalid_frames" : "invalid frames" + "perf_invalid_frames" : "invalid frames", + "edt_conf_fbs_tonemapping_expl": "If enabled, HyperHDR will try to correct colors of the HDR10 content that was received by flatbuffers as SDR format. Default 3D LUT file 'lut_lin_tables.3d' is already included. You can generate one and preview the effect using link in the 'Advanced menu'.
Your typical hidden configuration folder to upload that file in is (check 'Logs' page to confirm):
Rpi→/home/pi/.hyperhdr
Windows→c:/Users/NAME/.hyperhdr", + "edt_conf_fbs_tonemapping_title": "HDR to SDR tone mapping" } diff --git a/include/flatbufserver/FlatBufferConnection.h b/include/flatbufserver/FlatBufferConnection.h index 220eb232c..9aaa3ac96 100644 --- a/include/flatbufserver/FlatBufferConnection.h +++ b/include/flatbufserver/FlatBufferConnection.h @@ -75,6 +75,8 @@ class FlatBufferConnection : public QObject /// void sendMessage(const uint8_t* buffer, uint32_t size); + bool isFree(); + public slots: /// /// @brief Set the leds according to the given image @@ -100,6 +102,8 @@ private slots: /// ///void setVideoModeHdr(int hdr); + void onImage(const Image& image); + private: /// @@ -132,4 +136,5 @@ private slots: flatbuffers::FlatBufferBuilder _builder; bool _registered; + bool _free; }; diff --git a/include/flatbufserver/FlatBufferServer.h b/include/flatbufserver/FlatBufferServer.h index 11e90400f..730dff4ff 100644 --- a/include/flatbufserver/FlatBufferServer.h +++ b/include/flatbufserver/FlatBufferServer.h @@ -21,9 +21,15 @@ class FlatBufferServer : public QObject { Q_OBJECT public: - FlatBufferServer(const QJsonDocument& config, QObject* parent = nullptr); + FlatBufferServer(const QJsonDocument& config, const QString& configurationPath, QObject* parent = nullptr); ~FlatBufferServer() override; + static FlatBufferServer* instance; + static FlatBufferServer* getInstance() { return instance; } + +signals: + void hdrToneMappingChanged(bool enabled, uint8_t* lutBuffer); + public slots: /// /// @brief Handle settings update @@ -34,6 +40,8 @@ public slots: void initServer(); + void setHdrToneMappingEnabled(bool enabled); + private slots: /// /// @brief Is called whenever a new socket wants to connect @@ -57,6 +65,17 @@ private slots: void stopServer(); + /// + /// @brief Get shared LUT file folder + /// + QString GetSharedLut(); + + /// + /// @brief Load LUT file + /// + void loadLutFile(); + + private: QTcpServer* _server; NetOrigin* _netOrigin; @@ -67,4 +86,10 @@ private slots: BonjourServiceRegister* _serviceRegister = nullptr; QVector _openConnections; + + // tone mapping + bool _hdrToneMappingEnabled; + uint8_t* _lutBuffer; + bool _lutBufferInit; + QString _configurationPath; }; diff --git a/sources/api/API.cpp b/sources/api/API.cpp index 60a1b70bf..04bb799b7 100644 --- a/sources/api/API.cpp +++ b/sources/api/API.cpp @@ -25,6 +25,7 @@ #include #include #include +#include // bonjour wrapper #include @@ -211,7 +212,7 @@ bool API::setComponentState(const QString& comp, bool& compState, QString& reply input = "VIDEOGRABBER"; Components component = stringToComponent(input); if (component == COMP_ALL) - { + { QMetaObject::invokeMethod(HyperHdrIManager::getInstance(), "toggleStateAllInstances", Qt::QueuedConnection, Q_ARG(bool, compState)); return true; @@ -237,7 +238,11 @@ void API::setLedMappingType(int type, hyperhdr::Components callerComp) void API::setVideoModeHdr(int hdr, hyperhdr::Components callerComp) { - QMetaObject::invokeMethod(GrabberWrapper::getInstance(), "setHdrToneMappingEnabled", Qt::QueuedConnection, Q_ARG(int, hdr)); + if (GrabberWrapper::getInstance() != nullptr) + QMetaObject::invokeMethod(GrabberWrapper::getInstance(), "setHdrToneMappingEnabled", Qt::QueuedConnection, Q_ARG(int, hdr)); + + if (FlatBufferServer::getInstance() != nullptr) + QMetaObject::invokeMethod(FlatBufferServer::getInstance(), "setHdrToneMappingEnabled", Qt::QueuedConnection, Q_ARG(bool, hdr)); } bool API::setEffect(const EffectCmdData& dat, hyperhdr::Components callerComp) diff --git a/sources/flatbufserver/FlatBufferClient.cpp b/sources/flatbufserver/FlatBufferClient.cpp index 2a29448f8..2a9efd5cf 100644 --- a/sources/flatbufserver/FlatBufferClient.cpp +++ b/sources/flatbufserver/FlatBufferClient.cpp @@ -6,7 +6,10 @@ #include #include -FlatBufferClient::FlatBufferClient(QTcpSocket* socket, int timeout, QObject* parent) +// util includes +#include + +FlatBufferClient::FlatBufferClient(QTcpSocket* socket, int timeout, bool hdrToneMappingEnabled, uint8_t* lutBuffer, QObject* parent) : QObject(parent) , _log(Logger::getInstance("FLATBUFSERVER")) , _socket(socket) @@ -14,6 +17,8 @@ FlatBufferClient::FlatBufferClient(QTcpSocket* socket, int timeout, QObject* par , _timeoutTimer(new QTimer(this)) , _timeout(timeout * 1000) , _priority() + , _hdrToneMappingEnabled(hdrToneMappingEnabled) + , _lutBuffer(lutBuffer) { // timer setup _timeoutTimer->setSingleShot(true); @@ -65,6 +70,12 @@ void FlatBufferClient::forceClose() _socket->close(); } +void FlatBufferClient::setHdrToneMappingEnabled(bool enabled, uint8_t* lutBuffer) +{ + _hdrToneMappingEnabled = enabled; + _lutBuffer = lutBuffer; +} + void FlatBufferClient::disconnected() { Debug(_log, "Socket Closed"); @@ -164,6 +175,10 @@ void FlatBufferClient::handleImageCommand(const hyperhdrnet::Image* image) Image imageDest(width, height); memmove(imageDest.memptr(), imageData->data(), imageData->size()); + + // tone mapping + FrameDecoder::applyLUT((uint8_t*)imageDest.memptr(), imageDest.width(), imageDest.height(), _lutBuffer, _hdrToneMappingEnabled); + emit setGlobalInputImage(_priority, imageDest, duration); } diff --git a/sources/flatbufserver/FlatBufferClient.h b/sources/flatbufserver/FlatBufferClient.h index a878dbd50..90e3b805c 100644 --- a/sources/flatbufserver/FlatBufferClient.h +++ b/sources/flatbufserver/FlatBufferClient.h @@ -26,7 +26,7 @@ class FlatBufferClient : public QObject /// @param timeout The timeout when a client is automatically disconnected and the priority unregistered /// @param parent The parent /// - explicit FlatBufferClient(QTcpSocket* socket, int timeout, QObject* parent = nullptr); + explicit FlatBufferClient(QTcpSocket* socket, int timeout, bool hdrToneMappingEnabled, uint8_t* lutBuffer, QObject* parent = nullptr); signals: /// @@ -65,6 +65,11 @@ public slots: /// void forceClose(); + /// + /// @brief Change HDR tone mapping + /// + void setHdrToneMappingEnabled(bool enabled, uint8_t* lutBuffer); + private slots: /// /// @brief Is called whenever the socket got new data to read @@ -140,4 +145,8 @@ private slots: // Flatbuffers builder flatbuffers::FlatBufferBuilder _builder; + + // tone mapping + bool _hdrToneMappingEnabled; + uint8_t* _lutBuffer; }; diff --git a/sources/flatbufserver/FlatBufferConnection.cpp b/sources/flatbufserver/FlatBufferConnection.cpp index 48133721c..688f391d3 100644 --- a/sources/flatbufserver/FlatBufferConnection.cpp +++ b/sources/flatbufserver/FlatBufferConnection.cpp @@ -18,6 +18,7 @@ FlatBufferConnection::FlatBufferConnection(const QString& origin, const QString& , _prevSocketState(QAbstractSocket::UnconnectedState) , _log(Logger::getInstance("FLATBUFCONN")) , _registered(false) + , _free(true) { QStringList parts = address.split(":"); if (parts.size() != 2) @@ -45,6 +46,8 @@ FlatBufferConnection::FlatBufferConnection(const QString& origin, const QString& connect(&_timer, &QTimer::timeout, this, &FlatBufferConnection::connectToHost); _timer.start(); + + connect(this, &FlatBufferConnection::onImage, this, &FlatBufferConnection::setImage); } FlatBufferConnection::~FlatBufferConnection() @@ -126,6 +129,8 @@ void FlatBufferConnection::setColor(const ColorRgb& color, int priority, int dur void FlatBufferConnection::setImage(const Image& image) { + _free = false; + auto imgData = _builder.CreateVector(reinterpret_cast(image.memptr()), image.size()); auto rawImg = hyperhdrnet::CreateRawImage(_builder, imgData, image.width(), image.height()); auto imageReq = hyperhdrnet::CreateImage(_builder, hyperhdrnet::ImageType_RawImage, rawImg.Union(), -1); @@ -134,6 +139,14 @@ void FlatBufferConnection::setImage(const Image& image) _builder.Finish(req); sendMessage(_builder.GetBufferPointer(), _builder.GetSize()); _builder.Clear(); + + _free = true; +} + + +bool FlatBufferConnection::isFree() +{ + return _free; } void FlatBufferConnection::clear(int priority) diff --git a/sources/flatbufserver/FlatBufferServer.cpp b/sources/flatbufserver/FlatBufferServer.cpp index df38593d8..c6c6fb78f 100644 --- a/sources/flatbufserver/FlatBufferServer.cpp +++ b/sources/flatbufserver/FlatBufferServer.cpp @@ -15,21 +15,38 @@ #include #include #include +#include +#include +#include -FlatBufferServer::FlatBufferServer(const QJsonDocument& config, QObject* parent) +#define LUT_FILE_SIZE 50331648 + +FlatBufferServer* FlatBufferServer::instance = nullptr; + +FlatBufferServer::FlatBufferServer(const QJsonDocument& config, const QString& configurationPath, QObject* parent) : QObject(parent) , _server(new QTcpServer(this)) , _log(Logger::getInstance("FLATBUFSERVER")) , _timeout(5000) , _config(config) + , _hdrToneMappingEnabled(false) + , _lutBuffer(nullptr) + , _lutBufferInit(false) + , _configurationPath(configurationPath) { - + FlatBufferServer::instance = this; } FlatBufferServer::~FlatBufferServer() { stopServer(); delete _server; + + if (_lutBuffer != NULL) + free(_lutBuffer); + _lutBuffer = NULL; + + FlatBufferServer::instance = nullptr; } void FlatBufferServer::initServer() @@ -39,6 +56,19 @@ void FlatBufferServer::initServer() // apply config handleSettingsUpdate(settings::type::FLATBUFSERVER, _config); + + loadLutFile(); +} + +void FlatBufferServer::setHdrToneMappingEnabled(bool enabled) +{ + bool status = _hdrToneMappingEnabled && enabled; + + if (status) + loadLutFile(); + + // inform clients + emit hdrToneMappingChanged(status && _lutBufferInit, _lutBuffer); } void FlatBufferServer::handleSettingsUpdate(settings::type type, const QJsonDocument& config) @@ -56,6 +86,10 @@ void FlatBufferServer::handleSettingsUpdate(settings::type type, const QJsonDocu _port = port; } + // HDR tone mapping + _hdrToneMappingEnabled = obj["hdrToneMapping"].toBool(); + setHdrToneMappingEnabled(_hdrToneMappingEnabled); + // new timeout just for new connections _timeout = obj["timeout"].toInt(5000); // enable check @@ -72,7 +106,7 @@ void FlatBufferServer::newConnection() if (_netOrigin->accessAllowed(socket->peerAddress(), socket->localAddress())) { Debug(_log, "New connection from %s", QSTRING_CSTR(socket->peerAddress().toString())); - FlatBufferClient* client = new FlatBufferClient(socket, _timeout, this); + FlatBufferClient* client = new FlatBufferClient(socket, _timeout, _hdrToneMappingEnabled, _lutBuffer, this); // internal connect(client, &FlatBufferClient::clientDisconnected, this, &FlatBufferServer::clientDisconnected); connect(client, &FlatBufferClient::registerGlobalInput, GlobalSignals::getInstance(), &GlobalSignals::registerGlobalInput); @@ -80,6 +114,7 @@ void FlatBufferServer::newConnection() connect(client, &FlatBufferClient::setGlobalInputImage, GlobalSignals::getInstance(), &GlobalSignals::setGlobalImage); connect(client, &FlatBufferClient::setGlobalInputColor, GlobalSignals::getInstance(), &GlobalSignals::setGlobalColor); connect(GlobalSignals::getInstance(), &GlobalSignals::globalRegRequired, client, &FlatBufferClient::registationRequired); + connect(this, &FlatBufferServer::hdrToneMappingChanged, client, &FlatBufferClient::setHdrToneMappingEnabled); _openConnections.append(client); } else @@ -136,3 +171,77 @@ void FlatBufferServer::stopServer() Info(_log, "Stopped"); } } + +QString FlatBufferServer::GetSharedLut() +{ +#ifdef __APPLE__ + QString ret = QString("%1%2").arg(QCoreApplication::applicationDirPath()).arg("/../lut"); + QFileInfo info(ret); + ret = info.absoluteFilePath(); + return ret; +#else + return QCoreApplication::applicationDirPath(); +#endif +} + +// copied from Grabber::loadLutFile() +// color should always be RGB24 for flatbuffers +void FlatBufferServer::loadLutFile() +{ + QString fileName1 = QString("%1%2").arg(_configurationPath).arg("/lut_lin_tables.3d"); + QString fileName2 = QString("%1%2").arg(GetSharedLut()).arg("/lut_lin_tables.3d"); + QList files({ fileName1, fileName2 }); + +#ifdef __linux__ + QString fileName3 = QString("/usr/share/hyperhdr/lut/lut_lin_tables.3d"); + files.append(fileName3); +#endif + + _lutBufferInit = false; + + if (_hdrToneMappingEnabled) + { + for (QString fileName3d : files) + { + QFile file(fileName3d); + + if (file.open(QIODevice::ReadOnly)) + { + size_t length; + Debug(_log, "LUT file found: %s", QSTRING_CSTR(fileName3d)); + + length = file.size(); + + if (length == LUT_FILE_SIZE * 3) + { + qint64 index = 0; // RGB24 + + file.seek(index); + + if (_lutBuffer == NULL) + _lutBuffer = (unsigned char*)malloc(length + 4); + + if (file.read((char*)_lutBuffer, LUT_FILE_SIZE) != LUT_FILE_SIZE) + { + Error(_log, "Error reading LUT file %s", QSTRING_CSTR(fileName3d)); + } + else + { + _lutBufferInit = true; + Info(_log, "Found and loaded LUT: '%s'", QSTRING_CSTR(fileName3d)); + } + } + else + Error(_log, "LUT file has invalid length: %i %s. Please generate new one LUT table using the generator page.", length, QSTRING_CSTR(fileName3d)); + + file.close(); + + return; + } + else + Warning(_log, "LUT file is not found here: %s", QSTRING_CSTR(fileName3d)); + } + + Error(_log, "Could not find any required LUT file"); + } +} diff --git a/sources/hyperhdr/hyperhdr.cpp b/sources/hyperhdr/hyperhdr.cpp index e005f89eb..68090bcd5 100644 --- a/sources/hyperhdr/hyperhdr.cpp +++ b/sources/hyperhdr/hyperhdr.cpp @@ -277,7 +277,7 @@ void HyperHdrDaemon::startNetworkServices() connect(this, &HyperHdrDaemon::settingsChanged, _jsonServer, &JsonServer::handleSettingsUpdate); // Create FlatBuffer server in thread - _flatBufferServer = new FlatBufferServer(getSetting(settings::type::FLATBUFSERVER)); + _flatBufferServer = new FlatBufferServer(getSetting(settings::type::FLATBUFSERVER), _rootPath); QThread* fbThread = new QThread(this); fbThread->setObjectName("FlatBufferServerThread"); _flatBufferServer->moveToThread(fbThread); diff --git a/sources/hyperhdrbase/MessageForwarder.cpp b/sources/hyperhdrbase/MessageForwarder.cpp index 81382ea13..7552c3a29 100644 --- a/sources/hyperhdrbase/MessageForwarder.cpp +++ b/sources/hyperhdrbase/MessageForwarder.cpp @@ -84,6 +84,20 @@ void MessageForwarder::handleSettingsUpdate(settings::type type, const QJsonDocu if (!_flatSlaves.isEmpty() && obj["enable"].toBool() && _forwarder_enabled) { InfoIf(obj["enable"].toBool(true), _log, "Forward now to flatbuffer targets '%s'", QSTRING_CSTR(_flatSlaves.join(", "))); + + hyperhdr::Components activeCompId = _hyperhdr->getPriorityInfo(_hyperhdr->getCurrentPriority()).componentId; + + disconnect(_hyperhdr, &HyperHdrInstance::forwardV4lProtoMessage, 0, 0); + disconnect(_hyperhdr, &HyperHdrInstance::forwardSystemProtoMessage, 0, 0); + + if (activeCompId == hyperhdr::COMP_SYSTEMGRABBER) + { + connect(_hyperhdr, &HyperHdrInstance::forwardSystemProtoMessage, this, &MessageForwarder::forwardFlatbufferMessage, Qt::UniqueConnection); + } + else if (activeCompId == hyperhdr::COMP_VIDEOGRABBER) + { + connect(_hyperhdr, &HyperHdrInstance::forwardV4lProtoMessage, this, &MessageForwarder::forwardFlatbufferMessage, Qt::UniqueConnection); + } } else if (_flatSlaves.isEmpty() || !obj["enable"].toBool() || !_forwarder_enabled) { @@ -242,7 +256,8 @@ void MessageForwarder::forwardFlatbufferMessage(const QString& name, const Image if (_forwarder_enabled) { for (int i = 0; i < _forwardClients.size(); i++) - _forwardClients.at(i)->setImage(image); + if (_forwardClients.at(i)->isFree()) + emit _forwardClients.at(i)->onImage(image); } } diff --git a/sources/hyperhdrbase/schema/schema-flatbufServer.json b/sources/hyperhdrbase/schema/schema-flatbufServer.json index 68e36f94d..1ccec79fd 100644 --- a/sources/hyperhdrbase/schema/schema-flatbufServer.json +++ b/sources/hyperhdrbase/schema/schema-flatbufServer.json @@ -32,6 +32,15 @@ "minimum" : 1, "default" : 5, "propertyOrder" : 3 + }, + "hdrToneMapping" : + { + "type" : "boolean", + "format": "checkbox", + "required" : true, + "title" : "edt_conf_fbs_tonemapping_title", + "default" : false, + "propertyOrder" : 4 } }, "additionalProperties" : false