Files
CamSwapper/docs/hal-interface-map.md
2026-05-08 11:45:15 +02:00

11 KiB
Raw Permalink Blame History

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)

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.

# 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

# 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