From 7dc41ec368f609d3d1b15c79250401d727177f8a Mon Sep 17 00:00:00 2001 From: Jacquin Antoine Date: Mon, 13 Jul 2026 19:14:49 +0200 Subject: [PATCH] =?UTF-8?q?Add=20RTSP=20preview=20capture=20=E2=80=94=20wo?= =?UTF-8?q?rking=20with=20both=20cameras?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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://:554/ch0/stream0 - Wide: rtsp://: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 --- analysis/API_REFERENCE.md | 8 +++--- dwarfctl/cmd/dwarfctl/main.go | 46 ++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/analysis/API_REFERENCE.md b/analysis/API_REFERENCE.md index 87e0880..d493a0f 100644 --- a/analysis/API_REFERENCE.md +++ b/analysis/API_REFERENCE.md @@ -90,11 +90,13 @@ ws://:9900/?client_id= ### 2.3 RTSP preview ``` -rtsp:////stream0 +rtsp://:554//stream0 ``` -- `` comes from `StreamTypeAnn` (`com.convergence.dwarflab.data.bean.camera`). +- ``: `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`). -- 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. --- diff --git a/dwarfctl/cmd/dwarfctl/main.go b/dwarfctl/cmd/dwarfctl/main.go index 8aa3829..b5c5dc2 100644 --- a/dwarfctl/cmd/dwarfctl/main.go +++ b/dwarfctl/cmd/dwarfctl/main.go @@ -5,6 +5,7 @@ import ( "fmt" "math" "os" + "os/exec" "strconv" "strings" "time" @@ -44,6 +45,7 @@ func main() { cmdMonitor(), cmdInteractive(), cmdPano(), + cmdPreview(), ) 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() { sub.Flags().String("cam", "tele", "camera: tele|wide") + if sub.Name() == "photo" { + sub.Flags().Bool("raw", false, "capture RAW instead of JPEG") + } } return c } @@ -662,6 +667,45 @@ func cmdPower() *cobra.Command { 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 --- func cmdPano() *cobra.Command {