Loading cmds/screencap/screencap.cpp +184 −102 Original line number Diff line number Diff line Loading @@ -51,11 +51,13 @@ using namespace android; void usage(const char* pname, ftl::Optional<DisplayId> displayIdOpt) { fprintf(stderr, R"( usage: %s [-hp] [-d display-id] [FILENAME] usage: %s [-ahp] [-d display-id] [FILENAME] -h: this message -p: save the file as a png. -a: captures all the active displays. This appends an integer postfix to the FILENAME. e.g., FILENAME_0.png, FILENAME_1.png. If both -a and -d are given, it ignores -d. -d: specify the display ID to capture%s see "dumpsys SurfaceFlinger --display-id" for valid display IDs. -p: outputs in png format. --hint-for-seamless If set will use the hintForSeamless path in SF If FILENAME ends with .png it will be saved as a png. Loading @@ -64,7 +66,9 @@ If FILENAME is not given, the results will be printed to stdout. pname, displayIdOpt .transform([](DisplayId id) { return std::string(ftl::Concat(" (default: ", id.value, ')').str()); return std::string(ftl::Concat( " (If the id is not given, it defaults to ", id.value,')' ).str()); }) .value_or(std::string()) .c_str()); Loading Loading @@ -146,19 +150,119 @@ static status_t notifyMediaScanner(const char* fileName) { return NO_ERROR; } status_t capture(const DisplayId displayId, const gui::CaptureArgs& captureArgs, ScreenCaptureResults& outResult) { sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener(); ScreenshotClient::captureDisplay(displayId, captureArgs, captureListener); ScreenCaptureResults captureResults = captureListener->waitForResults(); if (!captureResults.fenceResult.ok()) { fprintf(stderr, "Failed to take screenshot. Status: %d\n", fenceStatus(captureResults.fenceResult)); return 1; } outResult = captureResults; return 0; } status_t saveImage(const char* fn, bool png, const ScreenCaptureResults& captureResults) { void* base = nullptr; ui::Dataspace dataspace = captureResults.capturedDataspace; sp<GraphicBuffer> buffer = captureResults.buffer; status_t result = buffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &base); if (base == nullptr || result != NO_ERROR) { String8 reason; if (result != NO_ERROR) { reason.appendFormat(" Error Code: %d", result); } else { reason = "Failed to write to buffer"; } fprintf(stderr, "Failed to take screenshot (%s)\n", reason.c_str()); return 1; } int fd = -1; if (fn == nullptr) { fd = dup(STDOUT_FILENO); if (fd == -1) { fprintf(stderr, "Error writing to stdout. (%s)\n", strerror(errno)); return 1; } } else { fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664); if (fd == -1) { fprintf(stderr, "Error opening file: %s (%s)\n", fn, strerror(errno)); return 1; } } if (png) { AndroidBitmapInfo info; info.format = flinger2bitmapFormat(buffer->getPixelFormat()); info.flags = ANDROID_BITMAP_FLAGS_ALPHA_PREMUL; info.width = buffer->getWidth(); info.height = buffer->getHeight(); info.stride = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat()); int result = AndroidBitmap_compress(&info, static_cast<int32_t>(dataspace), base, ANDROID_BITMAP_COMPRESS_FORMAT_PNG, 100, &fd, [](void* fdPtr, const void* data, size_t size) -> bool { int bytesWritten = write(*static_cast<int*>(fdPtr), data, size); return bytesWritten == size; }); if (result != ANDROID_BITMAP_RESULT_SUCCESS) { fprintf(stderr, "Failed to compress PNG (error code: %d)\n", result); } if (fn != NULL) { notifyMediaScanner(fn); } } else { uint32_t w = buffer->getWidth(); uint32_t h = buffer->getHeight(); uint32_t s = buffer->getStride(); uint32_t f = buffer->getPixelFormat(); uint32_t c = dataSpaceToInt(dataspace); write(fd, &w, 4); write(fd, &h, 4); write(fd, &f, 4); write(fd, &c, 4); size_t Bpp = bytesPerPixel(f); for (size_t y=0 ; y<h ; y++) { write(fd, base, w*Bpp); base = (void *)((char *)base + s*Bpp); } } close(fd); return 0; } int main(int argc, char** argv) { const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds(); if (ids.empty()) { const std::vector<PhysicalDisplayId> physicalDisplays = SurfaceComposerClient::getPhysicalDisplayIds(); if (physicalDisplays.empty()) { fprintf(stderr, "Failed to get ID for any displays.\n"); return 1; } std::optional<DisplayId> displayIdOpt; std::vector<DisplayId> displaysToCapture; gui::CaptureArgs captureArgs; const char* pname = argv[0]; bool png = false; bool all = false; int c; while ((c = getopt_long(argc, argv, "phd:", LONG_OPTIONS, nullptr)) != -1) { while ((c = getopt_long(argc, argv, "aphd:", LONG_OPTIONS, nullptr)) != -1) { switch (c) { case 'p': png = true; Loading @@ -177,12 +281,17 @@ int main(int argc, char** argv) fprintf(stderr, "Invalid display ID: Incorrect encoding.\n"); return 1; } displaysToCapture.push_back(displayIdOpt.value()); break; } case 'a': { all = true; break; } case '?': case 'h': if (ids.size() == 1) { displayIdOpt = ids.front(); if (physicalDisplays.size() >= 1) { displayIdOpt = physicalDisplays.front(); } usage(pname, displayIdOpt); return 1; Loading @@ -192,44 +301,52 @@ int main(int argc, char** argv) } } if (!displayIdOpt) { displayIdOpt = ids.front(); if (ids.size() > 1) { fprintf(stderr, "[Warning] Multiple displays were found, but no display id was specified! " "Defaulting to the first display found, however this default is not guaranteed " "to be consistent across captures. A display id should be specified.\n"); fprintf(stderr, "A display ID can be specified with the [-d display-id] option.\n"); fprintf(stderr, "See \"dumpsys SurfaceFlinger --display-id\" for valid display IDs.\n"); } } argc -= optind; argv += optind; int fd = -1; const char* fn = NULL; if (argc == 0) { fd = dup(STDOUT_FILENO); } else if (argc == 1) { fn = argv[0]; fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664); if (fd == -1) { fprintf(stderr, "Error opening file: %s (%s)\n", fn, strerror(errno)); // We don't expect more than 2 arguments. if (argc >= 2) { if (physicalDisplays.size() >= 1) { usage(pname, physicalDisplays.front()); } else { usage(pname, std::nullopt); } return 1; } const int len = strlen(fn); if (len >= 4 && 0 == strcmp(fn+len-4, ".png")) { std::string baseName; std::string suffix; if (argc == 1) { std::string_view filename = { argv[0] }; if (filename.ends_with(".png")) { baseName = filename.substr(0, filename.size()-4); suffix = ".png"; png = true; } else { baseName = filename; } } if (fd == -1) { usage(pname, displayIdOpt); return 1; if (all) { // Ignores -d if -a is given. displaysToCapture.clear(); for (int i = 0; i < physicalDisplays.size(); i++) { displaysToCapture.push_back(physicalDisplays[i]); } } void* base = NULL; if (displaysToCapture.empty()) { displaysToCapture.push_back(physicalDisplays.front()); if (physicalDisplays.size() > 1) { fprintf(stderr, "[Warning] Multiple displays were found, but no display id was specified! " "Defaulting to the first display found, however this default is not guaranteed " "to be consistent across captures. A display id should be specified.\n"); fprintf(stderr, "A display ID can be specified with the [-d display-id] option.\n"); fprintf(stderr, "See \"dumpsys SurfaceFlinger --display-id\" for valid display IDs.\n"); } } // setThreadPoolMaxThreadCount(0) actually tells the kernel it's // not allowed to spawn any additional threads, but we still spawn Loading @@ -238,74 +355,39 @@ int main(int argc, char** argv) ProcessState::self()->setThreadPoolMaxThreadCount(0); ProcessState::self()->startThreadPool(); sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener(); ScreenshotClient::captureDisplay(*displayIdOpt, captureArgs, captureListener); std::vector<ScreenCaptureResults> results; const size_t numDisplays = displaysToCapture.size(); for (int i=0; i<numDisplays; i++) { ScreenCaptureResults result; ScreenCaptureResults captureResults = captureListener->waitForResults(); if (!captureResults.fenceResult.ok()) { close(fd); fprintf(stderr, "Failed to take screenshot. Status: %d\n", fenceStatus(captureResults.fenceResult)); return 1; } ui::Dataspace dataspace = captureResults.capturedDataspace; sp<GraphicBuffer> buffer = captureResults.buffer; // 1. Capture the screen if (const status_t captureStatus = capture(displaysToCapture[i], captureArgs, result) != 0) { status_t result = buffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &base); if (base == nullptr || result != NO_ERROR) { String8 reason; if (result != NO_ERROR) { reason.appendFormat(" Error Code: %d", result); } else { reason = "Failed to write to buffer"; } fprintf(stderr, "Failed to take screenshot (%s)\n", reason.c_str()); close(fd); return 1; fprintf(stderr, "Capturing failed.\n"); return captureStatus; } if (png) { AndroidBitmapInfo info; info.format = flinger2bitmapFormat(buffer->getPixelFormat()); info.flags = ANDROID_BITMAP_FLAGS_ALPHA_PREMUL; info.width = buffer->getWidth(); info.height = buffer->getHeight(); info.stride = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat()); int result = AndroidBitmap_compress(&info, static_cast<int32_t>(dataspace), base, ANDROID_BITMAP_COMPRESS_FORMAT_PNG, 100, &fd, [](void* fdPtr, const void* data, size_t size) -> bool { int bytesWritten = write(*static_cast<int*>(fdPtr), data, size); return bytesWritten == size; }); if (result != ANDROID_BITMAP_RESULT_SUCCESS) { fprintf(stderr, "Failed to compress PNG (error code: %d)\n", result); // 2. Save the capture result as an image. // When there's more than one file to capture, add the index as postfix. std::string filename; if (!baseName.empty()) { filename = baseName; if (numDisplays > 1) { filename += "_"; filename += std::to_string(i); } if (fn != NULL) { notifyMediaScanner(fn); filename += suffix; } } else { uint32_t w = buffer->getWidth(); uint32_t h = buffer->getHeight(); uint32_t s = buffer->getStride(); uint32_t f = buffer->getPixelFormat(); uint32_t c = dataSpaceToInt(dataspace); write(fd, &w, 4); write(fd, &h, 4); write(fd, &f, 4); write(fd, &c, 4); size_t Bpp = bytesPerPixel(f); for (size_t y=0 ; y<h ; y++) { write(fd, base, w*Bpp); base = (void *)((char *)base + s*Bpp); const char* fn = nullptr; if (!filename.empty()) { fn = filename.c_str(); } if (const status_t saveImageStatus = saveImage(fn, png, result) != 0) { fprintf(stderr, "Saving image failed.\n"); return saveImageStatus; } } close(fd); return 0; } Loading
cmds/screencap/screencap.cpp +184 −102 Original line number Diff line number Diff line Loading @@ -51,11 +51,13 @@ using namespace android; void usage(const char* pname, ftl::Optional<DisplayId> displayIdOpt) { fprintf(stderr, R"( usage: %s [-hp] [-d display-id] [FILENAME] usage: %s [-ahp] [-d display-id] [FILENAME] -h: this message -p: save the file as a png. -a: captures all the active displays. This appends an integer postfix to the FILENAME. e.g., FILENAME_0.png, FILENAME_1.png. If both -a and -d are given, it ignores -d. -d: specify the display ID to capture%s see "dumpsys SurfaceFlinger --display-id" for valid display IDs. -p: outputs in png format. --hint-for-seamless If set will use the hintForSeamless path in SF If FILENAME ends with .png it will be saved as a png. Loading @@ -64,7 +66,9 @@ If FILENAME is not given, the results will be printed to stdout. pname, displayIdOpt .transform([](DisplayId id) { return std::string(ftl::Concat(" (default: ", id.value, ')').str()); return std::string(ftl::Concat( " (If the id is not given, it defaults to ", id.value,')' ).str()); }) .value_or(std::string()) .c_str()); Loading Loading @@ -146,19 +150,119 @@ static status_t notifyMediaScanner(const char* fileName) { return NO_ERROR; } status_t capture(const DisplayId displayId, const gui::CaptureArgs& captureArgs, ScreenCaptureResults& outResult) { sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener(); ScreenshotClient::captureDisplay(displayId, captureArgs, captureListener); ScreenCaptureResults captureResults = captureListener->waitForResults(); if (!captureResults.fenceResult.ok()) { fprintf(stderr, "Failed to take screenshot. Status: %d\n", fenceStatus(captureResults.fenceResult)); return 1; } outResult = captureResults; return 0; } status_t saveImage(const char* fn, bool png, const ScreenCaptureResults& captureResults) { void* base = nullptr; ui::Dataspace dataspace = captureResults.capturedDataspace; sp<GraphicBuffer> buffer = captureResults.buffer; status_t result = buffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &base); if (base == nullptr || result != NO_ERROR) { String8 reason; if (result != NO_ERROR) { reason.appendFormat(" Error Code: %d", result); } else { reason = "Failed to write to buffer"; } fprintf(stderr, "Failed to take screenshot (%s)\n", reason.c_str()); return 1; } int fd = -1; if (fn == nullptr) { fd = dup(STDOUT_FILENO); if (fd == -1) { fprintf(stderr, "Error writing to stdout. (%s)\n", strerror(errno)); return 1; } } else { fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664); if (fd == -1) { fprintf(stderr, "Error opening file: %s (%s)\n", fn, strerror(errno)); return 1; } } if (png) { AndroidBitmapInfo info; info.format = flinger2bitmapFormat(buffer->getPixelFormat()); info.flags = ANDROID_BITMAP_FLAGS_ALPHA_PREMUL; info.width = buffer->getWidth(); info.height = buffer->getHeight(); info.stride = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat()); int result = AndroidBitmap_compress(&info, static_cast<int32_t>(dataspace), base, ANDROID_BITMAP_COMPRESS_FORMAT_PNG, 100, &fd, [](void* fdPtr, const void* data, size_t size) -> bool { int bytesWritten = write(*static_cast<int*>(fdPtr), data, size); return bytesWritten == size; }); if (result != ANDROID_BITMAP_RESULT_SUCCESS) { fprintf(stderr, "Failed to compress PNG (error code: %d)\n", result); } if (fn != NULL) { notifyMediaScanner(fn); } } else { uint32_t w = buffer->getWidth(); uint32_t h = buffer->getHeight(); uint32_t s = buffer->getStride(); uint32_t f = buffer->getPixelFormat(); uint32_t c = dataSpaceToInt(dataspace); write(fd, &w, 4); write(fd, &h, 4); write(fd, &f, 4); write(fd, &c, 4); size_t Bpp = bytesPerPixel(f); for (size_t y=0 ; y<h ; y++) { write(fd, base, w*Bpp); base = (void *)((char *)base + s*Bpp); } } close(fd); return 0; } int main(int argc, char** argv) { const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds(); if (ids.empty()) { const std::vector<PhysicalDisplayId> physicalDisplays = SurfaceComposerClient::getPhysicalDisplayIds(); if (physicalDisplays.empty()) { fprintf(stderr, "Failed to get ID for any displays.\n"); return 1; } std::optional<DisplayId> displayIdOpt; std::vector<DisplayId> displaysToCapture; gui::CaptureArgs captureArgs; const char* pname = argv[0]; bool png = false; bool all = false; int c; while ((c = getopt_long(argc, argv, "phd:", LONG_OPTIONS, nullptr)) != -1) { while ((c = getopt_long(argc, argv, "aphd:", LONG_OPTIONS, nullptr)) != -1) { switch (c) { case 'p': png = true; Loading @@ -177,12 +281,17 @@ int main(int argc, char** argv) fprintf(stderr, "Invalid display ID: Incorrect encoding.\n"); return 1; } displaysToCapture.push_back(displayIdOpt.value()); break; } case 'a': { all = true; break; } case '?': case 'h': if (ids.size() == 1) { displayIdOpt = ids.front(); if (physicalDisplays.size() >= 1) { displayIdOpt = physicalDisplays.front(); } usage(pname, displayIdOpt); return 1; Loading @@ -192,44 +301,52 @@ int main(int argc, char** argv) } } if (!displayIdOpt) { displayIdOpt = ids.front(); if (ids.size() > 1) { fprintf(stderr, "[Warning] Multiple displays were found, but no display id was specified! " "Defaulting to the first display found, however this default is not guaranteed " "to be consistent across captures. A display id should be specified.\n"); fprintf(stderr, "A display ID can be specified with the [-d display-id] option.\n"); fprintf(stderr, "See \"dumpsys SurfaceFlinger --display-id\" for valid display IDs.\n"); } } argc -= optind; argv += optind; int fd = -1; const char* fn = NULL; if (argc == 0) { fd = dup(STDOUT_FILENO); } else if (argc == 1) { fn = argv[0]; fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664); if (fd == -1) { fprintf(stderr, "Error opening file: %s (%s)\n", fn, strerror(errno)); // We don't expect more than 2 arguments. if (argc >= 2) { if (physicalDisplays.size() >= 1) { usage(pname, physicalDisplays.front()); } else { usage(pname, std::nullopt); } return 1; } const int len = strlen(fn); if (len >= 4 && 0 == strcmp(fn+len-4, ".png")) { std::string baseName; std::string suffix; if (argc == 1) { std::string_view filename = { argv[0] }; if (filename.ends_with(".png")) { baseName = filename.substr(0, filename.size()-4); suffix = ".png"; png = true; } else { baseName = filename; } } if (fd == -1) { usage(pname, displayIdOpt); return 1; if (all) { // Ignores -d if -a is given. displaysToCapture.clear(); for (int i = 0; i < physicalDisplays.size(); i++) { displaysToCapture.push_back(physicalDisplays[i]); } } void* base = NULL; if (displaysToCapture.empty()) { displaysToCapture.push_back(physicalDisplays.front()); if (physicalDisplays.size() > 1) { fprintf(stderr, "[Warning] Multiple displays were found, but no display id was specified! " "Defaulting to the first display found, however this default is not guaranteed " "to be consistent across captures. A display id should be specified.\n"); fprintf(stderr, "A display ID can be specified with the [-d display-id] option.\n"); fprintf(stderr, "See \"dumpsys SurfaceFlinger --display-id\" for valid display IDs.\n"); } } // setThreadPoolMaxThreadCount(0) actually tells the kernel it's // not allowed to spawn any additional threads, but we still spawn Loading @@ -238,74 +355,39 @@ int main(int argc, char** argv) ProcessState::self()->setThreadPoolMaxThreadCount(0); ProcessState::self()->startThreadPool(); sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener(); ScreenshotClient::captureDisplay(*displayIdOpt, captureArgs, captureListener); std::vector<ScreenCaptureResults> results; const size_t numDisplays = displaysToCapture.size(); for (int i=0; i<numDisplays; i++) { ScreenCaptureResults result; ScreenCaptureResults captureResults = captureListener->waitForResults(); if (!captureResults.fenceResult.ok()) { close(fd); fprintf(stderr, "Failed to take screenshot. Status: %d\n", fenceStatus(captureResults.fenceResult)); return 1; } ui::Dataspace dataspace = captureResults.capturedDataspace; sp<GraphicBuffer> buffer = captureResults.buffer; // 1. Capture the screen if (const status_t captureStatus = capture(displaysToCapture[i], captureArgs, result) != 0) { status_t result = buffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &base); if (base == nullptr || result != NO_ERROR) { String8 reason; if (result != NO_ERROR) { reason.appendFormat(" Error Code: %d", result); } else { reason = "Failed to write to buffer"; } fprintf(stderr, "Failed to take screenshot (%s)\n", reason.c_str()); close(fd); return 1; fprintf(stderr, "Capturing failed.\n"); return captureStatus; } if (png) { AndroidBitmapInfo info; info.format = flinger2bitmapFormat(buffer->getPixelFormat()); info.flags = ANDROID_BITMAP_FLAGS_ALPHA_PREMUL; info.width = buffer->getWidth(); info.height = buffer->getHeight(); info.stride = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat()); int result = AndroidBitmap_compress(&info, static_cast<int32_t>(dataspace), base, ANDROID_BITMAP_COMPRESS_FORMAT_PNG, 100, &fd, [](void* fdPtr, const void* data, size_t size) -> bool { int bytesWritten = write(*static_cast<int*>(fdPtr), data, size); return bytesWritten == size; }); if (result != ANDROID_BITMAP_RESULT_SUCCESS) { fprintf(stderr, "Failed to compress PNG (error code: %d)\n", result); // 2. Save the capture result as an image. // When there's more than one file to capture, add the index as postfix. std::string filename; if (!baseName.empty()) { filename = baseName; if (numDisplays > 1) { filename += "_"; filename += std::to_string(i); } if (fn != NULL) { notifyMediaScanner(fn); filename += suffix; } } else { uint32_t w = buffer->getWidth(); uint32_t h = buffer->getHeight(); uint32_t s = buffer->getStride(); uint32_t f = buffer->getPixelFormat(); uint32_t c = dataSpaceToInt(dataspace); write(fd, &w, 4); write(fd, &h, 4); write(fd, &f, 4); write(fd, &c, 4); size_t Bpp = bytesPerPixel(f); for (size_t y=0 ; y<h ; y++) { write(fd, base, w*Bpp); base = (void *)((char *)base + s*Bpp); const char* fn = nullptr; if (!filename.empty()) { fn = filename.c_str(); } if (const status_t saveImageStatus = saveImage(fn, png, result) != 0) { fprintf(stderr, "Saving image failed.\n"); return saveImageStatus; } } close(fd); return 0; }