xref: /aosp_15_r20/frameworks/base/cmds/screencap/screencap.cpp (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <android/bitmap.h>
18 #include <android/graphics/bitmap.h>
19 #include <android/gui/DisplayCaptureArgs.h>
20 #include <binder/ProcessState.h>
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <ftl/concat.h>
24 #include <ftl/optional.h>
25 #include <getopt.h>
26 #include <gui/ISurfaceComposer.h>
27 #include <gui/SurfaceComposerClient.h>
28 #include <gui/SyncScreenCaptureListener.h>
29 #include <linux/fb.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <sys/ioctl.h>
34 #include <sys/mman.h>
35 #include <sys/wait.h>
36 #include <system/graphics.h>
37 #include <ui/GraphicTypes.h>
38 #include <ui/PixelFormat.h>
39 
40 using namespace android;
41 
42 #define COLORSPACE_UNKNOWN    0
43 #define COLORSPACE_SRGB       1
44 #define COLORSPACE_DISPLAY_P3 2
45 
usage(const char * pname,ftl::Optional<DisplayId> displayIdOpt)46 void usage(const char* pname, ftl::Optional<DisplayId> displayIdOpt) {
47     fprintf(stderr, R"(
48 usage: %s [-ahp] [-d display-id] [FILENAME]
49    -h: this message
50    -a: captures all the active displays. This appends an integer postfix to the FILENAME.
51        e.g., FILENAME_0.png, FILENAME_1.png. If both -a and -d are given, it ignores -d.
52    -d: specify the display ID to capture%s
53        see "dumpsys SurfaceFlinger --display-id" for valid display IDs.
54    -p: outputs in png format.
55    --hint-for-seamless If set will use the hintForSeamless path in SF
56 
57 If FILENAME ends with .png it will be saved as a png.
58 If FILENAME is not given, the results will be printed to stdout.
59 )",
60             pname,
61             displayIdOpt
62                 .transform([](DisplayId id) {
63                     return std::string(ftl::Concat(
64                     " (If the id is not given, it defaults to ", id.value,')'
65                     ).str());
66                 })
67                 .value_or(std::string())
68                 .c_str());
69 }
70 
71 // For options that only exist in long-form. Anything in the
72 // 0-255 range is reserved for short options (which just use their ASCII value)
73 namespace LongOpts {
74 enum {
75     Reserved = 255,
76     HintForSeamless,
77 };
78 }
79 
80 static const struct option LONG_OPTIONS[] = {{"png", no_argument, nullptr, 'p'},
81                                              {"jpeg", no_argument, nullptr, 'j'},
82                                              {"help", no_argument, nullptr, 'h'},
83                                              {"hint-for-seamless", no_argument, nullptr,
84                                               LongOpts::HintForSeamless},
85                                              {0, 0, 0, 0}};
86 
flinger2bitmapFormat(PixelFormat f)87 static int32_t flinger2bitmapFormat(PixelFormat f)
88 {
89     switch (f) {
90         case PIXEL_FORMAT_RGB_565:
91             return ANDROID_BITMAP_FORMAT_RGB_565;
92         default:
93             return ANDROID_BITMAP_FORMAT_RGBA_8888;
94     }
95 }
96 
dataSpaceToInt(ui::Dataspace d)97 static uint32_t dataSpaceToInt(ui::Dataspace d)
98 {
99     switch (d) {
100         case ui::Dataspace::V0_SRGB:
101             return COLORSPACE_SRGB;
102         case ui::Dataspace::DISPLAY_P3:
103             return COLORSPACE_DISPLAY_P3;
104         default:
105             return COLORSPACE_UNKNOWN;
106     }
107 }
108 
notifyMediaScanner(const char * fileName)109 static status_t notifyMediaScanner(const char* fileName) {
110     std::string filePath("file://");
111     filePath.append(fileName);
112     char *cmd[] = {
113         (char*) "am",
114         (char*) "broadcast",
115         (char*) "-a",
116         (char*) "android.intent.action.MEDIA_SCANNER_SCAN_FILE",
117         (char*) "-d",
118         &filePath[0],
119         nullptr
120     };
121 
122     int status;
123     int pid = fork();
124     if (pid < 0){
125         fprintf(stderr, "Unable to fork in order to send intent for media scanner.\n");
126         return UNKNOWN_ERROR;
127     }
128     if (pid == 0){
129         int fd = open("/dev/null", O_WRONLY);
130         if (fd < 0){
131             fprintf(stderr, "Unable to open /dev/null for media scanner stdout redirection.\n");
132             exit(1);
133         }
134         dup2(fd, 1);
135         int result = execvp(cmd[0], cmd);
136         close(fd);
137         exit(result);
138     }
139     wait(&status);
140 
141     if (status < 0) {
142         fprintf(stderr, "Unable to broadcast intent for media scanner.\n");
143         return UNKNOWN_ERROR;
144     }
145     return NO_ERROR;
146 }
147 
capture(const DisplayId displayId,const gui::CaptureArgs & captureArgs,ScreenCaptureResults & outResult)148 status_t capture(const DisplayId displayId,
149             const gui::CaptureArgs& captureArgs,
150             ScreenCaptureResults& outResult) {
151     sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener();
152     ScreenshotClient::captureDisplay(displayId, captureArgs, captureListener);
153 
154     ScreenCaptureResults captureResults = captureListener->waitForResults();
155     if (!captureResults.fenceResult.ok()) {
156         fprintf(stderr, "Failed to take screenshot. Status: %d\n",
157                 fenceStatus(captureResults.fenceResult));
158         return 1;
159     }
160 
161     outResult = captureResults;
162 
163     return 0;
164 }
165 
saveImage(const char * fn,std::optional<AndroidBitmapCompressFormat> format,const ScreenCaptureResults & captureResults)166 status_t saveImage(const char* fn, std::optional<AndroidBitmapCompressFormat> format,
167                    const ScreenCaptureResults& captureResults) {
168     void* base = nullptr;
169     ui::Dataspace dataspace = captureResults.capturedDataspace;
170     const sp<GraphicBuffer>& buffer = captureResults.buffer;
171 
172     status_t result = buffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &base);
173 
174     if (base == nullptr || result != NO_ERROR) {
175         String8 reason;
176         if (result != NO_ERROR) {
177             reason.appendFormat(" Error Code: %d", result);
178         } else {
179             reason = "Failed to write to buffer";
180         }
181         fprintf(stderr, "Failed to take screenshot (%s)\n", reason.c_str());
182         return 1;
183     }
184 
185     void* gainmapBase = nullptr;
186     sp<GraphicBuffer> gainmap = captureResults.optionalGainMap;
187 
188     if (gainmap) {
189         result = gainmap->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &gainmapBase);
190         if (gainmapBase == nullptr || result != NO_ERROR) {
191             fprintf(stderr, "Failed to capture gainmap with error code (%d)\n", result);
192             gainmapBase = nullptr;
193             // Fall-through: just don't attempt to write the gainmap
194         }
195     }
196 
197     int fd = -1;
198     if (fn == nullptr) {
199         fd = dup(STDOUT_FILENO);
200         if (fd == -1) {
201             fprintf(stderr, "Error writing to stdout. (%s)\n", strerror(errno));
202             if (gainmapBase) {
203                 gainmap->unlock();
204             }
205 
206             if (base) {
207                 buffer->unlock();
208             }
209             return 1;
210         }
211     } else {
212         fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664);
213         if (fd == -1) {
214             fprintf(stderr, "Error opening file: %s (%s)\n", fn, strerror(errno));
215             if (gainmapBase) {
216                 gainmap->unlock();
217             }
218 
219             if (base) {
220                 buffer->unlock();
221             }
222             return 1;
223         }
224     }
225 
226     if (format) {
227         AndroidBitmapInfo info;
228         info.format = flinger2bitmapFormat(buffer->getPixelFormat());
229         info.flags = ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
230         info.width = buffer->getWidth();
231         info.height = buffer->getHeight();
232         info.stride = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat());
233 
234         int result;
235 
236         if (gainmapBase) {
237             result = ABitmap_compressWithGainmap(&info, static_cast<ADataSpace>(dataspace), base,
238                                                  gainmapBase, captureResults.hdrSdrRatio, *format,
239                                                  100, &fd,
240                                                  [](void* fdPtr, const void* data,
241                                                     size_t size) -> bool {
242                                                      int bytesWritten =
243                                                              write(*static_cast<int*>(fdPtr), data,
244                                                                    size);
245                                                      return bytesWritten == size;
246                                                  });
247         } else {
248             result = AndroidBitmap_compress(&info, static_cast<int32_t>(dataspace), base, *format,
249                                             100, &fd,
250                                             [](void* fdPtr, const void* data, size_t size) -> bool {
251                                                 int bytesWritten = write(*static_cast<int*>(fdPtr),
252                                                                          data, size);
253                                                 return bytesWritten == size;
254                                             });
255         }
256 
257         if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
258             fprintf(stderr, "Failed to compress (error code: %d)\n", result);
259         }
260 
261         if (fn != NULL) {
262             notifyMediaScanner(fn);
263         }
264     } else {
265         uint32_t w = buffer->getWidth();
266         uint32_t h = buffer->getHeight();
267         uint32_t s = buffer->getStride();
268         uint32_t f = buffer->getPixelFormat();
269         uint32_t c = dataSpaceToInt(dataspace);
270 
271         write(fd, &w, 4);
272         write(fd, &h, 4);
273         write(fd, &f, 4);
274         write(fd, &c, 4);
275         size_t Bpp = bytesPerPixel(f);
276         for (size_t y=0 ; y<h ; y++) {
277             write(fd, base, w*Bpp);
278             base = (void *)((char *)base + s*Bpp);
279         }
280     }
281     close(fd);
282 
283     if (gainmapBase) {
284         gainmap->unlock();
285     }
286 
287     if (base) {
288         buffer->unlock();
289     }
290 
291     return 0;
292 }
293 
main(int argc,char ** argv)294 int main(int argc, char** argv)
295 {
296     const std::vector<PhysicalDisplayId> physicalDisplays =
297         SurfaceComposerClient::getPhysicalDisplayIds();
298 
299     if (physicalDisplays.empty()) {
300         fprintf(stderr, "Failed to get ID for any displays.\n");
301         return 1;
302     }
303     std::optional<DisplayId> displayIdOpt;
304     std::vector<DisplayId> displaysToCapture;
305     gui::CaptureArgs captureArgs;
306     const char* pname = argv[0];
307     bool png = false;
308     bool jpeg = false;
309     bool all = false;
310     int c;
311     while ((c = getopt_long(argc, argv, "apjhd:", LONG_OPTIONS, nullptr)) != -1) {
312         switch (c) {
313             case 'p':
314                 png = true;
315                 break;
316             case 'j':
317                 jpeg = true;
318                 break;
319             case 'd': {
320                 errno = 0;
321                 char* end = nullptr;
322                 const uint64_t id = strtoull(optarg, &end, 10);
323                 if (!end || *end != '\0' || errno == ERANGE) {
324                     fprintf(stderr, "Invalid display ID: Out of range [0, 2^64).\n");
325                     return 1;
326                 }
327 
328                 displayIdOpt = DisplayId::fromValue(id);
329                 if (!displayIdOpt) {
330                     fprintf(stderr, "Invalid display ID: Incorrect encoding.\n");
331                     return 1;
332                 }
333                 displaysToCapture.push_back(displayIdOpt.value());
334                 break;
335             }
336             case 'a': {
337                 all = true;
338                 break;
339             }
340             case '?':
341             case 'h':
342                 if (physicalDisplays.size() >= 1) {
343                     displayIdOpt = physicalDisplays.front();
344                 }
345                 usage(pname, displayIdOpt);
346                 return 1;
347             case LongOpts::HintForSeamless:
348                 captureArgs.hintForSeamlessTransition = true;
349                 break;
350         }
351     }
352 
353     argc -= optind;
354     argv += optind;
355 
356     // We don't expect more than 2 arguments.
357     if (argc >= 2) {
358         if (physicalDisplays.size() >= 1) {
359             usage(pname, physicalDisplays.front());
360         } else {
361             usage(pname, std::nullopt);
362         }
363         return 1;
364     }
365 
366     std::string baseName;
367     std::string suffix;
368 
369     if (argc == 1) {
370         std::string_view filename = { argv[0] };
371         if (filename.ends_with(".png")) {
372             baseName = filename.substr(0, filename.size()-4);
373             suffix = ".png";
374             png = true;
375         } else if (filename.ends_with(".jpeg")) {
376             baseName = filename.substr(0, filename.size() - 5);
377             suffix = ".jpeg";
378             jpeg = true;
379         } else if (filename.ends_with(".jpg")) {
380             baseName = filename.substr(0, filename.size() - 4);
381             suffix = ".jpg";
382             jpeg = true;
383         } else {
384             baseName = filename;
385         }
386     }
387 
388     if (all) {
389         // Ignores -d if -a is given.
390         displaysToCapture.clear();
391         for (int i = 0; i < physicalDisplays.size(); i++) {
392             displaysToCapture.push_back(physicalDisplays[i]);
393         }
394     }
395 
396     if (displaysToCapture.empty()) {
397         displaysToCapture.push_back(physicalDisplays.front());
398         if (physicalDisplays.size() > 1) {
399             fprintf(stderr,
400                     "[Warning] Multiple displays were found, but no display id was specified! "
401                     "Defaulting to the first display found, however this default is not guaranteed "
402                     "to be consistent across captures. A display id should be specified.\n");
403             fprintf(stderr, "A display ID can be specified with the [-d display-id] option.\n");
404             fprintf(stderr, "See \"dumpsys SurfaceFlinger --display-id\" for valid display IDs.\n");
405         }
406     }
407 
408     if (png && jpeg) {
409         fprintf(stderr, "Ambiguous file type");
410         return 1;
411     }
412 
413     std::optional<AndroidBitmapCompressFormat> format = std::nullopt;
414 
415     if (png) {
416         format = ANDROID_BITMAP_COMPRESS_FORMAT_PNG;
417     } else if (jpeg) {
418         format = ANDROID_BITMAP_COMPRESS_FORMAT_JPEG;
419         captureArgs.attachGainmap = true;
420     }
421 
422     // setThreadPoolMaxThreadCount(0) actually tells the kernel it's
423     // not allowed to spawn any additional threads, but we still spawn
424     // a binder thread from userspace when we call startThreadPool().
425     // See b/36066697 for rationale
426     ProcessState::self()->setThreadPoolMaxThreadCount(0);
427     ProcessState::self()->startThreadPool();
428 
429     std::vector<ScreenCaptureResults> results;
430     const size_t numDisplays = displaysToCapture.size();
431     for (int i=0; i<numDisplays; i++) {
432         ScreenCaptureResults result;
433 
434         // 1. Capture the screen
435         if (const status_t captureStatus =
436             capture(displaysToCapture[i], captureArgs, result) != 0) {
437 
438             fprintf(stderr, "Capturing failed.\n");
439             return captureStatus;
440         }
441 
442         // 2. Save the capture result as an image.
443         // When there's more than one file to capture, add the index as postfix.
444         std::string filename;
445         if (!baseName.empty()) {
446             filename = baseName;
447             if (numDisplays > 1) {
448                 filename += "_";
449                 filename += std::to_string(i);
450             }
451             filename += suffix;
452         }
453         const char* fn = nullptr;
454         if (!filename.empty()) {
455             fn = filename.c_str();
456         }
457         if (const status_t saveImageStatus = saveImage(fn, format, result) != 0) {
458             fprintf(stderr, "Saving image failed.\n");
459             return saveImageStatus;
460         }
461     }
462 
463     return 0;
464 }
465