actual DSi camera support (#1520)

basically feeding something that isn't a fixed stripe pattern, and emulating enough of the camera hardware to make this work
This commit is contained in:
Arisotura
2022-10-02 16:47:57 +02:00
committed by GitHub
parent c1c4cbc838
commit 3f4573574a
23 changed files with 2024 additions and 270 deletions

View File

@ -14,6 +14,7 @@ set(SOURCES_QT_SDL
InputConfig/MapButton.h
InputConfig/resources/ds.qrc
VideoSettingsDialog.cpp
CameraSettingsDialog.cpp
AudioSettingsDialog.cpp
FirmwareSettingsDialog.cpp
PathSettingsDialog.cpp
@ -33,8 +34,9 @@ set(SOURCES_QT_SDL
Platform.cpp
QPathInput.h
ROMManager.cpp
SaveManager.cpp
SaveManager.cpp
CameraManager.cpp
ArchiveUtil.h
ArchiveUtil.cpp
@ -58,11 +60,11 @@ if (WIN32)
endif()
if (USE_QT6)
find_package(Qt6 COMPONENTS Core Gui Widgets Network OpenGL OpenGLWidgets REQUIRED)
set(QT_LINK_LIBS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::OpenGL Qt6::OpenGLWidgets)
find_package(Qt6 COMPONENTS Core Gui Widgets Network Multimedia OpenGL OpenGLWidgets REQUIRED)
set(QT_LINK_LIBS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Multimedia Qt6::OpenGL Qt6::OpenGLWidgets)
else()
find_package(Qt5 COMPONENTS Core Gui Widgets Network REQUIRED)
set(QT_LINK_LIBS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network)
find_package(Qt5 COMPONENTS Core Gui Widgets Network Multimedia REQUIRED)
set(QT_LINK_LIBS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network Qt5::Multimedia)
endif()
set(CMAKE_AUTOMOC ON)

View File

@ -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;
}
}
}

View File

@ -0,0 +1,133 @@
/*
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/.
*/
#ifndef CAMERAMANAGER_H
#define CAMERAMANAGER_H
#include <QCamera>
#if QT_VERSION >= 0x060000
#include <QMediaDevices>
#include <QCameraDevice>
#include <QMediaCaptureSession>
#include <QVideoSink>
#else
#include <QCameraInfo>
#include <QAbstractVideoSurface>
#include <QVideoSurfaceFormat>
#endif
#include <QMutex>
#include "types.h"
class CameraManager;
#if QT_VERSION >= 0x060000
class CameraFrameDumper : public QVideoSink
{
Q_OBJECT
public:
CameraFrameDumper(QObject* parent = nullptr);
public slots:
void present(const QVideoFrame& frame);
private:
CameraManager* cam;
};
#else
class CameraFrameDumper : public QAbstractVideoSurface
{
Q_OBJECT
public:
CameraFrameDumper(QObject* parent = nullptr);
bool present(const QVideoFrame& frame) override;
QList<QVideoFrame::PixelFormat> supportedPixelFormats(QAbstractVideoBuffer::HandleType type = QAbstractVideoBuffer::NoHandle) const override;
private:
CameraManager* cam;
};
#endif
class CameraManager : public QObject
{
Q_OBJECT
public:
CameraManager(int num, int width, int height, bool yuv);
~CameraManager();
void init();
void deInit();
void start();
void stop();
bool isStarted();
void setXFlip(bool flip);
void captureFrame(u32* frame, int width, int height, bool yuv);
void feedFrame(u32* frame, int width, int height, bool yuv);
void feedFrame_NV12(u8* planeY, u8* planeUV, int width, int height);
signals:
void camStartSignal();
void camStopSignal();
private slots:
void camStart();
void camStop();
private:
int num;
int startNum;
int inputType;
QString imagePath;
QString camDeviceName;
QCamera* camDevice;
CameraFrameDumper* camDumper;
#if QT_VERSION >= 0x060000
QMediaCaptureSession* camSession;
#endif
int frameWidth, frameHeight;
bool frameFormatYUV;
u32* frameBuffer;
u32* tempFrameBuffer;
QMutex frameMutex;
bool xFlip;
void copyFrame_Straight(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip, bool yuv);
void copyFrame_RGBtoYUV(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip);
void copyFrame_YUVtoRGB(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip);
};
#endif // CAMERAMANAGER_H

View File

