External Camera HAL fails on gralloc.gbm: 'unsupported flexible yuv layout' (patch attached, untested)
Summary
The External Camera HAL fails to deliver any frames when run on Waydroid's gralloc.gbm (Mesa GBM) backend. Every USB/webcam-based camera attempt — stock Camera app, third-party camera apps, video-call apps — opens the camera (LED flashes on), gets the first frame attempt, and immediately tears down (LED off) with:
E [email protected]: formatConvert: unsupported flexible yuv layout y 0x0 cb 0x0 cr 0x0 y_str 0 c_str 0 c_step 0
E [email protected]: threadLoop: format coversion failed!
D Camera2CameraImpl: CameraDevice.onError(): N failed with ERROR_CAMERA_DEVICE while in OPENED state.This affects every camera app on Waydroid configurations using ro.hardware.gralloc=gbm, regardless of which physical UVC camera is plugged in. Camera2 API users see "session setup failed" or equivalent app-side errors; the user-facing symptom is a brief LED flash + camera not working.
Related (root-cause-adjacent): waydroid/android_external_mesa3d#4 — Mesa's libgbm lacks NV12 / generic-YUV support, so it returns buffers whose YCbCrLayout has no plane pointers or strides.
Environment
- Waydroid 1.6.2 (Android 13 GAPPS image)
- Host: Ubuntu 26.04 LTS, kernel 7.0.0-22-generic
- Hardware: Intel UHD Graphics 620 (HP Elite x2 G4),
ro.hardware.gralloc=gbm - Mesa version: 26.04 LTS shipped
- Cameras tested: HP 8MP rear + HP Full-HD front (both UVC, MJPG+YUYV)
- Camera apps tested: LineageOS Aperture (stock), Bale (ir.nasim)
- Hardware itself verified working on host:
sudo fswebcam -d /dev/video2 …captures cleanly.
Root cause analysis
ExternalCameraDeviceSession::OutputThread::threadLoop calls sHandleImporter.lockYCbCr(...) on the output buffer. For IMPLEMENTATION_DEFINED-backed buffers, Waydroid's gralloc.gbm returns a YCbCrLayout with y/cb/cr = nullptr and zero strides (Mesa's libgbm has no NV12 path, so it can't fill the layout). getFourCcFromLayout() then classifies the empty layout as FLEX_YUV_GENERIC. formatConvert() hits the unimplemented FLEX_YUV_GENERIC case — a TODO since 2018 (b/72261744) — and returns -1. The session tears down.
Relevant code (lineage-20.0 / AOSP android-13 paths):
hardware/interfaces/camera/device/3.4/default/ExternalCameraDeviceSession.cpp:1580—lockYCbCrcallhardware/interfaces/camera/device/3.4/default/ExternalCameraUtils.cpp:222—getFourCcFromLayoutreturns FLEX_YUV_GENERIC for empty layouthardware/interfaces/camera/device/3.4/default/ExternalCameraUtils.cpp:371—formatConverterrors out on FLEX_YUV_GENERIC
Proposed fix (patch attached)
Detect the empty-layout case at the lockYCbCr call site and fall back to a plain sHandleImporter.lock() + synthesize an I420 layout from buffer dimensions. The synthesized layout then routes through the existing V4L2_PIX_FMT_YUV420 path in formatConvert. No new format implementation required, no behavior change for working gralloc backends.
diff --git a/camera/device/3.4/default/ExternalCameraDeviceSession.cpp b/camera/device/3.4/default/ExternalCameraDeviceSession.cpp
--- a/camera/device/3.4/default/ExternalCameraDeviceSession.cpp
+++ b/camera/device/3.4/default/ExternalCameraDeviceSession.cpp
@@ -1583,6 +1583,36 @@ bool ExternalCameraDeviceSession::OutputThread::threadLoop() {
ALOGV("%s: outLayout y %p cb %p cr %p y_str %d c_str %d c_step %d",
__FUNCTION__, outLayout.y, outLayout.cb, outLayout.cr,
outLayout.yStride, outLayout.cStride, outLayout.chromaStep);
+ // Fallback for gralloc implementations that don't fill in
+ // YCbCrLayout for IMPLEMENTATION_DEFINED-backed buffers
+ // (e.g. Waydroid's gralloc.gbm based on Mesa GBM, which
+ // lacks NV12 support per waydroid/android_external_mesa3d#4).
+ // Lock the buffer as a raw byte region and synthesize an
+ // I420 layout. Cleaner than failing the entire camera session.
+ if (outLayout.y == nullptr) {
+ ALOGW("%s: lockYCbCr returned null layout, falling back to "
+ "plain lock with synthesized I420 layout",
+ __FUNCTION__);
+ const size_t i420Size = halBuf.width * halBuf.height * 3 / 2;
+ void* rawBuf = sHandleImporter.lock(*(halBuf.bufPtr),
+ halBuf.usage,
+ static_cast<int>(i420Size));
+ if (rawBuf == nullptr) {
+ lk.unlock();
+ return onDeviceError("%s: fallback lock failed", __FUNCTION__);
+ }
+ uint8_t* base = static_cast<uint8_t*>(rawBuf);
+ const size_t ySize = halBuf.width * halBuf.height;
+ const size_t cSize = ySize / 4;
+ outLayout.y = base;
+ outLayout.cb = base + ySize;
+ outLayout.cr = base + ySize + cSize;
+ outLayout.yStride = halBuf.width;
+ outLayout.cStride = halBuf.width / 2;
+ outLayout.chromaStep = 1;
+ }
+
// Convert to output buffer size/format
uint32_t outputFourcc = getFourCcFromLayout(outLayout);Status
The patch is not yet compile- or runtime-tested. I wrote it after careful code review but lack an AOSP build environment large enough to compile the External Camera HAL .so and replace it in a Waydroid container. I'm filing it here in the hope that someone with the build infrastructure can take it from here. Happy to test the resulting .so against this hardware if someone packages it.
Reproducer
- Waydroid 1.6.2 on Ubuntu 26.04 with a UVC webcam (any HP business laptop with built-in camera works).
- Launch LineageOS Aperture (default Camera app in Waydroid).
sudo lxc-attach -P /var/lib/waydroid/lxc -n waydroid -- logcat -d | grep ExtCamshows the failure within seconds.
Related issues
- waydroid/android_external_mesa3d#4 — underlying Mesa GBM NV12 gap
- waydroid/waydroid#186 (open since 2023), #170, #284, #519, #622 — user reports of the same camera failure across many setups
Source: waydroid/waydroid