TL;DR: RAW capture on Android starts with Camera2, not CameraX, the moment DNG output and per-frame control actually matter. The decisions that shape everything downstream are made at capture-session setup, not in the capture callback; buffer handling that works for JPEG silently breaks on RAW; and the gap between what CameraCharacteristics advertises and what a device actually delivers under load only shows up once you test on real hardware, in the field, at volume.
PhotoTile and PhotoBatch were built for desktops, where a RAW file arrives from a dedicated camera body and the processing engine controls the whole pipeline end to end. A mobile companion changes that assumption: the RAW file now has to come from the phone's own sensor, through a capture API we don't control, on hardware we didn't choose. That's a different engineering problem than decoding a RAW file someone else captured, and it's the one we ran into building a field-capture mode for studio users who wanted to shoot and triage on location before syncing to a desktop session.
This is exactly the kind of problem a dedicated Android app development team — at a shop like YuSMP Group — gets called in to solve when an in-house crew is stretched across desktop, web, and mobile at once. What follows are the decisions that shaped our Camera2 RAW pipeline: why Camera2 won over CameraX, what we got wrong about buffer handling, where device-reported capabilities lied to us, and the pitfalls that only showed up after we shipped.
Why Camera2, Not CameraX, Once RAW Actually Matters
CameraX is the API most Android teams reach for first, and for good reason: it's lifecycle-aware, handles orientation and rotation for you, and gets a working preview-and-capture flow onto the screen in a fraction of the code Camera2 needs. For a standard JPEG capture flow, that trade is almost always worth it.
RAW changes the calculation. CameraX's RAW support assumes a fairly conventional single-capture flow built on top of Camera2 underneath; the moment we needed direct control over per-request capture settings, multiple concurrent output surfaces, and explicit session parameters tuned for a specific sensor's RAW behavior, we were fighting CameraX's abstraction instead of using it. Camera2 exposes that control directly — CameraCharacteristics, capture requests, and ImageReader surfaces — at the cost of writing and owning a lot more of the session lifecycle ourselves. For a RAW-first feature, that cost is the point: the control is what makes the feature possible at all.
The Capture Session Decisions That Shape Everything Downstream
Camera2's core unit of work is the capture session: a configuration of output surfaces and capture requests that the camera device is told to honor. Get the session configuration wrong at setup time, and the fix usually isn't a small patch — it's tearing down and reopening the session, which is slow enough to be visible to the user.
Preview Session vs. Still-Capture Session
Our first version tried to run preview and RAW still-capture through the same session configuration, reasoning that one session was simpler to manage than two. In practice, the output surface combination that gives you a smooth preview — a small, continuously-updating SurfaceTexture — and the one that gives you a full-resolution RAW_SENSOR buffer are different enough that forcing them into one session meant compromising on the preview's frame rate just to keep the RAW surface valid. Splitting into a dedicated preview session and a separate still-capture session, switched explicitly when the user presses the shutter, fixed both at once.
What We Fixed at Session Config Time (and Why It Mattered Later)
Three things we locked in at session configuration ended up mattering far more than we expected once the feature was in the field: the exact output format for the RAW surface (RAW_SENSOR rather than relying on the device's default), an explicit maxImages value on the ImageReader sized for our actual buffer-pool budget rather than the platform default, and a fixed capture template (TEMPLATE_STILL_CAPTURE) instead of letting the system infer one. Each of those is a one-line decision at session setup and a multi-day rebuild if you have to change it after the capture flow is already built around the wrong assumption.
Where Naive DNG/RAW Buffer Handling Breaks
JPEG and RAW look similar from the API surface — both arrive as an Image object from an ImageReader — and that similarity is exactly what made our first RAW buffer handling wrong. Code that worked fine for a few-hundred-kilobyte compressed JPEG frame choked the moment the same pattern ran against an uncompressed RAW_SENSOR buffer, which is routinely 40–100MB+ depending on sensor resolution.
The specific failure was holding onto Image references longer than necessary. JPEG buffers are small enough that a sloppy reference lifecycle barely shows up in memory profiling. RAW buffers are not: failing to call Image.close() promptly after handing the data off to a background writer thread meant the ImageReader's buffer pool exhausted within a handful of captures, and subsequent acquireNextImage() calls either blocked or returned null depending on the device. The fix was treating every Image as a resource with an explicit, immediate lifecycle — copy or hand off the underlying byte buffer to the processing thread, close the Image, and never let a reference outlive the capture callback that produced it.
Device Capabilities: What's Documented vs. What's Actually There
CameraCharacteristics is the API's way of telling you what a device supports — RAW capability, supported stream configurations, maximum resolutions, hardware level. On paper, querying it before configuring a session should be enough to know whether RAW capture will work. In the field, it wasn't sufficient on its own.
Several devices in our test pool reported RAW_SENSOR capability correctly but behaved differently under real-world conditions than they did in a quiet lab test: thermal throttling on sustained capture bursts, memory pressure from other apps causing the system to reclaim camera buffer allocations mid-session, and at least one mid-range device that advertised RAW support but produced visibly degraded sensor data compared to its own JPEG output from the identical scene. None of that shows up in a CameraCharacteristics query taken once at startup. The practical fix wasn't a single code change — it was building capability detection as an ongoing runtime check (does this capture actually succeed, with valid data, right now) layered on top of the static CameraCharacteristics query, with a documented fallback to processed-format capture when RAW underperforms.
Performance: Memory, Buffer Allocation, and Dropped Frames
Skip Frames When You Have To
Not every capture burst can keep up with full RAW processing in real time, and pretending otherwise just means every frame processes a little late instead of some frames processing on time. Once we accepted that a sustained burst would occasionally need to drop a frame rather than fall progressively further behind, the fix was straightforward: a bounded queue between the capture callback and the processing thread, with an explicit drop-oldest policy once the queue is full, rather than an unbounded queue that quietly grows until the app runs out of memory.
Managing Buffer Pools Without Leaking Memory
The ImageReader's maxImages setting is a hard ceiling on how many buffers the system will allocate for that surface, and it's also the single biggest lever on peak memory use for a RAW pipeline. Set it too low, and capture stalls waiting for buffers to free up; set it too high "to be safe," and a device with 4-6GB of RAM runs out of memory well before you hit any CPU bottleneck. We ended up sizing it explicitly around our actual pipeline depth — one buffer being captured, one being handed off, one in flight to storage — rather than any platform default.
The Pitfalls That Hit Our First Release
Two issues didn't show up until real users, on real devices, started using the feature at volume:
Rapid shutter presses before the previous capture finished. Our first version assumed the shutter button would be disabled during an in-flight capture, which worked in every manual test but not against a user tapping fast on a slower mid-range device where the UI update lagged the actual capture state. The result was a queued second capture request against a session that hadn't finished tearing down the first, occasionally producing a corrupted or empty RAW buffer. The fix was tracking capture state explicitly in the session layer, not just the UI layer, and rejecting a second capture request at the API boundary rather than trusting the button's disabled state.
Orientation metadata silently wrong on RAW output. JPEG capture on Android handles orientation correction largely for you. RAW_SENSOR output does not — the sensor orientation has to be read from CameraCharacteristics and written into the DNG metadata explicitly, and we missed this on the first release. The result was RAW files that looked correctly oriented in our own viewer (which respected a separate, correct value we'd stored) but appeared rotated when opened in any third-party RAW tool that trusted only the DNG's own orientation tag.
When This Is Worth Building In-House vs. Bringing in a Dedicated Android Team
Building a standard camera feature — point, shoot, save a JPEG — doesn't need this level of investment, and CameraX plus a generalist mobile developer covers it well. RAW capture is a different scope decision. It touches device-specific hardware behavior, buffer and memory management under real-world load, and a fragmented Android device landscape that a pre-launch test pool of a handful of phones won't fully represent. If your team is simultaneously carrying desktop, web, and mobile work — which is the normal state for a photo-software vendor adding a mobile companion — that's exactly the signal to bring in Android specialists for this piece specifically, rather than stretching a generalist team across a problem that rewards deep, device-level expertise.
Frequently Asked Questions
Do I need Camera2 if I'm only doing JPEG capture?
No. CameraX (or even the legacy Camera API for simple cases) handles standard JPEG capture well and with far less boilerplate. Camera2's extra control only pays for itself once you need RAW/DNG output, manual exposure chains, or per-frame capture metadata.
What's the real difference between CameraX and Camera2 for RAW?
CameraX is built on top of Camera2 and exposes a simplified lifecycle-aware API, but its RAW support assumes a fairly standard capture flow. Camera2 gives you direct control over capture sessions, per-request settings, and ImageReader surfaces, which is what you need when a device's RAW behavior doesn't match the common case.
How much RAM should I budget for a DNG capture buffer pool?
A single uncompressed RAW_SENSOR buffer from a modern 50+ megapixel sensor can run 100MB or more. Budget for at least three buffers in flight per capture (sensor read, processing, and the next queued request) before you add any in-memory preview or thumbnail generation on top.
Why does RAW capture work on some Android devices but fail silently on others?
CameraCharacteristics reports what a device claims to support, not always what it reliably delivers under load. Some devices advertise RAW_SENSOR capability but throttle, crop, or silently fall back to a processed format once thermal or memory pressure kicks in, and that only shows up in field testing, not on the spec sheet.
Can I post-process RAW frames in real time without dropping frames?
Only if the processing is decoupled from the capture session on its own thread with a bounded queue. Any heavy per-frame work done synchronously inside the capture callback will stall the session and drop frames well before the CPU itself is the bottleneck.
Is RenderScript still relevant, or should I use something else now?
RenderScript is deprecated and shouldn't be the starting point for new work. Most teams now reach for Vulkan compute, GPU image-processing libraries, or simply offload heavy per-frame transforms to native code via the NDK, depending on how much control they need over the pipeline.
When does it make sense to hire an outside Android team instead of building this in-house?
Once the work stretches beyond a UI feature into device-specific camera behavior, buffer management, and performance tuning across a fragmented hardware landscape, and your existing team is also carrying desktop, web, or other mobile platforms at the same time.

For the full list of reported capabilities a capture session can query before configuration, see the Android Developers reference for CameraCharacteristics.



