From 2d11a2031b99db9b503a7ad7efd1f18606af4012 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Fri, 20 Dec 2013 13:40:34 -0800 Subject: Provide raw H.264 output from screenrecord This adds an experimental (undocumented) "--raw" flag. If set, we output an H.264 byte stream rather than a .mp4 file. If the filename is "-", we send the output to stdout. If stdout is a tty, we reconfigure it to avoid CRLF line termination over adb. Change-Id: I5193f6193c9c1a59f92eefad0ed399f24afbf6de --- cmds/screenrecord/screenrecord.cpp | 153 ++++++++++++++++++++++++++++--------- cmds/screenrecord/screenrecord.h | 2 +- 2 files changed, 116 insertions(+), 39 deletions(-) (limited to 'cmds') diff --git a/cmds/screenrecord/screenrecord.cpp b/cmds/screenrecord/screenrecord.cpp index 61f83e3..b6f150c 100644 --- a/cmds/screenrecord/screenrecord.cpp +++ b/cmds/screenrecord/screenrecord.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include "screenrecord.h" @@ -61,6 +62,7 @@ static const uint32_t kFallbackHeight = 720; // 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 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? @@ -298,10 +300,12 @@ static status_t prepareVirtualDisplay(const DisplayInfo& mainDpyInfo, * input frames are coming from the virtual display as fast as SurfaceFlinger * wants to send them. * + * Exactly one of muxer or rawFp must be non-null. + * * The muxer must *not* have been started before calling. */ static status_t runEncoder(const sp& encoder, - const sp& muxer, const sp& mainDpy, + const sp& muxer, FILE* rawFp, const sp& mainDpy, const sp& virtualDpy, uint8_t orientation) { static int kTimeout = 250000; // be responsive on signal status_t err; @@ -311,6 +315,8 @@ static status_t runEncoder(const sp& encoder, int64_t endWhenNsec = startWhenNsec + seconds_to_nanoseconds(gTimeLimitSec); DisplayInfo mainDpyInfo; + assert((rawFp == NULL && muxer != NULL) || (rawFp != NULL && muxer == NULL)); + Vector > buffers; err = encoder->getOutputBuffers(&buffers); if (err != NO_ERROR) { @@ -342,15 +348,16 @@ static status_t runEncoder(const sp& encoder, case NO_ERROR: // got a buffer if ((flags & MediaCodec::BUFFER_FLAG_CODECCONFIG) != 0) { - // ignore this -- we passed the CSD into MediaMuxer when - // we got the format change notification - ALOGV("Got codec config buffer (%u bytes); ignoring", size); - size = 0; + ALOGV("Got codec config buffer (%u bytes)", size); + if (muxer != NULL) { + // ignore this -- we passed the CSD into MediaMuxer when + // we got the format change notification + size = 0; + } } if (size != 0) { ALOGV("Got data in buffer %d, size=%d, pts=%lld", bufIndex, size, ptsUsec); - assert(trackIdx != -1); { // scope ATRACE_NAME("orientation"); @@ -379,14 +386,23 @@ static status_t runEncoder(const sp& encoder, ptsUsec = systemTime(SYSTEM_TIME_MONOTONIC) / 1000; } - // The MediaMuxer docs are unclear, but it appears that we - // need to pass either the full set of BufferInfo flags, or - // (flags & BUFFER_FLAG_SYNCFRAME). - // - // If this blocks for too long we could drop frames. We may - // want to queue these up and do them on a different thread. - { // scope + if (muxer == NULL) { + fwrite(buffers[bufIndex]->data(), 1, size, rawFp); + // Flush the data immediately in case we're streaming. + // We don't want to do this if all we've written is + // the SPS/PPS data because mplayer gets confused. + if ((flags & MediaCodec::BUFFER_FLAG_CODECCONFIG) == 0) { + fflush(rawFp); + } + } else { + // The MediaMuxer docs are unclear, but it appears that we + // need to pass either the full set of BufferInfo flags, or + // (flags & BUFFER_FLAG_SYNCFRAME). + // + // If this blocks for too long we could drop frames. We may + // want to queue these up and do them on a different thread. ATRACE_NAME("write sample"); + assert(trackIdx != -1); err = muxer->writeSampleData(buffers[bufIndex], trackIdx, ptsUsec, flags); if (err != NO_ERROR) { @@ -418,12 +434,14 @@ static status_t runEncoder(const sp& encoder, ALOGV("Encoder format changed"); sp newFormat; encoder->getOutputFormat(&newFormat); - trackIdx = muxer->addTrack(newFormat); - ALOGV("Starting muxer"); - err = muxer->start(); - if (err != NO_ERROR) { - fprintf(stderr, "Unable to start muxer (err=%d)\n", err); - return err; + if (muxer != NULL) { + trackIdx = muxer->addTrack(newFormat); + ALOGV("Starting muxer"); + err = muxer->start(); + if (err != NO_ERROR) { + fprintf(stderr, "Unable to start muxer (err=%d)\n", err); + return err; + } } } break; @@ -457,6 +475,44 @@ static status_t runEncoder(const sp& encoder, } /* + * Raw H.264 byte stream output requested. Send the output to stdout + * if desired. If the output is a tty, reconfigure it to avoid the + * CRLF line termination that we see with "adb shell" commands. + */ +static FILE* prepareRawOutput(const char* fileName) { + FILE* rawFp = NULL; + + if (strcmp(fileName, "-") == 0) { + if (gVerbose) { + fprintf(stderr, "ERROR: verbose output and '-' not compatible"); + return NULL; + } + rawFp = stdout; + } else { + rawFp = fopen(fileName, "w"); + if (rawFp == NULL) { + fprintf(stderr, "fopen raw failed: %s\n", strerror(errno)); + return NULL; + } + } + + int fd = fileno(rawFp); + if (isatty(fd)) { + // best effort -- reconfigure tty for "raw" + ALOGD("raw video output to tty (fd=%d)", fd); + struct termios term; + if (tcgetattr(fd, &term) == 0) { + cfmakeraw(&term); + if (tcsetattr(fd, TCSANOW, &term) == 0) { + ALOGD("tty successfully configured for raw"); + } + } + } + + return rawFp; +} + +/* * Main "do work" method. * * Configures codec, muxer, and virtual display, then starts moving bits @@ -558,16 +614,26 @@ static status_t recordScreen(const char* fileName) { return err; } - // Configure muxer. We have to wait for the CSD blob from the encoder - // before we can start it. - sp muxer = new MediaMuxer(fileName, - MediaMuxer::OUTPUT_FORMAT_MPEG_4); - if (gRotate) { - muxer->setOrientationHint(90); // TODO: does this do anything? + sp muxer = NULL; + FILE* rawFp = NULL; + if (gRawOutput) { + rawFp = prepareRawOutput(fileName); + if (rawFp == NULL) { + encoder->release(); + return -1; + } + } 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, mainDpy, dpy, mainDpyInfo.orientation); + 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 @@ -584,9 +650,13 @@ static status_t recordScreen(const char* fileName) { overlay->stop(); } encoder->stop(); - // If we don't stop muxer explicitly, i.e. let the destructor run, - // it may hang (b/11050628). - muxer->stop(); + if (muxer != NULL) { + // If we don't stop muxer explicitly, i.e. let the destructor run, + // it may hang (b/11050628). + muxer->stop(); + } else if (rawFp != stdout) { + fclose(rawFp); + } encoder->release(); return err; @@ -753,6 +823,7 @@ int main(int argc, char* const argv[]) { { "show-frame-time", no_argument, NULL, 'f' }, { "bugreport", no_argument, NULL, 'u' }, { "rotate", no_argument, NULL, 'r' }, + { "raw", no_argument, NULL, 'w' }, { NULL, 0, NULL, 0 } }; @@ -818,6 +889,10 @@ int main(int argc, char* const argv[]) { // experimental feature gRotate = true; break; + case 'w': + // experimental feature + gRawOutput = true; + break; default: if (ic != '?') { fprintf(stderr, "getopt_long returned unexpected value 0x%x\n", ic); @@ -831,17 +906,19 @@ int main(int argc, char* const argv[]) { return 2; } - // 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 - // now for better diagnostics. const char* fileName = argv[optind]; - int fd = open(fileName, O_CREAT | O_RDWR, 0644); - if (fd < 0) { - fprintf(stderr, "Unable to open '%s': %s\n", fileName, strerror(errno)); - return 1; + if (!gRawOutput) { + // 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 + // now for better diagnostics. + int fd = open(fileName, O_CREAT | O_RDWR, 0644); + if (fd < 0) { + fprintf(stderr, "Unable to open '%s': %s\n", fileName, strerror(errno)); + return 1; + } + close(fd); } - close(fd); status_t err = recordScreen(fileName); if (err == NO_ERROR) { diff --git a/cmds/screenrecord/screenrecord.h b/cmds/screenrecord/screenrecord.h index 95e8a68..9b058c2 100644 --- a/cmds/screenrecord/screenrecord.h +++ b/cmds/screenrecord/screenrecord.h @@ -18,6 +18,6 @@ #define SCREENRECORD_SCREENRECORD_H #define kVersionMajor 1 -#define kVersionMinor 1 +#define kVersionMinor 2 #endif /*SCREENRECORD_SCREENRECORD_H*/ -- cgit v1.1