summaryrefslogtreecommitdiffstats
path: root/cmds/screenrecord/screenrecord.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'cmds/screenrecord/screenrecord.cpp')
-rw-r--r--cmds/screenrecord/screenrecord.cpp196
1 files changed, 129 insertions, 67 deletions
diff --git a/cmds/screenrecord/screenrecord.cpp b/cmds/screenrecord/screenrecord.cpp
index b6f150c..a17fc51 100644
--- a/cmds/screenrecord/screenrecord.cpp
+++ b/cmds/screenrecord/screenrecord.cpp
@@ -50,6 +50,7 @@
#include "screenrecord.h"
#include "Overlay.h"
+#include "FrameOutput.h"
using namespace android;
@@ -58,11 +59,14 @@ static const uint32_t kMaxBitRate = 200 * 1000000; // 200Mbps
static const uint32_t kMaxTimeLimitSec = 180; // 3 minutes
static const uint32_t kFallbackWidth = 1280; // 720p
static const uint32_t kFallbackHeight = 720;
+static const char* kMimeTypeAvc = "video/avc";
// Command-line parameters.
static bool gVerbose = false; // chatty on stdout
static bool gRotate = false; // rotate 90 degrees
-static bool gRawOutput = false; // generate raw H.264 byte stream output
+static enum {
+ FORMAT_MP4, FORMAT_H264, FORMAT_FRAMES
+} gOutputFormat = FORMAT_MP4; // data format for output
static bool gSizeSpecified = false; // was size explicitly requested?
static bool gWantInfoScreen = false; // do we want initial info screen?
static bool gWantFrameTime = false; // do we want times on each frame?
@@ -142,14 +146,14 @@ static status_t prepareEncoder(float displayFps, sp<MediaCodec>* pCodec,
status_t err;
if (gVerbose) {
- printf("Configuring recorder for %dx%d video at %.2fMbps\n",
- gVideoWidth, gVideoHeight, gBitRate / 1000000.0);
+ printf("Configuring recorder for %dx%d %s at %.2fMbps\n",
+ gVideoWidth, gVideoHeight, kMimeTypeAvc, gBitRate / 1000000.0);
}
sp<AMessage> format = new AMessage;
format->setInt32("width", gVideoWidth);
format->setInt32("height", gVideoHeight);
- format->setString("mime", "video/avc");
+ format->setString("mime", kMimeTypeAvc);
format->setInt32("color-format", OMX_COLOR_FormatAndroidOpaque);
format->setInt32("bitrate", gBitRate);
format->setFloat("frame-rate", displayFps);
@@ -159,16 +163,18 @@ static status_t prepareEncoder(float displayFps, sp<MediaCodec>* pCodec,
looper->setName("screenrecord_looper");
looper->start();
ALOGV("Creating codec");
- sp<MediaCodec> codec = MediaCodec::CreateByType(looper, "video/avc", true);
+ sp<MediaCodec> codec = MediaCodec::CreateByType(looper, kMimeTypeAvc, true);
if (codec == NULL) {
- fprintf(stderr, "ERROR: unable to create video/avc codec instance\n");
+ fprintf(stderr, "ERROR: unable to create %s codec instance\n",
+ kMimeTypeAvc);
return UNKNOWN_ERROR;
}
err = codec->configure(format, NULL, NULL,
MediaCodec::CONFIGURE_FLAG_ENCODE);
if (err != NO_ERROR) {
- fprintf(stderr, "ERROR: unable to configure codec (err=%d)\n", err);
+ fprintf(stderr, "ERROR: unable to configure %s codec at %dx%d (err=%d)\n",
+ kMimeTypeAvc, gVideoWidth, gVideoHeight, err);
codec->release();
return err;
}
@@ -513,7 +519,7 @@ static FILE* prepareRawOutput(const char* fileName) {
}
/*
- * Main "do work" method.
+ * Main "do work" start point.
*
* Configures codec, muxer, and virtual display, then starts moving bits
* around.
@@ -555,30 +561,40 @@ static status_t recordScreen(const char* fileName) {
// Configure and start the encoder.
sp<MediaCodec> encoder;
+ sp<FrameOutput> frameOutput;
sp<IGraphicBufferProducer> encoderInputSurface;
- err = prepareEncoder(mainDpyInfo.fps, &encoder, &encoderInputSurface);
-
- if (err != NO_ERROR && !gSizeSpecified) {
- // fallback is defined for landscape; swap if we're in portrait
- bool needSwap = gVideoWidth < gVideoHeight;
- uint32_t newWidth = needSwap ? kFallbackHeight : kFallbackWidth;
- uint32_t newHeight = needSwap ? kFallbackWidth : kFallbackHeight;
- if (gVideoWidth != newWidth && gVideoHeight != newHeight) {
- ALOGV("Retrying with 720p");
- fprintf(stderr, "WARNING: failed at %dx%d, retrying at %dx%d\n",
- gVideoWidth, gVideoHeight, newWidth, newHeight);
- gVideoWidth = newWidth;
- gVideoHeight = newHeight;
- err = prepareEncoder(mainDpyInfo.fps, &encoder,
- &encoderInputSurface);
+ if (gOutputFormat != FORMAT_FRAMES) {
+ err = prepareEncoder(mainDpyInfo.fps, &encoder, &encoderInputSurface);
+
+ if (err != NO_ERROR && !gSizeSpecified) {
+ // fallback is defined for landscape; swap if we're in portrait
+ bool needSwap = gVideoWidth < gVideoHeight;
+ uint32_t newWidth = needSwap ? kFallbackHeight : kFallbackWidth;
+ uint32_t newHeight = needSwap ? kFallbackWidth : kFallbackHeight;
+ if (gVideoWidth != newWidth && gVideoHeight != newHeight) {
+ ALOGV("Retrying with 720p");
+ fprintf(stderr, "WARNING: failed at %dx%d, retrying at %dx%d\n",
+ gVideoWidth, gVideoHeight, newWidth, newHeight);
+ gVideoWidth = newWidth;
+ gVideoHeight = newHeight;
+ err = prepareEncoder(mainDpyInfo.fps, &encoder,
+ &encoderInputSurface);
+ }
}
- }
- if (err != NO_ERROR) return err;
-
- // From here on, we must explicitly release() the encoder before it goes
- // out of scope, or we will get an assertion failure from stagefright
- // later on in a different thread.
+ if (err != NO_ERROR) return err;
+ // From here on, we must explicitly release() the encoder before it goes
+ // out of scope, or we will get an assertion failure from stagefright
+ // later on in a different thread.
+ } else {
+ // We're not using an encoder at all. The "encoder input surface" we hand to
+ // SurfaceFlinger will just feed directly to us.
+ frameOutput = new FrameOutput();
+ err = frameOutput->createInputSurface(gVideoWidth, gVideoHeight, &encoderInputSurface);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ }
// Draw the "info" page by rendering a frame with GLES and sending
// it directly to the encoder.
@@ -595,7 +611,7 @@ static status_t recordScreen(const char* fileName) {
overlay = new Overlay();
err = overlay->start(encoderInputSurface, &bufferProducer);
if (err != NO_ERROR) {
- encoder->release();
+ if (encoder != NULL) encoder->release();
return err;
}
if (gVerbose) {
@@ -610,46 +626,83 @@ static status_t recordScreen(const char* fileName) {
sp<IBinder> dpy;
err = prepareVirtualDisplay(mainDpyInfo, bufferProducer, &dpy);
if (err != NO_ERROR) {
- encoder->release();
+ if (encoder != NULL) encoder->release();
return err;
}
sp<MediaMuxer> muxer = NULL;
FILE* rawFp = NULL;
- if (gRawOutput) {
- rawFp = prepareRawOutput(fileName);
- if (rawFp == NULL) {
- encoder->release();
- return -1;
+ switch (gOutputFormat) {
+ case FORMAT_MP4: {
+ // Configure muxer. We have to wait for the CSD blob from the encoder
+ // before we can start it.
+ muxer = new MediaMuxer(fileName, MediaMuxer::OUTPUT_FORMAT_MPEG_4);
+ if (gRotate) {
+ muxer->setOrientationHint(90); // TODO: does this do anything?
+ }
+ break;
+ }
+ case FORMAT_H264:
+ case FORMAT_FRAMES: {
+ rawFp = prepareRawOutput(fileName);
+ if (rawFp == NULL) {
+ if (encoder != NULL) encoder->release();
+ return -1;
+ }
+ break;
+ }
+ default:
+ fprintf(stderr, "ERROR: unknown format %d\n", gOutputFormat);
+ abort();
+ }
+
+ if (gOutputFormat == FORMAT_FRAMES) {
+ // TODO: if we want to make this a proper feature, we should output
+ // an outer header with version info. Right now we never change
+ // the frame size or format, so we could conceivably just send
+ // the current frame header once and then follow it with an
+ // unbroken stream of data.
+
+ // Make the EGL context current again. This gets unhooked if we're
+ // using "--bugreport" mode.
+ // TODO: figure out if we can eliminate this
+ frameOutput->prepareToCopy();
+
+ while (!gStopRequested) {
+ // Poll for frames, the same way we do for MediaCodec. We do
+ // all of the work on the main thread.
+ //
+ // Ideally we'd sleep indefinitely and wake when the
+ // stop was requested, but this will do for now. (It almost
+ // works because wait() wakes when a signal hits, but we
+ // need to handle the edge cases.)
+ err = frameOutput->copyFrame(rawFp, 250000);
+ if (err == ETIMEDOUT) {
+ err = NO_ERROR;
+ } else if (err != NO_ERROR) {
+ ALOGE("Got error %d from copyFrame()", err);
+ break;
+ }
}
} else {
- // Configure muxer. We have to wait for the CSD blob from the encoder
- // before we can start it.
- muxer = new MediaMuxer(fileName, MediaMuxer::OUTPUT_FORMAT_MPEG_4);
- if (gRotate) {
- muxer->setOrientationHint(90); // TODO: does this do anything?
+ // Main encoder loop.
+ err = runEncoder(encoder, muxer, rawFp, mainDpy, dpy,
+ mainDpyInfo.orientation);
+ if (err != NO_ERROR) {
+ fprintf(stderr, "Encoder failed (err=%d)\n", err);
+ // fall through to cleanup
}
- }
-
- // Main encoder loop.
- err = runEncoder(encoder, muxer, rawFp, mainDpy, dpy,
- mainDpyInfo.orientation);
- if (err != NO_ERROR) {
- fprintf(stderr, "Encoder failed (err=%d)\n", err);
- // fall through to cleanup
- }
- if (gVerbose) {
- printf("Stopping encoder and muxer\n");
+ if (gVerbose) {
+ printf("Stopping encoder and muxer\n");
+ }
}
// Shut everything down, starting with the producer side.
encoderInputSurface = NULL;
SurfaceComposerClient::destroyDisplay(dpy);
- if (overlay != NULL) {
- overlay->stop();
- }
- encoder->stop();
+ if (overlay != NULL) overlay->stop();
+ if (encoder != NULL) encoder->stop();
if (muxer != NULL) {
// If we don't stop muxer explicitly, i.e. let the destructor run,
// it may hang (b/11050628).
@@ -657,7 +710,7 @@ static status_t recordScreen(const char* fileName) {
} else if (rawFp != stdout) {
fclose(rawFp);
}
- encoder->release();
+ if (encoder != NULL) encoder->release();
return err;
}
@@ -819,11 +872,12 @@ int main(int argc, char* const argv[]) {
{ "size", required_argument, NULL, 's' },
{ "bit-rate", required_argument, NULL, 'b' },
{ "time-limit", required_argument, NULL, 't' },
+ { "bugreport", no_argument, NULL, 'u' },
+ // "unofficial" options
{ "show-device-info", no_argument, NULL, 'i' },
{ "show-frame-time", no_argument, NULL, 'f' },
- { "bugreport", no_argument, NULL, 'u' },
{ "rotate", no_argument, NULL, 'r' },
- { "raw", no_argument, NULL, 'w' },
+ { "output-format", required_argument, NULL, 'o' },
{ NULL, 0, NULL, 0 }
};
@@ -875,23 +929,31 @@ int main(int argc, char* const argv[]) {
return 2;
}
break;
- case 'i':
+ case 'u':
gWantInfoScreen = true;
- break;
- case 'f':
gWantFrameTime = true;
break;
- case 'u':
+ case 'i':
gWantInfoScreen = true;
+ break;
+ case 'f':
gWantFrameTime = true;
break;
case 'r':
// experimental feature
gRotate = true;
break;
- case 'w':
- // experimental feature
- gRawOutput = true;
+ case 'o':
+ if (strcmp(optarg, "mp4") == 0) {
+ gOutputFormat = FORMAT_MP4;
+ } else if (strcmp(optarg, "h264") == 0) {
+ gOutputFormat = FORMAT_H264;
+ } else if (strcmp(optarg, "frames") == 0) {
+ gOutputFormat = FORMAT_FRAMES;
+ } else {
+ fprintf(stderr, "Unknown format '%s'\n", optarg);
+ return 2;
+ }
break;
default:
if (ic != '?') {
@@ -907,7 +969,7 @@ int main(int argc, char* const argv[]) {
}
const char* fileName = argv[optind];
- if (!gRawOutput) {
+ if (gOutputFormat == FORMAT_MP4) {
// MediaMuxer tries to create the file in the constructor, but we don't
// learn about the failure until muxer.start(), which returns a generic
// error code without logging anything. We attempt to create the file