@ -0,0 +1,304 @@
/*
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 <stdio.h>
#include <QFileDialog>
#include <QPaintEvent>
#include <QPainter>
#include "types.h"
#include "CameraSettingsDialog.h"
#include "ui_CameraSettingsDialog.h"
CameraSettingsDialog* CameraSettingsDialog::currentDlg = nullptr;
extern std::string EmuDirectory;
extern CameraManager* camManager[2];
CameraPreviewPanel::CameraPreviewPanel(QWidget* parent) : QWidget(parent)
{
currentCam = nullptr;
updateTimer = startTimer(50);
}
CameraPreviewPanel::~CameraPreviewPanel()
{
killTimer(updateTimer);
}
void CameraPreviewPanel::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
if (!currentCam)
{
painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0));
return;
}
QImage picture(256, 192, QImage::Format_RGB32);
currentCam->captureFrame((u32*)picture.bits(), 256, 192, false);
painter.drawImage(0, 0, picture);
}
CameraSettingsDialog::CameraSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::CameraSettingsDialog)
{
previewPanel = nullptr;
currentCfg = nullptr;
currentCam = nullptr;
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
for (int i = 0; i < 2; i++)
{
oldCamSettings[i] = Config::Camera[i];
}
ui->cbCameraSel->addItem("DSi outer camera");
ui->cbCameraSel->addItem("DSi inner camera");
#if QT_VERSION >= 0x060000
const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
for (const QCameraDevice &cameraInfo : cameras)
{
QString name = cameraInfo.description();
QCameraDevice::Position pos = cameraInfo.position();
if (pos != QCameraDevice::UnspecifiedPosition)
{
name += " (";
if (pos == QCameraDevice::FrontFace)
name += "inner camera";
else if (pos == QCameraDevice::BackFace)
name += "outer camera";
name += ")";
}
ui->cbPhysicalCamera->addItem(name, QString(cameraInfo.id()));
}
#else
const QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
for (const QCameraInfo &cameraInfo : cameras)
{
QString name = cameraInfo.description();
QCamera::Position pos = cameraInfo.position();
if (pos != QCamera::UnspecifiedPosition)
{
name += " (";
if (pos == QCamera::FrontFace)
name += "inner camera";
else if (pos == QCamera::BackFace)
name += "outer camera";
name += ")";
}
ui->cbPhysicalCamera->addItem(name, cameraInfo.deviceName());
}
#endif
ui->rbPictureCamera->setEnabled(ui->cbPhysicalCamera->count() > 0);
grpInputType = new QButtonGroup(this);
grpInputType->addButton(ui->rbPictureNone, 0);
grpInputType->addButton(ui->rbPictureImg, 1);
grpInputType->addButton(ui->rbPictureCamera, 2);
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
connect(grpInputType, SIGNAL(buttonClicked(int)), this, SLOT(onChangeInputType(int)));
#else
connect(grpInputType, SIGNAL(idClicked(int)), this, SLOT(onChangeInputType(int)));
#endif
previewPanel = new CameraPreviewPanel(this);
QVBoxLayout* previewLayout = new QVBoxLayout();
previewLayout->addWidget(previewPanel);
ui->grpPreview->setLayout(previewLayout);
previewPanel->setMinimumSize(256, 192);
previewPanel->setMaximumSize(256, 192);
on_cbCameraSel_currentIndexChanged(ui->cbCameraSel->currentIndex());
}
CameraSettingsDialog::~CameraSettingsDialog()
{
delete ui;
}
void CameraSettingsDialog::on_CameraSettingsDialog_accepted()
{
for (int i = 0; i < 2; i++)
{
camManager[i]->stop();
}
Config::Save();
closeDlg();
}
void CameraSettingsDialog::on_CameraSettingsDialog_rejected()
{
for (int i = 0; i < 2; i++)
{
camManager[i]->stop();
camManager[i]->deInit();
Config::Camera[i] = oldCamSettings[i];
camManager[i]->init();
}
closeDlg();
}
void CameraSettingsDialog::on_cbCameraSel_currentIndexChanged(int id)
{
if (!previewPanel) return;
if (currentCam)
{
currentCam->stop();
}
currentId = id;
currentCfg = &Config::Camera[id];
//currentCam = camManager[id];
currentCam = nullptr;
populateCamControls(id);
currentCam = camManager[id];
previewPanel->setCurrentCam(currentCam);
currentCam->start();
}
void CameraSettingsDialog::onChangeInputType(int type)
{
if (!currentCfg) return;
if (currentCam)
{
currentCam->stop();
currentCam->deInit();
}
currentCfg->InputType = type;
ui->txtSrcImagePath->setEnabled(type == 1);
ui->btnSrcImageBrowse->setEnabled(type == 1);
ui->cbPhysicalCamera->setEnabled((type == 2) && (ui->cbPhysicalCamera->count()>0));
currentCfg->ImagePath = ui->txtSrcImagePath->text().toStdString();
if (ui->cbPhysicalCamera->count() > 0)
currentCfg->CamDeviceName = ui->cbPhysicalCamera->currentData().toString().toStdString();
if (currentCam)
{
currentCam->init();
currentCam->start();
}
}
void CameraSettingsDialog::on_txtSrcImagePath_textChanged()
{
if (!currentCfg) return;
if (currentCam)
{
currentCam->stop();
currentCam->deInit();
}
currentCfg->ImagePath = ui->txtSrcImagePath->text().toStdString();
if (currentCam)
{
currentCam->init();
currentCam->start();
}
}
void CameraSettingsDialog::on_btnSrcImageBrowse_clicked()
{
QString file = QFileDialog::getOpenFileName(this,
"Select image file...",
QString::fromStdString(EmuDirectory),
"Image files (*.png *.jpg *.jpeg *.bmp);;Any file (*.*)");
if (file.isEmpty()) return;
ui->txtSrcImagePath->setText(file);
}
void CameraSettingsDialog::on_cbPhysicalCamera_currentIndexChanged(int id)
{
if (!currentCfg) return;
if (currentCam)
{
currentCam->stop();
currentCam->deInit();
}
currentCfg->CamDeviceName = ui->cbPhysicalCamera->itemData(id).toString().toStdString();
if (currentCam)
{
currentCam->init();
currentCam->start();
}
}
void CameraSettingsDialog::populateCamControls(int id)
{
Config::CameraConfig& cfg = Config::Camera[id];
int type = cfg.InputType;
if (type < 0 || type >= grpInputType->buttons().count()) type = 0;
grpInputType->button(type)->setChecked(true);
ui->txtSrcImagePath->setText(QString::fromStdString(cfg.ImagePath));
bool deviceset = false;
QString device = QString::fromStdString(cfg.CamDeviceName);
for (int i = 0; i < ui->cbPhysicalCamera->count(); i++)
{
QString itemdev = ui->cbPhysicalCamera->itemData(i).toString();
if (itemdev == device)
{
ui->cbPhysicalCamera->setCurrentIndex(i);
deviceset = true;
break;
}
}
if (!deviceset)
ui->cbPhysicalCamera->setCurrentIndex(0);
onChangeInputType(type);
ui->chkFlipPicture->setChecked(cfg.XFlip);
}
void CameraSettingsDialog::on_chkFlipPicture_clicked()
{
if (!currentCfg) return;
currentCfg->XFlip = ui->chkFlipPicture->isChecked();
if (currentCam) currentCam->setXFlip(currentCfg->XFlip);
}

View File

@ -0,0 +1,108 @@
/*
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/.
*/
#ifndef CAMERASETTINGSDIALOG_H
#define CAMERASETTINGSDIALOG_H
#include <QDialog>
#include <QButtonGroup>
#include "Config.h"
#include "CameraManager.h"
namespace Ui { class CameraSettingsDialog; }
class CameraSettingsDialog;
class CameraPreviewPanel : public QWidget
{
Q_OBJECT
public:
CameraPreviewPanel(QWidget* parent);
~CameraPreviewPanel();
void setCurrentCam(CameraManager* cam)
{
currentCam = cam;
}
protected:
void paintEvent(QPaintEvent* event) override;
void timerEvent(QTimerEvent* event) override
{
repaint();
}
private:
int updateTimer;
CameraManager* currentCam;
};
class CameraSettingsDialog : public QDialog
{
Q_OBJECT
public:
explicit CameraSettingsDialog(QWidget* parent);
~CameraSettingsDialog();
static CameraSettingsDialog* currentDlg;
static CameraSettingsDialog* openDlg(QWidget* parent)
{
if (currentDlg)
{
currentDlg->activateWindow();
return currentDlg;
}
currentDlg = new CameraSettingsDialog(parent);
currentDlg->open();
return currentDlg;
}
static void closeDlg()
{
currentDlg = nullptr;
}
private slots:
void on_CameraSettingsDialog_accepted();
void on_CameraSettingsDialog_rejected();
void on_cbCameraSel_currentIndexChanged(int id);
void onChangeInputType(int type);
void on_txtSrcImagePath_textChanged();
void on_btnSrcImageBrowse_clicked();
void on_cbPhysicalCamera_currentIndexChanged(int id);
void on_chkFlipPicture_clicked();
private:
Ui::CameraSettingsDialog* ui;
QButtonGroup* grpInputType;
CameraPreviewPanel* previewPanel;
int currentId;
Config::CameraConfig* currentCfg;
CameraManager* currentCam;
Config::CameraConfig oldCamSettings[2];
void populateCamControls(int id);
};
#endif // CAMERASETTINGSDIALOG_H

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CameraSettingsDialog</class>
<widget class="QDialog" name="CameraSettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>605</width>
<height>341</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Camera settings - melonDS</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Configure emulated camera:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cbCameraSel"/>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Picture source</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="2">
<widget class="QLineEdit" name="txtSrcImagePath"/>
</item>
<item row="0" column="0" colspan="4">
<widget class="QRadioButton" name="rbPictureNone">
<property name="text">
<string>None (blank)</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QPushButton" name="btnSrcImageBrowse">
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QRadioButton" name="rbPictureCamera">
<property name="text">
<string>Physical camera:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="rbPictureImg">
<property name="text">
<string>Image file:</string>
</property>
</widget>
</item>
<item row="2" column="2" colspan="2">
<widget class="QComboBox" name="cbPhysicalCamera"/>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Picture settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QCheckBox" name="chkFlipPicture">
<property name="text">
<string>Flip horizontally</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="2" rowspan="3">
<widget class="QGroupBox" name="grpPreview">
<property name="title">
<string>Preview</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>CameraSettingsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>CameraSettingsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -140,6 +140,8 @@ bool DSBatteryLevelOkay;
int DSiBatteryLevel;
bool DSiBatteryCharging;
CameraConfig Camera[2];
const char* kConfigFile = "melonDS.ini";
const char* kUniqueConfigFile = "melonDS.%d.ini";
@ -316,6 +318,17 @@ ConfigEntry ConfigFile[] =
{"DSiBatteryLevel", 0, &DSiBatteryLevel, 0xF, true},
{"DSiBatteryCharging", 1, &DSiBatteryCharging, true, true},
// TODO!!
// we need a more elegant way to deal with this
{"Camera0_InputType", 0, &Camera[0].InputType, 0, false},
{"Camera0_ImagePath", 2, &Camera[0].ImagePath, (std::string)"", false},
{"Camera0_CamDeviceName", 2, &Camera[0].CamDeviceName, (std::string)"", false},
{"Camera0_XFlip", 1, &Camera[0].XFlip, false, false},
{"Camera1_InputType", 0, &Camera[1].InputType, 0, false},
{"Camera1_ImagePath", 2, &Camera[1].ImagePath, (std::string)"", false},
{"Camera1_CamDeviceName", 2, &Camera[1].CamDeviceName, (std::string)"", false},
{"Camera1_XFlip", 1, &Camera[1].XFlip, false, false},
{"", -1, nullptr, 0, false}
};

View File

@ -61,6 +61,14 @@ struct ConfigEntry
bool InstanceUnique; // whether the setting can exist individually for each instance in multiplayer
};
struct CameraConfig
{
int InputType; // 0=blank 1=image 2=camera
std::string ImagePath;
std::string CamDeviceName;
bool XFlip;
};
extern int KeyMapping[12];
extern int JoyMapping[12];
@ -175,6 +183,8 @@ extern bool DSBatteryLevelOkay;
extern int DSiBatteryLevel;
extern bool DSiBatteryCharging;
extern CameraConfig Camera[2];
void Load();
void Save();

View File

@ -33,6 +33,7 @@
#include "Platform.h"
#include "Config.h"
#include "ROMManager.h"
#include "CameraManager.h"
#include "LAN_Socket.h"
#include "LAN_PCap.h"
#include "LocalMP.h"
@ -40,8 +41,11 @@
std::string EmuDirectory;
extern CameraManager* camManager[2];
void emuStop();
namespace Platform
{
@ -99,7 +103,6 @@ void IPCDeInit()
IPCBuffer->detach();
delete IPCBuffer;
}
IPCBuffer = nullptr;
}
@ -492,8 +495,6 @@ u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask)
return LocalMP::RecvReplies(data, timestamp, aidmask);
}
bool LAN_Init()
{
if (Config::DirectLAN)
@ -537,4 +538,20 @@ int LAN_RecvPacket(u8* data)
return LAN_Socket::RecvPacket(data);
}
void Camera_Start(int num)
{
return camManager[num]->start();
}
void Camera_Stop(int num)
{
return camManager[num]->stop();
}
void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv)
{
return camManager[num]->captureFrame(frame, width, height, yuv);
}
}

