This makes the RTSP demuxer act as a server, listening for an
incoming connection.
Signed-off-by: Martin Storsjö <martin@martin.st>
... | ... |
@@ -347,6 +347,8 @@ Flags for @code{rtsp_flags}: |
347 | 347 |
@table @option |
348 | 348 |
@item filter_src |
349 | 349 |
Accept packets only from negotiated peer address and port. |
350 |
+@item listen |
|
351 |
+Act as a server, listening for an incoming connection. |
|
350 | 352 |
@end table |
351 | 353 |
|
352 | 354 |
When receiving data over UDP, the demuxer tries to reorder received packets |
... | ... |
@@ -379,6 +381,12 @@ To send a stream in realtime to a RTSP server, for others to watch: |
379 | 379 |
avconv -re -i @var{input} -f rtsp -muxdelay 0.1 rtsp://server/live.sdp |
380 | 380 |
@end example |
381 | 381 |
|
382 |
+To receive a stream in realtime: |
|
383 |
+ |
|
384 |
+@example |
|
385 |
+avconv -rtsp_flags listen -i rtsp://ownaddress/live.sdp @var{output} |
|
386 |
+@end example |
|
387 |
+ |
|
382 | 388 |
@section sap |
383 | 389 |
|
384 | 390 |
Session Announcement Protocol (RFC 2974). This is not technically a |
... | ... |
@@ -63,7 +63,8 @@ |
63 | 63 |
|
64 | 64 |
#define RTSP_FLAG_OPTS(name, longname) \ |
65 | 65 |
{ name, longname, OFFSET(rtsp_flags), AV_OPT_TYPE_FLAGS, {0}, INT_MIN, INT_MAX, DEC, "rtsp_flags" }, \ |
66 |
- { "filter_src", "Only receive packets from the negotiated peer IP", 0, AV_OPT_TYPE_CONST, {RTSP_FLAG_FILTER_SRC}, 0, 0, DEC, "rtsp_flags" } |
|
66 |
+ { "filter_src", "Only receive packets from the negotiated peer IP", 0, AV_OPT_TYPE_CONST, {RTSP_FLAG_FILTER_SRC}, 0, 0, DEC, "rtsp_flags" }, \ |
|
67 |
+ { "listen", "Wait for incoming connections", 0, AV_OPT_TYPE_CONST, {RTSP_FLAG_LISTEN}, 0, 0, DEC, "rtsp_flags" } |
|
67 | 68 |
|
68 | 69 |
#define RTSP_MEDIATYPE_OPTS(name, longname) \ |
69 | 70 |
{ name, longname, OFFSET(media_type_mask), AV_OPT_TYPE_FLAGS, { (1 << (AVMEDIA_TYPE_DATA+1)) - 1 }, INT_MIN, INT_MAX, DEC, "allowed_media_types" }, \ |
... | ... |
@@ -83,6 +84,7 @@ const AVOption ff_rtsp_options[] = { |
83 | 83 |
RTSP_MEDIATYPE_OPTS("allowed_media_types", "Media types to accept from the server"), |
84 | 84 |
{ "min_port", "Minimum local UDP port", OFFSET(rtp_port_min), AV_OPT_TYPE_INT, {RTSP_RTP_PORT_MIN}, 0, 65535, DEC|ENC }, |
85 | 85 |
{ "max_port", "Maximum local UDP port", OFFSET(rtp_port_max), AV_OPT_TYPE_INT, {RTSP_RTP_PORT_MAX}, 0, 65535, DEC|ENC }, |
86 |
+ { "timeout", "Maximum timeout (in seconds) to wait for incoming connections. -1 is infinite. Implies flag listen", OFFSET(initial_timeout), AV_OPT_TYPE_INT, {-1}, INT_MIN, INT_MAX, DEC }, |
|
86 | 87 |
{ NULL }, |
87 | 88 |
}; |
88 | 89 |
|
... | ... |
@@ -1714,14 +1716,24 @@ static int udp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st, |
1714 | 1714 |
} |
1715 | 1715 |
#if CONFIG_RTSP_DEMUXER |
1716 | 1716 |
if (tcp_fd != -1 && p[0].revents & POLLIN) { |
1717 |
- RTSPMessageHeader reply; |
|
1718 |
- |
|
1719 |
- ret = ff_rtsp_read_reply(s, &reply, NULL, 0, NULL); |
|
1720 |
- if (ret < 0) |
|
1721 |
- return ret; |
|
1722 |
- /* XXX: parse message */ |
|
1723 |
- if (rt->state != RTSP_STATE_STREAMING) |
|
1724 |
- return 0; |
|
1717 |
+ if (rt->rtsp_flags & RTSP_FLAG_LISTEN) { |
|
1718 |
+ if (rt->state == RTSP_STATE_STREAMING) { |
|
1719 |
+ if (!ff_rtsp_parse_streaming_commands(s)) |
|
1720 |
+ return AVERROR_EOF; |
|
1721 |
+ else |
|
1722 |
+ av_log(s, AV_LOG_WARNING, |
|
1723 |
+ "Unable to answer to TEARDOWN\n"); |
|
1724 |
+ } else |
|
1725 |
+ return 0; |
|
1726 |
+ } else { |
|
1727 |
+ RTSPMessageHeader reply; |
|
1728 |
+ ret = ff_rtsp_read_reply(s, &reply, NULL, 0, NULL); |
|
1729 |
+ if (ret < 0) |
|
1730 |
+ return ret; |
|
1731 |
+ /* XXX: parse message */ |
|
1732 |
+ if (rt->state != RTSP_STATE_STREAMING) |
|
1733 |
+ return 0; |
|
1734 |
+ } |
|
1725 | 1735 |
} |
1726 | 1736 |
#endif |
1727 | 1737 |
} else if (n == 0 && ++timeout_cnt >= MAX_TIMEOUTS) { |
... | ... |
@@ -372,11 +372,17 @@ typedef struct RTSPState { |
372 | 372 |
* Minimum and maximum local UDP ports. |
373 | 373 |
*/ |
374 | 374 |
int rtp_port_min, rtp_port_max; |
375 |
+ |
|
376 |
+ /** |
|
377 |
+ * Timeout to wait for incoming connections. |
|
378 |
+ */ |
|
379 |
+ int initial_timeout; |
|
375 | 380 |
} RTSPState; |
376 | 381 |
|
377 | 382 |
#define RTSP_FLAG_FILTER_SRC 0x1 /**< Filter incoming UDP packets - |
378 | 383 |
receive packets only from the right |
379 | 384 |
source address and port. */ |
385 |
+#define RTSP_FLAG_LISTEN 0x2 /**< Wait for incoming connections. */ |
|
380 | 386 |
|
381 | 387 |
/** |
382 | 388 |
* Describe a single stream, as identified by a single m= line block in the |
... | ... |
@@ -529,6 +535,12 @@ int ff_rtsp_setup_input_streams(AVFormatContext *s, RTSPMessageHeader *reply); |
529 | 529 |
int ff_rtsp_setup_output_streams(AVFormatContext *s, const char *addr); |
530 | 530 |
|
531 | 531 |
/** |
532 |
+ * Parse RTSP commands (OPTIONS, PAUSE and TEARDOWN) during streaming in |
|
533 |
+ * listen mode. |
|
534 |
+ */ |
|
535 |
+int ff_rtsp_parse_streaming_commands(AVFormatContext *s); |
|
536 |
+ |
|
537 |
+/** |
|
532 | 538 |
* Parse an SDP description of streams by populating an RTSPState struct |
533 | 539 |
* within the AVFormatContext; also allocate the RTP streams and the |
534 | 540 |
* pollfd array used for UDP streams. |
... | ... |
@@ -37,4 +37,18 @@ RTSP_STATUS_SERVICE =503, /**< Service Unavailable */ |
37 | 37 |
RTSP_STATUS_VERSION =505, /**< RTSP Version not supported */ |
38 | 38 |
}; |
39 | 39 |
|
40 |
+enum RTSPMethod { |
|
41 |
+ DESCRIBE, |
|
42 |
+ ANNOUNCE, |
|
43 |
+ OPTIONS, |
|
44 |
+ SETUP, |
|
45 |
+ PLAY, |
|
46 |
+ PAUSE, |
|
47 |
+ TEARDOWN, |
|
48 |
+ GET_PARAMETER, |
|
49 |
+ SET_PARAMETER, |
|
50 |
+ REDIRECT, |
|
51 |
+ RECORD, |
|
52 |
+ UNKNOWN = -1, |
|
53 |
+}; |
|
40 | 54 |
#endif /* AVFORMAT_RTSPCODES_H */ |
... | ... |
@@ -22,6 +22,7 @@ |
22 | 22 |
#include "libavutil/avstring.h" |
23 | 23 |
#include "libavutil/intreadwrite.h" |
24 | 24 |
#include "libavutil/mathematics.h" |
25 |
+#include "libavutil/random_seed.h" |
|
25 | 26 |
#include "avformat.h" |
26 | 27 |
|
27 | 28 |
#include "internal.h" |
... | ... |
@@ -31,11 +32,30 @@ |
31 | 31 |
#include "rdt.h" |
32 | 32 |
#include "url.h" |
33 | 33 |
|
34 |
+static const struct RTSPStatusMessage { |
|
35 |
+ enum RTSPStatusCode code; |
|
36 |
+ const char *message; |
|
37 |
+} status_messages[] = { |
|
38 |
+ { RTSP_STATUS_OK, "OK" }, |
|
39 |
+ { RTSP_STATUS_METHOD, "Method Not Allowed" }, |
|
40 |
+ { RTSP_STATUS_BANDWIDTH, "Not Enough Bandwidth" }, |
|
41 |
+ { RTSP_STATUS_SESSION, "Session Not Found" }, |
|
42 |
+ { RTSP_STATUS_STATE, "Method Not Valid in This State" }, |
|
43 |
+ { RTSP_STATUS_AGGREGATE, "Aggregate operation not allowed" }, |
|
44 |
+ { RTSP_STATUS_ONLY_AGGREGATE, "Only aggregate operation allowed" }, |
|
45 |
+ { RTSP_STATUS_TRANSPORT, "Unsupported transport" }, |
|
46 |
+ { RTSP_STATUS_INTERNAL, "Internal Server Error" }, |
|
47 |
+ { RTSP_STATUS_SERVICE, "Service Unavailable" }, |
|
48 |
+ { RTSP_STATUS_VERSION, "RTSP Version not supported" }, |
|
49 |
+ { 0, "NULL" } |
|
50 |
+}; |
|
51 |
+ |
|
34 | 52 |
static int rtsp_read_close(AVFormatContext *s) |
35 | 53 |
{ |
36 | 54 |
RTSPState *rt = s->priv_data; |
37 | 55 |
|
38 |
- ff_rtsp_send_cmd_async(s, "TEARDOWN", rt->control_uri, NULL); |
|
56 |
+ if (!(rt->rtsp_flags & RTSP_FLAG_LISTEN)) |
|
57 |
+ ff_rtsp_send_cmd_async(s, "TEARDOWN", rt->control_uri, NULL); |
|
39 | 58 |
|
40 | 59 |
ff_rtsp_close_streams(s); |
41 | 60 |
ff_rtsp_close_connections(s); |
... | ... |
@@ -45,6 +65,429 @@ static int rtsp_read_close(AVFormatContext *s) |
45 | 45 |
return 0; |
46 | 46 |
} |
47 | 47 |
|
48 |
+static inline int read_line(AVFormatContext *s, char *rbuf, const int rbufsize, |
|
49 |
+ int *rbuflen) |
|
50 |
+{ |
|
51 |
+ RTSPState *rt = s->priv_data; |
|
52 |
+ int idx = 0; |
|
53 |
+ int ret = 0; |
|
54 |
+ *rbuflen = 0; |
|
55 |
+ |
|
56 |
+ do { |
|
57 |
+ ret = ffurl_read_complete(rt->rtsp_hd, rbuf + idx, 1); |
|
58 |
+ if (ret < 0) |
|
59 |
+ return ret; |
|
60 |
+ if (rbuf[idx] == '\r') { |
|
61 |
+ /* Ignore */ |
|
62 |
+ } else if (rbuf[idx] == '\n') { |
|
63 |
+ rbuf[idx] = '\0'; |
|
64 |
+ *rbuflen = idx; |
|
65 |
+ return 0; |
|
66 |
+ } else |
|
67 |
+ idx++; |
|
68 |
+ } while (idx < rbufsize); |
|
69 |
+ av_log(s, AV_LOG_ERROR, "Message too long\n"); |
|
70 |
+ return AVERROR(EIO); |
|
71 |
+} |
|
72 |
+ |
|
73 |
+static int rtsp_send_reply(AVFormatContext *s, enum RTSPStatusCode code, |
|
74 |
+ const char *extracontent, uint16_t seq) |
|
75 |
+{ |
|
76 |
+ RTSPState *rt = s->priv_data; |
|
77 |
+ char message[4096]; |
|
78 |
+ int index = 0; |
|
79 |
+ while (status_messages[index].code) { |
|
80 |
+ if (status_messages[index].code == code) { |
|
81 |
+ snprintf(message, sizeof(message), "RTSP/1.0 %d %s\r\n", |
|
82 |
+ code, status_messages[index].message); |
|
83 |
+ break; |
|
84 |
+ } |
|
85 |
+ index++; |
|
86 |
+ } |
|
87 |
+ if (!status_messages[index].code) |
|
88 |
+ return AVERROR(EINVAL); |
|
89 |
+ av_strlcatf(message, sizeof(message), "CSeq: %d\r\n", seq); |
|
90 |
+ av_strlcatf(message, sizeof(message), "Server: %s\r\n", LIBAVFORMAT_IDENT); |
|
91 |
+ if (extracontent) |
|
92 |
+ av_strlcat(message, extracontent, sizeof(message)); |
|
93 |
+ av_strlcat(message, "\r\n", sizeof(message)); |
|
94 |
+ av_dlog(s, "Sending response:\n%s", message); |
|
95 |
+ ffurl_write(rt->rtsp_hd, message, strlen(message)); |
|
96 |
+ |
|
97 |
+ return 0; |
|
98 |
+} |
|
99 |
+ |
|
100 |
+static inline int check_sessionid(AVFormatContext *s, |
|
101 |
+ RTSPMessageHeader *request) |
|
102 |
+{ |
|
103 |
+ RTSPState *rt = s->priv_data; |
|
104 |
+ unsigned char *session_id = rt->session_id; |
|
105 |
+ if (!session_id[0]) { |
|
106 |
+ av_log(s, AV_LOG_WARNING, "There is no session-id at the moment\n"); |
|
107 |
+ return 0; |
|
108 |
+ } |
|
109 |
+ if (strcmp(session_id, request->session_id)) { |
|
110 |
+ av_log(s, AV_LOG_ERROR, "Unexpected session-id %s\n", |
|
111 |
+ request->session_id); |
|
112 |
+ rtsp_send_reply(s, RTSP_STATUS_SESSION, NULL, request->seq); |
|
113 |
+ return AVERROR_STREAM_NOT_FOUND; |
|
114 |
+ } |
|
115 |
+ return 0; |
|
116 |
+} |
|
117 |
+ |
|
118 |
+static inline int rtsp_read_request(AVFormatContext *s, |
|
119 |
+ RTSPMessageHeader *request, |
|
120 |
+ const char *method) |
|
121 |
+{ |
|
122 |
+ RTSPState *rt = s->priv_data; |
|
123 |
+ char rbuf[1024]; |
|
124 |
+ int rbuflen, ret; |
|
125 |
+ do { |
|
126 |
+ ret = read_line(s, rbuf, sizeof(rbuf), &rbuflen); |
|
127 |
+ if (ret) |
|
128 |
+ return ret; |
|
129 |
+ if (rbuflen > 1) { |
|
130 |
+ av_dlog(s, "Parsing[%d]: %s\n", rbuflen, rbuf); |
|
131 |
+ ff_rtsp_parse_line(request, rbuf, rt, method); |
|
132 |
+ } |
|
133 |
+ } while (rbuflen > 0); |
|
134 |
+ if (request->seq != rt->seq + 1) { |
|
135 |
+ av_log(s, AV_LOG_ERROR, "Unexpected Sequence number %d\n", |
|
136 |
+ request->seq); |
|
137 |
+ return AVERROR(EINVAL); |
|
138 |
+ } |
|
139 |
+ if (rt->session_id[0] && strcmp(method, "OPTIONS")) { |
|
140 |
+ ret = check_sessionid(s, request); |
|
141 |
+ if (ret) |
|
142 |
+ return ret; |
|
143 |
+ } |
|
144 |
+ |
|
145 |
+ return 0; |
|
146 |
+} |
|
147 |
+ |
|
148 |
+static int rtsp_read_announce(AVFormatContext *s) |
|
149 |
+{ |
|
150 |
+ RTSPState *rt = s->priv_data; |
|
151 |
+ RTSPMessageHeader request = { 0 }; |
|
152 |
+ char sdp[4096]; |
|
153 |
+ int ret; |
|
154 |
+ |
|
155 |
+ ret = rtsp_read_request(s, &request, "ANNOUNCE"); |
|
156 |
+ if (ret) |
|
157 |
+ return ret; |
|
158 |
+ rt->seq++; |
|
159 |
+ if (strcmp(request.content_type, "application/sdp")) { |
|
160 |
+ av_log(s, AV_LOG_ERROR, "Unexpected content type %s\n", |
|
161 |
+ request.content_type); |
|
162 |
+ rtsp_send_reply(s, RTSP_STATUS_SERVICE, NULL, request.seq); |
|
163 |
+ return AVERROR_OPTION_NOT_FOUND; |
|
164 |
+ } |
|
165 |
+ if (request.content_length && request.content_length < sizeof(sdp) - 1) { |
|
166 |
+ /* Read SDP */ |
|
167 |
+ if (ffurl_read_complete(rt->rtsp_hd, sdp, request.content_length) |
|
168 |
+ < request.content_length) { |
|
169 |
+ av_log(s, AV_LOG_ERROR, |
|
170 |
+ "Unable to get complete SDP Description in ANNOUNCE\n"); |
|
171 |
+ rtsp_send_reply(s, RTSP_STATUS_INTERNAL, NULL, request.seq); |
|
172 |
+ return AVERROR(EIO); |
|
173 |
+ } |
|
174 |
+ sdp[request.content_length] = '\0'; |
|
175 |
+ av_log(s, AV_LOG_VERBOSE, "SDP: %s\n", sdp); |
|
176 |
+ ret = ff_sdp_parse(s, sdp); |
|
177 |
+ if (ret) |
|
178 |
+ return ret; |
|
179 |
+ rtsp_send_reply(s, RTSP_STATUS_OK, NULL, request.seq); |
|
180 |
+ return 0; |
|
181 |
+ } |
|
182 |
+ av_log(s, AV_LOG_ERROR, |
|
183 |
+ "Content-Length header value exceeds sdp allocated buffer (4KB)\n"); |
|
184 |
+ rtsp_send_reply(s, RTSP_STATUS_INTERNAL, |
|
185 |
+ "Content-Length exceeds buffer size", request.seq); |
|
186 |
+ return AVERROR(EIO); |
|
187 |
+} |
|
188 |
+ |
|
189 |
+static int rtsp_read_options(AVFormatContext *s) |
|
190 |
+{ |
|
191 |
+ RTSPState *rt = s->priv_data; |
|
192 |
+ RTSPMessageHeader request = { 0 }; |
|
193 |
+ int ret = 0; |
|
194 |
+ |
|
195 |
+ /* Parsing headers */ |
|
196 |
+ ret = rtsp_read_request(s, &request, "OPTIONS"); |
|
197 |
+ if (ret) |
|
198 |
+ return ret; |
|
199 |
+ rt->seq++; |
|
200 |
+ /* Send Reply */ |
|
201 |
+ rtsp_send_reply(s, RTSP_STATUS_OK, |
|
202 |
+ "Public: ANNOUNCE, PAUSE, SETUP, TEARDOWN, RECORD\r\n", |
|
203 |
+ request.seq); |
|
204 |
+ return 0; |
|
205 |
+} |
|
206 |
+ |
|
207 |
+static int rtsp_read_setup(AVFormatContext *s, char* host, char *controlurl) |
|
208 |
+{ |
|
209 |
+ RTSPState *rt = s->priv_data; |
|
210 |
+ RTSPMessageHeader request = { 0 }; |
|
211 |
+ int ret = 0; |
|
212 |
+ char url[1024]; |
|
213 |
+ RTSPStream *rtsp_st; |
|
214 |
+ char responseheaders[1024]; |
|
215 |
+ int localport = -1; |
|
216 |
+ int transportidx = 0; |
|
217 |
+ int streamid = 0; |
|
218 |
+ |
|
219 |
+ ret = rtsp_read_request(s, &request, "SETUP"); |
|
220 |
+ if (ret) |
|
221 |
+ return ret; |
|
222 |
+ rt->seq++; |
|
223 |
+ if (!request.nb_transports) { |
|
224 |
+ av_log(s, AV_LOG_ERROR, "No transport defined in SETUP\n"); |
|
225 |
+ return AVERROR_INVALIDDATA; |
|
226 |
+ } |
|
227 |
+ for (transportidx = 0; transportidx < request.nb_transports; |
|
228 |
+ transportidx++) { |
|
229 |
+ if (!request.transports[transportidx].mode_record || |
|
230 |
+ (request.transports[transportidx].lower_transport != |
|
231 |
+ RTSP_LOWER_TRANSPORT_UDP && |
|
232 |
+ request.transports[transportidx].lower_transport != |
|
233 |
+ RTSP_LOWER_TRANSPORT_TCP)) { |
|
234 |
+ av_log(s, AV_LOG_ERROR, "mode=record/receive not set or transport" |
|
235 |
+ " protocol not supported (yet)\n"); |
|
236 |
+ return AVERROR_INVALIDDATA; |
|
237 |
+ } |
|
238 |
+ } |
|
239 |
+ if (request.nb_transports > 1) |
|
240 |
+ av_log(s, AV_LOG_WARNING, "More than one transport not supported, " |
|
241 |
+ "using first of all\n"); |
|
242 |
+ for (streamid = 0; streamid < rt->nb_rtsp_streams; streamid++) { |
|
243 |
+ if (!strcmp(rt->rtsp_streams[streamid]->control_url, |
|
244 |
+ controlurl)) |
|
245 |
+ break; |
|
246 |
+ } |
|
247 |
+ if (streamid == rt->nb_rtsp_streams) { |
|
248 |
+ av_log(s, AV_LOG_ERROR, "Unable to find requested track\n"); |
|
249 |
+ return AVERROR_STREAM_NOT_FOUND; |
|
250 |
+ } |
|
251 |
+ rtsp_st = rt->rtsp_streams[streamid]; |
|
252 |
+ localport = rt->rtp_port_min; |
|
253 |
+ |
|
254 |
+ if (request.transports[0].lower_transport == RTSP_LOWER_TRANSPORT_TCP) { |
|
255 |
+ rt->lower_transport = RTSP_LOWER_TRANSPORT_TCP; |
|
256 |
+ if ((ret = ff_rtsp_open_transport_ctx(s, rtsp_st))) { |
|
257 |
+ rtsp_send_reply(s, RTSP_STATUS_TRANSPORT, NULL, request.seq); |
|
258 |
+ return ret; |
|
259 |
+ } |
|
260 |
+ rtsp_st->interleaved_min = request.transports[0].interleaved_min; |
|
261 |
+ rtsp_st->interleaved_max = request.transports[0].interleaved_max; |
|
262 |
+ snprintf(responseheaders, sizeof(responseheaders), "Transport: " |
|
263 |
+ "RTP/AVP/TCP;unicast;mode=receive;interleaved=%d-%d" |
|
264 |
+ "\r\n", request.transports[0].interleaved_min, |
|
265 |
+ request.transports[0].interleaved_max); |
|
266 |
+ } else { |
|
267 |
+ do { |
|
268 |
+ ff_url_join(url, sizeof(url), "rtp", NULL, host, localport, NULL); |
|
269 |
+ av_dlog(s, "Opening: %s", url); |
|
270 |
+ ret = ffurl_open(&rtsp_st->rtp_handle, url, AVIO_FLAG_READ_WRITE, |
|
271 |
+ &s->interrupt_callback, NULL); |
|
272 |
+ if (ret) |
|
273 |
+ localport += 2; |
|
274 |
+ } while (ret || localport > rt->rtp_port_max); |
|
275 |
+ if (localport > rt->rtp_port_max) { |
|
276 |
+ rtsp_send_reply(s, RTSP_STATUS_TRANSPORT, NULL, request.seq); |
|
277 |
+ return ret; |
|
278 |
+ } |
|
279 |
+ |
|
280 |
+ av_dlog(s, "Listening on: %d", |
|
281 |
+ ff_rtp_get_local_rtp_port(rtsp_st->rtp_handle)); |
|
282 |
+ if ((ret = ff_rtsp_open_transport_ctx(s, rtsp_st))) { |
|
283 |
+ rtsp_send_reply(s, RTSP_STATUS_TRANSPORT, NULL, request.seq); |
|
284 |
+ return ret; |
|
285 |
+ } |
|
286 |
+ |
|
287 |
+ localport = ff_rtp_get_local_rtp_port(rtsp_st->rtp_handle); |
|
288 |
+ snprintf(responseheaders, sizeof(responseheaders), "Transport: " |
|
289 |
+ "RTP/AVP/UDP;unicast;mode=receive;source=%s;" |
|
290 |
+ "client_port=%d-%d;server_port=%d-%d\r\n", |
|
291 |
+ host, request.transports[0].client_port_min, |
|
292 |
+ request.transports[0].client_port_max, localport, |
|
293 |
+ localport + 1); |
|
294 |
+ } |
|
295 |
+ |
|
296 |
+ /* Establish sessionid if not previously set */ |
|
297 |
+ /* Put this in a function? */ |
|
298 |
+ /* RFC 2326: session id must be at least 8 digits */ |
|
299 |
+ while (strlen(rt->session_id) < 8) |
|
300 |
+ av_strlcatf(rt->session_id, 512, "%u", av_get_random_seed()); |
|
301 |
+ |
|
302 |
+ av_strlcatf(responseheaders, sizeof(responseheaders), "Session: %s\r\n", |
|
303 |
+ rt->session_id); |
|
304 |
+ /* Send Reply */ |
|
305 |
+ rtsp_send_reply(s, RTSP_STATUS_OK, responseheaders, request.seq); |
|
306 |
+ |
|
307 |
+ rt->state = RTSP_STATE_PAUSED; |
|
308 |
+ return 0; |
|
309 |
+} |
|
310 |
+ |
|
311 |
+static int rtsp_read_record(AVFormatContext *s) |
|
312 |
+{ |
|
313 |
+ RTSPState *rt = s->priv_data; |
|
314 |
+ RTSPMessageHeader request = { 0 }; |
|
315 |
+ int ret = 0; |
|
316 |
+ char responseheaders[1024]; |
|
317 |
+ |
|
318 |
+ ret = rtsp_read_request(s, &request, "RECORD"); |
|
319 |
+ if (ret) |
|
320 |
+ return ret; |
|
321 |
+ ret = check_sessionid(s, &request); |
|
322 |
+ if (ret) |
|
323 |
+ return ret; |
|
324 |
+ rt->seq++; |
|
325 |
+ snprintf(responseheaders, sizeof(responseheaders), "Session: %s\r\n", |
|
326 |
+ rt->session_id); |
|
327 |
+ rtsp_send_reply(s, RTSP_STATUS_OK, responseheaders, request.seq); |
|
328 |
+ |
|
329 |
+ rt->state = RTSP_STATE_STREAMING; |
|
330 |
+ return 0; |
|
331 |
+} |
|
332 |
+ |
|
333 |
+static inline int parse_command_line(AVFormatContext *s, const char *line, |
|
334 |
+ int linelen, char *uri, int urisize, |
|
335 |
+ char *method, int methodsize, |
|
336 |
+ enum RTSPMethod *methodcode) |
|
337 |
+{ |
|
338 |
+ RTSPState *rt = s->priv_data; |
|
339 |
+ const char *linept, *searchlinept; |
|
340 |
+ linept = strchr(line, ' '); |
|
341 |
+ if (linept - line > methodsize - 1) { |
|
342 |
+ av_log(s, AV_LOG_ERROR, "Method string too long\n"); |
|
343 |
+ return AVERROR(EIO); |
|
344 |
+ } |
|
345 |
+ memcpy(method, line, linept - line); |
|
346 |
+ method[linept - line] = '\0'; |
|
347 |
+ linept++; |
|
348 |
+ if (!strcmp(method, "ANNOUNCE")) |
|
349 |
+ *methodcode = ANNOUNCE; |
|
350 |
+ else if (!strcmp(method, "OPTIONS")) |
|
351 |
+ *methodcode = OPTIONS; |
|
352 |
+ else if (!strcmp(method, "RECORD")) |
|
353 |
+ *methodcode = RECORD; |
|
354 |
+ else if (!strcmp(method, "SETUP")) |
|
355 |
+ *methodcode = SETUP; |
|
356 |
+ else if (!strcmp(method, "PAUSE")) |
|
357 |
+ *methodcode = PAUSE; |
|
358 |
+ else if (!strcmp(method, "TEARDOWN")) |
|
359 |
+ *methodcode = TEARDOWN; |
|
360 |
+ else |
|
361 |
+ *methodcode = UNKNOWN; |
|
362 |
+ /* Check method with the state */ |
|
363 |
+ if (rt->state == RTSP_STATE_IDLE) { |
|
364 |
+ if ((*methodcode != ANNOUNCE) && (*methodcode != OPTIONS)) { |
|
365 |
+ av_log(s, AV_LOG_ERROR, "Unexpected command in Idle State %s\n", |
|
366 |
+ line); |
|
367 |
+ return AVERROR_PROTOCOL_NOT_FOUND; |
|
368 |
+ } |
|
369 |
+ } else if (rt->state == RTSP_STATE_PAUSED) { |
|
370 |
+ if ((*methodcode != OPTIONS) && (*methodcode != RECORD) |
|
371 |
+ && (*methodcode != SETUP)) { |
|
372 |
+ av_log(s, AV_LOG_ERROR, "Unexpected command in Paused State %s\n", |
|
373 |
+ line); |
|
374 |
+ return AVERROR_PROTOCOL_NOT_FOUND; |
|
375 |
+ } |
|
376 |
+ } else if (rt->state == RTSP_STATE_STREAMING) { |
|
377 |
+ if ((*methodcode != PAUSE) && (*methodcode != OPTIONS) |
|
378 |
+ && (*methodcode != TEARDOWN)) { |
|
379 |
+ av_log(s, AV_LOG_ERROR, "Unexpected command in Streaming State" |
|
380 |
+ " %s\n", line); |
|
381 |
+ return AVERROR_PROTOCOL_NOT_FOUND; |
|
382 |
+ } |
|
383 |
+ } else { |
|
384 |
+ av_log(s, AV_LOG_ERROR, "Unexpected State [%d]\n", rt->state); |
|
385 |
+ return AVERROR_BUG; |
|
386 |
+ } |
|
387 |
+ |
|
388 |
+ searchlinept = strchr(linept, ' '); |
|
389 |
+ if (searchlinept == NULL) { |
|
390 |
+ av_log(s, AV_LOG_ERROR, "Error parsing message URI\n"); |
|
391 |
+ return AVERROR_INVALIDDATA; |
|
392 |
+ } |
|
393 |
+ if (searchlinept - linept > urisize - 1) { |
|
394 |
+ av_log(s, AV_LOG_ERROR, "uri string length exceeded buffer size\n"); |
|
395 |
+ return AVERROR(EIO); |
|
396 |
+ } |
|
397 |
+ memcpy(uri, linept, searchlinept - linept); |
|
398 |
+ uri[searchlinept - linept] = '\0'; |
|
399 |
+ if (strcmp(rt->control_uri, uri)) { |
|
400 |
+ char host[128], path[512], auth[128]; |
|
401 |
+ int port; |
|
402 |
+ char ctl_host[128], ctl_path[512], ctl_auth[128]; |
|
403 |
+ int ctl_port; |
|
404 |
+ av_url_split(NULL, 0, auth, sizeof(auth), host, sizeof(host), &port, |
|
405 |
+ path, sizeof(path), uri); |
|
406 |
+ av_url_split(NULL, 0, ctl_auth, sizeof(ctl_auth), ctl_host, |
|
407 |
+ sizeof(ctl_host), &ctl_port, ctl_path, sizeof(ctl_path), |
|
408 |
+ rt->control_uri); |
|
409 |
+ if (strcmp(host, ctl_host)) |
|
410 |
+ av_log(s, AV_LOG_INFO, "Host %s differs from expected %s\n", |
|
411 |
+ host, ctl_host); |
|
412 |
+ if (strcmp(path, ctl_path) && *methodcode != SETUP) |
|
413 |
+ av_log(s, AV_LOG_WARNING, "WARNING: Path %s differs from expected" |
|
414 |
+ " %s\n", path, ctl_path); |
|
415 |
+ if (*methodcode == ANNOUNCE) { |
|
416 |
+ av_log(s, AV_LOG_INFO, |
|
417 |
+ "Updating control URI to %s\n", uri); |
|
418 |
+ strcpy(rt->control_uri, uri); |
|
419 |
+ } |
|
420 |
+ } |
|
421 |
+ |
|
422 |
+ linept = searchlinept + 1; |
|
423 |
+ if (!av_strstart(linept, "RTSP/1.0", NULL)) { |
|
424 |
+ av_log(s, AV_LOG_ERROR, "Error parsing protocol or version\n"); |
|
425 |
+ return AVERROR_PROTOCOL_NOT_FOUND; |
|
426 |
+ } |
|
427 |
+ return 0; |
|
428 |
+} |
|
429 |
+ |
|
430 |
+int ff_rtsp_parse_streaming_commands(AVFormatContext *s) |
|
431 |
+{ |
|
432 |
+ RTSPState *rt = s->priv_data; |
|
433 |
+ unsigned char rbuf[4096]; |
|
434 |
+ unsigned char method[10]; |
|
435 |
+ char uri[500]; |
|
436 |
+ int ret; |
|
437 |
+ int rbuflen = 0; |
|
438 |
+ RTSPMessageHeader request = { 0 }; |
|
439 |
+ enum RTSPMethod methodcode; |
|
440 |
+ |
|
441 |
+ ret = read_line(s, rbuf, sizeof(rbuf), &rbuflen); |
|
442 |
+ if (ret < 0) |
|
443 |
+ return ret; |
|
444 |
+ ret = parse_command_line(s, rbuf, rbuflen, uri, sizeof(uri), method, |
|
445 |
+ sizeof(method), &methodcode); |
|
446 |
+ if (ret) { |
|
447 |
+ av_log(s, AV_LOG_ERROR, "RTSP: Unexpected Command\n"); |
|
448 |
+ return ret; |
|
449 |
+ } |
|
450 |
+ |
|
451 |
+ ret = rtsp_read_request(s, &request, method); |
|
452 |
+ if (ret) |
|
453 |
+ return ret; |
|
454 |
+ rt->seq++; |
|
455 |
+ if (methodcode == PAUSE) { |
|
456 |
+ rt->state = RTSP_STATE_PAUSED; |
|
457 |
+ ret = rtsp_send_reply(s, RTSP_STATUS_OK, NULL , request.seq); |
|
458 |
+ // TODO: Missing date header in response |
|
459 |
+ } else if (methodcode == OPTIONS) { |
|
460 |
+ ret = rtsp_send_reply(s, RTSP_STATUS_OK, |
|
461 |
+ "Public: ANNOUNCE, PAUSE, SETUP, TEARDOWN, " |
|
462 |
+ "RECORD\r\n", request.seq); |
|
463 |
+ } else if (methodcode == TEARDOWN) { |
|
464 |
+ rt->state = RTSP_STATE_IDLE; |
|
465 |
+ ret = rtsp_send_reply(s, RTSP_STATUS_OK, NULL , request.seq); |
|
466 |
+ return 0; |
|
467 |
+ } |
|
468 |
+ return ret; |
|
469 |
+} |
|
470 |
+ |
|
48 | 471 |
static int rtsp_read_play(AVFormatContext *s) |
49 | 472 |
{ |
50 | 473 |
RTSPState *rt = s->priv_data; |
... | ... |
@@ -157,6 +600,67 @@ int ff_rtsp_setup_input_streams(AVFormatContext *s, RTSPMessageHeader *reply) |
157 | 157 |
return 0; |
158 | 158 |
} |
159 | 159 |
|
160 |
+static int rtsp_listen(AVFormatContext *s) |
|
161 |
+{ |
|
162 |
+ RTSPState *rt = s->priv_data; |
|
163 |
+ char host[128], path[512], auth[128]; |
|
164 |
+ char uri[500]; |
|
165 |
+ int port; |
|
166 |
+ char tcpname[500]; |
|
167 |
+ unsigned char rbuf[4096]; |
|
168 |
+ unsigned char method[10]; |
|
169 |
+ int rbuflen = 0; |
|
170 |
+ int ret; |
|
171 |
+ enum RTSPMethod methodcode; |
|
172 |
+ |
|
173 |
+ /* extract hostname and port */ |
|
174 |
+ av_url_split(NULL, 0, auth, sizeof(auth), host, sizeof(host), &port, |
|
175 |
+ path, sizeof(path), s->filename); |
|
176 |
+ |
|
177 |
+ /* ff_url_join. No authorization by now (NULL) */ |
|
178 |
+ ff_url_join(rt->control_uri, sizeof(rt->control_uri), "rtsp", NULL, host, |
|
179 |
+ port, "%s", path); |
|
180 |
+ /* Create TCP connection */ |
|
181 |
+ ff_url_join(tcpname, sizeof(tcpname), "tcp", NULL, host, port, |
|
182 |
+ "?listen&listen_timeout=%d", rt->initial_timeout * 1000); |
|
183 |
+ |
|
184 |
+ if (ret = ffurl_open(&rt->rtsp_hd, tcpname, AVIO_FLAG_READ_WRITE, |
|
185 |
+ &s->interrupt_callback, NULL)) { |
|
186 |
+ av_log(s, AV_LOG_ERROR, "Unable to open RTSP for listening\n"); |
|
187 |
+ return ret; |
|
188 |
+ } |
|
189 |
+ rt->state = RTSP_STATE_IDLE; |
|
190 |
+ rt->rtsp_hd_out = rt->rtsp_hd; |
|
191 |
+ for (;;) { /* Wait for incoming RTSP messages */ |
|
192 |
+ ret = read_line(s, rbuf, sizeof(rbuf), &rbuflen); |
|
193 |
+ if (ret < 0) |
|
194 |
+ return ret; |
|
195 |
+ ret = parse_command_line(s, rbuf, rbuflen, uri, sizeof(uri), method, |
|
196 |
+ sizeof(method), &methodcode); |
|
197 |
+ if (ret) { |
|
198 |
+ av_log(s, AV_LOG_ERROR, "RTSP: Unexpected Command\n"); |
|
199 |
+ return ret; |
|
200 |
+ } |
|
201 |
+ |
|
202 |
+ if (methodcode == ANNOUNCE) { |
|
203 |
+ ret = rtsp_read_announce(s); |
|
204 |
+ rt->state = RTSP_STATE_PAUSED; |
|
205 |
+ } else if (methodcode == OPTIONS) { |
|
206 |
+ ret = rtsp_read_options(s); |
|
207 |
+ } else if (methodcode == RECORD) { |
|
208 |
+ ret = rtsp_read_record(s); |
|
209 |
+ if (!ret) |
|
210 |
+ return 0; // We are ready for streaming |
|
211 |
+ } else if (methodcode == SETUP) |
|
212 |
+ ret = rtsp_read_setup(s, host, uri); |
|
213 |
+ if (ret) { |
|
214 |
+ ffurl_close(rt->rtsp_hd); |
|
215 |
+ return AVERROR_INVALIDDATA; |
|
216 |
+ } |
|
217 |
+ } |
|
218 |
+ return 0; |
|
219 |
+} |
|
220 |
+ |
|
160 | 221 |
static int rtsp_probe(AVProbeData *p) |
161 | 222 |
{ |
162 | 223 |
if (av_strstart(p->filename, "rtsp:", NULL)) |
... | ... |
@@ -169,23 +673,32 @@ static int rtsp_read_header(AVFormatContext *s) |
169 | 169 |
RTSPState *rt = s->priv_data; |
170 | 170 |
int ret; |
171 | 171 |
|
172 |
- ret = ff_rtsp_connect(s); |
|
173 |
- if (ret) |
|
174 |
- return ret; |
|
175 |
- |
|
176 |
- rt->real_setup_cache = !s->nb_streams ? NULL : |
|
177 |
- av_mallocz(2 * s->nb_streams * sizeof(*rt->real_setup_cache)); |
|
178 |
- if (!rt->real_setup_cache && s->nb_streams) |
|
179 |
- return AVERROR(ENOMEM); |
|
180 |
- rt->real_setup = rt->real_setup_cache + s->nb_streams; |
|
172 |
+ if (rt->initial_timeout > 0) |
|
173 |
+ rt->rtsp_flags |= RTSP_FLAG_LISTEN; |
|
181 | 174 |
|
182 |
- if (rt->initial_pause) { |
|
183 |
- /* do not start immediately */ |
|
175 |
+ if (rt->rtsp_flags & RTSP_FLAG_LISTEN) { |
|
176 |
+ ret = rtsp_listen(s); |
|
177 |
+ if (ret) |
|
178 |
+ return ret; |
|
184 | 179 |
} else { |
185 |
- if (rtsp_read_play(s) < 0) { |
|
186 |
- ff_rtsp_close_streams(s); |
|
187 |
- ff_rtsp_close_connections(s); |
|
188 |
- return AVERROR_INVALIDDATA; |
|
180 |
+ ret = ff_rtsp_connect(s); |
|
181 |
+ if (ret) |
|
182 |
+ return ret; |
|
183 |
+ |
|
184 |
+ rt->real_setup_cache = !s->nb_streams ? NULL : |
|
185 |
+ av_mallocz(2 * s->nb_streams * sizeof(*rt->real_setup_cache)); |
|
186 |
+ if (!rt->real_setup_cache && s->nb_streams) |
|
187 |
+ return AVERROR(ENOMEM); |
|
188 |
+ rt->real_setup = rt->real_setup_cache + s->nb_streams; |
|
189 |
+ |
|
190 |
+ if (rt->initial_pause) { |
|
191 |
+ /* do not start immediately */ |
|
192 |
+ } else { |
|
193 |
+ if (rtsp_read_play(s) < 0) { |
|
194 |
+ ff_rtsp_close_streams(s); |
|
195 |
+ ff_rtsp_close_connections(s); |
|
196 |
+ return AVERROR_INVALIDDATA; |
|
197 |
+ } |
|
189 | 198 |
} |
190 | 199 |
} |
191 | 200 |
|
... | ... |
@@ -349,20 +862,22 @@ retry: |
349 | 349 |
} |
350 | 350 |
rt->packets++; |
351 | 351 |
|
352 |
- /* send dummy request to keep TCP connection alive */ |
|
353 |
- if ((av_gettime() - rt->last_cmd_time) / 1000000 >= rt->timeout / 2 || |
|
354 |
- rt->auth_state.stale) { |
|
355 |
- if (rt->server_type == RTSP_SERVER_WMS || |
|
356 |
- (rt->server_type != RTSP_SERVER_REAL && |
|
357 |
- rt->get_parameter_supported)) { |
|
358 |
- ff_rtsp_send_cmd_async(s, "GET_PARAMETER", rt->control_uri, NULL); |
|
359 |
- } else { |
|
360 |
- ff_rtsp_send_cmd_async(s, "OPTIONS", "*", NULL); |
|
352 |
+ if (!(rt->rtsp_flags & RTSP_FLAG_LISTEN)) { |
|
353 |
+ /* send dummy request to keep TCP connection alive */ |
|
354 |
+ if ((av_gettime() - rt->last_cmd_time) / 1000000 >= rt->timeout / 2 || |
|
355 |
+ rt->auth_state.stale) { |
|
356 |
+ if (rt->server_type == RTSP_SERVER_WMS || |
|
357 |
+ (rt->server_type != RTSP_SERVER_REAL && |
|
358 |
+ rt->get_parameter_supported)) { |
|
359 |
+ ff_rtsp_send_cmd_async(s, "GET_PARAMETER", rt->control_uri, NULL); |
|
360 |
+ } else { |
|
361 |
+ ff_rtsp_send_cmd_async(s, "OPTIONS", "*", NULL); |
|
362 |
+ } |
|
363 |
+ /* The stale flag should be reset when creating the auth response in |
|
364 |
+ * ff_rtsp_send_cmd_async, but reset it here just in case we never |
|
365 |
+ * called the auth code (if we didn't have any credentials set). */ |
|
366 |
+ rt->auth_state.stale = 0; |
|
361 | 367 |
} |
362 |
- /* The stale flag should be reset when creating the auth response in |
|
363 |
- * ff_rtsp_send_cmd_async, but reset it here just in case we never |
|
364 |
- * called the auth code (if we didn't have any credentials set). */ |
|
365 |
- rt->auth_state.stale = 0; |
|
366 | 368 |
} |
367 | 369 |
|
368 | 370 |
return 0; |
... | ... |
@@ -30,7 +30,7 @@ |
30 | 30 |
#include "libavutil/avutil.h" |
31 | 31 |
|
32 | 32 |
#define LIBAVFORMAT_VERSION_MAJOR 54 |
33 |
-#define LIBAVFORMAT_VERSION_MINOR 6 |
|
33 |
+#define LIBAVFORMAT_VERSION_MINOR 7 |
|
34 | 34 |
#define LIBAVFORMAT_VERSION_MICRO 0 |
35 | 35 |
|
36 | 36 |
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ |