292 lines
9.5 KiB
C++
292 lines
9.5 KiB
C++
#include "hyperiongrabber.h"
|
|
#include <QCoreApplication>
|
|
#include <QScreen>
|
|
#include <QGuiApplication>
|
|
#include <QBuffer>
|
|
#include <QImage>
|
|
#include <QOpenGLContext>
|
|
#include <QOpenGLFramebufferObject>
|
|
#include <QOpenGLFunctions>
|
|
#include <QOpenGLTexture>
|
|
#include <QOpenGLShader>
|
|
#include <QOpenGLShaderProgram>
|
|
#include <QOpenGLBuffer>
|
|
#include <QOpenGLVertexArrayObject>
|
|
#include <QOffscreenSurface>
|
|
|
|
// public
|
|
|
|
HyperionGrabber::HyperionGrabber(QHash<QString, QString> opts)
|
|
{
|
|
QString addr = "localhost";
|
|
unsigned short port = 19444;
|
|
unsigned short scale = 8;
|
|
unsigned short priority = 100;
|
|
QString redAdjust = "", greenAdjust = "", blueAdjust = "";
|
|
QString temperature = "", threshold = "", transform = "";
|
|
|
|
_scale_m = scale;
|
|
|
|
QHashIterator<QString, QString> i(opts);
|
|
while (i.hasNext()) {
|
|
i.next();
|
|
if ((i.key() == "a" || i.key() == "address") && !(i.value().isNull() && i.value().isEmpty())) {
|
|
addr = i.value();
|
|
} else if ((i.key() == "p" || i.key() == "port") && i.value().toUShort()) {
|
|
port = i.value().toUShort();
|
|
} else if ((i.key() == "c" || i.key() == "priority") && (i.value().toUShort() && i.value().toUShort() <= 255)) {
|
|
priority = i.value().toUShort();
|
|
} else if ((i.key() == "s" || i.key() == "scale") && i.value().toUShort()) {
|
|
scale = i.value().toUShort();
|
|
} else if ((i.key() == "f" || i.key() == "frameskip") && i.value().toUShort()) {
|
|
_frameskip_m = i.value().toUShort();
|
|
} else if ((i.key() == "i" || i.key() == "inactive") && i.value().toInt()) {
|
|
_inactiveTime_m = (i.value().toInt() * 1000);
|
|
} else if (i.key() == "r" || i.key() == "redadjust") {
|
|
redAdjust = _parseColorArr(i.value(), 1);
|
|
} else if (i.key() == "g" || i.key() == "greenadjust") {
|
|
greenAdjust = _parseColorArr(i.value(), 1);
|
|
} else if (i.key() == "b" || i.key() == "blueadjust") {
|
|
blueAdjust = _parseColorArr(i.value(), 1);
|
|
} else if (i.key() == "t" || i.key() == "temperature") {
|
|
temperature = _parseColorArr(i.value(), 1);
|
|
} else if (i.key() == "d" || i.key() == "threshold") {
|
|
threshold = _parseColorArr(i.value(), 0);
|
|
} else if ((i.key() == "l" || i.key() == "transform") && _parseColorArr(i.value(), 0) != "") {
|
|
transform = i.value();
|
|
}
|
|
}
|
|
|
|
_hyperionPriority_m = QString::number(priority);
|
|
_hclient_p = new HyperionClient(addr, port, _hyperionPriority_m);
|
|
_hclient_p->ledAdjustments(redAdjust, greenAdjust, blueAdjust, temperature, threshold, transform);
|
|
|
|
// Initialize OpenGL for hardware acceleration
|
|
QSurfaceFormat format;
|
|
format.setRenderableType(QSurfaceFormat::OpenGL);
|
|
format.setProfile(QSurfaceFormat::CoreProfile);
|
|
format.setVersion(3, 3); // Or higher, depending on your OpenGL version
|
|
format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
|
|
format.setRedBufferSize(8);
|
|
format.setGreenBufferSize(8);
|
|
format.setBlueBufferSize(8);
|
|
format.setAlphaBufferSize(8);
|
|
format.setDepthBufferSize(24);
|
|
format.setStencilBufferSize(8);
|
|
|
|
_offscreenSurface = new QOffscreenSurface();
|
|
_offscreenSurface->setFormat(format);
|
|
_offscreenSurface->create();
|
|
|
|
_context = new QOpenGLContext(this);
|
|
_context->setFormat(format);
|
|
_context->create();
|
|
_context->makeCurrent(_offscreenSurface);
|
|
|
|
_functions = _context->functions();
|
|
_functions->initializeOpenGLFunctions();
|
|
|
|
// Shaders for scaling
|
|
const char *vertexShaderSource = R"(
|
|
attribute highp vec4 vertices;
|
|
attribute highp vec2 texCoords;
|
|
varying highp vec2 v_texCoords;
|
|
void main() {
|
|
gl_Position = vertices;
|
|
v_texCoords = texCoords;
|
|
}
|
|
)";
|
|
|
|
const char *fragmentShaderSource = R"(
|
|
uniform sampler2D textureSampler;
|
|
varying highp vec2 v_texCoords;
|
|
void main() {
|
|
gl_FragColor = texture2D(textureSampler, v_texCoords);
|
|
}
|
|
)";
|
|
|
|
_shaderProgram = new QOpenGLShaderProgram(this);
|
|
_shaderProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
|
|
_shaderProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
|
|
_shaderProgram->link();
|
|
|
|
_fbo = new QOpenGLFramebufferObject(1, 1, QOpenGLFramebufferObject::CombinedDepthStencil); // Placeholder size, will be resized later
|
|
_texture = new QOpenGLTexture(QOpenGLTexture::Target2D); // Placeholder, will be created from QVideoFrame
|
|
|
|
_waylandGrabber_p = new WaylandGrabber(this);
|
|
connect(_waylandGrabber_p, &WaylandGrabber::frameReady, this, &HyperionGrabber::_processFrame);
|
|
_waylandGrabber_p->start();
|
|
|
|
|
|
QScreen *screen = QGuiApplication::primaryScreen();
|
|
if (screen) {
|
|
_setImgSize(screen->size().width() / scale, screen->size().height() / scale);
|
|
} else {
|
|
qWarning() << "Could not get primary screen size for Wayland grabber.";
|
|
_setImgSize(1920 / scale, 1080 / scale); // Default to 1080p if screen not found
|
|
}
|
|
|
|
if (_inactiveTime_m) {
|
|
_timer_p = new QTimer(this);
|
|
connect(_timer_p, &QTimer::timeout, this, &HyperionGrabber::_inActivity);
|
|
_timer_p->start(_inactiveTime_m);
|
|
}
|
|
}
|
|
|
|
HyperionGrabber::~HyperionGrabber()
|
|
{
|
|
if (_timer_p) {
|
|
_timer_p->stop();
|
|
delete _timer_p;
|
|
}
|
|
delete _hclient_p;
|
|
|
|
// OpenGL cleanup
|
|
if (_context) {
|
|
_context->makeCurrent(_offscreenSurface);
|
|
delete _fbo;
|
|
delete _shaderProgram;
|
|
delete _texture;
|
|
_context->doneCurrent();
|
|
delete _context;
|
|
}
|
|
if (_offscreenSurface) {
|
|
delete _offscreenSurface;
|
|
}
|
|
}
|
|
|
|
// private
|
|
|
|
QString HyperionGrabber::_parseColorArr(QString value, bool isInt)
|
|
{
|
|
QStringList values = value.split(',');
|
|
if (values.size() != 3) {
|
|
return "";
|
|
}
|
|
value = "[";
|
|
for (int i = 0; i < 3; i++) {
|
|
if (isInt && (values.at(i).toInt() < 0 || values.at(i).toInt() > 255)) {
|
|
return "";
|
|
}
|
|
if (!isInt && (values.at(i).toDouble() < 0.0 || values.at(i).toDouble() > 1.0)) {
|
|
return "";
|
|
}
|
|
value.append(values.at(i));
|
|
value.append(",");
|
|
}
|
|
value.chop(1);
|
|
value.append("]");
|
|
return value;
|
|
}
|
|
|
|
// private slots
|
|
|
|
void HyperionGrabber::_inActivity()
|
|
{
|
|
_hclient_p->clearLeds();
|
|
}
|
|
|
|
void HyperionGrabber::_processFrame(const QVideoFrame &frame)
|
|
{
|
|
if (!frame.isValid()) {
|
|
return;
|
|
}
|
|
|
|
_frameCounter_m++;
|
|
if (_frameskip_m > 0 && (_frameCounter_m % (_frameskip_m + 1) != 0)) {
|
|
return;
|
|
}
|
|
|
|
_context->makeCurrent(_offscreenSurface);
|
|
|
|
// Convert QVideoFrame to QImage
|
|
QImage inputImage = frame.toImage();
|
|
if (inputImage.isNull()) {
|
|
qWarning() << "Failed to convert QVideoFrame to QImage.";
|
|
_context->doneCurrent();
|
|
return;
|
|
}
|
|
// qDebug() << "Input QImage dimensions:" << inputImage.width() << "x" << inputImage.height();
|
|
// qDebug() << "Input QImage format:" << inputImage.format();
|
|
|
|
// Resize FBO if needed
|
|
QSize targetSize(inputImage.width() / _scale_m, inputImage.height() / _scale_m);
|
|
if (_fbo->size() != targetSize) {
|
|
delete _fbo;
|
|
_fbo = new QOpenGLFramebufferObject(targetSize, QOpenGLFramebufferObject::CombinedDepthStencil);
|
|
}
|
|
|
|
_fbo->bind();
|
|
_functions->glViewport(0, 0, _fbo->width(), _fbo->height());
|
|
|
|
// qDebug() << "FBO size:" << _fbo->size();
|
|
|
|
_functions->glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
_functions->glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
// Create texture from QImage
|
|
if (_texture->isCreated()) {
|
|
_texture->destroy();
|
|
}
|
|
_texture->create();
|
|
_texture->setData(inputImage.flipped(Qt::Vertical)); // Flip vertically
|
|
|
|
// qDebug() << "Texture size after setData:" << _texture->width() << "x" << _texture->height();
|
|
|
|
_shaderProgram->bind();
|
|
_shaderProgram->setUniformValue("textureSampler", 0); // Texture unit 0
|
|
|
|
// Vertices for a quad
|
|
GLfloat vertices[] = {
|
|
-1.0f, -1.0f,
|
|
1.0f, -1.0f,
|
|
-1.0f, 1.0f,
|
|
1.0f, 1.0f,
|
|
};
|
|
|
|
// Texture coordinates (flipped vertically for QImage top-left origin)
|
|
GLfloat texCoords[] = {
|
|
0.0f, 1.0f, // Top-left of texture (maps to bottom-left of quad)
|
|
1.0f, 1.0f, // Top-right of texture (maps to bottom-right of quad)
|
|
0.0f, 0.0f, // Bottom-left of texture (maps to top-left of quad)
|
|
1.0f, 0.0f, // Bottom-right of texture (maps to top-right of quad)
|
|
};
|
|
|
|
_shaderProgram->setAttributeArray("vertices", vertices, 2);
|
|
_shaderProgram->enableAttributeArray("vertices");
|
|
_shaderProgram->setAttributeArray("texCoords", texCoords, 2);
|
|
_shaderProgram->enableAttributeArray("texCoords");
|
|
|
|
_functions->glActiveTexture(GL_TEXTURE0);
|
|
_texture->bind();
|
|
|
|
_functions->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
|
|
|
_shaderProgram->disableAttributeArray("vertices");
|
|
_shaderProgram->disableAttributeArray("texCoords");
|
|
_shaderProgram->release();
|
|
|
|
_fbo->release();
|
|
|
|
// Read pixels back from FBO
|
|
QImage scaledImage = _fbo->toImage();
|
|
scaledImage = scaledImage.flipped(Qt::Vertical); // Flip vertically again if needed
|
|
|
|
QByteArray ba;
|
|
QBuffer buffer(&ba);
|
|
buffer.open(QIODevice::WriteOnly);
|
|
scaledImage.save(&buffer, "PNG"); // Save as PNG for now, can be optimized later
|
|
int rawDataSize = scaledImage.sizeInBytes();
|
|
_hclient_p->setImgSize(scaledImage.width(), scaledImage.height());
|
|
_hclient_p->sendImage(scaledImage.constBits(), rawDataSize);
|
|
|
|
_context->doneCurrent();
|
|
}
|
|
|
|
void HyperionGrabber::_setImgSize(int width, int height)
|
|
{
|
|
_hclient_p->setImgSize(width, height);
|
|
}
|
|
|
|
|