Android provides a nice utility for recording the device screen from the command-line via ADB:
adb shell screenrecord
Unfortunately, it comes with one BIG caveat: You can only record up to 180 seconds in one go.
This was a serious issue for us at Mobile.dev because we rely on this tool for Android screen recordings on Maestro Cloud. In fact, the 3-minute recording limit was one of the most common complaints we were hearing from our users.
We tried several creative approaches, but ultimately only one solution gave us everything we needed:
- Allows recordings beyond 3 minutes
- Produces a single video file that doesn’t skip
- Doesn’t require maintaining many branches of changes on AOSP
- Works on any version of Android
- Works on emulators and production physical devices
- Easy to integrate into our infrastructure codebase
What we tried
❌ Run the command multiple times
This solution outputs multiple mp4 files. It’s possible to stitch the resulting files together (eg. with ffmpeg) but your recording will skip at the seams between the recordings.
❌ Modify and recompile from source
The screenrecording
binary is dynamically linked so you’ll need to build a separate binary for each version of Android you want to run this on. Possible, but if you’ve ever dealt with AOSP before, you already know how painful this is.
❌ Use scrcpy instead of adb screenrecord
scrcpy doesn’t suffer from the 3-minute time limit, but it’s a user-facing product and not built to easily integrate with other tools or existing codebases.
❌ Build a statically linked screenrecord binary
This would potentially allow a single modified binary to run on multiple Android OS versions. Unfortunately, it’s not possible to compile a statically linked screenrecord binary due to it’s many dynamic library dependencies in AOSP.
What ended up working
✅ Edit a few bytes in the screenrecord binary
Ultimately, we opted for an approach that allowed us to directly modify the screenrecord maximum time limit. Using binary rewriting, we were able to increase this limit to any duration we wanted.
Just tell me how you did it already!
Alright, now for the fun stuff:
The 3-minute limitation in the screenrecord command is hardcoded as a single constant in the AOSP source code. That’s great, because it means it will be relatively easy to modify this value, even in the compiled binary.
static const uint32_t kMaxTimeLimitSec = 180; // 3 minutes
Finding the right bytes to modify
We used a tool called Redare2 to help poke around in the screenrecording binary. Ramping up on this tool took a bit of time, but luckily there’s this cool new thing called ChatGPT that’s super helpful in these situations 😉.
So with our new friends Redare2 and ChatGPT, we were able to find a simple command that searches for the exact bytes we were after:
r2 -q -c "/x b4000000" screenrecord
The screenrecord
argument is the path to the file we want to inspect. We pulled this particular binary off an Android device using:
adb pull /system/bin/screenrecord
But what’s all this other stuff? Here’s where ChatGPT is really helpful:
So we’re searching the binary for this sequence of bytes: b4000000
. Why? This is the 4-byte little-endian hexidecimal representation of 180
, which is the value of the kMaxTimeLimitSec
constant we were interested in.
If we can find where this constant value is defined, we can update those bytes to a different number!
As it turns out, b4000000
appears multiple times in the binary. The left column lists the address of each occurrence:
# search for occurrences of 0xb4000000
$ r2 -qc "/x b4000000" screenrecord
0x00020854 hit0_0 b4000000
0x00015c79 hit0_1 b4000000
0x000169a2 hit0_2 b4000000
0x000183b6 hit0_3 b4000000
0x000199a5 hit0_4 b4000000
0x000199b6 hit0_5 b4000000
0x000199f7 hit0_6 b4000000
0x00019c8e hit0_7 b4000000
0x0001affa hit0_8 b4000000
0x0001b158 hit0_9 b4000000
0x0001b173 hit0_10 b4000000
0x0001c0f9 hit0_11 b4000000
0x0001d487 hit0_12 b4000000
0x00004d1c hit0_13 b4000000
0x000077ad hit0_14 b4000000
0x00007c9a hit0_15 b4000000
0x00007cff hit0_16 b4000000
0x0000814c hit0_17 b4000000
0x0000eaa7 hit0_18 b4000000
0x00010648 hit0_19 b4000000
Luckily, the first entry (0x00020854
) is the one we are looking for. We can verify this by searching for references to that address:
# analyze the binary, then list all references to 0x00020854
$ r2 -qc "aaa; axt 0x00020854" screenrecord
(nofunc) 0x15c6e [DATA:r--] mov dword [0x00020854], eax
(nofunc) 0x16344 [DATA:r--] mov r9d, dword [0x00020854]
(nofunc) 0x16cdc [DATA:r--] mov r15d, dword [0x00020854]
The exact assembly here isn’t super important. What does matter is that yes, there are indeed instructions that reference the 4-byte value at 0x00020854
. The same command does not yield any results for any of the other addresses listed.
Here’s what that tells us: 0x00020854
is the only address that both…
- …contains the 4-byte representation of the value
180
, and - …is referenced by at least one instruction in the binary
This alone gives us high confidence that we have the right address that corresponds to the kMaxTimeLimitSec
constant. But as a final check, let’s take a look at the first instruction that references this address:
(nofunc) 0x15c6e [DATA:r--] mov dword [0x00020854], eax
We can use Radare2’s pd
command to view the instruction right before that one:
# seek to 0x15c6e, then print 1 instruction before that address
$ r2 -qc "s 0x15c6e; pd -1" screenrecord
0x00015c69 e8126d0000 call sym.imp.atoi
It’s a call to the sym.imp.atoi
function. Looking back at the source, the only reference to atoi
is in this part of the code:
case 't':
gTimeLimitSec = atoi(optarg);
if (gTimeLimitSec == 0 || gTimeLimitSec > kMaxTimeLimitSec) {
fprintf(stderr,
"Time limit %ds outside acceptable range [1,%d]\n",
gTimeLimitSec, kMaxTimeLimitSec);
return 2;
}
break;
And we see that kMaxTimeLimitSec
is referenced there as expected.
Rewriting the binary
So we just did a lot of work to verify that 0x00020854
is the address we need to modify in the file. It currently holds the value 180
— Let’s update it to 3600
to see what happens!
# seek to 0x00020854, then write 3600(0x100E0000) to that address
r2 -q -c "s 0x00020854; wx 0x100E0000" -w screenrecord
Now we have a local screenrecord binary that we hope has been updated to increase its time limit to 3600 seconds (1 hour). Let’s verify that:
# push the modified screenrecord binary to /data/local/tmp/
adb push ./screenrecord /data/local/tmp
# make the file executable
adb shell chmod +x /data/local/tmp/screenrecord
# print the help message
adb shell /data/local/tmp/screenrecord --help
If you’ve done this sort of work before, you never expect this to behave as expected without a lot of debugging and trial and error. But to our surprise, this is what we saw on our first attempt:
The usage message was updated with the modified value! We then validated that we could indeed record a video longer than the original 3-minute limit using the modified binary! 🥳🥳🥳
Integrating into our infrastructure
The last step for us was integrating the fix into our infrastructure. This was a matter of pushing the modified screenrecord binary to our Android devices before making them available to our device pool. In the end, a small change.
Wrapping up
This was an example of a very deep dive that resulted in a simple solution. We were especially relieved that, in the end, the fix required only a few lines of code changes.
And of course — most importantly — we’re happy to share that we have completely removed the Android screen recordings limits on Maestro Cloud 🙌
If you haven’t checked out Maestro yet, we promise it will blow your mind. Get started here and make sure to Join us on Slack!