mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2024-11-21 10:55:51 +02:00
rtsp: Add listen mode
This makes the RTSP demuxer act as a server, listening for an incoming connection. Signed-off-by: Martin Storsjö <martin@martin.st>
This commit is contained in:
parent
6e71c1202b
commit
a8ad6ffafe
@ -31,6 +31,7 @@ version <next>:
|
||||
- join audio filter
|
||||
- audio channel mapping filter
|
||||
- Microsoft ATC Screen decoder
|
||||
- RTSP listen mode
|
||||
|
||||
|
||||
version 0.8:
|
||||
|
@ -347,6 +347,8 @@ Flags for @code{rtsp_flags}:
|
||||
@table @option
|
||||
@item filter_src
|
||||
Accept packets only from negotiated peer address and port.
|
||||
@item listen
|
||||
Act as a server, listening for an incoming connection.
|
||||
@end table
|
||||
|
||||
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:
|
||||
avconv -re -i @var{input} -f rtsp -muxdelay 0.1 rtsp://server/live.sdp
|
||||
@end example
|
||||
|
||||
To receive a stream in realtime:
|
||||
|
||||
@example
|
||||
avconv -rtsp_flags listen -i rtsp://ownaddress/live.sdp @var{output}
|
||||
@end example
|
||||
|
||||
@section sap
|
||||
|
||||
Session Announcement Protocol (RFC 2974). This is not technically a
|
||||
|
@ -63,7 +63,8 @@
|
||||
|
||||
#define RTSP_FLAG_OPTS(name, longname) \
|
||||
{ name, longname, OFFSET(rtsp_flags), AV_OPT_TYPE_FLAGS, {0}, INT_MIN, INT_MAX, DEC, "rtsp_flags" }, \
|
||||
{ "filter_src", "Only receive packets from the negotiated peer IP", 0, AV_OPT_TYPE_CONST, {RTSP_FLAG_FILTER_SRC}, 0, 0, DEC, "rtsp_flags" }
|
||||
{ "filter_src", "Only receive packets from the negotiated peer IP", 0, AV_OPT_TYPE_CONST, {RTSP_FLAG_FILTER_SRC}, 0, 0, DEC, "rtsp_flags" }, \
|
||||
{ "listen", "Wait for incoming connections", 0, AV_OPT_TYPE_CONST, {RTSP_FLAG_LISTEN}, 0, 0, DEC, "rtsp_flags" }
|
||||
|
||||
#define RTSP_MEDIATYPE_OPTS(name, longname) \
|
||||
{ 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[] = {
|
||||
RTSP_MEDIATYPE_OPTS("allowed_media_types", "Media types to accept from the server"),
|
||||
{ "min_port", "Minimum local UDP port", OFFSET(rtp_port_min), AV_OPT_TYPE_INT, {RTSP_RTP_PORT_MIN}, 0, 65535, DEC|ENC },
|
||||
{ "max_port", "Maximum local UDP port", OFFSET(rtp_port_max), AV_OPT_TYPE_INT, {RTSP_RTP_PORT_MAX}, 0, 65535, DEC|ENC },
|
||||
{ "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 },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
@ -1714,14 +1716,24 @@ static int udp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st,
|
||||
}
|
||||
#if CONFIG_RTSP_DEMUXER
|
||||
if (tcp_fd != -1 && p[0].revents & POLLIN) {
|
||||
RTSPMessageHeader reply;
|
||||
|
||||
ret = ff_rtsp_read_reply(s, &reply, NULL, 0, NULL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
/* XXX: parse message */
|
||||
if (rt->state != RTSP_STATE_STREAMING)
|
||||
return 0;
|
||||
if (rt->rtsp_flags & RTSP_FLAG_LISTEN) {
|
||||
if (rt->state == RTSP_STATE_STREAMING) {
|
||||
if (!ff_rtsp_parse_streaming_commands(s))
|
||||
return AVERROR_EOF;
|
||||
else
|
||||
av_log(s, AV_LOG_WARNING,
|
||||
"Unable to answer to TEARDOWN\n");
|
||||
} else
|
||||
return 0;
|
||||
} else {
|
||||
RTSPMessageHeader reply;
|
||||
ret = ff_rtsp_read_reply(s, &reply, NULL, 0, NULL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
/* XXX: parse message */
|
||||
if (rt->state != RTSP_STATE_STREAMING)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else if (n == 0 && ++timeout_cnt >= MAX_TIMEOUTS) {
|
||||
|
@ -372,11 +372,17 @@ typedef struct RTSPState {
|
||||
* Minimum and maximum local UDP ports.
|
||||
*/
|
||||
int rtp_port_min, rtp_port_max;
|
||||
|
||||
/**
|
||||
* Timeout to wait for incoming connections.
|
||||
*/
|
||||
int initial_timeout;
|
||||
} RTSPState;
|
||||
|
||||
#define RTSP_FLAG_FILTER_SRC 0x1 /**< Filter incoming UDP packets -
|
||||
receive packets only from the right
|
||||
source address and port. */
|
||||
#define RTSP_FLAG_LISTEN 0x2 /**< Wait for incoming connections. */
|
||||
|
||||
/**
|
||||
* Describe a single stream, as identified by a single m= line block in the
|
||||
@ -528,6 +534,12 @@ int ff_rtsp_setup_input_streams(AVFormatContext *s, RTSPMessageHeader *reply);
|
||||
*/
|
||||
int ff_rtsp_setup_output_streams(AVFormatContext *s, const char *addr);
|
||||
|
||||
/**
|
||||
* Parse RTSP commands (OPTIONS, PAUSE and TEARDOWN) during streaming in
|
||||
* listen mode.
|
||||
*/
|
||||
int ff_rtsp_parse_streaming_commands(AVFormatContext *s);
|
||||
|
||||
/**
|
||||
* Parse an SDP description of streams by populating an RTSPState struct
|
||||
* within the AVFormatContext; also allocate the RTP streams and the
|
||||
|
@ -37,4 +37,18 @@ RTSP_STATUS_SERVICE =503, /**< Service Unavailable */
|
||||
RTSP_STATUS_VERSION =505, /**< RTSP Version not supported */
|
||||
};
|
||||
|
||||
enum RTSPMethod {
|
||||
DESCRIBE,
|
||||
ANNOUNCE,
|
||||
OPTIONS,
|
||||
SETUP,
|
||||
PLAY,
|
||||
PAUSE,
|
||||
TEARDOWN,
|
||||
GET_PARAMETER,
|
||||
SET_PARAMETER,
|
||||
REDIRECT,
|
||||
RECORD,
|
||||
UNKNOWN = -1,
|
||||
};
|
||||
#endif /* AVFORMAT_RTSPCODES_H */
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/intreadwrite.h"
|
||||
#include "libavutil/mathematics.h"
|
||||
#include "libavutil/random_seed.h"
|
||||
#include "avformat.h"
|
||||
|
||||
#include "internal.h"
|
||||
@ -31,11 +32,30 @@
|
||||
#include "rdt.h"
|
||||
#include "url.h"
|
||||
|
||||
static const struct RTSPStatusMessage {
|
||||
enum RTSPStatusCode code;
|
||||
const char *message;
|
||||
} status_messages[] = {
|
||||
{ RTSP_STATUS_OK, "OK" },
|
||||
{ RTSP_STATUS_METHOD, "Method Not Allowed" },
|
||||
{ RTSP_STATUS_BANDWIDTH, "Not Enough Bandwidth" },
|
||||
{ RTSP_STATUS_SESSION, "Session Not Found" },
|
||||
{ RTSP_STATUS_STATE, "Method Not Valid in This State" },
|
||||
{ RTSP_STATUS_AGGREGATE, "Aggregate operation not allowed" },
|
||||
{ RTSP_STATUS_ONLY_AGGREGATE, "Only aggregate operation allowed" },
|
||||
{ RTSP_STATUS_TRANSPORT, "Unsupported transport" },
|
||||
{ RTSP_STATUS_INTERNAL, "Internal Server Error" },
|
||||
{ RTSP_STATUS_SERVICE, "Service Unavailable" },
|
||||
{ RTSP_STATUS_VERSION, "RTSP Version not supported" },
|
||||
{ 0, "NULL" }
|
||||
};
|
||||
|
||||
static int rtsp_read_close(AVFormatContext *s)
|
||||
{
|
||||
RTSPState *rt = s->priv_data;
|
||||
|
||||
ff_rtsp_send_cmd_async(s, "TEARDOWN", rt->control_uri, NULL);
|
||||
if (!(rt->rtsp_flags & RTSP_FLAG_LISTEN))
|
||||
ff_rtsp_send_cmd_async(s, "TEARDOWN", rt->control_uri, NULL);
|
||||
|
||||
ff_rtsp_close_streams(s);
|
||||
ff_rtsp_close_connections(s);
|
||||
@ -45,6 +65,429 @@ static int rtsp_read_close(AVFormatContext *s)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int read_line(AVFormatContext *s, char *rbuf, const int rbufsize,
|
||||
int *rbuflen)
|
||||
{
|
||||
RTSPState *rt = s->priv_data;
|
||||
int idx = 0;
|
||||
int ret = 0;
|
||||
*rbuflen = 0;
|
||||
|
||||
do {
|
||||
ret = ffurl_read_complete(rt->rtsp_hd, rbuf + idx, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (rbuf[idx] == '\r') {
|
||||
/* Ignore */
|
||||
} else if (rbuf[idx] == '\n') {
|
||||
rbuf[idx] = '\0';
|
||||
*rbuflen = idx;
|
||||
return 0;
|
||||
} else
|
||||
idx++;
|
||||
} while (idx < rbufsize);
|
||||
av_log(s, AV_LOG_ERROR, "Message too long\n");
|
||||
return AVERROR(EIO);
|
||||
}
|
||||
|
||||
static int rtsp_send_reply(AVFormatContext *s, enum RTSPStatusCode code,
|
||||
const char *extracontent, uint16_t seq)
|
||||
{
|
||||
RTSPState *rt = s->priv_data;
|
||||
char message[4096];
|
||||
int index = 0;
|
||||
while (status_messages[index].code) {
|
||||
if (status_messages[index].code == code) {
|
||||
snprintf(message, sizeof(message), "RTSP/1.0 %d %s\r\n",
|
||||
code, status_messages[index].message);
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
if (!status_messages[index].code)
|
||||
return AVERROR(EINVAL);
|
||||
av_strlcatf(message, sizeof(message), "CSeq: %d\r\n", seq);
|
||||
av_strlcatf(message, sizeof(message), "Server: %s\r\n", LIBAVFORMAT_IDENT);
|
||||
if (extracontent)
|
||||
av_strlcat(message, extracontent, sizeof(message));
|
||||
av_strlcat(message, "\r\n", sizeof(message));
|
||||
av_dlog(s, "Sending response:\n%s", message);
|
||||
ffurl_write(rt->rtsp_hd, message, strlen(message));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int check_sessionid(AVFormatContext *s,
|
||||
RTSPMessageHeader *request)
|
||||
{
|
||||
RTSPState *rt = s->priv_data;
|
||||
unsigned char *session_id = rt->session_id;
|
||||
if (!session_id[0]) {
|
||||
av_log(s, AV_LOG_WARNING, "There is no session-id at the moment\n");
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(session_id, request->session_id)) {
|
||||
av_log(s, AV_LOG_ERROR, "Unexpected session-id %s\n",
|
||||
request->session_id);
|
||||
rtsp_send_reply(s, RTSP_STATUS_SESSION, NULL, request->seq);
|
||||
return AVERROR_STREAM_NOT_FOUND;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int rtsp_read_request(AVFormatContext *s,
|
||||
RTSPMessageHeader *request,
|
||||
const char *method)
|
||||
{
|
||||
RTSPState *rt = s->priv_data;
|
||||
char rbuf[1024];
|
||||
int rbuflen, ret;
|
||||
do {
|
||||
ret = read_line(s, rbuf, sizeof(rbuf), &rbuflen);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (rbuflen > 1) {
|
||||
av_dlog(s, "Parsing[%d]: %s\n", rbuflen, rbuf);
|
||||
ff_rtsp_parse_line(request, rbuf, rt, method);
|
||||
}
|
||||
} while (rbuflen > 0);
|
||||
if (request->seq != rt->seq + 1) {
|
||||
av_log(s, AV_LOG_ERROR, "Unexpected Sequence number %d\n",
|
||||
request->seq);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
if (rt->session_id[0] && strcmp(method, "OPTIONS")) {
|
||||
ret = check_sessionid(s, request);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtsp_read_announce(AVFormatContext *s)
|
||||
{
|
||||
RTSPState *rt = s->priv_data;
|
||||
RTSPMessageHeader request = { 0 };
|
||||
char sdp[4096];
|
||||
int ret;
|
||||
|
||||
ret = rtsp_read_request(s, &request, "ANNOUNCE");
|
||||
if (ret)
|
||||
return ret;
|
||||
rt->seq++;
|
||||
if (strcmp(request.content_type, "application/sdp")) {
|
||||
av_log(s, AV_LOG_ERROR, "Unexpected content type %s\n",
|
||||
request.content_type);
|
||||
rtsp_send_reply(s, RTSP_STATUS_SERVICE, NULL, request.seq);
|
||||
return AVERROR_OPTION_NOT_FOUND;
|
||||
}
|
||||
if (request.content_length && request.content_length < sizeof(sdp) - 1) {
|
||||
/* Read SDP */
|
||||
if (ffurl_read_complete(rt->rtsp_hd, sdp, request.content_length)
|
||||
< request.content_length) {
|
||||
av_log(s, AV_LOG_ERROR,
|
||||
"Unable to get complete SDP Description in ANNOUNCE\n");
|
||||
rtsp_send_reply(s, RTSP_STATUS_INTERNAL, NULL, request.seq);
|
||||
return AVERROR(EIO);
|
||||
}
|
||||
sdp[request.content_length] = '\0';
|
||||
av_log(s, AV_LOG_VERBOSE, "SDP: %s\n", sdp);
|
||||
ret = ff_sdp_parse(s, sdp);
|
||||
if (ret)
|
||||
return ret;
|
||||
rtsp_send_reply(s, RTSP_STATUS_OK, NULL, request.seq);
|
||||
return 0;
|
||||
}
|
||||
av_log(s, AV_LOG_ERROR,
|
||||
"Content-Length header value exceeds sdp allocated buffer (4KB)\n");
|
||||
rtsp_send_reply(s, RTSP_STATUS_INTERNAL,
|
||||
"Content-Length exceeds buffer size", request.seq);
|
||||
return AVERROR(EIO);
|
||||
}
|
||||
|
||||
static int rtsp_read_options(AVFormatContext *s)
|
||||
{
|
||||
RTSPState *rt = s->priv_data;
|
||||
RTSPMessageHeader request = { 0 };
|
||||
int ret = 0;
|
||||
|
||||
/* Parsing headers */
|
||||
ret = rtsp_read_request(s, &request, "OPTIONS");
|
||||
if (ret)
|
||||
return ret;
|
||||
rt->seq++;
|
||||
/* Send Reply */
|
||||
rtsp_send_reply(s, RTSP_STATUS_OK,
|
||||
"Public: ANNOUNCE, PAUSE, SETUP, TEARDOWN, RECORD\r\n",
|
||||
request.seq);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtsp_read_setup(AVFormatContext *s, char* host, char *controlurl)
|
||||
{
|
||||
RTSPState *rt = s->priv_data;
|
||||
RTSPMessageHeader request = { 0 };
|
||||
int ret = 0;
|
||||
char url[1024];
|
||||
RTSPStream *rtsp_st;
|
||||
char responseheaders[1024];
|
||||
int localport = -1;
|
||||
int transportidx = 0;
|
||||
int streamid = 0;
|
||||
|
||||
ret = rtsp_read_request(s, &request, "SETUP");
|
||||
if (ret)
|
||||
return ret;
|
||||
rt->seq++;
|
||||
if (!request.nb_transports) {
|
||||
av_log(s, AV_LOG_ERROR, "No transport defined in SETUP\n");
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
for (transportidx = 0; transportidx < request.nb_transports;
|
||||
transportidx++) {
|
||||
if (!request.transports[transportidx].mode_record ||
|
||||
(request.transports[transportidx].lower_transport !=
|
||||
RTSP_LOWER_TRANSPORT_UDP &&
|
||||
request.transports[transportidx].lower_transport !=
|
||||
RTSP_LOWER_TRANSPORT_TCP)) {
|
||||
av_log(s, AV_LOG_ERROR, "mode=record/receive not set or transport"
|
||||
" protocol not supported (yet)\n");
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
}
|
||||
if (request.nb_transports > 1)
|
||||
av_log(s, AV_LOG_WARNING, "More than one transport not supported, "
|
||||
"using first of all\n");
|
||||
for (streamid = 0; streamid < rt->nb_rtsp_streams; streamid++) {
|
||||
if (!strcmp(rt->rtsp_streams[streamid]->control_url,
|
||||
controlurl))
|
||||
break;
|
||||
}
|
||||
if (streamid == rt->nb_rtsp_streams) {
|
||||
av_log(s, AV_LOG_ERROR, "Unable to find requested track\n");
|
||||
return AVERROR_STREAM_NOT_FOUND;
|
||||
}
|
||||
rtsp_st = rt->rtsp_streams[streamid];
|
||||
localport = rt->rtp_port_min;
|
||||
|
||||
if (request.transports[0].lower_transport == RTSP_LOWER_TRANSPORT_TCP) {
|
||||
rt->lower_transport = RTSP_LOWER_TRANSPORT_TCP;
|
||||
if ((ret = ff_rtsp_open_transport_ctx(s, rtsp_st))) {
|
||||
rtsp_send_reply(s, RTSP_STATUS_TRANSPORT, NULL, request.seq);
|
||||
return ret;
|
||||
}
|
||||
rtsp_st->interleaved_min = request.transports[0].interleaved_min;
|
||||
rtsp_st->interleaved_max = request.transports[0].interleaved_max;
|
||||
snprintf(responseheaders, sizeof(responseheaders), "Transport: "
|
||||
"RTP/AVP/TCP;unicast;mode=receive;interleaved=%d-%d"
|
||||
"\r\n", request.transports[0].interleaved_min,
|
||||
request.transports[0].interleaved_max);
|
||||
} else {
|
||||
do {
|
||||
ff_url_join(url, sizeof(url), "rtp", NULL, host, localport, NULL);
|
||||
av_dlog(s, "Opening: %s", url);
|
||||
ret = ffurl_open(&rtsp_st->rtp_handle, url, AVIO_FLAG_READ_WRITE,
|
||||
&s->interrupt_callback, NULL);
|
||||
if (ret)
|
||||
localport += 2;
|
||||
} while (ret || localport > rt->rtp_port_max);
|
||||
if (localport > rt->rtp_port_max) {
|
||||
rtsp_send_reply(s, RTSP_STATUS_TRANSPORT, NULL, request.seq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
av_dlog(s, "Listening on: %d",
|
||||
ff_rtp_get_local_rtp_port(rtsp_st->rtp_handle));
|
||||
if ((ret = ff_rtsp_open_transport_ctx(s, rtsp_st))) {
|
||||
rtsp_send_reply(s, RTSP_STATUS_TRANSPORT, NULL, request.seq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
localport = ff_rtp_get_local_rtp_port(rtsp_st->rtp_handle);
|
||||
snprintf(responseheaders, sizeof(responseheaders), "Transport: "
|
||||
"RTP/AVP/UDP;unicast;mode=receive;source=%s;"
|
||||
"client_port=%d-%d;server_port=%d-%d\r\n",
|
||||
host, request.transports[0].client_port_min,
|
||||
request.transports[0].client_port_max, localport,
|
||||
localport + 1);
|
||||
}
|
||||
|
||||
/* Establish sessionid if not previously set */
|
||||
/* Put this in a function? */
|
||||
/* RFC 2326: session id must be at least 8 digits */
|
||||
while (strlen(rt->session_id) < 8)
|
||||
av_strlcatf(rt->session_id, 512, "%u", av_get_random_seed());
|
||||
|
||||
av_strlcatf(responseheaders, sizeof(responseheaders), "Session: %s\r\n",
|
||||
rt->session_id);
|
||||
/* Send Reply */
|
||||
rtsp_send_reply(s, RTSP_STATUS_OK, responseheaders, request.seq);
|
||||
|
||||
rt->state = RTSP_STATE_PAUSED;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtsp_read_record(AVFormatContext *s)
|
||||
{
|
||||
RTSPState *rt = s->priv_data;
|
||||
RTSPMessageHeader request = { 0 };
|
||||
int ret = 0;
|
||||
char responseheaders[1024];
|
||||
|
||||
ret = rtsp_read_request(s, &request, "RECORD");
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = check_sessionid(s, &request);
|
||||
if (ret)
|
||||
return ret;
|
||||
rt->seq++;
|
||||
snprintf(responseheaders, sizeof(responseheaders), "Session: %s\r\n",
|
||||
rt->session_id);
|
||||
rtsp_send_reply(s, RTSP_STATUS_OK, responseheaders, request.seq);
|
||||
|
||||
rt->state = RTSP_STATE_STREAMING;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int parse_command_line(AVFormatContext *s, const char *line,
|
||||
int linelen, char *uri, int urisize,
|
||||
char *method, int methodsize,
|
||||
enum RTSPMethod *methodcode)
|
||||
{
|
||||
RTSPState *rt = s->priv_data;
|
||||
const char *linept, *searchlinept;
|
||||
linept = strchr(line, ' ');
|
||||
if (linept - line > methodsize - 1) {
|
||||
av_log(s, AV_LOG_ERROR, "Method string too long\n");
|
||||
return AVERROR(EIO);
|
||||
}
|
||||
memcpy(method, line, linept - line);
|
||||
method[linept - line] = '\0';
|
||||
linept++;
|
||||
if (!strcmp(method, "ANNOUNCE"))
|
||||
*methodcode = ANNOUNCE;
|
||||
else if (!strcmp(method, "OPTIONS"))
|
||||
*methodcode = OPTIONS;
|
||||
else if (!strcmp(method, "RECORD"))
|
||||
*methodcode = RECORD;
|
||||
else if (!strcmp(method, "SETUP"))
|
||||
*methodcode = SETUP;
|
||||
else if (!strcmp(method, "PAUSE"))
|
||||
*methodcode = PAUSE;
|
||||
else if (!strcmp(method, "TEARDOWN"))
|
||||
*methodcode = TEARDOWN;
|
||||
else
|
||||
*methodcode = UNKNOWN;
|
||||
/* Check method with the state */
|
||||
if (rt->state == RTSP_STATE_IDLE) {
|
||||
if ((*methodcode != ANNOUNCE) && (*methodcode != OPTIONS)) {
|
||||
av_log(s, AV_LOG_ERROR, "Unexpected command in Idle State %s\n",
|
||||
line);
|
||||
return AVERROR_PROTOCOL_NOT_FOUND;
|
||||
}
|
||||
} else if (rt->state == RTSP_STATE_PAUSED) {
|
||||
if ((*methodcode != OPTIONS) && (*methodcode != RECORD)
|
||||
&& (*methodcode != SETUP)) {
|
||||
av_log(s, AV_LOG_ERROR, "Unexpected command in Paused State %s\n",
|
||||
line);
|
||||
return AVERROR_PROTOCOL_NOT_FOUND;
|
||||
}
|
||||
} else if (rt->state == RTSP_STATE_STREAMING) {
|
||||
if ((*methodcode != PAUSE) && (*methodcode != OPTIONS)
|
||||
&& (*methodcode != TEARDOWN)) {
|
||||
av_log(s, AV_LOG_ERROR, "Unexpected command in Streaming State"
|
||||
" %s\n", line);
|
||||
return AVERROR_PROTOCOL_NOT_FOUND;
|
||||
}
|
||||
} else {
|
||||
av_log(s, AV_LOG_ERROR, "Unexpected State [%d]\n", rt->state);
|
||||
return AVERROR_BUG;
|
||||
}
|
||||
|
||||
searchlinept = strchr(linept, ' ');
|
||||
if (searchlinept == NULL) {
|
||||
av_log(s, AV_LOG_ERROR, "Error parsing message URI\n");
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
if (searchlinept - linept > urisize - 1) {
|
||||
av_log(s, AV_LOG_ERROR, "uri string length exceeded buffer size\n");
|
||||
return AVERROR(EIO);
|
||||
}
|
||||
memcpy(uri, linept, searchlinept - linept);
|
||||
uri[searchlinept - linept] = '\0';
|
||||
if (strcmp(rt->control_uri, uri)) {
|
||||
char host[128], path[512], auth[128];
|
||||
int port;
|
||||
char ctl_host[128], ctl_path[512], ctl_auth[128];
|
||||
int ctl_port;
|
||||
av_url_split(NULL, 0, auth, sizeof(auth), host, sizeof(host), &port,
|
||||
path, sizeof(path), uri);
|
||||
av_url_split(NULL, 0, ctl_auth, sizeof(ctl_auth), ctl_host,
|
||||
sizeof(ctl_host), &ctl_port, ctl_path, sizeof(ctl_path),
|
||||
rt->control_uri);
|
||||
if (strcmp(host, ctl_host))
|
||||
av_log(s, AV_LOG_INFO, "Host %s differs from expected %s\n",
|
||||
host, ctl_host);
|
||||
if (strcmp(path, ctl_path) && *methodcode != SETUP)
|
||||
av_log(s, AV_LOG_WARNING, "WARNING: Path %s differs from expected"
|
||||
" %s\n", path, ctl_path);
|
||||
if (*methodcode == ANNOUNCE) {
|
||||
av_log(s, AV_LOG_INFO,
|
||||
"Updating control URI to %s\n", uri);
|
||||
strcpy(rt->control_uri, uri);
|
||||
}
|
||||
}
|
||||
|
||||
linept = searchlinept + 1;
|
||||
if (!av_strstart(linept, "RTSP/1.0", NULL)) {
|
||||
av_log(s, AV_LOG_ERROR, "Error parsing protocol or version\n");
|
||||
return AVERROR_PROTOCOL_NOT_FOUND;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ff_rtsp_parse_streaming_commands(AVFormatContext *s)
|
||||
{
|
||||
RTSPState *rt = s->priv_data;
|
||||
unsigned char rbuf[4096];
|
||||
unsigned char method[10];
|
||||
char uri[500];
|
||||
int ret;
|
||||
int rbuflen = 0;
|
||||
RTSPMessageHeader request = { 0 };
|
||||
enum RTSPMethod methodcode;
|
||||
|
||||
ret = read_line(s, rbuf, sizeof(rbuf), &rbuflen);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = parse_command_line(s, rbuf, rbuflen, uri, sizeof(uri), method,
|
||||
sizeof(method), &methodcode);
|
||||
if (ret) {
|
||||
av_log(s, AV_LOG_ERROR, "RTSP: Unexpected Command\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = rtsp_read_request(s, &request, method);
|
||||
if (ret)
|
||||
return ret;
|
||||
rt->seq++;
|
||||
if (methodcode == PAUSE) {
|
||||
rt->state = RTSP_STATE_PAUSED;
|
||||
ret = rtsp_send_reply(s, RTSP_STATUS_OK, NULL , request.seq);
|
||||
// TODO: Missing date header in response
|
||||
} else if (methodcode == OPTIONS) {
|
||||
ret = rtsp_send_reply(s, RTSP_STATUS_OK,
|
||||
"Public: ANNOUNCE, PAUSE, SETUP, TEARDOWN, "
|
||||
"RECORD\r\n", request.seq);
|
||||
} else if (methodcode == TEARDOWN) {
|
||||
rt->state = RTSP_STATE_IDLE;
|
||||
ret = rtsp_send_reply(s, RTSP_STATUS_OK, NULL , request.seq);
|
||||
return 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rtsp_read_play(AVFormatContext *s)
|
||||
{
|
||||
RTSPState *rt = s->priv_data;
|
||||
@ -157,6 +600,67 @@ int ff_rtsp_setup_input_streams(AVFormatContext *s, RTSPMessageHeader *reply)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtsp_listen(AVFormatContext *s)
|
||||
{
|
||||
RTSPState *rt = s->priv_data;
|
||||
char host[128], path[512], auth[128];
|
||||
char uri[500];
|
||||
int port;
|
||||
char tcpname[500];
|
||||
unsigned char rbuf[4096];
|
||||
unsigned char method[10];
|
||||
int rbuflen = 0;
|
||||
int ret;
|
||||
enum RTSPMethod methodcode;
|
||||
|
||||
/* extract hostname and port */
|
||||
av_url_split(NULL, 0, auth, sizeof(auth), host, sizeof(host), &port,
|
||||
path, sizeof(path), s->filename);
|
||||
|
||||
/* ff_url_join. No authorization by now (NULL) */
|
||||
ff_url_join(rt->control_uri, sizeof(rt->control_uri), "rtsp", NULL, host,
|
||||
port, "%s", path);
|
||||
/* Create TCP connection */
|
||||
ff_url_join(tcpname, sizeof(tcpname), "tcp", NULL, host, port,
|
||||
"?listen&listen_timeout=%d", rt->initial_timeout * 1000);
|
||||
|
||||
if (ret = ffurl_open(&rt->rtsp_hd, tcpname, AVIO_FLAG_READ_WRITE,
|
||||
&s->interrupt_callback, NULL)) {
|
||||
av_log(s, AV_LOG_ERROR, "Unable to open RTSP for listening\n");
|
||||
return ret;
|
||||
}
|
||||
rt->state = RTSP_STATE_IDLE;
|
||||
rt->rtsp_hd_out = rt->rtsp_hd;
|
||||
for (;;) { /* Wait for incoming RTSP messages */
|
||||
ret = read_line(s, rbuf, sizeof(rbuf), &rbuflen);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = parse_command_line(s, rbuf, rbuflen, uri, sizeof(uri), method,
|
||||
sizeof(method), &methodcode);
|
||||
if (ret) {
|
||||
av_log(s, AV_LOG_ERROR, "RTSP: Unexpected Command\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (methodcode == ANNOUNCE) {
|
||||
ret = rtsp_read_announce(s);
|
||||
rt->state = RTSP_STATE_PAUSED;
|
||||
} else if (methodcode == OPTIONS) {
|
||||
ret = rtsp_read_options(s);
|
||||
} else if (methodcode == RECORD) {
|
||||
ret = rtsp_read_record(s);
|
||||
if (!ret)
|
||||
return 0; // We are ready for streaming
|
||||
} else if (methodcode == SETUP)
|
||||
ret = rtsp_read_setup(s, host, uri);
|
||||
if (ret) {
|
||||
ffurl_close(rt->rtsp_hd);
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtsp_probe(AVProbeData *p)
|
||||
{
|
||||
if (av_strstart(p->filename, "rtsp:", NULL))
|
||||
@ -169,23 +673,32 @@ static int rtsp_read_header(AVFormatContext *s)
|
||||
RTSPState *rt = s->priv_data;
|
||||
int ret;
|
||||
|
||||
ret = ff_rtsp_connect(s);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (rt->initial_timeout > 0)
|
||||
rt->rtsp_flags |= RTSP_FLAG_LISTEN;
|
||||
|
||||
rt->real_setup_cache = !s->nb_streams ? NULL :
|
||||
av_mallocz(2 * s->nb_streams * sizeof(*rt->real_setup_cache));
|
||||
if (!rt->real_setup_cache && s->nb_streams)
|
||||
return AVERROR(ENOMEM);
|
||||
rt->real_setup = rt->real_setup_cache + s->nb_streams;
|
||||
|
||||
if (rt->initial_pause) {
|
||||
/* do not start immediately */
|
||||
if (rt->rtsp_flags & RTSP_FLAG_LISTEN) {
|
||||
ret = rtsp_listen(s);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
if (rtsp_read_play(s) < 0) {
|
||||
ff_rtsp_close_streams(s);
|
||||
ff_rtsp_close_connections(s);
|
||||
return AVERROR_INVALIDDATA;
|
||||
ret = ff_rtsp_connect(s);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
rt->real_setup_cache = !s->nb_streams ? NULL :
|
||||
av_mallocz(2 * s->nb_streams * sizeof(*rt->real_setup_cache));
|
||||
if (!rt->real_setup_cache && s->nb_streams)
|
||||
return AVERROR(ENOMEM);
|
||||
rt->real_setup = rt->real_setup_cache + s->nb_streams;
|
||||
|
||||
if (rt->initial_pause) {
|
||||
/* do not start immediately */
|
||||
} else {
|
||||
if (rtsp_read_play(s) < 0) {
|
||||
ff_rtsp_close_streams(s);
|
||||
ff_rtsp_close_connections(s);
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -349,20 +862,22 @@ retry:
|
||||
}
|
||||
rt->packets++;
|
||||
|
||||
/* send dummy request to keep TCP connection alive */
|
||||
if ((av_gettime() - rt->last_cmd_time) / 1000000 >= rt->timeout / 2 ||
|
||||
rt->auth_state.stale) {
|
||||
if (rt->server_type == RTSP_SERVER_WMS ||
|
||||
(rt->server_type != RTSP_SERVER_REAL &&
|
||||
rt->get_parameter_supported)) {
|
||||
ff_rtsp_send_cmd_async(s, "GET_PARAMETER", rt->control_uri, NULL);
|
||||
} else {
|
||||
ff_rtsp_send_cmd_async(s, "OPTIONS", "*", NULL);
|
||||
if (!(rt->rtsp_flags & RTSP_FLAG_LISTEN)) {
|
||||
/* send dummy request to keep TCP connection alive */
|
||||
if ((av_gettime() - rt->last_cmd_time) / 1000000 >= rt->timeout / 2 ||
|
||||
rt->auth_state.stale) {
|
||||
if (rt->server_type == RTSP_SERVER_WMS ||
|
||||
(rt->server_type != RTSP_SERVER_REAL &&
|
||||
rt->get_parameter_supported)) {
|
||||
ff_rtsp_send_cmd_async(s, "GET_PARAMETER", rt->control_uri, NULL);
|
||||
} else {
|
||||
ff_rtsp_send_cmd_async(s, "OPTIONS", "*", NULL);
|
||||
}
|
||||
/* The stale flag should be reset when creating the auth response in
|
||||
* ff_rtsp_send_cmd_async, but reset it here just in case we never
|
||||
* called the auth code (if we didn't have any credentials set). */
|
||||
rt->auth_state.stale = 0;
|
||||
}
|
||||
/* The stale flag should be reset when creating the auth response in
|
||||
* ff_rtsp_send_cmd_async, but reset it here just in case we never
|
||||
* called the auth code (if we didn't have any credentials set). */
|
||||
rt->auth_state.stale = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -30,7 +30,7 @@
|
||||
#include "libavutil/avutil.h"
|
||||
|
||||
#define LIBAVFORMAT_VERSION_MAJOR 54
|
||||
#define LIBAVFORMAT_VERSION_MINOR 6
|
||||
#define LIBAVFORMAT_VERSION_MINOR 7
|
||||
#define LIBAVFORMAT_VERSION_MICRO 0
|
||||
|
||||
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
|
||||
|
Loading…
Reference in New Issue
Block a user