blob: 12de82a462632dd59da4c49522681474b0c7fc3d [file] [log] [blame]
Mike Lockwoodc59b2f92012-10-24 12:31:10 -07001/*
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
Alec Mourif4d1b622024-08-06 14:56:34 +000017#include <android/bitmap.h>
Alec Mouri6f6679b2024-07-15 22:43:54 +000018#include <android/graphics/bitmap.h>
Alec Mourif4d1b622024-08-06 14:56:34 +000019#include <android/gui/DisplayCaptureArgs.h>
20#include <binder/ProcessState.h>
Mike Lockwoodc59b2f92012-10-24 12:31:10 -070021#include <errno.h>
Mike Lockwoodc59b2f92012-10-24 12:31:10 -070022#include <fcntl.h>
Dominik Laskowski622c4ae2023-05-26 12:10:16 -040023#include <ftl/concat.h>
24#include <ftl/optional.h>
Alec Mouri6f6679b2024-07-15 22:43:54 +000025#include <getopt.h>
Chavi Weingarten797bdc92020-09-10 20:55:11 +000026#include <gui/ISurfaceComposer.h>
chaviwbc100492020-08-18 16:06:40 -070027#include <gui/SurfaceComposerClient.h>
28#include <gui/SyncScreenCaptureListener.h>
Alec Mouri6f6679b2024-07-15 22:43:54 +000029#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>
Peiyong Lin10a34d12018-09-19 13:56:12 -070037#include <ui/GraphicTypes.h>
Mathias Agopian0137fb82013-03-20 15:38:07 -070038#include <ui/PixelFormat.h>
39
Mike Lockwoodc59b2f92012-10-24 12:31:10 -070040using namespace android;
41
Romain Guy26a2b972017-04-17 09:39:51 -070042#define COLORSPACE_UNKNOWN 0
43#define COLORSPACE_SRGB 1
44#define COLORSPACE_DISPLAY_P3 2
45
Dominik Laskowski622c4ae2023-05-26 12:10:16 -040046void usage(const char* pname, ftl::Optional<DisplayId> displayIdOpt) {
John Reck4de6e742023-11-14 18:32:32 -050047 fprintf(stderr, R"(
Yein Joe644f582024-02-15 17:55:15 +000048usage: %s [-ahp] [-d display-id] [FILENAME]
John Reck4de6e742023-11-14 18:32:32 -050049 -h: this message
Yein Joe644f582024-02-15 17:55:15 +000050 -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.
John Reck4de6e742023-11-14 18:32:32 -050052 -d: specify the display ID to capture%s
53 see "dumpsys SurfaceFlinger --display-id" for valid display IDs.
Yein Joe644f582024-02-15 17:55:15 +000054 -p: outputs in png format.
John Reck4de6e742023-11-14 18:32:32 -050055 --hint-for-seamless If set will use the hintForSeamless path in SF
56
57If FILENAME ends with .png it will be saved as a png.
58If FILENAME is not given, the results will be printed to stdout.
59)",
Dominik Laskowski622c4ae2023-05-26 12:10:16 -040060 pname,
61 displayIdOpt
Yein Joe644f582024-02-15 17:55:15 +000062 .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());
Mike Lockwoodc59b2f92012-10-24 12:31:10 -070069}
70
John Reck4de6e742023-11-14 18:32:32 -050071// 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)
73namespace LongOpts {
74enum {
75 Reserved = 255,
76 HintForSeamless,
77};
78}
79
Alec Mouri6f6679b2024-07-15 22:43:54 +000080static 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}};
John Reck4de6e742023-11-14 18:32:32 -050086
Derek Sollenbergera3ef0942020-04-08 15:47:55 -040087static int32_t flinger2bitmapFormat(PixelFormat f)
Mike Lockwoodc59b2f92012-10-24 12:31:10 -070088{
89 switch (f) {
Mike Lockwoodc59b2f92012-10-24 12:31:10 -070090 case PIXEL_FORMAT_RGB_565:
Derek Sollenbergera3ef0942020-04-08 15:47:55 -040091 return ANDROID_BITMAP_FORMAT_RGB_565;
Mike Lockwoodc59b2f92012-10-24 12:31:10 -070092 default:
Derek Sollenbergera3ef0942020-04-08 15:47:55 -040093 return ANDROID_BITMAP_FORMAT_RGBA_8888;
Romain Guy26a2b972017-04-17 09:39:51 -070094 }
95}
96
Peiyong Lin10a34d12018-09-19 13:56:12 -070097static uint32_t dataSpaceToInt(ui::Dataspace d)
Romain Guy26a2b972017-04-17 09:39:51 -070098{
99 switch (d) {
Peiyong Lin10a34d12018-09-19 13:56:12 -0700100 case ui::Dataspace::V0_SRGB:
Romain Guy26a2b972017-04-17 09:39:51 -0700101 return COLORSPACE_SRGB;
Peiyong Lin10a34d12018-09-19 13:56:12 -0700102 case ui::Dataspace::DISPLAY_P3:
Romain Guy26a2b972017-04-17 09:39:51 -0700103 return COLORSPACE_DISPLAY_P3;
104 default:
105 return COLORSPACE_UNKNOWN;
106 }
107}
108
Umair Khancfed2322014-01-15 08:08:50 -0500109static status_t notifyMediaScanner(const char* fileName) {
Dichen Zhangd31f2192020-03-12 12:25:09 -0700110 std::string filePath("file://");
111 filePath.append(fileName);
Dichen Zhangd31f2192020-03-12 12:25:09 -0700112 char *cmd[] = {
113 (char*) "am",
114 (char*) "broadcast",
Dichen Zhange361a262020-04-17 10:05:49 -0700115 (char*) "-a",
Dichen Zhangd31f2192020-03-12 12:25:09 -0700116 (char*) "android.intent.action.MEDIA_SCANNER_SCAN_FILE",
117 (char*) "-d",
George Burgess IV5c46fb62020-03-16 12:07:30 -0700118 &filePath[0],
Dichen Zhangd31f2192020-03-12 12:25:09 -0700119 nullptr
120 };
121
122 int status;
123 int pid = fork();
124 if (pid < 0){
Yein Joe644f582024-02-15 17:55:15 +0000125 fprintf(stderr, "Unable to fork in order to send intent for media scanner.\n");
126 return UNKNOWN_ERROR;
Dichen Zhangd31f2192020-03-12 12:25:09 -0700127 }
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) {
Umair Khancfed2322014-01-15 08:08:50 -0500142 fprintf(stderr, "Unable to broadcast intent for media scanner.\n");
143 return UNKNOWN_ERROR;
144 }
145 return NO_ERROR;
146}
147
Yein Joe644f582024-02-15 17:55:15 +0000148status_t capture(const DisplayId displayId,
149 const gui::CaptureArgs& captureArgs,
150 ScreenCaptureResults& outResult) {
chaviwbc100492020-08-18 16:06:40 -0700151 sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener();
Yein Joe644f582024-02-15 17:55:15 +0000152 ScreenshotClient::captureDisplay(displayId, captureArgs, captureListener);
Mike Lockwoodc59b2f92012-10-24 12:31:10 -0700153
chaviwbc100492020-08-18 16:06:40 -0700154 ScreenCaptureResults captureResults = captureListener->waitForResults();
Patrick Williams8d455722022-08-19 14:31:26 +0000155 if (!captureResults.fenceResult.ok()) {
jeimysantiago8ee92d12023-07-21 15:40:13 +0000156 fprintf(stderr, "Failed to take screenshot. Status: %d\n",
Yein Joe644f582024-02-15 17:55:15 +0000157 fenceStatus(captureResults.fenceResult));
chaviwbc100492020-08-18 16:06:40 -0700158 return 1;
159 }
Yein Joe644f582024-02-15 17:55:15 +0000160
161 outResult = captureResults;
162
163 return 0;
164}
165
Alec Mouri6f6679b2024-07-15 22:43:54 +0000166status_t saveImage(const char* fn, std::optional<AndroidBitmapCompressFormat> format,
167 const ScreenCaptureResults& captureResults) {
Yein Joe644f582024-02-15 17:55:15 +0000168 void* base = nullptr;
chaviwbc27bc72020-07-27 16:44:35 -0700169 ui::Dataspace dataspace = captureResults.capturedDataspace;
Alec Mouri6f6679b2024-07-15 22:43:54 +0000170 const sp<GraphicBuffer>& buffer = captureResults.buffer;
chaviwbc27bc72020-07-27 16:44:35 -0700171
jeimysantiago8ee92d12023-07-21 15:40:13 +0000172 status_t result = buffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &base);
Chavi Weingartend7ec64c2017-11-30 01:52:01 +0000173
chaviw7f4dc7e2018-09-11 13:59:30 -0700174 if (base == nullptr || result != NO_ERROR) {
175 String8 reason;
chaviwa69a1b82019-04-30 16:53:33 -0700176 if (result != NO_ERROR) {
177 reason.appendFormat(" Error Code: %d", result);
chaviw7f4dc7e2018-09-11 13:59:30 -0700178 } else {
chaviwa69a1b82019-04-30 16:53:33 -0700179 reason = "Failed to write to buffer";
chaviw7f4dc7e2018-09-11 13:59:30 -0700180 }
181 fprintf(stderr, "Failed to take screenshot (%s)\n", reason.c_str());
Steven Morelanda89ae862018-05-24 17:48:28 -0700182 return 1;
Chavi Weingartend7ec64c2017-11-30 01:52:01 +0000183 }
184
Alec Mouri6f6679b2024-07-15 22:43:54 +0000185 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
Yein Joe644f582024-02-15 17:55:15 +0000197 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));
Alec Mouri6f6679b2024-07-15 22:43:54 +0000202 if (gainmapBase) {
203 gainmap->unlock();
204 }
205
206 if (base) {
207 buffer->unlock();
208 }
Yein Joe644f582024-02-15 17:55:15 +0000209 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));
Alec Mouri6f6679b2024-07-15 22:43:54 +0000215 if (gainmapBase) {
216 gainmap->unlock();
217 }
218
219 if (base) {
220 buffer->unlock();
221 }
Yein Joe644f582024-02-15 17:55:15 +0000222 return 1;
223 }
224 }
225
Alec Mouri6f6679b2024-07-15 22:43:54 +0000226 if (format) {
Derek Sollenbergera3ef0942020-04-08 15:47:55 -0400227 AndroidBitmapInfo info;
chaviwbc27bc72020-07-27 16:44:35 -0700228 info.format = flinger2bitmapFormat(buffer->getPixelFormat());
Derek Sollenbergera3ef0942020-04-08 15:47:55 -0400229 info.flags = ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
chaviwbc27bc72020-07-27 16:44:35 -0700230 info.width = buffer->getWidth();
231 info.height = buffer->getHeight();
232 info.stride = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat());
Derek Sollenbergera3ef0942020-04-08 15:47:55 -0400233
Alec Mouri6f6679b2024-07-15 22:43:54 +0000234 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,
Derek Sollenbergera3ef0942020-04-08 15:47:55 -0400250 [](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 });
Alec Mouri6f6679b2024-07-15 22:43:54 +0000255 }
Derek Sollenbergera3ef0942020-04-08 15:47:55 -0400256
257 if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
Alec Mouri6f6679b2024-07-15 22:43:54 +0000258 fprintf(stderr, "Failed to compress (error code: %d)\n", result);
Derek Sollenbergera3ef0942020-04-08 15:47:55 -0400259 }
260
Chavi Weingartend7ec64c2017-11-30 01:52:01 +0000261 if (fn != NULL) {
262 notifyMediaScanner(fn);
263 }
264 } else {
chaviwbc27bc72020-07-27 16:44:35 -0700265 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);
Derek Sollenbergera3ef0942020-04-08 15:47:55 -0400270
Chavi Weingartend7ec64c2017-11-30 01:52:01 +0000271 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);
Mike Lockwoodc59b2f92012-10-24 12:31:10 -0700279 }
280 }
281 close(fd);
Josh Gao90982582017-06-19 13:38:20 -0700282
Alec Mouri6f6679b2024-07-15 22:43:54 +0000283 if (gainmapBase) {
284 gainmap->unlock();
285 }
286
287 if (base) {
288 buffer->unlock();
289 }
290
Steven Morelanda89ae862018-05-24 17:48:28 -0700291 return 0;
Peiyong Lin10a34d12018-09-19 13:56:12 -0700292}
Yein Joe644f582024-02-15 17:55:15 +0000293
294int 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;
Alec Mouri6f6679b2024-07-15 22:43:54 +0000308 bool jpeg = false;
Yein Joe644f582024-02-15 17:55:15 +0000309 bool all = false;
310 int c;
Alec Mouri6f6679b2024-07-15 22:43:54 +0000311 while ((c = getopt_long(argc, argv, "apjhd:", LONG_OPTIONS, nullptr)) != -1) {
Yein Joe644f582024-02-15 17:55:15 +0000312 switch (c) {
313 case 'p':
314 png = true;
315 break;
Alec Mouri6f6679b2024-07-15 22:43:54 +0000316 case 'j':
317 jpeg = true;
318 break;
Yein Joe644f582024-02-15 17:55:15 +0000319 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;
Alec Mouri6f6679b2024-07-15 22:43:54 +0000375 } 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;
Yein Joe644f582024-02-15 17:55:15 +0000383 } 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
Alec Mouri6f6679b2024-07-15 22:43:54 +0000408 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
Yein Joe644f582024-02-15 17:55:15 +0000422 // 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 }
Alec Mouri6f6679b2024-07-15 22:43:54 +0000457 if (const status_t saveImageStatus = saveImage(fn, format, result) != 0) {
Yein Joe644f582024-02-15 17:55:15 +0000458 fprintf(stderr, "Saving image failed.\n");
459 return saveImageStatus;
460 }
461 }
462
463 return 0;
464}