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, video recording, 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
  • πŸ₯Ύ Optional start on boot

⚠️ 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.
  • Raw Audio Stream (/audio/raw)
    • Usage: Listen to the raw (unprocessed) microphone source, bypassing any system audio processing (e.g. noise suppression, echo cancellation, AGC). On Android 7.0+ (API 24+) this uses the UNPROCESSED audio source; on older devices it falls back to the standard microphone.
    • Format: audio/wav chunked transfer-encoding (WAV container, 16-bit PCM mono, 44.1kHz).
      • VLC/MPV: Run vlc https://[ip_address]:[port]/audio/raw or mpv https://[ip_address]:[port]/audio/raw.
  • 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
  • Start Recording (/record/start, POST)
    • Usage: Start recording the live camera feed to a local MP4 file. The camera and H.264 encoder are started on demand if not already running.
    • Responses:
      • 201 Created: Recording started; returns the recording status JSON.
      • 409 Conflict: {"error":"already_recording", ...} β€” a recording is already in progress.
      • 503 Service Unavailable: {"error":"no_encoder", ...} β€” failed to initialize the H.264 encoder.
      • 500 Internal Server Error: {"error":"start_failed", ...} β€” recording failed to start.
  • Stop Recording (/record/stop, POST)
    • Usage: Stop the in-progress local MP4 recording and finalize the file.
    • Responses:
      • 200 OK: Recording stopped; returns the final recording status JSON.
      • 409 Conflict: {"error":"not_recording", ...} β€” no recording is in progress.
      • 500 Internal Server Error: {"error":"stop_failed", ...} β€” failed to finalize the recording.
  • Recording Status (/record/status)
    • Usage: Query whether a local MP4 recording is currently active.
    • Format: application/json β€” {"recording":<bool>, "uri":<string>, "durationMs":<long>, "width":<int>, "height":<int>} (or {"recording":false} when idle).
  • Enable Streaming (/control/start)
    • Usage: Re-enable streaming of the media routes after it has been disabled (e.g. by a home-automation system).
    • Format: application/json β€” {"streaming":true}
  • Disable Streaming (/control/stop)
    • Usage: Stop serving the media routes (/video/*, /audio*) without closing the listening port, and disconnect any viewers that are currently connected. The /control/* endpoints remain reachable so streaming can be turned back on remotely. When disabled, media routes return 503 Service Unavailable with a hint to POST /control/start.
    • Format: application/json β€” {"streaming":false}
  • Streaming Status (/control/status)
    • Usage: Query whether streaming is currently enabled.
    • Format: application/json β€” {"streaming":<bool>}

πŸŽ›οΈ 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.

πŸ€– AI generated code disclaimer

Some of the code in this repository may be generated with the assistance of AI tools. All changes are reviewed and tested on a real device with a human in the loop before being released.

πŸ›‚ 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, android.permission.WRITE_EXTERNAL_STORAGE): Required on older Android versions to load custom TLS/HTTPS certificates from file storage and to save locally recorded MP4 files (WRITE_EXTERNAL_STORAGE only applies to Android 9 / API 28 and below; on newer versions recordings are saved to app-specific storage without extra permissions).
  • Wi-Fi & Location (android.permission.ACCESS_WIFI_STATE, android.permission.ACCESS_FINE_LOCATION, android.permission.ACCESS_COARSE_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. (Location permissions only apply to Android 12L / API 32 and below.)
  • Start on boot (android.permission.RECEIVE_BOOT_COMPLETED): Allows the app to optionally start streaming automatically when the device boots. This feature is off by default and must be enabled in the app settings.
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

About

An Android H.264 / MJPEG IP Camera app

Topics

Resources

Stars

337 stars

Watchers

5 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages