Understanding Android Camera2 YUV_420_888 Format and Data Extraction
The article explains Android’s camera2 YUV_420_888 format, detailing Y, U, V plane structures, the roles of pixelStride and rowStride, how to detect planar versus semi‑planar layouts, handle row padding, and correctly extract and de‑interleave the data for reliable image processing.
In Android video development, capturing camera data is a key step. The Android platform provides two camera APIs – camera1 and camera2 . This article focuses on the camera2 API and the YUV_420_888 image format, explaining how the data is stored and how to correctly retrieve Y, U, and V components.
Problem background : The author encountered unexpected image results when reading the camera data. The typical usage pattern is to create an ImageReader with the desired format and set a listener to receive frames.
mImageReader = ImageReader.newInstance(1920, 1080, ImageFormat.YUV_420_888, 2);
mImageReader.setOnImageAvailableListener(...);The listener implementation often looks like this:
private byte[] y;
private byte[] u;
private byte[] v;
private ReentrantLock lock = new ReentrantLock();
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireNextImage();
// Y:U:V == 4:2:2
if (image.getFormat() == ImageFormat.YUV_420_888) {
Image.Plane[] planes = image.getPlanes();
lock.lock();
if (y == null || u == null || v == null) {
y = new byte[planes[0].getBuffer().limit() - planes[0].getBuffer().position()];
u = new byte[planes[1].getBuffer().limit() - planes[1].getBuffer().position()];
v = new byte[planes[2].getBuffer().limit() - planes[2].getBuffer().position()];
}
if (image.getPlanes()[0].getBuffer().remaining() == y.length) {
planes[0].getBuffer().get(y);
planes[1].getBuffer().get(u);
planes[2].getBuffer().get(v);
// process y, u, v
}
lock.unlock();
}
image.close();
}YUV420 basics : YUV420 is a 4:2:0 chroma‑subsampled format. The sampling means that for every two horizontal pixels, the U and V components are sampled once. There are two major families:
Planar (YUV420P) – separate planes for Y, U, and V. Common sub‑formats are YU12 (U before V) and YV12 (V before U).
Semi‑planar (YUV420SP) – a full Y plane followed by an interleaved UV plane. Typical sub‑formats are NV12 (U‑V interleaved) and NV21 (V‑U interleaved).
The official Android documentation describes YUV_420_888 as a generic YCbCr format that can represent any of the above four‑component layouts (YU12, YV12, NV12, NV21). It provides three Image.Plane objects, each with its own rowStride and pixelStride values.
Key parameters :
pixelStride – the distance (in bytes) between two consecutive samples of the same color component within a row. It can be 1 (contiguous) or 2 (every other byte contains the next sample).
rowStride – the number of bytes occupied by a full row in memory. It may be larger than the image width because of alignment padding.
Understanding these two values is essential for correctly reconstructing the YUV data.
Data extraction strategies :
Planar (pixelStride = 1) : Y, U, and V are stored in three separate buffers. You can read each buffer directly. byte[] y = planes[0].getBuffer().array(); byte[] u = planes[1].getBuffer().array(); byte[] v = planes[2].getBuffer().array();
Semi‑planar (pixelStride = 2) : The second plane contains interleaved UV (or VU) data. You need to de‑interleave the bytes. A typical approach is to take every even byte as U and every odd byte as V (or vice‑versa for NV21). byte[] u = new byte[(planes[1].getBuffer().remaining() + 1) / 2]; byte[] v = new byte[(planes[2].getBuffer().remaining() + 1) / 2]; int uIdx = 0, vIdx = 0; for (int i = 0; i < planes[1].getBuffer().remaining(); i++) { if (i % 2 == 0) u[uIdx++] = planes[1].getBuffer().get(i); } for (int i = 0; i < planes[2].getBuffer().remaining(); i++) { if (i % 2 == 0) v[vIdx++] = planes[2].getBuffer().get(i); }
Special case – row padding : Some devices add extra bytes at the end of each row for alignment (e.g., rowStride > width). Detect this by comparing rowStride * height with the actual buffer size. When padding exists, discard the extra bytes at the end of each row before processing.
Practical workflow :
Obtain rowStride and pixelStride from each plane.
If rowStride is larger than the image width, treat the extra bytes as padding and skip them when copying rows.
Check pixelStride of the UV plane: If it is 1 → planar format (YUV420P). Read U and V directly. If it is 2 → semi‑planar format (NV12/NV21). De‑interleave the UV data as shown above.
Combine the Y plane with the correctly extracted UV data to obtain the final image (e.g., convert to NV12 or NV21 for further processing).
Final code example that distinguishes the two cases:
private byte[] y;
private byte[] u;
private byte[] v;
private ReentrantLock lock = new ReentrantLock();
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireNextImage();
if (image.getFormat() == ImageFormat.YUV_420_888) {
Image.Plane[] planes = image.getPlanes();
lock.lock();
if (y == null || u == null || v == null) {
y = new byte[planes[0].getBuffer().remaining()];
u = new byte[planes[1].getBuffer().remaining()];
v = new byte[planes[2].getBuffer().remaining()];
}
planes[0].getBuffer().get(y);
planes[1].getBuffer().get(u);
planes[2].getBuffer().get(v);
// Determine format based on pixelStride
if (planes[1].getPixelStride() == 1) { // planar (YUV420P)
encodeDataTopush(y, u, v);
} else if (planes[1].getPixelStride() == 2) { // semi‑planar (NV12/NV21)
byte[] tempU = new byte[(u.length + 1) / 2];
byte[] tempV = new byte[(v.length + 1) / 2];
int idxU = 0, idxV = 0;
for (int i = 0; i < u.length; i++) {
if (i % 2 == 0) tempU[idxU++] = u[i];
}
for (int i = 0; i < v.length; i++) {
if (i % 2 == 0) tempV[idxV++] = v[i];
}
encodeData(y, tempU, tempV);
}
lock.unlock();
}
image.close();
}By correctly handling pixelStride , rowStride , and possible padding, developers can reliably obtain YUV data from Android cameras and avoid the visual artifacts that originally motivated this article.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.