Initial commit
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
# Buffer Sharing Design: Video → HAL
|
||||
|
||||
## Problem
|
||||
|
||||
We need to inject decoded video frames into the camera HAL's output buffer pipeline at the HAL process level (camera provider APEX), replacing real camera frames with virtual video frames.
|
||||
|
||||
## Architecture Context
|
||||
|
||||
```
|
||||
[App Process] [Camera Provider APEX Process]
|
||||
┌─────────────────┐ ┌──────────────────────────────────┐
|
||||
│ ExoPlayer │ │ libgooglecamerahal.so │
|
||||
│ ↓ decodes │ │ ↓ processCaptureRequest() │
|
||||
│ MediaCodec │ │ ↓ fills output buffers │
|
||||
│ ↓ YUV frames │ │ ↓ (AHardwareBuffer/gralloc) │
|
||||
│ VirtualCamera │ │ cameraserver reads via binder │
|
||||
│ Renderer │ └──────────────────────────────────┘
|
||||
│ ↓ renders to │ ↑
|
||||
│ Surface │ │ WE INJECT HERE
|
||||
└─────────────────┘ ┌──────────────────────────────────┘
|
||||
│ libcamera_hook.so (LD_PRELOAD) │
|
||||
│ intercepts processCaptureRequest │
|
||||
└──────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Evaluated Approaches
|
||||
|
||||
### 1. AHardwareBuffer Direct Write (CHOSEN)
|
||||
|
||||
**How it works**: HAL output buffers are `buffer_handle_t` backed by gralloc/AHardwareBuffer. Lock the buffer for CPU write, copy YUV frame data, unlock.
|
||||
|
||||
**Pros**:
|
||||
- No extra memory allocation needed (reuse HAL's buffer)
|
||||
- Direct write to the buffer cameraserver will read
|
||||
- Works with YUV_420_888 (format 32) which is CPU-accessible
|
||||
- Pixel 9a (API 36) fully supports AHardwareBuffer_lock()
|
||||
|
||||
**Cons**:
|
||||
- CPU copy required (not zero-copy)
|
||||
- IMPLEMENTATION_DEFINED (format 35) buffers may not be CPU-lockable
|
||||
- Must handle buffer stride/alignment correctly
|
||||
|
||||
**Verdict**: **CHOSEN** for YUV_420_888 preview streams. This is the primary virtual camera use case.
|
||||
|
||||
### 2. Shared Memory (ashmem/memfd) Cross-Process
|
||||
|
||||
**How it works**: App process decodes video, writes YUV frames to shared memory. Provider process reads from shared memory and copies into HAL buffers.
|
||||
|
||||
**Pros**:
|
||||
- Clean separation: decoder in app, injection in provider
|
||||
- App has full MediaCodec/ExoPlayer access
|
||||
- memfd_create() available on Android 11+ (API 30+)
|
||||
|
||||
**Cons**:
|
||||
- Extra copy: decoder → shared memory → HAL buffer
|
||||
- Cross-process synchronization complexity
|
||||
- Need IPC mechanism (binder, socket, or signal)
|
||||
|
||||
**Verdict**: **CHOSEN** as the transport mechanism from app to provider process. Combined with Approach 1 for the final write.
|
||||
|
||||
### 3. ION/dmabuf
|
||||
|
||||
**How it works**: Allocate ION memory, pass dmabuf fd to both decoder and HAL.
|
||||
|
||||
**Pros**:
|
||||
- Zero-copy potential
|
||||
- GPU/ISP accessible
|
||||
|
||||
**Cons**:
|
||||
- Vendor-specific ION heap configurations
|
||||
- Requires /dev/ion access (SELinux restrictions in provider process)
|
||||
- Complex buffer lifecycle management
|
||||
- Overkill for our use case
|
||||
|
||||
**Verdict**: **REJECTED**. Too complex, vendor-dependent, and SELinux-hostile.
|
||||
|
||||
## Chosen Architecture
|
||||
|
||||
### Two-Stage Pipeline
|
||||
|
||||
```
|
||||
Stage 1: App Process (Video Decoding)
|
||||
┌─────────────────────────────────────┐
|
||||
│ ExoPlayer → MediaCodec │
|
||||
│ ↓ decoded YUV420 frames │
|
||||
│ FrameRingBuffer (shared memory) │
|
||||
│ ↓ memfd + mmap │
|
||||
│ SharedMemoryWriter │
|
||||
└─────────────────────────────────────┘
|
||||
│
|
||||
│ memfd (fd passed via binder/property)
|
||||
↓
|
||||
Stage 2: Provider Process (HAL Injection)
|
||||
┌─────────────────────────────────────┐
|
||||
│ SharedMemoryReader │
|
||||
│ ↓ reads latest YUV frame │
|
||||
│ BufferConverter │
|
||||
│ ↓ handles format/stride conversion │
|
||||
│ AHardwareBufferWriter │
|
||||
│ ↓ lock → memcpy → unlock │
|
||||
│ HAL output buffer (to cameraserver) │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Buffer Lifecycle
|
||||
|
||||
1. **Allocation**: HAL allocates output buffers during `configureStreams()`. We observe and record buffer dimensions, format, and stride.
|
||||
|
||||
2. **Frame Production**: App process decodes video via MediaCodec, writes YUV frames to a ring buffer in shared memory (memfd). Each frame has a sequence number and timestamp.
|
||||
|
||||
3. **Frame Consumption**: In `processCaptureRequest()`, our hook:
|
||||
a. Reads the latest frame from shared memory
|
||||
b. Locks the HAL output buffer via `AHardwareBuffer_lock()` (for YUV_420_888)
|
||||
c. Copies frame data with stride conversion if needed
|
||||
d. Unlocks the buffer
|
||||
e. Returns to cameraserver (appears as real camera frame)
|
||||
|
||||
4. **Synchronization**:
|
||||
- Shared memory ring buffer uses atomic sequence numbers
|
||||
- Reader always grabs the latest complete frame (no blocking)
|
||||
- If no frame available, forward to real HAL (passthrough)
|
||||
|
||||
### Format Handling
|
||||
|
||||
| Stream Format | Strategy |
|
||||
|---|---|
|
||||
| YUV_420_888 (32) | Direct AHardwareBuffer_lock + memcpy |
|
||||
| IMPLEMENTATION_DEFINED (35) | Passthrough to real HAL (opaque GPU format) |
|
||||
| JPEG/BLOB (37) | Passthrough to real HAL (encode handled by HAL) |
|
||||
|
||||
### Memory Layout
|
||||
|
||||
Shared memory ring buffer (memfd):
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Header (256 bytes) │
|
||||
│ - magic: 0xCSWAPR00 │
|
||||
│ - frame_count: uint32 │
|
||||
│ - write_index: atomic<uint32> │
|
||||
│ - read_index: atomic<uint32> │
|
||||
│ - frame_width: uint32 │
|
||||
│ - frame_height: uint32 │
|
||||
│ - frame_size: uint32 │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ Frame 0 (width × height × 3/2 bytes) │
|
||||
│ - Y plane: width × height │
|
||||
│ - U plane: width/2 × height/2 │
|
||||
│ - V plane: width/2 × height/2 │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ Frame 1 ... │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ Frame N-1 ... │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Ring size: 4 frames (enough for 30fps video with slight timing jitter).
|
||||
|
||||
### AHardwareBuffer Write Procedure
|
||||
|
||||
```cpp
|
||||
// In processCaptureRequest hook:
|
||||
for (int i = 0; i < request->output_buffer_count; i++) {
|
||||
auto& buf = request->output_buffers[i];
|
||||
auto stream = find_stream(buf.stream_id);
|
||||
|
||||
if (stream->format == FORMAT_YUV_420_888 && g_hook_state.virtual_camera_enabled) {
|
||||
// Lock buffer for CPU write
|
||||
AHardwareBuffer* ahb = AHardwareBuffer_fromNativeHandle(buf.handle);
|
||||
AHardwareBuffer_Desc desc;
|
||||
AHardwareBuffer_describe(ahb, &desc);
|
||||
|
||||
void* cpu_addr = nullptr;
|
||||
int result = AHardwareBuffer_lock(ahb,
|
||||
AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
|
||||
-1, nullptr, &cpu_addr);
|
||||
|
||||
if (result == 0 && cpu_addr) {
|
||||
// Copy YUV frame from shared memory
|
||||
copy_yuv_frame_to_buffer(cpu_addr, desc.stride, desc.height,
|
||||
g_frame_reader->latest_frame());
|
||||
AHardwareBuffer_unlock(ahb, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Tasks
|
||||
|
||||
- **Task 7**: Video decoder (MediaCodec NDK) in app process
|
||||
- **Task 9**: Buffer format conversion + shared memory ring buffer
|
||||
- **Task 6**: processCaptureRequest integration (AHardwareBuffer write)
|
||||
|
||||
## Pixel 9a Specifics
|
||||
|
||||
- API level: 36 (Android 16)
|
||||
- AHardwareBuffer: fully supported
|
||||
- Gralloc: `/vendor/lib64/hw/gralloc.gs101.so` (Tensor G3)
|
||||
- YUV_420_888: CPU-accessible, linear layout
|
||||
- IMPLEMENTATION_DEFINED: opaque, GPU-only (passthrough)
|
||||
@@ -0,0 +1,268 @@
|
||||
# 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
|
||||
```
|
||||
@@ -0,0 +1,212 @@
|
||||
# CamSwapper System-Wide HAL Hook Testing Tutorial
|
||||
|
||||
This guide walks you through testing the system-wide camera HAL hook feature on a rooted Pixel 9a. This mode injects virtual camera feeds into all camera apps simultaneously via LD_PRELOAD, with no per-app Xposed scoping required.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Rooted Pixel 9a (Magisk or KernelSU installed)
|
||||
- ADB (Android Debug Bridge) set up on your computer
|
||||
- `camswapper-hal-hook-v1.zip` — the flashable module ZIP (pre-built, in the repo)
|
||||
- A test video file (MP4, H.264/H.265/VP9) or RTSP stream URL
|
||||
|
||||
## Step 1: Prepare the Device
|
||||
|
||||
1. Connect your Pixel 9a via USB
|
||||
2. Enable USB debugging in Developer Options
|
||||
3. Authorize the ADB connection on your device
|
||||
4. Verify ADB connection:
|
||||
```bash
|
||||
adb devices
|
||||
```
|
||||
You should see your device serial with "device" status.
|
||||
|
||||
5. Verify root access:
|
||||
```bash
|
||||
adb shell su -c id
|
||||
```
|
||||
Should return `uid=0(root) gid=0(root) groups=0(root)`.
|
||||
|
||||
## Step 2: Install the Module (ZIP Flash)
|
||||
|
||||
The module is packaged as a standard ZIP file that can be flashed directly in Magisk or KernelSU.
|
||||
|
||||
### Option A: Flash via Magisk App
|
||||
1. Transfer `camswapper-hal-hook-v1.zip` to your device
|
||||
2. Open Magisk app → Modules tab → "Install from storage"
|
||||
3. Select `camswapper-hal-hook-v1.zip`
|
||||
4. Wait for installation to complete
|
||||
5. Tap "Reboot"
|
||||
|
||||
### Option B: Flash via KernelSU Manager
|
||||
1. Transfer `camswapper-hal-hook-v1.zip` to your device
|
||||
2. Open KernelSU app → Modules tab → "+" button
|
||||
3. Select `camswapper-hal-hook-v1.zip`
|
||||
4. Wait for installation to complete
|
||||
5. Tap "Reboot"
|
||||
|
||||
### Option C: Flash via Custom Recovery (TWRP)
|
||||
1. Push the ZIP to your device: `adb push camswapper-hal-hook-v1.zip /sdcard/`
|
||||
2. Boot into recovery
|
||||
3. Flash the ZIP
|
||||
4. Reboot system
|
||||
|
||||
### Verify Installation
|
||||
After reboot, check the module is recognized:
|
||||
```bash
|
||||
adb shell su -c "ls -la /data/adb/modules/camera-hook/"
|
||||
```
|
||||
You should see `module.prop`, `post-fs-data.sh`, `service.sh`, `libcamera_hook.so`, `system.prop`, `sepolicy.rule`, and `customize.sh`.
|
||||
|
||||
## Step 3: Verify Module Installation
|
||||
|
||||
After the device reboots, run the included integration test script or verify manually.
|
||||
|
||||
### Option A: Run Integration Test Script
|
||||
```bash
|
||||
./test_hal_wrapper.sh
|
||||
```
|
||||
This script checks prerequisites, module installation, wrap property, config file, and hook loading status.
|
||||
|
||||
### Option B: Manual Verification
|
||||
|
||||
1. Check the wrap property is set correctly:
|
||||
```bash
|
||||
adb shell getprop wrap.android.hardware.camera.provider@2.7-service-google
|
||||
```
|
||||
Expected output: `LD_PRELOAD=/data/adb/modules/camera-hook/libcamera_hook.so`
|
||||
|
||||
2. Check the camera provider process is running:
|
||||
```bash
|
||||
adb shell pidof android.hardware.camera.provider@2.7-service-google
|
||||
```
|
||||
Should return a PID number.
|
||||
|
||||
3. Verify `libcamera_hook.so` is loaded in the provider process:
|
||||
```bash
|
||||
adb shell su -c "cat /proc/\$(pidof android.hardware.camera.provider@2.7-service-google)/maps | grep libcamera_hook"
|
||||
```
|
||||
Should show the path to `libcamera_hook.so`.
|
||||
|
||||
4. Check hook initialization logs:
|
||||
```bash
|
||||
adb logcat -d -s CameraHook
|
||||
```
|
||||
Should show hook initialization messages.
|
||||
|
||||
5. Check for SELinux denials:
|
||||
```bash
|
||||
adb shell su -c "dmesg | grep \"avc: denied\" | grep camera"
|
||||
```
|
||||
Should return empty if no denials are present.
|
||||
|
||||
## Step 4: Configure Video Source
|
||||
|
||||
The HAL hook reads configuration from `/data/local/camera_magic/config.txt`. You can configure it via the CamSwapper app or manually.
|
||||
|
||||
### Option A: Use CamSwapper App
|
||||
|
||||
1. Install the CamSwapper app on your device
|
||||
2. Open the app and navigate to HAL Mode settings
|
||||
3. Toggle "Enable HAL Mode"
|
||||
4. Select source mode: File or RTSP
|
||||
5. For File mode: select your video file (place it in `/data/local/camera_magic/video.mp4` or update config manually)
|
||||
6. For RTSP mode: enter your RTSP stream URL
|
||||
|
||||
### Option B: Manual Config File
|
||||
|
||||
Create or edit the config file directly via adb:
|
||||
|
||||
```bash
|
||||
adb shell su -c "mkdir -p /data/local/camera_magic"
|
||||
adb shell su -c "echo 'enabled=1' > /data/local/camera_magic/config.txt"
|
||||
adb shell su -c "echo 'source_mode=file' >> /data/local/camera_magic/config.txt"
|
||||
adb shell su -c "echo 'video_path=/data/local/camera_magic/test_video.mp4' >> /data/local/camera_magic/config.txt"
|
||||
adb shell su -c "echo 'rtsp_url=' >> /data/local/camera_magic/config.txt"
|
||||
adb shell su -c "chmod 644 /data/local/camera_magic/config.txt"
|
||||
```
|
||||
|
||||
#### Config File Format
|
||||
```
|
||||
enabled=1 # 0=off, 1=on
|
||||
source_mode=file # file or rtsp
|
||||
video_path=/data/local/camera_magic/video.mp4
|
||||
rtsp_url=rtsp://192.168.1.100:554/stream
|
||||
```
|
||||
|
||||
## Step 5: Test the Virtual Camera
|
||||
|
||||
1. Push your test video file to the device:
|
||||
```bash
|
||||
adb push test_video.mp4 /data/local/camera_magic/video.mp4
|
||||
adb shell su -c "chmod 644 /data/local/camera_magic/video.mp4"
|
||||
```
|
||||
|
||||
2. Open any camera app (Google Camera, Instagram, Telegram, etc.)
|
||||
|
||||
3. The camera preview should display your virtual video instead of the real camera feed.
|
||||
|
||||
4. Check hook logs for frame injection:
|
||||
```bash
|
||||
adb logcat -s CameraHook
|
||||
```
|
||||
Should show FPS counts and frame injection messages.
|
||||
|
||||
## Step 6: Test RTSP Stream (Optional)
|
||||
|
||||
1. Update config to RTSP mode:
|
||||
```bash
|
||||
adb shell su -c "sed -i 's/source_mode=file/source_mode=rtsp/' /data/local/camera_magic/config.txt"
|
||||
adb shell su -c "sed -i 's|video_path=.*|rtsp_url=rtsp://YOUR_RTSP_URL|' /data/local/camera_magic/config.txt"
|
||||
```
|
||||
|
||||
2. Restart the camera provider process to reload config:
|
||||
```bash
|
||||
adb shell su -c "killall android.hardware.camera.provider@2.7-service-google"
|
||||
```
|
||||
The process will restart automatically and load the new config.
|
||||
|
||||
3. Open a camera app to view the RTSP stream.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Hook Not Loading
|
||||
- Verify module is in `/data/adb/modules/camera-hook/`
|
||||
- Check wrap property is set correctly
|
||||
- Reboot the device
|
||||
- Check `adb shell dmesg | grep CameraHook` for error messages
|
||||
|
||||
### No Virtual Feed Showing
|
||||
- Verify `enabled=1` in config file
|
||||
- Check video file path is correct and accessible
|
||||
- Test RTSP URL with VLC first to ensure it's reachable
|
||||
- View hook logs: `adb logcat -s CameraHook`
|
||||
- Verify `libcamera_hook.so` is loaded in the provider process
|
||||
|
||||
### SELinux Denials
|
||||
- Check `adb shell dmesg | grep "avc: denied"`
|
||||
- Ensure `sepolicy.rule` is present in the module directory
|
||||
- Temporary test: set SELinux to Permissive with `adb shell su -c setenforce 0`
|
||||
|
||||
### Camera App Crashes
|
||||
- Check logcat for crashes: `adb logcat -d | grep -i crash`
|
||||
- Verify video format is supported (H.264/H.265/VP9)
|
||||
- Try a lower resolution/bitrate video file
|
||||
|
||||
## Uninstall/Disable HAL Hook
|
||||
|
||||
### Temporary Disable
|
||||
Set `enabled=0` in config file:
|
||||
```bash
|
||||
adb shell su -c "sed -i 's/enabled=1/enabled=0/' /data/local/camera_magic/config.txt"
|
||||
```
|
||||
Restart camera provider: `adb shell su -c "killall android.hardware.camera.provider@2.7-service-google"`
|
||||
|
||||
### Permanent Uninstall
|
||||
```bash
|
||||
adb shell su -c "rm -rf /data/adb/modules/camera-hook"
|
||||
adb shell su -c "setprop wrap.android.hardware.camera.provider@2.7-service-google ''"
|
||||
adb shell su -c "rm -rf /data/local/camera_magic"
|
||||
adb reboot
|
||||
```
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user