WIP: Add V4L2

This commit is contained in:
uncor3
2025-09-24 15:23:42 +00:00
parent 249f39449e
commit 7191581c1b
2 changed files with 169 additions and 1 deletions
+156 -1
View File
@@ -1,11 +1,22 @@
#include "airplaywindow.h"
#include <QApplication>
#include <QCheckBox>
#include <QDebug>
#include <QHBoxLayout>
#include <QLabel>
#include <QMessageBox>
#include <QPixmap>
#include <QPushButton>
#include <QVBoxLayout>
// V4L2 includes
#include <cstring>
#include <errno.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <unistd.h>
// Include the rpiplay server functions
extern "C" {
int start_server_qt(const char *name);
@@ -17,12 +28,18 @@ std::function<void(uint8_t *, int, int)> qt_video_callback;
AirPlayWindow::AirPlayWindow(QWidget *parent)
: QMainWindow(parent), m_videoLabel(nullptr), m_statusLabel(nullptr),
m_serverThread(nullptr), m_serverRunning(false)
m_serverThread(nullptr), m_serverRunning(false), m_v4l2_fd(-1),
m_v4l2_width(0), m_v4l2_height(0), m_v4l2_enabled(false)
{
setupUI();
// Setup video callback
qt_video_callback = [this](uint8_t *data, int width, int height) {
// V4L2 output if enabled
if (m_v4l2_enabled) {
writeFrameToV4L2(data, width, height);
}
QByteArray frameData((const char *)data, width * height * 3);
QMetaObject::invokeMethod(this, "updateVideoFrame",
Qt::QueuedConnection,
@@ -34,6 +51,7 @@ AirPlayWindow::AirPlayWindow(QWidget *parent)
AirPlayWindow::~AirPlayWindow()
{
stopAirPlayServer();
closeV4L2();
qt_video_callback = nullptr;
}
@@ -63,6 +81,24 @@ void AirPlayWindow::setupUI()
statusLayout->addWidget(m_statusLabel);
statusLayout->addStretch();
// V4L2 controls
QCheckBox *v4l2CheckBox = new QCheckBox("Enable V4L2 Output");
connect(v4l2CheckBox, &QCheckBox::toggled, this, [this](bool enabled) {
m_v4l2_enabled = enabled;
if (!enabled) {
closeV4L2();
}
qDebug() << "V4L2 output" << (enabled ? "enabled" : "disabled");
});
QPushButton *testV4L2Btn = new QPushButton("Test V4L2");
testV4L2Btn->setToolTip("Test V4L2 loopback device availability");
connect(testV4L2Btn, &QPushButton::clicked, this,
[this]() { testV4L2Device(); });
statusLayout->addWidget(v4l2CheckBox);
statusLayout->addWidget(testV4L2Btn);
statusLayout->addWidget(startBtn);
statusLayout->addWidget(stopBtn);
@@ -153,3 +189,122 @@ void AirPlayServerThread::run()
stop_server_qt();
emit statusChanged(false);
}
// V4L2 Implementation
void AirPlayWindow::initV4L2(int width, int height, const char *device)
{
closeV4L2(); // Close previous device if any
m_v4l2_fd = open(device, O_WRONLY);
if (m_v4l2_fd < 0) {
qWarning("Failed to open V4L2 device %s: %s", device, strerror(errno));
QMessageBox::warning(
this, "V4L2 Error",
QString("Failed to open V4L2 device %1.\n"
"Make sure v4l2loopback module is loaded:\n"
"sudo modprobe v4l2loopback")
.arg(device));
return;
}
struct v4l2_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
fmt.fmt.pix.width = width;
fmt.fmt.pix.height = height;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24;
fmt.fmt.pix.field = V4L2_FIELD_NONE;
fmt.fmt.pix.bytesperline = width * 3;
fmt.fmt.pix.sizeimage = (unsigned int)width * height * 3;
if (ioctl(m_v4l2_fd, VIDIOC_S_FMT, &fmt) < 0) {
qWarning("Failed to set V4L2 format: %s", strerror(errno));
::close(m_v4l2_fd);
m_v4l2_fd = -1;
QMessageBox::warning(
this, "V4L2 Error",
"Failed to set V4L2 video format. Device may not support RGB24.");
return;
}
m_v4l2_width = width;
m_v4l2_height = height;
qDebug("V4L2 device %s initialized to %dx%d", device, width, height);
}
void AirPlayWindow::closeV4L2()
{
if (m_v4l2_fd >= 0) {
::close(m_v4l2_fd);
m_v4l2_fd = -1;
qDebug("V4L2 device closed.");
}
}
void AirPlayWindow::writeFrameToV4L2(uint8_t *data, int width, int height)
{
// Check if V4L2 device needs to be initialized or re-initialized
if (m_v4l2_fd < 0 || m_v4l2_width != width || m_v4l2_height != height) {
initV4L2(width, height, "/dev/video0"); // Use your v4l2loopback device
}
// Write frame to V4L2 device if it's open
if (m_v4l2_fd >= 0) {
ssize_t bytes_written =
write(m_v4l2_fd, data, (size_t)width * height * 3);
if (bytes_written < 0) {
qWarning("Failed to write frame to V4L2 device: %s",
strerror(errno));
closeV4L2(); // Close on error to retry initialization
}
}
}
void AirPlayWindow::testV4L2Device()
{
const char *device = "/dev/video0";
int fd = open(device, O_WRONLY);
if (fd < 0) {
QMessageBox::critical(this, "V4L2 Test",
QString("Failed to open V4L2 device %1.\n"
"Error: %2\n\n"
"To fix this, run:\n"
"sudo modprobe v4l2loopback video_nr=0")
.arg(device)
.arg(strerror(errno)));
return;
}
// Test if device supports V4L2_PIX_FMT_RGB24
struct v4l2_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
fmt.fmt.pix.width = 1280;
fmt.fmt.pix.height = 720;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24;
fmt.fmt.pix.field = V4L2_FIELD_NONE;
fmt.fmt.pix.bytesperline = 1280 * 3;
fmt.fmt.pix.sizeimage = 1280 * 720 * 3;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
::close(fd);
QMessageBox::warning(
this, "V4L2 Test",
QString("V4L2 device %1 exists but doesn't support RGB24 format.\n"
"Error: %2")
.arg(device)
.arg(strerror(errno)));
return;
}
::close(fd);
QMessageBox::information(
this, "V4L2 Test",
QString("✓ V4L2 device %1 is working correctly!\n\n"
"You can now:\n"
"• View output with: ffplay %1\n"
"• Use in OBS as Video Capture Device\n"
"• Record with: ffmpeg -f v4l2 -i %1 output.mp4")
.arg(device));
}