314 lines
10 KiB
Markdown
314 lines
10 KiB
Markdown
# CamSwapper Status - Android 16 (Pixel 9a / tegu)
|
|
|
|
**Date**: 2026-05-08
|
|
**Device**: Pixel 9a (tegu)
|
|
**Android Version**: 16 (API 36)
|
|
**Root Solution**: KernelSU with ReZygisk
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
**Goal**: Replace physical camera frames with virtual video/RTSP stream at the system level (HAL layer).
|
|
|
|
**Current Status**: ⚠️ **PARTIALLY WORKING** - Hook library built and Zygisk module loads, but injection into camera processes **FAILS** because cameraserver and camera provider are **init services**, not zygote-forked processes.
|
|
|
|
---
|
|
|
|
## Process Architecture (Pixel 9a)
|
|
|
|
### Camera-Related Processes
|
|
|
|
| Process | PID | PPID | Type | Can Zygisk Hook? |
|
|
|---------|-----|------|------|-------------------|
|
|
| `cameraserver` | ~12018 | 1 (init) | **init service** | ❌ NO |
|
|
| `camera.provider` | ~12019 | 1 (init) | **init service** | ❌ NO |
|
|
| `virtual_camera` | ~12361 | 1 (init) | **init service** | ❌ NO |
|
|
| `zygote64` | 958 | 1 (init) | init service | ✅ YES (but irrelevant) |
|
|
| `system_server` | varies | zygote64 | zygote-forked | ✅ YES |
|
|
|
|
### Key Discovery
|
|
|
|
From `zygisk.hpp` line 30-45:
|
|
> "Please note that modules will only be loaded after zygote has forked the child process."
|
|
|
|
**This means Zygisk CANNOT hook init services.** The cameraserver and camera provider are started by init (PPID=1), not forked from zygote.
|
|
|
|
---
|
|
|
|
## What Works
|
|
|
|
### 1. HAL Hook Library (`libcamera_hook.so`)
|
|
- **Built**: 336KB (stripped), ARM64 aarch64
|
|
- **Location**: `/data/local/camera_magic/libcamera_hook.so`
|
|
- **Functionality**:
|
|
- ✅ Intercepts `dlopen`/`dlsym` to hook Camera HAL symbols
|
|
- ✅ Implements `CameraProviderHook`, `CameraDeviceHook`, `CameraDeviceSessionHook`
|
|
- ✅ `inject_video_frame()` implemented (reads from shared memory ring buffer)
|
|
- ✅ Video decoder (MediaCodec NDK) → YUV420 → shared memory
|
|
- ✅ RTSP client → RTP receive → shared memory
|
|
- ✅ Buffer converter (YUV420↔NV21 with NEON)
|
|
|
|
### 2. Zygisk Module (`camswapper-zygisk-v2.zip`)
|
|
- **Properly implemented** using Zygisk API v4
|
|
- **File**: `/data/adb/modules/camera-hook-zygisk/zygisk/arm64-v8a.so`
|
|
- **Status**: Loaded in ReZygisk (confirmed in `/data/adb/rezygisk/state.json`)
|
|
- **Symbols**: `zygisk_module_entry` exported correctly
|
|
- **Code**: Uses `zygisk::ModuleBase` with `REGISTER_ZYGISK_MODULE(CamSwapperModule)`
|
|
|
|
### 3. KernelSU Module (`camswapper-kernelsu-v2.zip`)
|
|
- **Contains**: `libcamera_hook.so`, `service.sh`, `post-fs-data.sh`
|
|
- **Injection method**: ptrace dlopen (BROKEN - see below)
|
|
|
|
---
|
|
|
|
## What Does NOT Work
|
|
|
|
### 1. ❌ Zygisk Injection Fails for Camera Processes
|
|
|
|
**Root Cause**: cameraserver and camera.provider are **init services** (PPID=1), not zygote-forked.
|
|
|
|
**Evidence**:
|
|
```bash
|
|
$ ps -A -o pid,ppid,cmd | grep camera
|
|
12018 1 cameraserver
|
|
12019 1 camera.provider
|
|
12361 1 virtual_camera
|
|
```
|
|
|
|
**Zygisk module loaded in zygote64**:
|
|
```bash
|
|
$ cat /proc/zygote64/maps | grep camera-hook
|
|
70fa88f000-70fa8c2000 r-xp ... /data/adb/modules/camera-hook-zygisk/zygisk/arm64-v8a.so
|
|
```
|
|
|
|
**But NOT in cameraserver**:
|
|
```bash
|
|
$ cat /proc/cameraserver/maps | grep camera-hook
|
|
(empty - not loaded)
|
|
```
|
|
|
|
**Conclusion**: Zygisk `postServerSpecialize()` only runs for `system_server`, not cameraserver. The `is_camera_process()` check in `zygisk_entry.cpp` never triggers because cameraserver isn't zygote-forked.
|
|
|
|
---
|
|
|
|
### 2. ❌ Ptrace dlopen Injection Crashes (SIGSEGV)
|
|
|
|
**Method**: Attach to cameraserver via ptrace, call `__loader_dlopen` in linker64 to load `libcamera_hook.so`.
|
|
|
|
**Result**: Crashes with SIGSEGV when loading `libcamera_hook.so` (works for `libc.so`).
|
|
|
|
**Root Cause**: Linker namespace restrictions on Android 16. The `__loader_dlopen` function requires proper namespace setup that ptrace can't replicate.
|
|
|
|
**Evidence** (from previous testing):
|
|
- dlopen for `libc.so` → returns valid handle (0x2443dbca3ce35c9b)
|
|
- dlopen for `libcamera_hook.so` → returns NULL or crashes
|
|
- APEX linker namespace blocks `/data/local/tmp` paths
|
|
|
|
---
|
|
|
|
### 3. ⚠️ Virtual Camera Service (Potential Solution)
|
|
|
|
**Service**: `virtual_camera` (PID ~12361)
|
|
**Interface**: `android.hardware.camera.provider.ICameraProvider/virtual/0`
|
|
**Status**: Running but provides **0 camera devices**
|
|
|
|
**Evidence**:
|
|
```bash
|
|
$ dumpsys android.hardware.camera.provider.ICameraProvider/virtual/0
|
|
== Camera Provider HAL ... virtual/0-1 (v2.0, remote) static info: 0 devices: ==
|
|
```
|
|
|
|
**Service Definition** (`/system/etc/init/virtual_camera.hal.rc`):
|
|
```rc
|
|
service virtual_camera /system/bin/virtual_camera
|
|
class core
|
|
user system
|
|
group system
|
|
interface aidl virtual_camera
|
|
interface aidl android.hardware.camera.provider.ICameraProvider/virtual/0
|
|
oneshot
|
|
disabled
|
|
```
|
|
|
|
**Key Finding**: The service is "disabled" in RC file but is somehow running. It provides 0 devices, meaning it's not configured with a virtual camera stream.
|
|
|
|
---
|
|
|
|
## Attempted Solutions (All Failed)
|
|
|
|
### 1. `wrap.cameraserver` Property
|
|
- **Method**: Set `wrap.cameraserver=/system/bin/cameraserver_wrapper`
|
|
- **Result**: ❌ Silently ignored on production build (ro.debuggable=0)
|
|
|
|
### 2. Bind-Mount Wrapper
|
|
- **Method**: Bind-mount wrapper binary over `/system/bin/cameraserver`
|
|
- **Result**: ❌ cameraserver crashes (SELinux context or APEX library resolution)
|
|
|
|
### 3. Ptrace dlopen Injection
|
|
- **Result**: ❌ SIGSEGV (linker namespace issue)
|
|
|
|
### 4. Zygisk Module (API v4)
|
|
- **Result**: ❌ Loads in zygote but can't hook init services
|
|
|
|
### 5. Original `zygisk_entry.cpp` (Wrong API)
|
|
- **Problem**: Exported raw C symbols (`zygisk_module_entry`, `pre_app_specialize`, etc.)
|
|
- **Fix**: Rewrote to use proper `zygisk::ModuleBase` class
|
|
- **Result**: Still can't hook init services (fundamental limitation)
|
|
|
|
---
|
|
|
|
## Path Forward (Recommended Approaches)
|
|
|
|
### Option A: Use `virtual_camera` Service (BEST OPTION)
|
|
|
|
**Concept**: Instead of hooking existing camera provider, configure Android's built-in `virtual_camera` service to provide virtual camera devices.
|
|
|
|
**Steps**:
|
|
1. Determine how to configure `virtual_camera` to provide a camera device
|
|
2. Point it to our video/RTSP stream
|
|
3. No injection needed - uses Android's native virtual camera support
|
|
|
|
**Research Needed**:
|
|
- Read AOSP source for `virtual_camera` service
|
|
- Find configuration file or binder interface to add virtual camera streams
|
|
- Check `IVirtualCameraService` interface (service list shows it exists)
|
|
|
|
---
|
|
|
|
### Option B: Hook at Binder IPC Layer
|
|
|
|
**Concept**: Intercept camera HAL binder calls instead of hooking the process.
|
|
|
|
**Method**:
|
|
1. Use binder hook (via Zygisk or KernelSU)
|
|
2. Intercept `ICameraDeviceSession::processCaptureRequest`
|
|
3. Replace buffer contents before they reach the camera HAL
|
|
|
|
**Advantage**: Works regardless of which process handles the camera
|
|
|
|
---
|
|
|
|
### Option C: Modify init.rc (Require Reboot + Possible Bootloop)
|
|
|
|
**Concept**: Add `setenv LD_PRELOAD=/path/to/libcamera_hook.so` to cameraserver service definition.
|
|
|
|
**Risk**: High - modifying init.rc can cause bootloops on production builds
|
|
|
|
---
|
|
|
|
### Option D: Use `virtualizationservice` (Discovered Running)
|
|
|
|
**Concept**: Pixel 9a runs `virtualizationservice` (PID 6360) which spawns `virtmgr_virtualizationservice` and `virtual_camera`.
|
|
|
|
**Possibility**: The virtual camera infrastructure is already running - we just need to configure it properly.
|
|
|
|
---
|
|
|
|
## File Inventory
|
|
|
|
### Built Files
|
|
| File | Size | Purpose |
|
|
|------|------|---------|
|
|
| `native/build/libcamera_hook.so` | 336KB | HAL hook library (stripped) |
|
|
| `zygisk-module/zygisk/arm64-v8a.so` | 223KB | Zygisk module (proper API v4) |
|
|
| `camswapper-kernelsu-v2.zip` | 1.9MB | KernelSU module zip |
|
|
| `camswapper-zygisk-v2.zip` | 79KB | Zygisk module zip |
|
|
|
|
### Source Files
|
|
| File | Lines | Purpose |
|
|
|------|-------|---------|
|
|
| `native/src/camera_wrapper.cpp` | 386 | HAL hook with `inject_video_frame()` |
|
|
| `native/src/video_decoder.cpp` | 401 | MediaCodec decoder → shared memory |
|
|
| `native/src/rtsp_client.cpp` | 653 | RTSP client → RTP receive |
|
|
| `native/src/buffer_converter.cpp` | 173 | YUV420↔NV21 conversion |
|
|
| `zygisk-module/zygisk_entry.cpp` | 88 | Zygisk module (API v4) |
|
|
| `zygisk-module/jni/zygisk.hpp` | 391 | Zygisk API header |
|
|
|
|
### Headers
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `native/include/video_decoder.h` | Ring buffer shared memory definitions |
|
|
| `native/include/rtsp_client.h` | RTSP client state structures |
|
|
| `native/include/camera_hal/*.h` | AIDL camera HAL interface definitions |
|
|
|
|
---
|
|
|
|
## Key Learnings for Future AI
|
|
|
|
### 1. Always Check Process Parent (PPID)
|
|
Before attempting Zygisk injection, run:
|
|
```bash
|
|
adb shell "ps -A -o pid,ppid,cmd | grep <target_process>"
|
|
```
|
|
If PPID=1 (init), Zygisk **cannot** hook it.
|
|
|
|
### 2. Zygisk API v4 Correct Usage
|
|
```cpp
|
|
#include "zygisk.hpp"
|
|
class MyModule : public zygisk::ModuleBase {
|
|
void onLoad(Api *api, JNIEnv *env) override { ... }
|
|
void postServerSpecialize(const ServerSpecializeArgs *args) override { ... }
|
|
};
|
|
REGISTER_ZYGISK_MODULE(MyModule)
|
|
```
|
|
|
|
### 3. ReZygisk State File
|
|
Check module loading status:
|
|
```bash
|
|
adb shell "su -c 'cat /data/adb/rezygisk/state.json'"
|
|
```
|
|
|
|
### 4. Virtual Camera Service Exists
|
|
On Android 16 (Pixel 9a), `virtual_camera` service is available but disabled by default. It provides 0 devices until configured.
|
|
|
|
### 5. Ptrace dlopen Broken on Android 16
|
|
Linker namespace restrictions prevent ptrace-based dlopen for anything other than system libraries.
|
|
|
|
---
|
|
|
|
## Next Session Action Plan
|
|
|
|
1. **Research `virtual_camera` configuration**:
|
|
- Search AOSP source for `virtual_camera` implementation
|
|
- Find how to add virtual camera streams
|
|
- Check `IVirtualCameraService` binder interface
|
|
|
|
2. **Test Option A first** (most promising):
|
|
- Enable virtual camera with custom stream
|
|
- Verify it appears as a camera device
|
|
- Point it to video/RTSP source
|
|
|
|
3. **If Option A fails**, try Option B (binder hook)
|
|
|
|
4. **Document findings** in this file for future reference
|
|
|
|
---
|
|
|
|
## Quick Reference Commands
|
|
|
|
```bash
|
|
# Check camera processes
|
|
adb shell "ps -Z | grep -E 'cameraserver|camera.provider|virtual_camera'"
|
|
|
|
# Check if Zygisk module loaded in process
|
|
adb shell "su -c 'cat /proc/\$(pidof zygote64)/maps | grep camera-hook'"
|
|
|
|
# Check virtual camera status
|
|
adb shell "dumpsys android.hardware.camera.provider.ICameraProvider/virtual/0"
|
|
|
|
# Check binder services
|
|
adb shell "service list | grep -i camera"
|
|
|
|
# View ReZygisk state
|
|
adb shell "su -c 'cat /data/adb/rezygisk/state.json'"
|
|
|
|
# Check module.prop description (updated by service.sh)
|
|
adb shell "su -c 'cat /data/adb/modules/camera-hook-zygisk/module.prop'"
|
|
```
|
|
|
|
---
|
|
|
|
**Last Updated**: 2026-05-08 by Atlas (OhMyOpenCode)
|
|
**Session ID**: Compressed conversation (b3, b4, b5, b6, b7, b12, b13, b14, b15, b16, b17)
|