diff mbox series

[FFmpeg-devel,1/3] avformat/rtsp: add support for satip://

Message ID 20201222003600.27621-1-ffmpeg@tmm1.net
State Superseded
Headers show
Series [FFmpeg-devel,1/3] avformat/rtsp: add support for satip:// | expand

Checks

Context Check Description
andriy/x86_make success Make finished
andriy/x86_make_fate success Make fate finished
andriy/PPC64_make success Make finished
andriy/PPC64_make_fate success Make fate finished

Commit Message

Aman Karmani Dec. 22, 2020, 12:35 a.m. UTC
From: Aman Karmani <aman@tmm1.net>

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 and 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].

This commit adds proper spec-compliant support for SAT>IP, including:

- support for the satip:// psuedo-protocol
- 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

[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
---
 libavformat/rtsp.c    | 52 ++++++++++++++++++++++++++++++++++++++-----
 libavformat/rtsp.h    |  6 +++++
 libavformat/rtspdec.c |  1 +
 3 files changed, 53 insertions(+), 6 deletions(-)

Comments

Andriy Gelman Dec. 22, 2020, 8:15 p.m. UTC | #1
On Mon, 21. Dec 16:35, Aman Karmani wrote:
> From: Aman Karmani <aman@tmm1.net>
> 
> 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 and 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].
> 
> This commit adds proper spec-compliant support for SAT>IP, including:
> 
> - support for the satip:// psuedo-protocol
> - 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
> 
> [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
> ---
>  libavformat/rtsp.c    | 52 ++++++++++++++++++++++++++++++++++++++-----
>  libavformat/rtsp.h    |  6 +++++
>  libavformat/rtspdec.c |  1 +
>  3 files changed, 53 insertions(+), 6 deletions(-)
> 
> diff --git a/libavformat/rtsp.c b/libavformat/rtsp.c
> index c7ffa07d9e..4a863dbac3 100644
> --- a/libavformat/rtsp.c
> +++ b/libavformat/rtsp.c
> @@ -252,6 +252,24 @@ static void finalize_rtp_handler_init(AVFormatContext *s, RTSPStream *rtsp_st,
>      }
>  }
>  
> +static int init_satip_stream(AVFormatContext *s)
> +{
> +    RTSPState *rt = s->priv_data;
> +    RTSPStream *rtsp_st;
> +    rtsp_st = av_mallocz(sizeof(RTSPStream));
> +    if (!rtsp_st)
> +        return AVERROR(ENOMEM);
> +    rtsp_st->stream_index = -1;
> +    dynarray_add(&rt->rtsp_streams,
> +                 &rt->nb_rtsp_streams, rtsp_st);
> +    av_strlcpy(rtsp_st->control_url,
> +               rt->control_uri, sizeof(rtsp_st->control_url));
> +    rtsp_st->sdp_payload_type = 33; // MP2T
> +    init_rtp_handler(&ff_mpegts_dynamic_handler, rtsp_st, NULL);
> +    finalize_rtp_handler_init(s, rtsp_st, NULL);
> +    return 0;
> +}
> +
>  /* parse the rtpmap description: <codec_name>/<clock_rate>[/<other params>] */
>  static int sdp_parse_rtpmap(AVFormatContext *s,
>                              AVStream *st, RTSPStream *rtsp_st,
> @@ -1116,6 +1134,9 @@ void ff_rtsp_parse_line(AVFormatContext *s,
>      } else if (av_stristart(p, "Content-Type:", &p)) {
>          p += strspn(p, SPACE_CHARS);
>          av_strlcpy(reply->content_type, p, sizeof(reply->content_type));
> +    } else if (av_stristart(p, "com.ses.streamID:", &p)) {
> +        p += strspn(p, SPACE_CHARS);
> +        av_strlcpy(reply->stream_id, p, sizeof(reply->stream_id));
>      }
>  }
>  
> @@ -1495,8 +1516,10 @@ int ff_rtsp_make_setup_request(AVFormatContext *s, const char *host, int port,
>          rtp_opened:
>              port = ff_rtp_get_local_rtp_port(rtsp_st->rtp_handle);
>          have_port:

> -            snprintf(transport, sizeof(transport) - 1,
> -                     "%s/UDP;", trans_pref);
> +            av_strlcat(transport, trans_pref, sizeof(transport));

transport needs to be initialized to an empty string. Otherwise av_strlcat will
keep all the random bytes before the first NULL.
Aman Karmani Dec. 22, 2020, 8:39 p.m. UTC | #2
On Tue, Dec 22, 2020 at 12:17 PM Andriy Gelman <andriy.gelman@gmail.com>
wrote:

> On Mon, 21. Dec 16:35, Aman Karmani wrote:
> > From: Aman Karmani <aman@tmm1.net>
> >
> > 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 and 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].
> >
> > This commit adds proper spec-compliant support for SAT>IP, including:
> >
> > - support for the satip:// psuedo-protocol
> > - 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
> >
> > [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
> > ---
> >  libavformat/rtsp.c    | 52 ++++++++++++++++++++++++++++++++++++++-----
> >  libavformat/rtsp.h    |  6 +++++
> >  libavformat/rtspdec.c |  1 +
> >  3 files changed, 53 insertions(+), 6 deletions(-)
> >
> > diff --git a/libavformat/rtsp.c b/libavformat/rtsp.c
> > index c7ffa07d9e..4a863dbac3 100644
> > --- a/libavformat/rtsp.c
> > +++ b/libavformat/rtsp.c
> > @@ -252,6 +252,24 @@ static void
> finalize_rtp_handler_init(AVFormatContext *s, RTSPStream *rtsp_st,
> >      }
> >  }
> >
> > +static int init_satip_stream(AVFormatContext *s)
> > +{
> > +    RTSPState *rt = s->priv_data;
> > +    RTSPStream *rtsp_st;
> > +    rtsp_st = av_mallocz(sizeof(RTSPStream));
> > +    if (!rtsp_st)
> > +        return AVERROR(ENOMEM);
> > +    rtsp_st->stream_index = -1;
> > +    dynarray_add(&rt->rtsp_streams,
> > +                 &rt->nb_rtsp_streams, rtsp_st);
> > +    av_strlcpy(rtsp_st->control_url,
> > +               rt->control_uri, sizeof(rtsp_st->control_url));
> > +    rtsp_st->sdp_payload_type = 33; // MP2T
> > +    init_rtp_handler(&ff_mpegts_dynamic_handler, rtsp_st, NULL);
> > +    finalize_rtp_handler_init(s, rtsp_st, NULL);
> > +    return 0;
> > +}
> > +
> >  /* parse the rtpmap description: <codec_name>/<clock_rate>[/<other
> params>] */
> >  static int sdp_parse_rtpmap(AVFormatContext *s,
> >                              AVStream *st, RTSPStream *rtsp_st,
> > @@ -1116,6 +1134,9 @@ void ff_rtsp_parse_line(AVFormatContext *s,
> >      } else if (av_stristart(p, "Content-Type:", &p)) {
> >          p += strspn(p, SPACE_CHARS);
> >          av_strlcpy(reply->content_type, p, sizeof(reply->content_type));
> > +    } else if (av_stristart(p, "com.ses.streamID:", &p)) {
> > +        p += strspn(p, SPACE_CHARS);
> > +        av_strlcpy(reply->stream_id, p, sizeof(reply->stream_id));
> >      }
> >  }
> >
> > @@ -1495,8 +1516,10 @@ int ff_rtsp_make_setup_request(AVFormatContext
> *s, const char *host, int port,
> >          rtp_opened:
> >              port = ff_rtp_get_local_rtp_port(rtsp_st->rtp_handle);
> >          have_port:
>
> > -            snprintf(transport, sizeof(transport) - 1,
> > -                     "%s/UDP;", trans_pref);
> > +            av_strlcat(transport, trans_pref, sizeof(transport));
>
> transport needs to be initialized to an empty string. Otherwise av_strlcat
> will
> keep all the random bytes before the first NULL.


Thanks for catching this. Updated the v2 patch to use av_strlcpy, which
makes more sense anyway given this code is inside a loop.



>
> --
> Andriy
>
diff mbox series

Patch

diff --git a/libavformat/rtsp.c b/libavformat/rtsp.c
index c7ffa07d9e..4a863dbac3 100644
--- a/libavformat/rtsp.c
+++ b/libavformat/rtsp.c
@@ -252,6 +252,24 @@  static void finalize_rtp_handler_init(AVFormatContext *s, RTSPStream *rtsp_st,
     }
 }
 
