11 KiB
Pixel 9a Camera HAL Interface Map
Generated: 2026-05-07 Device: Pixel 9a (tegu), API 36 (Android 16) Device ID: 58241JEBF08428
1. Architecture Overview
CRITICAL DISCOVERY: Camera HAL is a Separate Process
The Pixel 9a camera HAL is NOT a shared library loaded by system_server. It is a separate process running as the system user, communicating with cameraserver via AIDL Binder IPC.
┌─────────────────────────────────────────────────────────────┐
│ App Process │
│ (Google Camera, Fossify Camera, Instagram, etc.) │
│ Uses Camera2 API → Binder IPC → cameraserver │
└────────────────────────┬────────────────────────────────────┘
│ Camera2 API (Binder)
┌────────────────────────▼────────────────────────────────────┐
│ cameraserver (PID 1211) │
│ Process: /system/bin/cameraserver │
│ User: cameraserver │
│ Role: Manages camera devices, routes requests to HAL │
└────────────────────────┬────────────────────────────────────┘
│ AIDL Binder IPC (ICameraProvider)
┌────────────────────────▼────────────────────────────────────┐
│ Camera Provider HAL Process (PID 994) │
│ Binary: /apex/com.google.pixel.camera.hal/bin/hw/ │
│ android.hardware.camera.provider@2.7-service-google│
│ User: system │
│ APEX: com.google.pixel.camera.hal │
│ Interface: ICameraProvider/internal/0 (AIDL v3) │
│ Role: Implements camera HAL, talks to kernel drivers │
└────────────────────────┬────────────────────────────────────┘
│ Kernel drivers (/dev/lwis-*)
┌────────────────────────▼────────────────────────────────────┐
│ Camera Hardware │
│ Sensors, ISP, OIS, Actuator, EEPROM │
└─────────────────────────────────────────────────────────────┘
Implications for HAL Hooking
The traditional "HAL wrapper library loaded by system_server" approach is WRONG for Pixel 9a. The correct interception strategies are:
- Hook the camera provider process — Inject a library into the provider process via
wrap.property +LD_PRELOAD - Intercept Binder/AIDL IPC — Hook the Binder communication between
cameraserverand the provider - Replace the APEX binary — Swap the provider binary with a wrapper (requires APEX modification)
- Hook at cameraserver level — Intercept in
cameraserverbefore requests reach the provider
Recommended approach: Option 1 (hook the camera provider process via wrap. property). The provider process is the most direct interception point.
2. AIDL Interface Hierarchy
Interface Chain
ICameraProvider (v3)
└── getCameraIdList() → ["device@1.1/internal/0", ...]
└── openCamera(deviceId, callback) → ICameraDevice
└── ICameraDevice (v4)
└── open(sessionCallback) → ICameraDeviceSession
└── ICameraDeviceSession (v4)
├── configureStreams(config) → StreamConfiguration
├── processCaptureRequest(request) → Status
├── flush() → Status
└── close()
Key AIDL Interfaces
ICameraProvider (android.hardware.camera.provider-V4-ndk.so)
interface ICameraProvider {
CameraStatus[] getCameraIdList(out String[] cameraIds);
CameraStatus isSetTorchModeSupported(String cameraId, out boolean support);
CameraStatus openCamera(String cameraId, ICameraDeviceCallback callback,
out ICameraDevice device);
CameraStatus setTorchMode(String cameraId, boolean enabled);
CameraStatus notifyDeviceStateChange(long physicalCameraId, long deviceState);
}
ICameraDevice (android.hardware.camera.device-V4-ndk.so)
interface ICameraDevice {
CameraMetadata getCameraCharacteristics();
int getResourceCost();
CameraStatus open(ICameraDeviceCallback callback,
out ICameraDeviceSession session);
void close();
}
ICameraDeviceSession — PRIMARY INTERCEPTION TARGET
interface ICameraDeviceSession {
// ★★★ KEY FUNCTION 1 ★★★
// Configure output/input streams for the camera
CameraStatus configureStreams(
in StreamConfiguration requestedConfiguration,
out HalStreamConfiguration halConfiguration);
// ★★★ KEY FUNCTION 2 ★★★
// Process a capture request — THIS IS WHERE WE REPLACE FRAMES
CameraStatus processCaptureRequest(
in CaptureRequest request,
out CaptureResultMetadata resultMetadata);
// ★★★ KEY FUNCTION 3 ★★★
CameraStatus flush();
void close();
CameraStatus getSignalStreamMap(out SignalStreamMap streamMap);
CameraStatus processPhysicalCaptureRequest(
in PhysicalCaptureRequestInfo physicalRequestInfo,
out CaptureResultMetadata resultMetadata);
CameraStatus setRepeatingRequests(in CaptureRequest[] requests, out int32_t sequenceId);
CameraStatus cancelRepeatingRequest(int32_t sequenceId);
}
3. Camera Device Configuration
Device Mapping
| API Device | HAL ID | Facing | Description |
|---|---|---|---|
| Device 0 | HAL 2 (Rear) | Back | Main rear camera |
| Device 0 | HAL 3 (RearWide) | Back | Ultra-wide rear camera |
| Device 1 | HAL 1 (Front) | Front | Selfie camera |
Stream Configuration Formats
From android.scaler.availableStreamConfigurations:
| Format Code | Format Name | Description | Max Resolution |
|---|---|---|---|
| 32 | HAL_PIXEL_FORMAT_YCBCR_420_888 |
Flexible YUV 4:2:0 | 4000×3000 |
| 35 | HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED |
GPU/ISP opaque format | 4208×3120 |
| 36 | HAL_PIXEL_FORMAT_RAW16 |
Raw sensor data | Varies |
| 37 | HAL_PIXEL_FORMAT_BLOB (JPEG) |
Compressed JPEG | 4000×3000 |
Most important format for virtual camera: Format 32 (YUV_420_888) — this is what preview streams use and what we need to feed video frames into.
Buffer Flow
1. App calls CameraDevice.createCaptureSession() with Surface targets
2. cameraserver calls ICameraDeviceSession.configureStreams()
→ HAL allocates/configures output buffers
3. App calls CaptureRequest.Builder.addTarget(surface)
4. cameraserver calls ICameraDeviceSession.processCaptureRequest()
→ HAL fills buffers with camera sensor data
→ Buffers are returned to cameraserver → app surfaces
Our interception point: Step 4 — replace buffer contents with decoded video frames BEFORE returning to cameraserver.
4. Key Libraries in APEX
Location: /apex/com.google.pixel.camera.hal/lib64/
| Library | Purpose | Relevance |
|---|---|---|
libgooglecamerahal.so |
Main Google Camera HAL implementation | PRIMARY TARGET |
libgooglecamerahalutils.so |
HAL utility functions | Supporting |
android.hardware.camera.provider-V4-ndk.so |
AIDL Provider interface stubs | Interface definition |
android.hardware.camera.device-V4-ndk.so |
AIDL Device interface stubs | Interface definition |
android.hardware.camera.common-V1-ndk.so |
Common camera types | Interface definition |
android.hardware.camera.metadata-V3-ndk.so |
Camera metadata handling | Needed for result metadata |
liblyric_hwl.so |
Google Lyric Hardware Layer | Google-specific ISP |
libg3a.so |
3A algorithms (AE/AWB/AF) | Auto-exposure/whitebalance/focus |
libcamerasuezclient.so |
Suez framework client | Google analytics/telemetry |
libion.so / libion_google.so |
ION memory allocator | Buffer allocation |
libdmabufheap.so |
DMA-BUF heap allocator | Buffer sharing |
libyuv.so |
YUV format conversion | Format conversion |
5. Interception Strategy
Approach: Hook the Camera Provider Process
Since the camera HAL runs as a separate process, we inject our hooking library into that process.
Option A: wrap. Property + LD_PRELOAD (Recommended)
# Set via Magisk service.sh at boot:
setprop wrap.android.hardware.camera.provider@2.7-service-google \
"LD_PRELOAD=/data/local/camera_magic/libcamera_hook.so"
The wrap. property tells Android's init to restart the process with LD_PRELOAD.
Option B: Replace APEX Binary
Replace the provider binary with a wrapper that loads the original + our hooks. More invasive but more reliable.
Functions to Hook
| Function | Library | Purpose | Hook Strategy |
|---|---|---|---|
configureStreams |
libgooglecamerahal.so |
Know stream sizes/formats | Observe and store config |
processCaptureRequest |
libgooglecamerahal.so |
Replace frames | Fill buffers with video data |
flush |
libgooglecamerahal.so |
Cleanup | Pass through + cleanup our buffers |
Buffer Replacement Flow
1. configureStreams() called → store stream config (resolution, format, buffer count)
2. Allocate our own video frame buffers (matching HAL format)
3. processCaptureRequest() called:
a. Check if virtual camera is enabled (/data/local/camera_magic/config.txt)
b. If enabled: copy decoded video frame into request's output buffer
c. If disabled: pass through to original HAL
d. Return OK status
4. cameraserver receives buffer → sends to app → app shows virtual video
6. SELinux Considerations
The camera provider process runs as system user with its own SELinux context. Injecting libraries requires:
- SELinux policy allowing
systemprocess to load libraries from/data/local/ - Magisk
sepolicy.ruleto add:allow hal_camera_server system_data_file:file { read open execute };
7. Verification Commands
# Verify camera provider process
adb shell ps -A | grep camera.provider
# Check AIDL service registration
adb shell service list | grep camera
# View camera service events
adb shell dumpsys media.camera | head -30
# Check stream configurations
adb shell dumpsys media.camera | grep availableStreamConfigurations
# Monitor camera usage
adb shell dumpsys media.camera | grep "CONNECT\|DISCONNECT"
# Check loaded libraries in provider process
adb shell su -c 'cat /proc/$(pidof android.hardware.camera.provider@2.7-service-google)/maps' | grep camera