diff mbox series

[FFmpeg-devel,ONLY,FOR,TEST,2/2] avformat/rtmp: add rtmp over srt

Message ID tencent_4B0B91E62AD0A9E6DFE7DF2A9913195C5B06@qq.com
State New
Headers show
Series [FFmpeg-devel,1/2] avformat/libsrt: support bidirectional transmission | 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

zhilizhao(赵志立) May 18, 2021, 4:03 p.m. UTC
---
Test with:
./ffplay -listen 1 rtmpsrt://127.0.0.1:8888
./ffmpeg -re -i bunny.mp4 -c copy -f flv rtmpsrt://127.0.0.1:8888

 configure               |   2 +
 libavformat/Makefile    |   2 +
 libavformat/protocols.c |   2 +
 libavformat/rtmpproto.c |  11 ++-
 libavformat/rtmpsrt.c   | 167 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 183 insertions(+), 1 deletion(-)
 create mode 100644 libavformat/rtmpsrt.c

Comments

Thomas Volkert May 18, 2021, 4:19 p.m. UTC | #1
Hi,

from my understand "ffrtmpsrt" combines rtmp (with flv (de-)muxing),
which demands by design for a transmission without packet loss, with a
transmission protocol based on UDP, which can have packet loss. So, I am
not sure if we actually should add something like this to our source code.

Best regards,
Thomas.

