ffmpeg/libavformat/rtspdec.c

1011 lines
34 KiB
C
Raw Normal View History

/*
* RTSP demuxer
* Copyright (c) 2002 Fabrice Bellard
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "config_components.h"
#include "libavutil/avstring.h"
#include "libavutil/intreadwrite.h"
#include "libavutil/mathematics.h"
#include "libavutil/mem.h"
#include "libavutil/random_seed.h"
2012-07-27 16:28:36 +02:00
#include "libavutil/time.h"
#include "avformat.h"
#include "demux.h"
#include "internal.h"
#include "network.h"
#include "os_support.h"
#include "rtpproto.h"
#include "rtsp.h"
#include "rdt.h"
#include "tls.h"
#include "url.h"
#include "version.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;
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);
ff_network_close();
rt->real_setup = NULL;
av_freep(&rt->real_setup_cache);
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 ? ret : AVERROR_EOF;
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[MAX_URL_SIZE];
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_log(s, AV_LOG_TRACE, "Sending response:\n%s", message);
ffurl_write(rt->rtsp_hd_out, 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[MAX_URL_SIZE];
int rbuflen, ret;
do {
ret = read_line(s, rbuf, sizeof(rbuf), &rbuflen);
if (ret)
return ret;
if (rbuflen > 1) {
av_log(s, AV_LOG_TRACE, "Parsing[%d]: %s\n", rbuflen, rbuf);
ff_rtsp_parse_line(s, 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;
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) {
sdp = av_malloc(request.content_length + 1);
if (!sdp)
return AVERROR(ENOMEM);
/* 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);
av_free(sdp);
return AVERROR(EIO);
}
sdp[request.content_length] = '\0';
av_log(s, AV_LOG_VERBOSE, "SDP: %s\n", sdp);
ret = ff_sdp_parse(s, sdp);
av_free(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[MAX_URL_SIZE];
RTSPStream *rtsp_st;
char responseheaders[MAX_URL_SIZE];
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;
/* check if the stream has already been setup */
if (rtsp_st->transport_priv) {
if (CONFIG_RTPDEC && rt->transport == RTSP_TRANSPORT_RDT)
ff_rdt_parse_close(rtsp_st->transport_priv);
else if (CONFIG_RTPDEC && rt->transport == RTSP_TRANSPORT_RTP)
ff_rtp_parse_close(rtsp_st->transport_priv);
rtsp_st->transport_priv = NULL;
}
if (rtsp_st->rtp_handle)
ffurl_closep(&rtsp_st->rtp_handle);
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: "
avformat/rtsp: Send mode=record instead of mode=receive in Transport header Fixes server compatibility issues with rtspclientsink GStreamer plugin. >From specification: RFC 7826 "Real-Time Streaming Protocol Version 2.0" (https://datatracker.ietf.org/doc/html/rfc7826), section 18.54: mode: The mode parameter indicates the methods to be supported for this session. The currently defined valid value is "PLAY". If not provided, the default is "PLAY". The "RECORD" value was defined in RFC 2326; in this specification, it is unspecified but reserved. RECORD and other values may be specified in the future. RFC 2326 "Real Time Streaming Protocol (RTSP)" (https://datatracker.ietf.org/doc/html/rfc2326), section 12.39: mode: The mode parameter indicates the methods to be supported for this session. Valid values are PLAY and RECORD. If not provided, the default is PLAY. mode=receive was always like this, from the initial commit 'a8ad6ffa rtsp: Add listen mode'. For comparison, Wowza was used to push RTSP stream to. Both GStreamer and FFmpeg had no issues. Here is the capture of Wowza responding to SETUP request: 200 OK CSeq: 3 Server: Wowza Streaming Engine 4.8.26+4 build20231212155517 Cache-Control: no-cache Expires: Mon, 15 Jan 2024 19:40:31 GMT Transport: RTP/AVP/UDP;unicast;client_port=11640-11641;mode=record;source=172.17.0.2;server_port=6976-6977 Date: Mon, 15 Jan 2024 19:40:31 GMT Session: 1401457689;timeout=60 Test setup: Server: ffmpeg -loglevel trace -y -rtsp_flags listen -i rtsp://0.0.0.0:30800/live.stream t.mp4 FFmpeg client: ffmpeg -re -i "Big Buck Bunny - FULL HD 30FPS.mp4" -c:v libx264 -f rtsp rtsp://127.0.0.1:30800/live.stream GStreamer client: gst-launch-1.0 videotestsrc is-live=true pattern=smpte ! queue ! videorate ! videoscale ! video/x-raw,width=640,height=360,framerate=60/1 ! timeoverlay font-desc="Sans, 84" halignment=center valignment=center ! queue ! videoconvert ! tee name=t t. ! x264enc bitrate=9000 pass=cbr speed-preset=ultrafast byte-stream=false key-int-max=15 threads=1 ! video/x-h264,profile=baseline ! queue ! rsink. audiotestsrc ! voaacenc ! queue ! rsink. t. ! queue ! autovideosink rtspclientsink name=rsink location=rtsp://localhost:30800/live.stream Test results: modified FFmpeg client -> stock server : ok stock FFmpeg client -> modified server : ok modified FFmpeg client -> modified server : ok GStreamer client -> modified server : ok Signed-off-by: Paul Orlyk <paul.orlyk@gmail.com> Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
2024-01-15 21:37:25 +01:00
"RTP/AVP/TCP;unicast;mode=record;interleaved=%d-%d"
"\r\n", request.transports[0].interleaved_min,
request.transports[0].interleaved_max);
} else {
do {
AVDictionary *opts = NULL;
av_dict_set_int(&opts, "buffer_size", rt->buffer_size, 0);
ff_url_join(url, sizeof(url), "rtp", NULL, host, localport, NULL);
av_log(s, AV_LOG_TRACE, "Opening: %s\n", url);
ret = ffurl_open_whitelist(&rtsp_st->rtp_handle, url, AVIO_FLAG_READ_WRITE,
&s->interrupt_callback, &opts,
s->protocol_whitelist, s->protocol_blacklist, NULL);
av_dict_free(&opts);
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_log(s, AV_LOG_TRACE, "Listening on: %d\n",
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: "
avformat/rtsp: Send mode=record instead of mode=receive in Transport header Fixes server compatibility issues with rtspclientsink GStreamer plugin. >From specification: RFC 7826 "Real-Time Streaming Protocol Version 2.0" (https://datatracker.ietf.org/doc/html/rfc7826), section 18.54: mode: The mode parameter indicates the methods to be supported for this session. The currently defined valid value is "PLAY". If not provided, the default is "PLAY". The "RECORD" value was defined in RFC 2326; in this specification, it is unspecified but reserved. RECORD and other values may be specified in the future. RFC 2326 "Real Time Streaming Protocol (RTSP)" (https://datatracker.ietf.org/doc/html/rfc2326), section 12.39: mode: The mode parameter indicates the methods to be supported for this session. Valid values are PLAY and RECORD. If not provided, the default is PLAY. mode=receive was always like this, from the initial commit 'a8ad6ffa rtsp: Add listen mode'. For comparison, Wowza was used to push RTSP stream to. Both GStreamer and FFmpeg had no issues. Here is the capture of Wowza responding to SETUP request: 200 OK CSeq: 3 Server: Wowza Streaming Engine 4.8.26+4 build20231212155517 Cache-Control: no-cache Expires: Mon, 15 Jan 2024 19:40:31 GMT Transport: RTP/AVP/UDP;unicast;client_port=11640-11641;mode=record;source=172.17.0.2;server_port=6976-6977 Date: Mon, 15 Jan 2024 19:40:31 GMT Session: 1401457689;timeout=60 Test setup: Server: ffmpeg -loglevel trace -y -rtsp_flags listen -i rtsp://0.0.0.0:30800/live.stream t.mp4 FFmpeg client: ffmpeg -re -i "Big Buck Bunny - FULL HD 30FPS.mp4" -c:v libx264 -f rtsp rtsp://127.0.0.1:30800/live.stream GStreamer client: gst-launch-1.0 videotestsrc is-live=true pattern=smpte ! queue ! videorate ! videoscale ! video/x-raw,width=640,height=360,framerate=60/1 ! timeoverlay font-desc="Sans, 84" halignment=center valignment=center ! queue ! videoconvert ! tee name=t t. ! x264enc bitrate=9000 pass=cbr speed-preset=ultrafast byte-stream=false key-int-max=15 threads=1 ! video/x-h264,profile=baseline ! queue ! rsink. audiotestsrc ! voaacenc ! queue ! rsink. t. ! queue ! autovideosink rtspclientsink name=rsink location=rtsp://localhost:30800/live.stream Test results: modified FFmpeg client -> stock server : ok stock FFmpeg client -> modified server : ok modified FFmpeg client -> modified server : ok GStreamer client -> modified server : ok Signed-off-by: Paul Orlyk <paul.orlyk@gmail.com> Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
2024-01-15 21:37:25 +01:00
"RTP/AVP/UDP;unicast;mode=record;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[MAX_URL_SIZE];
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) {
av_log(s, AV_LOG_ERROR, "Error parsing method string\n");
return AVERROR_INVALIDDATA;
}
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) {
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);
av_strlcpy(rt->control_uri, uri, sizeof(rt->control_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[MAX_URL_SIZE];
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;
av_log(s, AV_LOG_TRACE, "Parsing[%d]: %s\n", rbuflen, rbuf);
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 ret;
}
static int rtsp_read_play(AVFormatContext *s)
{
RTSPState *rt = s->priv_data;
RTSPMessageHeader reply1, *reply = &reply1;
int i;
char cmd[MAX_URL_SIZE];
av_log(s, AV_LOG_DEBUG, "hello state=%d\n", rt->state);
rt->nb_byes = 0;
if (rt->lower_transport == RTSP_LOWER_TRANSPORT_UDP) {
for (i = 0; i < rt->nb_rtsp_streams; i++) {
RTSPStream *rtsp_st = rt->rtsp_streams[i];
/* Try to initialize the connection state in a
* potential NAT router by sending dummy packets.
* RTP/RTCP dummy packets are used for RDT, too.
*/
if (rtsp_st->rtp_handle &&
!(rt->server_type == RTSP_SERVER_WMS && i > 1))
ff_rtp_send_punch_packets(rtsp_st->rtp_handle);
}
}
if (!(rt->server_type == RTSP_SERVER_REAL && rt->need_subscription)) {
if (rt->transport == RTSP_TRANSPORT_RTP) {
for (i = 0; i < rt->nb_rtsp_streams; i++) {
RTSPStream *rtsp_st = rt->rtsp_streams[i];
RTPDemuxContext *rtpctx = rtsp_st->transport_priv;
if (!rtpctx)
continue;
ff_rtp_reset_packet_queue(rtpctx);
rtpctx->last_rtcp_ntp_time = AV_NOPTS_VALUE;
rtpctx->first_rtcp_ntp_time = AV_NOPTS_VALUE;
rtpctx->base_timestamp = 0;
rtpctx->timestamp = 0;
rtpctx->unwrapped_timestamp = 0;
rtpctx->rtcp_ts_offset = 0;
}
}
if (rt->state == RTSP_STATE_PAUSED) {
cmd[0] = 0;
} else {
snprintf(cmd, sizeof(cmd),
"Range: npt=%"PRId64".%03"PRId64"-\r\n",
rt->seek_timestamp / AV_TIME_BASE,
rt->seek_timestamp / (AV_TIME_BASE / 1000) % 1000);
}
ff_rtsp_send_cmd(s, "PLAY", rt->control_uri, cmd, reply, NULL);
if (reply->status_code != RTSP_STATUS_OK) {
return ff_rtsp_averror(reply->status_code, -1);
}
if (rt->transport == RTSP_TRANSPORT_RTP &&
reply->range_start != AV_NOPTS_VALUE) {
for (i = 0; i < rt->nb_rtsp_streams; i++) {
RTSPStream *rtsp_st = rt->rtsp_streams[i];
RTPDemuxContext *rtpctx = rtsp_st->transport_priv;
AVStream *st = NULL;
if (!rtpctx || rtsp_st->stream_index < 0)
continue;
st = s->streams[rtsp_st->stream_index];
rtpctx->range_start_offset =
av_rescale_q(reply->range_start, AV_TIME_BASE_Q,
st->time_base);
}
}
}
rt->state = RTSP_STATE_STREAMING;
return 0;
}
/* pause the stream */
static int rtsp_read_pause(AVFormatContext *s)
{
RTSPState *rt = s->priv_data;
RTSPMessageHeader reply1, *reply = &reply1;
if (rt->state != RTSP_STATE_STREAMING)
return 0;
else if (!(rt->server_type == RTSP_SERVER_REAL && rt->need_subscription)) {
ff_rtsp_send_cmd(s, "PAUSE", rt->control_uri, NULL, reply, NULL);
if (reply->status_code != RTSP_STATUS_OK) {
return ff_rtsp_averror(reply->status_code, -1);
}
}
rt->state = RTSP_STATE_PAUSED;
return 0;
}
int ff_rtsp_setup_input_streams(AVFormatContext *s, RTSPMessageHeader *reply)
{
RTSPState *rt = s->priv_data;
char cmd[MAX_URL_SIZE];
unsigned char *content = NULL;
int ret;
/* describe the stream */
snprintf(cmd, sizeof(cmd),
"Accept: application/sdp\r\n");
if (rt->server_type == RTSP_SERVER_REAL) {
/**
* The Require: attribute is needed for proper streaming from
* Realmedia servers.
*/
av_strlcat(cmd,
"Require: com.real.retain-entity-for-setup\r\n",
sizeof(cmd));
}
ff_rtsp_send_cmd(s, "DESCRIBE", rt->control_uri, cmd, reply, &content);
if (reply->status_code != RTSP_STATUS_OK) {
av_freep(&content);
return ff_rtsp_averror(reply->status_code, AVERROR_INVALIDDATA);
}
if (!content)
return AVERROR_INVALIDDATA;
av_log(s, AV_LOG_VERBOSE, "SDP:\n%s\n", content);
/* now we got the SDP description, we parse it */
ret = ff_sdp_parse(s, (const char *)content);
av_freep(&content);
if (ret < 0)
return ret;
return 0;
}
static int rtsp_listen(AVFormatContext *s)
{
RTSPState *rt = s->priv_data;
2014-10-09 22:07:12 +02:00
char proto[128], host[128], path[512], auth[128];
char uri[500];
int port;
2014-10-09 22:07:12 +02:00
int default_port = RTSP_DEFAULT_PORT;
char tcpname[500];
2014-10-09 22:07:12 +02:00
const char *lower_proto = "tcp";
unsigned char rbuf[MAX_URL_SIZE];
unsigned char method[10];
int rbuflen = 0;
int ret;
enum RTSPMethod methodcode;
if (!ff_network_init())
return AVERROR(EIO);
/* extract hostname and port */
2014-10-09 22:07:12 +02:00
av_url_split(proto, sizeof(proto), auth, sizeof(auth), host, sizeof(host),
&port, path, sizeof(path), s->url);
/* ff_url_join. No authorization by now (NULL) */
2014-10-09 22:07:12 +02:00
ff_url_join(rt->control_uri, sizeof(rt->control_uri), proto, NULL, host,
port, "%s", path);
2014-10-09 22:07:12 +02:00
if (!strcmp(proto, "rtsps")) {
lower_proto = "tls";
default_port = RTSPS_DEFAULT_PORT;
}
if (port < 0)
2014-10-09 22:07:12 +02:00
port = default_port;
/* Create TCP connection */
2014-10-09 22:07:12 +02:00
ff_url_join(tcpname, sizeof(tcpname), lower_proto, NULL, host, port,
"?listen&listen_timeout=%d", rt->initial_timeout * 1000);
if (ret = ffurl_open_whitelist(&rt->rtsp_hd, tcpname, AVIO_FLAG_READ_WRITE,
&s->interrupt_callback, NULL,
s->protocol_whitelist, s->protocol_blacklist, NULL)) {
av_log(s, AV_LOG_ERROR, "Unable to open RTSP for listening\n");
goto fail;
}
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)
goto fail;
av_log(s, AV_LOG_TRACE, "Parsing[%d]: %s\n", rbuflen, rbuf);
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");
goto fail;
}
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) {
ret = AVERROR_INVALIDDATA;
goto fail;
}
}
fail:
ff_rtsp_close_streams(s);
ff_rtsp_close_connections(s);
ff_network_close();
return ret;
}
static int rtsp_probe(const AVProbeData *p)
{
2014-10-09 22:07:35 +02:00
if (
#if CONFIG_TLS_PROTOCOL
av_strstart(p->filename, "rtsps:", NULL) ||
#endif
avformat/rtsp: add support for satip:// The SAT>IP protocol[1] is similar to RTSP. However SAT>IP servers are assumed to speak only MP2T, so DESCRIBE is not used in the same way. When no streams are active, DESCRIBE will return 404 according to the spec (see section 3.5.7). When streams are active, DESCRIBE will return a list of all current streams along with information about their signal strengths. Previously, attemping to use ffmpeg with a rtsp:// url that points to a SAT>IP server would work with some devices, but fail due to 404 response on others. Further, if the SAT>IP server was already streaming, ffmpeg would incorrectly consume the DESCRIBE SDP response and join an existing tuner instead of requesting a new session with the URL provided by the user. These issues have been noted by many users across the internet[2][3][4]. This commit adds proper spec-compliant support for SAT>IP, including: - support for the satip:// psuedo-protocol[5] - avoiding the use of DESCRIBE - parsing and consuming the com.ses.streamID response header - using "Transport: RTP/AVP;unicast" because the optional "/UDP" suffix confuses some servers This patch has been validated against multiple SAT>IP vendor devices: - Telestar Digibit R2 (https://telestar.de/en/produkt/digibit-r1-2/) - Kathrein EXIP 418 (https://www.kathrein-ds.com/en/produkte/sat-zf-verteiltechnik/sat-ip/227/exip-418) - Kathrein EXIP 4124 (https://www.kathrein-ds.com/en/products/sat-if-signal-distribution/sat-ip/226/exip-4124) - Megasat MEG-8000 (https://www.megasat.tv/produkt/sat-ip-server-3/) - Megasat Twin (https://www.megasat.tv/en/produkt/sat-ip-server-twin/) - Triax TSS 400 (https://www.conrad.com/p/triax-tss-400-mkii-sat-ip-server-595256) [1] https://www.satip.info/sites/satip/files/resource/satip_specification_version_1_2_2.pdf [2] https://stackoverflow.com/questions/61194344/does-ffmpeg-violate-the-satip-specification-describe-syntax [3] https://github.com/kodi-pvr/pvr.iptvsimple/issues/196 [4] https://forum.kodi.tv/showthread.php?tid=359072&pid=2995884#pid2995884 [5] https://www.satip.info/resources/channel-lists/
2020-12-16 21:40:16 +01:00
av_strstart(p->filename, "satip:", NULL) ||
2014-10-09 22:07:35 +02:00
av_strstart(p->filename, "rtsp:", NULL))
return AVPROBE_SCORE_MAX;
return 0;
}
static int rtsp_read_header(AVFormatContext *s)
{
RTSPState *rt = s->priv_data;
int ret;
if (rt->initial_timeout > 0)
rt->rtsp_flags |= RTSP_FLAG_LISTEN;
if (rt->rtsp_flags & RTSP_FLAG_LISTEN) {
ret = rtsp_listen(s);
if (ret)
return ret;
} else {
ret = ff_rtsp_connect(s);
if (ret)
return ret;
rt->real_setup_cache = !s->nb_streams ? NULL :
av_calloc(s->nb_streams, 2 * sizeof(*rt->real_setup_cache));
if (!rt->real_setup_cache && s->nb_streams) {
ret = AVERROR(ENOMEM);
goto fail;
}
rt->real_setup = rt->real_setup_cache + s->nb_streams;
if (rt->initial_pause) {
/* do not start immediately */
} else {
ret = rtsp_read_play(s);
if (ret < 0)
goto fail;
}
}
return 0;
fail:
rtsp_read_close(s);
return ret;
}
int ff_rtsp_tcp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st,
uint8_t *buf, int buf_size)
{
RTSPState *rt = s->priv_data;
int id, len, i, ret;
RTSPStream *rtsp_st;
av_log(s, AV_LOG_TRACE, "tcp_read_packet:\n");
redo:
for (;;) {
RTSPMessageHeader reply;
ret = ff_rtsp_read_reply(s, &reply, NULL, 1, NULL);
if (ret < 0)
return ret;
if (ret == 1) /* received '$' */
break;
/* XXX: parse message */
if (rt->state != RTSP_STATE_STREAMING)
return 0;
}
ret = ffurl_read_complete(rt->rtsp_hd, buf, 3);
if (ret != 3)
return AVERROR(EIO);
id = buf[0];
len = AV_RB16(buf + 1);
av_log(s, AV_LOG_TRACE, "id=%d len=%d\n", id, len);
if (len > buf_size || len < 8)
goto redo;
/* get the data */
ret = ffurl_read_complete(rt->rtsp_hd, buf, len);
if (ret != len)
return AVERROR(EIO);
if (rt->transport == RTSP_TRANSPORT_RDT &&
(ret = ff_rdt_parse_header(buf, len, &id, NULL, NULL, NULL, NULL)) < 0)
return ret;
/* find the matching stream */
for (i = 0; i < rt->nb_rtsp_streams; i++) {
rtsp_st = rt->rtsp_streams[i];
if (id >= rtsp_st->interleaved_min &&
id <= rtsp_st->interleaved_max)
goto found;
}
goto redo;
found:
*prtsp_st = rtsp_st;
return len;
}
static int resetup_tcp(AVFormatContext *s)
{
RTSPState *rt = s->priv_data;
char host[1024];
int port;
av_url_split(NULL, 0, NULL, 0, host, sizeof(host), &port, NULL, 0,
s->url);
ff_rtsp_undo_setup(s, 0);
return ff_rtsp_make_setup_request(s, host, port, RTSP_LOWER_TRANSPORT_TCP,
rt->real_challenge);
}
static int rtsp_read_packet(AVFormatContext *s, AVPacket *pkt)
{
RTSPState *rt = s->priv_data;
int ret;
RTSPMessageHeader reply1, *reply = &reply1;
char cmd[MAX_URL_SIZE];
retry:
if (rt->server_type == RTSP_SERVER_REAL) {
int i;
for (i = 0; i < s->nb_streams; i++)
rt->real_setup[i] = s->streams[i]->discard;
if (!rt->need_subscription) {
if (memcmp (rt->real_setup, rt->real_setup_cache,
sizeof(enum AVDiscard) * s->nb_streams)) {
snprintf(cmd, sizeof(cmd),
"Unsubscribe: %s\r\n",
rt->last_subscription);
ff_rtsp_send_cmd(s, "SET_PARAMETER", rt->control_uri,
cmd, reply, NULL);
if (reply->status_code != RTSP_STATUS_OK)
return ff_rtsp_averror(reply->status_code, AVERROR_INVALIDDATA);
rt->need_subscription = 1;
}
}
if (rt->need_subscription) {
int r, rule_nr, first = 1;
memcpy(rt->real_setup_cache, rt->real_setup,
sizeof(enum AVDiscard) * s->nb_streams);
rt->last_subscription[0] = 0;
snprintf(cmd, sizeof(cmd),
"Subscribe: ");
for (i = 0; i < rt->nb_rtsp_streams; i++) {
rule_nr = 0;
for (r = 0; r < s->nb_streams; r++) {
if (s->streams[r]->id == i) {
if (s->streams[r]->discard != AVDISCARD_ALL) {
if (!first)
av_strlcat(rt->last_subscription, ",",
sizeof(rt->last_subscription));
ff_rdt_subscribe_rule(
rt->last_subscription,
sizeof(rt->last_subscription), i, rule_nr);
first = 0;
}
rule_nr++;
}
}
}
av_strlcatf(cmd, sizeof(cmd), "%s\r\n", rt->last_subscription);
ff_rtsp_send_cmd(s, "SET_PARAMETER", rt->control_uri,
cmd, reply, NULL);
if (reply->status_code != RTSP_STATUS_OK)
return ff_rtsp_averror(reply->status_code, AVERROR_INVALIDDATA);
rt->need_subscription = 0;
if (rt->state == RTSP_STATE_STREAMING)
rtsp_read_play (s);
}
}
ret = ff_rtsp_fetch_packet(s, pkt);
if (ret < 0) {
if (ret == AVERROR(ETIMEDOUT) && !rt->packets) {
if (rt->lower_transport == RTSP_LOWER_TRANSPORT_UDP &&
rt->lower_transport_mask & (1 << RTSP_LOWER_TRANSPORT_TCP)) {
RTSPMessageHeader reply1, *reply = &reply1;
av_log(s, AV_LOG_WARNING, "UDP timeout, retrying with TCP\n");
if (rtsp_read_pause(s) != 0)
return -1;
// TEARDOWN is required on Real-RTSP, but might make
// other servers close the connection.
if (rt->server_type == RTSP_SERVER_REAL)
ff_rtsp_send_cmd(s, "TEARDOWN", rt->control_uri, NULL,
reply, NULL);
rt->session_id[0] = '\0';
if (resetup_tcp(s) == 0) {
rt->state = RTSP_STATE_IDLE;
rt->need_subscription = 1;
if (rtsp_read_play(s) != 0)
return -1;
goto retry;
}
}
}
return ret;
}
rt->packets++;
if (!(rt->rtsp_flags & RTSP_FLAG_LISTEN)) {
/* send dummy request to keep TCP connection alive */
if ((av_gettime_relative() - 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", rt->control_uri, 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;
}
}
return 0;
}
static int rtsp_read_seek(AVFormatContext *s, int stream_index,
int64_t timestamp, int flags)
{
RTSPState *rt = s->priv_data;
int ret;
rt->seek_timestamp = av_rescale_q(timestamp,
s->streams[stream_index]->time_base,
AV_TIME_BASE_Q);
switch(rt->state) {
default:
case RTSP_STATE_IDLE:
break;
case RTSP_STATE_STREAMING:
if ((ret = rtsp_read_pause(s)) != 0)
return ret;
rt->state = RTSP_STATE_SEEKING;
if ((ret = rtsp_read_play(s)) != 0)
return ret;
break;
case RTSP_STATE_PAUSED:
rt->state = RTSP_STATE_IDLE;
break;
}
return 0;
}
static const AVClass rtsp_demuxer_class = {
.class_name = "RTSP demuxer",
.item_name = av_default_item_name,
.option = ff_rtsp_options,
.version = LIBAVUTIL_VERSION_INT,
};
const FFInputFormat ff_rtsp_demuxer = {
.p.name = "rtsp",
.p.long_name = NULL_IF_CONFIG_SMALL("RTSP input"),
.p.flags = AVFMT_NOFILE,
.p.priv_class = &rtsp_demuxer_class,
.priv_data_size = sizeof(RTSPState),
.read_probe = rtsp_probe,
.read_header = rtsp_read_header,
.read_packet = rtsp_read_packet,
.read_close = rtsp_read_close,
.read_seek = rtsp_read_seek,
.read_play = rtsp_read_play,
.read_pause = rtsp_read_pause,
};