Getting an RTSP Stream from a Mac Camera
2025-07-02
I’ve been working on a certain project which I am not ready to share yet, but which involves reading from RTP streams.
Before I ordered myself some RTSP compatible cameras, I needed a quick way to get an RTP stream to test out the general idea. The only cameras I have on hand are my iPhone, my MacBook and an old IP camera from which I’ve so far failed to extract an RTP stream.
I decided to see what I could do with my MacBook camera and after some trial and error landed on using mediamtx.
From it’s Github README:
MediaMTX is a ready-to-use and zero-dependency real-time media server and media proxy that allows to publish, read, proxy, record and playback video and audio streams. It has been conceived as a “media router” that routes media streams from one end to the other.
I used mediamtx in conjunction with good old ffmpeg to get the RTP stream from the Mac camera. ffmpeg takes the camera input, does some encoding voodoo and then sends it to mediamtx which serves it as an RTP stream.
This is the script I ended up with after a lot of help from Claude (and with added comments for clarity):
#!/bin/bash
# Exit immediately if any command fails
set -e
# RTSP Stream Manager
# Get camera device from first argument, default to device 0 if not provided
DEVICE=${1:-0}
# Variables to store process IDs for cleanup
MEDIAMTX_PID=""
FFMPEG_PID=""
# Cleanup function to gracefully stop both services
cleanup() {
echo "Stopping services..."
# Kill FFmpeg process if it's running
if [ ! -z "$FFMPEG_PID" ]; then
kill -9 $FFMPEG_PID 2>/dev/null
echo "FFmpeg stopped"
fi
# Kill MediaMTX process if it's running
if [ ! -z "$MEDIAMTX_PID" ]; then
kill -9 $MEDIAMTX_PID 2>/dev/null
echo "MediaMTX stopped"
fi
exit 0
}
# Set up signal traps to run cleanup on script interruption or exit
trap cleanup SIGINT SIGTERM EXIT
# Start MediaMTX streaming server in background
echo "Starting MediaMTX server..."
mediamtx mediamtx.yml &
# Store the process ID for cleanup
MEDIAMTX_PID=$!
# Give MediaMTX time to initialize before starting FFmpeg
sleep 2
# Display connection information to user
echo "Starting camera stream from device $DEVICE..."
echo "View RTSP: rtsp://localhost:8554/live"
echo "View Web: http://localhost:8889/live"
echo "Press Ctrl+C to stop both services"
# Start FFmpeg to capture camera and stream to MediaMTX
ffmpeg -f avfoundation \ # Use macOS AVFoundation for camera access
-video_size 1280x720 \ # Set capture resolution
-framerate 30 \ # Set capture frame rate
-i "$DEVICE" \ # Input device (camera number)
-c:v libx264 \ # Use x264 video codec
-preset ultrafast \ # Use fastest encoding preset (lower CPU usage)
-tune zerolatency \ # Optimize for low latency streaming
-pix_fmt yuv420p \ # Set pixel format for compatibility
-f flv \ # Output format (FLV for RTMP)
"rtmp://localhost:1935/live" & # Send to MediaMTX via RTMP
# Store FFmpeg process ID for cleanup
FFMPEG_PID=$!
# Wait for FFmpeg process to complete or be terminated
wait $FFMPEG_PID
Note: I also learned about the
trap
command in bash which allows you to run a cleanup function when the script is interrupted or exits. I read further on it at The Bash Trap Command on Linux Journal by Mitch Frazier.
mediamtx.yml
is an extremely barebones file:
# Minimal MediaMTX config
paths:
all: {}
I needed to have something, for without it mediamtx would complain about missing authentication configuration. This file doesn’t have any authentication configuration, so I’m guessing mediamtx sees it and is like: “I have an explicit config file, but it doesn’t have any authentication, so I won’t require any”. Good enough, I suppose.
On running, the script gives us both an RTSP stream and something we can see on a web page. I am not quite sure what that’s doing. Though by looking at the network requests, I see there’s a Content-Type application/sdp
response, which suggests to me WebRTC since an SDP (Session Description Protocol) is typically used in WebRTC. Pretty neat!
I can view the RTSP stream in VLC by going to File -> Open Network
and entering rtsp://localhost:8554/live
.
Hope you had fun reading!