Skip to content
OpenIPCPublic

About

Miniature and universal H.264 and H.265 decoder for Android devices

Topics

Resources

Code of conduct

Stars

5 stars

Watchers

0 watching

Forks

Latest commit

 

History

538 Commits

Folders and files

Repository files navigation

OpenIPC logo

OpenIPC Decoder

English | Русский | 简体中文

Build Release License Platform

A small Android app for watching RTSP streams from IP cameras, with hardware H.264/H.265 decoding. It is built for cameras running OpenIPC firmware and works on phones, tablets, TVs, set-top boxes and car devices.

Multi-view with eight cameras and the menu

Download

Ready-to-install builds are in the latest release:

File What it is
decoder-v2.apk Current version, recommended
decoder.apk Legacy version from the basic branch

Each 2.xx version also has a release of its own — v2.01 and later, on the releases page — with that version's APK and the changes since the previous one.

A beta channel is available on Google Play. To join it, send your e-mail through the OpenIPC Telegram channels.

Features

  • Video: RTSP playback, hardware H.264/H.265 decoding via MediaCodec, MJPEG over RTSP (RTP/JPEG) and over HTTP (multipart/x-mixed-replace) in the single view and in multi-view, automatic decoder recovery, a lossy UDP stream shown with concealed damage instead of a black picture, a warning when the device's video decoder stops responding, picture fitted to any screen shape
  • Audio: AAC (RFC 3640 mpeg4-generic, ADTS, MP4A-LATM), Opus, G.711 μ-law/A-law, L16 PCM (mono/stereo); in multi-view, the sound of the selected cell
  • Display: single camera, or multi-view (Quad) with up to 8 cameras plus the U cell (a camera that pushes RTP) in a 2×2 or 3×3 grid; adaptive decoding (full rate or key frames per cell, learnt per device); S: key frames only, in multi-view and in the single view
  • Controls: pinch-to-zoom, pan, double-tap to reset, Android TV remote (Leanback launcher)
  • Network: RTSP over TCP or UDP (Auto, the default, falls back to TCP when UDP is refused, brings no video or keeps losing key frames), the transport in use shown on connect, RTSP multicast, plain RTP pushed over UDP by the camera (unicast or multicast), Basic and Digest authentication, RTSP keepalive, automatic reconnection with exponential back-off, connection-quality indicator
  • WebUI: built-in browser for the camera's web interface
  • More: 8 camera slots, URLs stored encrypted, long-press screenshot, URLs can be shared from other apps

