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:
Jacquin Antoine
2026-07-13 19:14:49 +02:00
parent 4d2480c248
commit 7dc41ec368
2 changed files with 50 additions and 4 deletions

View File

@ -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 {