# 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: 1. **Hook the camera provider process** — Inject a library into the provider process via `wrap.` property + `LD_PRELOAD` 2. **Intercept Binder/AIDL IPC** — Hook the Binder communication between `cameraserver` and the provider 3. **Replace the APEX binary** — Swap the provider binary with a wrapper (requires APEX modification) 4. **Hook at cameraserver level** — Intercept in `cameraserver` before 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) ```aidl 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) ```aidl interface ICameraDevice { CameraMetadata getCameraCharacteristics(); int getResourceCost(); CameraStatus open(ICameraDeviceCallback callback, out ICameraDeviceSession session); void close(); } ``` #### ICameraDeviceSession — PRIMARY INTERCEPTION TARGET ```aidl 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) ```bash # 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: 1. SELinux policy allowing `system` process to load libraries from `/data/local/` 2. Magisk `sepolicy.rule` to add: ``` allow hal_camera_server system_data_file:file { read open execute }; ``` --- ## 7. Verification Commands ```bash # 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 ```