Skip to content

Repository files navigation

Android IP Camera

downloads downloads

An Android H.264 / MJPEG IP Camera app

Desktop Browser

Install

Features

  • 🌎 Built in server, just open the video stream in a web browser, video app or even set it as a camera for Frigate or a Home Assistant MJPEG IP Camera (using https://[ip_address]:4444/video/mjpeg)
  • 📴 Option to turn the display off while streaming
  • 🤳 Switch between the main or selfie camera
  • 🎛️ Remote web interface with controls for camera section, image rotation, audio/video sync, flash light toggle, resolution, zoom, exposure and contrast
  • 🖼️ Choose between different image quality settings and frame rates (to help reduce phone over heating)
  • 🛂 Username and password protection
  • 🔐 Automatic TLS certificate support to protect stream and login details via HTTPS

⚠️ Warning

If you are planning to run this 24/7, please make sure that your phone does not stay at 100% charge. Doing so may damage the battery and cause it to swell up, which could cause it to explode.

Some models include an option to only charge to 80%, make sure this is enabled where possible.

Note: running at a higher image quality may cause some phones to over heat, which can also damage the battery.

Frigate config

Use the example config below to add your phones camera to Frigate, optionally uncommenting the audio lines (if required) & update the rtsp stream:

go2rtc:
  streams:
    android-cam-video:
      - "https://[ip_address]:4444/video/h264"
    # android-cam-audio:
    #   - "https://[ip_address]:4444/audio"
    # android-cam:
    #  - ffmpeg:android-cam-video#video=copy
    #  - ffmpeg:android-cam-audio#audio=copy

cameras:
  android-cam:
    enabled: true
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/android-cam-video # or android-cam
          input_args: preset-rtsp-restream
          roles:
            - detect
            - record
    #        - audio

Server URL Paths & Remote Control API

When the streaming server is running (default port 4444, via https:// or http:// depending on TLS configuration), you can access the following endpoints:

📺 Streams and Interfaces

  • Web Control Panel (/)
    • Usage: Open https://[ip_address]:[port]/ (or http://...) in any web browser.
    • Description: Serves the built-in control panel, which plays the rear or front camera video stream and allows muting/unmuting audio. Remote controls include: camera section, image rotation, flash light toggle, resolution, zoom, exposure and contrast.
  • Motion JPEG Video Stream (/video/mjpeg)
    • Usage: Open directly in a web browser or configure in external home automation tools (e.g. Home Assistant MJPEG IP Camera).
    • Format: multipart/x-mixed-replace; boundary=frame
  • Raw H.264 Video Stream (/video/h264)
    • Usage: Play in media players that support raw Annex-B H.264 stream demuxing.
      • VLC: Run vlc https://[ip_address]:[port]/video/h264 (may require ignoring TLS validation if self-signed).
      • MPV: Run mpv https://[ip_address]:[port]/video/h264 --demuxer-lavf-format=h264
  • Live Audio Stream (/audio)
    • Usage: Listen to the live microphone audio.
    • Format: audio/wav chunked transfer-encoding (WAV container, 16-bit PCM mono, 44.1kHz).
      • VLC/MPV: Run vlc https://[ip_address]:[port]/audio or mpv https://[ip_address]:[port]/audio.
  • Still Snapshot (/video/snapshot)
    • Usage: Fetch a single high-resolution image.
    • Format: image/jpeg
    • Query Parameter: ?camera=<id|front|back|toggle> to query a specific camera sensor.
      • Example: https://[ip_address]:[port]/video/snapshot?camera=back
  • Device Info and Capabilities JSON (/info.json)
    • Usage: Query available camera sensors, their supported resolutions, device battery percentage, Wi-Fi signal strength and individual camera len settings.
    • Format: application/json

🎛️ Remote Control Commands

Settings can be changed dynamically by passing query parameters in HTTP GET requests (e.g., to the root path / or any control endpoint).

  • Parameters:
    • camera=<id|front|back|toggle>: Switches the active camera sensor (supports logical:physical ids).
    • resolution=<low|medium|high|auto|max|WxH>: Change capture/stream resolution. Use low|medium|high for presets; auto/max or explicit WxH may be used to control the negotiated stream size.
    • zoom=<value>: Adjusts digital zoom (e.g., 1.0, 2.5).
    • scale=<value>: Adjusts preview stream scale (per-camera; e.g., 0.5, 1.0).
    • exposure=<value>: Adjusts exposure value (per-camera).
    • contrast=<value>: Adjusts contrast (software; per-camera, integer).
    • torch=<on|off|toggle>: Controls the flashlight.
    • audio_gain=<value>: Changes microphone gain multiplier (e.g., 1.0, 2.0).
    • focus_distance=<0..1|-1>: Set manual focus distance (0..1). Use -1 to restore autofocus.
    • snapshot_res=<max|stream>: Choose snapshot resolution for the selected camera (max for full sensor, stream to match current stream resolution).
    • rotate=<degrees>: Rotate preview/snapshot (persisted per-camera).
    • mirror=<true|false>: Mirror the video.
    • api=<auto|camerax|camera1>: Prefer capture API implementation.
  • Example command: https://[ip_address]:[port]/?torch=on&zoom=2.0

HTTPS/TLS certificates

To protect the stream and the password from being sent in plain-text over HTTP, a certificate can be used to start the stream over HTTPS.

The app will automatically generate a self-signed certificate on first launch, but if you have your own domain you can use Let's Encrypt to generate a trusted certificate and skip the self-signed security warning message, by changing the TLS certificate in the settings.

To generate a new self-signed certificate, clear the app settings and restart or clone this repo and run ./scripts/generate-certificate.sh then use the certificate personal_certificate.p12 file it generates.

Permissions

The app uses the following permissions to function:

  • Camera (android.permission.CAMERA): Required to capture and stream the video feed.
  • Microphone (android.permission.RECORD_AUDIO): Required to record and stream audio. (Requested optionally at runtime; streaming works without audio if denied).
  • Notifications (android.permission.POST_NOTIFICATIONS): Required on Android 13+ to post a persistent foreground service notification, keeping the background streaming server running reliably.
  • Network (android.permission.INTERNET, android.permission.ACCESS_NETWORK_STATE): Required to host the server and stream data to your browser/connected clients.
  • Storage (android.permission.READ_EXTERNAL_STORAGE): Required on older Android versions to load custom TLS/HTTPS certificates from file storage.
  • Wi-Fi & Location (android.permission.ACCESS_WIFI_STATE, android.permission.ACCESS_FINE_LOCATION, android.permission.NEARBY_WIFI_DEVICES): Used optionally to read the current Wi-Fi network's connection signal strength so it can be displayed in the web control panel overlay. If not granted, the Wi-Fi icon will be hidden.
Reproducible builds

This project uses reproducible builds. Release APKs should be built from a clean tree at the tagged commit using Gradle directly:

./gradlew clean assembleRelease

The release variant will automatically sign the APK build. Build-tools 35+ is known to produce signatures that fail reproducibility verification.

Build Variants

By default, release builds generate architecture-specific APK splits (armeabi-v7a, arm64-v8a) in addition to a universal APK. For F-Droid and other scenarios where a single universal APK is preferred, you can disable ABI splits:

F-Droid builds (single universal APK):

./gradlew clean assembleRelease -PenableAbiSplits=false

Release builds with architecture-specific APKs (default):

./gradlew clean assembleRelease

The enableAbiSplits property defaults to true. Set it to false to generate only the universal APK, which is the recommended approach for F-Droid to avoid unnecessary complexity in the build pipeline.

To verify that two unsigned builds from the same source are identical:

mkdir -p build/unsigned
./gradlew clean assembleRelease -PskipSigning --no-daemon --max-workers=1 -Dorg.gradle.parallel=false
cp app/build/outputs/apk/release/*universal-release.apk build/unsigned/build1.apk
./gradlew clean assembleRelease -PskipSigning --no-daemon --max-workers=1 -Dorg.gradle.parallel=false
cp app/build/outputs/apk/release/*universal-release.apk build/unsigned/build2.apk
cmp -s build/unsigned/build1.apk build/unsigned/build2.apk && echo OK
# or: shasum -a 256 build/unsigned/build1.apk build/unsigned/build2.apk

If they differ, inspect with diffoscope build/unsigned/build1.apk build/unsigned/build2.apk.

To verify your signed release APK matches an unsigned rebuild use apksigcopier - the first APK must be signed:

./gradlew clean assembleRelease
apksigcopier compare app/build/outputs/apk/release/*universal-release.apk --unsigned build/unsigned/build1.apk && echo OK

CI runs this check automatically via the Reproducible Build workflow.

Builds downloaded from the official repository should match the following signing certificate:

apksigner verify --print-certs app/build/outputs/apk/release/*universal-release.apk
Signer #1 certificate DN: CN=DigitallyRefined
Signer #1 certificate SHA-256 digest: 1111be81c861e199c6485d367c37680c4b778fba301980d2f0f9a2800f77f70a
Signer #1 certificate SHA-1 digest: 1560ceccdd719b2b97d431ad9a4c877abf5c2f32
Signer #1 certificate MD5 digest: 5fdf04f5b6bab9fdacbe28aa6dc85abb

Releases

Sponsor this project

Packages

Used by

Contributors

Languages