From c4b8ef1b920e59e636e60f8e8b38354c3f0fdd0c Mon Sep 17 00:00:00 2001 From: Jurn Wubben Date: Sat, 27 Sep 2025 17:23:41 +0200 Subject: [PATCH 1/4] bugfixes --- src/main.c | 452 +++++++++++++++++++++++++---------------------------- 1 file changed, 212 insertions(+), 240 deletions(-) diff --git a/src/main.c b/src/main.c index b964fe5..a03cea6 100644 --- a/src/main.c +++ b/src/main.c @@ -6,155 +6,152 @@ #include #include #include -#include #include #include #include #include +#include -#define WIDTH 400 -#define HEIGHT 240 -#define FB_SIZE (WIDTH * HEIGHT * 3) -#define STORAGE_DIR "sdmc:/printcam" -#define ENDPOINT_PATH STORAGE_DIR "/endpoint.txt" -#define API_ENDPOINT_DEFAULT "https://example.com/upload" -#define JPEG_QUALITY 100 +#define W 400 +#define H 240 +#define FB_BYTES (W * H * 3) +#define DIR "sdmc:/printcam" +#define EP_PATH DIR "/endpoint.txt" +#define EP_DEFAULT "https://example.com/upload" +#define JPG_QUALITY 100 +#define CAM_TIMEOUT_NS (5ULL * 1000ULL * 1000ULL * 1000ULL) -static volatile bool exitRequested = false; +static volatile bool quit = false; static Thread camThread = NULL; -static Handle ready[2] = {0, 0}; +static Handle readyEv[2] = {0, 0}; static u8 *camBuf[2] = {NULL, NULL}; -static u8 *rgbBuf = NULL; -static volatile u8 writeIdx = 0, dispIdx = 0; -static volatile bool camera_reinit = false; -static volatile int camera_index = 0; -static const u32 cam_selects[2] = {SELECT_OUT1, SELECT_IN1}; -static char api_endpoint[512] = API_ENDPOINT_DEFAULT; -static PrintConsole bottomScreen; +static u8 *dispBuf = NULL; +static volatile u8 writeIdx = 0; +static volatile u8 dispIdx = 0; +static volatile bool reinitCam = false; +static volatile int camIndex = 0; +static char endpoint[512] = EP_DEFAULT; +static PrintConsole bottom; -static bool ensure_storage_dir(void) { - if (mkdir(STORAGE_DIR, 0777) == 0) - return true; - return errno == EEXIST; -} - -static bool ensure_default_endpoint_file(void) { - FILE *f = fopen(ENDPOINT_PATH, "r"); - if (f) { - fclose(f); - return true; - } - f = fopen(ENDPOINT_PATH, "w"); - if (!f) - return false; - if (fprintf(f, "%s\n", API_ENDPOINT_DEFAULT) < 0) { - fclose(f); - return false; - } - fclose(f); - return true; -} - -static int load_endpoint_from_sd(void) { - FILE *f = fopen(ENDPOINT_PATH, "r"); - if (!f) +static int init_endpoint_config(void) { + if (mkdir(DIR, 0777) != 0 && errno != EEXIST) return -1; - if (!fgets(api_endpoint, sizeof(api_endpoint), f)) { + FILE *f = fopen(EP_PATH, "r"); + if (f != NULL) { + if (fgets(endpoint, sizeof(endpoint), f) == NULL) { + fclose(f); + return -1; + } fclose(f); + size_t n = strlen(endpoint); + while (n && (endpoint[n - 1] == '\n' || endpoint[n - 1] == '\r')) { + endpoint[--n] = '\0'; + } + if (n == 0) + return -1; + return 1; + } + if (errno != ENOENT) + return -1; + FILE *w = fopen(EP_PATH, "w"); + if (!w) + return -1; + if (fprintf(w, "%s\n", EP_DEFAULT) < 0) { + fclose(w); + unlink(EP_PATH); return -1; } - size_t len = strlen(api_endpoint); - while (len && - (api_endpoint[len - 1] == '\n' || api_endpoint[len - 1] == '\r')) { - api_endpoint[len - 1] = '\0'; - --len; - } - fclose(f); + fflush(w); + fsync(fileno(w)); + fclose(w); + strncpy(endpoint, EP_DEFAULT, sizeof(endpoint) - 1); + endpoint[sizeof(endpoint) - 1] = '\0'; return 0; } -static inline void writePix(u8 *dst, int x, int y, u8 r, u8 g, u8 b) { - int off = (x * HEIGHT + (HEIGHT - 1 - y)) * 3; - dst[off] = b; - dst[off + 1] = g; - dst[off + 2] = r; +static inline void put_pixel(u8 *d, int x, int y, u8 r, u8 g, u8 b) { + int off = (x * H + (H - 1 - y)) * 3; + d[off + 0] = b; + d[off + 1] = g; + d[off + 2] = r; } -static void blitYUYVtoRGB(const u8 *src, u8 *dst) { - for (int y = 0; y < HEIGHT; ++y) { - for (int x = 0; x < WIDTH; x += 2) { - u8 y0 = src[0], u = src[1], y1 = src[2], v = src[3]; - src += 4; - int8_t u2 = (int8_t)(u - 128), v2 = (int8_t)(v - 128); - int r = (int)(1.402f * v2), g = (int)(-0.344f * u2 - 0.714f * v2), - b = (int)(1.772f * u2); - int R = y0 + r, G = y0 + g, B = y0 + b; +static void yuyv_to_display(const u8 *s, u8 *d) { + for (int y = 0; y < H; ++y) { + for (int x = 0; x < W; x += 2) { + u8 y0 = s[0]; + u8 u = s[1]; + u8 y1 = s[2]; + u8 v = s[3]; + s += 4; + int8_t u2 = (int8_t)(u - 128); + int8_t v2 = (int8_t)(v - 128); + int rcoef = (int)(1.402f * v2); + int gcoef = (int)(-0.344f * u2 - 0.714f * v2); + int bcoef = (int)(1.772f * u2); + int R = y0 + rcoef; + int G = y0 + gcoef; + int B = y0 + bcoef; R = R < 0 ? 0 : (R > 255 ? 255 : R); G = G < 0 ? 0 : (G > 255 ? 255 : G); B = B < 0 ? 0 : (B > 255 ? 255 : B); - writePix(dst, x, y, (u8)R, (u8)G, (u8)B); - R = y1 + r; - G = y1 + g; - B = y1 + b; + put_pixel(d, x, y, (u8)R, (u8)G, (u8)B); + R = y1 + rcoef; + G = y1 + gcoef; + B = y1 + bcoef; R = R < 0 ? 0 : (R > 255 ? 255 : R); G = G < 0 ? 0 : (G > 255 ? 255 : G); B = B < 0 ? 0 : (B > 255 ? 255 : B); - writePix(dst, x + 1, y, (u8)R, (u8)G, (u8)B); + put_pixel(d, x + 1, y, (u8)R, (u8)G, (u8)B); } } } -static void convert_display_to_row_major(const u8 *disp, u8 *out) { - for (int y = 0; y < HEIGHT; ++y) { - for (int x = 0; x < WIDTH; ++x) { - int disp_off = (x * HEIGHT + (HEIGHT - 1 - y)) * 3; - int out_off = (y * WIDTH + x) * 3; - out[out_off + 0] = disp[disp_off + 0]; - out[out_off + 1] = disp[disp_off + 1]; - out[out_off + 2] = disp[disp_off + 2]; +static void display_to_row_major(const u8 *in, u8 *out) { + for (int y = 0; y < H; ++y) { + for (int x = 0; x < W; ++x) { + int di = (x * H + (H - 1 - y)) * 3; + int oi = (y * W + x) * 3; + out[oi + 0] = in[di + 0]; + out[oi + 1] = in[di + 1]; + out[oi + 2] = in[di + 2]; } } } -static int encode_bgr_to_jpeg_file(const u8 *in_bgr, int width, int height, - int quality, const char *path) { +static int write_jpeg(const u8 *bgr, const char *path) { struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; - FILE *f = NULL; - unsigned char *row = NULL; - int row_stride = width * 3; - if (!in_bgr || !path) - return -1; - f = fopen(path, "wb"); + FILE *f = fopen(path, "wb"); if (!f) return -1; cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); jpeg_stdio_dest(&cinfo, f); - cinfo.image_width = width; - cinfo.image_height = height; + cinfo.image_width = W; + cinfo.image_height = H; cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; jpeg_set_defaults(&cinfo); - jpeg_set_quality(&cinfo, quality, TRUE); + jpeg_set_quality(&cinfo, JPG_QUALITY, TRUE); jpeg_start_compress(&cinfo, TRUE); - row = malloc(row_stride); + int rowstride = W * 3; + u8 *row = malloc(rowstride); if (!row) { - jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); fclose(f); return -1; } while (cinfo.next_scanline < cinfo.image_height) { - const u8 *src = in_bgr + (size_t)cinfo.next_scanline * (size_t)row_stride; - for (int x = 0; x < width; ++x) { + const u8 *src = bgr + (size_t)cinfo.next_scanline * rowstride; + for (int x = 0; x < W; ++x) { row[x * 3 + 0] = src[x * 3 + 2]; row[x * 3 + 1] = src[x * 3 + 1]; row[x * 3 + 2] = src[x * 3 + 0]; } - JSAMPROW row_pointer[1]; - row_pointer[0] = row; - jpeg_write_scanlines(&cinfo, row_pointer, 1); + JSAMPROW rp[1]; + rp[0] = row; + jpeg_write_scanlines(&cinfo, rp, 1); } free(row); jpeg_finish_compress(&cinfo); @@ -163,140 +160,115 @@ static int encode_bgr_to_jpeg_file(const u8 *in_bgr, int width, int height, return 0; } -Result http_post_file(const char *url, const char *filename) { - Result ret = 0; - httpcContext context; - u32 statuscode = 0; - FILE *f = fopen(filename, "rb"); +static Result post_multipart(const char *url, const char *filepath) { + FILE *f = fopen(filepath, "rb"); if (!f) return -1; if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return -1; } - long fsize = ftell(f); - if (fsize < 0) { + long sz = ftell(f); + if (sz < 0) { fclose(f); return -1; } rewind(f); - u8 *filebuf = malloc((size_t)fsize); + u8 *filebuf = malloc((size_t)sz); if (!filebuf) { fclose(f); return -1; } - if (fread(filebuf, 1, (size_t)fsize, f) != (size_t)fsize) { + if (fread(filebuf, 1, (size_t)sz, f) != (size_t)sz) { free(filebuf); fclose(f); return -1; } fclose(f); - char boundary[64]; - srand((unsigned)time(NULL)); - snprintf(boundary, sizeof(boundary), "----printcam%08x", rand()); - const char *filename_only = filename; - const char *p = strrchr(filename, '/'); - if (p) - filename_only = p + 1; - char preamble[512]; - int pre_len = snprintf( - preamble, sizeof(preamble), + char boundary[48]; + snprintf(boundary, sizeof(boundary), "----printcam%08x", (unsigned)rand()); + const char *fn = strrchr(filepath, '/'); + if (fn) + fn++; + else + fn = filepath; + char head[256]; + int headlen = snprintf( + head, sizeof(head), "--%s\r\n" "Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n" "Content-Type: image/jpeg\r\n\r\n", - boundary, filename_only); - const char *epilogue_fmt = "\r\n--%s--\r\n"; - int epilogue_len = snprintf(NULL, 0, epilogue_fmt, boundary); - char *epilogue = malloc((size_t)epilogue_len + 1); - if (!epilogue) { + boundary, fn); + char tail[64]; + int taillen = snprintf(tail, sizeof(tail), "\r\n--%s--\r\n", boundary); + size_t total = (size_t)headlen + (size_t)sz + (size_t)taillen; + u8 *buf = malloc(total); + if (!buf) { free(filebuf); return -1; } - snprintf(epilogue, (size_t)epilogue_len + 1, epilogue_fmt, boundary); - size_t multipart_size = - (size_t)pre_len + (size_t)fsize + (size_t)epilogue_len; - u8 *multipart_buf = malloc(multipart_size); - if (!multipart_buf) { - free(filebuf); - free(epilogue); - return -1; - } - memcpy(multipart_buf, preamble, (size_t)pre_len); - memcpy(multipart_buf + pre_len, filebuf, (size_t)fsize); - memcpy(multipart_buf + pre_len + fsize, epilogue, (size_t)epilogue_len); + memcpy(buf, head, (size_t)headlen); + memcpy(buf + headlen, filebuf, (size_t)sz); + memcpy(buf + headlen + sz, tail, (size_t)taillen); free(filebuf); - free(epilogue); - ret = httpcOpenContext(&context, HTTPC_METHOD_POST, url, 0); - if (ret != 0) { - free(multipart_buf); - return ret; + httpcContext ctx; + Result r = httpcOpenContext(&ctx, HTTPC_METHOD_POST, url, 0); + if (r != 0) { + free(buf); + return r; } - httpcSetSSLOpt(&context, SSLCOPT_DisableVerify); - httpcSetKeepAlive(&context, HTTPC_KEEPALIVE_ENABLED); - char content_type_header[128]; - snprintf(content_type_header, sizeof(content_type_header), - "multipart/form-data; boundary=%s", boundary); - httpcAddRequestHeaderField(&context, "User-Agent", "printcam/1.0"); - httpcAddRequestHeaderField(&context, "Content-Type", content_type_header); - ret = - httpcAddPostDataRaw(&context, (u32 *)multipart_buf, (u32)multipart_size); - if (ret != 0) { - httpcCloseContext(&context); - free(multipart_buf); - return ret; + httpcSetSSLOpt(&ctx, SSLCOPT_DisableVerify); + httpcSetKeepAlive(&ctx, HTTPC_KEEPALIVE_ENABLED); + char ct[128]; + snprintf(ct, sizeof(ct), "multipart/form-data; boundary=%s", boundary); + httpcAddRequestHeaderField(&ctx, "User-Agent", "printcam/1.0"); + httpcAddRequestHeaderField(&ctx, "Content-Type", ct); + r = httpcAddPostDataRaw(&ctx, (u32 *)buf, (u32)total); + if (r != 0) { + httpcCloseContext(&ctx); + free(buf); + return r; } - ret = httpcBeginRequest(&context); - if (ret != 0) { - httpcCloseContext(&context); - free(multipart_buf); - return ret; - } - ret = httpcGetResponseStatusCode(&context, &statuscode); - if (ret == 0) { + r = httpcBeginRequest(&ctx); + if (r == 0) { + u32 sc = 0; + httpcGetResponseStatusCode(&ctx, &sc); u32 downloaded = 0; u32 contentsize = 0; - while (true) { - ret = httpcGetDownloadSizeState(&context, &downloaded, &contentsize); - if (ret != 0) - break; - if (contentsize == 0) - break; - if (downloaded >= contentsize) - break; - u32 remaining = contentsize - downloaded; - u32 toread = remaining > 4096 ? 4096 : remaining; + while (httpcGetDownloadSizeState(&ctx, &downloaded, &contentsize) == 0 && + contentsize != 0 && downloaded < contentsize) { + u32 rem = contentsize - downloaded; + u32 toread = rem > 4096 ? 4096 : rem; u8 tmp[4096]; - ret = httpcReceiveData(&context, tmp, toread); - if (ret != 0) + if (httpcReceiveData(&ctx, tmp, toread) != 0) break; } } - httpcCloseContext(&context); - free(multipart_buf); - return ret; + httpcCloseContext(&ctx); + free(buf); + return r; } -static void cameraThreadFunc(void *arg) { +static void cam_thread(void *arg) { acInit(); camInit(); - u32 sel = cam_selects[camera_index]; + u32 sel = camIndex ? SELECT_IN1 : SELECT_OUT1; CAMU_SetSize(sel, SIZE_CTR_TOP_LCD, CONTEXT_A); CAMU_SetOutputFormat(sel, OUTPUT_YUV_422, CONTEXT_A); CAMU_SetFrameRate(sel, FRAME_RATE_30); CAMU_SetNoiseFilter(sel, true); CAMU_SetAutoExposure(sel, true); CAMU_SetAutoWhiteBalance(sel, true); - u32 maxBytes; - CAMU_GetMaxBytes(&maxBytes, WIDTH, HEIGHT); - CAMU_SetTransferBytes(PORT_BOTH, maxBytes, WIDTH, HEIGHT); + u32 max; + CAMU_GetMaxBytes(&max, W, H); + CAMU_SetTransferBytes(PORT_BOTH, max, W, H); CAMU_Activate(sel); - Handle ev = 0; CAMU_ClearBuffer(PORT_BOTH); CAMU_SynchronizeVsyncTiming(SELECT_OUT1, SELECT_OUT2); CAMU_StartCapture(PORT_BOTH); - while (!exitRequested) { - if (camera_reinit) { - sel = cam_selects[camera_index]; + while (!quit) { + if (reinitCam) { + sel = camIndex ? SELECT_IN1 : SELECT_OUT1; CAMU_StopCapture(PORT_BOTH); CAMU_ClearBuffer(PORT_BOTH); CAMU_SetSize(sel, SIZE_CTR_TOP_LCD, CONTEXT_A); @@ -305,16 +277,17 @@ static void cameraThreadFunc(void *arg) { CAMU_SetNoiseFilter(sel, true); CAMU_SetAutoExposure(sel, true); CAMU_SetAutoWhiteBalance(sel, true); - CAMU_SetTransferBytes(PORT_BOTH, maxBytes, WIDTH, HEIGHT); + CAMU_SetTransferBytes(PORT_BOTH, max, W, H); CAMU_Activate(sel); CAMU_StartCapture(PORT_BOTH); - camera_reinit = false; + reinitCam = false; } - CAMU_SetReceiving(&ev, camBuf[writeIdx], PORT_CAM1, WIDTH * HEIGHT * 2, - maxBytes); - svcWaitSynchronization(ev, U64_MAX); + Handle ev = 0; + int recv = camIndex ? PORT_CAM2 : PORT_CAM1; + CAMU_SetReceiving(&ev, camBuf[writeIdx], recv, W * H * 2, max); + svcWaitSynchronization(ev, CAM_TIMEOUT_NS); svcCloseHandle(ev); - svcSignalEvent(ready[writeIdx]); + svcSignalEvent(readyEv[writeIdx]); writeIdx ^= 1; } CAMU_StopCapture(PORT_BOTH); @@ -323,91 +296,90 @@ static void cameraThreadFunc(void *arg) { acExit(); } -int main(int argc, char *argv[]) { +int main(int argc, char **argv) { gfxInitDefault(); httpcInit(4 * 1024 * 1024); - consoleInit(GFX_BOTTOM, &bottomScreen); - consoleSelect(&bottomScreen); - printf("printcam\nL/R=take X=switch-front/back Y=reload-url START=exit\n"); - if (!ensure_storage_dir()) - printf("Failed to create %s\n", STORAGE_DIR); - if (!ensure_default_endpoint_file()) - printf("Failed to create default endpoint file\n"); - if (load_endpoint_from_sd() == 0) - printf("Endpoint: %s\n", api_endpoint); - camBuf[0] = (u8 *)memalign(0x40, WIDTH * HEIGHT * 2); - camBuf[1] = (u8 *)memalign(0x40, WIDTH * HEIGHT * 2); - rgbBuf = (u8 *)memalign(0x40, FB_SIZE); - u8 *linearBuf = malloc(FB_SIZE); - if (!camBuf[0] || !camBuf[1] || !rgbBuf || !linearBuf) { - printf("Out of memory\n"); - goto cleanup; + consoleInit(GFX_BOTTOM, &bottom); + consoleSelect(&bottom); + printf("printcam\nL/R=take X=switch Y=reload START=exit\n"); + int rc = init_endpoint_config(); + if (rc == 1) + printf("Endpoint loaded: %s\n", endpoint); + else if (rc == 0) + printf("Endpoint created: %s\n", endpoint); + else + printf("Endpoint init failed errno=%d %s\n", errno, strerror(errno)); + camBuf[0] = memalign(0x40, W * H * 2); + camBuf[1] = memalign(0x40, W * H * 2); + dispBuf = memalign(0x40, FB_BYTES); + u8 *linear = malloc(FB_BYTES); + if (!camBuf[0] || !camBuf[1] || !dispBuf || !linear) { + printf("OOM\n"); + goto end; } - svcCreateEvent(&ready[0], RESET_ONESHOT); - svcCreateEvent(&ready[1], RESET_ONESHOT); + svcCreateEvent(&readyEv[0], RESET_ONESHOT); + svcCreateEvent(&readyEv[1], RESET_ONESHOT); gfxSetDoubleBuffering(GFX_TOP, true); - camThread = threadCreate(cameraThreadFunc, NULL, 0x1000, 0x2F, -2, false); + camThread = threadCreate(cam_thread, NULL, 0x1000, 0x2F, -2, false); while (aptMainLoop()) { hidScanInput(); u32 k = hidKeysDown(); if (k & KEY_START) break; if (k & KEY_X) { - camera_index ^= 1; - camera_reinit = true; - printf("Switched to %s\n", - camera_index ? "front/input1" : "back/output1"); + camIndex ^= 1; + reinitCam = true; + printf("Switched to %s\n", camIndex ? "front" : "back"); } if (k & KEY_Y) { - if (load_endpoint_from_sd() == 0) - printf("Reloaded endpoint: %s\n", api_endpoint); + int r = init_endpoint_config(); + if (r >= 0) + printf("Reload=%d endpoint=%s\n", r, endpoint); else - printf("Failed to reload endpoint\n"); + printf("Reload failed errno=%d %s\n", errno, strerror(errno)); } if (k & (KEY_L | KEY_R)) { - consoleSelect(&bottomScreen); - printf("Snapshot requested\n"); - convert_display_to_row_major(rgbBuf, linearBuf); - char jpgpath[128]; + consoleSelect(&bottom); + printf("Snapshot\n"); + display_to_row_major(dispBuf, linear); + char path[128]; time_t t = time(NULL); - strftime(jpgpath, sizeof(jpgpath), STORAGE_DIR "/%Y%m%d_%H%M%S.jpg", - localtime(&t)); - if (encode_bgr_to_jpeg_file(linearBuf, WIDTH, HEIGHT, JPEG_QUALITY, - jpgpath) == 0) { - printf("Wrote %s\n", jpgpath); - Result res = http_post_file(api_endpoint, jpgpath); - printf("http_post_file returned: %" PRIx32 "\n", (u32)res); + strftime(path, sizeof(path), DIR "/%Y%m%d_%H%M%S.jpg", localtime(&t)); + if (write_jpeg(linear, path) == 0) { + printf("Wrote %s\n", path); + Result r = post_multipart(endpoint, path); + printf("upload: 0x%" PRIx32 "\n", (u32)r); } else { - printf("JPEG write failed\n"); + printf("JPEG failed\n"); } } gspWaitForVBlank(); - if (svcWaitSynchronization(ready[dispIdx], 0) == 0) { - blitYUYVtoRGB(camBuf[dispIdx], rgbBuf); + if (svcWaitSynchronization(readyEv[dispIdx], 0) == 0) { + yuyv_to_display(camBuf[dispIdx], dispBuf); dispIdx ^= 1; } u8 *fb = gfxGetFramebuffer(GFX_TOP, GFX_LEFT, NULL, NULL); - memcpy(fb, rgbBuf, FB_SIZE); + memcpy(fb, dispBuf, FB_BYTES); gfxSwapBuffersGpu(); } -cleanup: - exitRequested = true; +end: + quit = true; if (camThread) threadJoin(camThread, U64_MAX); if (camThread) threadFree(camThread); - if (ready[0]) - svcCloseHandle(ready[0]); - if (ready[1]) - svcCloseHandle(ready[1]); + if (readyEv[0]) + svcCloseHandle(readyEv[0]); + if (readyEv[1]) + svcCloseHandle(readyEv[1]); if (camBuf[0]) free(camBuf[0]); if (camBuf[1]) free(camBuf[1]); - if (rgbBuf) - free(rgbBuf); - if (linearBuf) - free(linearBuf); + if (dispBuf) + free(dispBuf); + if (linear) + free(linear); httpcExit(); gfxExit(); return 0; From 5715d608831c8b9f1101568efbdca0cf32b7ebc9 Mon Sep 17 00:00:00 2001 From: Jurn Wubben Date: Sat, 27 Sep 2025 23:26:03 +0200 Subject: [PATCH 2/4] Added makerom to flake --- flake.lock | 17 +++++++++++++++++ flake.nix | 54 ++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/flake.lock b/flake.lock index c17892d..dda97c7 100644 --- a/flake.lock +++ b/flake.lock @@ -55,6 +55,22 @@ "type": "github" } }, + "makerom": { + "flake": false, + "locked": { + "lastModified": 1756011173, + "narHash": "sha256-nPRUwJwHA6cwGb0nocdCde7vfgxoRJF9T90mgEOMYu4=", + "owner": "3DSGuy", + "repo": "Project_CTR", + "rev": "f55e0fbc00f12ffb77f7af869f93472bed320ac6", + "type": "github" + }, + "original": { + "owner": "3DSGuy", + "repo": "Project_CTR", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1751949589, @@ -91,6 +107,7 @@ "inputs": { "devkitNix": "devkitNix", "flake-utils": "flake-utils_2", + "makerom": "makerom", "nixpkgs": "nixpkgs_2" } }, diff --git a/flake.nix b/flake.nix index b63d103..084b2e5 100644 --- a/flake.nix +++ b/flake.nix @@ -3,6 +3,10 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; flake-utils.url = "github:numtide/flake-utils"; devkitNix.url = "github:bandithedoge/devkitNix"; + makerom = { + flake = false; + url = "github:/3DSGuy/Project_CTR"; + }; }; outputs = { @@ -10,6 +14,7 @@ nixpkgs, flake-utils, devkitNix, + makerom, ... }: flake-utils.lib.eachDefaultSystem ( @@ -23,18 +28,47 @@ pkgs.mkShell.override { stdenv = pkgs.devkitNix.stdenvARM; } { - packages = with pkgs; [imagemagick]; + packages = [self.packages.${pkgs.system}.makerom]; }; - packages.default = pkgs.devkitNix.stdenvARM.mkDerivation { - name = "somding"; - src = ./.; - buildInputs = [pkgs.imagemagick pkgs.which]; + packages = { + default = pkgs.devkitNix.stdenvARM.mkDerivation { + name = "print3d"; + src = ./.; + buildInputs = [pkgs.imagemagick pkgs.which]; - # makeFlags = ["TARGET=example"]; - installPhase = '' - mkdir $out - cp 3ds.3dsx $out - ''; + # makeFlags = ["TARGET=example"]; + installPhase = '' + mkdir $out + cp 3ds.3dsx $out + ''; + }; + makerom = + pkgs.stdenv.mkDerivation + { + name = "makerom"; + src = "${makerom}/makerom"; + + buildInputs = let + pkgDep = name: + pkgs.stdenv.mkDerivation { + inherit name; + src = "${makerom}/makerom/deps/${name}"; + + installPhase = '' + mkdir -p $out/lib + cp bin/${name}.a $out/lib + ''; + }; + in [ + (pkgDep "libblz") + (pkgDep "libmbedtls") + (pkgDep "libyaml") + ]; + installPhase = '' + mkdir -p $out/bin + cp bin/makerom $out/bin + ''; + }; }; } ); From 1b7592e9b7a81e40fe09dbef54a0ebb220aee5eb Mon Sep 17 00:00:00 2001 From: Jurn Wubben Date: Sun, 28 Sep 2025 12:53:48 +0200 Subject: [PATCH 3/4] [BROKEN] playback works, taking a pic doesn't and it's 15fps --- Makefile | 2 +- src/main.c | 603 +++++++++++++++++++---------------------------------- 2 files changed, 219 insertions(+), 386 deletions(-) diff --git a/Makefile b/Makefile index 9cfdee9..70caab2 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++14 ASFLAGS := -g $(ARCH) LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) -LIBS := -lbox2d -lcitro2d -lcitro3d -ljpeg -lctru -lm +LIBS := -lbox2d -lcitro2d -lcitro3d -lctru -lm -ljpeg -lcurl #--------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level containing diff --git a/src/main.c b/src/main.c index b964fe5..cd034fe 100644 --- a/src/main.c +++ b/src/main.c @@ -1,414 +1,247 @@ -#include - #include <3ds.h> -#include -#include -#include -#include #include -#include +#include +#include +#include #include #include -#include -#include +#include +#include +#include + +#define WAIT_TIMEOUT 1000000000ULL #define WIDTH 400 #define HEIGHT 240 -#define FB_SIZE (WIDTH * HEIGHT * 3) -#define STORAGE_DIR "sdmc:/printcam" -#define ENDPOINT_PATH STORAGE_DIR "/endpoint.txt" -#define API_ENDPOINT_DEFAULT "https://example.com/upload" -#define JPEG_QUALITY 100 -static volatile bool exitRequested = false; -static Thread camThread = NULL; -static Handle ready[2] = {0, 0}; -static u8 *camBuf[2] = {NULL, NULL}; -static u8 *rgbBuf = NULL; -static volatile u8 writeIdx = 0, dispIdx = 0; -static volatile bool camera_reinit = false; -static volatile int camera_index = 0; -static const u32 cam_selects[2] = {SELECT_OUT1, SELECT_IN1}; -static char api_endpoint[512] = API_ENDPOINT_DEFAULT; -static PrintConsole bottomScreen; +#define PIC_WIDTH 640 +#define PIC_HEIGHT 480 -static bool ensure_storage_dir(void) { - if (mkdir(STORAGE_DIR, 0777) == 0) - return true; - return errno == EEXIST; +#define SCREEN_SIZE WIDTH *HEIGHT * 2 +#define PIC_SIZE PIC_WIDTH *PIC_HEIGHT * 2 +#define BUF_SIZE SCREEN_SIZE * 2 + +#define CAMS SELECT_IN1_OUT1 +#define PORT PORT_CAM1 + +static jmp_buf exitJmp; +bool activeCam = false; + +Result getActiveSelect() { return activeCam ? SELECT_IN1 : SELECT_OUT1; }; + +inline void clearScreen(void) { + u8 *frame = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, NULL, NULL); + memset(frame, 0, 320 * 240 * 3); } -static bool ensure_default_endpoint_file(void) { - FILE *f = fopen(ENDPOINT_PATH, "r"); - if (f) { - fclose(f); - return true; - } - f = fopen(ENDPOINT_PATH, "w"); - if (!f) - return false; - if (fprintf(f, "%s\n", API_ENDPOINT_DEFAULT) < 0) { - fclose(f); - return false; - } - fclose(f); - return true; -} +void hang(char *message) { + clearScreen(); + printf("%s", message); + printf("Press start to exit"); -static int load_endpoint_from_sd(void) { - FILE *f = fopen(ENDPOINT_PATH, "r"); - if (!f) - return -1; - if (!fgets(api_endpoint, sizeof(api_endpoint), f)) { - fclose(f); - return -1; - } - size_t len = strlen(api_endpoint); - while (len && - (api_endpoint[len - 1] == '\n' || api_endpoint[len - 1] == '\r')) { - api_endpoint[len - 1] = '\0'; - --len; - } - fclose(f); - return 0; -} + while (aptMainLoop()) { + hidScanInput(); -static inline void writePix(u8 *dst, int x, int y, u8 r, u8 g, u8 b) { - int off = (x * HEIGHT + (HEIGHT - 1 - y)) * 3; - dst[off] = b; - dst[off + 1] = g; - dst[off + 2] = r; -} - -static void blitYUYVtoRGB(const u8 *src, u8 *dst) { - for (int y = 0; y < HEIGHT; ++y) { - for (int x = 0; x < WIDTH; x += 2) { - u8 y0 = src[0], u = src[1], y1 = src[2], v = src[3]; - src += 4; - int8_t u2 = (int8_t)(u - 128), v2 = (int8_t)(v - 128); - int r = (int)(1.402f * v2), g = (int)(-0.344f * u2 - 0.714f * v2), - b = (int)(1.772f * u2); - int R = y0 + r, G = y0 + g, B = y0 + b; - R = R < 0 ? 0 : (R > 255 ? 255 : R); - G = G < 0 ? 0 : (G > 255 ? 255 : G); - B = B < 0 ? 0 : (B > 255 ? 255 : B); - writePix(dst, x, y, (u8)R, (u8)G, (u8)B); - R = y1 + r; - G = y1 + g; - B = y1 + b; - R = R < 0 ? 0 : (R > 255 ? 255 : R); - G = G < 0 ? 0 : (G > 255 ? 255 : G); - B = B < 0 ? 0 : (B > 255 ? 255 : B); - writePix(dst, x + 1, y, (u8)R, (u8)G, (u8)B); - } + u32 kHeld = hidKeysHeld(); + if (kHeld & KEY_START) + longjmp(exitJmp, 1); } } -static void convert_display_to_row_major(const u8 *disp, u8 *out) { - for (int y = 0; y < HEIGHT; ++y) { - for (int x = 0; x < WIDTH; ++x) { - int disp_off = (x * HEIGHT + (HEIGHT - 1 - y)) * 3; - int out_off = (y * WIDTH + x) * 3; - out[out_off + 0] = disp[disp_off + 0]; - out[out_off + 1] = disp[disp_off + 1]; - out[out_off + 2] = disp[disp_off + 2]; - } - } -} - -static int encode_bgr_to_jpeg_file(const u8 *in_bgr, int width, int height, - int quality, const char *path) { - struct jpeg_compress_struct cinfo; - struct jpeg_error_mgr jerr; - FILE *f = NULL; - unsigned char *row = NULL; - int row_stride = width * 3; - if (!in_bgr || !path) - return -1; - f = fopen(path, "wb"); - if (!f) - return -1; - cinfo.err = jpeg_std_error(&jerr); - jpeg_create_compress(&cinfo); - jpeg_stdio_dest(&cinfo, f); - cinfo.image_width = width; - cinfo.image_height = height; - cinfo.input_components = 3; - cinfo.in_color_space = JCS_RGB; - jpeg_set_defaults(&cinfo); - jpeg_set_quality(&cinfo, quality, TRUE); - jpeg_start_compress(&cinfo, TRUE); - row = malloc(row_stride); - if (!row) { - jpeg_finish_compress(&cinfo); - jpeg_destroy_compress(&cinfo); - fclose(f); - return -1; - } - while (cinfo.next_scanline < cinfo.image_height) { - const u8 *src = in_bgr + (size_t)cinfo.next_scanline * (size_t)row_stride; - for (int x = 0; x < width; ++x) { - row[x * 3 + 0] = src[x * 3 + 2]; - row[x * 3 + 1] = src[x * 3 + 1]; - row[x * 3 + 2] = src[x * 3 + 0]; - } - JSAMPROW row_pointer[1]; - row_pointer[0] = row; - jpeg_write_scanlines(&cinfo, row_pointer, 1); - } - free(row); - jpeg_finish_compress(&cinfo); - jpeg_destroy_compress(&cinfo); - fclose(f); - return 0; -} - -Result http_post_file(const char *url, const char *filename) { - Result ret = 0; - httpcContext context; - u32 statuscode = 0; - FILE *f = fopen(filename, "rb"); - if (!f) - return -1; - if (fseek(f, 0, SEEK_END) != 0) { - fclose(f); - return -1; - } - long fsize = ftell(f); - if (fsize < 0) { - fclose(f); - return -1; - } - rewind(f); - u8 *filebuf = malloc((size_t)fsize); - if (!filebuf) { - fclose(f); - return -1; - } - if (fread(filebuf, 1, (size_t)fsize, f) != (size_t)fsize) { - free(filebuf); - fclose(f); - return -1; - } - fclose(f); - char boundary[64]; - srand((unsigned)time(NULL)); - snprintf(boundary, sizeof(boundary), "----printcam%08x", rand()); - const char *filename_only = filename; - const char *p = strrchr(filename, '/'); - if (p) - filename_only = p + 1; - char preamble[512]; - int pre_len = snprintf( - preamble, sizeof(preamble), - "--%s\r\n" - "Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n" - "Content-Type: image/jpeg\r\n\r\n", - boundary, filename_only); - const char *epilogue_fmt = "\r\n--%s--\r\n"; - int epilogue_len = snprintf(NULL, 0, epilogue_fmt, boundary); - char *epilogue = malloc((size_t)epilogue_len + 1); - if (!epilogue) { - free(filebuf); - return -1; - } - snprintf(epilogue, (size_t)epilogue_len + 1, epilogue_fmt, boundary); - size_t multipart_size = - (size_t)pre_len + (size_t)fsize + (size_t)epilogue_len; - u8 *multipart_buf = malloc(multipart_size); - if (!multipart_buf) { - free(filebuf); - free(epilogue); - return -1; - } - memcpy(multipart_buf, preamble, (size_t)pre_len); - memcpy(multipart_buf + pre_len, filebuf, (size_t)fsize); - memcpy(multipart_buf + pre_len + fsize, epilogue, (size_t)epilogue_len); - free(filebuf); - free(epilogue); - ret = httpcOpenContext(&context, HTTPC_METHOD_POST, url, 0); - if (ret != 0) { - free(multipart_buf); - return ret; - } - httpcSetSSLOpt(&context, SSLCOPT_DisableVerify); - httpcSetKeepAlive(&context, HTTPC_KEEPALIVE_ENABLED); - char content_type_header[128]; - snprintf(content_type_header, sizeof(content_type_header), - "multipart/form-data; boundary=%s", boundary); - httpcAddRequestHeaderField(&context, "User-Agent", "printcam/1.0"); - httpcAddRequestHeaderField(&context, "Content-Type", content_type_header); - ret = - httpcAddPostDataRaw(&context, (u32 *)multipart_buf, (u32)multipart_size); - if (ret != 0) { - httpcCloseContext(&context); - free(multipart_buf); - return ret; - } - ret = httpcBeginRequest(&context); - if (ret != 0) { - httpcCloseContext(&context); - free(multipart_buf); - return ret; - } - ret = httpcGetResponseStatusCode(&context, &statuscode); - if (ret == 0) { - u32 downloaded = 0; - u32 contentsize = 0; - while (true) { - ret = httpcGetDownloadSizeState(&context, &downloaded, &contentsize); - if (ret != 0) - break; - if (contentsize == 0) - break; - if (downloaded >= contentsize) - break; - u32 remaining = contentsize - downloaded; - u32 toread = remaining > 4096 ? 4096 : remaining; - u8 tmp[4096]; - ret = httpcReceiveData(&context, tmp, toread); - if (ret != 0) - break; - } - } - httpcCloseContext(&context); - free(multipart_buf); - return ret; -} - -static void cameraThreadFunc(void *arg) { - acInit(); - camInit(); - u32 sel = cam_selects[camera_index]; - CAMU_SetSize(sel, SIZE_CTR_TOP_LCD, CONTEXT_A); - CAMU_SetOutputFormat(sel, OUTPUT_YUV_422, CONTEXT_A); - CAMU_SetFrameRate(sel, FRAME_RATE_30); - CAMU_SetNoiseFilter(sel, true); - CAMU_SetAutoExposure(sel, true); - CAMU_SetAutoWhiteBalance(sel, true); - u32 maxBytes; - CAMU_GetMaxBytes(&maxBytes, WIDTH, HEIGHT); - CAMU_SetTransferBytes(PORT_BOTH, maxBytes, WIDTH, HEIGHT); - CAMU_Activate(sel); - Handle ev = 0; - CAMU_ClearBuffer(PORT_BOTH); - CAMU_SynchronizeVsyncTiming(SELECT_OUT1, SELECT_OUT2); - CAMU_StartCapture(PORT_BOTH); - while (!exitRequested) { - if (camera_reinit) { - sel = cam_selects[camera_index]; - CAMU_StopCapture(PORT_BOTH); - CAMU_ClearBuffer(PORT_BOTH); - CAMU_SetSize(sel, SIZE_CTR_TOP_LCD, CONTEXT_A); - CAMU_SetOutputFormat(sel, OUTPUT_YUV_422, CONTEXT_A); - CAMU_SetFrameRate(sel, FRAME_RATE_30); - CAMU_SetNoiseFilter(sel, true); - CAMU_SetAutoExposure(sel, true); - CAMU_SetAutoWhiteBalance(sel, true); - CAMU_SetTransferBytes(PORT_BOTH, maxBytes, WIDTH, HEIGHT); - CAMU_Activate(sel); - CAMU_StartCapture(PORT_BOTH); - camera_reinit = false; - } - CAMU_SetReceiving(&ev, camBuf[writeIdx], PORT_CAM1, WIDTH * HEIGHT * 2, - maxBytes); - svcWaitSynchronization(ev, U64_MAX); - svcCloseHandle(ev); - svcSignalEvent(ready[writeIdx]); - writeIdx ^= 1; - } - CAMU_StopCapture(PORT_BOTH); - CAMU_Activate(SELECT_NONE); +void cleanup() { camExit(); + gfxExit(); acExit(); } -int main(int argc, char *argv[]) { - gfxInitDefault(); - httpcInit(4 * 1024 * 1024); - consoleInit(GFX_BOTTOM, &bottomScreen); - consoleSelect(&bottomScreen); - printf("printcam\nL/R=take X=switch-front/back Y=reload-url START=exit\n"); - if (!ensure_storage_dir()) - printf("Failed to create %s\n", STORAGE_DIR); - if (!ensure_default_endpoint_file()) - printf("Failed to create default endpoint file\n"); - if (load_endpoint_from_sd() == 0) - printf("Endpoint: %s\n", api_endpoint); - camBuf[0] = (u8 *)memalign(0x40, WIDTH * HEIGHT * 2); - camBuf[1] = (u8 *)memalign(0x40, WIDTH * HEIGHT * 2); - rgbBuf = (u8 *)memalign(0x40, FB_SIZE); - u8 *linearBuf = malloc(FB_SIZE); - if (!camBuf[0] || !camBuf[1] || !rgbBuf || !linearBuf) { - printf("Out of memory\n"); - goto cleanup; +void writePictureToFramebufferRGB565(void *fb, void *img, u16 x, u16 y, + u16 width, u16 height) { + u8 *fb_8 = (u8 *)fb; + u16 *img_16 = (u16 *)img; + int i, j, draw_x, draw_y; + for (j = 0; j < height; j++) { + for (i = 0; i < width; i++) { + draw_y = y + height - j; + draw_x = x + i; + u32 v = (draw_y + draw_x * height) * 3; + u16 data = img_16[j * width + i]; + uint8_t b = ((data >> 11) & 0x1F) << 3; + uint8_t g = ((data >> 5) & 0x3F) << 2; + uint8_t r = (data & 0x1F) << 3; + fb_8[v] = r; + fb_8[v + 1] = g; + fb_8[v + 2] = b; + } } - svcCreateEvent(&ready[0], RESET_ONESHOT); - svcCreateEvent(&ready[1], RESET_ONESHOT); +} + +void takePicture() { + u8 *buf = malloc(BUF_SIZE); + if (!buf) { + hang("Failed to allocate memory!"); + } + u32 bufSize; + + CAMU_GetMaxBytes(&bufSize, PIC_WIDTH, PIC_HEIGHT); + CAMU_SetTransferBytes(PORT, bufSize, PIC_WIDTH, PIC_HEIGHT); + CAMU_SwitchContext(getActiveSelect(), CONTEXT_B); + + Handle camReceiveEvent = 0; + CAMU_Activate(getActiveSelect()); + CAMU_ClearBuffer(PORT); + CAMU_StartCapture(PORT); + CAMU_SetReceiving(&camReceiveEvent, buf, PORT, PIC_SIZE, (s16)bufSize); + svcWaitSynchronization(camReceiveEvent, WAIT_TIMEOUT); + + CAMU_StopCapture(PORT); + CAMU_Activate(PORT_NONE); + svcCloseHandle(camReceiveEvent); + CAMU_SwitchContext(getActiveSelect(), CONTEXT_A); + + for (int i = 0; i < 5; i++) { + printf("%02x", buf[i]); + } + + free(buf); +} + +void setupCam(u32 *bufSize) { + camInit(); + + CAMU_SetSize(CAMS, SIZE_CTR_TOP_LCD, CONTEXT_A); + CAMU_SetSize(CAMS, SIZE_VGA, CONTEXT_B); + CAMU_SetOutputFormat(CAMS, OUTPUT_RGB_565, CONTEXT_BOTH); + CAMU_SetFrameRate(CAMS, FRAME_RATE_30); + CAMU_SetNoiseFilter(CAMS, true); + CAMU_SetAutoExposure(CAMS, true); + CAMU_SetAutoWhiteBalance(CAMS, true); + CAMU_SetTrimming(PORT_BOTH, false); + CAMU_PlayShutterSound(SHUTTER_SOUND_TYPE_MOVIE); + + CAMU_GetMaxBytes(bufSize, WIDTH, HEIGHT); + CAMU_SetTransferBytes(PORT_BOTH, *bufSize, WIDTH, HEIGHT); +} +void activateCam(Handle camReceiveEvent[2]) { + CAMU_Activate(getActiveSelect()); + CAMU_GetBufferErrorInterruptEvent(&camReceiveEvent[0], PORT); + CAMU_ClearBuffer(PORT); + CAMU_StartCapture(PORT); + + gfxFlushBuffers(); + gspWaitForVBlank(); + gfxSwapBuffers(); +} + +int main() { + acInit(); + gfxInitDefault(); + consoleInit(GFX_BOTTOM, NULL); + gfxSetDoubleBuffering(GFX_TOP, true); - camThread = threadCreate(cameraThreadFunc, NULL, 0x1000, 0x2F, -2, false); + gfxSetDoubleBuffering(GFX_BOTTOM, false); + + if (setjmp(exitJmp)) { + cleanup(); + return 0; + } + + u32 kDown; + u8 *buf = malloc(BUF_SIZE); + u32 bufSize; + Handle camReceiveEvent[2] = {0}; + bool captureInterrupted = false; + s32 index = 0; + + if (!buf) { + hang("Failed to allocate memory!"); + } + + printf("Initializing camera\n"); + + setupCam(&bufSize); + activateCam(camReceiveEvent); + + printf("Press Start to exit to Homebrew Launcher\n"); + while (aptMainLoop()) { - hidScanInput(); - u32 k = hidKeysDown(); - if (k & KEY_START) - break; - if (k & KEY_X) { - camera_index ^= 1; - camera_reinit = true; - printf("Switched to %s\n", - camera_index ? "front/input1" : "back/output1"); - } - if (k & KEY_Y) { - if (load_endpoint_from_sd() == 0) - printf("Reloaded endpoint: %s\n", api_endpoint); - else - printf("Failed to reload endpoint\n"); - } - if (k & (KEY_L | KEY_R)) { - consoleSelect(&bottomScreen); - printf("Snapshot requested\n"); - convert_display_to_row_major(rgbBuf, linearBuf); - char jpgpath[128]; - time_t t = time(NULL); - strftime(jpgpath, sizeof(jpgpath), STORAGE_DIR "/%Y%m%d_%H%M%S.jpg", - localtime(&t)); - if (encode_bgr_to_jpeg_file(linearBuf, WIDTH, HEIGHT, JPEG_QUALITY, - jpgpath) == 0) { - printf("Wrote %s\n", jpgpath); - Result res = http_post_file(api_endpoint, jpgpath); - printf("http_post_file returned: %" PRIx32 "\n", (u32)res); - } else { - printf("JPEG write failed\n"); + if (!captureInterrupted) { + hidScanInput(); + kDown = hidKeysDown(); + + if (kDown & KEY_START) { + break; + } + if (kDown & KEY_A) { + printf("Switching cameras...\n"); + + CAMU_StopCapture(PORT); + svcCloseHandle(camReceiveEvent[0]); + svcCloseHandle(camReceiveEvent[1]); + + activeCam ^= true; + activateCam(camReceiveEvent); + } + + if (kDown & KEY_R) { + printf("Taking pic...\n"); + + CAMU_StopCapture(PORT); + svcCloseHandle(camReceiveEvent[0]); + svcCloseHandle(camReceiveEvent[1]); + + takePicture(); + // setupCam(); + activateCam(camReceiveEvent); } } - gspWaitForVBlank(); - if (svcWaitSynchronization(ready[dispIdx], 0) == 0) { - blitYUYVtoRGB(camBuf[dispIdx], rgbBuf); - dispIdx ^= 1; + + if (camReceiveEvent[1] == 0) { + CAMU_SetReceiving(&camReceiveEvent[1], buf, PORT, SCREEN_SIZE, + (s16)bufSize); } - u8 *fb = gfxGetFramebuffer(GFX_TOP, GFX_LEFT, NULL, NULL); - memcpy(fb, rgbBuf, FB_SIZE); - gfxSwapBuffersGpu(); + if (captureInterrupted) { + CAMU_StartCapture(PORT); + captureInterrupted = false; + } + + svcWaitSynchronizationN(&index, camReceiveEvent, 2, false, WAIT_TIMEOUT); + switch (index) { + case 0: + svcCloseHandle(camReceiveEvent[1]); + camReceiveEvent[1] = 0; + + captureInterrupted = true; + continue; // skip screen update + break; + case 1: + svcCloseHandle(camReceiveEvent[1]); + camReceiveEvent[1] = 0; + break; + default: + break; + } + + writePictureToFramebufferRGB565( + gfxGetFramebuffer(GFX_TOP, GFX_LEFT, NULL, NULL), buf, 0, 0, WIDTH, + HEIGHT); + + gfxFlushBuffers(); + gspWaitForVBlank(); + gfxSwapBuffers(); } -cleanup: - exitRequested = true; - if (camThread) - threadJoin(camThread, U64_MAX); - if (camThread) - threadFree(camThread); - if (ready[0]) - svcCloseHandle(ready[0]); - if (ready[1]) - svcCloseHandle(ready[1]); - if (camBuf[0]) - free(camBuf[0]); - if (camBuf[1]) - free(camBuf[1]); - if (rgbBuf) - free(rgbBuf); - if (linearBuf) - free(linearBuf); - httpcExit(); - gfxExit(); + + CAMU_StopCapture(PORT); + for (int i = 0; i < 2; i++) { + if (camReceiveEvent[i] != 0) { + svcCloseHandle(camReceiveEvent[i]); + } + } + CAMU_Activate(PORT_NONE); + + // Exit + free(buf); + cleanup(); + + // Return to hbmenu return 0; } From e0a1e8d1532a567b19a7448d96f7839bfad50b26 Mon Sep 17 00:00:00 2001 From: Jurn Wubben Date: Sun, 28 Sep 2025 15:39:11 +0200 Subject: [PATCH 4/4] removed duplicate code --- src/main.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main.c b/src/main.c index a03cea6..b23e827 100644 --- a/src/main.c +++ b/src/main.c @@ -249,9 +249,7 @@ static Result post_multipart(const char *url, const char *filepath) { return r; } -static void cam_thread(void *arg) { - acInit(); - camInit(); +static void setup_camera(u32 max) { u32 sel = camIndex ? SELECT_IN1 : SELECT_OUT1; CAMU_SetSize(sel, SIZE_CTR_TOP_LCD, CONTEXT_A); CAMU_SetOutputFormat(sel, OUTPUT_YUV_422, CONTEXT_A); @@ -259,29 +257,30 @@ static void cam_thread(void *arg) { CAMU_SetNoiseFilter(sel, true); CAMU_SetAutoExposure(sel, true); CAMU_SetAutoWhiteBalance(sel, true); - u32 max; - CAMU_GetMaxBytes(&max, W, H); CAMU_SetTransferBytes(PORT_BOTH, max, W, H); CAMU_Activate(sel); +} + +static void cam_thread(void *arg) { + acInit(); + camInit(); + u32 max; + CAMU_GetMaxBytes(&max, W, H); + + setup_camera(max); // Initial setup CAMU_ClearBuffer(PORT_BOTH); CAMU_SynchronizeVsyncTiming(SELECT_OUT1, SELECT_OUT2); CAMU_StartCapture(PORT_BOTH); + while (!quit) { if (reinitCam) { - sel = camIndex ? SELECT_IN1 : SELECT_OUT1; CAMU_StopCapture(PORT_BOTH); CAMU_ClearBuffer(PORT_BOTH); - CAMU_SetSize(sel, SIZE_CTR_TOP_LCD, CONTEXT_A); - CAMU_SetOutputFormat(sel, OUTPUT_YUV_422, CONTEXT_A); - CAMU_SetFrameRate(sel, FRAME_RATE_30); - CAMU_SetNoiseFilter(sel, true); - CAMU_SetAutoExposure(sel, true); - CAMU_SetAutoWhiteBalance(sel, true); - CAMU_SetTransferBytes(PORT_BOTH, max, W, H); - CAMU_Activate(sel); + setup_camera(max); // Reuse same setup logic CAMU_StartCapture(PORT_BOTH); reinitCam = false; } + Handle ev = 0; int recv = camIndex ? PORT_CAM2 : PORT_CAM1; CAMU_SetReceiving(&ev, camBuf[writeIdx], recv, W * H * 2, max); @@ -290,6 +289,7 @@ static void cam_thread(void *arg) { svcSignalEvent(readyEv[writeIdx]); writeIdx ^= 1; } + CAMU_StopCapture(PORT_BOTH); CAMU_Activate(SELECT_NONE); camExit();