View File

@ -55,6 +55,7 @@
#include "EmuSettingsDialog.h"
#include "InputConfig/InputConfigDialog.h"
#include "VideoSettingsDialog.h"
#include "CameraSettingsDialog.h"
#include "AudioSettingsDialog.h"
#include "FirmwareSettingsDialog.h"
#include "PathSettingsDialog.h"
@ -88,6 +89,7 @@
#include "ROMManager.h"
#include "ArchiveUtil.h"
#include "CameraManager.h"
// TODO: uniform variable spelling
@ -115,6 +117,9 @@ u32 micExtBufferWritePos;
u32 micWavLength;
s16* micWavBuffer;
CameraManager* camManager[2];
bool camStarted[2];
const struct { int id; float ratio; const char* label; } aspectRatios[] =
{
{ 0, 1, "4:3 (native)" },
@ -127,6 +132,7 @@ const struct { int id; float ratio; const char* label; } aspectRatios[] =
void micCallback(void* data, Uint8* stream, int len);
void audioCallback(void* data, Uint8* stream, int len)
{
len /= (sizeof(s16) * 2);
@ -1537,6 +1543,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
actVideoSettings = menu->addAction("Video settings");
connect(actVideoSettings, &QAction::triggered, this, &MainWindow::onOpenVideoSettings);
actCameraSettings = menu->addAction("Camera settings");
connect(actCameraSettings, &QAction::triggered, this, &MainWindow::onOpenCameraSettings);
actAudioSettings = menu->addAction("Audio settings");
connect(actAudioSettings, &QAction::triggered, this, &MainWindow::onOpenAudioSettings);
@ -2751,6 +2760,27 @@ void MainWindow::onOpenVideoSettings()
connect(dlg, &VideoSettingsDialog::updateVideoSettings, this, &MainWindow::onUpdateVideoSettings);
}
void MainWindow::onOpenCameraSettings()
{
emuThread->emuPause();
camStarted[0] = camManager[0]->isStarted();
camStarted[1] = camManager[1]->isStarted();
if (camStarted[0]) camManager[0]->stop();
if (camStarted[1]) camManager[1]->stop();
CameraSettingsDialog* dlg = CameraSettingsDialog::openDlg(this);
connect(dlg, &CameraSettingsDialog::finished, this, &MainWindow::onCameraSettingsFinished);
}
void MainWindow::onCameraSettingsFinished(int res)
{
if (camStarted[0]) camManager[0]->start();
if (camStarted[1]) camManager[1]->start();
emuThread->emuUnpause();
}
void MainWindow::onOpenAudioSettings()
{
AudioSettingsDialog* dlg = AudioSettingsDialog::openDlg(this);
@ -3105,7 +3135,7 @@ bool MelonApplication::event(QEvent *event)
int main(int argc, char** argv)
{
srand(time(NULL));
srand(time(nullptr));
printf("melonDS " MELONDS_VERSION "\n");
printf(MELONDS_URL "\n");
@ -3195,11 +3225,17 @@ int main(int argc, char** argv)
micDevice = 0;
memset(micExtBuffer, 0, sizeof(micExtBuffer));
micExtBufferWritePos = 0;
micWavBuffer = nullptr;
camStarted[0] = false;
camStarted[1] = false;
camManager[0] = new CameraManager(0, 640, 480, true);
camManager[1] = new CameraManager(1, 640, 480, true);
camManager[0]->setXFlip(Config::Camera[0].XFlip);
camManager[1]->setXFlip(Config::Camera[1].XFlip);
ROMManager::EnableCheats(Config::EnableCheats != 0);
Frontend::Init_Audio(audioFreq);
@ -3252,6 +3288,9 @@ int main(int argc, char** argv)
if (micWavBuffer) delete[] micWavBuffer;
delete camManager[0];
delete camManager[1];
Config::Save();
SDL_Quit();

View File

@ -266,16 +266,18 @@ private slots:
void onOpenInputConfig();
void onInputConfigFinished(int res);
void onOpenVideoSettings();
void onOpenCameraSettings();
void onCameraSettingsFinished(int res);
void onOpenAudioSettings();
void onOpenFirmwareSettings();
void onOpenPathSettings();
void onUpdateAudioSettings();
void onAudioSettingsFinished(int res);
void onOpenMPSettings();
void onMPSettingsFinished(int res);
void onOpenWifiSettings();
void onWifiSettingsFinished(int res);
void onOpenFirmwareSettings();
void onFirmwareSettingsFinished(int res);
void onOpenPathSettings();
void onPathSettingsFinished(int res);
void onOpenInterfaceSettings();
void onInterfaceSettingsFinished(int res);
@ -359,6 +361,7 @@ public:
QAction* actPowerManagement;
QAction* actInputConfig;
QAction* actVideoSettings;
QAction* actCameraSettings;
QAction* actAudioSettings;
QAction* actMPSettings;
QAction* actWifiSettings;