aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/qt_sdl/CameraManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend/qt_sdl/CameraManager.cpp')
-rw-r--r--src/frontend/qt_sdl/CameraManager.cpp562
1 files changed, 562 insertions, 0 deletions
diff --git a/src/frontend/qt_sdl/CameraManager.cpp b/src/frontend/qt_sdl/CameraManager.cpp
new file mode 100644
index 0000000..23f25a6
--- /dev/null
+++ b/src/frontend/qt_sdl/CameraManager.cpp
@@ -0,0 +1,562 @@
+/*
+ Copyright 2016-2022 melonDS team
+
+ This file is part of melonDS.
+
+ melonDS is free software: you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#include "CameraManager.h"
+#include "Config.h"
+
+
+#if QT_VERSION >= 0x060000
+
+CameraFrameDumper::CameraFrameDumper(QObject* parent) : QVideoSink(parent)
+{
+ cam = (CameraManager*)parent;
+
+ connect(this, &CameraFrameDumper::videoFrameChanged, this, &CameraFrameDumper::present);
+}
+
+void CameraFrameDumper::present(const QVideoFrame& _frame)
+{
+ QVideoFrame frame(_frame);
+ if (!frame.map(QVideoFrame::ReadOnly))
+ return;
+ if (!frame.isReadable())
+ {
+ frame.unmap();
+ return;
+ }
+
+ switch (frame.pixelFormat())
+ {
+ case QVideoFrameFormat::Format_XRGB8888:
+ case QVideoFrameFormat::Format_YUYV:
+ cam->feedFrame((u32*)frame.bits(0), frame.width(), frame.height(), frame.pixelFormat() == QVideoFrameFormat::Format_YUYV);
+ break;
+
+ case QVideoFrameFormat::Format_NV12:
+ cam->feedFrame_NV12((u8*)frame.bits(0), (u8*)frame.bits(1), frame.width(), frame.height());
+ break;
+ }
+
+ frame.unmap();
+}
+
+#else
+
+CameraFrameDumper::CameraFrameDumper(QObject* parent) : QAbstractVideoSurface(parent)
+{
+ cam = (CameraManager*)parent;
+}
+
+bool CameraFrameDumper::present(const QVideoFrame& _frame)
+{
+ QVideoFrame frame(_frame);
+ if (!frame.map(QAbstractVideoBuffer::ReadOnly))
+ return false;
+ if (!frame.isReadable())
+ {
+ frame.unmap();
+ return false;
+ }
+
+ switch (frame.pixelFormat())
+ {
+ case QVideoFrame::Format_RGB32:
+ case QVideoFrame::Format_YUYV:
+ cam->feedFrame((u32*)frame.bits(0), frame.width(), frame.height(), frame.pixelFormat() == QVideoFrame::Format_YUYV);
+ break;
+
+ case QVideoFrame::Format_NV12:
+ cam->feedFrame_NV12((u8*)frame.bits(0), (u8*)frame.bits(1), frame.width(), frame.height());
+ break;
+ }
+
+ frame.unmap();
+
+ return true;
+}
+
+QList<QVideoFrame::PixelFormat> CameraFrameDumper::supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const
+{
+ QList<QVideoFrame::PixelFormat> ret;
+
+ ret.append(QVideoFrame::Format_RGB32);
+ ret.append(QVideoFrame::Format_YUYV);
+ ret.append(QVideoFrame::Format_NV12);
+
+ return ret;
+}
+
+#endif
+
+
+CameraManager::CameraManager(int num, int width, int height, bool yuv) : QObject()
+{
+ this->num = num;
+
+ startNum = 0;
+
+ // QCamera needs to be controlled from the UI thread, hence this
+ connect(this, SIGNAL(camStartSignal()), this, SLOT(camStart()));
+ connect(this, SIGNAL(camStopSignal()), this, SLOT(camStop()));
+
+ frameWidth = width;
+ frameHeight = height;
+ frameFormatYUV = yuv;
+
+ int fbsize = frameWidth * frameHeight;
+ if (yuv) fbsize /= 2;
+ frameBuffer = new u32[fbsize];
+ tempFrameBuffer = new u32[fbsize];
+
+ inputType = -1;
+ xFlip = false;
+ init();
+}
+
+CameraManager::~CameraManager()
+{
+ deInit();
+
+ // save settings here?
+
+ delete[] frameBuffer;
+}
+
+void CameraManager::init()
+{
+ if (inputType != -1)
+ deInit();
+
+ startNum = 0;
+
+ inputType = Config::Camera[num].InputType;
+ imagePath = QString::fromStdString(Config::Camera[num].ImagePath);
+ camDeviceName = QString::fromStdString(Config::Camera[num].CamDeviceName);
+
+ camDevice = nullptr;
+
+ {
+ // fill the framebuffer with black
+
+ int total = frameWidth * frameHeight;
+ u32 fill = 0;
+ if (frameFormatYUV)
+ {
+ total /= 2;
+ fill = 0x80008000;
+ }
+
+ for (int i = 0; i < total; i++)
+ frameBuffer[i] = fill;
+ }
+
+ if (inputType == 1)
+ {
+ // still image
+
+ QImage img(imagePath);
+ if (!img.isNull())
+ {
+ QImage imgconv = img.convertToFormat(QImage::Format_RGB32);
+ if (frameFormatYUV)
+ {
+ copyFrame_RGBtoYUV((u32*)img.bits(), img.width(), img.height(),
+ frameBuffer, frameWidth, frameHeight,
+ false);
+ }
+ else
+ {
+ copyFrame_Straight((u32*)img.bits(), img.width(), img.height(),
+ frameBuffer, frameWidth, frameHeight,
+ false, false);
+ }
+ }
+ }
+ else if (inputType == 2)
+ {
+ // physical camera
+
+#if QT_VERSION >= 0x060000
+ const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
+ for (const QCameraDevice& cam : cameras)
+ {
+ if (QString(cam.id()) == camDeviceName)
+ {
+ camDevice = new QCamera(cam);
+ break;
+ }
+ }
+
+ if (camDevice)
+ {
+ const QList<QCameraFormat> supported = camDevice->cameraDevice().videoFormats();
+ bool good = false;
+ for (const QCameraFormat& item : supported)
+ {
+ if (item.pixelFormat() != QVideoFrameFormat::Format_YUYV &&
+ item.pixelFormat() != QVideoFrameFormat::Format_NV12 &&
+ item.pixelFormat() != QVideoFrameFormat::Format_XRGB8888)
+ continue;
+
+ if (item.resolution().width() != 640 && item.resolution().height() != 480)
+ continue;
+
+ camDevice->setCameraFormat(item);
+ good = true;
+ break;
+ }
+
+ if (!good)
+ {
+ delete camDevice;
+ camDevice = nullptr;
+ }
+ else
+ {
+ camDumper = new CameraFrameDumper(this);
+
+ camSession = new QMediaCaptureSession(this);
+ camSession->setCamera(camDevice);
+ camSession->setVideoOutput(camDumper);
+ }
+ }
+#else
+ camDevice = new QCamera(camDeviceName.toUtf8());
+ if (camDevice->error() != QCamera::NoError)
+ {
+ delete camDevice;
+ camDevice = nullptr;
+ }
+
+ if (camDevice)
+ {
+ camDevice->load();
+
+ const QList<QCameraViewfinderSettings> supported = camDevice->supportedViewfinderSettings();
+ bool good = false;
+ for (const QCameraViewfinderSettings& item : supported)
+ {
+ if (item.pixelFormat() != QVideoFrame::Format_YUYV &&
+ item.pixelFormat() != QVideoFrame::Format_NV12 &&
+ item.pixelFormat() != QVideoFrame::Format_RGB32)
+ continue;
+
+ if (item.resolution().width() != 640 && item.resolution().height() != 480)
+ continue;
+
+ camDevice->setViewfinderSettings(item);
+ good = true;
+ break;
+ }
+
+ camDevice->unload();
+
+ if (!good)
+ {
+ delete camDevice;
+ camDevice = nullptr;
+ }
+ else
+ {
+ camDumper = new CameraFrameDumper(this);
+ camDevice->setViewfinder(camDumper);
+ }
+ }
+#endif
+ }
+}
+
+void CameraManager::deInit()
+{
+ if (inputType == 2)
+ {
+ if (camDevice)
+ {
+ camDevice->stop();
+ delete camDevice;
+ delete camDumper;
+#if QT_VERSION >= 0x060000
+ delete camSession;
+#endif
+ }
+ }
+
+ camDevice = nullptr;
+ inputType = -1;
+}
+
+void CameraManager::start()
+{
+ if (startNum == 1) return;
+ startNum = 1;
+
+ if (inputType == 2)
+ {
+ emit camStartSignal();
+ }
+}
+
+void CameraManager::stop()
+{
+ if (startNum == 0) return;
+ startNum = 0;
+
+ if (inputType == 2)
+ {
+ emit camStopSignal();
+ }
+}
+
+bool CameraManager::isStarted()
+{
+ return startNum != 0;
+}
+
+void CameraManager::camStart()
+{
+ if (camDevice)
+ camDevice->start();
+}
+
+void CameraManager::camStop()
+{
+ if (camDevice)
+ camDevice->stop();
+}
+
+void CameraManager::setXFlip(bool flip)
+{
+ xFlip = flip;
+}
+
+void CameraManager::captureFrame(u32* frame, int width, int height, bool yuv)
+{
+ frameMutex.lock();
+
+ if ((width == frameWidth) &&
+ (height == frameHeight) &&
+ (yuv == frameFormatYUV) &&
+ (!xFlip))
+ {
+ int len = width * height;
+ if (yuv) len /= 2;
+ memcpy(frame, frameBuffer, len * sizeof(u32));
+ }
+ else
+ {
+ if (yuv == frameFormatYUV)
+ {
+ copyFrame_Straight(frameBuffer, frameWidth, frameHeight,
+ frame, width, height,
+ xFlip, yuv);
+ }
+ else if (yuv)
+ {
+ copyFrame_RGBtoYUV(frameBuffer, frameWidth, frameHeight,
+ frame, width, height,
+ xFlip);
+ }
+ else
+ {
+ copyFrame_YUVtoRGB(frameBuffer, frameWidth, frameHeight,
+ frame, width, height,
+ xFlip);
+ }
+ }
+
+ frameMutex.unlock();
+}
+
+void CameraManager::feedFrame(u32* frame, int width, int height, bool yuv)
+{
+ frameMutex.lock();
+
+ if (width == frameWidth && height == frameHeight && yuv == frameFormatYUV)
+ {
+ int len = width * height;
+ if (yuv) len /= 2;
+ memcpy(frameBuffer, frame, len * sizeof(u32));
+ }
+ else
+ {
+ if (yuv == frameFormatYUV)
+ {
+ copyFrame_Straight(frame, width, height,
+ frameBuffer, frameWidth, frameHeight,
+ false, yuv);
+ }
+ else if (yuv)
+ {
+ copyFrame_RGBtoYUV(frame, width, height,
+ frameBuffer, frameWidth, frameHeight,
+ false);
+ }
+ else
+ {
+ copyFrame_YUVtoRGB(frame, width, height,
+ frameBuffer, frameWidth, frameHeight,
+ false);
+ }
+ }
+
+ frameMutex.unlock();
+}
+
+void CameraManager::feedFrame_NV12(u8* planeY, u8* planeUV, int width, int height)
+{
+ for (int y = 0; y < frameHeight; y++)
+ {
+ int sy = (y * height) / frameHeight;
+
+ for (int x = 0; x < frameWidth; x+=2)
+ {
+ int sx1 = (x * width) / frameWidth;
+ int sx2 = ((x+1) * width) / frameWidth;
+
+ u32 val;
+
+ u8 y1 = planeY[(sy*width) + sx1];
+ u8 y2 = planeY[(sy*width) + sx2];
+
+ int uvpos = (((sy>>1)*(width>>1)) + (sx1>>1));
+ u8 u = planeUV[uvpos << 1];
+ u8 v = planeUV[(uvpos << 1) + 1];
+
+ val = y1 | (u << 8) | (y2 << 16) | (v << 24);
+ tempFrameBuffer[((y*frameWidth) + x) >> 1] = val;
+ }
+ }
+
+ feedFrame(tempFrameBuffer, frameWidth, frameHeight, true);
+}
+
+void CameraManager::copyFrame_Straight(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip, bool yuv)
+{
+ u32 alpha = 0xFF000000;
+
+ if (yuv)
+ {
+ swidth /= 2;
+ dwidth /= 2;
+ alpha = 0;
+ }
+
+ for (int dy = 0; dy < dheight; dy++)
+ {
+ int sy = (dy * sheight) / dheight;
+
+ for (int dx = 0; dx < dwidth; dx++)
+ {
+ int sx = (dx * swidth) / dwidth;
+ if (xflip) sx = swidth-1 - sx;
+
+ dst[(dy * dwidth) + dx] = src[(sy * swidth) + sx] | alpha;
+ }
+ }
+}
+
+void CameraManager::copyFrame_RGBtoYUV(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip)
+{
+ for (int dy = 0; dy < dheight; dy++)
+ {
+ int sy = (dy * sheight) / dheight;
+
+ for (int dx = 0; dx < dwidth; dx+=2)
+ {
+ int sx;
+
+ sx = (dx * swidth) / dwidth;
+ if (xflip) sx = swidth-1 - sx;
+
+ u32 pixel1 = src[sy*swidth + sx];
+
+ sx = ((dx+1) * swidth) / dwidth;
+ if (xflip) sx = swidth-1 - sx;
+
+ u32 pixel2 = src[sy*swidth + sx];
+
+ int r1 = (pixel1 >> 16) & 0xFF;
+ int g1 = (pixel1 >> 8) & 0xFF;
+ int b1 = pixel1 & 0xFF;
+
+ int r2 = (pixel2 >> 16) & 0xFF;
+ int g2 = (pixel2 >> 8) & 0xFF;
+ int b2 = pixel2 & 0xFF;
+
+ int y1 = ((r1 * 19595) + (g1 * 38470) + (b1 * 7471)) >> 16;
+ int u1 = ((b1 - y1) * 32244) >> 16;
+ int v1 = ((r1 - y1) * 57475) >> 16;
+
+ int y2 = ((r2 * 19595) + (g2 * 38470) + (b2 * 7471)) >> 16;
+ int u2 = ((b2 - y2) * 32244) >> 16;
+ int v2 = ((r2 - y2) * 57475) >> 16;
+
+ u1 += 128; v1 += 128;
+ u2 += 128; v2 += 128;
+
+ y1 = std::clamp(y1, 0, 255); u1 = std::clamp(u1, 0, 255); v1 = std::clamp(v1, 0, 255);
+ y2 = std::clamp(y2, 0, 255); u2 = std::clamp(u2, 0, 255); v2 = std::clamp(v2, 0, 255);
+
+ // huh
+ u1 = (u1 + u2) >> 1;
+ v1 = (v1 + v2) >> 1;
+
+ dst[(dy*dwidth + dx) / 2] = y1 | (u1 << 8) | (y2 << 16) | (v1 << 24);
+ }
+ }
+}
+
+void CameraManager::copyFrame_YUVtoRGB(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip)
+{
+ for (int dy = 0; dy < dheight; dy++)
+ {
+ int sy = (dy * sheight) / dheight;
+
+ for (int dx = 0; dx < dwidth; dx+=2)
+ {
+ int sx = (dx * swidth) / dwidth;
+ if (xflip) sx = swidth-1 - sx;
+
+ u32 val = src[(sy*swidth + sx) / 2];
+
+ int y1 = val & 0xFF;
+ int u = (val >> 8) & 0xFF;
+ int y2 = (val >> 16) & 0xFF;
+ int v = (val >> 24) & 0xFF;
+
+ u -= 128; v -= 128;
+
+ int r1 = y1 + ((v * 91881) >> 16);
+ int g1 = y1 - ((v * 46793) >> 16) - ((u * 22544) >> 16);
+ int b1 = y1 + ((u * 116129) >> 16);
+
+ int r2 = y2 + ((v * 91881) >> 16);
+ int g2 = y2 - ((v * 46793) >> 16) - ((u * 22544) >> 16);
+ int b2 = y2 + ((u * 116129) >> 16);
+
+ r1 = std::clamp(r1, 0, 255); g1 = std::clamp(g1, 0, 255); b1 = std::clamp(b1, 0, 255);
+ r2 = std::clamp(r2, 0, 255); g2 = std::clamp(g2, 0, 255); b2 = std::clamp(b2, 0, 255);
+
+ u32 col1 = 0xFF000000 | (r1 << 16) | (g1 << 8) | b1;
+ u32 col2 = 0xFF000000 | (r2 << 16) | (g2 << 8) | b2;
+
+ dst[dy*dwidth + dx ] = col1;
+ dst[dy*dwidth + dx+1] = col2;
+ }
+ }
+}