From patchwork Fri Apr 19 17:20:50 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Thomas Jammet X-Patchwork-Id: 12817 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id DAC11447CBB for ; Fri, 19 Apr 2019 20:21:22 +0300 (EEST) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id B6B1068A7AC; Fri, 19 Apr 2019 20:21:22 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-wr1-f45.google.com (mail-wr1-f45.google.com [209.85.221.45]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 5C35B68053A for ; Fri, 19 Apr 2019 20:21:16 +0300 (EEST) Received: by mail-wr1-f45.google.com with SMTP id g3so7657189wrx.9 for ; Fri, 19 Apr 2019 10:21:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:from:date:message-id:subject:to; bh=d3iKzsC0aj2hyvtSJGDPKjD2YES0jiNEKL9f2bKq8d4=; b=alrYq3ox9hBxP9WaS6i4SCUYLbs3oS+R18pod2v7abrEsaV+Vl+JTyT6cTMGU9IGVf LcrPWf46c6FipkIgO+mYbKjxc9JFHaxlXPhiUQpM8xrXOLY3LEEGuj5gFK46qm4AaBki kQwrgFN8Fd9WmbQDPQCIoJ9lKufVvA3rz+NutR78wqzGNvatUpUUtaPaJLziZ/3ESEEV nUykJ4DRxiIkbcMJ7rKDL54w3SvvXxQsiLHYEfBxqxfX7ZqtL+LV8lyDC3Lft8OZ6ftb 9kndedzYp1iO2Zjm6QbOOReYdWVMbM/0xKLpC2QLrJoOEwNdblATBJqp4LoXZf2WZxN+ pM+w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:from:date:message-id:subject:to; bh=d3iKzsC0aj2hyvtSJGDPKjD2YES0jiNEKL9f2bKq8d4=; b=gH8BS7bAga0jtdR0LpQauYv9NUvxktmyiAdiWE+aqtcNtgBCIvmQPpv0LI4GU6m8Sy u2FVv5p7TcWxdYrOmDo7cfc2eV4LNinaXP1/YCxgcbfgL2Qqbq2wLdDy4DQd1mhllwp6 lYIoBEZrCHwqm8Ix22EakwoSiwmpNebd5UncubImWS7USrgoCXNJogUgcvMjESitH8lZ Y9NX1Y0xgwiaZmc2V9lDY/0TU2gjXZ386vmL8FGdFW1v7tZ3rEAw9onPnAxqt8ECmmde GMRgNXMNlgJexjn4iFyvIYkdqENkM+WBnHGm6eHJ03035K55HIfHG2syf3wC32S0DqjE 7reA== X-Gm-Message-State: APjAAAUJtNvJKl1+kluLA99VSaNu/MKFYQi+VF8q18xpt2g+TkjhvkU4 y23a3ZU+uY5ET6J7nBbaJ2349PEHO7A1iwyeYkyxBj4d X-Google-Smtp-Source: APXvYqwgM+9zI4dub1CWmgV6fY53eo20vKJtXYltZBdt5SZC+4VIsnpfXeQJZnX9wNtk8kIWy4PiJ4KbaXwTtmcCMa8= X-Received: by 2002:adf:d848:: with SMTP id k8mr4083813wrl.185.1555694475238; Fri, 19 Apr 2019 10:21:15 -0700 (PDT) MIME-Version: 1.0 From: Thomas Jammet Date: Fri, 19 Apr 2019 19:20:50 +0200 Message-ID: To: FFmpeg development discussions and patches Subject: [FFmpeg-devel] [PATCH] Add RTMFP support with librtmfp library X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Here is the updated version of the patch. Thomas Jammet --- configure | 4 + doc/protocols.texi | 120 +++++++++++++++++++ libavformat/Makefile | 1 + libavformat/librtmfp.c | 247 ++++++++++++++++++++++++++++++++++++++++ libavformat/protocols.c | 1 + 5 files changed, 373 insertions(+) create mode 100644 libavformat/librtmfp.c -- 2.19.1 diff --git a/configure b/configure index e10e2c2c46..1ba9c08621 100755 --- a/configure +++ b/configure @@ -257,6 +257,7 @@ External library support: --enable-librsvg enable SVG rasterization via librsvg [no] --enable-librubberband enable rubberband needed for rubberband filter [no] --enable-librtmp enable RTMP[E] support via librtmp [no] + --enable-librtmfp enable RTMFP support via librtmfp [no] --enable-libshine enable fixed-point MP3 encoding via libshine [no] --enable-libsmbclient enable Samba protocol via libsmbclient [no] --enable-libsnappy enable Snappy compression, needed for hap encoding [no] @@ -1775,6 +1776,7 @@ EXTERNAL_LIBRARY_LIST=" libpulse librsvg librtmp + librtmfp libshine libsmbclient libsnappy @@ -3386,6 +3388,7 @@ librtmpe_protocol_deps="librtmp" librtmps_protocol_deps="librtmp" librtmpt_protocol_deps="librtmp" librtmpte_protocol_deps="librtmp" +librtmfp_protocol_deps="librtmfp" libsmbclient_protocol_deps="libsmbclient gplv3" libsrt_protocol_deps="libsrt" libsrt_protocol_select="network" @@ -6189,6 +6192,7 @@ enabled libopus && { enabled libpulse && require_pkg_config libpulse libpulse pulse/pulseaudio.h pa_context_new enabled librsvg && require_pkg_config librsvg librsvg-2.0 librsvg-2.0/librsvg/rsvg.h rsvg_handle_render_cairo enabled librtmp && require_pkg_config librtmp librtmp librtmp/rtmp.h RTMP_Socket +enabled librtmfp && require_pkg_config librtmfp librtmfp librtmfp/librtmfp.h RTMFP_Connect enabled librubberband && require_pkg_config librubberband "rubberband >= 1.8.1" rubberband/rubberband-c.h rubberband_new -lstdc++ && append librubberband_extralibs "-lstdc++" enabled libshine && require_pkg_config libshine shine shine/layer3.h shine_encode_buffer enabled libsmbclient && { check_pkg_config libsmbclient smbclient libsmbclient.h smbc_init || diff --git a/doc/protocols.texi b/doc/protocols.texi index 3e4e7af3d4..61852d4d48 100644 --- a/doc/protocols.texi +++ b/doc/protocols.texi @@ -842,6 +842,126 @@ To play the same stream using @command{ffplay}: ffplay "rtmp://myserver/live/mystream live=1" @end example +@section librtmfp + +Real-Time Media Flow Protocol. + +Requires the presence of the librtmfp headers and library during +configuration. You need to explicitly configure the build with +"--enable-librtmfp". + +This protocol provides most functionalities of the UDP protocol RTMFP : +unicast, direct p2p and multicast (via NetGroup). + +The required syntax for an RTMFP URL is: +@example +rtmfp://@var{hostname}[:@var{port}][/@var{app}][/@var{playpath}] +@end example + +The accepted parameters are: +@table @option + +@item hostname +The address of the RTMFP server. + +@item port +The number of the UDP port to use (by default is 1935). + +@item app +It is the name of the application to access. It usually corresponds to the +path where the application is installed on the RTMFP server (e.g. +/ondemand/,/flash/live/, etc.). + +@item playpath +It is the path or name of the resource to play with reference to the +application specified in app. +@end table + +Additionally, the following parameters can be set via command line options +(or in code via @code{AVOption}s): +@table @option + +@item rtmfp_socketReceiveSize +Socket receive buffer size. + +@item rtmfp_socketSendSize +Socket send buffer size. + +@item rtmfp_audioUnbuffered +Unbuffered audio mode (default to false). + +@item rtmfp_videoUnbuffered +Unbuffered video mode (default to false). + +@item rtmfp_peerId +Connect to a peer for playing. + +@item rtmfp_p2pPublishing +Publish the stream in p2p mode (default to false). + +@item rtmfp_netgroup +Publish/Play the stream into a NetGroup (multicast). + +@item rtmfp_fallbackUrl +(Only with @code{rtmfp_negtroup}) Try to play an RTMFP unicast stream url +until the NetGroup connection is not ready. Can produce undefined behavior +if the stream codecs are different. + +@item rtmfp_fallbackTimeout +(Only with @code{rtmfp_negtroup}) Set the timeout in milliseconds to start +fallback to unicast. + +@item rtmfp_disableRateControl +(Only with @code{rtmfp_negtroup}) Disable the P2P connection rate control +to avoid unwanted disconnection. + +@item rtmfp_pushLimit +(Only with @code{rtmfp_negtroup}) Specifies the maximum number (-1) of +peers to which we will send push fragments. + +@item rtmfp_updatePeriod +(Only with @code{rtmfp_negtroup}) Specifies the interval in milliseconds +between messages sent to peers informating them that the local node has +new p2p multicast media fragments available. + +@item rtmfp_windowDuration +(Only with @code{rtmfp_negtroup}) Specifies the duration in milliseconds +of the p2p multicast reassembly window. + +@item rtmfp_swfurl +URL of the SWF player. By default no value will be sent. + +@item rtmfp_app +Name of application to connect to on the RTMFP server (by default 'live'). + +@item rtmfp_pageurl +URL of the web page in which the media was embedded. By default no value +will be sent. + +@item rtmfp_flashver +Version of the Flash plugin used to run the SWF player. By default +@code{WIN 20,0,0,286}. + +@item rtmfp_host +IPv4 host address to bind to (use this if you ave multiple interfaces). + +@item rtmfp_hostIPv6 +IPv6 host address to bind to (use this if you ave multiple interfaces). +@end table + +For example to read with @command{ffplay} a multimedia resource named +"sample" from the application "vod" from an RTMFP server "myserver": +@example +ffplay rtmfp://myserver/vod/sample +@end example + +To publish a multimedia resource named "sample" to an RTMFP server: +@example +ffmpeg -re -i -f flv rtmfp://myserver/sample +@end example + +For more information see: @url{https://github.com/MonaSolutions/librtmfp}. + @section rtp Real-time Transport Protocol. diff --git a/libavformat/Makefile b/libavformat/Makefile index 99be60d184..be1a7b15c0 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -627,6 +627,7 @@ OBJS-$(CONFIG_LIBRTMPE_PROTOCOL) += librtmp.o OBJS-$(CONFIG_LIBRTMPS_PROTOCOL) += librtmp.o OBJS-$(CONFIG_LIBRTMPT_PROTOCOL) += librtmp.o OBJS-$(CONFIG_LIBRTMPTE_PROTOCOL) += librtmp.o +OBJS-$(CONFIG_LIBRTMFP_PROTOCOL) += librtmfp.o OBJS-$(CONFIG_LIBSMBCLIENT_PROTOCOL) += libsmbclient.o OBJS-$(CONFIG_LIBSRT_PROTOCOL) += libsrt.o OBJS-$(CONFIG_LIBSSH_PROTOCOL) += libssh.o diff --git a/libavformat/librtmfp.c b/libavformat/librtmfp.c new file mode 100644 index 0000000000..a9a72f36e2 --- /dev/null +++ b/libavformat/librtmfp.c @@ -0,0 +1,247 @@ +/* + * RTMFP network protocol + * Copyright (c) 2019 Thomas Jammet + * + * 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 + */ + +/** + * @file + * RTMFP protocol based on https://github.com/MonaSolutions/librtmfp librtmfp + */ + +#include "libavutil/avstring.h" +#include "libavutil/opt.h" +#include "avformat.h" +#if CONFIG_NETWORK +#include "network.h" +#endif +#include + +#include + +typedef struct LibRTMFPContext { + const AVClass* class; + RTMFPConfig rtmfp; + unsigned int id; + int audioUnbuffered; + int videoUnbuffered; + int p2pPublishing; + char* peerId; + char* publication; + unsigned short streamId; + const char* swfUrl; + const char* app; + const char* pageUrl; + const char* flashVer; + const char* host; + const char* hostIPv6; + + // General options + int socketReceiveSize; + int socketSendSize; + + // NetGroup members + RTMFPGroupConfig group; + char* netgroup; + unsigned int updatePeriod; + unsigned int windowDuration; + unsigned int pushLimit; + char* fallbackUrl; + unsigned int fallbackTimeout; + int disableRateCtl; +} LibRTMFPContext; + +static void rtmfp_log(unsigned int level, const char* fileName, long line, const char* message) +{ + const char* strLevel = ""; + + switch (level) { + default: + case 1: level = AV_LOG_FATAL; strLevel = "FATAL"; break; + case 2: + case 3: level = AV_LOG_ERROR; strLevel = "ERROR"; break; + case 4: level = AV_LOG_WARNING; strLevel = "WARN"; break; + case 5: + case 6: level = AV_LOG_INFO; strLevel = "INFO"; break; + case 7: level = AV_LOG_DEBUG; strLevel = "DEBUG"; break; + case 8: level = AV_LOG_TRACE; strLevel = "TRACE"; break; + } + + av_log(NULL, level, "[%s] %s\n", strLevel, message); +} + +static int rtmfp_close(URLContext *s) +{ + av_log(s, AV_LOG_INFO, "Closing RTMFP connection...\n"); + RTMFP_Terminate(); + return 0; +} + +static void onStatusEvent(const char* code, const char* description) { + av_log(NULL, AV_LOG_INFO, "onStatusEvent : %s - %s\n", code, description); +} + +/** + * Open RTMFP connection and verify that the stream can be played. + * + * URL syntax: rtmp://server[:port][/app][/playpath][ keyword=value]... + * where 'app' is first one or two directories in the path + * (e.g. /ondemand/, /flash/live/, etc.) + * and 'playpath' is a file name (the rest of the path, + * may be prefixed with "mp4:") + * + * Additional RTMFP library options may be appended as + * space-separated key-value pairs. + */ +static int rtmfp_open(URLContext *s, const char *uri, int flags) +{ + LibRTMFPContext *ctx = s->priv_data; + int level = 0; + + switch (av_log_get_level()) { + case AV_LOG_FATAL: level = 1; break; + case AV_LOG_ERROR: level = 3; break; + case AV_LOG_WARNING: level = 4; break; + default: + case AV_LOG_INFO: level = 6; break; + case AV_LOG_DEBUG: level = 7; break; + case AV_LOG_VERBOSE: level = 8; break; + case AV_LOG_TRACE: level = 8; break; + } + + RTMFP_SetIntParameter("socketReceiveSize", ctx->socketReceiveSize); + RTMFP_SetIntParameter("socketSendSize", ctx->socketSendSize); + RTMFP_SetIntParameter("timeoutFallback", ctx->fallbackTimeout); + RTMFP_SetIntParameter("logLevel", level); + + RTMFP_Init(&ctx->rtmfp, &ctx->group, 1); + ctx->rtmfp.pOnStatusEvent = onStatusEvent; + ctx->rtmfp.isBlocking = 1; + ctx->rtmfp.swfUrl = ctx->swfUrl; + ctx->rtmfp.app = ctx->app; + ctx->rtmfp.pageUrl = ctx->pageUrl; + ctx->rtmfp.flashVer = ctx->flashVer; + ctx->rtmfp.host = ctx->host; + ctx->rtmfp.hostIPv6 = ctx->hostIPv6; + + RTMFP_LogSetCallback(rtmfp_log); + /*RTMFP_ActiveDump(); + RTMFP_DumpSetCallback(rtmfp_dump);*/ + RTMFP_InterruptSetCallback(s->interrupt_callback.callback, s->interrupt_callback.opaque); + + RTMFP_GetPublicationAndUrlFromUri(uri, &ctx->publication); + + if ((ctx->id = RTMFP_Connect(uri, &ctx->rtmfp)) == 0) + return -1; + + av_log(s, AV_LOG_INFO, "RTMFP Connect called : %d\n", ctx->id); + + // Wait for connection to happen + if (RTMFP_WaitForEvent(ctx->id, RTMFP_CONNECTED) == 0) + return -1; + + if (ctx->netgroup) { + ctx->group.netGroup = ctx->netgroup; + ctx->group.availabilityUpdatePeriod = ctx->updatePeriod; + ctx->group.windowDuration = ctx->windowDuration; + ctx->group.pushLimit = ctx->pushLimit; + ctx->group.isPublisher = (flags & AVIO_FLAG_WRITE) > 1; + ctx->group.isBlocking = 1; + ctx->group.disableRateControl = ctx->disableRateCtl>0; + ctx->streamId = RTMFP_Connect2Group(ctx->id, ctx->publication, &ctx->rtmfp, &ctx->group, !ctx->audioUnbuffered, !ctx->videoUnbuffered, ctx->fallbackUrl); + } else if (ctx->peerId) + ctx->streamId = RTMFP_Connect2Peer(ctx->id, ctx->peerId, ctx->publication, 1); + else if (ctx->p2pPublishing) + ctx->streamId = RTMFP_PublishP2P(ctx->id, ctx->publication, !ctx->audioUnbuffered, !ctx->videoUnbuffered, 1); + else if (flags & AVIO_FLAG_WRITE) + ctx->streamId = RTMFP_Publish(ctx->id, ctx->publication, !ctx->audioUnbuffered, !ctx->videoUnbuffered, 1); + else + ctx->streamId = RTMFP_Play(ctx->id, ctx->publication); + + if (!ctx->streamId) + return -1; + + s->is_streamed = 1; + return 0; +} + +static int rtmfp_write(URLContext *s, const uint8_t *buf, int size) +{ + LibRTMFPContext *ctx = s->priv_data; + int res = 0; + + res = RTMFP_Write(ctx->id, buf, size); + return (res < 0)? AVERROR_UNKNOWN : res; +} + +static int rtmfp_read(URLContext *s, uint8_t *buf, int size) +{ + LibRTMFPContext *ctx = s->priv_data; + int res = 0; + + res = RTMFP_Read(ctx->streamId, ctx->id, buf, size); + + return (res < 0)? AVERROR_UNKNOWN : res; +} + +#define OFFSET(x) offsetof(LibRTMFPContext, x) +#define DEC AV_OPT_FLAG_DECODING_PARAM +#define ENC AV_OPT_FLAG_ENCODING_PARAM +static const AVOption options[] = { + {"socketReceiveSize", "Socket receive buffer size", OFFSET(socketReceiveSize), AV_OPT_TYPE_INT, {.i64 = 212992}, 0, 0x0FFFFFFF, DEC|ENC}, + {"socketSendSize", "Socket send buffer size", OFFSET(socketSendSize), AV_OPT_TYPE_INT, {.i64 = 212992}, 0, 0x0FFFFFFF, DEC|ENC}, + {"audioUnbuffered", "Unbuffered audio mode (default to false)", OFFSET(audioUnbuffered), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, DEC|ENC}, + {"videoUnbuffered", "Unbuffered video mode (default to false)", OFFSET(videoUnbuffered), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, DEC|ENC}, + {"peerId", "Connect to a peer for playing", OFFSET(peerId), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, + {"p2pPublishing", "Publish the stream in p2p mode (default to false)", OFFSET(p2pPublishing), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, DEC|ENC}, + {"netgroup", "Publish/Play the stream into a NetGroup (multicast)", OFFSET(netgroup), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, + {"fallbackUrl", "Try to play a unicast stream url until the NetGroup connection is not ready (can produce undefined behavior if the stream codecs are different)", + OFFSET(fallbackUrl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, + {"fallbackTimeout", "Set the timeout in milliseconds to start fallback to unicast", OFFSET(fallbackTimeout), AV_OPT_TYPE_INT, {.i64 = 8000 }, 0, 120000, DEC|ENC}, + {"disableRateControl", "For Netgroup disable the P2P connection rate control to avoid disconnection", OFFSET(disableRateCtl), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, DEC|ENC}, + {"pushLimit", "Specifies the maximum number (-1) of peers to which we will send push fragments", OFFSET(pushLimit), AV_OPT_TYPE_INT, {.i64 = 4 }, 0, 255, DEC|ENC}, + {"updatePeriod", "Specifies the interval in milliseconds between messages sent to peers informating them that the local node has new p2p multicast media fragments available", + OFFSET(updatePeriod), AV_OPT_TYPE_INT, {.i64 = 100 }, 100, 10000, DEC|ENC}, + {"windowDuration", "Specifies the duration in milliseconds of the p2p multicast reassembly window", OFFSET(windowDuration), AV_OPT_TYPE_INT, {.i64 = 8000 }, 1000, 60000, DEC|ENC}, + {"rtmfp_swfurl", "URL of the SWF player. By default no value will be sent", OFFSET(swfUrl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, + {"rtmfp_app", "Name of application to connect to on the RTMFP server (by default 'live')", OFFSET(app), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, + {"rtmfp_pageurl", "URL of the web page in which the media was embedded. By default no value will be sent.", OFFSET(pageUrl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC}, + {"rtmfp_flashver", "Version of the Flash plugin used to run the SWF player. By default 'WIN 20,0,0,286'", OFFSET(flashVer), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, + {"rtmfp_host", "IPv4 host address to bind to (use this if you ave multiple interfaces)", OFFSET(host), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, + {"rtmfp_hostIPv6", "IPv6 host address to bind to (use this if you ave multiple interfaces)", OFFSET(hostIPv6), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, + { NULL }, +}; + +static const AVClass librtmfp_class = { + .class_name = "librtmfp protocol", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +URLProtocol ff_librtmfp_protocol = { + .name = "rtmfp", + .url_open = rtmfp_open, + .url_read = rtmfp_read, + .url_write = rtmfp_write, + .url_close = rtmfp_close, + .priv_data_size = sizeof(LibRTMFPContext), + .priv_data_class = &librtmfp_class, + .flags = URL_PROTOCOL_FLAG_NETWORK, +}; + diff --git a/libavformat/protocols.c b/libavformat/protocols.c index ad95659795..7ac985b1bc 100644 --- a/libavformat/protocols.c +++ b/libavformat/protocols.c @@ -65,6 +65,7 @@ extern const URLProtocol ff_librtmpe_protocol; extern const URLProtocol ff_librtmps_protocol; extern const URLProtocol ff_librtmpt_protocol; extern const URLProtocol ff_librtmpte_protocol; +extern const URLProtocol ff_librtmfp_protocol; extern const URLProtocol ff_libsrt_protocol; extern const URLProtocol ff_libssh_protocol; extern const URLProtocol ff_libsmbclient_protocol;