blob: 01b20f4a52674ae26f7cee37ef7b676f6af06adf [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>
18#include <android/gui/DisplayCaptureArgs.h>
19#include <binder/ProcessState.h>
Mike Lockwoodc59b2f92012-10-24 12:31:10 -070020#include <errno.h>
21#include <unistd.h>
22#include <stdio.h>
23#include <fcntl.h>
Umair Khancfed2322014-01-15 08:08:50 -050024#include <stdlib.h>
25#include <string.h>
John Reck4de6e742023-11-14 18:32:32 -050026#include <getopt.h>
Mike Lockwoodc59b2f92012-10-24 12:31:10 -070027
28#include <linux/fb.h>
29#include <sys/ioctl.h>
30#include <sys/mman.h>
Dichen Zhangd31f2192020-03-12 12:25:09 -070031#include <sys/wait.h>
Mike Lockwoodc59b2f92012-10-24 12:31:10 -070032
Derek Sollenbergera3ef0942020-04-08 15:47:55 -040033#include <android/bitmap.h>
34
Mathias Agopian0678a8c2013-03-19 20:56:00 -070035#include <binder/ProcessState.h>
36
Dominik Laskowski622c4ae2023-05-26 12:10:16 -040037#include <ftl/concat.h>
38#include <ftl/optional.h>
Chavi Weingarten797bdc92020-09-10 20:55:11 +000039#include <gui/ISurfaceComposer.h>
chaviwbc100492020-08-18 16:06:40 -070040#include <gui/SurfaceComposerClient.h>
41#include <gui/SyncScreenCaptureListener.h>
Mike Lockwoodc59b2f92012-10-24 12:31:10 -070042
Peiyong Lin10a34d12018-09-19 13:56:12 -070043#include <ui/GraphicTypes.h>
Mathias Agopian0137fb82013-03-20 15:38:07 -070044#include <ui/PixelFormat.h>
45
Romain Guy26a2b972017-04-17 09:39:51 -070046#include <system/graphics.h>
47
Mike Lockwoodc59b2f92012-10-24 12:31:10 -070048using namespace android;
49
Romain Guy26a2b972017-04-17 09:39:51 -070050#define COLORSPACE_UNKNOWN 0
51#define COLORSPACE_SRGB 1
52#define COLORSPACE_DISPLAY_P3 2
53
Dominik Laskowski622c4ae2023-05-26 12:10:16 -040054void usage(const char* pname, ftl::Optional<DisplayId> displayIdOpt) {
John Reck4de6e742023-11-14 18:32:32 -050055 fprintf(stderr, R"(
Yein Joe644f582024-02-15 17:55:15 +000056usage: %s [-ahp] [-d display-id] [FILENAME]
John Reck4de6e742023-11-14 18:32:32 -050057 -h: this message
Yein Joe644f582024-02-15 17:55:15 +000058 -a: captures all the active displays. This appends an integer postfix to the FILENAME.
59 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 -050060 -d: specify the display ID to capture%s
61 see "dumpsys SurfaceFlinger --display-id" for valid display IDs.
Yein Joe644f582024-02-15 17:55:15 +000062 -p: outputs in png format.
John Reck4de6e742023-11-14 18:32:32 -050063 --hint-for-seamless If set will use the hintForSeamless path in SF
64
65If FILENAME ends with .png it will be saved as a png.
66If FILENAME is not given, the results will be printed to stdout.
67)",
Dominik Laskowski622c4ae2023-05-26 12:10:16 -040068 pname,
69 displayIdOpt
Yein Joe644f582024-02-15 17:55:15 +000070 .transform([](DisplayId id) {
71 return std::string(ftl::Concat(
72 " (If the id is not given, it defaults to ", id.value,')'
73 ).str());
74 })
75 .value_or(std::string())
76 .c_str());
Mike Lockwoodc59b2f92012-10-24 12:31:10 -070077}
78
John Reck4de6e742023-11-14 18:32:32 -050079// For options that only exist in long-form. Anything in the
80// 0-255 range is reserved for short options (which just use their ASCII value)
81namespace LongOpts {
82enum {
83 Reserved = 255,
84 HintForSeamless,
85};
86}
87
88static const struct option LONG_OPTIONS[] = {
89 {"png", no_argument, nullptr, 'p'},
90 {"help", no_argument, nullptr, 'h'},
91 {"hint-for-seamless", no_argument, nullptr, LongOpts::HintForSeamless},
92 {0, 0, 0, 0}};
93
Derek Sollenbergera3ef0942020-04-08 15:47:55 -040094static int32_t flinger2bitmapFormat(PixelFormat f)
Mike Lockwoodc59b2f92012-10-24 12:31:10 -070095{
96 switch (f) {
Mike Lockwoodc59b2f92012-10-24 12:31:10 -070097 case PIXEL_FORMAT_RGB_565:
Derek Sollenbergera3ef0942020-04-08 15:47:55 -040098 return ANDROID_BITMAP_FORMAT_RGB_565;
Mike Lockwoodc59b2f92012-10-24 12:31:10 -070099 default:
Derek Sollenbergera3ef0942020-04-08 15:47:55 -0400100 return ANDROID_BITMAP_FORMAT_RGBA_8888;
Romain Guy26a2b972017-04-17 09:39:51 -0700101 }
102}
103
Peiyong Lin10a34d12018-09-19 13:56:12 -0700104static uint32_t dataSpaceToInt(ui::Dataspace d)
Romain Guy26a2b972017-04-17 09:39:51 -0700105{
106 switch (d) {
Peiyong Lin10a34d12018-09-19 13:56:12 -0700107 case ui::Dataspace::V0_SRGB:
Romain Guy26a2b972017-04-17 09:39:51 -0700108 return COLORSPACE_SRGB;
Peiyong Lin10a34d12018-09-19 13:56:12 -0700109 case ui::Dataspace::DISPLAY_P3:
Romain Guy26a2b972017-04-17 09:39:51 -0700110 return COLORSPACE_DISPLAY_P3;
111 default:
112 return COLORSPACE_UNKNOWN;
113 }
114}
115
Umair Khancfed2322014-01-15 08:08:50 -0500116static status_t notifyMediaScanner(const char* fileName) {
Dichen Zhangd31f2192020-03-12 12:25:09 -0700117 std::string filePath("file://");
118 filePath.append(fileName);
Dichen Zhangd31f2192020-03-12 12:25:09 -0700119 char *cmd[] = {
120 (char*) "am",
121 (char*) "broadcast",
Dichen Zhange361a262020-04-17 10:05:49 -0700122 (char*) "-a",
Dichen Zhangd31f2192020-03-12 12:25:09 -0700123 (char*) "android.intent.action.MEDIA_SCANNER_SCAN_FILE",
124 (char*) "-d",
George Burgess IV5c46fb62020-03-16 12:07:30 -0700125 &filePath[0],
Dichen Zhangd31f2192020-03-12 12:25:09 -0700126 nullptr
127 };
128
129 int status;
130 int pid = fork();
131 if (pid < 0){
Yein Joe644f582024-02-15 17:55:15 +0000132 fprintf(stderr, "Unable to fork in order to send intent for media scanner.\n");
133 return UNKNOWN_ERROR;
Dichen Zhangd31f2192020-03-12 12:25:09 -0700134 }
135 if (pid == 0){
136 int fd = open("/dev/null", O_WRONLY);
137 if (fd < 0){
138 fprintf(stderr, "Unable to open /dev/null for media scanner stdout redirection.\n");
139 exit(1);
140 }
141 dup2(fd, 1);
142 int result = execvp(cmd[0], cmd);
143 close(fd);
144 exit(result);
145 }
146 wait(&status);
147
148 if (status < 0) {
Umair Khancfed2322014-01-15 08:08:50 -0500149 fprintf(stderr, "Unable to broadcast intent for media scanner.\n");
150 return UNKNOWN_ERROR;
151 }
152 return NO_ERROR;
153}
154
Yein Joe644f582024-02-15 17:55:15 +0000155status_t capture(const DisplayId displayId,
156 const gui::CaptureArgs& captureArgs,
157 ScreenCaptureResults& outResult) {
chaviwbc100492020-08-18 16:06:40 -0700158 sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener();
Yein Joe644f582024-02-15 17:55:15 +0000159 ScreenshotClient::captureDisplay(displayId, captureArgs, captureListener);
Mike Lockwoodc59b2f92012-10-24 12:31:10 -0700160
chaviwbc100492020-08-18 16:06:40 -0700161 ScreenCaptureResults captureResults = captureListener->waitForResults();
Patrick Williams8d455722022-08-19 14:31:26 +0000162 if (!captureResults.fenceResult.ok()) {
jeimysantiago8ee92d12023-07-21 15:40:13 +0000163 fprintf(stderr, "Failed to take screenshot. Status: %d\n",
Yein Joe644f582024-02-15 17:55:15 +0000164 fenceStatus(captureResults.fenceResult));
chaviwbc100492020-08-18 16:06:40 -0700165 return 1;
166 }
Yein Joe644f582024-02-15 17:55:15 +0000167
168 outResult = captureResults;
169
170 return 0;
171}
172
173status_t saveImage(const char* fn, bool png, const ScreenCaptureResults& captureResults) {
174 void* base = nullptr;
chaviwbc27bc72020-07-27 16:44:35 -0700175 ui::Dataspace dataspace = captureResults.capturedDataspace;
176 sp<GraphicBuffer> buffer = captureResults.buffer;
177
jeimysantiago8ee92d12023-07-21 15:40:13 +0000178 status_t result = buffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &base);
Chavi Weingartend7ec64c2017-11-30 01:52:01 +0000179
chaviw7f4dc7e2018-09-11 13:59:30 -0700180 if (base == nullptr || result != NO_ERROR) {
181 String8 reason;
chaviwa69a1b82019-04-30 16:53:33 -0700182 if (result != NO_ERROR) {
183 reason.appendFormat(" Error Code: %d", result);
chaviw7f4dc7e2018-09-11 13:59:30 -0700184 } else {
chaviwa69a1b82019-04-30 16:53:33 -0700185 reason = "Failed to write to buffer";
chaviw7f4dc7e2018-09-11 13:59:30 -0700186 }
187 fprintf(stderr, "Failed to take screenshot (%s)\n", reason.c_str());
Steven Morelanda89ae862018-05-24 17:48:28 -0700188 return 1;
Chavi Weingartend7ec64c2017-11-30 01:52:01 +0000189 }
190
Yein Joe644f582024-02-15 17:55:15 +0000191 int fd = -1;
192 if (fn == nullptr) {
193 fd = dup(STDOUT_FILENO);
194 if (fd == -1) {
195 fprintf(stderr, "Error writing to stdout. (%s)\n", strerror(errno));
196 return 1;
197 }
198 } else {
199 fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664);
200 if (fd == -1) {
201 fprintf(stderr, "Error opening file: %s (%s)\n", fn, strerror(errno));
202 return 1;
203 }
204 }
205
Chavi Weingartend7ec64c2017-11-30 01:52:01 +0000206 if (png) {
Derek Sollenbergera3ef0942020-04-08 15:47:55 -0400207 AndroidBitmapInfo info;
chaviwbc27bc72020-07-27 16:44:35 -0700208 info.format = flinger2bitmapFormat(buffer->getPixelFormat());
Derek Sollenbergera3ef0942020-04-08 15:47:55 -0400209 info.flags = ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
chaviwbc27bc72020-07-27 16:44:35 -0700210 info.width = buffer->getWidth();
211 info.height = buffer->getHeight();
212 info.stride = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat());
Derek Sollenbergera3ef0942020-04-08 15:47:55 -0400213
chaviwbc27bc72020-07-27 16:44:35 -0700214 int result = AndroidBitmap_compress(&info, static_cast<int32_t>(dataspace), base,
Derek Sollenbergera3ef0942020-04-08 15:47:55 -0400215 ANDROID_BITMAP_COMPRESS_FORMAT_PNG, 100, &fd,
216 [](void* fdPtr, const void* data, size_t size) -> bool {
217 int bytesWritten = write(*static_cast<int*>(fdPtr),
218 data, size);
219 return bytesWritten == size;
220 });
221
222 if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
223 fprintf(stderr, "Failed to compress PNG (error code: %d)\n", result);
224 }
225
Chavi Weingartend7ec64c2017-11-30 01:52:01 +0000226 if (fn != NULL) {
227 notifyMediaScanner(fn);
228 }
229 } else {
chaviwbc27bc72020-07-27 16:44:35 -0700230 uint32_t w = buffer->getWidth();
231 uint32_t h = buffer->getHeight();
232 uint32_t s = buffer->getStride();
233 uint32_t f = buffer->getPixelFormat();
234 uint32_t c = dataSpaceToInt(dataspace);
Derek Sollenbergera3ef0942020-04-08 15:47:55 -0400235
Chavi Weingartend7ec64c2017-11-30 01:52:01 +0000236 write(fd, &w, 4);
237 write(fd, &h, 4);
238 write(fd, &f, 4);
239 write(fd, &c, 4);
240 size_t Bpp = bytesPerPixel(f);
241 for (size_t y=0 ; y<h ; y++) {
242 write(fd, base, w*Bpp);
243 base = (void *)((char *)base + s*Bpp);
Mike Lockwoodc59b2f92012-10-24 12:31:10 -0700244 }
245 }
246 close(fd);
Josh Gao90982582017-06-19 13:38:20 -0700247
Steven Morelanda89ae862018-05-24 17:48:28 -0700248 return 0;
Peiyong Lin10a34d12018-09-19 13:56:12 -0700249}
Yein Joe644f582024-02-15 17:55:15 +0000250
251int main(int argc, char** argv)
252{
253 const std::vector<PhysicalDisplayId> physicalDisplays =
254 SurfaceComposerClient::getPhysicalDisplayIds();
255
256 if (physicalDisplays.empty()) {
257 fprintf(stderr, "Failed to get ID for any displays.\n");
258 return 1;
259 }
260 std::optional<DisplayId> displayIdOpt;
261 std::vector<DisplayId> displaysToCapture;
262 gui::CaptureArgs captureArgs;
263 const char* pname = argv[0];
264 bool png = false;
265 bool all = false;
266 int c;
267 while ((c = getopt_long(argc, argv, "aphd:", LONG_OPTIONS, nullptr)) != -1) {
268 switch (c) {
269 case 'p':
270 png = true;
271 break;
272 case 'd': {
273 errno = 0;
274 char* end = nullptr;
275 const uint64_t id = strtoull(optarg, &end, 10);
276 if (!end || *end != '\0' || errno == ERANGE) {
277 fprintf(stderr, "Invalid display ID: Out of range [0, 2^64).\n");
278 return 1;
279 }
280
281 displayIdOpt = DisplayId::fromValue(id);
282 if (!displayIdOpt) {
283 fprintf(stderr, "Invalid display ID: Incorrect encoding.\n");
284 return 1;
285 }
286 displaysToCapture.push_back(displayIdOpt.value());
287 break;
288 }
289 case 'a': {
290 all = true;
291 break;
292 }
293 case '?':
294 case 'h':
295 if (physicalDisplays.size() >= 1) {
296 displayIdOpt = physicalDisplays.front();
297 }
298 usage(pname, displayIdOpt);
299 return 1;
300 case LongOpts::HintForSeamless:
301 captureArgs.hintForSeamlessTransition = true;
302 break;
303 }
304 }
305
306 argc -= optind;
307 argv += optind;
308
309 // We don't expect more than 2 arguments.
310 if (argc >= 2) {
311 if (physicalDisplays.size() >= 1) {
312 usage(pname, physicalDisplays.front());
313 } else {
314 usage(pname, std::nullopt);
315 }
316 return 1;
317 }
318
319 std::string baseName;
320 std::string suffix;
321
322 if (argc == 1) {
323 std::string_view filename = { argv[0] };
324 if (filename.ends_with(".png")) {
325 baseName = filename.substr(0, filename.size()-4);
326 suffix = ".png";
327 png = true;
328 } else {
329 baseName = filename;
330 }
331 }
332
333 if (all) {
334 // Ignores -d if -a is given.
335 displaysToCapture.clear();
336 for (int i = 0; i < physicalDisplays.size(); i++) {
337 displaysToCapture.push_back(physicalDisplays[i]);
338 }
339 }
340
341 if (displaysToCapture.empty()) {
342 displaysToCapture.push_back(physicalDisplays.front());
343 if (physicalDisplays.size() > 1) {
344 fprintf(stderr,
345 "[Warning] Multiple displays were found, but no display id was specified! "
346 "Defaulting to the first display found, however this default is not guaranteed "
347 "to be consistent across captures. A display id should be specified.\n");
348 fprintf(stderr, "A display ID can be specified with the [-d display-id] option.\n");
349 fprintf(stderr, "See \"dumpsys SurfaceFlinger --display-id\" for valid display IDs.\n");
350 }
351 }
352
353 // setThreadPoolMaxThreadCount(0) actually tells the kernel it's
354 // not allowed to spawn any additional threads, but we still spawn
355 // a binder thread from userspace when we call startThreadPool().
356 // See b/36066697 for rationale
357 ProcessState::self()->setThreadPoolMaxThreadCount(0);
358 ProcessState::self()->startThreadPool();
359
360 std::vector<ScreenCaptureResults> results;
361 const size_t numDisplays = displaysToCapture.size();
362 for (int i=0; i<numDisplays; i++) {
363 ScreenCaptureResults result;
364
365 // 1. Capture the screen
366 if (const status_t captureStatus =
367 capture(displaysToCapture[i], captureArgs, result) != 0) {
368
369 fprintf(stderr, "Capturing failed.\n");
370 return captureStatus;
371 }
372
373 // 2. Save the capture result as an image.
374 // When there's more than one file to capture, add the index as postfix.
375 std::string filename;
376 if (!baseName.empty()) {
377 filename = baseName;
378 if (numDisplays > 1) {
379 filename += "_";
380 filename += std::to_string(i);
381 }
382 filename += suffix;
383 }
384 const char* fn = nullptr;
385 if (!filename.empty()) {
386 fn = filename.c_str();
387 }
388 if (const status_t saveImageStatus = saveImage(fn, png, result) != 0) {
389 fprintf(stderr, "Saving image failed.\n");
390 return saveImageStatus;
391 }
392 }
393
394 return 0;
395}