+static int init_satip_stream(AVFormatContext *s)
+{
+    RTSPState *rt = s->priv_data;
+    RTSPStream *rtsp_st;
+    rtsp_st = av_mallocz(sizeof(RTSPStream));
+    if (!rtsp_st)
+        return AVERROR(ENOMEM);
+    rtsp_st->stream_index = -1;
+    dynarray_add(&rt->rtsp_streams,
+                 &rt->nb_rtsp_streams, rtsp_st);
+    av_strlcpy(rtsp_st->control_url,
+               rt->control_uri, sizeof(rtsp_st->control_url));
+    rtsp_st->sdp_payload_type = 33; // MP2T
+    init_rtp_handler(&ff_mpegts_dynamic_handler, rtsp_st, NULL);
+    finalize_rtp_handler_init(s, rtsp_st, NULL);
+    return 0;
+}
+
 /* parse the rtpmap description: <codec_name>/<clock_rate>[/<other params>] */
 static int sdp_parse_rtpmap(AVFormatContext *s,
                             AVStream *st, RTSPStream *rtsp_st,
@@ -1116,6 +1134,9 @@  void ff_rtsp_parse_line(AVFormatContext *s,
     } else if (av_stristart(p, "Content-Type:", &p)) {
         p += strspn(p, SPACE_CHARS);
         av_strlcpy(reply->content_type, p, sizeof(reply->content_type));
+    } else if (av_stristart(p, "com.ses.streamID:", &p)) {
+        p += strspn(p, SPACE_CHARS);
+        av_strlcpy(reply->stream_id, p, sizeof(reply->stream_id));
     }
 }
 
