|
|
|
@@ -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; |
|
|
|
|
|
|
|
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_timeout > 0) |
|
|
|
rt->rtsp_flags |= RTSP_FLAG_LISTEN; |
|
|
|
|
|
|
|
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; |
|
|
|
|