summaryrefslogtreecommitdiffhomepage
path: root/video-ps3eye
diff options
context:
space:
mode:
Diffstat (limited to 'video-ps3eye')
-rw-r--r--video-ps3eye/CMakeLists.txt69
-rw-r--r--video-ps3eye/dialog.ui139
-rw-r--r--video-ps3eye/lang/nl_NL.ts38
-rw-r--r--video-ps3eye/lang/ru_RU.ts38
-rw-r--r--video-ps3eye/lang/stub.ts38
-rw-r--r--video-ps3eye/lang/zh_CN.ts38
-rw-r--r--video-ps3eye/module.cpp307
-rw-r--r--video-ps3eye/module.hpp82
m---------video-ps3eye/ps3eye-driver0
-rw-r--r--video-ps3eye/shm-layout.hpp39
-rw-r--r--video-ps3eye/shm.cxx2
-rw-r--r--video-ps3eye/shm.hpp4
-rw-r--r--video-ps3eye/wrapper.cxx120
13 files changed, 914 insertions, 0 deletions
diff --git a/video-ps3eye/CMakeLists.txt b/video-ps3eye/CMakeLists.txt
new file mode 100644
index 00000000..1f1780f9
--- /dev/null
+++ b/video-ps3eye/CMakeLists.txt
@@ -0,0 +1,69 @@
+add_compile_definitions(PS3EYE_DEBUG)
+
+if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/ps3eye-driver/CMakeLists.txt")
+ add_subdirectory("ps3eye-driver")
+
+ if(NOT MSVC)
+ if(PKG_CONFIG_FOUND)
+ pkg_check_modules(libusb "libusb-1.0" QUIET)
+ endif()
+ if(libusb_FOUND)
+ include_directories(SYSTEM ${libusb_INCLUDE_DIRS})
+ link_libraries(${libusb_LIBRARIES})
+ link_directories(${libusb_LIBRARY_DIRS})
+ endif()
+ else()
+ set(SDK_LIBUSB CACHE PATH "")
+ if(SDK_LIBUSB)
+ set(libusb_FOUND TRUE)
+ link_directories("${SDK_LIBUSB}")
+ include_directories(SYSTEM "${SDK_LIBUSB}")
+ endif()
+ endif()
+endif()
+
+if(TARGET ps3eye-sdl AND FALSE)
+ install(TARGETS "ps3eye-sdl" DESTINATION "${opentrack-libexec}")
+ if(WIN32)
+ foreach(k ${SDL2_LIBRARIES})
+ get_filename_component(path "${k}" PATH)
+ set(lib "${path}/SDL2.dll")
+ if(EXISTS "${lib}")
+ otr_install_lib("${lib}" "${opentrack-libexec}")
+ break()
+ endif()
+ endforeach()
+ endif()
+endif()
+
+if(TARGET ps3eye-mode-test)
+ install(TARGETS "ps3eye-mode-test" DESTINATION "${opentrack-libexec}")
+endif()
+
+if(TARGET ps3eye-driver)
+ add_executable(ps3eye-subprocess "wrapper.cxx" "shm.cxx")
+ target_link_libraries(ps3eye-subprocess ps3eye-driver)
+ if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
+ target_link_libraries(ps3eye-subprocess rt)
+ endif()
+ install(TARGETS "ps3eye-subprocess" DESTINATION "${opentrack-libexec}")
+endif()
+
+if(TARGET ps3eye-subprocess)
+ otr_module(video-ps3eye)
+ target_link_libraries(${self} ps3eye-driver opentrack-video)
+ if(WIN32)
+ set(path "${SDK_LIBUSB}/libusb-1.0.dll")
+ if(EXISTS "${path}")
+ otr_install_lib("${path}" "${opentrack-libexec}")
+ endif()
+ set(vcrun "${SDK_LIBUSB}/vcruntime140.dll")
+ if(EXISTS "${vcrun}")
+ otr_install_lib("${vcrun}" "${opentrack-libexec}")
+ endif()
+ endif()
+endif()
+
+if(TARGET ps3eye-frame-test)
+ install(TARGETS "ps3eye-frame-test" DESTINATION "${opentrack-libexec}")
+endif()
diff --git a/video-ps3eye/dialog.ui b/video-ps3eye/dialog.ui
new file mode 100644
index 00000000..68060eb9
--- /dev/null
+++ b/video-ps3eye/dialog.ui
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Dialog</class>
+ <widget class="QWidget" name="Dialog">
+ <property name="windowModality">
+ <enum>Qt::ApplicationModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>439</width>
+ <height>124</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>439</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>PS3 Eye</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Camera settings</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="2">
+ <widget class="QSpinBox" name="exposure_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="buttonSymbols">
+ <enum>QAbstractSpinBox::NoButtons</enum>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QSpinBox" name="gain_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="buttonSymbols">
+ <enum>QAbstractSpinBox::NoButtons</enum>
+ </property>
+ <property name="maximum">
+ <number>63</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSlider" name="exposure_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Exposure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSlider" name="gain_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <number>63</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Gain</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/video-ps3eye/lang/nl_NL.ts b/video-ps3eye/lang/nl_NL.ts
new file mode 100644
index 00000000..f3a16fd4
--- /dev/null
+++ b/video-ps3eye/lang/nl_NL.ts
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="nl_NL">
+<context>
+ <name>Dialog</name>
+ <message>
+ <source>PS3 Eye</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Exposure</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Gain</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialog</name>
+ <message>
+ <source>Can&apos;t open camera</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>PS3 Eye driver error: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unknown error</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/video-ps3eye/lang/ru_RU.ts b/video-ps3eye/lang/ru_RU.ts
new file mode 100644
index 00000000..1edcf50d
--- /dev/null
+++ b/video-ps3eye/lang/ru_RU.ts
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru_RU">
+<context>
+ <name>Dialog</name>
+ <message>
+ <source>PS3 Eye</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Exposure</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Gain</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialog</name>
+ <message>
+ <source>Can&apos;t open camera</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>PS3 Eye driver error: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unknown error</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/video-ps3eye/lang/stub.ts b/video-ps3eye/lang/stub.ts
new file mode 100644
index 00000000..81ffc826
--- /dev/null
+++ b/video-ps3eye/lang/stub.ts
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+ <name>Dialog</name>
+ <message>
+ <source>PS3 Eye</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Exposure</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Gain</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>dialog</name>
+ <message>
+ <source>Can&apos;t open camera</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>PS3 Eye driver error: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unknown error</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/video-ps3eye/lang/zh_CN.ts b/video-ps3eye/lang/zh_CN.ts
new file mode 100644
index 00000000..9650c966
--- /dev/null
+++ b/video-ps3eye/lang/zh_CN.ts
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>Dialog</name>
+ <message>
+ <source>PS3 Eye</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera settings</source>
+ <translation>相机设置</translation>
+ </message>
+ <message>
+ <source>Exposure</source>
+ <translation>曝光</translation>
+ </message>
+ <message>
+ <source>Gain</source>
+ <translation>增益</translation>
+ </message>
+</context>
+<context>
+ <name>dialog</name>
+ <message>
+ <source>Can&apos;t open camera</source>
+ <translation>无法打开相机</translation>
+ </message>
+ <message>
+ <source>PS3 Eye driver error: %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Unknown error</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/video-ps3eye/module.cpp b/video-ps3eye/module.cpp
new file mode 100644
index 00000000..02543082
--- /dev/null
+++ b/video-ps3eye/module.cpp
@@ -0,0 +1,307 @@
+#include "module.hpp"
+#include "compat/library-path.hpp"
+#include "compat/sleep.hpp"
+#include "compat/run-in-thread.hpp"
+
+#include <cstddef>
+#include <thread>
+
+#include <QCoreApplication>
+#include <QMessageBox>
+
+#include <libusb.h>
+
+using namespace options;
+
+#ifdef __GNUG__
+# pragma clang diagnostic ignored "-Wcast-qual"
+#endif
+
+int device_count()
+{
+ libusb_context * ctx = nullptr;
+ libusb_device **list = nullptr;
+ ssize_t sz = 0;
+ int rc = 0, cnt = 0;
+
+ constexpr int vendor_id = 0x1415;
+ constexpr int product_id = 0x2000;
+
+ rc = libusb_init(&ctx);
+
+ if (rc)
+ goto end;
+
+ sz = libusb_get_device_list(ctx, &list);
+
+ if (sz < 0)
+ goto end;
+
+ for (int i = 0; i < sz; ++i) {
+ libusb_device *device = list[i];
+ libusb_device_descriptor desc = {};
+
+ if (libusb_get_device_descriptor(device, &desc))
+ goto end;
+
+ if (desc.idVendor == vendor_id && desc.idProduct == product_id)
+ cnt++;
+ }
+
+end:
+ if (list)
+ libusb_free_device_list(list, 1);
+ if (ctx)
+ libusb_exit(ctx);
+
+ return cnt;
+}
+
+#ifdef __linux__
+# include <unistd.h>
+#endif
+
+bool check_device_exists()
+{
+#ifdef __linux__
+ // don't show when system driver exists
+ if (!access("/sys/module/gspca_ov534", R_OK|X_OK))
+ return false;
+#endif
+
+ static bool ret = device_count() > 0;
+ return ret;
+}
+
+static const QString camera_name = QStringLiteral("PS3 Eye open driver");
+
+std::vector<QString> ps3eye_camera_::camera_names() const
+{
+ if (check_device_exists())
+ return { camera_name };
+ else
+ return {};
+}
+std::unique_ptr<camera> ps3eye_camera_::make_camera(const QString& name)
+{
+ if (name == camera_name && check_device_exists())
+ return std::make_unique<ps3eye_camera>();
+ else
+ return {};
+}
+
+static bool show_dialog_()
+{
+ auto& dlg = *new dialog;
+ dlg.setWindowFlag(Qt::MSWindowsFixedSizeDialogHint);
+ dlg.setAttribute(Qt::WA_DeleteOnClose);
+ dlg.adjustSize(); dlg.setFixedSize(dlg.size());
+ dlg.show();
+ return true;
+}
+
+bool ps3eye_camera_::show_dialog(const QString&)
+{
+ return show_dialog_();
+}
+
+bool ps3eye_camera_::can_show_dialog(const QString& name)
+{
+ return name == camera_name && check_device_exists();
+}
+
+ps3eye_camera::ps3eye_camera()
+{
+ if (!shm.success())
+ return;
+
+ static const QString library_path(OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH);
+
+ wrapper.setWorkingDirectory(library_path);
+#ifdef _WIN32
+ wrapper.setProgram("\"ps3eye-subprocess.exe\"");
+ // workaround apparent Qt 5.15.2 bug -sh 20210817
+ wrapper.setProcessChannelMode(QProcess::ForwardedChannels);
+#else
+ wrapper.setProgram("ps3eye-subprocess");
+#endif
+}
+
+ps3eye_camera::~ps3eye_camera()
+{
+ stop();
+}
+
+void ps3eye_camera::stop()
+{
+ open = false;
+
+ if (wrapper.state() != QProcess::NotRunning)
+ {
+ volatile auto& ptr = *(ps3eye::shm*)shm.ptr();
+ ptr.in.do_exit = 1;
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+ wrapper.waitForFinished(1000);
+
+ if (wrapper.state() != QProcess::NotRunning)
+ wrapper.kill();
+ wrapper.waitForFinished(1000);
+ }
+}
+
+bool ps3eye_camera::start(info& args)
+{
+ if (!shm.success())
+ return false;
+
+ stop();
+
+ volatile auto& ptr = *(ps3eye::shm*)shm.ptr();
+ QString error;
+
+ using mode = ps3eye::shm_in::mode;
+
+ open = false;
+ fr = {};
+ fr.channels = args.num_channels == 1 ? 1 : 3;
+ fr.channel_size = 1;
+
+ if (!args.width || args.width > 320)
+ {
+ ptr.in.resolution = mode::vga;
+ fr.width = 640; fr.height = 480;
+ }
+ else
+ {
+ ptr.in.resolution = mode::qvga;
+ fr.width = 320; fr.height = 240;
+ }
+
+ ptr.in.auto_gain = false;
+ ptr.in.framerate = (uint8_t)std::clamp(args.fps, 30, 187);
+ ptr.in.gain = (uint8_t)s.gain;
+ ptr.in.exposure = (uint8_t)s.exposure;
+ ptr.in.channels = args.num_channels == 1 ? 1 : 3;
+
+ sleep_ms = std::clamp(int(std::floor(450./ptr.in.framerate)), 1, 10);
+
+ wrapper.start();
+
+ constexpr int sleep_ms = 10, max_sleeps = 2000/sleep_ms;
+
+ for (int i = 0; i < max_sleeps; i++)
+ {
+ if (ptr.out.timecode > 0)
+ goto ok;
+ portable::sleep(sleep_ms);
+ }
+
+ if (ptr.out.error_string[0] == '\0')
+ error = QString{};
+ else
+ error = QString::fromLatin1((const char*)ptr.out.error_string,
+ strnlen((const char*)ptr.out.error_string, sizeof(ptr.out.error_string)));
+
+ run_in_thread_async(qApp, [error = std::move(error)] {
+ dialog::show_open_failure_msgbox(error);
+ });
+
+ return false;
+
+ok:
+ open = true;
+ return true;
+}
+
+std::tuple<const frame&, bool> ps3eye_camera::get_frame()
+{
+ auto volatile* ptr = (ps3eye::shm*)shm.ptr();
+
+ if (shm.success() && open)
+ {
+ int elapsed = std::min((int)std::ceil(t.elapsed_ms()), 100);
+ portable::sleep(sleep_ms - elapsed);
+
+ if (unsigned tc = ptr->out.timecode; tc != timecode)
+ {
+ timecode = tc;
+ goto ok;
+ }
+ }
+
+ for (int i = 0; i < 2000; i++)
+ {
+ if (unsigned tc = ptr->out.timecode; tc != timecode)
+ {
+ timecode = tc;
+ goto ok;
+ }
+ portable::sleep(1);
+ }
+
+ stop();
+ return { fr, false };
+
+ static_assert(offsetof(decltype(ptr->out), data_640x480) == offsetof(decltype(ptr->out), data_320x240));
+
+ok:
+ t.start();
+ memcpy(data, (unsigned char*)ptr->out.data_640x480,sizeof(ptr->out.data_640x480));
+ fr.data = data;
+ return { fr, true };
+}
+
+bool ps3eye_camera::show_dialog()
+{
+ return show_dialog_();
+}
+
+OTR_REGISTER_CAMERA(ps3eye_camera_)
+
+dialog::dialog(QWidget* parent) : QWidget(parent)
+{
+ ui.setupUi(this);
+ t.setInterval(500); t.setSingleShot(true);
+ tie_setting(s.exposure, ui.exposure_slider);
+ tie_setting(s.gain, ui.gain_slider);
+ ui.exposure_label->setValue((int)*s.exposure);
+ ui.gain_label->setValue((int)*s.gain);
+ connect(&s.exposure, value_::value_changed<slider_value>(), this, [this](const slider_value&) { t.stop(); t.start(); });
+ connect(&s.gain, value_::value_changed<slider_value>(), this, [this](const slider_value&) { t.stop(); t.start(); });
+ connect(ui.exposure_slider, &QSlider::valueChanged, ui.exposure_label, &QSpinBox::setValue);
+ connect(ui.gain_slider, &QSlider::valueChanged, ui.gain_label, &QSpinBox::setValue);
+ connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &dialog::do_ok);
+ connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &dialog::do_cancel);
+ connect(&t, &QTimer::timeout, this, [this] { s.set_exposure(); s.set_gain(); });
+}
+void dialog::show_open_failure_msgbox(const QString& error)
+{
+ const QString& error_ = error.isNull() ? tr("Unknown error") : error;
+ QMessageBox::critical(nullptr,
+ tr("Can't open camera"),
+ tr("PS3 Eye driver error: %1").arg(error_),
+ QMessageBox::Close);
+}
+
+// XXX copypasta -sh 20200329
+void settings::set_gain()
+{
+ if (!shm.success())
+ return;
+
+ auto& ptr = *(ps3eye::shm volatile*)shm.ptr();
+ ptr.in.gain = (unsigned char)*gain;
+ ++ptr.in.settings_updated;
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+}
+
+void settings::set_exposure()
+{
+ if (!shm.success())
+ return;
+
+ auto& ptr = *(ps3eye::shm volatile*)shm.ptr();
+ ptr.in.exposure = (unsigned char)*exposure;
+ ++ptr.in.settings_updated;
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+}
diff --git a/video-ps3eye/module.hpp b/video-ps3eye/module.hpp
new file mode 100644
index 00000000..f6934d70
--- /dev/null
+++ b/video-ps3eye/module.hpp
@@ -0,0 +1,82 @@
+#pragma once
+
+#include "video/camera.hpp"
+#include "shm-layout.hpp"
+#include "compat/shm.h"
+#include "options/options.hpp"
+#include "compat/macros.h"
+#include "compat/timer.hpp"
+#include "ui_dialog.h"
+
+#include <QDialog>
+#include <QProcess>
+#include <QTimer>
+
+using namespace options;
+
+using video::impl::camera;
+using video::impl::camera_;
+using video::frame;
+
+struct settings final
+{
+ bundle b = make_bundle("video-ps3eye");
+ shm_wrapper shm { "ps3eye-driver-shm", nullptr, sizeof(ps3eye::shm) };
+
+ value<slider_value> exposure{b, "exposure", {255, 0, 255}};
+ value<slider_value> gain{b, "gain", {30, 0, 63}};
+
+ void set_exposure();
+ void set_gain();
+};
+
+class dialog final : public QWidget
+{
+ Q_OBJECT
+ Ui_Dialog ui;
+ settings s;
+ QTimer t{this};
+
+ shm_wrapper shm { "ps3eye-driver-shm", nullptr, sizeof(ps3eye::shm) };
+
+ void do_ok() { s.b->save(); close(); deleteLater(); }
+ void do_cancel() { s.b->reload(); close(); deleteLater(); }
+
+protected:
+ void closeEvent(QCloseEvent*) override { do_cancel(); if (t.isActive()) { s.set_exposure(); s.set_gain(); } }
+
+public:
+ explicit dialog(QWidget* parent = nullptr);
+ static void show_open_failure_msgbox(const QString& error);
+};
+
+struct ps3eye_camera final : video::impl::camera
+{
+ QProcess wrapper;
+ shm_wrapper shm { "ps3eye-driver-shm", nullptr, sizeof(ps3eye::shm) };
+ settings s;
+ frame fr;
+ Timer t;
+ unsigned char data[640 * 480 * ps3eye::num_channels] = {};
+ int framerate = 30, sleep_ms = 1;
+ bool open = false;
+ unsigned timecode = 0;
+
+ ps3eye_camera();
+ ~ps3eye_camera() override;
+
+ bool start(info& args) override;
+ void stop() override;
+ bool is_open() override { return open; }
+
+ std::tuple<const frame&, bool> get_frame() override;
+ [[nodiscard]] bool show_dialog() override;
+};
+
+struct ps3eye_camera_ final : video::impl::camera_
+{
+ std::vector<QString> camera_names() const override;
+ std::unique_ptr<camera> make_camera(const QString& name) override;
+ bool show_dialog(const QString& camera_name) override;
+ bool can_show_dialog(const QString& camera_name) override;
+};
diff --git a/video-ps3eye/ps3eye-driver b/video-ps3eye/ps3eye-driver
new file mode 160000
+Subproject fe4eef71669bc6365a651b3c734ebd2bb5f57f8
diff --git a/video-ps3eye/shm-layout.hpp b/video-ps3eye/shm-layout.hpp
new file mode 100644
index 00000000..65b0a4f1
--- /dev/null
+++ b/video-ps3eye/shm-layout.hpp
@@ -0,0 +1,39 @@
+#pragma once
+#include <cstdint>
+
+namespace ps3eye {
+
+static constexpr unsigned num_channels = 3;
+
+struct shm_in {
+ enum class mode : uint8_t { qvga, vga, };
+
+ uint32_t settings_updated;
+ uint8_t framerate, channels;
+ mode resolution;
+ //uint8_t sharpness, contrast, brightness hue, saturation;
+ uint8_t gain, exposure, auto_gain, test_pattern;
+ uint8_t do_exit;
+};
+
+struct shm_out
+{
+ enum class status : uint8_t { starting, running, fail, terminate, };
+
+ uint32_t timecode;
+ uint32_t settings_updated_ack;
+ status status_;
+ char error_string[256];
+ union {
+ uint8_t data_320x240[320][240][num_channels];
+ uint8_t data_640x480[640][480][num_channels];
+ };
+};
+
+struct alignas(64) shm {
+ shm_out out;
+ [[maybe_unused]] const char _padding[128 - sizeof(shm_out) % 128]; // NOLINT
+ shm_in in;
+};
+
+} // ns ps3eye
diff --git a/video-ps3eye/shm.cxx b/video-ps3eye/shm.cxx
new file mode 100644
index 00000000..57e35c3a
--- /dev/null
+++ b/video-ps3eye/shm.cxx
@@ -0,0 +1,2 @@
+#include "shm.hpp"
+#include "../compat/shm.cpp"
diff --git a/video-ps3eye/shm.hpp b/video-ps3eye/shm.hpp
new file mode 100644
index 00000000..2bb8cb89
--- /dev/null
+++ b/video-ps3eye/shm.hpp
@@ -0,0 +1,4 @@
+#pragma once
+#define OTR_GENERIC_EXPORT
+#define OTR_GENERIC_IMPORT
+#include "../compat/shm.h"
diff --git a/video-ps3eye/wrapper.cxx b/video-ps3eye/wrapper.cxx
new file mode 100644
index 00000000..b7f58185
--- /dev/null
+++ b/video-ps3eye/wrapper.cxx
@@ -0,0 +1,120 @@
+#include "shm-layout.hpp"
+#include "shm.hpp"
+
+#include "ps3eye-driver/ps3eye.hpp"
+
+#include <cstdlib>
+#include <atomic>
+
+#ifdef __clang__
+# pragma clang diagnostic ignored "-Watomic-implicit-seq-cst"
+#endif
+
+#ifdef __GNUG__
+# pragma GCC diagnostic ignored "-Wcast-qual"
+# pragma GCC diagnostic ignored "-Wformat-security"
+# pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+
+template<int N, typename... xs>
+[[noreturn]]
+static void error(volatile ps3eye::shm_out& out, const char (&error)[N], const xs&... args)
+{
+ snprintf((char*)out.error_string, sizeof(ps3eye::shm_out::error_string), error, args...);
+ std::quick_exit(2);
+}
+
+static void update_settings(ps3eye::camera& camera, const volatile ps3eye::shm_in& in)
+{
+ //camera.set_framerate(in.framerate);
+ camera.set_auto_gain(in.auto_gain);
+ camera.set_gain(in.gain);
+ camera.set_exposure(in.exposure);
+ camera.set_test_pattern_status(in.test_pattern);
+}
+
+static ps3eye::resolution get_mode(ps3eye::shm_in::mode res)
+{
+ switch (res)
+ {
+ default:
+ case ps3eye::shm_in::mode::qvga:
+ return ps3eye::res_QVGA;
+ case ps3eye::shm_in::mode::vga:
+ return ps3eye::res_VGA;
+ }
+}
+
+int main(int argc, char** argv)
+{
+ (void)argc; (void)argv;
+ shm_wrapper mem_("ps3eye-driver-shm", nullptr, sizeof(ps3eye::shm));
+ volatile auto& ptr_ = *(ps3eye::shm*)mem_.ptr();
+ volatile auto& in = ptr_.in;
+ volatile auto& out = ptr_.out;
+ int num_channels = in.channels;
+
+ auto cameras = ps3eye::list_devices();
+
+ out.status_ = ps3eye::shm_out::status::starting;
+
+ if (cameras.empty())
+ error(out, "no camera found");
+
+ auto& camera = cameras[0];
+ camera->set_debug(true);
+ auto* frame = (uint8_t*)out.data_640x480;
+ decltype(out.timecode) timecode = 0;
+
+ auto fmt = num_channels == 1 ? ps3eye::format::Gray : ps3eye::format::BGR;
+
+ {
+ int framerate = in.framerate;
+ if (framerate <= 0)
+ framerate = 60;
+
+ if (!camera->init(get_mode(in.resolution), framerate, fmt))
+ error(out, "camera init failed: %s", camera->error_string());
+
+ update_settings(*camera, in);
+
+ if (!camera->start())
+ error(out, "can't start camera: %s", camera->error_string());
+ }
+
+ out.timecode = 0;
+ in.do_exit = false;
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+
+ for (;;)
+ {
+ {
+ auto cookie = in.settings_updated;
+ if (cookie != out.settings_updated_ack)
+ {
+ camera->stop();
+ update_settings(*camera, in);
+ int framerate = in.framerate;
+ if (framerate <= 0)
+ framerate = 60;
+ if (!camera->init(get_mode(in.resolution), framerate, fmt))
+ error(out, "camera init failed: %s", camera->error_string());
+ if (!camera->start())
+ error(out, "can't start camera: %s", camera->error_string());
+ out.settings_updated_ack = cookie;
+ }
+ }
+
+ if (!camera->get_frame(frame))
+ continue;
+
+ out.timecode = ++timecode;
+
+ if (in.do_exit)
+ break;
+
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+ }
+
+ return 0;
+}