Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ 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) |
| **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. |
Expand All @@ -81,7 +82,7 @@ The app accepts plain-text shares (`ACTION_SEND`, `text/plain`). Sharing an `rts

### 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. 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 (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.

## Known limitations

Expand Down
3 changes: 2 additions & 1 deletion README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Q** | Включить или выключить мультивид (Quad) |
| **1**–**8** | Переключиться на слот камеры. Цвет номера активного слота показывает качество связи: зелёный — джиттер меньше 100 мс, жёлтый — 100–300 мс, красный — больше 300 мс. Серый номер — пустой слот. |
| **U** | Показать на весь экран зарезервированную ячейку UDP RTP ([#68](https://github.com/OpenIPC/decoder/issues/68)) |
| **Settings** | Изменить RTSP-адрес активного слота (только в режиме одной камеры) |
| **WebUI** | Открыть веб-интерфейс камеры; логин и пароль берутся из RTSP-адреса (только в режиме одной камеры) |
| **Exit** | Закрыть приложение. Мелкий текст рядом — версия приложения и хеш сборки. |
Expand All @@ -81,7 +82,7 @@

### Мультивид (Quad)

Показывает настроенные слоты сеткой 2×2, а если занят хотя бы один из слотов 5–8 — сеткой 3×3. У каждой ячейки своё подключение и свой декодер, поэтому число плавно идущих потоков высокого разрешения ограничено видеодекодером устройства: для пяти и более камер лучше брать их дополнительные потоки (substream). Некоторые камеры допускают только одно RTSP-подключение одновременно. Долгое нажатие на ячейку включает её звук (повторное — выключает); у выбранной ячейки виден значок AUDIO, выбор запоминается. Коснитесь любой ячейки, чтобы открыть меню.
Показывает настроенные слоты сеткой 2×2, а если занят хотя бы один из слотов 5–8 — сеткой 3×3. Девятая ячейка сетки 3×3 зарезервирована под камеру, которая сама отправляет RTP по UDP (в планах, [#68](https://github.com/OpenIPC/decoder/issues/68)); пока в ней показаны адрес и порт, куда слать поток. Кнопка **U** в меню открывает её на весь экран. У каждой ячейки своё подключение и свой декодер, поэтому число плавно идущих потоков высокого разрешения ограничено видеодекодером устройства: для пяти и более камер лучше брать их дополнительные потоки (substream). Некоторые камеры допускают только одно RTSP-подключение одновременно. Долгое нажатие на ячейку включает её звук (повторное — выключает); у выбранной ячейки виден значок AUDIO, выбор запоминается. Коснитесь любой ячейки, чтобы открыть меню.

## Известные ограничения

Expand Down
3 changes: 2 additions & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
| **Q** | 开启或关闭多画面模式(Quad) |
| **1**–**8** | 切换摄像机槽位。当前槽位编号的颜色表示连接质量:绿色为抖动低于 100 毫秒,黄色为 100–300 毫秒,红色为高于 300 毫秒。灰色编号表示该槽位未配置。 |
| **U** | 全屏显示预留的 UDP RTP 画面([#68](https://github.com/OpenIPC/decoder/issues/68)) |
| **Settings** | 编辑当前槽位的 RTSP 地址(仅单路模式) |
| **WebUI** | 打开摄像机网页界面,用户名和密码取自 RTSP 地址(仅单路模式) |
| **Exit** | 退出应用。旁边的小字是应用版本号和构建哈希。 |
Expand All @@ -81,7 +82,7 @@

### 多画面模式(Quad)

以 2×2 网格显示已配置的槽位;只要使用了第 5–8 个槽位中的任意一个,就改为 3×3 网格。每个画面都有独立的连接和解码器,因此能流畅播放多少路高分辨率码流取决于设备的视频解码器:五路及以上时建议使用摄像机的子码流。部分摄像机同一时间只允许一个 RTSP 客户端。长按某个画面即可播放其声音(再次长按则静音);被选中的画面显示 AUDIO 标记,选择会被记住。点击任意画面即可打开菜单。
以 2×2 网格显示已配置的槽位;只要使用了第 5–8 个槽位中的任意一个,就改为 3×3 网格。3×3 网格的第九格预留给通过 UDP 主动推送 RTP 的摄像机(计划中,[#68](https://github.com/OpenIPC/decoder/issues/68));目前其中显示推流目标地址和端口。菜单中的 **U** 可将其全屏显示。每个画面都有独立的连接和解码器,因此能流畅播放多少路高分辨率码流取决于设备的视频解码器:五路及以上时建议使用摄像机的子码流。部分摄像机同一时间只允许一个 RTSP 客户端。长按某个画面即可播放其声音(再次长按则静音);被选中的画面显示 AUDIO 标记,选择会被记住。点击任意画面即可打开菜单。

## 已知限制

Expand Down
105 changes: 103 additions & 2 deletions android/app/src/main/java/com/openipc/decoder/Decoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,12 @@
import java.io.IOException;
import java.io.OutputStream;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.ArrayBlockingQueue;
Expand Down Expand Up @@ -266,6 +270,12 @@ public void onCodecChanged(boolean h265, AudioConfig audio) {

@Override
public void onSwitchCamera(int slot) {
if (leaveUdpView()) {
// From the UDP view back to a slot: (re)start it even if it was the active one
mActive = slot;
saveSettings();
return;
}
if (slot == mActive && !quadEnabled) return;
if (quadEnabled) {
// Exit quad mode and switch to selected camera
Expand All @@ -280,6 +290,10 @@ public void onSwitchCamera(int slot) {

@Override
public void onToggleQuad() {
if (leaveUdpView()) {
startQuad();
return;
}
boolean newState = !quadEnabled;
if (newState) startQuad(); else {
stopQuad();
Expand All @@ -292,6 +306,46 @@ public void onToggleQuad() {
}
}

@Override
public void onShowUdp() {
if (udpViewShown) return;
if (quadEnabled) {
stopQuad();
} else {
if (rtspClient != null) rtspClient.stop();
stopExecutor();
audioManager.closeAudio();
closeDecoder();
nalQueue.clear();
}
mSurface.setVisibility(View.GONE);
if (udpFullView == null) {
udpFullView = udpHintCell();
udpFullView.setTextSize(20);
((FrameLayout) findViewById(R.id.decoder)).addView(udpFullView,
new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
} else {
udpFullView.setText(udpHintText(localIpv4(), UDP_RTP_PORT));
}
// An unconfigured slot brings its black overlay to the front (clearVideo()):
// the hint must be above it each time it is shown
udpFullView.bringToFront();
udpFullView.setVisibility(View.VISIBLE);
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
udpViewShown = true;
menuManager.setUdpShown(true);
}

/** Hide the UDP view; true if it was shown. The caller starts what comes next. */
private boolean leaveUdpView() {
if (!udpViewShown) return false;
udpViewShown = false;
menuManager.setUdpShown(false);
if (udpFullView != null) udpFullView.setVisibility(View.GONE);
mSurface.setVisibility(View.VISIBLE);
return true;
}

@Override
public void onExit() { finishAndRemoveTask(); }

Expand Down Expand Up @@ -567,8 +621,9 @@ private void buildQuadGrid(int size) {
for (int col = 0; col < size; col++) {
int idx = row * size + col;
if (idx >= CAM_COUNT) {
// The last cell of a 3x3 grid: no slot, stays black
lr.addView(new View(this), new LinearLayout.LayoutParams(
// The ninth cell of a 3x3 grid is reserved for a pushed UDP RTP stream
// (#68); until the receiver exists it explains what will appear there
lr.addView(udpHintCell(), new LinearLayout.LayoutParams(
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
0, LinearLayout.LayoutParams.MATCH_PARENT, 1));
continue;
}
Expand Down Expand Up @@ -605,6 +660,49 @@ private void buildQuadGrid(int size) {
}
}

/** Port of the reserved UDP RTP cell (#68); 5600 is the usual default for pushed RTP. */
static final int UDP_RTP_PORT = 5600;
/** The UDP cell full-screen (menu button U); there is no receiver yet (#68). */
private TextView udpFullView;
private boolean udpViewShown;

private TextView udpHintCell() {
TextView hint = new TextView(this);
hint.setText(udpHintText(localIpv4(), UDP_RTP_PORT));
hint.setTextColor(Color.GRAY);
hint.setTextSize(13);
hint.setGravity(Gravity.CENTER);
hint.setPadding(dp(12), dp(12), dp(12), dp(12));
hint.setBackgroundColor(Color.BLACK);
// Like the camera cells and the video view: a tap opens the menu
hint.setOnClickListener(v -> menuManager.showMenu(findViewById(R.id.decoder),
quadEnabled, mActive, mVersion, BuildConfig.GIT_HASH, mHosts));
return hint;
}

static String udpHintText(String ip, int port) {
String target = (ip != null ? ip : "this device") + ":" + port;
return "UDP RTP \u00b7 port " + port + "\nWaiting for a stream\u2026\n\n"
+ "Send the camera's RTP/UDP output to\n" + target;
}

/** First site-local IPv4 address of this device, or null. */
static String localIpv4() {
try {
for (NetworkInterface nif : Collections.list(NetworkInterface.getNetworkInterfaces())) {
if (!nif.isUp() || nif.isLoopback()) continue;
for (InetAddress a : Collections.list(nif.getInetAddresses())) {
if (a instanceof Inet4Address && a.isSiteLocalAddress()) {
return a.getHostAddress();
}
}
}
} catch (Exception e) {
Log.w(TAG, "No local address: " + e.getMessage());
}
return null;
}

private void createQuadCells() {
quadCells = new QuadCell[CAM_COUNT];
for (int i = 0; i < CAM_COUNT; i++) {
Expand Down Expand Up @@ -950,6 +1048,8 @@ protected void onPause() {
super.onPause();
mResumed = false;
menuManager.onPauseDismissBrowser();
// The UDP view is not a saved mode: after a resume the slot or grid comes back
leaveUdpView();
stopStreams();
}

Expand Down Expand Up @@ -999,6 +1099,7 @@ protected void onNewIntent(Intent intent) {
// Android 10+ may deliver it to the resumed activity without onPause()/onResume():
// apply the shared link now
if (mResumed) {
leaveUdpView();
stopStreams();
startStreams();
}
Expand Down
Loading
Loading