summaryrefslogtreecommitdiffhomepage
path: root/video-ps3eye/module.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'video-ps3eye/module.cpp')
-rw-r--r--video-ps3eye/module.cpp307
1 files changed, 307 insertions, 0 deletions
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);
+}