RTSP DVR¶
media.play.rtsp_dvr
Sapsan serves recorded archives over RTSP. Clients discover available recording periods, select a UTC time, and control playback within one session. Reading an existing archive does not require an active input source.
Transport¶
RTSP 1.0 and interleaved RTP/RTCP share one TCP connection. UDP is unsupported: a UDP SETUP returns 461.
Supported codecs¶
- H.264
- HEVC / H.265
- AV1
- AAC
- Opus
- PCMA
- PCMU
This list describes RTSP packetization; the client must support the selected codecs.
AAC uses MPEG4-GENERIC with AU headers; AV1 uses RTP fragmentation and a sequence header. SDP declarations exist for MP4A-LATM and MPEG Audio (MPA, MP2/MP3), but the current output path does not provide their complete corresponding RTP packetization; they are excluded from the list above. Convert audio to AAC or Opus when needed. A codec name in SDP alone does not guarantee compatibility.
Protocol and URL¶
Control uses RTSP 1.0: RFC 2326. Track descriptions use SDP: RFC 4566. Media uses RTP/RTCP: RFC 3550. H.264 uses the RTP payload format in RFC 6184.
The list of archive periods in a single SDP attribute and the position parameter are extensions of the Sapsan archive interface. Their formats are described below. The supported capabilities and known differences are stated explicitly; RTSP 1.0 support alone does not imply support for every mode in these RFCs.
Access requires a recorded DVR archive, a configured RTSP listener, and a license that permits media.play.rtsp_dvr.
rtsp://sapsan.example.com:554/camera?dvr=true
The URL is static: time is passed in the Range header, not in the URL. The value dvr=true is required; other values are rejected. A regular URL without this parameter serves live playback and does not accept an archive Range: clock=.... For token authorization, append &token=<token>.
License¶
The media.play.rtsp_dvr feature uses the separate license limit media_play_rtsp_dvr. Live RTSP uses media_play_rtsp; permission to play live media does not grant archive access.
Admission is checked before reading the archive, including a direct PLAY without an earlier DESCRIBE. A zero limit prohibits access, while an exhausted numeric limit prevents new sessions: the server returns 453 Not Enough Bandwidth.
The archive session retains its licensed admission after the selected range ends. The next PLAY on the same connection uses that session. Release it with TEARDOWN or by closing the connection; EOF alone does not release the occupied slot. See licensing.
Available recording periods¶
The client starts with DESCRIBE:
DESCRIBE rtsp://sapsan.example.com:554/camera?dvr=true RTSP/1.0
CSeq: 1
Accept: application/sdp
The 200 OK response has an application/sdp body. It includes the following session-level attribute before the m= track descriptions:
a=range:clock=20261019T080000Z-20261019T093000Z;clock=20261019T100000Z-
This example describes two recording periods separated by a gap. Time uses YYYYMMDDThhmmssZ, in whole UTC seconds. A missing end time after the hyphen in the last period indicates ongoing recording.
- Order. Periods are listed in ascending time order.
- Merging. Adjacent periods are merged; gaps of up to and including 10 seconds are also merged when building the list. Such a period does not guarantee frames for every second it covers.
- Count. At most the 100 most recent periods are advertised. Older recordings that still exist can be requested with an explicit
PLAY, even when absent from this list. - Empty archive. The
a=rangeattribute is omitted. The server may use an available live source to describe the tracks.
When recordings exist, track descriptions come from the archive. DESCRIBE and playback therefore remain available after the input source disconnects.
Transport setup¶
Send SETUP for each required track using its identifier from the SDP. RTP/RTCP is carried within the same TCP connection as RTSP — interleaved TCP. In this example, <track-url> denotes the selected track URL:
SETUP <track-url> RTSP/1.0
CSeq: 2
Transport: RTP/AVP/TCP;unicast;interleaved=0-1
Relevant response headers:
RTSP/1.0 200 OK
CSeq: 2
Session: example-session;timeout=60
Transport: RTP/AVP/TCP;unicast;interleaved=0-1
Use the returned Session in subsequent requests. Choose a different interleaved channel pair for the next track. UDP transport is rejected with 461.
Examples show the relevant exchange fields. On the wire, lines end with CRLF, a blank line separates headers from the body, and Content-Length specifies the body length in bytes.
Time selection and seeking¶
To play an interval, send PLAY to the session URL:
PLAY rtsp://sapsan.example.com:554/camera?dvr=true RTSP/1.0
CSeq: 3
Session: example-session
Range: clock=20261019T081000Z-20261019T081500Z
Scale: 1
Essential response fields:
RTSP/1.0 200 OK
CSeq: 3
Session: example-session;timeout=60
Range: clock=20261019T081000Z-20261019T081500Z
Scale: 1
The RTP-Info response contains seq and rtptime for the configured tracks being delivered, including initial archive playback and seeking. On resumption after a pause, rtptime matches the actual next frame, including a jump across a recording gap.
PLAY request |
Sapsan behavior |
|---|---|
Range: clock=<from>-<to> |
Start at an independent fragment and deliver frames with DTS below <to> |
Range: clock=<from>- |
Select existing fragments from the specified time without an upper bound |
A new Range: clock=... in the same session |
Seek immediately; no new SETUP is needed |
Range: npt=now- |
Switch to live playback |
No Range, with a source already selected |
Continue playback, including after PAUSE |
No Range, on initial playback |
Select the live source |
Open numeric NPT, such as npt=10- |
451 Parameter Not Understood; existing playback continues |
The response Range reports the selected boundaries. If <from> falls inside an independent fragment, playback starts at its beginning, which may precede the request. The backward search is limited to 60 seconds; if no suitable start is found, the next independent fragment is selected. The end is limited by the requested <to> and the available recording. Frames with DTS equal to or greater than <to> are not delivered.
The hyphen is retained even for an open end. Requests accept whole UTC seconds, but an actual response boundary may include milliseconds, for example clock=20261019T081000.250Z-20261019T081500Z.
Playback runs forward at normal speed. Omit Scale or set it to 1; a request with Scale: 1 receives Scale: 1. Other speeds and the reverse form clock=-<to> return 451 Parameter Not Understood while existing playback continues.
If a recording gap separates selected fragments, the server moves to the next available fragment without waiting for the entire gap duration. Media timestamps jump across the missing period.
An open range reads the fragment selection available when PLAY was received and ends when that selection is exhausted. It does not pick up new fragments or switch to live media automatically. Live playback requires an explicit PLAY with npt=now-.
Current position¶
To obtain the delivery position, send GET_PARAMETER with position in the body:
GET_PARAMETER rtsp://sapsan.example.com:554/camera?dvr=true RTSP/1.0
CSeq: 4
Session: example-session
Content-Type: text/parameters
Content-Length: 10
position
The body here is position followed by CRLF, totaling 10 bytes. Response:
RTSP/1.0 200 OK
CSeq: 4
Session: example-session;timeout=60
Range: clock=20261019T081012Z
Supported: position
Time is returned in a header, not in the response body. It is a single timestamp without a hyphen, unlike the range in a PLAY response. The value is the DTS of the last delivered frame, truncated to whole seconds, rather than its display time on the client.
Before the first delivered frame of the selected source, including after a seek, position returns 503 Service Unavailable without Range, with Supported: position. During a pause and after archive EOF, it returns 200 OK and the last confirmed position. A 200 status alone does not indicate whether frames are still being delivered. An unknown parameter in a nonempty body returns 200 OK and Supported: position, without Range.
Pausing and ending a session¶
PAUSE at the session URL suspends delivery while retaining the position within the fragment. The next PLAY without Range resumes the remaining frames. A new Range: clock=... seeks immediately, including when multiple PLAY requests are pipelined; no interval queue is created.
During a pause, keep the session alive with an empty GET_PARAMETER. The server advertises timeout=60; send keepalive requests more frequently than this interval. Incoming RTSP requests and RTCP packets count as activity.
When a range that has delivered RTP media reaches its natural end, the server sends RTCP BYE for the configured tracks and ends the current delivery. The connection and session stay open: another range can be requested without repeating SETUP.
To release the session, send TEARDOWN with its Session. After 200 OK, the connection closes and the license slot is released.
Media and compatibility limits¶
For H.264, decoder parameters are supplied in SDP, and missing SPS/PPS are inserted before keyframes. Parameters are updated when the archive fragment configuration changes. RTP timestamps are derived from archive DTS; RTCP Sender Reports map them to NTP time.
RTCP Sender Reports contain cumulative RTP packet and payload octet counts. RTP headers and interleaved framing are excluded from the octet count. A seek using the same SSRC does not reset the counters.
The supported profile has the following limits:
- Transport. Interleaved TCP only; UDP returns
461. - Speed. Normal forward playback only; other
Scalevalues return451. - Time scale. Use
clockwith whole UTC seconds for archive seeking. Numeric NPT and SMPTE are unsupported. Fractional seconds are only allowed in responses. - Range sequencing. Each new archive
PLAYseeks immediately; there is no interval queue. - Ongoing recording. An open range does not read fragments created after playback starts.
Response codes¶
| Code | Cause |
|---|---|
400 Bad Request |
Invalid dvr or URL; missing Transport in SETUP |
403 Forbidden |
Authorization denied, or an encrypted stream unavailable for RTSP delivery |
404 Not Found |
Stream, archive, or track not found |
451 Parameter Not Understood |
Unsupported Range or Scale; archive requested through a regular live URL |
453 Not Enough Bandwidth |
Feature prohibited by the license or session limit reached |
454 Session Not Found |
Required session context is absent |
457 Invalid Range |
Empty or reversed window; no suitable archive fragments |
461 Unsupported Transport |
A transport other than interleaved TCP was requested |
503 Service Unavailable |
Source unavailable; position requested before the first frame of the selected source. Archive EOF alone does not cause 503 for position |
Client verification¶
Check the integration using a recording with known time boundaries and a gap:
- Obtain the SDP and compare
a=rangewith recording periods, accounting for the merging of short gaps. - Send
SETUPfor the required tracks. Check interleaved channel pairs and the session identifier. - Request
positionbefore the first frame: expect503withoutRange. - Start
PLAYinside a fragment withScale: 1. Check the selectedRange, returnedScale, and whetherrtptime/seqmatch the first RTP packet. No frame may have DTS >=<to>. - Pause within a fragment and resume with
PLAYwithoutRange. Verify that remaining frames and the position are retained. - Check
451for an unsupported speed and numeric NPT: existing playback must continue. - Wait for EOF and RTCP BYE, then request another range on the same connection. Check the position after EOF and cumulative Sender Report counters.
- Check license refusal
453for a zero or exhausted limit, and end the session withTEARDOWN.