On 18.05.2021 18:03, Zhao Zhili wrote:
> ---
> Test with:
> ./ffplay -listen 1 rtmpsrt://127.0.0.1:8888
> ./ffmpeg -re -i bunny.mp4 -c copy -f flv rtmpsrt://127.0.0.1:8888
>
>  configure               |   2 +
>  libavformat/Makefile    |   2 +
>  libavformat/protocols.c |   2 +
>  libavformat/rtmpproto.c |  11 ++-
>  libavformat/rtmpsrt.c   | 167 ++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 183 insertions(+), 1 deletion(-)
>  create mode 100644 libavformat/rtmpsrt.c
>
> diff --git a/configure b/configure
> index 82367fd30d..76cd56477a 100755
> --- a/configure
> +++ b/configure
> @@ -3476,6 +3476,7 @@ ffrtmpcrypt_protocol_deps_any="gcrypt gmp openssl mbedtls"
>  ffrtmpcrypt_protocol_select="tcp_protocol"
>  ffrtmphttp_protocol_conflict="librtmp_protocol"
>  ffrtmphttp_protocol_select="http_protocol"
> +ffrtmpsrt_protocol_select="libsrt_protocol"
>  ftp_protocol_select="tcp_protocol"
>  gopher_protocol_select="tcp_protocol"
>  gophers_protocol_select="tls_protocol"
> @@ -3502,6 +3503,7 @@ rtmpte_protocol_select="ffrtmpcrypt_protocol ffrtmphttp_protocol"
>  rtmpte_protocol_suggest="zlib"
>  rtmpts_protocol_select="ffrtmphttp_protocol https_protocol"
>  rtmpts_protocol_suggest="zlib"
> +rtmpsrt_protocol_select="ffrtmpsrt_protocol"
>  rtp_protocol_select="udp_protocol"
>  schannel_conflict="openssl gnutls libtls mbedtls"
>  sctp_protocol_deps="struct_sctp_event_subscribe struct_msghdr_msg_flags"
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index 85b5d8e7eb..7770fb2f8c 100644
> --- a/libavformat/Makefile
> +++ b/libavformat/Makefile
> @@ -618,6 +618,7 @@ OBJS-$(CONFIG_CRYPTO_PROTOCOL)           += crypto.o
>  OBJS-$(CONFIG_DATA_PROTOCOL)             += data_uri.o
>  OBJS-$(CONFIG_FFRTMPCRYPT_PROTOCOL)      += rtmpcrypt.o rtmpdigest.o rtmpdh.o
>  OBJS-$(CONFIG_FFRTMPHTTP_PROTOCOL)       += rtmphttp.o
> +OBJS-$(CONFIG_FFRTMPSRT_PROTOCOL)        += rtmpsrt.o
>  OBJS-$(CONFIG_FILE_PROTOCOL)             += file.o
>  OBJS-$(CONFIG_FTP_PROTOCOL)              += ftp.o urldecode.o
>  OBJS-$(CONFIG_GOPHER_PROTOCOL)           += gopher.o
> @@ -638,6 +639,7 @@ OBJS-$(CONFIG_RTMPS_PROTOCOL)            += rtmpproto.o rtmpdigest.o rtmppkt.o
>  OBJS-$(CONFIG_RTMPT_PROTOCOL)            += rtmpproto.o rtmpdigest.o rtmppkt.o
>  OBJS-$(CONFIG_RTMPTE_PROTOCOL)           += rtmpproto.o rtmpdigest.o rtmppkt.o
>  OBJS-$(CONFIG_RTMPTS_PROTOCOL)           += rtmpproto.o rtmpdigest.o rtmppkt.o
> +OBJS-$(CONFIG_RTMPSRT_PROTOCOL)          += rtmpproto.o rtmpdigest.o rtmppkt.o
>  OBJS-$(CONFIG_RTP_PROTOCOL)              += rtpproto.o ip.o
>  OBJS-$(CONFIG_SCTP_PROTOCOL)             += sctp.o
>  OBJS-$(CONFIG_SRTP_PROTOCOL)             += srtpproto.o srtp.o
> diff --git a/libavformat/protocols.c b/libavformat/protocols.c
> index 4b6b1c8e98..3f848338b0 100644
> --- a/libavformat/protocols.c
> +++ b/libavformat/protocols.c
> @@ -31,6 +31,7 @@ extern const URLProtocol ff_crypto_protocol;
>  extern const URLProtocol ff_data_protocol;
>  extern const URLProtocol ff_ffrtmpcrypt_protocol;
>  extern const URLProtocol ff_ffrtmphttp_protocol;
> +extern const URLProtocol ff_ffrtmpsrt_protocol;
>  extern const URLProtocol ff_file_protocol;
>  extern const URLProtocol ff_ftp_protocol;
>  extern const URLProtocol ff_gopher_protocol;
> @@ -51,6 +52,7 @@ extern const URLProtocol ff_rtmps_protocol;
>  extern const URLProtocol ff_rtmpt_protocol;
>  extern const URLProtocol ff_rtmpte_protocol;
>  extern const URLProtocol ff_rtmpts_protocol;
> +extern const URLProtocol ff_rtmpsrt_protocol;
>  extern const URLProtocol ff_rtp_protocol;
>  extern const URLProtocol ff_sctp_protocol;
>  extern const URLProtocol ff_srtp_protocol;
> diff --git a/libavformat/rtmpproto.c b/libavformat/rtmpproto.c
> index 5a540e3240..50e41662e8 100644
> --- a/libavformat/rtmpproto.c
> +++ b/libavformat/rtmpproto.c
> @@ -128,6 +128,7 @@ typedef struct RTMPContext {
>      char          auth_params[500];
>      int           do_reconnect;
>      int           auth_tried;
> +    int           rtmp_over_srt;
>  } RTMPContext;
>
>  #define PLAYER_KEY_OPEN_PART_LEN 30   ///< length of partial key used for first client digest signing
> @@ -2624,7 +2625,7 @@ static int rtmp_open(URLContext *s, const char *uri, int flags, AVDictionary **o
>          }
>      }
>
> -    if (rt->listen && strcmp(proto, "rtmp")) {
> +    if (rt->listen && strcmp(proto, "rtmp") && strcmp(proto, "rtmpsrt")) {
>          av_log(s, AV_LOG_ERROR, "rtmp_listen not available for %s\n",
>                 proto);
>          return AVERROR(EINVAL);
> @@ -2647,6 +2648,12 @@ static int rtmp_open(URLContext *s, const char *uri, int flags, AVDictionary **o
>          /* open the encrypted connection */
>          ff_url_join(buf, sizeof(buf), "ffrtmpcrypt", NULL, hostname, port, NULL);
>          rt->encrypted = 1;
> +    } else if (!strcmp(proto, "rtmpsrt") || rt->rtmp_over_srt) {
> +        if (rt->listen)
> +            av_dict_set(opts, "mode", "listener", 1);
> +        else
> +            av_dict_set(opts, "mode", "caller", 1);
> +        ff_url_join(buf, sizeof(buf), "ffrtmpsrt", NULL, hostname, port, "%s", path);
>      } else {
>          /* open the tcp connection */
>          if (port < 0)
> @@ -3116,6 +3123,7 @@ static const AVOption rtmp_options[] = {
>      {"rtmp_listen", "Listen for incoming rtmp connections", OFFSET(listen), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC, "rtmp_listen" },
>      {"listen",      "Listen for incoming rtmp connections", OFFSET(listen), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC, "rtmp_listen" },
>      {"timeout", "Maximum timeout (in seconds) to wait for incoming connections. -1 is infinite. Implies -rtmp_listen 1",  OFFSET(listen_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, DEC, "rtmp_listen" },
> +    {"rtmp_srt", "Force RTMP over SRT",  OFFSET(rtmp_over_srt), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC|ENC},
>      { NULL },
>  };
>
> @@ -3153,3 +3161,4 @@ RTMP_PROTOCOL(rtmps,  RTMPS)
>  RTMP_PROTOCOL(rtmpt,  RTMPT)
>  RTMP_PROTOCOL(rtmpte, RTMPTE)
>  RTMP_PROTOCOL(rtmpts, RTMPTS)
> +RTMP_PROTOCOL(rtmpsrt, RTMPSRT)
> diff --git a/libavformat/rtmpsrt.c b/libavformat/rtmpsrt.c
> new file mode 100644
> index 0000000000..0325973db9
> --- /dev/null
> +++ b/libavformat/rtmpsrt.c
> @@ -0,0 +1,167 @@
> +/*
> + * 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 "libavutil/avstring.h"
> +#include "libavutil/intfloat.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/time.h"
> +#include "internal.h"
> +#include "url.h"
> +
> +typedef struct RTMP_SrtContext {
> +    const AVClass *class;
> +    URLContext   *stream;
> +    char buf[1500];
> +    int buf_len;
> +    int buf_index;
> +    char *streamid;
> +} RTMP_SrtContext;
> +
> +static int rtmp_srt_open(URLContext *h, const char *uri, int flags, AVDictionary **opts)
> +{
> +    RTMP_SrtContext *s = h->priv_data;
> +    char buf[512];
> +    char host[256];
> +    int port;
> +    char path[1024];
> +    char *streamid;
> +    char *p;
> +
> +    av_url_split(NULL, 0, NULL, 0, host, sizeof(host), &port, path, sizeof(path), uri);
> +
> +    if (s->streamid) {
> +        streamid = av_strdup(s->streamid);
> +    } else {
> +        // rtmp path: /${app}/{stream}?txSecret=${txSecret}&txTime=${txTime}
> +        // streamid=#!::h=${rtmp-push-domain},r=${app}/${stream},txSecret=${txSecret},txTime=${txTime}
> +        for (p = path; *p; p++) {
> +            if (*p == '&' || *p == '?')
> +                *p = ',';
> +        }
> +        if (path[0] == '/')
> +            p = path + 1;
> +        else
> +            p = path;
> +        streamid = av_asprintf("#!::h=%s,r=%s", host, p);
> +    }
> +    av_log(h, AV_LOG_DEBUG, "streamid %s\n", streamid ? streamid : "");
> +    av_dict_set(opts, "streamid", streamid, AV_DICT_DONT_STRDUP_VAL);
> +
> +    av_dict_set(opts, "tlpktdrop", "0", 1);
> +    av_dict_set(opts, "payload_size", "max_size", 1);
> +
> +    ff_url_join(buf, sizeof(buf), "srt", NULL, host, port, NULL);
> +    return ffurl_open_whitelist(
> +        &s->stream, buf, AVIO_FLAG_READ_WRITE, &h->interrupt_callback, opts,
> +        h->protocol_whitelist, h->protocol_blacklist, h);
> +}
> +
> +static int read_from_buf(RTMP_SrtContext *s, unsigned char *buf, int size)
> +{
> +    int min = FFMIN(s->buf_len, size);
> +    memcpy(buf, s->buf + s->buf_index, min);
> +    if (min == s->buf_len) {
> +        s->buf_len = 0;
> +        s->buf_index = 0;
> +    } else {
> +        s->buf_len -= min;
> +        s->buf_index += min;
> +    }
> +    return min;
> +}
> +
> +static int rtmp_srt_read(URLContext *h, unsigned char *buf, int size)
> +{
> +    int ret;
> +    RTMP_SrtContext *s = h->priv_data;
> +    if (s->buf_len > 0) {
> +        return read_from_buf(s, buf, size);
> +    }
> +
> +    if (h->flags & AVIO_FLAG_NONBLOCK)
> +        s->stream->flags |= AVIO_FLAG_NONBLOCK;
> +    else
> +        s->stream->flags &= ~AVIO_FLAG_NONBLOCK;
> +    ret = ffurl_read(s->stream, s->buf, s->stream->max_packet_size);
> +    if (ret < 0) {
> +        return ret;
> +    }
> +    s->buf_len = ret;
> +    s->buf_index = 0;
> +    return read_from_buf(s, buf, size);
> +}
> +
> +static int rtmp_srt_write(URLContext *h, const unsigned char *buf, int size)
> +{
> +    int ret;
> +    int n;
> +    int len = 0;
> +    RTMP_SrtContext *s = h->priv_data;
> +
> +    if (h->flags & AVIO_FLAG_NONBLOCK)
> +        s->stream->flags |= AVIO_FLAG_NONBLOCK;
> +    else
> +        s->stream->flags &= ~AVIO_FLAG_NONBLOCK;
> +    while (size > 0) {
> +        n = size > s->stream->max_packet_size ? s->stream->max_packet_size : size;
> +        ret = ffurl_write(s->stream, buf + len, n);
> +        if (ret < 0) {
> +            return ret;
> +        }
> +        len += ret;
> +        size -= ret;
> +    }
> +
> +    return len;
> +}
> +
> +static int rtmp_srt_close(URLContext *h)
> +{
> +    RTMP_SrtContext *s = h->priv_data;
> +    return ffurl_closep(&s->stream);
> +}
> +
> +#define OFFSET(x) offsetof(RTMP_SrtContext, x)
> +#define DEC AV_OPT_FLAG_DECODING_PARAM
> +#define ENC AV_OPT_FLAG_ENCODING_PARAM
> +
> +static const AVOption ffrtmpsrt_options[] = {
> +    // There is a streamid option in ffmpeg_opt. When libsrt is used by rtmp,
> +    // the streamid option was passed to ffmpeg_opt and leads to error.
> +    { "rtmpsrt_streamid", "A string of up to 512 characters that an Initiator can pass to a Responder", OFFSET(streamid), AV_OPT_TYPE_STRING, { .str = NULL }, .flags = DEC|ENC },
> +    { NULL },
> +};
> +
> +static const AVClass ffrtmpsrt_class = {
> +    .class_name = "ffrtmpsrt",
> +    .item_name  = av_default_item_name,
> +    .option     = ffrtmpsrt_options,
> +    .version    = LIBAVUTIL_VERSION_INT,
> +};
> +
> +const URLProtocol ff_ffrtmpsrt_protocol = {
> +    .name           = "ffrtmpsrt",
> +    .url_open2      = rtmp_srt_open,
> +    .url_read       = rtmp_srt_read,
> +    .url_write      = rtmp_srt_write,
> +    .url_close      = rtmp_srt_close,
> +    .priv_data_size = sizeof(RTMP_SrtContext),
> +    .flags          = URL_PROTOCOL_FLAG_NETWORK,
> +    .priv_data_class= &ffrtmpsrt_class,
> +    .default_whitelist = "srt",
> +};
zhilizhao(赵志立) May 18, 2021, 4:32 p.m. UTC | #2
> On May 19, 2021, at 12:19 AM, Thomas Volkert <silvo@gmx.net> wrote:
> 
> Hi,
> 
> from my understand "ffrtmpsrt" combines rtmp (with flv (de-)muxing),
> which demands by design for a transmission without packet loss, with a
> transmission protocol based on UDP, which can have packet loss. So, I am
> not sure if we actually should add something like this to our source code.
> 

The patch is a (bad) test case for patch 1/2. I can't find a simple way to test the
bidirectional ability of libsrt. It's not intend for merge as the email subject says.

For the packet loss part, "tlpktdrop" is disabled explicitly. And libsrt has a file mode.
RTMP over SRT doesn't make much sense anyway.

> Best regards,
> Thomas.
> 
> On 18.05.2021 18:03, Zhao Zhili wrote:
>> ---
>> Test with:
>> ./ffplay -listen 1 rtmpsrt://127.0.0.1:8888
>> ./ffmpeg -re -i bunny.mp4 -c copy -f flv rtmpsrt://127.0.0.1:8888
>> 
>> configure               |   2 +
>> libavformat/Makefile    |   2 +
>> libavformat/protocols.c |   2 +
>> libavformat/rtmpproto.c |  11 ++-
>> libavformat/rtmpsrt.c   | 167 ++++++++++++++++++++++++++++++++++++++++
>> 5 files changed, 183 insertions(+), 1 deletion(-)
>> create mode 100644 libavformat/rtmpsrt.c
>> 
>> diff --git a/configure b/configure
>> index 82367fd30d..76cd56477a 100755
>> --- a/configure
>> +++ b/configure
>> @@ -3476,6 +3476,7 @@ ffrtmpcrypt_protocol_deps_any="gcrypt gmp openssl mbedtls"
>> ffrtmpcrypt_protocol_select="tcp_protocol"
>> ffrtmphttp_protocol_conflict="librtmp_protocol"
>> ffrtmphttp_protocol_select="http_protocol"
>> +ffrtmpsrt_protocol_select="libsrt_protocol"
>> ftp_protocol_select="tcp_protocol"
>> gopher_protocol_select="tcp_protocol"
>> gophers_protocol_select="tls_protocol"
>> @@ -3502,6 +3503,7 @@ rtmpte_protocol_select="ffrtmpcrypt_protocol ffrtmphttp_protocol"
>> rtmpte_protocol_suggest="zlib"
>> rtmpts_protocol_select="ffrtmphttp_protocol https_protocol"
>> rtmpts_protocol_suggest="zlib"
>> +rtmpsrt_protocol_select="ffrtmpsrt_protocol"
>> rtp_protocol_select="udp_protocol"
>> schannel_conflict="openssl gnutls libtls mbedtls"
>> sctp_protocol_deps="struct_sctp_event_subscribe struct_msghdr_msg_flags"
>> diff --git a/libavformat/Makefile b/libavformat/Makefile
>> index 85b5d8e7eb..7770fb2f8c 100644
>> --- a/libavformat/Makefile
>> +++ b/libavformat/Makefile
>> @@ -618,6 +618,7 @@ OBJS-$(CONFIG_CRYPTO_PROTOCOL)           += crypto.o
>> OBJS-$(CONFIG_DATA_PROTOCOL)             += data_uri.o
>> OBJS-$(CONFIG_FFRTMPCRYPT_PROTOCOL)      += rtmpcrypt.o rtmpdigest.o rtmpdh.o
>> OBJS-$(CONFIG_FFRTMPHTTP_PROTOCOL)       += rtmphttp.o
>> +OBJS-$(CONFIG_FFRTMPSRT_PROTOCOL)        += rtmpsrt.o
>> OBJS-$(CONFIG_FILE_PROTOCOL)             += file.o
>> OBJS-$(CONFIG_FTP_PROTOCOL)              += ftp.o urldecode.o
>> OBJS-$(CONFIG_GOPHER_PROTOCOL)           += gopher.o
>> @@ -638,6 +639,7 @@ OBJS-$(CONFIG_RTMPS_PROTOCOL)            += rtmpproto.o rtmpdigest.o rtmppkt.o
>> OBJS-$(CONFIG_RTMPT_PROTOCOL)            += rtmpproto.o rtmpdigest.o rtmppkt.o
>> OBJS-$(CONFIG_RTMPTE_PROTOCOL)           += rtmpproto.o rtmpdigest.o rtmppkt.o
>> OBJS-$(CONFIG_RTMPTS_PROTOCOL)           += rtmpproto.o rtmpdigest.o rtmppkt.o
>> +OBJS-$(CONFIG_RTMPSRT_PROTOCOL)          += rtmpproto.o rtmpdigest.o rtmppkt.o
>> OBJS-$(CONFIG_RTP_PROTOCOL)              += rtpproto.o ip.o
>> OBJS-$(CONFIG_SCTP_PROTOCOL)             += sctp.o
>> OBJS-$(CONFIG_SRTP_PROTOCOL)             += srtpproto.o srtp.o
>> diff --git a/libavformat/protocols.c b/libavformat/protocols.c
>> index 4b6b1c8e98..3f848338b0 100644
>> --- a/libavformat/protocols.c
>> +++ b/libavformat/protocols.c
>> @@ -31,6 +31,7 @@ extern const URLProtocol ff_crypto_protocol;
>> extern const URLProtocol ff_data_protocol;
>> extern const URLProtocol ff_ffrtmpcrypt_protocol;
>> extern const URLProtocol ff_ffrtmphttp_protocol;
>> +extern const URLProtocol ff_ffrtmpsrt_protocol;
>> extern const URLProtocol ff_file_protocol;
>> extern const URLProtocol ff_ftp_protocol;
>> extern const URLProtocol ff_gopher_protocol;
>> @@ -51,6 +52,7 @@ extern const URLProtocol ff_rtmps_protocol;
>> extern const URLProtocol ff_rtmpt_protocol;
>> extern const URLProtocol ff_rtmpte_protocol;
>> extern const URLProtocol ff_rtmpts_protocol;
>> +extern const URLProtocol ff_rtmpsrt_protocol;
>> extern const URLProtocol ff_rtp_protocol;
>> extern const URLProtocol ff_sctp_protocol;
>> extern const URLProtocol ff_srtp_protocol;
>> diff --git a/libavformat/rtmpproto.c b/libavformat/rtmpproto.c
>> index 5a540e3240..50e41662e8 100644
>> --- a/libavformat/rtmpproto.c
>> +++ b/libavformat/rtmpproto.c
>> @@ -128,6 +128,7 @@ typedef struct RTMPContext {
>>     char          auth_params[500];
>>     int           do_reconnect;
>>     int           auth_tried;
>> +    int           rtmp_over_srt;
>> } RTMPContext;
>> 
>> #define PLAYER_KEY_OPEN_PART_LEN 30   ///< length of partial key used for first client digest signing
>> @@ -2624,7 +2625,7 @@ static int rtmp_open(URLContext *s, const char *uri, int flags, AVDictionary **o
>>         }
>>     }
>> 
>> -    if (rt->listen && strcmp(proto, "rtmp")) {
>> +    if (rt->listen && strcmp(proto, "rtmp") && strcmp(proto, "rtmpsrt")) {
>>         av_log(s, AV_LOG_ERROR, "rtmp_listen not available for %s\n",
>>                proto);
>>         return AVERROR(EINVAL);
>> @@ -2647,6 +2648,12 @@ static int rtmp_open(URLContext *s, const char *uri, int flags, AVDictionary **o
>>         /* open the encrypted connection */
>>         ff_url_join(buf, sizeof(buf), "ffrtmpcrypt", NULL, hostname, port, NULL);
>>         rt->encrypted = 1;
>> +    } else if (!strcmp(proto, "rtmpsrt") || rt->rtmp_over_srt) {
>> +        if (rt->listen)
>> +            av_dict_set(opts, "mode", "listener", 1);
>> +        else
>> +            av_dict_set(opts, "mode", "caller", 1);
>> +        ff_url_join(buf, sizeof(buf), "ffrtmpsrt", NULL, hostname, port, "%s", path);
>>     } else {
>>         /* open the tcp connection */
>>         if (port < 0)
>> @@ -3116,6 +3123,7 @@ static const AVOption rtmp_options[] = {
>>     {"rtmp_listen", "Listen for incoming rtmp connections", OFFSET(listen), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC, "rtmp_listen" },
>>     {"listen",      "Listen for incoming rtmp connections", OFFSET(listen), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC, "rtmp_listen" },
>>     {"timeout", "Maximum timeout (in seconds) to wait for incoming connections. -1 is infinite. Implies -rtmp_listen 1", OFFSET(listen_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, DEC, "rtmp_listen" },
>> +    {"rtmp_srt", "Force RTMP over SRT",  OFFSET(rtmp_over_srt), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC|ENC},
>>     { NULL },
>> };
>> 
>> @@ -3153,3 +3161,4 @@ RTMP_PROTOCOL(rtmps,  RTMPS)
>> RTMP_PROTOCOL(rtmpt,  RTMPT)
>> RTMP_PROTOCOL(rtmpte, RTMPTE)
>> RTMP_PROTOCOL(rtmpts, RTMPTS)
>> +RTMP_PROTOCOL(rtmpsrt, RTMPSRT)
>> diff --git a/libavformat/rtmpsrt.c b/libavformat/rtmpsrt.c
>> new file mode 100644
>> index 0000000000..0325973db9
>> --- /dev/null
>> +++ b/libavformat/rtmpsrt.c
>> @@ -0,0 +1,167 @@
>> +/*
>> + * 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 "libavutil/avstring.h"
>> +#include "libavutil/intfloat.h"
>> +#include "libavutil/opt.h"
>> +#include "libavutil/time.h"
>> +#include "internal.h"
>> +#include "url.h"
>> +
>> +typedef struct RTMP_SrtContext {
>> +    const AVClass *class;
>> +    URLContext   *stream;
>> +    char buf[1500];
>> +    int buf_len;
>> +    int buf_index;
>> +    char *streamid;
>> +} RTMP_SrtContext;
>> +
>> +static int rtmp_srt_open(URLContext *h, const char *uri, int flags, AVDictionary **opts)
>> +{
>> +    RTMP_SrtContext *s = h->priv_data;
>> +    char buf[512];
>> +    char host[256];
>> +    int port;
>> +    char path[1024];
>> +    char *streamid;
>> +    char *p;
>> +
>> +    av_url_split(NULL, 0, NULL, 0, host, sizeof(host), &port, path, sizeof(path), uri);
>> +
>> +    if (s->streamid) {
>> +        streamid = av_strdup(s->streamid);
>> +    } else {
>> +        // rtmp path: /${app}/{stream}?txSecret=${txSecret}&txTime=${txTime}
>> +        // streamid=#!::h=${rtmp-push-domain},r=${app}/${stream},txSecret=${txSecret},txTime=${txTime}
>> +        for (p = path; *p; p++) {
>> +            if (*p == '&' || *p == '?')
>> +                *p = ',';
>> +        }
>> +        if (path[0] == '/')
>> +            p = path + 1;
>> +        else
>> +            p = path;
>> +        streamid = av_asprintf("#!::h=%s,r=%s", host, p);
>> +    }
>> +    av_log(h, AV_LOG_DEBUG, "streamid %s\n", streamid ? streamid : "");
>> +    av_dict_set(opts, "streamid", streamid, AV_DICT_DONT_STRDUP_VAL);
>> +
>> +    av_dict_set(opts, "tlpktdrop", "0", 1);
>> +    av_dict_set(opts, "payload_size", "max_size", 1);
>> +
>> +    ff_url_join(buf, sizeof(buf), "srt", NULL, host, port, NULL);
>> +    return ffurl_open_whitelist(
>> +        &s->stream, buf, AVIO_FLAG_READ_WRITE, &h->interrupt_callback, opts,
>> +        h->protocol_whitelist, h->protocol_blacklist, h);
>> +}
>> +
>> +static int read_from_buf(RTMP_SrtContext *s, unsigned char *buf, int size)
>> +{
>> +    int min = FFMIN(s->buf_len, size);
>> +    memcpy(buf, s->buf + s->buf_index, min);
>> +    if (min == s->buf_len) {
>> +        s->buf_len = 0;
>> +        s->buf_index = 0;
>> +    } else {
>> +        s->buf_len -= min;
>> +        s->buf_index += min;
>> +    }
>> +    return min;
>> +}
>> +
>> +static int rtmp_srt_read(URLContext *h, unsigned char *buf, int size)
>> +{
>> +    int ret;
>> +    RTMP_SrtContext *s = h->priv_data;
>> +    if (s->buf_len > 0) {
>> +        return read_from_buf(s, buf, size);
>> +    }
>> +
>> +    if (h->flags & AVIO_FLAG_NONBLOCK)
>> +        s->stream->flags |= AVIO_FLAG_NONBLOCK;
>> +    else
>> +        s->stream->flags &= ~AVIO_FLAG_NONBLOCK;
>> +    ret = ffurl_read(s->stream, s->buf, s->stream->max_packet_size);
>> +    if (ret < 0) {
>> +        return ret;
>> +    }
>> +    s->buf_len = ret;
>> +    s->buf_index = 0;
>> +    return read_from_buf(s, buf, size);
>> +}
>> +
>> +static int rtmp_srt_write(URLContext *h, const unsigned char *buf, int size)
>> +{
>> +    int ret;
>> +    int n;
>> +    int len = 0;
>> +    RTMP_SrtContext *s = h->priv_data;
>> +
>> +    if (h->flags & AVIO_FLAG_NONBLOCK)
>> +        s->stream->flags |= AVIO_FLAG_NONBLOCK;
>> +    else
>> +        s->stream->flags &= ~AVIO_FLAG_NONBLOCK;
>> +    while (size > 0) {
>> +        n = size > s->stream->max_packet_size ? s->stream->max_packet_size : size;
>> +        ret = ffurl_write(s->stream, buf + len, n);
>> +        if (ret < 0) {
>> +            return ret;
>> +        }
>> +        len += ret;
>> +        size -= ret;
>> +    }
>> +
>> +    return len;
>> +}
>> +
>> +static int rtmp_srt_close(URLContext *h)
>> +{
>> +    RTMP_SrtContext *s = h->priv_data;
>> +    return ffurl_closep(&s->stream);
>> +}
>> +
>> +#define OFFSET(x) offsetof(RTMP_SrtContext, x)
>> +#define DEC AV_OPT_FLAG_DECODING_PARAM
>> +#define ENC AV_OPT_FLAG_ENCODING_PARAM
>> +
>> +static const AVOption ffrtmpsrt_options[] = {
>> +    // There is a streamid option in ffmpeg_opt. When libsrt is used by rtmp,
>> +    // the streamid option was passed to ffmpeg_opt and leads to error.
>> +    { "rtmpsrt_streamid", "A string of up to 512 characters that an Initiator can pass to a Responder", OFFSET(streamid), AV_OPT_TYPE_STRING, { .str = NULL }, .flags = DEC|ENC },
>> +    { NULL },
>> +};
>> +
>> +static const AVClass ffrtmpsrt_class = {
>> +    .class_name = "ffrtmpsrt",
>> +    .item_name  = av_default_item_name,
>> +    .option     = ffrtmpsrt_options,
>> +    .version    = LIBAVUTIL_VERSION_INT,
>> +};
>> +
>> +const URLProtocol ff_ffrtmpsrt_protocol = {
>> +    .name           = "ffrtmpsrt",
>> +    .url_open2      = rtmp_srt_open,
>> +    .url_read       = rtmp_srt_read,
>> +    .url_write      = rtmp_srt_write,
>> +    .url_close      = rtmp_srt_close,
>> +    .priv_data_size = sizeof(RTMP_SrtContext),
>> +    .flags          = URL_PROTOCOL_FLAG_NETWORK,
>> +    .priv_data_class= &ffrtmpsrt_class,
>> +    .default_whitelist = "srt",
>> +};
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org <mailto:ffmpeg-devel@ffmpeg.org>
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel <https://ffmpeg.org/mailman/listinfo/ffmpeg-devel>
> 
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org <mailto:ffmpeg-devel-request@ffmpeg.org> with subject "unsubscribe".
diff mbox series

Patch

diff --git a/configure b/configure
index 82367fd30d..76cd56477a 100755
--- a/configure
+++ b/configure
@@ -3476,6 +3476,7 @@  ffrtmpcrypt_protocol_deps_any="gcrypt gmp openssl mbedtls"
 ffrtmpcrypt_protocol_select="tcp_protocol"
 ffrtmphttp_protocol_conflict="librtmp_protocol"
 ffrtmphttp_protocol_select="http_protocol"
+ffrtmpsrt_protocol_select="libsrt_protocol"
 ftp_protocol_select="tcp_protocol"
 gopher_protocol_select="tcp_protocol"
 gophers_protocol_select="tls_protocol"
@@ -3502,6 +3503,7 @@  rtmpte_protocol_select="ffrtmpcrypt_protocol ffrtmphttp_protocol"
 rtmpte_protocol_suggest="zlib"
 rtmpts_protocol_select="ffrtmphttp_protocol https_protocol"
 rtmpts_protocol_suggest="zlib"
+rtmpsrt_protocol_select="ffrtmpsrt_protocol"
 rtp_protocol_select="udp_protocol"
 schannel_conflict="openssl gnutls libtls mbedtls"
 sctp_protocol_deps="struct_sctp_event_subscribe struct_msghdr_msg_flags"
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 85b5d8e7eb..7770fb2f8c 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -618,6 +618,7 @@  OBJS-$(CONFIG_CRYPTO_PROTOCOL)           += crypto.o
 OBJS-$(CONFIG_DATA_PROTOCOL)             += data_uri.o
 OBJS-$(CONFIG_FFRTMPCRYPT_PROTOCOL)      += rtmpcrypt.o rtmpdigest.o rtmpdh.o
 OBJS-$(CONFIG_FFRTMPHTTP_PROTOCOL)       += rtmphttp.o
+OBJS-$(CONFIG_FFRTMPSRT_PROTOCOL)        += rtmpsrt.o
 OBJS-$(CONFIG_FILE_PROTOCOL)             += file.o
 OBJS-$(CONFIG_FTP_PROTOCOL)              += ftp.o urldecode.o
 OBJS-$(CONFIG_GOPHER_PROTOCOL)           += gopher.o
@@ -638,6 +639,7 @@  OBJS-$(CONFIG_RTMPS_PROTOCOL)            += rtmpproto.o rtmpdigest.o rtmppkt.o
 OBJS-$(CONFIG_RTMPT_PROTOCOL)            += rtmpproto.o rtmpdigest.o rtmppkt.o
 OBJS-$(CONFIG_RTMPTE_PROTOCOL)           += rtmpproto.o rtmpdigest.o rtmppkt.o
 OBJS-$(CONFIG_RTMPTS_PROTOCOL)           += rtmpproto.o rtmpdigest.o rtmppkt.o
+OBJS-$(CONFIG_RTMPSRT_PROTOCOL)          += rtmpproto.o rtmpdigest.o rtmppkt.o
 OBJS-$(CONFIG_RTP_PROTOCOL)              += rtpproto.o ip.o
 OBJS-$(CONFIG_SCTP_PROTOCOL)             += sctp.o
 OBJS-$(CONFIG_SRTP_PROTOCOL)             += srtpproto.o srtp.o
diff --git a/libavformat/protocols.c b/libavformat/protocols.c
index 4b6b1c8e98..3f848338b0 100644
--- a/libavformat/protocols.c
+++ b/libavformat/protocols.c
@@ -31,6 +31,7 @@  extern const URLProtocol ff_crypto_protocol;
 extern const URLProtocol ff_data_protocol;
 extern const URLProtocol ff_ffrtmpcrypt_protocol;
 extern const URLProtocol ff_ffrtmphttp_protocol;
+extern const URLProtocol ff_ffrtmpsrt_protocol;
 extern const URLProtocol ff_file_protocol;
 extern const URLProtocol ff_ftp_protocol;
 extern const URLProtocol ff_gopher_protocol;
@@ -51,6 +52,7 @@  extern const URLProtocol ff_rtmps_protocol;
 extern const URLProtocol ff_rtmpt_protocol;
 extern const URLProtocol ff_rtmpte_protocol;
 extern const URLProtocol ff_rtmpts_protocol;
+extern const URLProtocol ff_rtmpsrt_protocol;
 extern const URLProtocol ff_rtp_protocol;
 extern const URLProtocol ff_sctp_protocol;
 extern const URLProtocol ff_srtp_protocol;
diff --git a/libavformat/rtmpproto.c b/libavformat/rtmpproto.c
index 5a540e3240..50e41662e8 100644
--- a/libavformat/rtmpproto.c
+++ b/libavformat/rtmpproto.c
@@ -128,6 +128,7 @@  typedef struct RTMPContext {
     char          auth_params[500];
     int           do_reconnect;
     int           auth_tried;
+    int           rtmp_over_srt;
 } RTMPContext;
 
 #define PLAYER_KEY_OPEN_PART_LEN 30   ///< length of partial key used for first client digest signing
@@ -2624,7 +2625,7 @@  static int rtmp_open(URLContext *s, const char *uri, int flags, AVDictionary **o
         }
     }
 
-    if (rt->listen && strcmp(proto, "rtmp")) {
+    if (rt->listen && strcmp(proto, "rtmp") && strcmp(proto, "rtmpsrt")) {
         av_log(s, AV_LOG_ERROR, "rtmp_listen not available for %s\n",
                proto);
         return AVERROR(EINVAL);
@@ -2647,6 +2648,12 @@  static int rtmp_open(URLContext *s, const char *uri, int flags, AVDictionary **o
         /* open the encrypted connection */
         ff_url_join(buf, sizeof(buf), "ffrtmpcrypt", NULL, hostname, port, NULL);
         rt->encrypted = 1;
+    } else if (!strcmp(proto, "rtmpsrt") || rt->rtmp_over_srt) {
+        if (rt->listen)
+            av_dict_set(opts, "mode", "listener", 1);
+        else
+            av_dict_set(opts, "mode", "caller", 1);
+        ff_url_join(buf, sizeof(buf), "ffrtmpsrt", NULL, hostname, port, "%s", path);
     } else {
         /* open the tcp connection */
         if (port < 0)
@@ -3116,6 +3123,7 @@  static const AVOption rtmp_options[] = {
     {"rtmp_listen", "Listen for incoming rtmp connections", OFFSET(listen), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC, "rtmp_listen" },
     {"listen",      "Listen for incoming rtmp connections", OFFSET(listen), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC, "rtmp_listen" },
     {"timeout", "Maximum timeout (in seconds) to wait for incoming connections. -1 is infinite. Implies -rtmp_listen 1",  OFFSET(listen_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, DEC, "rtmp_listen" },
+    {"rtmp_srt", "Force RTMP over SRT",  OFFSET(rtmp_over_srt), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC|ENC},
     { NULL },
 };
 
@@ -3153,3 +3161,4 @@  RTMP_PROTOCOL(rtmps,  RTMPS)
 RTMP_PROTOCOL(rtmpt,  RTMPT)
 RTMP_PROTOCOL(rtmpte, RTMPTE)
 RTMP_PROTOCOL(rtmpts, RTMPTS)
+RTMP_PROTOCOL(rtmpsrt, RTMPSRT)
diff --git a/libavformat/rtmpsrt.c b/libavformat/rtmpsrt.c
new file mode 100644
index 0000000000..0325973db9
--- /dev/null
+++ b/libavformat/rtmpsrt.c
@@ -0,0 +1,167 @@ 
+/*
+ * 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 "libavutil/avstring.h"
+#include "libavutil/intfloat.h"
+#include "libavutil/opt.h"
+#include "libavutil/time.h"
+#include "internal.h"
+#include "url.h"
+
+typedef struct RTMP_SrtContext {
+    const AVClass *class;
+    URLContext   *stream;
+    char buf[1500];
+    int buf_len;
+    int buf_index;
+    char *streamid;
+} RTMP_SrtContext;
+
+static int rtmp_srt_open(URLContext *h, const char *uri, int flags, AVDictionary **opts)
+{
+    RTMP_SrtContext *s = h->priv_data;
+    char buf[512];
+    char host[256];
+    int port;
+    char path[1024];
+    char *streamid;
+    char *p;
+
+    av_url_split(NULL, 0, NULL, 0, host, sizeof(host), &port, path, sizeof(path), uri);
+
+    if (s->streamid) {
+        streamid = av_strdup(s->streamid);
+    } else {
+        // rtmp path: /${app}/{stream}?txSecret=${txSecret}&txTime=${txTime}
+        // streamid=#!::h=${rtmp-push-domain},r=${app}/${stream},txSecret=${txSecret},txTime=${txTime}
+        for (p = path; *p; p++) {
+            if (*p == '&' || *p == '?')
+                *p = ',';
+        }
+        if (path[0] == '/')
+            p = path + 1;
+        else
+            p = path;
+        streamid = av_asprintf("#!::h=%s,r=%s", host, p);
+    }
+    av_log(h, AV_LOG_DEBUG, "streamid %s\n", streamid ? streamid : "");
+    av_dict_set(opts, "streamid", streamid, AV_DICT_DONT_STRDUP_VAL);
+
+    av_dict_set(opts, "tlpktdrop", "0", 1);
+    av_dict_set(opts, "payload_size", "max_size", 1);
+
+    ff_url_join(buf, sizeof(buf), "srt", NULL, host, port, NULL);
+    return ffurl_open_whitelist(
+        &s->stream, buf, AVIO_FLAG_READ_WRITE, &h->interrupt_callback, opts,
+        h->protocol_whitelist, h->protocol_blacklist, h);
+}
+
+static int read_from_buf(RTMP_SrtContext *s, unsigned char *buf, int size)
+{
+    int min = FFMIN(s->buf_len, size);
+    memcpy(buf, s->buf + s->buf_index, min);
+    if (min == s->buf_len) {
+        s->buf_len = 0;
+        s->buf_index = 0;
+    } else {
+        s->buf_len -= min;
+        s->buf_index += min;
+    }
+    return min;
+}
+
+static int rtmp_srt_read(URLContext *h, unsigned char *buf, int size)
+{
+    int ret;
+    RTMP_SrtContext *s = h->priv_data;
+    if (s->buf_len > 0) {
+        return read_from_buf(s, buf, size);
+    }
+
+    if (h->flags & AVIO_FLAG_NONBLOCK)
+        s->stream->flags |= AVIO_FLAG_NONBLOCK;
+    else
+        s->stream->flags &= ~AVIO_FLAG_NONBLOCK;
+    ret = ffurl_read(s->stream, s->buf, s->stream->max_packet_size);
+    if (ret < 0) {
+        return ret;
+    }
+    s->buf_len = ret;
+    s->buf_index = 0;
+    return read_from_buf(s, buf, size);
+}
+
+static int rtmp_srt_write(URLContext *h, const unsigned char *buf, int size)
+{
+    int ret;
+    int n;
+    int len = 0;
+    RTMP_SrtContext *s = h->priv_data;
+
+    if (h->flags & AVIO_FLAG_NONBLOCK)
+        s->stream->flags |= AVIO_FLAG_NONBLOCK;
+    else
+        s->stream->flags &= ~AVIO_FLAG_NONBLOCK;
+    while (size > 0) {
+        n = size > s->stream->max_packet_size ? s->stream->max_packet_size : size;
+        ret = ffurl_write(s->stream, buf + len, n);
+        if (ret < 0) {
+            return ret;
+        }
+        len += ret;
+        size -= ret;
+    }
+
+    return len;
+}
+
+static int rtmp_srt_close(URLContext *h)
+{
+    RTMP_SrtContext *s = h->priv_data;
+    return ffurl_closep(&s->stream);
+}
+
+#define OFFSET(x) offsetof(RTMP_SrtContext, x)
+#define DEC AV_OPT_FLAG_DECODING_PARAM
+#define ENC AV_OPT_FLAG_ENCODING_PARAM
+
+static const AVOption ffrtmpsrt_options[] = {
+    // There is a streamid option in ffmpeg_opt. When libsrt is used by rtmp,
+    // the streamid option was passed to ffmpeg_opt and leads to error.
+    { "rtmpsrt_streamid", "A string of up to 512 characters that an Initiator can pass to a Responder", OFFSET(streamid), AV_OPT_TYPE_STRING, { .str = NULL }, .flags = DEC|ENC },
+    { NULL },
+};
+
+static const AVClass ffrtmpsrt_class = {
+    .class_name = "ffrtmpsrt",
+    .item_name  = av_default_item_name,
+    .option     = ffrtmpsrt_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+const URLProtocol ff_ffrtmpsrt_protocol = {
+    .name           = "ffrtmpsrt",
+    .url_open2      = rtmp_srt_open,
+    .url_read       = rtmp_srt_read,
+    .url_write      = rtmp_srt_write,
+    .url_close      = rtmp_srt_close,
+    .priv_data_size = sizeof(RTMP_SrtContext),
+    .flags          = URL_PROTOCOL_FLAG_NETWORK,
+    .priv_data_class= &ffrtmpsrt_class,
+    .default_whitelist = "srt",
+};