Skip to content
Snippets Groups Projects
Commit 5fcdf782 authored by bodzsoaa's avatar bodzsoaa
Browse files

Working reimplementation of libmueb v2.0

parent 224b85af
No related branches found
No related tags found
No related merge requests found
# Created by https://www.toptal.com/developers/gitignore/api/qt,qtcreator,c++
# Edit at https://www.toptal.com/developers/gitignore?templates=qt,qtcreator,c++
### C++ ###
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Linker files
*.ilk
# Debugger Files
*.pdb
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
### Qt ###
# C++ objects and libs
*.so.*
# Qt-es
object_script.*.Release
object_script.*.Debug
*_plugin_import.cpp
/.qmake.cache
/.qmake.stash
*.pro.user
*.pro.user.*
*.qbs.user
*.qbs.user.*
*.moc
moc_*.cpp
moc_*.h
qrc_*.cpp
ui_*.h
*.qmlc
*.jsc
Makefile*
*build-*
*.qm
*.prl
# Qt unit tests
target_wrapper.*
# QtCreator
*.autosave
# QtCreator Qml
*.qmlproject.user
*.qmlproject.user.*
# QtCreator CMake
CMakeLists.txt.user*
# QtCreator 4.8< compilation database
compile_commands.json
# QtCreator local machine specific files for imported projects
*creator.user*
### QtCreator ###
# gitignore for Qt Creator like IDE for pure C/C++ project without Qt
#
# Reference: http://doc.qt.io/qtcreator/creator-project-generic.html
# Qt Creator autogenerated files
# A listing of all the files included in the project
*.files
# Include directories
*.includes
# Project configuration settings like predefined Macros
*.config
# Qt Creator settings
*.creator
# User project settings
*.creator.user*
# Qt Creator backups
# Flags for Clang Code Model
*.cxxflags
*.cflags
# End of https://www.toptal.com/developers/gitignore/api/qt,qtcreator,c++
\ No newline at end of file
cmake_minimum_required(VERSION 3.17)
project(
libmueb
VERSION 4.0
DESCRIPTION "Schönherz Mátrix network library written in C++ using Qt"
LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC OFF)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC OFF)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(
Qt6
COMPONENTS Core Gui Network Concurrent
REQUIRED)
add_subdirectory(src)
#ifndef LIBMUEB_LIBMUEB_GLOBAL_H_
#define LIBMUEB_LIBMUEB_GLOBAL_H_
#include <QtCore/qglobal.h>
#include <QImage>
namespace libmueb {
// One frame to be transmitted/received. Just an alias for user convenience.
using Frame = QImage;
} // namespace libmueb
#if defined(LIBMUEB_LIBRARY)
#define LIBMUEB_EXPORT Q_DECL_EXPORT
#else
#define LIBMUEB_EXPORT Q_DECL_IMPORT
#endif
#endif // LIBMUEB_LIBMUEB_GLOBAL_H_
#ifndef LIBMUEB_MUEBRECEIVER_H_
#define LIBMUEB_MUEBRECEIVER_H_
#include <QObject>
#include "libmueb_global.h"
class MuebReceiverPrivate;
class LIBMUEB_EXPORT MuebReceiver final : public QObject {
Q_OBJECT
Q_DECLARE_PRIVATE_D(d_ptr_, MuebReceiver)
Q_DISABLE_COPY(MuebReceiver)
public:
static MuebReceiver& Instance();
libmueb::Frame frame();
signals:
void FrameChanged(libmueb::Frame frame);
private:
MuebReceiverPrivate* d_ptr_;
explicit MuebReceiver(QObject* parent = nullptr);
~MuebReceiver();
void readPendingDatagrams();
};
#endif // LIBMUEB_MUEBRECEIVER_H_
#ifndef LIBMUEB_MUEBTRANSMITTER_H_
#define LIBMUEB_MUEBTRANSMITTER_H_
#include <QObject>
#include <cstdint>
#include "libmueb_global.h"
class MuebTransmitterPrivate;
class LIBMUEB_EXPORT MuebTransmitter final : public QObject {
Q_OBJECT
Q_DECLARE_PRIVATE_D(d_ptr_, MuebTransmitter)
Q_DISABLE_COPY(MuebTransmitter)
public:
void SendFrame(libmueb::Frame frame);
static MuebTransmitter& Instance();
std::int32_t width() const;
std::int32_t height() const;
libmueb::Frame frame() const;
private:
MuebTransmitterPrivate* d_ptr_;
explicit MuebTransmitter(QObject* parent = nullptr);
~MuebTransmitter();
};
#endif // LIBMUEB_MUEBTRANSMITTER_H_
add_library(
muebtransmitter SHARED
${CMAKE_SOURCE_DIR}/include/libmueb/libmueb_global.h
${CMAKE_SOURCE_DIR}/include/libmueb/muebtransmitter.h configuration.h
muebtransmitter.cc configuration.cc)
target_include_directories(muebtransmitter PUBLIC ../include/${PROJECT_NAME})
target_link_libraries(
muebtransmitter
PUBLIC Qt6::Core Qt6::Gui
PRIVATE Qt6::Network Qt6::Concurrent)
target_compile_definitions(muebtransmitter PRIVATE LIBMUEB_LIBRARY)
add_library(
muebreceiver SHARED
${CMAKE_SOURCE_DIR}/include/libmueb/libmueb_global.h
${CMAKE_SOURCE_DIR}/include/libmueb/muebreceiver.h configuration.h
muebreceiver.cc configuration.cc)
target_include_directories(muebreceiver PUBLIC ../include/${PROJECT_NAME})
target_link_libraries(
muebreceiver
PUBLIC Qt6::Core Qt6::Gui
PRIVATE Qt6::Network)
target_compile_definitions(muebreceiver PRIVATE LIBMUEB_LIBRARY)
#include "configuration.h"
#include <cmath>
Configuration::Configuration(QObject *parent)
: QObject(parent), settings_("matrix-group", "libmueb") {
LoadSettings();
}
QImage Configuration::frame() const { return frame_; }
QHostAddress Configuration::target_address() const { return target_address_; }
std::uint32_t Configuration::floors() const { return floors_; }
std::uint32_t Configuration::rooms_per_floor() const {
return rooms_per_floor_;
}
std::uint32_t Configuration::windows_per_room() const {
return windows_per_room_;
}
std::uint32_t Configuration::vertical_pixel_unit() const {
return vertical_pixel_unit_;
}
std::uint32_t Configuration::horizontal_pixel_unit() const {
return horizontal_pixel_unit_;
}
std::uint32_t Configuration::pixels_per_window() const {
return pixels_per_window_;
}
std::uint32_t Configuration::window_per_floor() const {
return window_per_floor_;
}
std::uint32_t Configuration::windows() const { return windows_; }
std::uint32_t Configuration::pixels() const { return pixels_; }
std::int32_t Configuration::width() const { return width_; }
std::int32_t Configuration::height() const { return height_; }
std::uint8_t Configuration::protocol_type() const { return protocol_type_; }
std::uint32_t Configuration::window_byte_size() const {
return window_byte_size_;
}
std::uint32_t Configuration::max_windows_per_datagram() const {
return max_windows_per_datagram_;
}
std::uint32_t Configuration::packet_header_size() const {
return packet_header_size_;
}
std::uint32_t Configuration::packet_size() const { return packet_size_; }
std::uint32_t Configuration::packet_payload_size() const {
return packet_payload_size_;
}
std::uint32_t Configuration::max_pixel_per_datagram() const {
return max_pixel_per_datagram_;
}
std::uint32_t Configuration::remainder_packet_size() const {
return remainder_packet_size_;
}
std::uint16_t Configuration::unicast_animation_port() const {
return unicast_animation_port_;
}
std::uint16_t Configuration::broadcast_animation_port() const {
return broadcast_animation_port_;
}
std::uint8_t Configuration::max_packet_number() const {
return max_packet_number_;
}
std::uint8_t Configuration::color_depth() const { return color_depth_; }
std::uint8_t Configuration::factor() const { return factor_; }
bool Configuration::debug_mode() const { return debug_mode_; }
void Configuration::LoadSettings() {
// Building specific constants
floors_ = settings_.value("floors", 13).toUInt();
rooms_per_floor_ = settings_.value("rooms_per_floor", 8).toUInt();
windows_per_room_ = settings_.value("windows_per_room", 2).toUInt();
// Hardware specific constants
vertical_pixel_unit_ = settings_.value("vertical_pixel_unit", 2).toUInt();
horizontal_pixel_unit_ = settings_.value("horizontal_pixel_unit", 2).toUInt();
color_depth_ = settings_.value("color_depth", 3).toUInt();
// Software specific constants
pixels_per_window_ = vertical_pixel_unit_ * horizontal_pixel_unit_;
window_per_floor_ = rooms_per_floor_ * windows_per_room_;
windows_ = floors_ * window_per_floor_;
pixels_ = windows_ * pixels_per_window_;
width_ = window_per_floor_ * horizontal_pixel_unit_;
height_ = floors_ * vertical_pixel_unit_;
factor_ = 8 - color_depth_;
frame_ = QImage(width_, height_, QImage::Format_RGB888);
frame_.fill(Qt::black);
// Network protocol specific constants
protocol_type_ = 2;
unicast_animation_port_ =
settings_.value("unicast_animation_port", 3000).toUInt();
broadcast_animation_port_ =
settings_.value("broadcast_animation_port", 10000).toUInt();
// Debug specific constants
// Send packets to localhost
debug_mode_ = settings_.value("debugMode", false).toBool();
target_address_ =
(debug_mode_)
? QHostAddress("127.0.0.1")
: QHostAddress(
settings_.value("target_address", "10.6.255.255").toString());
window_byte_size_ = (color_depth_ >= 3 && color_depth_ < 5)
? pixels_per_window_ * 3 / 2
: pixels_per_window_ * 3;
max_windows_per_datagram_ =
settings_.value("max_windows_per_datagram", windows_).toUInt();
packet_header_size_ = 2;
packet_size_ =
packet_header_size_ + max_windows_per_datagram_ * window_byte_size_;
packet_payload_size_ = max_windows_per_datagram_ * window_byte_size_;
max_pixel_per_datagram_ = max_windows_per_datagram_ * pixels_per_window_;
max_packet_number_ = static_cast<std::uint32_t>(
std::ceil(static_cast<float>(windows_) / max_windows_per_datagram_));
// TODO Configuration check
}
#ifndef LIBMUEB_CONFIGURATION_H_
#define LIBMUEB_CONFIGURATION_H_
#include <QHostAddress>
#include <QImage>
#include <QObject>
#include <QSettings>
#include <cstdint>
// TODO check, remove unused parameters
// TODO check, variable types
class Configuration : public QObject {
Q_OBJECT
public:
explicit Configuration(QObject *parent = nullptr);
QImage frame() const;
QHostAddress target_address() const;
std::uint32_t floors() const;
std::uint32_t rooms_per_floor() const;
std::uint32_t windows_per_room() const;
std::uint32_t vertical_pixel_unit() const;
std::uint32_t horizontal_pixel_unit() const;
std::uint32_t pixels_per_window() const;
std::uint32_t window_per_floor() const;
std::uint32_t windows() const;
std::uint32_t pixels() const;
std::int32_t width() const;
std::int32_t height() const;
std::uint8_t protocol_type() const;
std::uint32_t window_byte_size() const;
std::uint32_t max_windows_per_datagram() const;
std::uint32_t packet_header_size() const;
std::uint32_t packet_size() const;
std::uint32_t packet_payload_size() const;
std::uint32_t max_pixel_per_datagram() const;
std::uint32_t remainder_packet_size() const;
std::uint16_t unicast_animation_port() const;
std::uint16_t broadcast_animation_port() const;
std::uint8_t max_packet_number() const;
std::uint8_t color_depth() const;
std::uint8_t factor() const;
bool debug_mode() const;
private:
QImage frame_;
QSettings settings_;
QHostAddress target_address_;
std::uint32_t floors_;
std::uint32_t rooms_per_floor_;
std::uint32_t windows_per_room_;
std::uint32_t vertical_pixel_unit_;
std::uint32_t horizontal_pixel_unit_;
std::uint32_t pixels_per_window_;
std::uint32_t window_per_floor_;
std::uint32_t windows_;
std::uint32_t pixels_;
// Qt width, height is signed
std::int32_t width_;
std::int32_t height_;
//
std::uint8_t protocol_type_;
std::uint32_t window_byte_size_;
std::uint32_t max_windows_per_datagram_;
std::uint32_t packet_header_size_;
std::uint32_t packet_size_;
std::uint32_t packet_payload_size_;
std::uint32_t max_pixel_per_datagram_;
std::uint32_t remainder_packet_size_;
std::uint16_t unicast_animation_port_;
std::uint16_t broadcast_animation_port_;
std::uint8_t max_packet_number_;
std::uint8_t color_depth_;
std::uint8_t factor_;
bool debug_mode_;
void LoadSettings();
};
#endif // LIBMUEB_CONFIGURATION_H_
#include "muebreceiver.h"
#include <QNetworkDatagram>
#include <QUdpSocket>
#include "configuration.h"
class MuebReceiverPrivate {
Q_DECLARE_PUBLIC(MuebReceiver)
Q_DISABLE_COPY(MuebReceiverPrivate)
public:
explicit MuebReceiverPrivate(MuebReceiver *q)
: frame(configuration.frame()), q_ptr(q) {
socket.bind(configuration.broadcast_animation_port());
QObject::connect(&socket, &QUdpSocket::readyRead, q,
&MuebReceiver::readPendingDatagrams);
qInfo() << "[MuebReceiver] UDP Socket will receive packets on port"
<< configuration.broadcast_animation_port();
}
Configuration configuration;
libmueb::Frame frame;
QUdpSocket socket;
MuebReceiver *q_ptr;
};
MuebReceiver::MuebReceiver(QObject *parent)
: QObject(parent), d_ptr_(new MuebReceiverPrivate(this)) {}
MuebReceiver::~MuebReceiver() { delete d_ptr_; }
MuebReceiver &MuebReceiver::Instance() {
static MuebReceiver instance;
return instance;
}
libmueb::Frame MuebReceiver::frame() { return d_ptr_->frame; }
inline void datagram_uncompress_error() {
qWarning() << "[MuebReceiver] Processed packet is invalid! Check the header "
"or packet contents(size)";
}
void MuebReceiver::readPendingDatagrams() {
while (d_ptr_->socket.hasPendingDatagrams()) {
if (d_ptr_->socket.pendingDatagramSize() ==
d_ptr_->configuration.packet_size()) {
QNetworkDatagram datagram = d_ptr_->socket.receiveDatagram();
QByteArray data = datagram.data();
// Process datagram
// Packet header check
// Check protocol
if (data[0] != d_ptr_->configuration.protocol_type()) {
datagram_uncompress_error();
return;
}
auto packet_number = data[1];
if (packet_number >= d_ptr_->configuration.max_packet_number() ||
packet_number < 0) {
datagram_uncompress_error();
return;
}
data.remove(0, d_ptr_->configuration.packet_header_size());
auto frame_begin =
d_ptr_->frame.bits() +
packet_number * d_ptr_->configuration.max_pixel_per_datagram();
// Uncompress 1 byte into 2 color components
if (d_ptr_->configuration.color_depth() < 5) {
for (auto i : data) {
*frame_begin = i & 0xf0;
frame_begin++;
*frame_begin = (i & 0x0f) << d_ptr_->configuration.factor();
frame_begin++;
}
// No compression
} else {
// FIXME use better copy method
for (auto i : data) {
*frame_begin = i;
frame_begin++;
}
}
emit(FrameChanged(d_ptr_->frame));
}
// Drop invalid packet
else {
qWarning() << "[MuebReceiver] Packet has invalid size!"
<< d_ptr_->socket.pendingDatagramSize() << "bytes";
d_ptr_->socket.receiveDatagram(0);
}
}
}
#include "muebtransmitter.h"
#include <QByteArray>
#include <QDebug>
#include <QNetworkDatagram>
#include <QUdpSocket>
#include <QtConcurrent>
#include "configuration.h"
class MuebTransmitterPrivate {
Q_DECLARE_PUBLIC(MuebTransmitter)
Q_DISABLE_COPY(MuebTransmitterPrivate)
public:
explicit MuebTransmitterPrivate(MuebTransmitter* q)
: datagram_(QByteArray(), configuration_.target_address(),
configuration_.broadcast_animation_port()),
q_ptr(q) {
qInfo().noquote() << "[MuebTransmitter] UDP Socket will send frame to"
<< QString("%1:%2")
.arg(configuration_.target_address().toString())
.arg(configuration_.broadcast_animation_port());
}
void SendFrame(libmueb::Frame frame) {
std::uint8_t packet_number{0};
QByteArray reduced_compressed_frame;
// Frame color reduction and compression
if (configuration_.color_depth() < 5) {
reduced_compressed_frame =
QtConcurrent::blockingMappedReduced<QByteArray>(
frame.constBits(), frame.constBits() + frame.sizeInBytes(),
/* Reference:
* http://threadlocalmutex.com/?p=48
* http://threadlocalmutex.com/?page_id=60
*/
[this](const uchar& color) -> uchar {
if (configuration_.color_depth() == 3) {
return (color * 225 + 4096) >> 13;
} else if (configuration_.color_depth() == 4) {
return (color * 15 + 135) >> 8;
}
return color;
},
[this](QByteArray& compressed_colors, const uchar& color) {
static bool msb{true};
// Compress 2 color components into 1 byte
if (msb) {
compressed_colors.append(color << configuration_.factor());
} else {
compressed_colors.back() = compressed_colors.back() | color;
}
msb = !msb;
},
QtConcurrent::OrderedReduce | QtConcurrent::SequentialReduce);
}
// No compression
else {
reduced_compressed_frame.setRawData(
reinterpret_cast<const char*>(frame.bits()), frame.sizeInBytes());
}
for (std::uint8_t i = 0; i < configuration_.max_packet_number(); ++i) {
if (configuration_.max_packet_number() == 1) {
reduced_compressed_frame.insert(0, configuration_.protocol_type())
.insert(1, packet_number);
datagram_.setData(reduced_compressed_frame);
} else {
QByteArray data;
data.append(configuration_.protocol_type())
.append(packet_number++)
.append(reduced_compressed_frame.sliced(
i * configuration_.packet_payload_size(),
configuration_.packet_payload_size()));
datagram_.setData(data);
}
socket_.writeDatagram(datagram_);
}
}
Configuration configuration_;
QUdpSocket socket_;
QNetworkDatagram datagram_;
MuebTransmitter* q_ptr;
};
MuebTransmitter::MuebTransmitter(QObject* parent)
: QObject(parent), d_ptr_(new MuebTransmitterPrivate(this)) {}
MuebTransmitter::~MuebTransmitter() { delete d_ptr_; }
void MuebTransmitter::SendFrame(libmueb::Frame frame) {
if (frame.isNull() || frame.format() == QImage::Format_Invalid ||
frame.width() != d_ptr_->configuration_.width() ||
frame.height() != d_ptr_->configuration_.height()) {
qWarning() << "[MuebTransmitter] Frame is invalid";
return;
}
frame.convertTo(QImage::Format_RGB888);
d_ptr_->SendFrame(frame);
}
MuebTransmitter& MuebTransmitter::Instance() {
static MuebTransmitter instance;
return instance;
}
int32_t MuebTransmitter::width() const {
return d_ptr_->configuration_.width();
}
int32_t MuebTransmitter::height() const {
return d_ptr_->configuration_.height();
}
libmueb::Frame MuebTransmitter::frame() const {
return d_ptr_->configuration_.frame();
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment