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

269 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```