mirror of
https://github.com/melonDS-emu/melonDS.git
synced 2024-11-14 13:27:41 -07:00
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:
parent
c1c4cbc838
commit
3f4573574a
2
.github/workflows/build-ubuntu-aarch64.yml
vendored
2
.github/workflows/build-ubuntu-aarch64.yml
vendored
@ -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: |
|
||||
|
2
.github/workflows/build-ubuntu.yml
vendored
2
.github/workflows/build-ubuntu.yml
vendored
@ -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
|
||||
|
@ -22,6 +22,8 @@
|
||||
<true/>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>We need microphone access so you can use the emulated DS microphone</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Camera access is needed for emulation of the DSi's cameras</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
17
src/DSi.cpp
17
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);
|
||||
|
@ -20,17 +20,25 @@
|
||||
#include <string.h>
|
||||
#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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
21
src/NDS.cpp
21
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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
562
src/frontend/qt_sdl/CameraManager.cpp
Normal file
562
src/frontend/qt_sdl/CameraManager.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
133
src/frontend/qt_sdl/CameraManager.h
Normal file
133
src/frontend/qt_sdl/CameraManager.h
Normal 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
|
304
src/frontend/qt_sdl/CameraSettingsDialog.cpp
Normal file
304
src/frontend/qt_sdl/CameraSettingsDialog.cpp
Normal 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);
|
||||
}
|
108
src/frontend/qt_sdl/CameraSettingsDialog.h
Normal file
108
src/frontend/qt_sdl/CameraSettingsDialog.h
Normal 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
|
170
src/frontend/qt_sdl/CameraSettingsDialog.ui
Normal file
170
src/frontend/qt_sdl/CameraSettingsDialog.ui
Normal 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>
|
@ -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}
|
||||
};
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user