ByteScope

screenrecord

changes the device

Records the screen to an MP4 file on the device.

Command
adb shell screenrecord
Commands
Display and capture
Risk
changes the device

Syntax

screenrecord [--size <width>x<height>] [--bit-rate <bps>] [--time-limit <seconds>] <device path>

Flags and arguments

TokenMeaning
--time-limit <s>Stop after this many seconds. The binary's own help says the default and the maximum are both 180.
--bit-rate <bps>Bits per second. The help text states a default of 20 Mbps.
--size <w>x<h>A *request*, not a guarantee: the help says the default is the display resolution if supported and 1280x720 if not, and advises a size the encoder supports.
--bugreportOverlay extra information, a timestamp among it, on the video. For a clip that has to establish *when* something happened rather than only what.
--display-id <id>Record one physical display rather than the primary one. Valid ids come from `dumpsys SurfaceFlinger --display-id`; naming a display that is not currently on produces a layer-stack error instead of a file.
--verboseReport progress on stdout while recording. Worth having the first time a clip comes out empty, because it tells you whether a frame ever reached the encoder.

Examples

  • screenrecord --time-limit 30 /sdcard/demo.mp4

    A 30-second clip, then pull the file.

  • screenrecord --bit-rate 8000000 --size 720x1560 /sdcard/small.mp4

    A smaller, cheaper recording for a bug report.

  • kill -INT $(pidof screenrecord)

    Stop early *properly* — see the notes on why this matters.

  • screenrecord --time-limit 0 /sdcard/long.mp4

    No ceiling at all, stopping only when you interrupt it. Android 14 and later; on 13 and earlier a zero is outside the accepted range and the command refuses.

  • dumpsys SurfaceFlinger --display-id

    List the display ids before guessing one, since a wrong id fails as a layer-stack error rather than as a bad argument.

Reading the output

Nothing while it runs, and the file appears when it finishes. A refusal — a size the encoder will not take, a path it cannot write — is printed by the binary itself, and that text is the answer rather than anything this page can predict.

Output fields

FieldMeaning
(no output)Recording. The file is written as it goes.
(an error line)The device's own refusal. Read it verbatim.
Time limit reachedWritten to stdout when the recording ended by itself rather than because something interrupted it. The file is complete and indexed.
WARNING: failed at 1440x3120, retrying at 1280x720The encoder refused the size the display reported, so the recorder halved its ambition and tried 720p on its own. It only does that when you did *not* pass `--size`; with an explicit size it fails rather than substituting something you did not ask for.

What access it needs

An authorised `adb` connection and nothing else — no root, no `adb root`, no `su`, and no developer-options switch beyond USB debugging itself. What decides where the file may land is the identity `adbd` runs your command under: it drops to the `shell` uid and gid and grants itself a fixed supplementary group list (`packages/modules/adb/daemon/main.cpp`), which includes `sdcard_rw`, `ext_data_rw` and `ext_obb_rw`. That is why a recording written under `/sdcard/` appears without argument while the same command aimed at `/system/` or at another app's private directory cannot create the file at all. Choose the destination with that in mind: `/sdcard/` and `/data/local/tmp/` both work, a read-only mount does not, and the binary calls `abort()` rather than explaining itself once the muxer's own `open()` has failed. Two refusals are routinely mistaken for permission problems and are neither. A DRM-protected surface records as black because the compositor declines to hand protected buffers to any encoder — root included — so no privilege changes that outcome. And stopping the recording cleanly needs a *second* shell, because the interrupt has to be delivered to a process that is already running: the Android Screen Mirror page holds that second channel open, which is the whole reason its stop button produces a playable file rather than a truncated one. `adb root` is not the answer to anything on this page; it is refused outright on a production build and only restarts `adbd` as root on `userdebug` and `eng` images.

Failure messages, decoded

MessageMeaning
Must specify output file (see --help).No filename survived argument parsing, so nothing was recorded. Every option here is a long option, which makes a misspelling expensive: `--timelimit` instead of `--time-limit` is consumed as an unknown option and the number after it goes with it, leaving the path in a position the parser never reaches. Check the spelling of the flags before checking the storage.
Invalid size '1080', must be width x height`--size` wants both dimensions joined by a literal `x`, and the recorder refuses anything else with exit status 2 before a single frame is captured. The value quoted back is what your shell actually delivered, so a size that lost its second half to an unquoted space shows up here as its first half — quote the argument and the ambiguity disappears.
Bit rate 50000bps outside acceptable range [100000,200000000]The bracketed pair is fixed in the source — a floor of 0.1 Mbps and a ceiling of 200 Mbps — while the leading number echoes what you asked for. `--bit-rate` accepts the `4M` shorthand as well as a plain count of bits, and the shorthand means megabits, so writing `4` asks for four bits per second and earns this line.
Time limit 300s outside acceptable range [1,180]Android 13 and earlier: there 180 seconds is a hard ceiling rather than merely the default, and a longer request is rejected before recording begins. Android 14 and Android 15 print a different line for the same mistake, quoting the range `[0,4294967295] seconds`, and they treat `0` as no limit at all — so a command refused on a phone running 13 can succeed on that same phone after it is upgraded. Believe the range the device quoted, not a number remembered from elsewhere.
ERROR: INVALID_LAYER_STACK, please check your display state.The capture could not be bound to a display's layer stack, so the recorder exits instead of writing a file full of nothing. Seen with the screen off, and with a `--display-id` naming a display that is not currently active. Wake the device first, or drop `--display-id` and let the recorder take the primary display.

What it changes, and the way back

It always writes: the MP4 goes to the device path you give, which is created or overwritten, so a wrong path can replace a real file and the old contents are gone. The file grows with the length and the bit rate and occupies storage until you remove it — `rm <path>` is the way back, after pulling it to the computer with `adb pull` if you want to keep it. Nothing else on the device changes.

Version and vendor notes

  • Stop it with an interrupt signal, not by killing the connection. An MP4 is only playable once its index has been written, and this binary writes that during the shutdown it runs on an interrupt — tearing down the socket instead leaves a truncated file that some players show as nothing at all.
  • No audio. This binary records video only; there is no flag for sound.
  • DRM-protected surfaces record as black, for the same reason a screenshot of them is black.
  • The frame size is fixed when recording starts, so rotating the phone part-way letterboxes the rest of the clip.
  • The 180-second ceiling is a property of the binary, not of the cable. When a build disagrees, believe the device's own refusal over any documentation.
  • That ceiling is where the releases genuinely differ. Android 11, 12 and 13 reject anything outside `[1,180]`; Android 14 and 15 accept up to 4294967295 seconds and document `0` as removing the limit, while still defaulting to 180. The refusal line quotes its own range, which is the only version check worth performing.
  • The destination is opened once early purely so a bad path fails legibly, naming the path and the reason. When the later open inside the muxer path fails instead, the message is the bare `ERROR: couldn't open file` with no path in it — so a failure with no path in it is a failure that happened late.

We have a UI for this

Android Screen Mirror runs this command over USB and parses what comes back — a Chromium browser, no SDK, nothing uploaded.

Open Android Screen Mirror

Related commands

Commands that turn up in the same session as screenrecord.

CommandWhat it does
screencapTakes a screenshot, either to a file on the phone or to standard output as PNG.
wm sizeReports the display's resolution, or overrides it so the phone pretends to be a different screen.
rmDeletes a file or a directory tree on the device.

More Display and capture commands

CommandWhat it does
wm densityReports the display density, or overrides it — which is what actually moves a layout breakpoint.

Checked against AOSP, toybox and kernel sources while writing the parsers behind our Web ADB tools. Where a behaviour genuinely varies by Android version or manufacturer, this page says so instead of giving a number that would be wrong on half the phones out there.

Frequently asked questions

How do I run an adb shell command?

Switch on developer options and USB debugging on the phone, plug it in, then run adb shell <command> from a machine with platform-tools installed — or run adb shell on its own to get an interactive prompt and type the command without the prefix. If the command produces binary output, such as a screenshot, use adb exec-out instead of adb shell: the plain shell service attaches a terminal, which rewrites newline bytes and corrupts the data.

Do adb shell commands need root?

Not the ones on this reference. They run as the shell user, which can manage packages for its own Android user, read most dumpsys services, write the settings provider, inject input and read shared storage. Root is only needed for things this reference deliberately avoids — reading another app's private data (except through run-as on a debuggable build), writing system partitions, or reading /data/anr directly.

Why does the output look different on my phone?

Either the Android version or the manufacturer. Some flags and fields arrived in a specific release, and those are noted per command. The rest is OEM patching, which hits dumpsys hardest: a dump is debug output whose text is whatever the service author last printed, and manufacturers change it and backport their changes. Read fields by name rather than by position, and treat an unfamiliar shape as a difference rather than as a failure.

Can I run this from a browser instead of a terminal?

For many commands, yes — the Web ADB tools on this site talk to a phone over WebUSB from a Chromium-based desktop browser, with no SDK installed and nothing uploaded. Each page links to the tool that runs its command, when one exists. Two limits are worth knowing: only one program can hold the ADB interface at a time, so a desktop adb server has to be stopped first, and adb forward and adb reverse cannot work in a browser at all.

Would rather click than type? The Web ADB Toolkit runs commands like this from a browser over USB. Or browse every command.