# 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 " ``` 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)