diff --git a/camera-management-api/usda_vision_system/camera/streamer.py b/camera-management-api/usda_vision_system/camera/streamer.py index 51f25d6..d2dc6d3 100644 --- a/camera-management-api/usda_vision_system/camera/streamer.py +++ b/camera-management-api/usda_vision_system/camera/streamer.py @@ -86,6 +86,9 @@ class CameraStreamer: # RTSP settings self.rtsp_fps = 15.0 # RTSP FPS (can be higher than MJPEG preview) + # Use MEDIAMTX_HOST env var if set, otherwise default to localhost + # Note: If API uses network_mode: host, MediaMTX container ports are exposed to host + # So localhost should work, but MediaMTX must be accessible on that port self.rtsp_host = os.getenv("MEDIAMTX_HOST", "localhost") self.rtsp_port = int(os.getenv("MEDIAMTX_RTSP_PORT", "8554")) self._rtsp_process: Optional[subprocess.Popen] = None @@ -525,15 +528,27 @@ class CameraStreamer: # Wait for frame dimensions to be set timeout = 10.0 # Wait up to 10 seconds for first frame start_time = time.time() + self.logger.info(f"Waiting for frame dimensions (current: {self._rtsp_frame_width}x{self._rtsp_frame_height})...") while self._rtsp_frame_width == 0 and (time.time() - start_time) < timeout: if self._stop_rtsp_event.is_set(): + self.logger.info("RTSP streaming stopped before frame dimensions were available") return - time.sleep(0.1) + # Check if streaming is actually producing frames + if not self.streaming: + self.logger.error("Camera streaming stopped, cannot start RTSP") + self.rtsp_streaming = False + return + time.sleep(0.5) + elapsed = time.time() - start_time + if elapsed > 2 and int(elapsed) % 2 == 0: # Log every 2 seconds + self.logger.debug(f"Still waiting for frame dimensions... ({elapsed:.1f}s elapsed, queue size: {self._rtsp_frame_queue.qsize()})") if self._rtsp_frame_width == 0: - self.logger.error("Could not determine frame dimensions for RTSP streaming") + self.logger.error(f"Could not determine frame dimensions for RTSP streaming after {timeout}s. Stream active: {self.streaming}, Queue size: {self._rtsp_frame_queue.qsize()}") self.rtsp_streaming = False return + + self.logger.info(f"Frame dimensions available: {self._rtsp_frame_width}x{self._rtsp_frame_height}") rtsp_url = f"rtsp://{self.rtsp_host}:{self.rtsp_port}/{self.camera_config.name}" self.logger.info(f"Publishing RTSP stream to {rtsp_url} with dimensions {self._rtsp_frame_width}x{self._rtsp_frame_height} @ {self.rtsp_fps}fps") diff --git a/diagnose_rtsp.sh b/diagnose_rtsp.sh new file mode 100755 index 0000000..7c35903 --- /dev/null +++ b/diagnose_rtsp.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# Comprehensive RTSP diagnostic script + +echo "==========================================" +echo "RTSP Streaming Diagnostic" +echo "==========================================" +echo "" + +echo "1. Checking API health..." +API_HEALTH=$(curl -s http://exp-dash:8000/health 2>&1) +if [ $? -eq 0 ]; then + echo " ✅ API is responding" +else + echo " ❌ API is not responding" + echo " Response: $API_HEALTH" + exit 1 +fi +echo "" + +echo "2. Checking camera status..." +CAMERA_STATUS=$(curl -s http://exp-dash:8000/cameras/camera1/status 2>&1) +echo "$CAMERA_STATUS" | python3 -m json.tool 2>/dev/null || echo "$CAMERA_STATUS" +echo "" + +echo "3. Starting camera streaming..." +curl -X POST http://exp-dash:8000/cameras/camera1/start-stream 2>&1 | python3 -m json.tool 2>/dev/null || curl -X POST http://exp-dash:8000/cameras/camera1/start-stream 2>&1 +echo "" + +echo "4. Waiting for stream to initialize (8 seconds)..." +sleep 8 + +echo "5. Starting RTSP streaming..." +RTSP_RESPONSE=$(curl -s -X POST http://exp-dash:8000/cameras/camera1/start-rtsp) +echo "$RTSP_RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$RTSP_RESPONSE" +echo "" + +echo "6. Waiting for RTSP to initialize (10 seconds)..." +sleep 10 + +echo "7. Checking for FFmpeg process..." +FFMPEG_PID=$(docker compose exec api bash -c "ps aux | grep '[f]fmpeg' | grep -v grep | awk '{print \$2}'" 2>/dev/null | head -1) +if [ -n "$FFMPEG_PID" ]; then + echo " ✅ FFmpeg is running (PID: $FFMPEG_PID)" + echo " Process details:" + docker compose exec api bash -c "ps aux | grep '[f]fmpeg' | grep -v grep" 2>/dev/null +else + echo " ❌ FFmpeg is NOT running" +fi +echo "" + +echo "8. Checking API logs for RTSP activity..." +echo " Recent RTSP/FFmpeg logs:" +docker compose logs api --tail 100 | grep -E "RTSP|FFmpeg|frame dimensions|Waiting|Could not|Starting RTSP|Publishing|error|Error" | tail -20 +echo "" + +echo "9. Checking MediaMTX for stream..." +PATH_INFO=$(curl -s http://localhost:8889/v2/paths/get/camera1 2>/dev/null) +if [ -n "$PATH_INFO" ] && [ "$PATH_INFO" != "null" ] && [ "$PATH_INFO" != "{}" ]; then + echo " ✅ Stream path exists in MediaMTX" + echo "$PATH_INFO" | python3 -m json.tool 2>/dev/null | head -20 || echo "$PATH_INFO" + SOURCE_READY=$(echo "$PATH_INFO" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('sourceReady', False))" 2>/dev/null || echo "false") + if [ "$SOURCE_READY" = "True" ]; then + echo "" + echo " ✅ Stream is READY!" + else + echo "" + echo " ⚠️ Stream exists but source is not ready" + fi +else + echo " ❌ Stream not found in MediaMTX" +fi +echo "" + +echo "10. MediaMTX recent activity..." +docker compose logs mediamtx --tail 20 | grep -E "camera1|RTSP|publishing|session" | tail -10 +echo "" + +echo "==========================================" +echo "Diagnostic Complete" +echo "==========================================" +echo "" +TAILSCALE_IP=$(tailscale ip -4 2>/dev/null || hostname -I | awk '{print $1}') +echo "If stream is ready, try accessing:" +echo " http://$TAILSCALE_IP:8889/static/" +echo " http://$TAILSCALE_IP:8889/camera1/webrtc" +echo "" + diff --git a/mediamtx.yml b/mediamtx.yml index b5fc1ed..b591529 100644 --- a/mediamtx.yml +++ b/mediamtx.yml @@ -9,11 +9,6 @@ paths: # allow any path to be read; publishers can be added on-demand readUser: any sourceOnDemand: no - sourceOnDemandStartTimeout: 10s - sourceOnDemandCloseAfter: 10s - # Keep source alive even without readers (remove timeout) - # sourceCloseAfter: never # Keep stream alive indefinitely - sourceCloseAfter: 30s # Keep stream alive for 30 seconds after last reader disconnects # Example on-demand publisher for a demo VOD (adjust file path): # vod: diff --git a/test_rtsp_working.md b/test_rtsp_working.md new file mode 100644 index 0000000..c469155 --- /dev/null +++ b/test_rtsp_working.md @@ -0,0 +1,59 @@ +# RTSP Streaming is Working! 🎉 + +**Status**: ✅ RTSP streaming is functional and VLC can view the stream! + +## Current Status + +- ✅ FFmpeg is encoding and publishing frames +- ✅ MediaMTX is receiving the RTSP stream +- ✅ VLC can successfully view the stream via RTSP +- ⚠️ WebRTC requires the stream to be active when accessed + +## Access Methods + +### 1. RTSP (Working - Use This!) +```bash +rtsp://100.93.40.84:8554/camera1 +``` + +**For VLC:** +- File → Open Network Stream +- URL: `rtsp://100.93.40.84:8554/camera1` +- Or with TCP: `rtsp://100.93.40.84:8554/camera1?transport=tcp` + +### 2. WebRTC (Browser Player) +The WebRTC player needs the stream to be active when you open it. + +**To use WebRTC:** +1. First, make sure RTSP is running: + ```bash + curl -X POST http://exp-dash:8000/cameras/camera1/start-rtsp + ``` + +2. Then quickly open (within 10 seconds): + ``` + http://100.93.40.84:8889/camera1/webrtc + ``` + +**Note**: WebRTC uses POST requests to `/camera1/whep`, so 404/405 errors are normal if the stream isn't active. + +## Troubleshooting WebRTC + +If you see "stream not found" in the browser: +- The RTSP stream may have timed out +- Restart RTSP and immediately open the WebRTC URL +- MediaMTX closes streams after ~10 seconds without active viewers + +## Quick Test Commands + +```bash +# Check if RTSP is running +curl -X POST http://exp-dash:8000/cameras/camera1/start-rtsp + +# Check stream status +curl -s http://localhost:8889/v2/paths/get/camera1 | python3 -m json.tool + +# Full diagnostic +./diagnose_rtsp.sh +``` +