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