Every "turn an old Android phone into a security camera" guide ends at the same place: the phone is recording. That is where the guide stops and where the actual engineering problem starts.
Capture is the easy half. Two weeks later something happened at roughly 4pm on a Tuesday and you have to find it. That is the half nobody specs, nobody benchmarks, and nobody puts on a feature list.
This is a write-up of the retrieval problem on Android specifically: what the timestamps actually mean, why motion clips make retrieval worse rather than better, what seeking inside a long recording costs, and the file-naming decision that turns a day of footage into an O(1) lookup.
1. A recorded file has three clocks, and they disagree
On Android a video file carries at least three separate time values, and it is easy to assume they are the same thing. They are not.
-
File.lastModified()/MediaStore.MediaColumns.DATE_MODIFIED- set when the last byte was written. For a continuous recording this is the moment recording stopped. -
MediaStore.MediaColumns.DATE_ADDED- when the row entered the MediaStore index. That is when the media scanner noticed the file, which is not when the camera opened and not necessarily when it closed. -
MediaStore.MediaColumns.DATE_TAKEN- the value you actually want, and the one most likely to come back null for a file written by a third-party app rather than the system camera.
The practical failure is mundane and it bites immediately: sort a folder by "date" and a six-hour overnight recording sorts under the morning, because that is when the write finished. The timestamp is recording a write-stop event. You are looking for a real-world event. Those are different things, and the gap between them is exactly as long as your recording.
Anything you build on top of DATE_MODIFIED inherits that offset. If your mental model is "the file's date tells me when the thing happened," it only holds for short clips, and it silently degrades as recording length grows.
2. Motion clips convert one retrieval problem into a worse one
Motion detection is sold as the answer to retrieval: don't record everything, record only what matters, and now you have a short list instead of a haystack.
In practice the detector fires on a hedge in wind, on headlights sweeping a wall, on a cat, on the infrared cutover at dusk, on a spider on the lens. So the short list is not short.
Run the arithmetic on a realistic day:
| clips/day | clip length | time to watch them all at 1x |
|---|---|---|
| 50 | 8s | 6.7 min |
| 200 | 8s | 26.7 min |
| 200 | 15s | 50.0 min |
Fifty minutes of watching, every day, to establish that nothing happened. And each of those clips is named by timestamp, which means in a file listing they are visually identical - same prefix, same extension, same icon, differing in six digits. You cannot skim them. You have to open them.
The retrieval cost did not go down. It got converted from "scrub one file" into "open two hundred files," which is worse, because scrubbing is continuous and opening is discrete. You can drag a scrubber. You cannot drag a folder.
There is a false-negative side too, and it is the more serious one: motion detection triggers on change, so a person who walks in and then stands still generates a clip of the walking in and nothing afterwards. Continuous recording has no opinion about what is interesting. That is its weakness on storage and its strength on evidence.
3. Continuous recording: one file, and the cost of seeking into it
The opposite design is one long file. This solves the false-negative problem completely and creates two new ones.
Size. Bitrate determines everything, and the numbers are easy to compute:
| bitrate | per hour | per 24h |
|---|---|---|
| 2 Mbps | 0.88 GB | 21.1 GB |
| 4 Mbps | 1.76 GB | 42.2 GB |
| 8 Mbps | 3.52 GB | 84.4 GB |
Seek cost. This is the part that gets skipped. H.264 and H.265 are inter-frame codecs: most frames are described as differences from other frames, and you can only begin decoding at a sync sample (an IDR keyframe). In an MP4 the stss box is the sync sample table - the list of which samples are seekable entry points. A player seeking to an arbitrary time finds the nearest preceding sync sample and decodes forward from there, discarding output until it reaches your target.
So your keyframe interval sets your scrubbing latency. At 30fps:
| keyframe interval | GOP | worst-case frames decoded to land on a target |
|---|---|---|
| 1s | 30 | 29 |
| 2s | 60 | 59 |
| 5s | 150 | 149 |
| 10s | 300 | 299 |
A six-hour recording at 30fps is 648,000 frames. On a modern phone with hardware decode, a 60-frame catch-up is imperceptible. On the seven-year-old handset you deliberately chose for this job, possibly falling back to software decode for an unusual profile, a 300-frame catch-up per scrub is the difference between a usable timeline and a spinner. Long keyframe intervals give you a smaller file and a scrubber nobody wants to touch.
There is also a durability problem specific to one enormous file, which I wrote up separately: an MP4's index is written at the end, so an interrupted recording produces a file that contains all of your video and cannot play any of it. That mechanism, and the two ways around it, are here: Why an Interrupted Android Recording Won't Play (moov Atom, Scoped Storage, and Two Fixes).
4. Segmentation is the retrieval fix, not just the crash fix
Segmenting - closing the file every N minutes and opening a new one - is usually justified on crash safety. An interruption costs you the current segment instead of the whole day. True, and worth it on its own.
The stronger argument is retrieval, and it is a filename argument.
Twenty-four hours in ten-minute segments is 144 files. If the segment's start time is encoded in the filename, then mapping a wall-clock moment to a file is arithmetic, not search:
index = floor((target_time - first_segment_start) / segment_length)
No database. No index rebuild. No scan. Then you seek at most ten minutes into one file, and that seek is bounded by your GOP from section 3. Retention becomes a file delete rather than a rewrite of a growing file, which also means "disk full" can mean delete oldest instead of stop recording.
The point worth internalising is that the filename is the index, and it is the only index that survives everything else. Compare the alternatives:
- mtime as index - breaks the first time the file is copied, because plenty of copy paths do not preserve modification time. Also, per section 1, it is the wrong instant anyway.
- A database inside the app - dies when the app is uninstalled, and does not travel with the files when you pull them onto a laptop.
-
MediaStore metadata - can be rebuilt by the system, and
DATE_TAKENmay be null to begin with. - The filename - survives copying, survives uninstall, survives being emailed to somebody, and is greppable with tools that know nothing about your app.
Design for the case where your software is not present. That is exactly the case where retrieval matters most.
5. The overlay tradeoff, stated honestly
Burning a timestamp into the frame is an irreversible modification of the pixels. It cannot be undone, it slightly degrades the image, and it costs a compositing step at encode time.
It is also the only timestamp that survives the share path. Metadata is stripped or rewritten by nearly every messaging app, upload pipeline, and re-encode. Screenshot a frame and every timestamp except the burned-in one is gone.
So the rule is about audience, not preference: if a recording will only ever be reviewed by you, on the device, the filename is enough. If it might ever need to be shown to somebody who did not record it, the pixels are the only clock you can rely on.
6. The step that deletes the retrieval problem
Everything above is the cost of looking backwards. The cheapest retrieval is the one you never perform.
If you can watch the camera live, right now, from wherever you are, then a whole class of "go find the footage" tasks stops existing. You do not scrub 144 segments to find out whether the delivery arrived - you look. Recording stops being the primary feature and becomes the fallback for the times you were not watching.
That reframing is why live viewing is worth the plumbing, and the plumbing is the genuinely hard part on a home network: How to Watch Your Phone's Camera From Outside Your Home Network (Without Port Forwarding).
And live viewing is only useful if the phone can serve a feed with the screen dark and the device otherwise idle, which is its own Android problem - foreground services, Camera2 lifecycle, and the doze behaviour around them: How an Android Phone Keeps Recording Video With the Screen Off.
The questions actually worth asking
Feature lists compete on capture because capture is measurable and demos well. Almost none of them answer these:
- What is the file named, and does the name contain the moment the segment started?
- How long is a segment, and what does an interruption cost?
- What is the keyframe interval, and therefore how fast is a scrub on the specific old phone I am using?
- When storage fills, does it delete oldest or stop recording?
- Can I find 4pm last Tuesday without opening the app that recorded it?
- Can I just look right now instead?
Question six is the one that makes the other five matter less.
I work on Background Camera RemoteStream, an Android app for exactly this setup: recording and live remote viewing with the screen off, no account required.
Top comments (0)