From 3f4573574a581da849408ebc376ca97d5e82bc7d Mon Sep 17 00:00:00 2001 From: Arisotura Date: Sun, 2 Oct 2022 16:47:57 +0200 Subject: [PATCH] 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 --- .github/workflows/build-ubuntu-aarch64.yml | 2 +- .github/workflows/build-ubuntu.yml | 2 +- res/melon.plist.in | 2 + src/DSi.cpp | 17 +- src/DSi_Camera.cpp | 752 ++++++++++++++----- src/DSi_Camera.h | 76 +- src/DSi_I2C.cpp | 18 +- src/DSi_NDMA.cpp | 2 +- src/NDS.cpp | 21 +- src/NDS.h | 1 + src/Platform.h | 12 +- src/frontend/qt_sdl/CMakeLists.txt | 14 +- src/frontend/qt_sdl/CameraManager.cpp | 562 ++++++++++++++ src/frontend/qt_sdl/CameraManager.h | 133 ++++ src/frontend/qt_sdl/CameraSettingsDialog.cpp | 304 ++++++++ src/frontend/qt_sdl/CameraSettingsDialog.h | 108 +++ src/frontend/qt_sdl/CameraSettingsDialog.ui | 170 +++++ src/frontend/qt_sdl/Config.cpp | 13 + src/frontend/qt_sdl/Config.h | 10 + src/frontend/qt_sdl/Platform.cpp | 23 +- src/frontend/qt_sdl/main.cpp | 43 +- src/frontend/qt_sdl/main.h | 7 +- src/teakra/src/teakra.cpp | 2 +- 23 files changed, 2024 insertions(+), 270 deletions(-) create mode 100644 src/frontend/qt_sdl/CameraManager.cpp create mode 100644 src/frontend/qt_sdl/CameraManager.h create mode 100644 src/frontend/qt_sdl/CameraSettingsDialog.cpp create mode 100644 src/frontend/qt_sdl/CameraSettingsDialog.h create mode 100644 src/frontend/qt_sdl/CameraSettingsDialog.ui 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();