@@ -1495,8 +1516,10 @@  int ff_rtsp_make_setup_request(AVFormatContext *s, const char *host, int port,
         rtp_opened:
             port = ff_rtp_get_local_rtp_port(rtsp_st->rtp_handle);
         have_port:
-            snprintf(transport, sizeof(transport) - 1,
-                     "%s/UDP;", trans_pref);
+            av_strlcat(transport, trans_pref, sizeof(transport));
+            av_strlcat(transport,
+                       rt->server_type == RTSP_SERVER_SATIP ? ";" : "/UDP;",
+                       sizeof(transport));
             if (rt->server_type != RTSP_SERVER_REAL)
                 av_strlcat(transport, "unicast;", sizeof(transport));
             av_strlcatf(transport, sizeof(transport),
@@ -1559,6 +1582,15 @@  int ff_rtsp_make_setup_request(AVFormatContext *s, const char *host, int port,
             goto fail;
         }
 
+        if (rt->server_type == RTSP_SERVER_SATIP && reply->stream_id[0]) {
+            char proto[128], host[128], path[512], auth[128];
+            int port;
+            av_url_split(proto, sizeof(proto), auth, sizeof(auth), host, sizeof(host),
+                        &port, path, sizeof(path), rt->control_uri);
+            ff_url_join(rt->control_uri, sizeof(rt->control_uri), proto, NULL, host,
+                        port, "/stream=%s", reply->stream_id);
+        }
+
         /* XXX: same protocol for all streams is required */
         if (i > 0) {
             if (reply->transports[0].lower_transport != rt->lower_transport ||
@@ -1710,6 +1742,9 @@  redirect:
         lower_rtsp_proto         = "tls";
         default_port             = RTSPS_DEFAULT_PORT;
         rt->lower_transport_mask = 1 << RTSP_LOWER_TRANSPORT_TCP;
+    } else if (!strcmp(proto, "satip")) {
+        av_strlcpy(proto, "rtsp", sizeof(proto));
+        rt->server_type = RTSP_SERVER_SATIP;
     }
 
     if (*auth) {
@@ -1857,7 +1892,9 @@  redirect:
 
     /* request options supported by the server; this also detects server
      * type */
-    for (rt->server_type = RTSP_SERVER_RTP;;) {
+    if (rt->server_type != RTSP_SERVER_SATIP)
+        rt->server_type = RTSP_SERVER_RTP;
+    for (;;) {
         cmd[0] = 0;
         if (rt->server_type == RTSP_SERVER_REAL)
             av_strlcat(cmd,
@@ -1892,9 +1929,12 @@  redirect:
         break;
     }
 
-    if (CONFIG_RTSP_DEMUXER && s->iformat)
-        err = ff_rtsp_setup_input_streams(s, reply);
-    else if (CONFIG_RTSP_MUXER)
+    if (CONFIG_RTSP_DEMUXER && s->iformat) {
+        if (rt->server_type == RTSP_SERVER_SATIP)
+            err = init_satip_stream(s);
+        else
+            err = ff_rtsp_setup_input_streams(s, reply);
+    } else if (CONFIG_RTSP_MUXER)
         err = ff_rtsp_setup_output_streams(s, host);
     else
         av_assert0(0);
diff --git a/libavformat/rtsp.h b/libavformat/rtsp.h
index b74cdc148a..c7c3b3cb52 100644
--- a/libavformat/rtsp.h
+++ b/libavformat/rtsp.h
@@ -188,6 +188,11 @@  typedef struct RTSPMessageHeader {
      * Content type header
      */
     char content_type[64];
+
+    /**
+     * Stream ID header
+     */
+    char stream_id[64];
 } RTSPMessageHeader;
 
 /**
@@ -210,6 +215,7 @@  enum RTSPServerType {
     RTSP_SERVER_RTP,  /**< Standards-compliant RTP-server */
     RTSP_SERVER_REAL, /**< Realmedia-style server */
     RTSP_SERVER_WMS,  /**< Windows Media server */
+    RTSP_SERVER_SATIP,/**< SAT>IP server */
     RTSP_SERVER_NB
 };
 
diff --git a/libavformat/rtspdec.c b/libavformat/rtspdec.c
index bfbb01d586..113da975e1 100644
--- a/libavformat/rtspdec.c
+++ b/libavformat/rtspdec.c
@@ -724,6 +724,7 @@  static int rtsp_probe(const AVProbeData *p)
 #if CONFIG_TLS_PROTOCOL
         av_strstart(p->filename, "rtsps:", NULL) ||
 #endif
+        av_strstart(p->filename, "satip:", NULL) ||
         av_strstart(p->filename, "rtsp:", NULL))
         return AVPROBE_SCORE_MAX;
     return 0;