Add RTSP preview capture — working with both cameras
Major breakthrough: RTSP preview fully operational on DWARF Mini.
Key discovery: RTSP (port 554) only works AFTER opening the camera via
WebSocket. Without CMD_CAMERA_OPEN (10000/12000), the RTSP server accepts
connections but never sends frames. This was the missing piece.
RTSP details:
- Tele: rtsp://<ip>:554/ch0/stream0
- Wide: rtsp://<ip>:554/ch1/stream0
- Codec: MJPEG over RTSP (not H.264)
- Transport: TCP (confirmed from RtspPlayerView.java ijkplayer options)
- Resolution: 1920x1080 both cameras
Port 8092 (MJPEG HTTP /mainstream, /secondstream) is inactive on the Mini.
The DWARF Mini uses RTSP exclusively, not the HTTP MJPEG fallback.
Added `dwarfctl preview grab [--cam tele|wide] [output.jpg]` that:
1. Opens the camera via WebSocket
2. Waits 2s for RTSP to become available
3. Captures one frame via ffmpeg
Updated API_REFERENCE.md with the corrected RTSP details and the camera-open
prerequisite.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
@ -90,11 +90,13 @@ ws://<telescope_ip>:9900/?client_id=<client_id>
|
|||||||
|
|
||||||
### 2.3 RTSP preview
|
### 2.3 RTSP preview
|
||||||
```
|
```
|
||||||
rtsp://<telescope_ip>/<stream_selector>/stream0
|
rtsp://<telescope_ip>:554/<channel>/stream0
|
||||||
```
|
```
|
||||||
- `<stream_selector>` comes from `StreamTypeAnn` (`com.convergence.dwarflab.data.bean.camera`).
|
- `<channel>`: `ch0` = Tele camera, `ch1` = Wide camera
|
||||||
|
- **IMPORTANT**: The camera MUST be opened via WebSocket (`CMD_CAMERA_TELE_OPEN_CAMERA` 10000 or `CMD_CAMERA_WIDE_OPEN_CAMERA` 12000) before the RTSP stream becomes available. Without opening the camera, the RTSP server accepts connections but never sends frames.
|
||||||
- Player options force `rtsp_transport = tcp` (see `RtspPlayerView.java:195`).
|
- Player options force `rtsp_transport = tcp` (see `RtspPlayerView.java:195`).
|
||||||
- Two cameras exist: **Tele** (main/long-focus) and **Wide** (wide-angle/guide).
|
- Codec: MJPEG over RTSP (not H.264).
|
||||||
|
- Port 8092 (MJPEG HTTP `/mainstream` and `/secondstream`) exists but is **inactive** on the DWARF Mini — the Mini uses RTSP exclusively.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -44,6 +45,7 @@ func main() {
|
|||||||
cmdMonitor(),
|
cmdMonitor(),
|
||||||
cmdInteractive(),
|
cmdInteractive(),
|
||||||
cmdPano(),
|
cmdPano(),
|
||||||
|
cmdPreview(),
|
||||||
)
|
)
|
||||||
|
|
||||||
rootCmd.MarkPersistentFlagRequired("ip")
|
rootCmd.MarkPersistentFlagRequired("ip")
|
||||||
@ -316,9 +318,12 @@ func cmdCamera() *cobra.Command {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
// Add --cam to all subcommands
|
// Add --cam to all subcommands, --raw to photo only
|
||||||
for _, sub := range c.Commands() {
|
for _, sub := range c.Commands() {
|
||||||
sub.Flags().String("cam", "tele", "camera: tele|wide")
|
sub.Flags().String("cam", "tele", "camera: tele|wide")
|
||||||
|
if sub.Name() == "photo" {
|
||||||
|
sub.Flags().Bool("raw", false, "capture RAW instead of JPEG")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
@ -662,6 +667,45 @@ func cmdPower() *cobra.Command {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- preview ---
|
||||||
|
|
||||||
|
func cmdPreview() *cobra.Command {
|
||||||
|
c := &cobra.Command{Use: "preview", Short: "RTSP preview frame capture"}
|
||||||
|
c.AddCommand(
|
||||||
|
&cobra.Command{
|
||||||
|
Use: "grab [--cam tele|wide] [output.jpg]",
|
||||||
|
Short: "Capture one frame from RTSP (opens camera automatically)",
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
cam := mustCam(flagCam(cmd))
|
||||||
|
ch := "ch0"
|
||||||
|
if cam == api.CameraWide {
|
||||||
|
ch = "ch1"
|
||||||
|
}
|
||||||
|
out := "/tmp/preview.jpg"
|
||||||
|
if len(args) > 0 {
|
||||||
|
out = args[0]
|
||||||
|
}
|
||||||
|
scope, cleanup := dial()
|
||||||
|
must(scope.OpenCamera(cam))
|
||||||
|
cleanup()
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
url := fmt.Sprintf("rtsp://%s:554/%s/stream0", flagIP, ch)
|
||||||
|
ffmpegCmd := exec.Command("ffmpeg", "-rtsp_transport", "tcp",
|
||||||
|
"-i", url, "-frames:v", "1", "-q:v", "2", "-y", out)
|
||||||
|
if err := ffmpegCmd.Run(); err != nil {
|
||||||
|
die("ffmpeg: %v (is ffmpeg installed?)", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("frame saved to %s\n", out)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
for _, sub := range c.Commands() {
|
||||||
|
sub.Flags().String("cam", "tele", "camera: tele|wide")
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
// --- panorama ---
|
// --- panorama ---
|
||||||
|
|
||||||
func cmdPano() *cobra.Command {
|
func cmdPano() *cobra.Command {
|
||||||
|
|||||||
Reference in New Issue
Block a user