Files
CamSwapper/docs/STATUS-ANDROID16-PIXEL9A.md

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)