Short answer: Android's Compatibility Definition Document does require six concurrent hardware video encoder sessions — but only from devices that declare a Media Performance Class. A budget or older handset that reports
MEDIA_PERFORMANCE_CLASS = 0is exempt from that requirement entirely. The only number that describes your phone isgetMaxSupportedInstances(), and its own documentation calls it "a hint for an upper bound." If you are building a 24/7 camera app, the encoder is a second scarce resource with its own limit, its own advertising API, and its own way of being taken away from you.
I wrote up the camera side of this question a couple of weeks ago: whether a phone can record and live-stream at once depends on its Camera2 hardware level, because Android publishes a table of stream combinations every device must support, and the lowest tier caps two simultaneous consumers at preview resolution.
That article was only half the story, and a reader-shaped hole has been sitting in it since.
Camera2's guarantee is about frames leaving the sensor. It says nothing about what happens after. Two camera output surfaces are not two videos — they are two sources of raw frames that still have to be compressed by something. On any modern Android device that something is a fixed-function hardware block, and there are not very many of it.
The camera can hand you two streams and the encoder can still say no.
How many video encoder sessions can an Android phone run at the same time?
There is no single answer, and the reason is structural rather than evasive. Android specifies a floor, but it applies the floor conditionally. Devices that opt into the performance-class system inherit a hard requirement; devices that do not, do not. Below is what the Compatibility Definition Document actually says, requirement 5.1/H-1-4, which lives in section 2.2.7.1 (Handheld Media Performance) rather than in the general video encoding section:
| CDD | Media Performance Class | Concurrent hardware encoder requirement |
|---|---|---|
| Android 11 | R |
6 sessions (AVC or HEVC), any codec combination, at 720p@30fps |
| Android 12 | S |
6 sessions (AVC, HEVC, VP9 or later) at 720p@30fps — "Only 2 instances are required if VP9 codec is present" |
| Android 13 | T |
6 sessions (AVC, HEVC, VP9, AV1 or later) at 1080p@30fps |
| Android 14 | U |
6 sessions of 8-bit (SDR) encoders — 4 at 1080p@30fps and 2 at 4K@30fps |
| Android 15 / 16 | V |
Same 4×1080p + 2×4K split, plus "there MUST NOT be more than 1 frame dropped per second" |
Two things in that table matter more than the numbers.
The first is the 720p→1080p migration between Android 12 and 13. If you read a Stack Overflow answer from 2021 that says "six encoders at 720p" and you are targeting a modern device, that sentence has quietly changed meaning underneath you.
The second is the qualifier that every one of those rows carries, and that almost nobody quotes with them. Each requirement is introduced with a conditional: if the device returns a given constant for MEDIA_PERFORMANCE_CLASS. A handset that declares no performance class at all — which is the default, and which is what an enormous number of shipping budget devices do — is not in breach of anything when it supports two encoder instances instead of six. It was never asked for six.
This is the part that matters if the phone you are targeting cost $80 and shipped four years ago. The reassuring number in the spec is not a promise to you.
The number that does describe your phone
The CDD's companion requirement, 5.1/H-1-3, is the useful one, because it is about disclosure rather than capability. Devices MUST advertise the maximum number of concurrent encoder sessions "via the CodecCapabilities.getMaxSupportedInstances() and VideoCapabilities.getSupportedPerformancePoints() methods."
So the phone will tell you. You have to ask.
MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
for (MediaCodecInfo info : list.getCodecInfos()) {
if (!info.isEncoder()) continue;
for (String type : info.getSupportedTypes()) {
if (!type.equals(MediaFormat.MIMETYPE_VIDEO_AVC)) continue;
int max = info.getCapabilitiesForType(type).getMaxSupportedInstances();
Log.i(TAG, info.getName() + " -> " + max);
}
}
getMaxSupportedInstances() has been available since API 23, and on the device side it is not a computed value — it is read from a static configuration file the OEM ships. The AOSP integration docs describe the exact knob: a <Limit name="concurrent-instances" max="…" /> element inside the codec's entry in /etc/media_codecs.xml, enforced at certification time by a CTS test named testGetMaxSupportedInstances.
That has a consequence worth internalising. The value is a vendor's declaration, checked once, about a static ceiling. It is not a live measurement of what is free right now. The javadoc is unusually candid about this:
"This is a hint for an upper bound. Applications should not expect to successfully operate more instances than the returned value, but the actual number of concurrently operable instances may be less as it depends on the available resources at time of use."
Read that twice if you are writing an always-on app. It says the ceiling is real and the floor is nothing. You can query a 4, be the only camera app running, and still fail to allocate your second encoder — because a video call, a screen recorder, or the OS itself got there first.
getSupportedPerformancePoints() is the companion API (added in API 29), and it carries the concurrency caveat explicitly: performance points "assume a single active codec," and for multi-codec use cases you are told to take the highest pixel count and add the frame rates of each individual codec. It is also allowed to return null — notably on devices upgraded to Android 10 or later without a corresponding vendor image update, which describes a lot of long-lived hardware. Plan for the null.
Two failures that look identical and are not
When allocation does not go your way, MediaCodec throws a CodecException, and the error code distinguishes two situations that a naive catch block will flatten into one.
ERROR_INSUFFICIENT_RESOURCE means, per the documentation, "required resource was not able to be allocated." You asked for an encoder and there was not one to give. Nothing of yours was disturbed. This is the polite failure: your existing sessions are intact, and the right response is usually to degrade — drop the second stream to a lower resolution, or run one instead of two — rather than to give up.
ERROR_RECLAIMED is the one that should change your architecture. It means "the resource manager reclaimed the media resource used by the codec." Something else on the device wanted an encoder, the system decided that something outranked you, and it took yours. The documentation is blunt about what you are allowed to do next: "the codec must be released, as it has moved to terminal state."
That last clause is easy to skim past and expensive to get wrong. A reclaimed codec is not retryable in place. It is not a transient hiccup you can sleep on and resume. CodecException exposes two predicates — isRecoverable(), which means you can stop(), configure(), and start() your way back, and isTransient(), which means resources are temporarily unavailable and the call may be retried later — and the docs note the two are never true at the same time. Reclaim is documented as neither. The object is dead; you rebuild from scratch, and you decide what your app looks like in the interval.
For a recorder that is meant to run unattended for weeks, "what your app looks like in the interval" is the entire design problem. Nobody is holding the phone. Nobody will tap Retry.
Who wins, and how honest the documentation actually is
Here is where I want to be careful, because this is the point at which most write-ups start asserting things that the official documentation does not say.
The public docs describe reclaim in terms of priority between processes, not in terms of foreground and background. The SoC integration guide says a vendor error code is used by the media resource manager "as the indicator to potentially preempt media resource from other lower priority process," and the vendor-facing priority knob it documents, OMX_IndexConfigPriority, has exactly two levels — realtime and best-effort — on an inverted scale where a higher number means lower priority. It is explicitly framed as "a hint used at codec configuration and resource planning."
Since Android 15 there is an app-facing knob too: MediaFormat.KEY_IMPORTANCE. The resource manager "may use the codec importance, along with other factors when reclaiming codecs from an application." Two qualifications come attached to it in the same documentation, and both limit how much you should lean on it. "The specifics of reclaim policy is device dependent." And the value is "only relevant within the context of that application" — it lets you tell the system which of your own codecs to sacrifice first. It is not a way to outrank someone else's video call.
What I could not find in any official Google documentation is a statement that the media codec resource manager considers whether your app is in the foreground. It is a reasonable inference from how the AOSP implementation derives process priority, and in practice foregrounded apps do tend to win. But it is an inference from source, not a documented contract, and a documented contract is the only thing you can safely design against. If someone tells you a foreground service is therefore safe from encoder reclaim, ask them for the page. I looked, and I do not believe it exists.
The one piece of unambiguous official guidance is aimed at the other side of the problem — being a good citizen rather than a protected one. The stop() javadoc says plainly that to ensure the codec is available to other clients you should call release() and "don't just rely on garbage collection to eventually do this for you." That is worth honouring even when you are the app that wants to keep running, because in a world of two encoder instances, the app that holds one it is not using is the reason someone else's recording just died.
What this means if you are building one of these
The design conclusions are unglamorous and they hold across the whole category:
-
Query, never assume.
getMaxSupportedInstances()at startup, per codec, and branch on it. A model-name allowlist is a bug with a maintenance schedule. - Treat one encoder as the honest baseline. Two is an optimisation you attempt and fall back from. On a non-performance-class device it is not owed to you by anything.
-
Handle
ERROR_RECLAIMEDas a lifecycle event, not an error. Release, rebuild, and — for an unattended recorder — decide explicitly whether the file keeps rolling while the stream does not. That asymmetry is usually the right answer, because the recording is the artifact and the live view is a convenience. - Release aggressively. Every second you hold an encoder you are not writing frames into is a second you are the problem.
This is the same shape as what the low-memory killer does to a long-running camera service, and the same shape as what happens when two apps want the lens at once: Android hands a long-lived background workload a set of resources that were designed around a user holding the phone and looking at it. Nothing about a 24/7 recorder is the case these systems were tuned for. The work is in noticing that early and building the fallback before the device forces you to.
Background Camera RemoteStream is built around exactly this constraint — recording with the screen off, with a live view served over your own network, on hardware that is usually several years old and rarely generous. More on the architecture at superfunicular.com.
Sources: Android Compatibility Definition Documents for Android 11–16 (§2.2.7.1, requirements 5.1/H-1-3 and 5.1/H-1-4), source.android.com; MediaCodecInfo.CodecCapabilities, MediaCodecInfo.VideoCapabilities, MediaCodec.CodecException and MediaFormat API reference, developer.android.com; media codec OEM and SoC integration guides, source.android.com. CDD figures were read from the version-specific documents and reflect the requirement text as published for each release; Android 17's CDD moves these counts out into a separate Media Performance Class definition, so the figures above are quoted through Android 16.
Top comments (0)