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;