diff --git a/README.md b/README.md index b4dba6ac..3de05614 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,10 @@ A small Android app for watching RTSP streams from IP cameras, with hardware H.2 Ready-to-install builds are in the [**latest** release](https://github.com/OpenIPC/decoder/releases/tag/latest): -| File | What it is | -|------------------|-------------------------------------------------------------| -| `decoder-v2.apk` | **Current version**, recommended | -| `decoder.apk` | Legacy version from the `basic` branch | +| File | What it is | +|------------------|----------------------------------------| +| `decoder-v2.apk` | **Current version**, recommended | +| `decoder.apk` | Legacy version from the `basic` branch | A beta channel is available on [Google Play](https://play.google.com/apps/internaltest/4701231141824898735). To join it, send your e-mail through the [OpenIPC Telegram channels](https://openipc.org/our-channels). @@ -32,14 +32,14 @@ A beta channel is available on [Google Play](https://play.google.com/apps/intern - **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 at once in a 2×2 or 3×3 grid - **Controls:** pinch-to-zoom, pan, double-tap to reset, Android TV remote (Leanback launcher) -- **Network:** Basic and Digest authentication, RTSP keepalive, automatic reconnection with exponential back-off, connection-quality indicator +- **Network:** RTSP over TCP or UDP (Auto falls back to TCP), 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 over TCP (see [Known limitations](#known-limitations)) +- An IP camera with RTSP, or one that pushes RTP over UDP (OpenIPC Majestic `outgoing`), see [Transports](#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](https://github.com/OpenIPC/decoder/issues/15) ## Quick start @@ -50,6 +50,7 @@ A beta channel is available on [Google Play](https://play.google.com/apps/intern ``` rtsp://username:password@ip_address:554/stream=0 ``` + Under the URL you can choose the [transport](#transports); TCP is the default. 4. Press **Save**. The app connects straight away and reconnects automatically if the stream drops. ## Controls @@ -60,8 +61,8 @@ A beta channel is available on [Google Play](https://play.google.com/apps/intern |--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Q** | Turn multi-view (Quad) on or off | | **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. | -| **U** | Show the reserved UDP RTP cell full-screen ([#68](https://github.com/OpenIPC/decoder/issues/68)) | -| **Settings** | Edit the RTSP URL of the active slot (single-camera mode only) | +| **U** | Show the reserved UDP cell of the 3×3 grid full-screen | +| **Settings** | Edit the URL and the transport of the active slot (single-camera mode only) | | **WebUI** | Open the camera's web interface; credentials come from the RTSP URL (single-camera mode only) | | **Exit** | Close the app. The small text next to it is the app version and build hash. | @@ -77,18 +78,58 @@ A beta channel is available on [Google Play](https://play.google.com/apps/intern ### Opening a camera from another app -The app accepts plain-text shares (`ACTION_SEND`, `text/plain`). Sharing an `rtsp://` URL from another app, for example OpenIPC Network, puts it into the active slot. +The app accepts plain-text shares (`ACTION_SEND`, `text/plain`). Sharing an `rtsp://` or `udp://` 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: + +```sh +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 is reserved for a camera that pushes RTP over UDP (planned, [#68](https://github.com/OpenIPC/decoder/issues/68)); for now it shows the address and port to send to. **U** in the menu opens it full-screen. Each cell has its own connection and decoder, so the device's video decoder limits how many high-resolution streams play smoothly: with five or more cameras, their substreams are a good choice. 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. +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 is reserved for a camera that pushes RTP over UDP; for now it only shows the address and port to send to, and **U** in the menu opens it full-screen. To watch such a camera today, put a [`udp://` URL](#udp-slots-majestic-outgoing) into any slot. Each cell has its own connection and decoder, so the device's video decoder limits how many high-resolution streams play smoothly: with five or more cameras, their substreams are a good choice. 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** (default) | 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** | 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 | + +### 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): + +```yaml +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. ## Known limitations > [!IMPORTANT] > Each item has an issue — comments, test results and pull requests are welcome. > -> - **RTSP over TCP only.** No UDP or multicast yet — [#7](https://github.com/OpenIPC/decoder/issues/7) +> - **RTSP multicast needs Android 8.0+** and a server that offers it; OpenIPC cameras do not — [#69](https://github.com/OpenIPC/decoder/issues/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](https://github.com/OpenIPC/decoder/issues/14) > - **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](https://github.com/OpenIPC/decoder/issues/16) > @@ -128,27 +169,29 @@ Debug and release builds are signed with different keys. To switch from one to t ## 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-over-TCP session: handshake, RTP receive loop, keepalive, watchdog, reconnect with back-off | -| `RtspAuth`, `Sdp` | Basic and Digest authentication; parsing of the camera's session description | -| `NalAssembler` | Rebuilds H.264/H.265 NAL units from RTP packets, drops units damaged by packet loss | -| `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 | +| 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 | +| `NalAssembler` | Rebuilds H.264/H.265 NAL units from RTP packets, drops units damaged by packet loss | +| `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 | 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 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](https://github.com/OpenIPC/decoder/issues/16). +- 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](https://github.com/OpenIPC/decoder/issues/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"`). ## Device compatibility diff --git a/README.ru.md b/README.ru.md index 25d7b504..44c51aef 100644 --- a/README.ru.md +++ b/README.ru.md @@ -19,10 +19,10 @@ Готовые сборки лежат в [релизе **latest**](https://github.com/OpenIPC/decoder/releases/tag/latest): -| Файл | Что это | -|------------------|----------------------------------------------------| -| `decoder-v2.apk` | **Текущая версия**, рекомендуется | -| `decoder.apk` | Старая версия из ветки `basic` | +| Файл | Что это | +|------------------|-----------------------------------| +| `decoder-v2.apk` | **Текущая версия**, рекомендуется | +| `decoder.apk` | Старая версия из ветки `basic` | Есть бета-канал в [Google Play](https://play.google.com/apps/internaltest/4701231141824898735). Чтобы попасть в него, пришлите свой e-mail через [Telegram-каналы OpenIPC](https://openipc.org/our-channels). @@ -32,14 +32,14 @@ - **Звук:** AAC (RFC 3640 `mpeg4-generic`, ADTS, MP4A-LATM), Opus, G.711 μ-law/A-law, L16 PCM (моно и стерео); в мультивиде — звук выбранной ячейки - **Режимы:** одна камера или мультивид (Quad) — до 8 камер на одном экране, сеткой 2×2 или 3×3 - **Управление:** масштаб двумя пальцами, перемещение, сброс двойным касанием, пульт Android TV (Leanback) -- **Сеть:** авторизация Basic и Digest, RTSP keepalive, автоматическое переподключение с растущей задержкой, индикатор качества связи +- **Сеть:** RTSP поверх TCP или UDP (Auto переходит на TCP), RTSP multicast, приём RTP, который камера сама шлёт по UDP (unicast или multicast), авторизация Basic и Digest, RTSP keepalive, автоматическое переподключение с растущей задержкой, индикатор качества связи - **WebUI:** встроенный браузер для веб-интерфейса камеры - **Прочее:** 8 слотов для камер, адреса хранятся в зашифрованном виде, скриншот долгим нажатием, приём ссылок из других приложений ## Требования - Android 5.0 (API 21) или новее -- IP-камера с RTSP поверх TCP (см. [Известные ограничения](#известные-ограничения)) +- IP-камера с RTSP или камера, которая сама отправляет RTP по UDP (OpenIPC Majestic `outgoing`), см. [Транспорты](#транспорты) - Сервисы Google (GAPPS) **не** нужны: приложение работает и на AOSP-сборках без них. WebUI использует системный WebView, поэтому ему нужен рабочий пакет WebView (на некоторых устройствах это *Android System WebView* от Google); его отключение затрагивает только WebUI — [#15](https://github.com/OpenIPC/decoder/issues/15) ## Быстрый старт @@ -50,6 +50,7 @@ ``` rtsp://username:password@ip_address:554/stream=0 ``` + Под адресом можно выбрать [транспорт](#транспорты); по умолчанию TCP. 4. Нажмите **Save**. Приложение сразу подключится и само переподключится, если поток прервётся. ## Управление @@ -60,8 +61,8 @@ |--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Q** | Включить или выключить мультивид (Quad) | | **1**–**8** | Переключиться на слот камеры. Цвет номера активного слота показывает качество связи: зелёный — джиттер меньше 100 мс, жёлтый — 100–300 мс, красный — больше 300 мс. Серый номер — пустой слот. | -| **U** | Показать на весь экран зарезервированную ячейку UDP RTP ([#68](https://github.com/OpenIPC/decoder/issues/68)) | -| **Settings** | Изменить RTSP-адрес активного слота (только в режиме одной камеры) | +| **U** | Показать на весь экран зарезервированную ячейку UDP сетки 3×3 | +| **Settings** | Изменить адрес и транспорт активного слота (только в режиме одной камеры) | | **WebUI** | Открыть веб-интерфейс камеры; логин и пароль берутся из RTSP-адреса (только в режиме одной камеры) | | **Exit** | Закрыть приложение. Мелкий текст рядом — версия приложения и хеш сборки. | @@ -77,18 +78,58 @@ ### Открытие камеры из другого приложения -Приложение принимает текст через «Поделиться» (`ACTION_SEND`, `text/plain`). Если поделиться ссылкой `rtsp://` из другого приложения, например из OpenIPC Network, она запишется в активный слот. +Приложение принимает текст через «Поделиться» (`ACTION_SEND`, `text/plain`). Если поделиться ссылкой `rtsp://` или `udp://` из другого приложения, например из OpenIPC Network, она запишется в активный слот. Необязательный параметр `transport` (`tcp`, `udp`, `auto`, `multicast`) задаёт транспорт слота: + +```sh +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 +``` ### Мультивид (Quad) -Показывает настроенные слоты сеткой 2×2, а если занят хотя бы один из слотов 5–8 — сеткой 3×3. Девятая ячейка сетки 3×3 зарезервирована под камеру, которая сама отправляет RTP по UDP (в планах, [#68](https://github.com/OpenIPC/decoder/issues/68)); пока в ней показаны адрес и порт, куда слать поток. Кнопка **U** в меню открывает её на весь экран. У каждой ячейки своё подключение и свой декодер, поэтому число плавно идущих потоков высокого разрешения ограничено видеодекодером устройства: для пяти и более камер лучше брать их дополнительные потоки (substream). Некоторые камеры допускают только одно RTSP-подключение одновременно. Долгое нажатие на ячейку включает её звук (повторное — выключает); у выбранной ячейки виден значок AUDIO, выбор запоминается. Коснитесь любой ячейки, чтобы открыть меню. +Показывает настроенные слоты сеткой 2×2, а если занят хотя бы один из слотов 5–8 — сеткой 3×3. Девятая ячейка сетки 3×3 зарезервирована под камеру, которая сама отправляет RTP по UDP; пока в ней показаны только адрес и порт, куда слать поток, а кнопка **U** в меню открывает её на весь экран. Чтобы смотреть такую камеру уже сейчас, укажите в любом слоте [адрес `udp://`](#слоты-udp-majestic-outgoing). У каждой ячейки своё подключение и свой декодер, поэтому число плавно идущих потоков высокого разрешения ограничено видеодекодером устройства: для пяти и более камер лучше брать их дополнительные потоки (substream). Некоторые камеры допускают только одно RTSP-подключение одновременно. Долгое нажатие на ячейку включает её звук (повторное — выключает); у выбранной ячейки виден значок AUDIO, выбор запоминается. Коснитесь любой ячейки, чтобы открыть меню. + +## Транспорты + +У каждого слота свой транспорт: он выбирается под адресом в **Settings** (или параметром `transport` в присланной ссылке). + +| Транспорт | Как приходит видео | Примечание | +|------------------------|-------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| **TCP** (по умолчанию) | RTP внутри RTSP-соединения | Работает везде, в том числе через NAT и межсетевые экраны | +| **UDP** | RTP по UDP, пара портов на дорожку | Меньше задержка в локальной сети. Потерянные пакеты пропускаются, а не запрашиваются заново. На TCP не переходит | +| **Auto** | UDP, а если UDP не работает — TCP | Переходит на TCP, если камера отказала в UDP или видео не пришло за 3 с, и остаётся на TCP, пока слот не изменят | +| **Multicast** | RTSP multicast (Android 8.0+), при отказе — TCP | Для серверов с RTSP multicast. OpenIPC Majestic и камеры XM его не поддерживают: отвечают 400 или 451, и слот играет по TCP в том же соединении | + +### Слоты UDP (Majestic outgoing) + +Слот может принимать и RTP, который камера отправляет сама, без RTSP. Для этого вместо `rtsp://` укажите адрес `udp://`: + +| Адрес | Значение | +|-------------------------------------|-----------------------------------------------------------------------------| +| `udp://@:5600` | Слушать порт 5600 | +| `udp://@239.1.2.3:5600` | Войти в multicast-группу 239.1.2.3 на порту 5600 | +| `udp://192.168.1.10@239.1.2.3:5600` | То же, но принимать пакеты только от этого отправителя | +| `udp://@:5600?codec=h265&pt=97` | Задать кодек или тип нагрузки видео, если значения по умолчанию не подходят | + +Кодек определяется по типу нагрузки RTP (96 → H.264, 97 → H.265), если не задан `codec=`; смену кодека на камере приложение подхватывает на лету. Только видео. Два слота не могут слушать один и тот же порт. + +На камере добавьте устройство в адресаты `outgoing` в `/etc/majestic.yaml` (затем перезапустите Majestic): + +```yaml +outgoing: + servers: + - udp://192.168.1.20:5600 +``` + +`/api/v1/outgoing.json` на камере показывает, идёт ли отправка. Majestic умеет слать и в multicast-группу (`udp://239.1.2.3:5600`), тогда одну камеру могут смотреть несколько устройств. В Wi‑Fi приложение держит multicast lock, пока состоит в группе. ## Известные ограничения > [!IMPORTANT] > На каждый пункт заведён issue — комментарии, результаты проверок и pull request приветствуются. > -> - **Только RTSP поверх TCP.** UDP и multicast пока не поддерживаются — [#7](https://github.com/OpenIPC/decoder/issues/7) +> - **RTSP multicast требует Android 8.0+** и сервера, который его поддерживает; камеры OpenIPC его не поддерживают — [#69](https://github.com/OpenIPC/decoder/issues/69) > - **Не поддерживаются звук G.726 и MP4A-LATM с конфигурацией внутри потока (`cpresent=1`).** OpenIPC Majestic их не использует, а камеры с G.726 обычно умеют отдавать G.711 или AAC — [#14](https://github.com/OpenIPC/decoder/issues/14) > - **Логин и пароль передаются в открытом виде** по RTSP/HTTP без шифрования (Basic, если камера не запросила Digest); RTSPS не поддерживается и не планируется — [#16](https://github.com/OpenIPC/decoder/issues/16) > @@ -128,27 +169,29 @@ export KEY_PASS=your_key_password ## Архитектура -| Класс | Назначение | -|-------------------------------------|-------------------------------------------------------------------------------------------------------------------------| -| `Decoder` | Главная activity: интерфейс, жизненный цикл, настройки, декодирование видео одной камеры, сетка мультивида | -| `RtspClient` | Поток одной камеры: RTSP-сессия, сборка NAL, звук, качество связи | -| `RtspSession` | Одна RTSP-сессия поверх TCP: рукопожатие, приём RTP, keepalive, сторожевой таймер, переподключение с растущей задержкой | -| `RtspAuth`, `Sdp` | Авторизация Basic и Digest; разбор описания сессии камеры | -| `NalAssembler` | Сборка NAL-блоков H.264/H.265 из RTP-пакетов, отбрасывание блоков, повреждённых потерями | -| `MediaCodecManager`, `DecoderRetry` | Жизненный цикл аппаратного видеодекодера и восстановление после ошибок | -| `AudioManager`, `AudioConfig` | Звук: преобразование G.711/L16, декодирование AAC/Opus, вывод через AudioTrack | -| `QuadCell` | Одна ячейка мультивида: своя сессия, декодер и звук по выбору | -| `MenuManager` | Всплывающее меню, редактор адреса, браузер WebUI | -| `CredentialStore`, `KeystoreCipher` | Зашифрованное хранение адресов камер (Android Keystore) | -| `FramePool`, `Frame` | Пул буферов, снижает нагрузку на сборщик мусора | -| `App`, `Crash` | Обработчик необработанных исключений и экран отчёта о сбое | +| Класс | Назначение | +|------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------| +| `Decoder` | Главная activity: интерфейс, жизненный цикл, настройки, декодирование видео одной камеры, сетка мультивида | +| `RtspClient` | Поток одной камеры: RTSP-сессия, сборка NAL, звук, качество связи | +| `RtspSession` | Одна RTSP-сессия поверх TCP, UDP или multicast: рукопожатие, приём RTP, keepalive, сторожевой таймер, переподключение с растущей задержкой | +| `RtspAuth`, `Sdp` | Авторизация Basic и Digest; разбор описания сессии камеры | +| `Transport`, `UdpReceiver`, `RtpReorder` | Согласование транспорта; приём по UDP и multicast с фильтром отправителя; восстановление порядка пакетов | +| `RtpListener` | Слоты `udp://`: RTP, который камера отправляет сама | +| `NalAssembler` | Сборка NAL-блоков H.264/H.265 из RTP-пакетов, отбрасывание блоков, повреждённых потерями | +| `MediaCodecManager`, `DecoderRetry` | Жизненный цикл аппаратного видеодекодера и восстановление после ошибок | +| `AudioManager`, `AudioConfig` | Звук: преобразование G.711/L16, декодирование AAC/Opus, вывод через AudioTrack | +| `QuadCell` | Одна ячейка мультивида: своя сессия, декодер и звук по выбору | +| `MenuManager` | Всплывающее меню, редактор адреса, браузер WebUI | +| `CredentialStore`, `KeystoreCipher` | Зашифрованное хранение адресов камер (Android Keystore) | +| `FramePool`, `Frame` | Пул буферов, снижает нагрузку на сборщик мусора | +| `App`, `Crash` | Обработчик необработанных исключений и экран отчёта о сбое | Сеть, декодирование видео и звук работают в отдельных потоках, поэтому медленный декодер не блокирует сокет. ## Безопасность - Логин и пароль берутся из адреса и передаются в заголовке `Authorization`: сначала Basic, Digest — если камера его запросит. В строку RTSP-запроса они не попадают. -- RTSP и HTTP без шифрования, RTSPS не поддерживается. Если камера ждёт логин и пароль прямо в пути или параметрах адреса (например, `user=…_password=…`), они уходят с каждым запросом. Используйте приложение только в доверенных сетях, см. [#16](https://github.com/OpenIPC/decoder/issues/16). +- RTSP, RTP по UDP и HTTP без шифрования, RTSPS не поддерживается. Если камера ждёт логин и пароль прямо в пути или параметрах адреса (например, `user=…_password=…`), они уходят с каждым запросом. Используйте приложение только в доверенных сетях, см. [#16](https://github.com/OpenIPC/decoder/issues/16). - Адреса камер вместе с паролями хранятся в закрытом хранилище приложения в зашифрованном виде (AES-256-GCM, ключ в Android Keystore, Android 6.0+). На Android 5.x или если Keystore недоступен — в открытом виде. Резервное копирование отключено (`allowBackup="false"`). ## Совместимость с устройствами diff --git a/README.zh-CN.md b/README.zh-CN.md index f6a0d76f..77382d82 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -19,10 +19,10 @@ 可直接安装的版本位于 [**latest** 发布页](https://github.com/OpenIPC/decoder/releases/tag/latest): -| 文件 | 说明 | -|------------------|-----------------------------------------| -| `decoder-v2.apk` | **当前版本**,推荐使用 | -| `decoder.apk` | `basic` 分支的旧版本 | +| 文件 | 说明 | +|------------------|------------------------| +| `decoder-v2.apk` | **当前版本**,推荐使用 | +| `decoder.apk` | `basic` 分支的旧版本 | [Google Play](https://play.google.com/apps/internaltest/4701231141824898735) 上提供测试渠道。如需加入,请通过 [OpenIPC Telegram 频道](https://openipc.org/our-channels)发送您的电子邮箱。 @@ -32,14 +32,14 @@ - **音频:** AAC(RFC 3640 `mpeg4-generic`、ADTS、MP4A-LATM)、Opus、G.711 μ-law/A-law、L16 PCM(单声道/立体声);多画面模式下播放所选画面的声音 - **显示:** 单路画面,或多画面模式(Quad),以 2×2 或 3×3 网格最多同时显示 8 路摄像机 - **操作:** 双指缩放、拖动平移、双击复位,支持 Android TV 遥控器(Leanback 启动器) -- **网络:** 支持 Basic 和 Digest 认证、RTSP 保活,断线后自动重连(指数退避),连接质量指示 +- **网络:** RTSP over TCP 或 UDP(Auto 模式可回退到 TCP)、RTSP 组播、接收摄像机通过 UDP 主动推送的 RTP(单播或组播),支持 Basic 和 Digest 认证、RTSP 保活,断线后自动重连(指数退避),连接质量指示 - **WebUI:** 内置浏览器,可打开摄像机的网页管理界面 - **其他:** 8 个摄像机槽位,地址加密保存,长按截图,可接收其他应用分享的地址 ## 系统要求 - Android 5.0(API 21)或更高版本 -- 支持 RTSP over TCP 的 IP 摄像机(参见[已知限制](#已知限制)) +- 支持 RTSP 的 IP 摄像机,或能通过 UDP 主动推送 RTP 的摄像机(OpenIPC Majestic `outgoing`),参见[传输方式](#传输方式) - **不**需要 Google 服务(GAPPS):应用可在没有 GAPPS 的 AOSP 系统上运行。WebUI 使用系统 WebView,因此需要可用的 WebView 软件包(部分设备上为 Google 的 *Android System WebView*);禁用它只影响 WebUI — [#15](https://github.com/OpenIPC/decoder/issues/15) ## 快速上手 @@ -50,6 +50,7 @@ ``` rtsp://username:password@ip_address:554/stream=0 ``` + 地址下方可选择[传输方式](#传输方式),默认为 TCP。 4. 点击 **Save**。应用会立即连接;视频流中断时会自动重连。 ## 操作说明 @@ -60,8 +61,8 @@ |--------------|-----------------------------------------------------------------------------------------------------------------------------------------------| | **Q** | 开启或关闭多画面模式(Quad) | | **1**–**8** | 切换摄像机槽位。当前槽位编号的颜色表示连接质量:绿色为抖动低于 100 毫秒,黄色为 100–300 毫秒,红色为高于 300 毫秒。灰色编号表示该槽位未配置。 | -| **U** | 全屏显示预留的 UDP RTP 画面([#68](https://github.com/OpenIPC/decoder/issues/68)) | -| **Settings** | 编辑当前槽位的 RTSP 地址(仅单路模式) | +| **U** | 全屏显示 3×3 网格中预留的 UDP 画面 | +| **Settings** | 编辑当前槽位的地址和传输方式(仅单路模式) | | **WebUI** | 打开摄像机网页界面,用户名和密码取自 RTSP 地址(仅单路模式) | | **Exit** | 退出应用。旁边的小字是应用版本号和构建哈希。 | @@ -77,18 +78,58 @@ ### 从其他应用打开摄像机 -应用可接收纯文本分享(`ACTION_SEND`,`text/plain`)。从其他应用(例如 OpenIPC Network)分享 `rtsp://` 地址后,该地址会写入当前槽位。 +应用可接收纯文本分享(`ACTION_SEND`,`text/plain`)。从其他应用(例如 OpenIPC Network)分享 `rtsp://` 或 `udp://` 地址后,该地址会写入当前槽位。可选参数 `transport`(`tcp`、`udp`、`auto`、`multicast`)用于设置该槽位的传输方式: + +```sh +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 +``` ### 多画面模式(Quad) -以 2×2 网格显示已配置的槽位;只要使用了第 5–8 个槽位中的任意一个,就改为 3×3 网格。3×3 网格的第九格预留给通过 UDP 主动推送 RTP 的摄像机(计划中,[#68](https://github.com/OpenIPC/decoder/issues/68));目前其中显示推流目标地址和端口。菜单中的 **U** 可将其全屏显示。每个画面都有独立的连接和解码器,因此能流畅播放多少路高分辨率码流取决于设备的视频解码器:五路及以上时建议使用摄像机的子码流。部分摄像机同一时间只允许一个 RTSP 客户端。长按某个画面即可播放其声音(再次长按则静音);被选中的画面显示 AUDIO 标记,选择会被记住。点击任意画面即可打开菜单。 +以 2×2 网格显示已配置的槽位;只要使用了第 5–8 个槽位中的任意一个,就改为 3×3 网格。3×3 网格的第九格预留给通过 UDP 主动推送 RTP 的摄像机;目前其中仅显示推流目标地址和端口,菜单中的 **U** 可将其全屏显示。如需立即观看此类摄像机,请在任意槽位中填写 [`udp://` 地址](#udp-槽位-majestic-outgoing)。每个画面都有独立的连接和解码器,因此能流畅播放多少路高分辨率码流取决于设备的视频解码器:五路及以上时建议使用摄像机的子码流。部分摄像机同一时间只允许一个 RTSP 客户端。长按某个画面即可播放其声音(再次长按则静音);被选中的画面显示 AUDIO 标记,选择会被记住。点击任意画面即可打开菜单。 + +## 传输方式 + +每个槽位可单独设置传输方式:在 **Settings** 中地址下方选择(或通过分享链接的 `transport` 参数设置)。 + +| 传输方式 | 视频如何到达 | 说明 | +|-----------------|---------------------------------------------|-------------------------------------------------------------------------------------------------------------------------| +| **TCP**(默认) | RTP 封装在 RTSP 连接中 | 适用于所有环境,也可穿过 NAT 和防火墙 | +| **UDP** | 通过 UDP 传输 RTP,每路一对端口 | 局域网内延迟更低。丢失的包直接跳过,不会重传。不会回退到 TCP | +| **Auto** | 优先 UDP,UDP 不可用时改用 TCP | 摄像机拒绝 UDP 或 3 秒内未收到视频时回退到 TCP,并保持 TCP 直到修改该槽位 | +| **Multicast** | RTSP 组播(Android 8.0+),被拒绝时改用 TCP | 适用于提供 RTSP 组播的服务器。OpenIPC Majestic 和 XM 摄像机不支持:它们返回 400 或 451,槽位会在同一连接中改用 TCP 播放 | + +### UDP 槽位 (Majestic outgoing) + +槽位也可以接收摄像机不经 RTSP 主动推送的 RTP。为此请填写 `udp://` 地址代替 `rtsp://`: + +| 地址 | 含义 | +|-------------------------------------|----------------------------------------| +| `udp://@:5600` | 监听 5600 端口 | +| `udp://@239.1.2.3:5600` | 加入 239.1.2.3 组播组,端口 5600 | +| `udp://192.168.1.10@239.1.2.3:5600` | 同上,但只接收该发送端的数据包 | +| `udp://@:5600?codec=h265&pt=97` | 默认值不适用时,指定编码或视频负载类型 | + +除非指定 `codec=`,编码根据 RTP 负载类型判断(96 → H.264,97 → H.265);播放过程中摄像机切换编码也会自动跟随。仅支持视频。两个槽位不能监听同一端口。 + +在摄像机上,将本设备添加为 `outgoing` 的目标(编辑 `/etc/majestic.yaml` 后重启 Majestic): + +```yaml +outgoing: + servers: + - udp://192.168.1.20:5600 +``` + +摄像机上的 `/api/v1/outgoing.json` 可查看推流是否正常。Majestic 也可以推送到组播组(`udp://239.1.2.3:5600`),这样多台设备可以同时观看同一台摄像机。在 Wi‑Fi 下,应用加入组播组期间会持有 multicast lock。 ## 已知限制 > [!IMPORTANT] > 每一项都有对应的 issue。欢迎留言、提交测试结果和 pull request。 > -> - **仅支持 RTSP over TCP**,暂不支持 UDP 和组播 — [#7](https://github.com/OpenIPC/decoder/issues/7) +> - **RTSP 组播需要 Android 8.0+**,且服务器须支持;OpenIPC 摄像机不支持 — [#69](https://github.com/OpenIPC/decoder/issues/69) > - **不支持 G.726 音频,也不支持流内配置的 MP4A-LATM(`cpresent=1`)。** OpenIPC Majestic 不使用这些格式,支持 G.726 的摄像机通常也能输出 G.711 或 AAC — [#14](https://github.com/OpenIPC/decoder/issues/14) > - **用户名和密码以明文传输**(未加密的 RTSP/HTTP;摄像机未要求 Digest 时发送 Basic),不支持 RTSPS,也不计划支持 — [#16](https://github.com/OpenIPC/decoder/issues/16) > @@ -128,27 +169,29 @@ export KEY_PASS=your_key_password ## 架构 -| 类 | 作用 | -|-------------------------------------|-----------------------------------------------------------------| -| `Decoder` | 主 Activity:界面、生命周期、设置、单路视频解码、多画面网格 | -| `RtspClient` | 单路数据流:RTSP 会话、NAL 重组、音频、连接质量 | -| `RtspSession` | 单个 RTSP over TCP 会话:握手、RTP 接收、保活、看门狗、退避重连 | -| `RtspAuth`、`Sdp` | Basic 与 Digest 认证;解析摄像机的会话描述 | -| `NalAssembler` | 将 RTP 包重组为 H.264/H.265 NAL 单元,丢弃因丢包而损坏的单元 | -| `MediaCodecManager`、`DecoderRetry` | 硬件视频解码器的生命周期管理与出错后的恢复 | -| `AudioManager`、`AudioConfig` | 音频处理:G.711/L16 转换、AAC/Opus 解码、通过 AudioTrack 播放 | -| `QuadCell` | 多画面中的单个画面:独立的会话、解码器和可选音频 | -| `MenuManager` | 弹出菜单、地址编辑器、WebUI 浏览器 | -| `CredentialStore`、`KeystoreCipher` | 摄像机地址加密存储(Android Keystore) | -| `FramePool`、`Frame` | 缓冲区池,减轻垃圾回收压力 | -| `App`、`Crash` | 未捕获异常处理与崩溃报告界面 | +| 类 | 作用 | +|------------------------------------------|---------------------------------------------------------------------------| +| `Decoder` | 主 Activity:界面、生命周期、设置、单路视频解码、多画面网格 | +| `RtspClient` | 单路数据流:RTSP 会话、NAL 重组、音频、连接质量 | +| `RtspSession` | 单个 RTSP 会话(TCP、UDP 或组播):握手、RTP 接收、保活、看门狗、退避重连 | +| `RtspAuth`、`Sdp` | Basic 与 Digest 认证;解析摄像机的会话描述 | +| `Transport`、`UdpReceiver`、`RtpReorder` | 传输方式协商;带发送端过滤的 UDP 与组播接收;数据包重排序 | +| `RtpListener` | `udp://` 槽位:接收摄像机主动推送的 RTP | +| `NalAssembler` | 将 RTP 包重组为 H.264/H.265 NAL 单元,丢弃因丢包而损坏的单元 | +| `MediaCodecManager`、`DecoderRetry` | 硬件视频解码器的生命周期管理与出错后的恢复 | +| `AudioManager`、`AudioConfig` | 音频处理:G.711/L16 转换、AAC/Opus 解码、通过 AudioTrack 播放 | +| `QuadCell` | 多画面中的单个画面:独立的会话、解码器和可选音频 | +| `MenuManager` | 弹出菜单、地址编辑器、WebUI 浏览器 | +| `CredentialStore`、`KeystoreCipher` | 摄像机地址加密存储(Android Keystore) | +| `FramePool`、`Frame` | 缓冲区池,减轻垃圾回收压力 | +| `App`、`Crash` | 未捕获异常处理与崩溃报告界面 | 网络、视频解码和音频分别运行在独立线程中,解码器较慢时也不会阻塞网络读取。 ## 安全说明 - 用户名和密码从地址中提取,通过 `Authorization` 头发送:默认使用 Basic,摄像机要求时改用 Digest。不会出现在 RTSP 请求行中。 -- RTSP 和 HTTP 均未加密,不支持 RTSPS。若摄像机要求将用户名和密码写在地址路径或参数中(例如 `user=…_password=…`),它们会随每个请求发送。请仅在可信网络中使用,参见 [#16](https://github.com/OpenIPC/decoder/issues/16)。 +- RTSP、UDP 上的 RTP 和 HTTP 均未加密,不支持 RTSPS。若摄像机要求将用户名和密码写在地址路径或参数中(例如 `user=…_password=…`),它们会随每个请求发送。请仅在可信网络中使用,参见 [#16](https://github.com/OpenIPC/decoder/issues/16)。 - 摄像机地址(含密码)加密保存在应用私有存储中(AES-256-GCM,密钥保存在 Android Keystore,Android 6.0 及以上),在 Android 5.x 或 Keystore 不可用时以明文保存,并已禁用备份(`allowBackup="false"`)。 ## 设备兼容性