Developer: Afaq Ahmad
License: Apache License 2.0
Repository: https://github.com/yourusername/OpenSourceAndroidVirtualCamera
This is a fully open-source Android virtual camera service that substitutes the physical camera with a pre-recorded video. The service runs entirely in the background and feeds the chosen video in an endless loop to the Android camera framework, appearing as a standard camera to applications such as Zoom, Google Meet, Teams, WhatsApp, OBS, or any live-streaming client.
This project is developed by Afaq Ahmad and is released under the Apache License 2.0 to ensure it remains fully open-source, modifiable, and study-friendly for the community.
- Endless Video Loop - Selected video plays in a seamless infinite loop without user intervention.
- Background Operation - Runs as a foreground service; continues even when UI is dismissed or screen is off.
- Minimal Resource Usage - Optimized decoder loop keeps battery and CPU impact low.
- Start/Stop Control - Can be started or stopped programmatically or through the small control screen.
- Custom Video Selection - Use the bundled sample video or select any video from device storage.
- Boot Auto-Start - Optionally starts automatically on device boot (requires user grant).
- No Root Required - Uses
MediaProjectionAPIs available on Android 10+. - Fully Open Source - Apache 2.0 licensed; all code is readable and modifiable.
┌─────────────────────────────────────────────┐
│ MainActivity (UI) │
│ - Permission handling │
│ - Video picker (custom / default) │
│ - Start / Stop buttons │
│ - MediaProjection permission grant flow │
└──────────────────┬──────────────────────────┘
│ binds/starts
┌──────────────────▼──────────────────────────┐
│ VirtualCameraService (Foreground) │
│ - Receives projection token from Activity │
│ - Creates MediaProjection + VirtualDisplay │
│ - Owns VideoDecoder + CoreVirtualCamera │
│ - Manages lifecycle (start/stop/boot) │
│ - Persistent notification with Stop action │
└──────────────────┬──────────────────────────┘
│
┌───────────┴────────────┐
▼ ▼
┌──────────────┐ ┌───────────────────┐
│ VideoDecoder │ │ CoreVirtualCamera │
│ -Extractor/ │ │ - ImageReader │
│ MediaCodec │ │ - RGBA→NV21 │
│ -Seek/loop │ │ - Frame sink │
│ -Callback │ │ - VirtualDisplay │
└──────────────┘ └───────────────────┘
- Android 10 (API 29) or higher
- Android Studio Hedgehog (2023.1.1) or newer
- Java 11 or Kotlin 1.9+ runtime
- Gradle 8+ wrapper included
| Permission | Purpose | Required |
|---|---|---|
CAMERA |
Register virtual camera capability | Yes |
FOREGROUND_SERVICE |
Background service operation | Yes |
FOREGROUND_SERVICE_CAMERA |
Camera-type foreground service | Yes |
POST_NOTIFICATIONS |
Foreground service notification (Android 13+) | Conditional |
READ_EXTERNAL_STORAGE |
Pick custom video from storage | Conditional |
SYSTEM_ALERT_WINDOW |
Overlay permission (if needed) | No |
cd OpenSourceAndroidVirtualCameraEnsure local.properties exists:
sdk.dir=/Users/macos/Library/Android/sdkOr set environment variable:
export ANDROID_HOME=/Users/macos/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/platform-tools./gradlew clean assembleDebugapp/build/outputs/apk/debug/app-debug.apk
adb install -r app/build/outputs/apk/debug/app-debug.apkadb shell am start -n com.virtualcamera/.MainActivity- Open the app on your device.
- Tap "Start Virtual Camera" or "Start with Custom Video".
- Android will show a "Screen Capture" permission dialog.
- Tap "Start Now" to grant
MediaProjectionaccess.- This permission is safe: it only lets the app capture its own virtual display content, not your real screen.
- Open Zoom, Google Meet, or any camera app.
- In camera settings, select "virtual_camera_fd".
- You should see the looping video feed.
The project does not bundle a large video file. Use one of the two methods below:
- Open the app.
- Tap "Start with Custom Video".
- Pick any MP4/WebM file from your device.
- Grant projection permission when prompted.
Place your video file at:
app/src/main/res/raw/sample_video.mp4
The app will automatically detect and use it when you tap "Start Virtual Camera".
- Uses
MediaExtractor+MediaCodecfor hardware-accelerated decoding. - Reads the video track, configures the decoder, and starts a decode loop on a dedicated thread.
- On end-of-stream, the decoder seeks back to frame 0 and continues looping.
- Calls
DecoderCallback.onFrameAvailable()for each decoded frame.
- Creates a
MediaProjectionfrom the cached permission token. - Creates a
VirtualDisplaybacked by anImageReader(RGBA_8888). - Converts RGBA frames to NV21 format using
rgbaToNV21(). - Stores the latest frame for consumption by the decoder callback.
- Receives start/stop/boot intents.
- Manages
VideoDecoderandCoreVirtualCameralifecycles. - Posts a persistent notification with a Stop action.
- Uses
START_STICKYso the service restarts if killed by the system.
- Handles runtime permissions (
CAMERA,POST_NOTIFICATIONS, storage). - Launches
MediaProjectionpermission flow. - Binds to the service for state observation (
IDLE/RUNNING/ERROR). - Observes
stateFlowto update the UI status.
# 1. Install app
adb install -r app/build/outputs/apk/debug/app-debug.apk
# 2. Launch main activity
adb shell am start -n com.virtualcamera/.MainActivity
# 3. Watch logcat for service state
adb logcat -s VirtualCameraService VideoDecoder CoreVirtualCamera MainActivity- Install and launch the app.
- Tap "Start with Custom Video" and pick a short video (e.g., 30 seconds).
- Grant the screen capture permission.
- Open Zoom/Meet, select the virtual camera.
- Let it run for 10 minutes.
- Confirm:
- No unexpected pauses or freezes.
- Video loops seamlessly at the end of each cycle.
- Foreground notification remains visible.
- Logcat shows no crash loops.
# Monitor logs for 10 minutes
adb logcat -s VirtualCameraService VideoDecoder | grep --line-buffered -E "(error|ERROR|STOPPED|START)"app/src/main/java/com/virtualcamera/
├── CoreVirtualCamera.kt # MediaProjection + VirtualDisplay + frame conversion
├── VideoDecoder.kt # MediaExtractor + MediaCodec decode loop
├── VirtualCameraService.kt # Foreground service + notification lifecycle
├── MainActivity.kt # UI, permissions, projection flow
└── FrameSink.kt # Interface for frame data transport
Place sample_video.mp4 in app/src/main/res/raw/.
Edit VideoDecoder output format or CoreVirtualCamera width/height (default 1280×720).
Use Intent.ACTION_OPEN_DOCUMENT + VideoDecoder URI-fed MediaExtractor.
Edit decodeLoop() in VideoDecoder.kt — the loop is a standard MediaCodec input/output pump.
| Problem | Solution |
|---|---|
MediaProjection denied |
Re-tap Re-grant in the app or reinstall. |
| Virtual camera not listed | Ensure you granted projection and the service is RUNNING. |
| APK won't install | Verify compileSdk/targetSdk versions, uninstall old APK first. |
Build fails on minSdk |
Ensure SDK Platform 29 is installed in SDK Manager. |
Apache License 2.0 (OSI-approved)
Copyright 2024 Afaq Ahmad
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
This project is maintained by Afaq Ahmad. Contributions, issues, and pull requests are welcome on GitHub.
- Fork the repository.
- Create your feature branch (
git checkout -b feature/new-feature). - Commit your changes.
- Push to the branch (
git push origin feature/new-feature). - Open a Pull Request.
- Developer: Afaq Ahmad
- Project: Open Source Android Virtual Camera
- License: Apache 2.0