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