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

10 KiB

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)

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:

$ ps -A -o pid,ppid,cmd | grep camera
12018     1 cameraserver
12019     1 camera.provider
12361     1 virtual_camera

Zygisk module loaded in zygote64:

$ 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:

$ 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:

$ 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):

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)

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:

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

#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:

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

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