Requirements

  • Android 5.0 (API 21) or higher
  • An IP camera with RTSP, or one that pushes RTP over UDP (OpenIPC Majestic outgoing), see Transports
  • Google services (GAPPS) are not required: the app works on AOSP builds without them. WebUI uses the system WebView, so it needs a working WebView package (on some devices this is Google's Android System WebView); disabling it affects only WebUI — #15

Quick start

  1. Install the APK from the latest release and launch the app.
  2. Tap the screen, or press OK on a remote, to open the menu.
  3. Choose a camera number (1–8), then press Settings and enter the camera URL:
    rtsp://username:password@ip_address:554/stream=0
    
    Under the URL you can choose the transport; Auto (UDP, falling back to TCP) is the default for a new slot.
  4. Press Save. The app connects straight away and reconnects automatically if the stream drops.

Controls

Menu

The menu is a grid: the eight slots on top, two rows of four below.

[ 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 ]
[ Settings | WebUI  | Grid   | Slides ]
[ Exit     | Reset  | Debug  | UDP    ]

The menu on a phone WebUI on a phone: the page scrolls the field above the keyboard

Item Action
1–8 Switch to a camera slot. The active slot's number shows the connection quality: green under 100 ms of jitter, yellow 100–300 ms, red above 300 ms. A grey number is an empty slot.
Settings Edit the URL and the transport of the active slot (single view only; grey elsewhere)
WebUI Open the camera's web interface; credentials come from the RTSP URL (single view only; grey elsewhere)
Grid The view: multi-view (2×2 or 3×3) or the single camera. Grid does not change the playback mode (Slides)
Slides The playback mode: every frame, or key frames only (the picture updates once per key frame interval, MJPEG once a second), the lowest decoder load. Works in multi-view and in the single view and never changes the view
Exit Close the app. The small text under it is the app version and build hash.
Reset Clear all settings after a confirmation (Cancel has the focus) to set the app up again from scratch: camera slots, transports and the other settings are removed, and the app opens as right after installing. The cameras are not changed
Debug Text in the corner of every multi-view cell (resolution, codec, decoded/received fps, key frames, status), or in none
UDP The U cell full-screen: the camera that pushes RTP over UDP to port 5600

Items that are on — Grid, Slides, Debug, UDP and the active slot — have an orange fill; the item the remote is on has a light blue frame.

Gestures and keys

Input Action
Tap / OK / Enter Open or close the menu
Back / Esc Close the menu
Pinch, then drag Zoom (up to 5×) and pan, single view
Double tap Reset zoom
Long press Single view: save a screenshot to Pictures/OpenIPC. Multi-view: play this cell's sound

Opening a camera from another app

The app accepts plain-text shares (ACTION_SEND, text/plain). Sharing an rtsp://, udp:// or http:// URL from another app, for example OpenIPC Network, puts it into the active slot. An optional transport extra (tcp, udp, auto, multicast) sets the slot's transport:

adb shell am start -a android.intent.action.SEND -t text/plain \
  --es android.intent.extra.TEXT 'rtsp://user:pass@192.168.1.10/stream=0' \
  --es transport udp -n com.openipc.decoder/.Decoder

Multi-view (Quad)

Shows the configured slots in a 2×2 grid, or in a 3×3 grid once any of slots 5–8 is used. The ninth cell of the 3×3 grid, the U cell, plays a camera that pushes RTP over UDP to port 5600 of the device (udp://@:5600, see udp:// slots); until a stream arrives it shows every address of the device to send to. UDP in the menu opens it full-screen. Other ports or multicast groups go into a slot as a udp:// URL. Each cell has its own connection and decoder, and the device's video decoder has a fixed budget: the AG-216 test box, for example, decodes about two 1080p30 streams at full rate. So the grid adapts: cells start on key frames only, then decode every frame in priority order (the U cell while it receives a stream, the cell whose sound plays, then slot order) while the decoder keeps up. Cells beyond that stay on key frames, with the picture updated once per key frame interval (key frames in the corner text, menu Debug). The limit is learnt on each device by measurement. Substreams of the cameras, where available, let more cells play at full rate. A key-frame cell over the hardware decoder's instance limit (the AG-216 has 4 × H.265) uses a software decoder instead (· software in the corner); only beyond that budget too does it show Decoder limit: N × H.265. When even key frames are too much, the lowest-priority cell is paused (Paused: decoder overloaded) and tried again after a quiet period; losses in the first seconds after the grid starts, when every decoder starts at once, do not count. A cell whose UDP stream loses a piece of every key frame shows the frames with the damage concealed (· damaged) rather than a black picture. Some cameras accept only one RTSP client at a time. Long-press a cell to play its audio (long-press it again to mute); the selected cell shows an AUDIO badge and is remembered. Tap any cell to open the menu.

Transports

Each slot has its own transport, chosen under the URL in Settings (or with the transport extra of a shared link):

Transport How the video arrives Notes
TCP RTP inside the RTSP connection Works everywhere, also through NAT and firewalls
UDP RTP over UDP, a port pair per track Lower latency on a LAN. Lost packets are skipped, not resent. Never falls back
Auto (default) UDP, or TCP when UDP does not work Falls back to TCP when the camera refuses UDP or no video arrives within 3 s, then stays on TCP until the slot is changed
Multicast RTSP multicast (Android 8.0+), or TCP when it is refused For servers that offer RTSP multicast. OpenIPC Majestic and XM cameras do not: they reply 400 or 451, and the slot plays over TCP in the same connection

The transport actually in use is shown when a stream starts in single view (Connected • UDP • H.265 …), on the debug page and in /stats.json (transport; the slot's setting is transport_mode). After a fallback it reads, for example, TCP (auto).

Single view right after connecting: transport, codecs and bitrate

UDP slots (Majestic outgoing)

A slot can also receive RTP that the camera pushes by itself, without RTSP. Put a udp:// URL into the slot instead of rtsp://:

URL Meaning
udp://@:5600 Listen on port 5600
udp://@239.1.2.3:5600 Join the multicast group 239.1.2.3 on port 5600
udp://192.168.1.10@239.1.2.3:5600 The same, but accept packets from this sender only
udp://@:5600?codec=h265&pt=97 Set the codec or the video payload type when the defaults do not fit

The codec is taken from the RTP payload type (96 → H.264, 97 → H.265) unless codec= is given, and a codec change on the camera is followed while playing. Video only. Two slots cannot listen on the same port.

On the camera, add the device as an outgoing destination in /etc/majestic.yaml (then restart Majestic):

outgoing:
  servers:
    - udp://192.168.1.20:5600

/api/v1/outgoing.json on the camera shows whether the destination is live. Majestic can also send to a multicast group (udp://239.1.2.3:5600), so several devices can watch one camera. On Wi‑Fi the app holds a multicast lock while it is in a group.

HTTP MJPEG slots

A slot also takes an HTTP MJPEG URL, such as Majestic's http://user:password@<camera>/mjpeg: a multipart/x-mixed-replace stream with one JPEG per part, chunked or not. Basic credentials are sent with the request, Digest after the camera asks for it; the stream reconnects like RTSP. It plays in the single view and in multi-view; the transport shows as HTTP. There is no audio on this path.

Debug page (port 8085)

While the app runs it serves its debug information over HTTP on port 8085, for a browser or curl on the same network:

  • http://<device>:8085/ — the snapshot fills the page, with each cell's camera, size, fps and losses (in red) drawn over it; the header shows the version, the device time and uptime. Info On / Off in the header hides the text over the cells (and the list below them on a narrow screen), leaving only the picture; it is Off until switched on, and the choice is remembered in the browser. A red note in the header says when the device's video decoder stopped responding (decoder_stuck);
  • http://<device>:8085/snapshot.jpg (?labels=0: frames only) — a picture laid out like the screen, at the screen's resolution (at most 1920×1080), made from the frames the app shows right now; a cell without video is black with its status. Taken only when asked for; the page refreshes it every 3, 5 (default) or 10 s while the tab is visible;
  • http://<device>:8085/stats.json — the same as JSON, for scripts;
  • In single view the page and the snapshot still show the whole grid (#100): the camera on screen is framed in blue, every other configured slot gets a short stream check (connect, a few seconds of packets, no decoding) about every 45 s while the page is open, and its cell says what was found, e.g. Stream OK · H.265 · 25 fps · 3.1 Mbit/s · checked 12 s ago, or the error. A slot of the camera on screen is never checked (some cameras take one client only), nor a udp:// slot. stats.json has these cells under slots;
  • http://<device>:8085/log — the last 500 lines of the app's log.

Only clients on the local network are answered (loopback, 10/8, 172.16/12, 192.168/16, link-local, IPv6 fc00::/7); others get 403. Logins and passwords are never shown.

Debug page: snapshot with each cell's text
/stats.json /log

Known limitations

Important

Each item has an issue — comments, test results and pull requests are welcome.

  • RTSP multicast needs Android 8.0+ and a server that offers it; OpenIPC cameras do not — #69
  • G.726 audio and MP4A-LATM with in-band configuration (cpresent=1) are not supported. OpenIPC Majestic does not use them, and cameras that offer G.726 can usually send G.711 or AAC instead — #14
  • MJPEG is decoded in software on devices without a usable JPEG decoder (the AG-216 lists one but cannot create it); hardware decoding is used where the device offers video/jpeg — #132
  • The video decoder of the AG-216 can stop responding until the device is restarted: frames arrive, none is decoded; the app says so on the screen and on the debug page, the cause is not known yet — #145
  • Switching between multi-view and the single view can freeze the picture for seconds on some TV boxes (seen on the AG-216) — #76
  • Credentials are sent in clear text over plain RTSP/HTTP (Basic auth unless the camera asks for Digest); RTSPS is not supported and not planned — #16

Known bugs are tracked separately under the bug label.

Branches

Branch Status
v2 Main development branch (default). Modular code in android/. Builds are published as decoder-v2.apk.
basic Legacy single-file version (1.0x), kept for reference and older setups. Builds are published as decoder.apk.

Please base pull requests on v2.

Building

You need JDK 17 and the Android SDK (platform 36). Point the build at the SDK with ANDROID_HOME or android/local.properties.

cd android
./gradlew assembleDebug        # debug build, no signing keys needed
# → app/build/outputs/apk/debug/app-debug.apk

A release build needs a keystore at android/key.jks and these environment variables:

export KEYSTORE_PASS=your_password
export KEY_ALIAS=your_alias
export KEY_PASS=your_key_password
./gradlew assembleRelease      # → app/build/outputs/apk/release/app-release.apk

Debug and release builds are signed with different keys. To switch from one to the other, uninstall the app first; its saved camera URLs are removed with it.

CI: every push is built by GitHub Actions. Pushes to v2 and basic update the latest release and notify the OpenIPC Telegram channel. Other branches only attach the APK as a workflow artifact on the run page.

Architecture

Class Role
Decoder Main activity: UI, lifecycle, settings, single-view video decoding, multi-view grid
RtspClient Single-view stream: RTSP session, NAL assembly, audio, connection quality
RtspSession One RTSP session over TCP, UDP or multicast: handshake, RTP receive, keepalive, watchdog, reconnect with back-off
RtspAuth, Sdp Basic and Digest authentication; parsing of the camera's session description
Transport, UdpReceiver, RtpReorder Transport negotiation; UDP and multicast receive with a sender filter; packet reordering
RtpListener udp:// slots: plain RTP pushed by the camera
HttpMjpeg http:// slots: MJPEG over HTTP (multipart/x-mixed-replace, chunked or not)
NalAssembler Rebuilds H.264/H.265 NAL units from RTP packets, drops units damaged by packet loss
JpegAssembler MJPEG: rebuilds whole JPEG frames from RTP/JPEG (RFC 2435)
JpegCodec, JpegRenderer MJPEG on screen: the device's JPEG decoder where there is one, otherwise BitmapFactory
DecodePlanner Multi-view: which cells decode every frame and which only key frames, within the learnt decoder capacity
MediaCodecManager, DecoderRetry Hardware video decoder lifecycle and recovery after decoder errors
AudioManager, AudioConfig Audio pipeline: G.711/L16 conversion, AAC/Opus decoding, AudioTrack playback
QuadCell One multi-view cell: its own session, decoder and optional audio
MenuManager Popup menu, URL editor, WebUI browser
CredentialStore, KeystoreCipher Encrypted storage of camera URLs (Android Keystore)
FramePool, Frame Buffer pool that reduces garbage-collector load
App, Crash Uncaught-exception handler and crash report screen
DebugServer, NetInfo, Snapshots Debug page on port 8085 (LAN clients only), device addresses, snapshots of the screen

Network, video decoding and audio run on separate threads, so a slow decoder never blocks the socket.

Security notes

  • Credentials are taken from the URL and sent in the Authorization header: Basic first, Digest when the camera asks for it. They are not included in the RTSP request line.
  • RTSP, RTP over UDP and HTTP are not encrypted, and RTSPS is not supported. Credentials that a camera expects inside the URL path or query (for example user=…_password=…) are sent with every request. Use the app only on trusted networks, see #16.
  • Camera URLs, including passwords, are stored encrypted (AES-256-GCM with a key kept in the Android Keystore, Android 6.0+) in the app's private storage. On Android 5.x, or if the Keystore is unavailable, they are stored in plain text. Backup is disabled (allowBackup="false").
  • The debug page on port 8085 is not encrypted and has no password; it answers only local-network clients and never shows logins or passwords.

Device compatibility

Tested the app on your device? Please submit a Device report. It takes a minute and helps other users. Tested a camera? Submit a Camera report: manufacturer, model, RTSP URL (without the password), codecs and what works.

Verified devices

Most devices below were reported with the legacy 1.0x version (basic branch); the App column shows the version that was tested. New reports should include the app version.

Type Device Android Kernel App Notes
Phone Asus ZC553KL 8.1 3.18.71 1.0x
Phone Blackview BV4900Pro 12 4.19.191 1.0x
Phone Oppo 5X ColorOS 15.0 5.15.149 1.0x
Phone Oppo A17 ColorOS 12.1 4.19.191 1.0x
Phone Samsung Galaxy A51 13 4.14.113 1.0x
Phone Samsung Galaxy M21 (SM-M215F/DSN) 12 4.14.113 1.0x
Phone Samsung Galaxy S25 15 6.6.30 1.0x
Phone Samsung A55 5G 14 6.1.93 1.0x
Phone Samsung Galaxy J7 10 3.18.150 1.0x
Phone Samsung S8 9 4.4.153 1.0x
Phone Samsung S23 Ultra 15 5.15.153 1.0x
Phone TECNO KM4 15 5.15.194 v2 2.04 Lab touch device: WebUI with the on-screen keyboard checked (#159, #160); Close under the status bar (#169)
Phone Xiaomi Redmi 7A 10 4.9.261 1.0x
Phone Xiaomi Redmi Note 7 (M1901F7G) 10 4.4.192 1.0x
Phone Xiaomi Redmi Note 13 4G 13 5.15.94 1.0x
Tablet Lenovo TB-8504X 8.1.0 3.18.71 v2 2.00 H.265 and multi-view work; WebUI input fixed in 2.02 (#159)
Tablet Lenovo TB-X304L 8.1.0 3.18.71 1.0x Picture cropped in 1.0x, fixed in v2 (#17)
Tablet Lenovo TB-X606F 10 — 1.0x Picture cropped in 1.0x, fixed in v2 (#17)
Tablet Lenovo Tab P11 11 4.19.157-perf+ 1.0x Picture cropped in 1.0x, fixed in v2 (#17)
Tablet Samsung Galaxy Tab A 7.1.1 3.10.49 1.0x
Tablet Samsung Galaxy Tab A 11 4.9.227 1.0x
Tablet Samsung Tab A7 (SM-T505) 12 4.19.157-perf- 1.0x
Set-top box A95X F3 AIR 9 (SlimBOXtv AOSP 9.14, no GAPPS) 4.9.113 1.0x
Set-top box IE X3 Air 9 (SlimBOXtv AOSP 9.14, no GAPPS) 4.9.113 1.0x
Set-top box AG-216 10 4.9.170 v2 2.00 v2 test device; decoder budget measured at ≈ 1080p60 in total (two 1080p30 at full rate)
Set-top box SberBox SBDV-00001 custom 4.9.228 1.0x
Set-top box X96Q (Allwinner H313) 10 4.9.170 1.0x
Projector SIBIRLINK HY300 / HY320 11 4.19.323 v2 2.02 1080p H.264 / H.265 work; 2304×1296 and 2560×1440 do not start (#165, #166)
TV Haier Candy Android TV 2K 11 4.9.243+ 1.0x No icon in the device's app list
TV Yandex TV Novex NVX-55U169TSY 7.1.1 4.4.3 1.0x
Car Media Center JCAC10003 (ac8227/ac8229) 12 3.18.79+ 1.0x
Car Mirror Z55 8.1.0 4.4.83 1.0x

Not working

Type Device Android Kernel App Notes
Set-top box Unnamed TV box 5.1.1 3.14.29 1.0x No video

Version history

Changes are listed on the releases page and in the commit history. The history of 1.0x versions is in the basic branch README.

License

The Prosperity Public License 3.0.0: free for noncommercial use, with a 30-day trial for commercial use. See LICENSE.md for the full terms.

Support

Telegram

About

Miniature and universal H.264 and H.265 decoder for Android devices

Topics

Resources

Code of conduct

Stars

5 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Contributors

Languages