screenrecord
changes the deviceRecords 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
| Token | Meaning |
|---|---|
| --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. |
| --bugreport | Overlay 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. |
| --verbose | Report 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.mp4A 30-second clip, then pull the file.
screenrecord --bit-rate 8000000 --size 720x1560 /sdcard/small.mp4A 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.mp4No 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-idList 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
| Field | Meaning |
|---|---|
| (no output) | Recording. The file is written as it goes. |
| (an error line) | The device's own refusal. Read it verbatim. |
| Time limit reached | Written 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 1280x720 | The 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
| Message | Meaning |
|---|---|
| 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.
Related commands
Commands that turn up in the same session as screenrecord.
More Display and capture commands
| Command | What it does |
|---|---|
| wm density | Reports 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.