screenrecord.cpp revision 2c041c1c927fdbf60f53f1a1960e5155a79007b7
1/*
2 * Copyright 2013 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#define LOG_TAG "ScreenRecord"
18//#define LOG_NDEBUG 0
19#include <utils/Log.h>
20
21#include <binder/IPCThreadState.h>
22#include <utils/Errors.h>
23#include <utils/Thread.h>
24#include <utils/Timers.h>
25
26#include <gui/Surface.h>
27#include <gui/SurfaceComposerClient.h>
28#include <gui/ISurfaceComposer.h>
29#include <ui/DisplayInfo.h>
30#include <media/openmax/OMX_IVCommon.h>
31#include <media/stagefright/foundation/ABuffer.h>
32#include <media/stagefright/foundation/ADebug.h>
33#include <media/stagefright/foundation/AMessage.h>
34#include <media/stagefright/MediaCodec.h>
35#include <media/stagefright/MediaErrors.h>
36#include <media/stagefright/MediaMuxer.h>
37#include <media/ICrypto.h>
38
39#include <stdlib.h>
40#include <string.h>
41#include <stdio.h>
42#include <fcntl.h>
43#include <signal.h>
44#include <getopt.h>
45
46using namespace android;
47
48static const uint32_t kMinBitRate = 100000;         // 0.1Mbps
49static const uint32_t kMaxBitRate = 100 * 1000000;  // 100Mbps
50static const uint32_t kMaxTimeLimitSec = 180;       // 3 minutes
51static const uint32_t kFallbackWidth = 1280;        // 720p
52static const uint32_t kFallbackHeight = 720;
53
54// Command-line parameters.
55static bool gVerbose = false;               // chatty on stdout
56static bool gRotate = false;                // rotate 90 degrees
57static bool gSizeSpecified = false;         // was size explicitly requested?
58static uint32_t gVideoWidth = 0;            // default width+height
59static uint32_t gVideoHeight = 0;
60static uint32_t gBitRate = 4000000;         // 4Mbps
61static uint32_t gTimeLimitSec = kMaxTimeLimitSec;
62
63// Set by signal handler to stop recording.
64static bool gStopRequested;
65
66// Previous signal handler state, restored after first hit.
67static struct sigaction gOrigSigactionINT;
68static struct sigaction gOrigSigactionHUP;
69
70
71/*
72 * Catch keyboard interrupt signals.  On receipt, the "stop requested"
73 * flag is raised, and the original handler is restored (so that, if
74 * we get stuck finishing, a second Ctrl-C will kill the process).
75 */
76static void signalCatcher(int signum)
77{
78    gStopRequested = true;
79    switch (signum) {
80    case SIGINT:
81    case SIGHUP:
82        sigaction(SIGINT, &gOrigSigactionINT, NULL);
83        sigaction(SIGHUP, &gOrigSigactionHUP, NULL);
84        break;
85    default:
86        abort();
87        break;
88    }
89}
90
91/*
92 * Configures signal handlers.  The previous handlers are saved.
93 *
94 * If the command is run from an interactive adb shell, we get SIGINT
95 * when Ctrl-C is hit.  If we're run from the host, the local adb process
96 * gets the signal, and we get a SIGHUP when the terminal disconnects.
97 */
98static status_t configureSignals()
99{
100    struct sigaction act;
101    memset(&act, 0, sizeof(act));
102    act.sa_handler = signalCatcher;
103    if (sigaction(SIGINT, &act, &gOrigSigactionINT) != 0) {
104        status_t err = -errno;
105        fprintf(stderr, "Unable to configure SIGINT handler: %s\n",
106                strerror(errno));
107        return err;
108    }
109    if (sigaction(SIGHUP, &act, &gOrigSigactionHUP) != 0) {
110        status_t err = -errno;
111        fprintf(stderr, "Unable to configure SIGHUP handler: %s\n",
112                strerror(errno));
113        return err;
114    }
115    return NO_ERROR;
116}
117
118/*
119 * Returns "true" if the device is rotated 90 degrees.
120 */
121static bool isDeviceRotated(int orientation) {
122    return orientation != DISPLAY_ORIENTATION_0 &&
123            orientation != DISPLAY_ORIENTATION_180;
124}
125
126/*
127 * Configures and starts the MediaCodec encoder.  Obtains an input surface
128 * from the codec.
129 */
130static status_t prepareEncoder(float displayFps, sp<MediaCodec>* pCodec,
131        sp<IGraphicBufferProducer>* pBufferProducer) {
132    status_t err;
133
134    if (gVerbose) {
135        printf("Configuring recorder for %dx%d video at %.2fMbps\n",
136                gVideoWidth, gVideoHeight, gBitRate / 1000000.0);
137    }
138
139    sp<AMessage> format = new AMessage;
140    format->setInt32("width", gVideoWidth);
141    format->setInt32("height", gVideoHeight);
142    format->setString("mime", "video/avc");
143    format->setInt32("color-format", OMX_COLOR_FormatAndroidOpaque);
144    format->setInt32("bitrate", gBitRate);
145    format->setFloat("frame-rate", displayFps);
146    format->setInt32("i-frame-interval", 10);
147
148    sp<ALooper> looper = new ALooper;
149    looper->setName("screenrecord_looper");
150    looper->start();
151    ALOGV("Creating codec");
152    sp<MediaCodec> codec = MediaCodec::CreateByType(looper, "video/avc", true);
153    if (codec == NULL) {
154        fprintf(stderr, "ERROR: unable to create video/avc codec instance\n");
155        return UNKNOWN_ERROR;
156    }
157    err = codec->configure(format, NULL, NULL,
158            MediaCodec::CONFIGURE_FLAG_ENCODE);
159    if (err != NO_ERROR) {
160        fprintf(stderr, "ERROR: unable to configure codec (err=%d)\n", err);
161        return err;
162    }
163
164    ALOGV("Creating buffer producer");
165    sp<IGraphicBufferProducer> bufferProducer;
166    err = codec->createInputSurface(&bufferProducer);
167    if (err != NO_ERROR) {
168        fprintf(stderr,
169            "ERROR: unable to create encoder input surface (err=%d)\n", err);
170        return err;
171    }
172
173    ALOGV("Starting codec");
174    err = codec->start();
175    if (err != NO_ERROR) {
176        fprintf(stderr, "ERROR: unable to start codec (err=%d)\n", err);
177        return err;
178    }
179
180    ALOGV("Codec prepared");
181    *pCodec = codec;
182    *pBufferProducer = bufferProducer;
183    return 0;
184}
185
186/*
187 * Configures the virtual display.  When this completes, virtual display
188 * frames will start being sent to the encoder's surface.
189 */
190static status_t prepareVirtualDisplay(const DisplayInfo& mainDpyInfo,
191        const sp<IGraphicBufferProducer>& bufferProducer,
192        sp<IBinder>* pDisplayHandle) {
193    status_t err;
194
195    // Set the region of the layer stack we're interested in, which in our
196    // case is "all of it".  If the app is rotated (so that the width of the
197    // app is based on the height of the display), reverse width/height.
198    bool deviceRotated = isDeviceRotated(mainDpyInfo.orientation);
199    uint32_t sourceWidth, sourceHeight;
200    if (!deviceRotated) {
201        sourceWidth = mainDpyInfo.w;
202        sourceHeight = mainDpyInfo.h;
203    } else {
204        ALOGV("using rotated width/height");
205        sourceHeight = mainDpyInfo.w;
206        sourceWidth = mainDpyInfo.h;
207    }
208    Rect layerStackRect(sourceWidth, sourceHeight);
209
210    // We need to preserve the aspect ratio of the display.
211    float displayAspect = (float) sourceHeight / (float) sourceWidth;
212
213
214    // Set the way we map the output onto the display surface (which will
215    // be e.g. 1280x720 for a 720p video).  The rect is interpreted
216    // post-rotation, so if the display is rotated 90 degrees we need to
217    // "pre-rotate" it by flipping width/height, so that the orientation
218    // adjustment changes it back.
219    //
220    // We might want to encode a portrait display as landscape to use more
221    // of the screen real estate.  (If players respect a 90-degree rotation
222    // hint, we can essentially get a 720x1280 video instead of 1280x720.)
223    // In that case, we swap the configured video width/height and then
224    // supply a rotation value to the display projection.
225    uint32_t videoWidth, videoHeight;
226    uint32_t outWidth, outHeight;
227    if (!gRotate) {
228        videoWidth = gVideoWidth;
229        videoHeight = gVideoHeight;
230    } else {
231        videoWidth = gVideoHeight;
232        videoHeight = gVideoWidth;
233    }
234    if (videoHeight > (uint32_t)(videoWidth * displayAspect)) {
235        // limited by narrow width; reduce height
236        outWidth = videoWidth;
237        outHeight = (uint32_t)(videoWidth * displayAspect);
238    } else {
239        // limited by short height; restrict width
240        outHeight = videoHeight;
241        outWidth = (uint32_t)(videoHeight / displayAspect);
242    }
243    uint32_t offX, offY;
244    offX = (videoWidth - outWidth) / 2;
245    offY = (videoHeight - outHeight) / 2;
246    Rect displayRect(offX, offY, offX + outWidth, offY + outHeight);
247
248    if (gVerbose) {
249        if (gRotate) {
250            printf("Rotated content area is %ux%u at offset x=%d y=%d\n",
251                    outHeight, outWidth, offY, offX);
252        } else {
253            printf("Content area is %ux%u at offset x=%d y=%d\n",
254                    outWidth, outHeight, offX, offY);
255        }
256    }
257
258
259    sp<IBinder> dpy = SurfaceComposerClient::createDisplay(
260            String8("ScreenRecorder"), false /* secure */);
261
262    SurfaceComposerClient::openGlobalTransaction();
263    SurfaceComposerClient::setDisplaySurface(dpy, bufferProducer);
264    SurfaceComposerClient::setDisplayProjection(dpy,
265            gRotate ? DISPLAY_ORIENTATION_90 : DISPLAY_ORIENTATION_0,
266            layerStackRect, displayRect);
267    SurfaceComposerClient::setDisplayLayerStack(dpy, 0);    // default stack
268    SurfaceComposerClient::closeGlobalTransaction();
269
270    *pDisplayHandle = dpy;
271
272    return NO_ERROR;
273}
274
275/*
276 * Runs the MediaCodec encoder, sending the output to the MediaMuxer.  The
277 * input frames are coming from the virtual display as fast as SurfaceFlinger
278 * wants to send them.
279 *
280 * The muxer must *not* have been started before calling.
281 */
282static status_t runEncoder(const sp<MediaCodec>& encoder,
283        const sp<MediaMuxer>& muxer) {
284    static int kTimeout = 250000;   // be responsive on signal
285    status_t err;
286    ssize_t trackIdx = -1;
287    uint32_t debugNumFrames = 0;
288    int64_t startWhenNsec = systemTime(CLOCK_MONOTONIC);
289    int64_t endWhenNsec = startWhenNsec + seconds_to_nanoseconds(gTimeLimitSec);
290
291    Vector<sp<ABuffer> > buffers;
292    err = encoder->getOutputBuffers(&buffers);
293    if (err != NO_ERROR) {
294        fprintf(stderr, "Unable to get output buffers (err=%d)\n", err);
295        return err;
296    }
297
298    // This is set by the signal handler.
299    gStopRequested = false;
300
301    // Run until we're signaled.
302    while (!gStopRequested) {
303        size_t bufIndex, offset, size;
304        int64_t ptsUsec;
305        uint32_t flags;
306
307        if (systemTime(CLOCK_MONOTONIC) > endWhenNsec) {
308            if (gVerbose) {
309                printf("Time limit reached\n");
310            }
311            break;
312        }
313
314        ALOGV("Calling dequeueOutputBuffer");
315        err = encoder->dequeueOutputBuffer(&bufIndex, &offset, &size, &ptsUsec,
316                &flags, kTimeout);
317        ALOGV("dequeueOutputBuffer returned %d", err);
318        switch (err) {
319        case NO_ERROR:
320            // got a buffer
321            if ((flags & MediaCodec::BUFFER_FLAG_CODECCONFIG) != 0) {
322                // ignore this -- we passed the CSD into MediaMuxer when
323                // we got the format change notification
324                ALOGV("Got codec config buffer (%u bytes); ignoring", size);
325                size = 0;
326            }
327            if (size != 0) {
328                ALOGV("Got data in buffer %d, size=%d, pts=%lld",
329                        bufIndex, size, ptsUsec);
330                CHECK(trackIdx != -1);
331
332                // If the virtual display isn't providing us with timestamps,
333                // use the current time.
334                if (ptsUsec == 0) {
335                    ptsUsec = systemTime(SYSTEM_TIME_MONOTONIC) / 1000;
336                }
337
338                // The MediaMuxer docs are unclear, but it appears that we
339                // need to pass either the full set of BufferInfo flags, or
340                // (flags & BUFFER_FLAG_SYNCFRAME).
341                err = muxer->writeSampleData(buffers[bufIndex], trackIdx,
342                        ptsUsec, flags);
343                if (err != NO_ERROR) {
344                    fprintf(stderr, "Failed writing data to muxer (err=%d)\n",
345                            err);
346                    return err;
347                }
348                debugNumFrames++;
349            }
350            err = encoder->releaseOutputBuffer(bufIndex);
351            if (err != NO_ERROR) {
352                fprintf(stderr, "Unable to release output buffer (err=%d)\n",
353                        err);
354                return err;
355            }
356            if ((flags & MediaCodec::BUFFER_FLAG_EOS) != 0) {
357                // Not expecting EOS from SurfaceFlinger.  Go with it.
358                ALOGD("Received end-of-stream");
359                gStopRequested = false;
360            }
361            break;
362        case -EAGAIN:                       // INFO_TRY_AGAIN_LATER
363            ALOGV("Got -EAGAIN, looping");
364            break;
365        case INFO_FORMAT_CHANGED:           // INFO_OUTPUT_FORMAT_CHANGED
366            {
367                // format includes CSD, which we must provide to muxer
368                ALOGV("Encoder format changed");
369                sp<AMessage> newFormat;
370                encoder->getOutputFormat(&newFormat);
371                trackIdx = muxer->addTrack(newFormat);
372                ALOGV("Starting muxer");
373                err = muxer->start();
374                if (err != NO_ERROR) {
375                    fprintf(stderr, "Unable to start muxer (err=%d)\n", err);
376                    return err;
377                }
378            }
379            break;
380        case INFO_OUTPUT_BUFFERS_CHANGED:   // INFO_OUTPUT_BUFFERS_CHANGED
381            // not expected for an encoder; handle it anyway
382            ALOGV("Encoder buffers changed");
383            err = encoder->getOutputBuffers(&buffers);
384            if (err != NO_ERROR) {
385                fprintf(stderr,
386                        "Unable to get new output buffers (err=%d)\n", err);
387                return err;
388            }
389            break;
390        case INVALID_OPERATION:
391            fprintf(stderr, "Request for encoder buffer failed\n");
392            return err;
393        default:
394            fprintf(stderr,
395                    "Got weird result %d from dequeueOutputBuffer\n", err);
396            return err;
397        }
398    }
399
400    ALOGV("Encoder stopping (req=%d)", gStopRequested);
401    if (gVerbose) {
402        printf("Encoder stopping; recorded %u frames in %lld seconds\n",
403                debugNumFrames,
404                nanoseconds_to_seconds(systemTime(CLOCK_MONOTONIC) - startWhenNsec));
405    }
406    return NO_ERROR;
407}
408
409/*
410 * Main "do work" method.
411 *
412 * Configures codec, muxer, and virtual display, then starts moving bits
413 * around.
414 */
415static status_t recordScreen(const char* fileName) {
416    status_t err;
417
418    // Configure signal handler.
419    err = configureSignals();
420    if (err != NO_ERROR) return err;
421
422    // Start Binder thread pool.  MediaCodec needs to be able to receive
423    // messages from mediaserver.
424    sp<ProcessState> self = ProcessState::self();
425    self->startThreadPool();
426
427    // Get main display parameters.
428    sp<IBinder> mainDpy = SurfaceComposerClient::getBuiltInDisplay(
429            ISurfaceComposer::eDisplayIdMain);
430    DisplayInfo mainDpyInfo;
431    err = SurfaceComposerClient::getDisplayInfo(mainDpy, &mainDpyInfo);
432    if (err != NO_ERROR) {
433        fprintf(stderr, "ERROR: unable to get display characteristics\n");
434        return err;
435    }
436    if (gVerbose) {
437        printf("Main display is %dx%d @%.2ffps (orientation=%u)\n",
438                mainDpyInfo.w, mainDpyInfo.h, mainDpyInfo.fps,
439                mainDpyInfo.orientation);
440    }
441
442    bool rotated = isDeviceRotated(mainDpyInfo.orientation);
443    if (gVideoWidth == 0) {
444        gVideoWidth = rotated ? mainDpyInfo.h : mainDpyInfo.w;
445    }
446    if (gVideoHeight == 0) {
447        gVideoHeight = rotated ? mainDpyInfo.w : mainDpyInfo.h;
448    }
449
450    // Configure and start the encoder.
451    sp<MediaCodec> encoder;
452    sp<IGraphicBufferProducer> bufferProducer;
453    err = prepareEncoder(mainDpyInfo.fps, &encoder, &bufferProducer);
454    if (err != NO_ERROR && !gSizeSpecified) {
455        if (gVideoWidth != kFallbackWidth && gVideoHeight != kFallbackHeight) {
456            ALOGV("Retrying with 720p");
457            fprintf(stderr, "WARNING: failed at %dx%d, retrying at 720p\n",
458                    gVideoWidth, gVideoHeight);
459            gVideoWidth = kFallbackWidth;
460            gVideoHeight = kFallbackHeight;
461            err = prepareEncoder(mainDpyInfo.fps, &encoder, &bufferProducer);
462        }
463    }
464    if (err != NO_ERROR) {
465        return err;
466    }
467
468    // Configure virtual display.
469    sp<IBinder> dpy;
470    err = prepareVirtualDisplay(mainDpyInfo, bufferProducer, &dpy);
471    if (err != NO_ERROR) return err;
472
473    // Configure, but do not start, muxer.
474    sp<MediaMuxer> muxer = new MediaMuxer(fileName,
475            MediaMuxer::OUTPUT_FORMAT_MPEG_4);
476    if (gRotate) {
477        muxer->setOrientationHint(90);
478    }
479
480    // Main encoder loop.
481    err = runEncoder(encoder, muxer);
482    if (err != NO_ERROR) return err;
483
484    if (gVerbose) {
485        printf("Stopping encoder and muxer\n");
486    }
487
488    // Shut everything down, starting with the producer side.
489    bufferProducer = NULL;
490    SurfaceComposerClient::destroyDisplay(dpy);
491
492    encoder->stop();
493    muxer->stop();
494    encoder->release();
495
496    return 0;
497}
498
499/*
500 * Sends a broadcast to the media scanner to tell it about the new video.
501 */
502static status_t notifyMediaScanner(const char* fileName) {
503    String8 command("am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://");
504    command.append(fileName);
505    if (gVerbose) {
506        printf("Shell: %s\n", command.string());
507    }
508
509    // TODO: for non-verbose mode we should suppress stdout
510    int status = system(command.string());
511    if (status < 0) {
512        fprintf(stderr, "Unable to fork shell for media scanner broadcast\n");
513        return UNKNOWN_ERROR;
514    } else if (status != 0) {
515        fprintf(stderr, "am command failed (status=%d): '%s'\n",
516                status, command.string());
517        return UNKNOWN_ERROR;
518    }
519    return NO_ERROR;
520}
521
522/*
523 * Parses a string of the form "1280x720".
524 *
525 * Returns true on success.
526 */
527static bool parseWidthHeight(const char* widthHeight, uint32_t* pWidth,
528        uint32_t* pHeight) {
529    long width, height;
530    char* end;
531
532    // Must specify base 10, or "0x0" gets parsed differently.
533    width = strtol(widthHeight, &end, 10);
534    if (end == widthHeight || *end != 'x' || *(end+1) == '\0') {
535        // invalid chars in width, or missing 'x', or missing height
536        return false;
537    }
538    height = strtol(end + 1, &end, 10);
539    if (*end != '\0') {
540        // invalid chars in height
541        return false;
542    }
543
544    *pWidth = width;
545    *pHeight = height;
546    return true;
547}
548
549/*
550 * Dumps usage on stderr.
551 */
552static void usage() {
553    fprintf(stderr,
554        "Usage: screenrecord [options] <filename>\n"
555        "\n"
556        "Records the device's display to a .mp4 file.\n"
557        "\n"
558        "Options:\n"
559        "--size WIDTHxHEIGHT\n"
560        "    Set the video size, e.g. \"1280x720\".  Default is the device's main\n"
561        "    display resolution (if supported), 1280x720 if not.  For best results,\n"
562        "    use a size supported by the AVC encoder.\n"
563        "--bit-rate RATE\n"
564        "    Set the video bit rate, in megabits per second.  Default %dMbps.\n"
565        "--time-limit TIME\n"
566        "    Set the maximum recording time, in seconds.  Default / maximum is %d.\n"
567        "--rotate\n"
568        "    Rotate the output 90 degrees.\n"
569        "--verbose\n"
570        "    Display interesting information on stdout.\n"
571        "--help\n"
572        "    Show this message.\n"
573        "\n"
574        "Recording continues until Ctrl-C is hit or the time limit is reached.\n"
575        "\n",
576        gBitRate / 1000000, gTimeLimitSec
577        );
578}
579
580/*
581 * Parses args and kicks things off.
582 */
583int main(int argc, char* const argv[]) {
584    static const struct option longOptions[] = {
585        { "help",       no_argument,        NULL, 'h' },
586        { "verbose",    no_argument,        NULL, 'v' },
587        { "size",       required_argument,  NULL, 's' },
588        { "bit-rate",   required_argument,  NULL, 'b' },
589        { "time-limit", required_argument,  NULL, 't' },
590        { "rotate",     no_argument,        NULL, 'r' },
591        { NULL,         0,                  NULL, 0 }
592    };
593
594    while (true) {
595        int optionIndex = 0;
596        int ic = getopt_long(argc, argv, "", longOptions, &optionIndex);
597        if (ic == -1) {
598            break;
599        }
600
601        switch (ic) {
602        case 'h':
603            usage();
604            return 0;
605        case 'v':
606            gVerbose = true;
607            break;
608        case 's':
609            if (!parseWidthHeight(optarg, &gVideoWidth, &gVideoHeight)) {
610                fprintf(stderr, "Invalid size '%s', must be width x height\n",
611                        optarg);
612                return 2;
613            }
614            if (gVideoWidth == 0 || gVideoHeight == 0) {
615                fprintf(stderr,
616                    "Invalid size %ux%u, width and height may not be zero\n",
617                    gVideoWidth, gVideoHeight);
618                return 2;
619            }
620            gSizeSpecified = true;
621            break;
622        case 'b':
623            gBitRate = atoi(optarg);
624            if (gBitRate < kMinBitRate || gBitRate > kMaxBitRate) {
625                fprintf(stderr,
626                        "Bit rate %dbps outside acceptable range [%d,%d]\n",
627                        gBitRate, kMinBitRate, kMaxBitRate);
628                return 2;
629            }
630            break;
631        case 't':
632            gTimeLimitSec = atoi(optarg);
633            if (gTimeLimitSec == 0 || gTimeLimitSec > kMaxTimeLimitSec) {
634                fprintf(stderr,
635                        "Time limit %ds outside acceptable range [1,%d]\n",
636                        gTimeLimitSec, kMaxTimeLimitSec);
637                return 2;
638            }
639            break;
640        case 'r':
641            gRotate = true;
642            break;
643        default:
644            if (ic != '?') {
645                fprintf(stderr, "getopt_long returned unexpected value 0x%x\n", ic);
646            }
647            return 2;
648        }
649    }
650
651    if (optind != argc - 1) {
652        fprintf(stderr, "Must specify output file (see --help).\n");
653        return 2;
654    }
655
656    // MediaMuxer tries to create the file in the constructor, but we don't
657    // learn about the failure until muxer.start(), which returns a generic
658    // error code without logging anything.  We attempt to create the file
659    // now for better diagnostics.
660    const char* fileName = argv[optind];
661    int fd = open(fileName, O_CREAT | O_RDWR, 0644);
662    if (fd < 0) {
663        fprintf(stderr, "Unable to open '%s': %s\n", fileName, strerror(errno));
664        return 1;
665    }
666    close(fd);
667
668    status_t err = recordScreen(fileName);
669    if (err == NO_ERROR) {
670        // Try to notify the media scanner.  Not fatal if this fails.
671        notifyMediaScanner(fileName);
672    }
673    ALOGD(err == NO_ERROR ? "success" : "failed");
674    return (int) err;
675}
676