diff --git a/.github/workflows/build-ubuntu-aarch64.yml b/.github/workflows/build-ubuntu-aarch64.yml
index c9a602c9..c5004aa2 100644
--- a/.github/workflows/build-ubuntu-aarch64.yml
+++ b/.github/workflows/build-ubuntu-aarch64.yml
@@ -33,7 +33,7 @@ jobs:
rm /etc/apt/sources.list
mv /etc/apt/sources.list{.new,}
apt update
- DEBIAN_FRONTEND=noninteractive apt install -y {gcc-10,g++-10,pkg-config}-aarch64-linux-gnu {libsdl2,qtbase5,libslirp,libarchive,libepoxy}-dev:arm64 cmake dpkg-dev
+ DEBIAN_FRONTEND=noninteractive apt install -y {gcc-10,g++-10,pkg-config}-aarch64-linux-gnu {libsdl2,qtbase5,qtmultimedia5,libslirp,libarchive,libepoxy}-dev:arm64 cmake dpkg-dev
- name: Configure
shell: bash
run: |
diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml
index bbbec5c7..b9580e0c 100644
--- a/.github/workflows/build-ubuntu.yml
+++ b/.github/workflows/build-ubuntu.yml
@@ -19,7 +19,7 @@ jobs:
run: |
sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list
sudo apt update
- sudo apt install cmake libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default libslirp0 libslirp-dev libarchive-dev libepoxy-dev --allow-downgrades
+ sudo apt install cmake libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default qtmultimedia5-dev libslirp0 libslirp-dev libarchive-dev libepoxy-dev --allow-downgrades
- name: Create build environment
run: mkdir ${{runner.workspace}}/build
- name: Configure
diff --git a/res/melon.plist.in b/res/melon.plist.in
index 20d385a0..58d0c9ba 100644
--- a/res/melon.plist.in
+++ b/res/melon.plist.in
@@ -22,6 +22,8 @@
NSMicrophoneUsageDescription
We need microphone access so you can use the emulated DS microphone
+ NSCameraUsageDescription
+ Camera access is needed for emulation of the DSi's cameras
CFBundleDocumentTypes
diff --git a/src/DSi.cpp b/src/DSi.cpp
index 85c6cb66..02172612 100644
--- a/src/DSi.cpp
+++ b/src/DSi.cpp
@@ -94,6 +94,7 @@ bool Init()
#endif
if (!DSi_I2C::Init()) return false;
+ if (!DSi_CamModule::Init()) return false;
if (!DSi_AES::Init()) return false;
if (!DSi_DSP::Init()) return false;
@@ -121,6 +122,7 @@ void DeInit()
#endif
DSi_I2C::DeInit();
+ DSi_CamModule::DeInit();
DSi_AES::DeInit();
DSi_DSP::DeInit();
@@ -142,6 +144,7 @@ void Reset()
for (int i = 0; i < 8; i++) NDMAs[i]->Reset();
DSi_I2C::Reset();
+ DSi_CamModule::Reset();
DSi_DSP::Reset();
SDMMC->CloseHandles();
@@ -241,7 +244,7 @@ void DoSavestate(Savestate* file)
NDMAs[i]->DoSavestate(file);
DSi_AES::DoSavestate(file);
- DSi_Camera::DoSavestate(file);
+ DSi_CamModule::DoSavestate(file);
DSi_DSP::DoSavestate(file);
DSi_I2C::DoSavestate(file);
SDMMC->DoSavestate(file);
@@ -2230,7 +2233,7 @@ u8 ARM9IORead8(u32 addr)
if ((addr & 0xFFFFFF00) == 0x04004200)
{
if (!(SCFG_EXT[0] & (1<<17))) return 0;
- return DSi_Camera::Read8(addr);
+ return DSi_CamModule::Read8(addr);
}
if (addr >= 0x04004300 && addr <= 0x04004400)
@@ -2262,7 +2265,7 @@ u16 ARM9IORead16(u32 addr)
if ((addr & 0xFFFFFF00) == 0x04004200)
{
if (!(SCFG_EXT[0] & (1<<17))) return 0;
- return DSi_Camera::Read16(addr);
+ return DSi_CamModule::Read16(addr);
}
if (addr >= 0x04004300 && addr <= 0x04004400)
@@ -2324,7 +2327,7 @@ u32 ARM9IORead32(u32 addr)
if ((addr & 0xFFFFFF00) == 0x04004200)
{
if (!(SCFG_EXT[0] & (1<<17))) return 0;
- return DSi_Camera::Read32(addr);
+ return DSi_CamModule::Read32(addr);
}
return NDS::ARM9IORead32(addr);
@@ -2388,7 +2391,7 @@ void ARM9IOWrite8(u32 addr, u8 val)
if ((addr & 0xFFFFFF00) == 0x04004200)
{
if (!(SCFG_EXT[0] & (1<<17))) return;
- return DSi_Camera::Write8(addr, val);
+ return DSi_CamModule::Write8(addr, val);
}
if (addr >= 0x04004300 && addr <= 0x04004400)
@@ -2448,7 +2451,7 @@ void ARM9IOWrite16(u32 addr, u16 val)
if ((addr & 0xFFFFFF00) == 0x04004200)
{
if (!(SCFG_EXT[0] & (1<<17))) return;
- return DSi_Camera::Write16(addr, val);
+ return DSi_CamModule::Write16(addr, val);
}
if (addr >= 0x04004300 && addr <= 0x04004400)
@@ -2598,7 +2601,7 @@ void ARM9IOWrite32(u32 addr, u32 val)
if ((addr & 0xFFFFFF00) == 0x04004200)
{
if (!(SCFG_EXT[0] & (1<<17))) return;
- return DSi_Camera::Write32(addr, val);
+ return DSi_CamModule::Write32(addr, val);
}
return NDS::ARM9IOWrite32(addr, val);
diff --git a/src/DSi_Camera.cpp b/src/DSi_Camera.cpp
index e83434f8..842a9eee 100644
--- a/src/DSi_Camera.cpp
+++ b/src/DSi_Camera.cpp
@@ -20,17 +20,25 @@
#include
#include "DSi.h"
#include "DSi_Camera.h"
+#include "Platform.h"
-DSi_Camera* DSi_Camera0; // 78 / facing outside
-DSi_Camera* DSi_Camera1; // 7A / selfie cam
+namespace DSi_CamModule
+{
-u16 DSi_Camera::ModuleCnt;
-u16 DSi_Camera::Cnt;
+Camera* Camera0; // 78 / facing outside
+Camera* Camera1; // 7A / selfie cam
-u8 DSi_Camera::FrameBuffer[640*480*4];
-u32 DSi_Camera::FrameLength;
-u32 DSi_Camera::TransferPos;
+u16 ModuleCnt;
+u16 Cnt;
+
+u32 CropStart, CropEnd;
+
+// pixel data buffer holds a maximum of 512 words, regardless of how long scanlines are
+u32 DataBuffer[512];
+u32 BufferReadPos, BufferWritePos;
+u32 BufferNumLines;
+Camera* CurCamera;
// note on camera data/etc intervals
// on hardware those are likely affected by several factors
@@ -41,133 +49,353 @@ const u32 kIRQInterval = 1120000; // ~30 FPS
const u32 kTransferStart = 60000;
-bool DSi_Camera::Init()
+bool Init()
{
- DSi_Camera0 = new DSi_Camera(0);
- DSi_Camera1 = new DSi_Camera(1);
+ Camera0 = new Camera(0);
+ Camera1 = new Camera(1);
return true;
}
-void DSi_Camera::DeInit()
+void DeInit()
{
- delete DSi_Camera0;
- delete DSi_Camera1;
+ delete Camera0;
+ delete Camera1;
}
-void DSi_Camera::Reset()
+void Reset()
{
- DSi_Camera0->ResetCam();
- DSi_Camera1->ResetCam();
+ Camera0->Reset();
+ Camera1->Reset();
ModuleCnt = 0; // CHECKME
Cnt = 0;
- memset(FrameBuffer, 0, 640*480*4);
- TransferPos = 0;
- FrameLength = 256*192*2; // TODO: make it check frame size, data type, etc
+ CropStart = 0;
+ CropEnd = 0;
+
+ memset(DataBuffer, 0, 512*sizeof(u32));
+ BufferReadPos = 0;
+ BufferWritePos = 0;
+ BufferNumLines = 0;
+ CurCamera = nullptr;
NDS::ScheduleEvent(NDS::Event_DSi_CamIRQ, true, kIRQInterval, IRQ, 0);
}
-void DSi_Camera::DoSavestate(Savestate* file)
+void DoSavestate(Savestate* file)
{
file->Section("CAMi");
file->Var16(&ModuleCnt);
file->Var16(&Cnt);
- file->VarArray(FrameBuffer, sizeof(FrameBuffer));
+ /*file->VarArray(FrameBuffer, sizeof(FrameBuffer));
file->Var32(&TransferPos);
- file->Var32(&FrameLength);
+ file->Var32(&FrameLength);*/
- DSi_Camera0->DoCamSavestate(file);
- DSi_Camera1->DoCamSavestate(file);
+ Camera0->DoSavestate(file);
+ Camera1->DoSavestate(file);
}
-void DSi_Camera::IRQ(u32 param)
+void IRQ(u32 param)
{
- DSi_Camera* activecam = nullptr;
+ Camera* activecam = nullptr;
- // TODO: check which camera has priority if both are activated
- // (or does it just jumble both data sources together, like it
- // does for, say, overlapping VRAM?)
- if (DSi_Camera0->IsActivated()) activecam = DSi_Camera0;
- else if (DSi_Camera1->IsActivated()) activecam = DSi_Camera1;
+ // TODO: cameras don't have any priority!
+ // activating both together will jumble the image data together
+ if (Camera0->IsActivated()) activecam = Camera0;
+ else if (Camera1->IsActivated()) activecam = Camera1;
if (activecam)
{
- RequestFrame(activecam->Num);
+ activecam->StartTransfer();
if (Cnt & (1<<11))
NDS::SetIRQ(0, NDS::IRQ_DSi_Camera);
if (Cnt & (1<<15))
- NDS::ScheduleEvent(NDS::Event_DSi_CamTransfer, false, kTransferStart, Transfer, 0);
+ {
+ BufferReadPos = 0;
+ BufferWritePos = 0;
+ BufferNumLines = 0;
+ CurCamera = activecam;
+ NDS::ScheduleEvent(NDS::Event_DSi_CamTransfer, false, kTransferStart, TransferScanline, 0);
+ }
}
NDS::ScheduleEvent(NDS::Event_DSi_CamIRQ, true, kIRQInterval, IRQ, 0);
}
-void DSi_Camera::RequestFrame(u32 cam)
+void TransferScanline(u32 line)
{
- if (!(Cnt & (1<<13))) printf("CAMERA: !! REQUESTING YUV FRAME\n");
+ u32* dstbuf = &DataBuffer[BufferWritePos];
+ int maxlen = 512 - BufferWritePos;
- // TODO: picture size, data type, cropping, etc
- // generate test pattern
- // TODO: get picture from platform (actual camera, video file, whatever source)
- for (u32 y = 0; y < 192; y++)
+ u32 tmpbuf[512];
+ int datalen = CurCamera->TransferScanline(tmpbuf, 512);
+
+ // TODO: must be tweaked such that each block has enough time to transfer
+ u32 delay = datalen*4 + 16;
+
+ int copystart = 0;
+ int copylen = datalen;
+
+ if (Cnt & (1<<14))
{
- for (u32 x = 0; x < 256; x++)
+ // crop picture
+
+ int ystart = (CropStart >> 16) & 0x1FF;
+ int yend = (CropEnd >> 16) & 0x1FF;
+ if (line < ystart || line > yend)
{
- u16* px = (u16*)&FrameBuffer[((y*256) + x) * 2];
+ if (!CurCamera->TransferDone())
+ NDS::ScheduleEvent(NDS::Event_DSi_CamTransfer, false, delay, TransferScanline, line+1);
- if ((x & 0x8) ^ (y & 0x8))
- *px = 0x8000;
- else
- *px = 0xFC00 | ((y >> 3) << 5);
+ return;
}
+
+ int xstart = (CropStart >> 1) & 0x1FF;
+ int xend = (CropEnd >> 1) & 0x1FF;
+
+ copystart = xstart;
+ copylen = xend+1 - xstart;
+
+ if ((copystart + copylen) > datalen)
+ copylen = datalen - copystart;
+ if (copylen < 0)
+ copylen = 0;
}
-}
-void DSi_Camera::Transfer(u32 pos)
-{
- u32 numscan = (Cnt & 0x000F) + 1;
- u32 numpix = numscan * 256; // CHECKME
-
- // TODO: present data
- //printf("CAM TRANSFER POS=%d/%d\n", pos, 0x6000*2);
-
- DSi::CheckNDMAs(0, 0x0B);
-
- pos += numpix;
- if (pos >= 0x6000*2) // HACK
+ if (copylen > maxlen)
{
- // transfer done
+ copylen = maxlen;
+ Cnt |= (1<<4);
+ }
+
+ if (Cnt & (1<<13))
+ {
+ // convert to RGB
+
+ for (u32 i = 0; i < copylen; i++)
+ {
+ u32 val = tmpbuf[copystart + i];
+
+ 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 = (r1 >> 3) | ((g1 >> 3) << 5) | ((b1 >> 3) << 10) | 0x8000;
+ u32 col2 = (r2 >> 3) | ((g2 >> 3) << 5) | ((b2 >> 3) << 10) | 0x8000;
+
+ dstbuf[i] = col1 | (col2 << 16);
+ }
}
else
{
- // keep going
+ // return raw data
- // TODO: must be tweaked such that each block has enough time to transfer
- u32 delay = numpix*2 + 16;
-
- NDS::ScheduleEvent(NDS::Event_DSi_CamTransfer, false, delay, Transfer, pos);
+ memcpy(dstbuf, &tmpbuf[copystart], copylen*sizeof(u32));
}
+
+ u32 numscan = Cnt & 0x000F;
+ if (BufferNumLines >= numscan)
+ {
+ BufferReadPos = 0; // checkme
+ BufferWritePos = 0;
+ BufferNumLines = 0;
+ DSi::CheckNDMAs(0, 0x0B);
+ }
+ else
+ {
+ BufferWritePos += copylen;
+ if (BufferWritePos > 512) BufferWritePos = 512;
+ BufferNumLines++;
+ }
+
+ if (CurCamera->TransferDone())
+ return;
+
+ NDS::ScheduleEvent(NDS::Event_DSi_CamTransfer, false, delay, TransferScanline, line+1);
}
-DSi_Camera::DSi_Camera(u32 num)
+u8 Read8(u32 addr)
+{
+ //
+
+ printf("unknown DSi cam read8 %08X\n", addr);
+ return 0;
+}
+
+u16 Read16(u32 addr)
+{
+ switch (addr)
+ {
+ case 0x04004200: return ModuleCnt;
+ case 0x04004202: return Cnt;
+ }
+
+ printf("unknown DSi cam read16 %08X\n", addr);
+ return 0;
+}
+
+u32 Read32(u32 addr)
+{
+ switch (addr)
+ {
+ case 0x04004204:
+ {
+ u32 ret = DataBuffer[BufferReadPos];
+ if (Cnt & (1<<15))
+ {
+ if (BufferReadPos < 511)
+ BufferReadPos++;
+ // CHECKME!!!!
+ // also presumably we should set bit4 in Cnt if there's no new data to be read
+ }
+
+ return ret;
+ }
+
+ case 0x04004210: return CropStart;
+ case 0x04004214: return CropEnd;
+ }
+
+ printf("unknown DSi cam read32 %08X\n", addr);
+ return 0;
+}
+
+void Write8(u32 addr, u8 val)
+{
+ //
+
+ printf("unknown DSi cam write8 %08X %02X\n", addr, val);
+}
+
+void Write16(u32 addr, u16 val)
+{
+ switch (addr)
+ {
+ case 0x04004200:
+ {
+ u16 oldcnt = ModuleCnt;
+ ModuleCnt = val;
+
+ if ((ModuleCnt & (1<<1)) && !(oldcnt & (1<<1)))
+ {
+ // reset shit to zero
+ // CHECKME
+
+ Cnt = 0;
+ }
+
+ if ((ModuleCnt & (1<<5)) && !(oldcnt & (1<<5)))
+ {
+ // TODO: reset I2C??
+ }
+ }
+ return;
+
+ case 0x04004202:
+ {
+ // TODO: during a transfer, clearing bit15 does not reflect immediately
+ // maybe it needs to finish the trasnfer or atleast the current block
+
+ // checkme
+ u16 oldmask;
+ if (Cnt & 0x8000)
+ {
+ val &= 0x8F20;
+ oldmask = 0x601F;
+ }
+ else
+ {
+ val &= 0xEF2F;
+ oldmask = 0x0010;
+ }
+
+ Cnt = (Cnt & oldmask) | (val & ~0x0020);
+ if (val & (1<<5))
+ {
+ Cnt &= ~(1<<4);
+ BufferReadPos = 0;
+ BufferWritePos = 0;
+ }
+
+ if ((val & (1<<15)) && !(Cnt & (1<<15)))
+ {
+ // start transfer
+ //DSi::CheckNDMAs(0, 0x0B);
+ }
+ }
+ return;
+
+ case 0x04004210:
+ if (Cnt & (1<<15)) return;
+ CropStart = (CropStart & 0x01FF0000) | (val & 0x03FE);
+ return;
+ case 0x04004212:
+ if (Cnt & (1<<15)) return;
+ CropStart = (CropStart & 0x03FE) | ((val & 0x01FF) << 16);
+ return;
+ case 0x04004214:
+ if (Cnt & (1<<15)) return;
+ CropEnd = (CropEnd & 0x01FF0000) | (val & 0x03FE);
+ return;
+ case 0x04004216:
+ if (Cnt & (1<<15)) return;
+ CropEnd = (CropEnd & 0x03FE) | ((val & 0x01FF) << 16);
+ return;
+ }
+
+ printf("unknown DSi cam write16 %08X %04X\n", addr, val);
+}
+
+void Write32(u32 addr, u32 val)
+{
+ switch (addr)
+ {
+ case 0x04004210:
+ if (Cnt & (1<<15)) return;
+ CropStart = val & 0x01FF03FE;
+ return;
+ case 0x04004214:
+ if (Cnt & (1<<15)) return;
+ CropEnd = val & 0x01FF03FE;
+ return;
+ }
+
+ printf("unknown DSi cam write32 %08X %08X\n", addr, val);
+}
+
+
+
+Camera::Camera(u32 num)
{
Num = num;
}
-DSi_Camera::~DSi_Camera()
+Camera::~Camera()
{
}
-void DSi_Camera::DoCamSavestate(Savestate* file)
+void Camera::DoSavestate(Savestate* file)
{
char magic[5] = "CAMx";
magic[3] = '0' + Num;
@@ -185,12 +413,10 @@ void DSi_Camera::DoCamSavestate(Savestate* file)
file->Var16(&MiscCnt);
file->Var16(&MCUAddr);
- // TODO: MCUData??
-
file->VarArray(MCURegs, 0x8000);
}
-void DSi_Camera::ResetCam()
+void Camera::Reset()
{
DataPos = 0;
RegAddr = 0;
@@ -202,9 +428,18 @@ void DSi_Camera::ResetCam()
ClocksCnt = 0;
StandbyCnt = 0x4029; // checkme
MiscCnt = 0;
+
+ MCUAddr = 0;
+ memset(MCURegs, 0, 0x8000);
+
+ // default state is preview mode (checkme)
+ MCURegs[0x2104] = 3;
+
+ TransferY = 0;
+ memset(FrameBuffer, 0, (640*480/2)*sizeof(u32));
}
-bool DSi_Camera::IsActivated()
+bool Camera::IsActivated()
{
if (StandbyCnt & (1<<14)) return false; // standby
if (!(MiscCnt & (1<<9))) return false; // data transfer not enabled
@@ -213,31 +448,99 @@ bool DSi_Camera::IsActivated()
}
-void DSi_Camera::I2C_Start()
+void Camera::StartTransfer()
{
-}
+ TransferY = 0;
-u8 DSi_Camera::I2C_Read(bool last)
-{
- u8 ret;
-
- if (DataPos < 2)
+ u8 state = MCURegs[0x2104];
+ if (state == 3) // preview
{
- printf("DSi_Camera: WHAT??\n");
- ret = 0;
+ FrameWidth = *(u16*)&MCURegs[0x2703];
+ FrameHeight = *(u16*)&MCURegs[0x2705];
+ FrameReadMode = *(u16*)&MCURegs[0x2717];
+ FrameFormat = *(u16*)&MCURegs[0x2755];
+ }
+ else if (state == 7) // capture
+ {
+ FrameWidth = *(u16*)&MCURegs[0x2707];
+ FrameHeight = *(u16*)&MCURegs[0x2709];
+ FrameReadMode = *(u16*)&MCURegs[0x272D];
+ FrameFormat = *(u16*)&MCURegs[0x2757];
}
else
{
- if (DataPos & 0x1)
- {
- ret = RegData & 0xFF;
- RegAddr += 2; // checkme
- }
- else
- {
- RegData = I2C_ReadReg(RegAddr);
- ret = RegData >> 8;
- }
+ FrameWidth = 0;
+ FrameHeight = 0;
+ FrameReadMode = 0;
+ FrameFormat = 0;
+ }
+
+ Platform::Camera_CaptureFrame(Num, FrameBuffer, 640, 480, true);
+}
+
+bool Camera::TransferDone()
+{
+ return TransferY >= FrameHeight;
+}
+
+int Camera::TransferScanline(u32* buffer, int maxlen)
+{
+ if (TransferY >= FrameHeight)
+ return 0;
+
+ if (FrameWidth > 640 || FrameHeight > 480 ||
+ FrameWidth < 2 || FrameHeight < 2 ||
+ (FrameWidth & 1))
+ {
+ // TODO work out something for these cases?
+ printf("CAM%d: invalid resolution %dx%d\n", Num, FrameWidth, FrameHeight);
+ //memset(buffer, 0, width*height*sizeof(u16));
+ return 0;
+ }
+
+ // TODO: non-YUV pixel formats and all
+
+ int retlen = FrameWidth >> 1;
+ int sy = (TransferY * 480) / FrameHeight;
+ if (FrameReadMode & (1<<1))
+ sy = 479 - sy;
+
+ for (int dx = 0; dx < retlen; dx++)
+ {
+ if (dx >= maxlen) break;
+
+ int sx = (dx * 640) / FrameWidth;
+ if (!(FrameReadMode & (1<<0)))
+ sx = 639 - sx;
+
+ u32 pixel3 = FrameBuffer[sy*320 + sx];
+ buffer[dx] = pixel3;
+ }
+
+ TransferY++;
+
+ return retlen;
+}
+
+
+void Camera::I2C_Start()
+{
+ DataPos = 0;
+}
+
+u8 Camera::I2C_Read(bool last)
+{
+ u8 ret;
+
+ if (DataPos & 0x1)
+ {
+ ret = RegData & 0xFF;
+ RegAddr += 2; // checkme
+ }
+ else
+ {
+ RegData = I2C_ReadReg(RegAddr);
+ ret = RegData >> 8;
}
if (last) DataPos = 0;
@@ -246,7 +549,7 @@ u8 DSi_Camera::I2C_Read(bool last)
return ret;
}
-void DSi_Camera::I2C_Write(u8 val, bool last)
+void Camera::I2C_Write(u8 val, bool last)
{
if (DataPos < 2)
{
@@ -275,7 +578,7 @@ void DSi_Camera::I2C_Write(u8 val, bool last)
else DataPos++;
}
-u16 DSi_Camera::I2C_ReadReg(u16 addr)
+u16 Camera::I2C_ReadReg(u16 addr)
{
switch (addr)
{
@@ -287,6 +590,23 @@ u16 DSi_Camera::I2C_ReadReg(u16 addr)
case 0x0018: return StandbyCnt;
case 0x001A: return MiscCnt;
+ case 0x098C: return MCUAddr;
+ case 0x0990:
+ case 0x0992:
+ case 0x0994:
+ case 0x0996:
+ case 0x0998:
+ case 0x099A:
+ case 0x099C:
+ case 0x099E:
+ {
+ addr -= 0x0990;
+ u16 ret = MCU_Read((MCUAddr & 0x7FFF) + addr);
+ if (!(MCUAddr & (1<<15)))
+ ret |= (MCU_Read((MCUAddr & 0x7FFF) + addr+1) << 8);
+ return ret;
+ }
+
case 0x301A: return ((~StandbyCnt) & 0x4000) >> 12;
}
@@ -294,7 +614,7 @@ u16 DSi_Camera::I2C_ReadReg(u16 addr)
return 0;
}
-void DSi_Camera::I2C_WriteReg(u16 addr, u16 val)
+void Camera::I2C_WriteReg(u16 addr, u16 val)
{
switch (addr)
{
@@ -312,18 +632,47 @@ void DSi_Camera::I2C_WriteReg(u16 addr, u16 val)
return;
case 0x0016:
ClocksCnt = val;
- printf("ClocksCnt=%04X\n", val);
+ //printf("ClocksCnt=%04X\n", val);
return;
case 0x0018:
- // TODO: this shouldn't be instant, but uh
- val &= 0x003F;
- val |= ((val & 0x0001) << 14);
- StandbyCnt = val;
- printf("CAM%d STBCNT=%04X (%04X)\n", Num, StandbyCnt, val);
+ {
+ bool wasactive = IsActivated();
+ // TODO: this shouldn't be instant, but uh
+ val &= 0x003F;
+ val |= ((val & 0x0001) << 14);
+ StandbyCnt = val;
+ //printf("CAM%d STBCNT=%04X (%04X)\n", Num, StandbyCnt, val);
+ bool isactive = IsActivated();
+ if (isactive && !wasactive) Platform::Camera_Start(Num);
+ else if (wasactive && !isactive) Platform::Camera_Stop(Num);
+ }
return;
case 0x001A:
- MiscCnt = val & 0x0B7B;
- printf("CAM%d MISCCNT=%04X (%04X)\n", Num, MiscCnt, val);
+ {
+ bool wasactive = IsActivated();
+ MiscCnt = val & 0x0B7B;
+ //printf("CAM%d MISCCNT=%04X (%04X)\n", Num, MiscCnt, val);
+ bool isactive = IsActivated();
+ if (isactive && !wasactive) Platform::Camera_Start(Num);
+ else if (wasactive && !isactive) Platform::Camera_Stop(Num);
+ }
+ return;
+
+ case 0x098C:
+ MCUAddr = val;
+ return;
+ case 0x0990:
+ case 0x0992:
+ case 0x0994:
+ case 0x0996:
+ case 0x0998:
+ case 0x099A:
+ case 0x099C:
+ case 0x099E:
+ addr -= 0x0990;
+ MCU_Write((MCUAddr & 0x7FFF) + addr, val&0xFF);
+ if (!(MCUAddr & (1<<15)))
+ MCU_Write((MCUAddr & 0x7FFF) + addr+1, val>>8);
return;
}
@@ -331,117 +680,122 @@ void DSi_Camera::I2C_WriteReg(u16 addr, u16 val)
}
-u8 DSi_Camera::Read8(u32 addr)
-{
- //
+// TODO: not sure at all what is the accessible range
+// or if there is any overlap in the address range
- printf("unknown DSi cam read8 %08X\n", addr);
- return 0;
+u8 Camera::MCU_Read(u16 addr)
+{
+ addr &= 0x7FFF;
+
+ return MCURegs[addr];
}
-u16 DSi_Camera::Read16(u32 addr)
+void Camera::MCU_Write(u16 addr, u8 val)
{
+ addr &= 0x7FFF;
+
switch (addr)
{
- case 0x04004200: return ModuleCnt;
- case 0x04004202: return Cnt;
- }
-
- printf("unknown DSi cam read16 %08X\n", addr);
- return 0;
-}
-
-u32 DSi_Camera::Read32(u32 addr)
-{
- switch (addr)
- {
- case 0x04004204:
- {
- // TODO
- return 0xFC00801F;
- /*if (!(Cnt & (1<<15))) return 0; // CHECKME
- u32 ret = *(u32*)&FrameBuffer[TransferPos];
- TransferPos += 4;
- if (TransferPos >= FrameLength) TransferPos = 0;
- dorp += 4;
- //if (dorp >= (256*4*2))
- if (TransferPos == 0)
- {
- dorp = 0;
- Cnt &= ~(1<<4);
- }
- return ret;*/
- }
- }
-
- printf("unknown DSi cam read32 %08X\n", addr);
- return 0;
-}
-
-void DSi_Camera::Write8(u32 addr, u8 val)
-{
- //
-
- printf("unknown DSi cam write8 %08X %02X\n", addr, val);
-}
-
-void DSi_Camera::Write16(u32 addr, u16 val)
-{
- switch (addr)
- {
- case 0x04004200:
- {
- u16 oldcnt = ModuleCnt;
- ModuleCnt = val;
-
- if ((ModuleCnt & (1<<1)) && !(oldcnt & (1<<1)))
- {
- // reset shit to zero
- // CHECKME
-
- Cnt = 0;
- }
-
- if ((ModuleCnt & (1<<5)) && !(oldcnt & (1<<5)))
- {
- // TODO: reset I2C??
- }
- }
+ case 0x2103: // SEQ_CMD
+ MCURegs[addr] = 0;
+ if (val == 2) MCURegs[0x2104] = 7; // capture mode
+ else if (val == 1) MCURegs[0x2104] = 3; // preview mode
+ else if (val != 5 && val != 6)
+ printf("CAM%d: atypical SEQ_CMD %04X\n", Num, val);
return;
- case 0x04004202:
- {
- // checkme
- u16 oldmask;
- if (Cnt & 0x8000)
- {
- val &= 0x8F20;
- oldmask = 0x601F;
- }
- else
- {
- val &= 0xEF2F;
- oldmask = 0x0010;
- }
-
- Cnt = (Cnt & oldmask) | (val & ~0x0020);
- if (val & (1<<5)) Cnt &= ~(1<<4);
-
- if ((val & (1<<15)) && !(Cnt & (1<<15)))
- {
- // start transfer
- //DSi::CheckNDMAs(0, 0x0B);
- }
- }
+ case 0x2104: // SEQ_STATE, read-only
return;
}
- printf("unknown DSi cam write16 %08X %04X\n", addr, val);
+ MCURegs[addr] = val;
}
-void DSi_Camera::Write32(u32 addr, u32 val)
+
+void Camera::InputFrame(u32* data, int width, int height, bool rgb)
{
- //
+ // TODO: double-buffering?
- printf("unknown DSi cam write32 %08X %08X\n", addr, val);
+ if (width == 640 && height == 480 && !rgb)
+ {
+ memcpy(FrameBuffer, data, (640*480/2)*sizeof(u32));
+ return;
+ }
+
+ if (rgb)
+ {
+ for (int dy = 0; dy < 480; dy++)
+ {
+ int sy = (dy * height) / 480;
+
+ for (int dx = 0; dx < 640; dx+=2)
+ {
+ int sx;
+
+ sx = (dx * width) / 640;
+ u32 pixel1 = data[sy*width + sx];
+
+ sx = ((dx+1) * width) / 640;
+ u32 pixel2 = data[sy*width + 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;
+
+ FrameBuffer[(dy*640 + dx) / 2] = y1 | (u1 << 8) | (y2 << 16) | (v1 << 24);
+ }
+ }
+ }
+ else
+ {
+ for (int dy = 0; dy < 480; dy++)
+ {
+ int sy = (dy * height) / 480;
+
+ for (int dx = 0; dx < 640; dx+=2)
+ {
+ int sx = (dx * width) / 640;
+
+ FrameBuffer[(dy*640 + dx) / 2] = data[(sy*width + sx) / 2];
+ }
+ }
+ }
}
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DSi_Camera.h b/src/DSi_Camera.h
index e5926bb0..75e97f27 100644
--- a/src/DSi_Camera.h
+++ b/src/DSi_Camera.h
@@ -22,38 +22,53 @@
#include "types.h"
#include "Savestate.h"
-class DSi_Camera
+namespace DSi_CamModule
+{
+
+class Camera;
+
+extern Camera* Camera0;
+extern Camera* Camera1;
+
+bool Init();
+void DeInit();
+void Reset();
+
+void DoSavestate(Savestate* file);
+
+void IRQ(u32 param);
+
+void TransferScanline(u32 line);
+
+u8 Read8(u32 addr);
+u16 Read16(u32 addr);
+u32 Read32(u32 addr);
+void Write8(u32 addr, u8 val);
+void Write16(u32 addr, u16 val);
+void Write32(u32 addr, u32 val);
+
+class Camera
{
public:
- static bool Init();
- static void DeInit();
- static void Reset();
+ Camera(u32 num);
+ ~Camera();
- static void DoSavestate(Savestate* file);
+ void DoSavestate(Savestate* file);
- static void IRQ(u32 param);
- static void RequestFrame(u32 cam);
-
- static void Transfer(u32 pos);
-
- DSi_Camera(u32 num);
- ~DSi_Camera();
-
- void DoCamSavestate(Savestate* file);
-
- void ResetCam();
+ void Reset();
bool IsActivated();
+ void StartTransfer();
+ bool TransferDone();
+
+ // lengths in words
+ int TransferScanline(u32* buffer, int maxlen);
+
void I2C_Start();
u8 I2C_Read(bool last);
void I2C_Write(u8 val, bool last);
- static u8 Read8(u32 addr);
- static u16 Read16(u32 addr);
- static u32 Read32(u32 addr);
- static void Write8(u32 addr, u8 val);
- static void Write16(u32 addr, u16 val);
- static void Write32(u32 addr, u32 val);
+ void InputFrame(u32* data, int width, int height, bool rgb);
u32 Num;
@@ -73,20 +88,17 @@ private:
u16 MiscCnt;
u16 MCUAddr;
- u16* MCUData;
-
u8 MCURegs[0x8000];
- static u16 ModuleCnt;
- static u16 Cnt;
+ u8 MCU_Read(u16 addr);
+ void MCU_Write(u16 addr, u8 val);
- static u8 FrameBuffer[640*480*4];
- static u32 TransferPos;
- static u32 FrameLength;
+ u16 FrameWidth, FrameHeight;
+ u16 FrameReadMode, FrameFormat;
+ int TransferY;
+ u32 FrameBuffer[640*480/2]; // YUYV framebuffer, two pixels per word
};
-
-extern DSi_Camera* DSi_Camera0;
-extern DSi_Camera* DSi_Camera1;
+}
#endif // DSI_CAMERA_H
diff --git a/src/DSi_I2C.cpp b/src/DSi_I2C.cpp
index 5d13b2a6..5889bef0 100644
--- a/src/DSi_I2C.cpp
+++ b/src/DSi_I2C.cpp
@@ -169,7 +169,6 @@ u32 Device;
bool Init()
{
if (!DSi_BPTWL::Init()) return false;
- if (!DSi_Camera::Init()) return false;
return true;
}
@@ -177,7 +176,6 @@ bool Init()
void DeInit()
{
DSi_BPTWL::DeInit();
- DSi_Camera::DeInit();
}
void Reset()
@@ -188,7 +186,6 @@ void Reset()
Device = -1;
DSi_BPTWL::Reset();
- DSi_Camera::Reset();
}
void DoSavestate(Savestate* file)
@@ -200,12 +197,11 @@ void DoSavestate(Savestate* file)
file->Var32(&Device);
DSi_BPTWL::DoSavestate(file);
- // cameras are savestated from the DSi_Camera module
}
void WriteCnt(u8 val)
{
- //printf("I2C: write CNT %02X, %08X\n", val, NDS::GetPC(1));
+ //printf("I2C: write CNT %02X, %02X, %08X\n", val, Data, NDS::GetPC(1));
// TODO: check ACK flag
// TODO: transfer delay
@@ -224,8 +220,8 @@ void WriteCnt(u8 val)
switch (Device)
{
case 0x4A: Data = DSi_BPTWL::Read(islast); break;
- case 0x78: Data = DSi_Camera0->I2C_Read(islast); break;
- case 0x7A: Data = DSi_Camera1->I2C_Read(islast); break;
+ case 0x78: Data = DSi_CamModule::Camera0->I2C_Read(islast); break;
+ case 0x7A: Data = DSi_CamModule::Camera1->I2C_Read(islast); break;
case 0xA0:
case 0xE0: Data = 0xFF; break;
default:
@@ -250,8 +246,8 @@ void WriteCnt(u8 val)
switch (Device)
{
case 0x4A: DSi_BPTWL::Start(); break;
- case 0x78: DSi_Camera0->I2C_Start(); break;
- case 0x7A: DSi_Camera1->I2C_Start(); break;
+ case 0x78: DSi_CamModule::Camera0->I2C_Start(); break;
+ case 0x7A: DSi_CamModule::Camera1->I2C_Start(); break;
case 0xA0:
case 0xE0: ack = false; break;
default:
@@ -267,8 +263,8 @@ void WriteCnt(u8 val)
switch (Device)
{
case 0x4A: DSi_BPTWL::Write(Data, islast); break;
- case 0x78: DSi_Camera0->I2C_Write(Data, islast); break;
- case 0x7A: DSi_Camera1->I2C_Write(Data, islast); break;
+ case 0x78: DSi_CamModule::Camera0->I2C_Write(Data, islast); break;
+ case 0x7A: DSi_CamModule::Camera1->I2C_Write(Data, islast); break;
case 0xA0:
case 0xE0: ack = false; break;
default:
diff --git a/src/DSi_NDMA.cpp b/src/DSi_NDMA.cpp
index 3c61e676..ca834eb1 100644
--- a/src/DSi_NDMA.cpp
+++ b/src/DSi_NDMA.cpp
@@ -132,7 +132,7 @@ void DSi_NDMA::WriteCnt(u32 val)
// * microphone (ARM7 0C)
// * NDS-wifi?? (ARM7 07, likely not working)
- if (StartMode <= 0x03 || StartMode == 0x05 || (StartMode >= 0x0B && StartMode <= 0x0F) ||
+ if (StartMode <= 0x03 || StartMode == 0x05 || (StartMode >= 0x0C && StartMode <= 0x0F) ||
(StartMode >= 0x20 && StartMode <= 0x23) || StartMode == 0x25 || StartMode == 0x27 || (StartMode >= 0x2C && StartMode <= 0x2F))
printf("UNIMPLEMENTED ARM%d NDMA%d START MODE %02X, %08X->%08X LEN=%d BLK=%d CNT=%08X\n",
CPU?7:9, Num, StartMode, SrcAddr, DstAddr, TotalLength, BlockLength, Cnt);
diff --git a/src/NDS.cpp b/src/NDS.cpp
index 5262059c..4118836a 100644
--- a/src/NDS.cpp
+++ b/src/NDS.cpp
@@ -723,8 +723,8 @@ bool DoSavestate_Scheduler(Savestate* file)
DSi_SDHost::FinishRX,
DSi_SDHost::FinishTX,
DSi_NWifi::MSTimer,
- DSi_Camera::IRQ,
- DSi_Camera::Transfer,
+ DSi_CamModule::IRQ,
+ DSi_CamModule::TransferScanline,
DSi_DSP::DSPCatchUpU32,
nullptr
@@ -1282,6 +1282,21 @@ void SetLidClosed(bool closed)
}
}
+void CamInputFrame(int cam, u32* data, int width, int height, bool rgb)
+{
+ // TODO: support things like the GBA-slot camera addon
+ // whenever these are emulated
+
+ if (ConsoleType == 1)
+ {
+ switch (cam)
+ {
+ case 0: return DSi_CamModule::Camera0->InputFrame(data, width, height, rgb);
+ case 1: return DSi_CamModule::Camera1->InputFrame(data, width, height, rgb);
+ }
+ }
+}
+
void MicInputFrame(s16* data, int samples)
{
return SPI_TSC::MicInputFrame(data, samples);
@@ -2004,7 +2019,7 @@ void debug(u32 param)
fwrite(&val, 4, 1, shit);
}
fclose(shit);
- shit = fopen("debug/directboot7.bin", "wb");
+ shit = fopen("debug/camera7.bin", "wb");
for (u32 i = 0x02000000; i < 0x04000000; i+=4)
{
u32 val = DSi::ARM7Read32(i);
diff --git a/src/NDS.h b/src/NDS.h
index 8f408a03..824c2bc9 100644
--- a/src/NDS.h
+++ b/src/NDS.h
@@ -260,6 +260,7 @@ void SetKeyMask(u32 mask);
bool IsLidClosed();
void SetLidClosed(bool closed);
+void CamInputFrame(int cam, u32* data, int width, int height, bool rgb);
void MicInputFrame(s16* data, int samples);
void ScheduleEvent(u32 id, bool periodic, s32 delay, void (*func)(u32), u32 param);
diff --git a/src/Platform.h b/src/Platform.h
index 56f2c2e1..f2997ef8 100644
--- a/src/Platform.h
+++ b/src/Platform.h
@@ -147,6 +147,8 @@ void Mutex_Lock(Mutex* mutex);
void Mutex_Unlock(Mutex* mutex);
bool Mutex_TryLock(Mutex* mutex);
+void Sleep(u64 usecs);
+
// functions called when the NDS or GBA save files need to be written back to storage
// savedata and savelen are always the entire save memory buffer and its full length
@@ -177,7 +179,15 @@ void LAN_DeInit();
int LAN_SendPacket(u8* data, int len);
int LAN_RecvPacket(u8* data);
-void Sleep(u64 usecs);
+
+// interface for camera emulation
+// camera numbers:
+// 0 = DSi outer camera
+// 1 = DSi inner camera
+// other values reserved for future camera addon emulation
+void Camera_Start(int num);
+void Camera_Stop(int num);
+void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv);
}
diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt
index 5f1c490f..a8d6e4b0 100644
--- a/src/frontend/qt_sdl/CMakeLists.txt
+++ b/src/frontend/qt_sdl/CMakeLists.txt
@@ -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)
diff --git a/src/frontend/qt_sdl/CameraManager.cpp b/src/frontend/qt_sdl/CameraManager.cpp
new file mode 100644
index 00000000..23f25a65
--- /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 CameraFrameDumper::supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const
+{
+ QList 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 cameras = QMediaDevices::videoInputs();
+ for (const QCameraDevice& cam : cameras)
+ {
+ if (QString(cam.id()) == camDeviceName)
+ {
+ camDevice = new QCamera(cam);
+ break;
+ }
+ }
+
+ if (camDevice)
+ {
+ const QList 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 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;
+ }
+ }
+}
diff --git a/src/frontend/qt_sdl/CameraManager.h b/src/frontend/qt_sdl/CameraManager.h
new file mode 100644
index 00000000..36e8565d
--- /dev/null
+++ b/src/frontend/qt_sdl/CameraManager.h
@@ -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
+#if QT_VERSION >= 0x060000
+ #include
+ #include
+ #include
+ #include
+#else
+ #include
+ #include
+ #include
+#endif
+#include
+
+#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 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
diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.cpp b/src/frontend/qt_sdl/CameraSettingsDialog.cpp
new file mode 100644
index 00000000..1844e0fd
--- /dev/null
+++ b/src/frontend/qt_sdl/CameraSettingsDialog.cpp
@@ -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
+#include
+#include
+#include
+
+#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 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 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);
+}
diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.h b/src/frontend/qt_sdl/CameraSettingsDialog.h
new file mode 100644
index 00000000..8572ac42
--- /dev/null
+++ b/src/frontend/qt_sdl/CameraSettingsDialog.h
@@ -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
+#include
+
+#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
diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.ui b/src/frontend/qt_sdl/CameraSettingsDialog.ui
new file mode 100644
index 00000000..bbaf45b9
--- /dev/null
+++ b/src/frontend/qt_sdl/CameraSettingsDialog.ui
@@ -0,0 +1,170 @@
+
+
+ CameraSettingsDialog
+
+
+
+ 0
+ 0
+ 605
+ 341
+
+
+
+
+ 0
+ 0
+
+
+
+ Camera settings - melonDS
+
+
+
+ QLayout::SetFixedSize
+
+ -
+
+
-
+
+
+ Configure emulated camera:
+
+
+
+ -
+
+
+
+
+ -
+
+
-
+
+
+ Picture source
+
+
+
-
+
+
+ -
+
+
+ None (blank)
+
+
+
+ -
+
+
+ Browse...
+
+
+
+ -
+
+
+ Physical camera:
+
+
+
+ -
+
+
+ Image file:
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+ Picture settings
+
+
+
-
+
+
+ Flip horizontally
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ Preview
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ CameraSettingsDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ CameraSettingsDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp
index a8df8ee5..8b2f3d49 100644
--- a/src/frontend/qt_sdl/Config.cpp
+++ b/src/frontend/qt_sdl/Config.cpp
@@ -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}
};
diff --git a/src/frontend/qt_sdl/Config.h b/src/frontend/qt_sdl/Config.h
index cc6792c0..6ccae5f4 100644
--- a/src/frontend/qt_sdl/Config.h
+++ b/src/frontend/qt_sdl/Config.h
@@ -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();
diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp
index 68bdd3ea..f9eaf429 100644
--- a/src/frontend/qt_sdl/Platform.cpp
+++ b/src/frontend/qt_sdl/Platform.cpp
@@ -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);
+}
+
}
diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp
index 97b78fc2..a9d0c4d9 100644
--- a/src/frontend/qt_sdl/main.cpp
+++ b/src/frontend/qt_sdl/main.cpp
@@ -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();
diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h
index 5d03e54a..1977b7f7 100644
--- a/src/frontend/qt_sdl/main.h
+++ b/src/frontend/qt_sdl/main.h
@@ -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;
diff --git a/src/teakra/src/teakra.cpp b/src/teakra/src/teakra.cpp
index 76bc79fe..7b4c0285 100644
--- a/src/teakra/src/teakra.cpp
+++ b/src/teakra/src/teakra.cpp
@@ -50,7 +50,7 @@ struct Teakra::Impl {
}
void Reset() {
- shared_memory.raw.fill(0);
+ shared_memory.raw.fill(0); // BAD!!!!
miu.Reset();
apbp_from_cpu.Reset();
apbp_from_dsp.Reset();