* [FFmpeg-devel] [PATCH] Use AVOption calls to parse URL options (PR #20555)
@ 2025-09-19 19:07 Marton Balint via ffmpeg-devel
0 siblings, 0 replies; only message in thread
From: Marton Balint via ffmpeg-devel @ 2025-09-19 19:07 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Marton Balint
PR #20555 opened by Marton Balint (cus)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20555
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20555.patch
This removes lots of parsing code duplications, unifies parsing rules and also enhances the documentation.
Some notable changes, some of them slightly changes behaviour:
- unconditional complication of urldecode.c
- always doing URL decoding for URL option keys and values, previously URL decoding was only used in a few places
- proper rejection of invalid option values
- rejection of unknown options for libsrt and sctp. (other protocols might nest each other so unkown options can be OK)
- supporting OPT_TYPE_BOOL options with no value (e.g. ?verify instead of ?verify=1), previously only a few boolean options were supported this way
- the order of parsing AVOptions / URLOptions is unified, URL options now always override AVOptions, this mostly affects the TLS protocol
- previously you could merge multicast sources from AVOptions and URLOptions, from now only one of those is parsed.
>From 016015b8ef97443c1a65617ff5603748abd1a512 Mon Sep 17 00:00:00 2001
From: Marton Balint <cus@passwd.hu>
Date: Tue, 26 Aug 2025 22:57:40 +0200
Subject: [PATCH 01/10] avformat/urldecode: add ff_urldecode_len function
This will be used later to decode partial strings.
Signed-off-by: Marton Balint <cus@passwd.hu>
---
libavformat/urldecode.c | 18 ++++++++++++++++++
libavformat/urldecode.h | 16 ++++++++++++++++
2 files changed, 34 insertions(+)
diff --git a/libavformat/urldecode.c b/libavformat/urldecode.c
index e7fa27b3fa..fdaa41784f 100644
--- a/libavformat/urldecode.c
+++ b/libavformat/urldecode.c
@@ -28,6 +28,8 @@
#include <string.h>
+#include "libavutil/error.h"
+#include "libavutil/macros.h"
#include "libavutil/mem.h"
#include "libavutil/avstring.h"
#include "urldecode.h"
@@ -93,3 +95,19 @@ char *ff_urldecode(const char *url, int decode_plus_sign)
return dest;
}
+
+int ff_urldecode_len(char *dest, size_t dest_len, const char *url, size_t url_max_len, int decode_plus_sign)
+{
+ size_t written_bytes;
+ size_t url_len = strlen(url);
+
+ url_len = FFMIN(url_len, url_max_len);
+
+ if (dest_len <= url_len)
+ return AVERROR(EINVAL);
+
+ written_bytes = urldecode(dest, url, url_len, decode_plus_sign);
+ dest[written_bytes] = '\0';
+
+ return written_bytes;
+}
diff --git a/libavformat/urldecode.h b/libavformat/urldecode.h
index 80b11c3428..adca3e89a6 100644
--- a/libavformat/urldecode.h
+++ b/libavformat/urldecode.h
@@ -33,4 +33,20 @@
*/
char *ff_urldecode(const char *url, int decode_plus_sign);
+/**
+ * Decodes an URL from its percent-encoded form back into normal
+ * representation. This function returns the decoded URL in a string.
+ * The URL to be decoded does not necessarily have to be encoded but
+ * in that case the original string is duplicated.
+ *
+ * @param dest the destination buffer.
+ * @param dest_len the maximum available space in the destination buffer.
+ * Must be bigger than FFMIN(strlen(url), url_max_len) to avoid
+ * an AVERROR(EINVAL) result
+ * @param url_max_len the maximum number of chars to read from url
+ * @param decode_plus_sign if nonzero plus sign is decoded to space
+ * @return the number of written bytes to dest excluding the zero terminator,
+ * negative on error
+ */
+int ff_urldecode_len(char *dest, size_t dest_len, const char *url, size_t url_max_len, int decode_plus_sign);
#endif /* AVFORMAT_URLDECODE_H */
--
2.49.1
>From 717ff1eb3823f88f74b19066a0377dcb167ad91b Mon Sep 17 00:00:00 2001
From: Marton Balint <cus@passwd.hu>
Date: Tue, 26 Aug 2025 23:03:40 +0200
Subject: [PATCH 02/10] avformat: compile urldecode unconditionally
It will be used by the generic helper function to set options from URLs.
Signed-off-by: Marton Balint <cus@passwd.hu>
---
libavformat/Makefile | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/libavformat/Makefile b/libavformat/Makefile
index ab5551a735..801a0b0330 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -28,6 +28,7 @@ OBJS = allformats.o \
sdp.o \
seek.o \
url.o \
+ urldecode.o \
utils.o \
version.o \
@@ -536,10 +537,8 @@ OBJS-$(CONFIG_RTP_MUXER) += rtp.o \
rtpenc_vp8.o \
rtpenc_vp9.o \
rtpenc_xiph.o
-OBJS-$(CONFIG_RTSP_DEMUXER) += rtsp.o rtspdec.o httpauth.o \
- urldecode.o
-OBJS-$(CONFIG_RTSP_MUXER) += rtsp.o rtspenc.o httpauth.o \
- urldecode.o
+OBJS-$(CONFIG_RTSP_DEMUXER) += rtsp.o rtspdec.o httpauth.o
+OBJS-$(CONFIG_RTSP_MUXER) += rtsp.o rtspenc.o httpauth.o
OBJS-$(CONFIG_S337M_DEMUXER) += s337m.o spdif.o
OBJS-$(CONFIG_SAMI_DEMUXER) += samidec.o subtitles.o
OBJS-$(CONFIG_SAP_DEMUXER) += sapdec.o
@@ -684,13 +683,13 @@ OBJS-$(CONFIG_FFRTMPCRYPT_PROTOCOL) += rtmpcrypt.o rtmpdigest.o rtmpdh.o
OBJS-$(CONFIG_FFRTMPHTTP_PROTOCOL) += rtmphttp.o
OBJS-$(CONFIG_FILE_PROTOCOL) += file.o
OBJS-$(CONFIG_FD_PROTOCOL) += file.o
-OBJS-$(CONFIG_FTP_PROTOCOL) += ftp.o urldecode.o
+OBJS-$(CONFIG_FTP_PROTOCOL) += ftp.o
OBJS-$(CONFIG_GOPHER_PROTOCOL) += gopher.o
OBJS-$(CONFIG_GOPHERS_PROTOCOL) += gopher.o
OBJS-$(CONFIG_HLS_PROTOCOL) += hlsproto.o
-OBJS-$(CONFIG_HTTP_PROTOCOL) += http.o httpauth.o urldecode.o
-OBJS-$(CONFIG_HTTPPROXY_PROTOCOL) += http.o httpauth.o urldecode.o
-OBJS-$(CONFIG_HTTPS_PROTOCOL) += http.o httpauth.o urldecode.o
+OBJS-$(CONFIG_HTTP_PROTOCOL) += http.o httpauth.o
+OBJS-$(CONFIG_HTTPPROXY_PROTOCOL) += http.o httpauth.o
+OBJS-$(CONFIG_HTTPS_PROTOCOL) += http.o httpauth.o
OBJS-$(CONFIG_ICECAST_PROTOCOL) += icecast.o
OBJS-$(CONFIG_MD5_PROTOCOL) += md5proto.o
OBJS-$(CONFIG_MMSH_PROTOCOL) += mmsh.o mms.o asf_tags.o
@@ -724,7 +723,7 @@ OBJS-$(CONFIG_UDPLITE_PROTOCOL) += udp.o ip.o
OBJS-$(CONFIG_UNIX_PROTOCOL) += unix.o
# external library protocols
-OBJS-$(CONFIG_LIBAMQP_PROTOCOL) += libamqp.o urldecode.o
+OBJS-$(CONFIG_LIBAMQP_PROTOCOL) += libamqp.o
OBJS-$(CONFIG_LIBRIST_PROTOCOL) += librist.o
OBJS-$(CONFIG_LIBRTMP_PROTOCOL) += librtmp.o
OBJS-$(CONFIG_LIBRTMPE_PROTOCOL) += librtmp.o
@@ -732,7 +731,7 @@ OBJS-$(CONFIG_LIBRTMPS_PROTOCOL) += librtmp.o
OBJS-$(CONFIG_LIBRTMPT_PROTOCOL) += librtmp.o
OBJS-$(CONFIG_LIBRTMPTE_PROTOCOL) += librtmp.o
OBJS-$(CONFIG_LIBSMBCLIENT_PROTOCOL) += libsmbclient.o
-OBJS-$(CONFIG_LIBSRT_PROTOCOL) += libsrt.o urldecode.o
+OBJS-$(CONFIG_LIBSRT_PROTOCOL) += libsrt.o
OBJS-$(CONFIG_LIBSSH_PROTOCOL) += libssh.o
OBJS-$(CONFIG_LIBZMQ_PROTOCOL) += libzmq.o
--
2.49.1
>From 2c1295d878347a82e3cf4f06fe4462eae88a292a Mon Sep 17 00:00:00 2001
From: Marton Balint <cus@passwd.hu>
Date: Mon, 25 Aug 2025 00:54:29 +0200
Subject: [PATCH 03/10] avformat/utils: add helper function to set opts from
query string
Signed-off-by: Marton Balint <cus@passwd.hu>
---
libavformat/internal.h | 12 ++++++++
libavformat/utils.c | 66 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 78 insertions(+)
diff --git a/libavformat/internal.h b/libavformat/internal.h
index 0dfbf02ca0..1a50ba07d3 100644
--- a/libavformat/internal.h
+++ b/libavformat/internal.h
@@ -649,4 +649,16 @@ int ff_bprint_get_frame_filename(struct AVBPrint *buf, const char *path, int64_t
*/
int ff_dict_set_timestamp(AVDictionary **dict, const char *key, int64_t timestamp);
+/**
+ * Set a list of query string options on an object. Only the objects own
+ * options will be set.
+ *
+ * @param obj the object to set options on
+ * @param str the query string
+ * @param allow_unknown ignore unknown query string options. This can be OK if
+ * nested protocols are used.
+ * @return <0 on error
+ */
+int ff_parse_opts_from_query_string(void *obj, const char *str, int allow_unkown);
+
#endif /* AVFORMAT_INTERNAL_H */
diff --git a/libavformat/utils.c b/libavformat/utils.c
index 3573aa918e..a1af20cffb 100644
--- a/libavformat/utils.c
+++ b/libavformat/utils.c
@@ -25,10 +25,13 @@
#include "config.h"
#include "libavutil/avstring.h"
+#include "libavutil/avassert.h"
#include "libavutil/bprint.h"
#include "libavutil/dict.h"
#include "libavutil/internal.h"
#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+#include "libavutil/parseutils.h"
#include "libavutil/time.h"
#include "libavutil/time_internal.h"
@@ -41,6 +44,7 @@
#include "network.h"
#endif
#include "os_support.h"
+#include "urldecode.h"
/**
* @file
@@ -618,3 +622,65 @@ int ff_dict_set_timestamp(AVDictionary **dict, const char *key, int64_t timestam
return AVERROR_EXTERNAL;
}
}
+
+static const AVOption* find_opt(void *obj, const char *name, size_t len)
+{
+ char decoded_name[128];
+
+ if (ff_urldecode_len(decoded_name, sizeof(decoded_name), name, len, 1) < 0)
+ return NULL;
+
+ return av_opt_find(obj, decoded_name, NULL, 0, 0);
+}
+
+int ff_parse_opts_from_query_string(void *obj, const char *str, int allow_unknown)
+{
+ const AVOption *opt;
+ char optval[512];
+ int ret;
+
+ if (*str == '?')
+ str++;
+ while (*str) {
+ size_t len = strcspn(str, "=&");
+ opt = find_opt(obj, str, len);
+ if (!opt) {
+ if (!allow_unknown) {
+ av_log(obj, AV_LOG_ERROR, "Query string option '%.*s' does not exist\n", (int)len, str);
+ return AVERROR_OPTION_NOT_FOUND;
+ }
+ av_log(obj, AV_LOG_VERBOSE, "Ignoring unknown query string option '%.*s'\n", (int)len, str);
+ }
+ str += len;
+ if (!opt) {
+ len = strcspn(str, "&");
+ str += len;
+ } else if (*str == '&' || *str == '\0') {
+ /* Check for bool options without value, e.g. "?verify".
+ * Unfortunately "listen" is a tri-state INT for some protocols so
+ * we also have to allow that for backward compatibility. */
+ if (opt->type != AV_OPT_TYPE_BOOL && strcmp(opt->name, "listen")) {
+ av_log(obj, AV_LOG_ERROR, "Non-bool query string option '%s' has no value\n", opt->name);
+ return AVERROR(EINVAL);
+ }
+ ret = av_opt_set_int(obj, opt->name, 1, 0);
+ if (ret < 0)
+ return ret;
+ } else {
+ av_assert2(*str == '=');
+ str++;
+ len = strcspn(str, "&");
+ if (ff_urldecode_len(optval, sizeof(optval), str, len, 1) < 0) {
+ av_log(obj, AV_LOG_ERROR, "Query string option '%s' value is too long\n", opt->name);
+ return AVERROR(EINVAL);
+ }
+ ret = av_opt_set(obj, opt->name, optval, 0);
+ if (ret < 0)
+ return ret;
+ str += len;
+ }
+ if (*str)
+ str++;
+ }
+ return 0;
+}
--
2.49.1
>From 6eca18b7a2217c673d7f0f6d4c182791aac02953 Mon Sep 17 00:00:00 2001
From: Marton Balint <cus@passwd.hu>
Date: Mon, 25 Aug 2025 01:50:13 +0200
Subject: [PATCH 04/10] avformat/libsrt: use ff_parse_opts_from_query_string()
to set URL parameters
Signed-off-by: Marton Balint <cus@passwd.hu>
---
doc/protocols.texi | 15 ++---
libavformat/libsrt.c | 142 +------------------------------------------
2 files changed, 8 insertions(+), 149 deletions(-)
diff --git a/doc/protocols.texi b/doc/protocols.texi
index cd0726cc7a..133574b4db 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -1554,18 +1554,13 @@ srt://@var{hostname}:@var{port}[?@var{options}]
@end example
@var{options} contains a list of &-separated options of the form
-@var{key}=@var{val}.
+@var{key}=@var{val}. Standard percent-encoding (and using the plus sign for
+space) can be used to escape keys and values.
-or
+Options can also can be specified via command line options (or in code via
+@code{AVOption}s).
-@example
-@var{options} srt://@var{hostname}:@var{port}
-@end example
-
-@var{options} contains a list of '-@var{key} @var{val}'
-options.
-
-This protocol accepts the following options.
+The list of supported options follows.
@table @option
@item connect_timeout=@var{milliseconds}
diff --git a/libavformat/libsrt.c b/libavformat/libsrt.c
index 9e860abccd..ba04d9f782 100644
--- a/libavformat/libsrt.c
+++ b/libavformat/libsrt.c
@@ -386,8 +386,6 @@ static int libsrt_setup(URLContext *h, const char *uri, int flags)
struct addrinfo hints = { 0 }, *ai, *cur_ai;
int port, fd;
SRTContext *s = h->priv_data;
- const char *p;
- char buf[256];
int ret;
char hostname[1024],proto[1024],path[1024];
char portstr[10];
@@ -402,15 +400,6 @@ static int libsrt_setup(URLContext *h, const char *uri, int flags)
av_log(h, AV_LOG_ERROR, "Port missing in uri\n");
return AVERROR(EINVAL);
}
- p = strchr(uri, '?');
- if (p) {
- if (av_find_info_tag(buf, sizeof(buf), "timeout", p)) {
- s->rw_timeout = strtoll(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "listen_timeout", p)) {
- s->listen_timeout = strtoll(buf, NULL, 10);
- }
- }
if (s->rw_timeout >= 0) {
open_timeout = h->rw_timeout = s->rw_timeout;
}
@@ -534,7 +523,6 @@ static int libsrt_open(URLContext *h, const char *uri, int flags)
{
SRTContext *s = h->priv_data;
const char * p;
- char buf[1024];
int ret = 0;
if (srt_startup() < 0) {
@@ -544,133 +532,9 @@ static int libsrt_open(URLContext *h, const char *uri, int flags)
/* SRT options (srt/srt.h) */
p = strchr(uri, '?');
if (p) {
- if (av_find_info_tag(buf, sizeof(buf), "maxbw", p)) {
- s->maxbw = strtoll(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "pbkeylen", p)) {
- s->pbkeylen = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "passphrase", p)) {
- av_freep(&s->passphrase);
- s->passphrase = ff_urldecode(buf, 1);
- if (!s->passphrase) {
- ret = AVERROR(ENOMEM);
- goto err;
- }
- }
-#if SRT_VERSION_VALUE >= 0x010302
- if (av_find_info_tag(buf, sizeof(buf), "enforced_encryption", p)) {
- s->enforced_encryption = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "kmrefreshrate", p)) {
- s->kmrefreshrate = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "kmpreannounce", p)) {
- s->kmpreannounce = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "snddropdelay", p)) {
- s->snddropdelay = strtoll(buf, NULL, 10);
- }
-#endif
- if (av_find_info_tag(buf, sizeof(buf), "mss", p)) {
- s->mss = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "ffs", p)) {
- s->ffs = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "ipttl", p)) {
- s->ipttl = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "iptos", p)) {
- s->iptos = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "inputbw", p)) {
- s->inputbw = strtoll(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "oheadbw", p)) {
- s->oheadbw = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "latency", p)) {
- s->latency = strtoll(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "tsbpddelay", p)) {
- s->latency = strtoll(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "rcvlatency", p)) {
- s->rcvlatency = strtoll(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "peerlatency", p)) {
- s->peerlatency = strtoll(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "tlpktdrop", p)) {
- s->tlpktdrop = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "nakreport", p)) {
- s->nakreport = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "connect_timeout", p)) {
- s->connect_timeout = strtoll(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "payload_size", p) ||
- av_find_info_tag(buf, sizeof(buf), "pkt_size", p)) {
- s->payload_size = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "mode", p)) {
- if (!strcmp(buf, "caller")) {
- s->mode = SRT_MODE_CALLER;
- } else if (!strcmp(buf, "listener")) {
- s->mode = SRT_MODE_LISTENER;
- } else if (!strcmp(buf, "rendezvous")) {
- s->mode = SRT_MODE_RENDEZVOUS;
- } else {
- ret = AVERROR(EINVAL);
- goto err;
- }
- }
- if (av_find_info_tag(buf, sizeof(buf), "sndbuf", p)) {
- s->sndbuf = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "rcvbuf", p)) {
- s->rcvbuf = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "lossmaxttl", p)) {
- s->lossmaxttl = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "minversion", p)) {
- s->minversion = strtol(buf, NULL, 0);
- }
- if (av_find_info_tag(buf, sizeof(buf), "streamid", p)) {
- av_freep(&s->streamid);
- s->streamid = ff_urldecode(buf, 1);
- if (!s->streamid) {
- ret = AVERROR(ENOMEM);
- goto err;
- }
- }
- if (av_find_info_tag(buf, sizeof(buf), "smoother", p)) {
- av_freep(&s->smoother);
- s->smoother = ff_urldecode(buf, 1);
- if(!s->smoother) {
- ret = AVERROR(ENOMEM);
- goto err;
- }
- }
- if (av_find_info_tag(buf, sizeof(buf), "messageapi", p)) {
- s->messageapi = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "transtype", p)) {
- if (!strcmp(buf, "live")) {
- s->transtype = SRTT_LIVE;
- } else if (!strcmp(buf, "file")) {
- s->transtype = SRTT_FILE;
- } else {
- ret = AVERROR(EINVAL);
- goto err;
- }
- }
- if (av_find_info_tag(buf, sizeof(buf), "linger", p)) {
- s->linger = strtol(buf, NULL, 10);
- }
+ ret = ff_parse_opts_from_query_string(s, p, 0);
+ if (ret < 0)
+ goto err;
}
ret = libsrt_setup(h, uri, flags);
if (ret < 0)
--
2.49.1
>From bc5c0d24b3224170f898b8fc28e28c6277c1b145 Mon Sep 17 00:00:00 2001
From: Marton Balint <cus@passwd.hu>
Date: Mon, 25 Aug 2025 01:51:15 +0200
Subject: [PATCH 05/10] avformat/udp: use ff_parse_opts_from_query_string() to
set URL parameters
Signed-off-by: Marton Balint <cus@passwd.hu>
---
doc/protocols.texi | 7 +++-
libavformat/udp.c | 93 +++++++---------------------------------------
2 files changed, 19 insertions(+), 81 deletions(-)
diff --git a/doc/protocols.texi b/doc/protocols.texi
index 133574b4db..b4b6c25579 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -2114,7 +2114,12 @@ The required syntax for an UDP URL is:
udp://@var{hostname}:@var{port}[?@var{options}]
@end example
-@var{options} contains a list of &-separated options of the form @var{key}=@var{val}.
+@var{options} contains a list of &-separated options of the form
+@var{key}=@var{val}. Standard percent-encoding (and using the plus sign for
+space) can be used to escape keys and values.
+
+Options can also can be specified via command line options (or in code via
+@code{AVOption}s).
In case threading is enabled on the system, a circular buffer is used
to store the incoming data, which allows one to reduce loss of data due to
diff --git a/libavformat/udp.c b/libavformat/udp.c
index e1dec49010..05936fc055 100644
--- a/libavformat/udp.c
+++ b/libavformat/udp.c
@@ -36,6 +36,7 @@
#include "libavutil/opt.h"
#include "libavutil/log.h"
#include "libavutil/time.h"
+#include "internal.h"
#include "network.h"
#include "os_support.h"
#include "url.h"
@@ -697,7 +698,6 @@ static int udp_open(URLContext *h, const char *uri, int flags)
UDPContext *s = h->priv_data;
int is_output;
const char *p;
- char buf[256];
struct sockaddr_storage my_addr;
socklen_t len;
int ret;
@@ -708,87 +708,11 @@ static int udp_open(URLContext *h, const char *uri, int flags)
if (s->buffer_size < 0)
s->buffer_size = is_output ? UDP_TX_BUF_SIZE : UDP_RX_BUF_SIZE;
- if (s->sources) {
- if ((ret = ff_ip_parse_sources(h, s->sources, &s->filters)) < 0)
- goto fail;
- }
-
- if (s->block) {
- if ((ret = ff_ip_parse_blocks(h, s->block, &s->filters)) < 0)
- goto fail;
- }
-
p = strchr(uri, '?');
if (p) {
- if (av_find_info_tag(buf, sizeof(buf), "reuse", p)) {
- char *endptr = NULL;
- s->reuse_socket = strtol(buf, &endptr, 10);
- /* assume if no digits were found it is a request to enable it */
- if (buf == endptr)
- s->reuse_socket = 1;
- }
- if (av_find_info_tag(buf, sizeof(buf), "overrun_nonfatal", p)) {
- char *endptr = NULL;
- s->overrun_nonfatal = strtol(buf, &endptr, 10);
- /* assume if no digits were found it is a request to enable it */
- if (buf == endptr)
- s->overrun_nonfatal = 1;
- }
- if (av_find_info_tag(buf, sizeof(buf), "ttl", p)) {
- s->ttl = strtol(buf, NULL, 10);
- if (s->ttl < 0 || s->ttl > 255) {
- av_log(h, AV_LOG_ERROR, "ttl(%d) should be in range [0,255]\n", s->ttl);
- ret = AVERROR(EINVAL);
- goto fail;
- }
- }
- if (av_find_info_tag(buf, sizeof(buf), "udplite_coverage", p)) {
- s->udplite_coverage = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "localport", p)) {
- s->local_port = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "pkt_size", p)) {
- s->pkt_size = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "buffer_size", p)) {
- s->buffer_size = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "connect", p)) {
- s->is_connected = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "dscp", p)) {
- s->dscp = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "fifo_size", p)) {
- s->circular_buffer_size = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "bitrate", p)) {
- s->bitrate = strtoll(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "burst_bits", p)) {
- s->burst_bits = strtoll(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "localaddr", p)) {
- av_freep(&s->localaddr);
- s->localaddr = av_strdup(buf);
- if (!s->localaddr) {
- ret = AVERROR(ENOMEM);
- goto fail;
- }
- }
- if (av_find_info_tag(buf, sizeof(buf), "sources", p)) {
- if ((ret = ff_ip_parse_sources(h, buf, &s->filters)) < 0)
- goto fail;
- }
- if (av_find_info_tag(buf, sizeof(buf), "block", p)) {
- if ((ret = ff_ip_parse_blocks(h, buf, &s->filters)) < 0)
- goto fail;
- }
- if (!is_output && av_find_info_tag(buf, sizeof(buf), "timeout", p))
- s->timeout = strtol(buf, NULL, 10);
- if (is_output && av_find_info_tag(buf, sizeof(buf), "broadcast", p))
- s->is_broadcast = strtol(buf, NULL, 10);
+ ret = ff_parse_opts_from_query_string(s, p, 1);
+ if (ret < 0)
+ goto fail;
}
if (!HAVE_PTHREAD_CANCEL) {
int64_t optvals[] = {s->overrun_nonfatal, s->bitrate, s->circular_buffer_size};
@@ -800,6 +724,15 @@ static int udp_open(URLContext *h, const char *uri, int flags)
"on this build (pthread support is required)\n", optnames[i]);
}
}
+ if (s->sources) {
+ if ((ret = ff_ip_parse_sources(h, s->sources, &s->filters)) < 0)
+ goto fail;
+ }
+ if (s->block) {
+ if ((ret = ff_ip_parse_blocks(h, s->block, &s->filters)) < 0)
+ goto fail;
+ }
+
/* handling needed to support options picking from both AVOption and URL */
s->circular_buffer_size *= 188;
if (flags & AVIO_FLAG_WRITE) {
--
2.49.1
>From c899a1e142c03ab0c1d418d7fe61cb2d80d1085d Mon Sep 17 00:00:00 2001
From: Marton Balint <cus@passwd.hu>
Date: Mon, 25 Aug 2025 01:52:00 +0200
Subject: [PATCH 06/10] avformat/tcp: use ff_parse_opts_from_query_string() to
set URL parameters
Signed-off-by: Marton Balint <cus@passwd.hu>
---
doc/protocols.texi | 6 +++++-
libavformat/tcp.c | 36 ++++--------------------------------
2 files changed, 9 insertions(+), 33 deletions(-)
diff --git a/doc/protocols.texi b/doc/protocols.texi
index b4b6c25579..a329f59fe3 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -1913,7 +1913,11 @@ tcp://@var{hostname}:@var{port}[?@var{options}]
@end example
@var{options} contains a list of &-separated options of the form
-@var{key}=@var{val}.
+@var{key}=@var{val}. Standard percent-encoding (and using the plus sign for
+space) can be used to escape keys and values.
+
+Options can also can be specified via command line options (or in code via
+@code{AVOption}s).
The list of supported options follows.
diff --git a/libavformat/tcp.c b/libavformat/tcp.c
index c286698d33..ce9f69a50b 100644
--- a/libavformat/tcp.c
+++ b/libavformat/tcp.c
@@ -25,6 +25,7 @@
#include "libavutil/opt.h"
#include "libavutil/time.h"
+#include "internal.h"
#include "network.h"
#include "os_support.h"
#include "url.h"
@@ -151,7 +152,6 @@ static int tcp_open(URLContext *h, const char *uri, int flags)
int port, fd = -1;
TCPContext *s = h->priv_data;
const char *p;
- char buf[256];
int ret;
char hostname[1024],proto[1024],path[1024];
char portstr[10];
@@ -167,37 +167,9 @@ static int tcp_open(URLContext *h, const char *uri, int flags)
}
p = strchr(uri, '?');
if (p) {
- if (av_find_info_tag(buf, sizeof(buf), "listen", p)) {
- char *endptr = NULL;
- s->listen = strtol(buf, &endptr, 10);
- /* assume if no digits were found it is a request to enable it */
- if (buf == endptr)
- s->listen = 1;
- }
- if (av_find_info_tag(buf, sizeof(buf), "local_port", p)) {
- av_freep(&s->local_port);
- s->local_port = av_strdup(buf);
- if (!s->local_port)
- return AVERROR(ENOMEM);
- }
- if (av_find_info_tag(buf, sizeof(buf), "local_addr", p)) {
- av_freep(&s->local_addr);
- s->local_addr = av_strdup(buf);
- if (!s->local_addr)
- return AVERROR(ENOMEM);
- }
- if (av_find_info_tag(buf, sizeof(buf), "timeout", p)) {
- s->rw_timeout = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "listen_timeout", p)) {
- s->listen_timeout = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "tcp_nodelay", p)) {
- s->tcp_nodelay = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "tcp_keepalive", p)) {
- s->tcp_keepalive = strtol(buf, NULL, 10);
- }
+ int ret = ff_parse_opts_from_query_string(s, p, 1);
+ if (ret < 0)
+ return ret;
}
if (s->rw_timeout >= 0) {
s->open_timeout =
--
2.49.1
>From cf1b430dba68d5854c452cb8af33710cbf0fe474 Mon Sep 17 00:00:00 2001
From: Marton Balint <cus@passwd.hu>
Date: Mon, 25 Aug 2025 02:09:26 +0200
Subject: [PATCH 07/10] avformat/sctp: use ff_parse_opts_from_query_string() to
set URL parameters
Signed-off-by: Marton Balint <cus@passwd.hu>
---
doc/protocols.texi | 10 +++++++++-
libavformat/sctp.c | 8 +++-----
2 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/doc/protocols.texi b/doc/protocols.texi
index a329f59fe3..563aee6cc7 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -1535,7 +1535,15 @@ The accepted URL syntax is:
sctp://@var{host}:@var{port}[?@var{options}]
@end example
-The protocol accepts the following options:
+@var{options} contains a list of &-separated options of the form
+@var{key}=@var{val}. Standard percent-encoding (and using the plus sign for
+space) can be used to escape keys and values.
+
+Options can also can be specified via command line options (or in code via
+@code{AVOption}s).
+
+The list of supported options follows.
+
@table @option
@item listen
If set to any value, listen for an incoming connection. Outgoing connection is done by default.
diff --git a/libavformat/sctp.c b/libavformat/sctp.c
index 9d9e90097e..4122fbe312 100644
--- a/libavformat/sctp.c
+++ b/libavformat/sctp.c
@@ -185,7 +185,6 @@ static int sctp_open(URLContext *h, const char *uri, int flags)
int fd = -1;
SCTPContext *s = h->priv_data;
const char *p;
- char buf[256];
int ret;
char hostname[1024], proto[1024], path[1024];
char portstr[10];
@@ -201,10 +200,9 @@ static int sctp_open(URLContext *h, const char *uri, int flags)
p = strchr(uri, '?');
if (p) {
- if (av_find_info_tag(buf, sizeof(buf), "listen", p))
- s->listen = 1;
- if (av_find_info_tag(buf, sizeof(buf), "max_streams", p))
- s->max_streams = strtol(buf, NULL, 10);
+ ret = ff_parse_opts_from_query_string(s, p, 0);
+ if (ret < 0)
+ return ret;
}
hints.ai_family = AF_UNSPEC;
--
2.49.1
>From 3aa50f4a5ec4298bc488cec4c9fcb332ae5ae891 Mon Sep 17 00:00:00 2001
From: Marton Balint <cus@passwd.hu>
Date: Mon, 25 Aug 2025 21:35:40 +0200
Subject: [PATCH 08/10] avformat/rtpproto: use
ff_parse_opts_from_query_string() to set URL parameters
Signed-off-by: Marton Balint <cus@passwd.hu>
---
doc/protocols.texi | 8 +++--
libavformat/rtpproto.c | 69 +++++++++---------------------------------
2 files changed, 21 insertions(+), 56 deletions(-)
diff --git a/doc/protocols.texi b/doc/protocols.texi
index 563aee6cc7..3d9685ed6d 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -1162,9 +1162,13 @@ rtp://@var{hostname}[:@var{port}][?@var{options}]
@var{port} specifies the RTP port to use.
@var{options} contains a list of &-separated options of the form
-@var{key}=@var{val}.
+@var{key}=@var{val}. Standard percent-encoding (and using the plus sign for
+space) can be used to escape keys and values.
-The following options are supported:
+Options can also can be specified via command line options (or in code via
+@code{AVOption}s).
+
+The list of supported options follows.
@table @option
diff --git a/libavformat/rtpproto.c b/libavformat/rtpproto.c
index 69879b7fa8..c4309c19e4 100644
--- a/libavformat/rtpproto.c
+++ b/libavformat/rtpproto.c
@@ -29,6 +29,7 @@
#include "libavutil/avstring.h"
#include "libavutil/opt.h"
#include "avformat.h"
+#include "internal.h"
#include "rtp.h"
#include "rtpproto.h"
#include "url.h"
@@ -232,8 +233,7 @@ static int rtp_open(URLContext *h, const char *uri, int flags)
RTPContext *s = h->priv_data;
AVDictionary *fec_opts = NULL;
int rtp_port;
- char hostname[256], include_sources[1024] = "", exclude_sources[1024] = "";
- char *sources = include_sources, *block = exclude_sources;
+ char hostname[256];
char *fec_protocol = NULL;
char buf[1024];
char path[1024];
@@ -250,58 +250,17 @@ static int rtp_open(URLContext *h, const char *uri, int flags)
p = strchr(uri, '?');
if (p) {
- if (av_find_info_tag(buf, sizeof(buf), "ttl", p)) {
- s->ttl = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "rtcpport", p)) {
- s->rtcp_port = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "localport", p)) {
- s->local_rtpport = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "localrtpport", p)) {
- s->local_rtpport = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "localrtcpport", p)) {
- s->local_rtcpport = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "pkt_size", p)) {
- s->pkt_size = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "connect", p)) {
- s->connect = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "write_to_source", p)) {
- s->write_to_source = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "dscp", p)) {
- s->dscp = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "timeout", p)) {
- s->rw_timeout = strtol(buf, NULL, 10);
- }
- if (av_find_info_tag(buf, sizeof(buf), "sources", p)) {
- av_strlcpy(include_sources, buf, sizeof(include_sources));
- ff_ip_parse_sources(h, buf, &s->filters);
- } else {
- ff_ip_parse_sources(h, s->sources, &s->filters);
- sources = s->sources;
- }
- if (av_find_info_tag(buf, sizeof(buf), "block", p)) {
- av_strlcpy(exclude_sources, buf, sizeof(exclude_sources));
- ff_ip_parse_blocks(h, buf, &s->filters);
- } else {
- ff_ip_parse_blocks(h, s->block, &s->filters);
- block = s->block;
- }
- if (av_find_info_tag(buf, sizeof(buf), "localaddr", p)) {
- av_freep(&s->localaddr);
- s->localaddr = av_strdup(buf);
- if (!s->localaddr) {
- ret = AVERROR(ENOMEM);
- goto fail;
- }
- }
+ ret = ff_parse_opts_from_query_string(s, p, 1);
+ if (ret < 0)
+ goto fail;
+ }
+ if (s->sources) {
+ if ((ret = ff_ip_parse_sources(h, s->sources, &s->filters)) < 0)
+ goto fail;
+ }
+ if (s->block) {
+ if ((ret = ff_ip_parse_blocks(h, s->block, &s->filters)) < 0)
+ goto fail;
}
if (s->rw_timeout >= 0)
h->rw_timeout = s->rw_timeout;
@@ -334,6 +293,8 @@ static int rtp_open(URLContext *h, const char *uri, int flags)
}
for (i = 0; i < max_retry_count; i++) {
+ const char *sources = s->sources ? s->sources : "";
+ const char *block = s->block ? s->block : "";
build_udp_url(s, buf, sizeof(buf),
hostname, s->localaddr, rtp_port, s->local_rtpport,
sources, block);
--
2.49.1
>From ee7094027e66b8b8d9dbfdd80e8e94da54307712 Mon Sep 17 00:00:00 2001
From: Marton Balint <cus@passwd.hu>
Date: Tue, 26 Aug 2025 00:23:16 +0200
Subject: [PATCH 09/10] avformat/tls: use ff_parse_opts_from_query_string() to
set URL parameters
Note that this changes the code to work the same way as other protocols where
an URL parameter can override an AVOption.
Signed-off-by: Marton Balint <cus@passwd.hu>
---
doc/protocols.texi | 10 +++++++--
libavformat/tls.c | 46 +++++----------------------------------
libavformat/tls_mbedtls.c | 14 ------------
3 files changed, 14 insertions(+), 56 deletions(-)
diff --git a/doc/protocols.texi b/doc/protocols.texi
index 3d9685ed6d..9f88f005b9 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -1984,8 +1984,14 @@ The required syntax for a TLS/SSL url is:
tls://@var{hostname}:@var{port}[?@var{options}]
@end example
-The following parameters can be set via command line options
-(or in code via @code{AVOption}s):
+@var{options} contains a list of &-separated options of the form
+@var{key}=@var{val}. Standard percent-encoding (and using the plus sign for
+space) can be used to escape keys and values.
+
+Options can also can be specified via command line options (or in code via
+@code{AVOption}s).
+
+The list of supported options follows.
@table @option
diff --git a/libavformat/tls.c b/libavformat/tls.c
index bd9c05e6dc..2cf0c99a6b 100644
--- a/libavformat/tls.c
+++ b/libavformat/tls.c
@@ -31,41 +31,6 @@
#include "libavutil/mem.h"
#include "libavutil/parseutils.h"
-static int set_options(TLSShared *c, const char *uri)
-{
- char buf[1024];
- const char *p = strchr(uri, '?');
- if (!p)
- return 0;
-
- if (!c->ca_file && av_find_info_tag(buf, sizeof(buf), "cafile", p)) {
- c->ca_file = av_strdup(buf);
- if (!c->ca_file)
- return AVERROR(ENOMEM);
- }
-
- if (!c->verify && av_find_info_tag(buf, sizeof(buf), "verify", p)) {
- char *endptr = NULL;
- c->verify = strtol(buf, &endptr, 10);
- if (buf == endptr)
- c->verify = 1;
- }
-
- if (!c->cert_file && av_find_info_tag(buf, sizeof(buf), "cert", p)) {
- c->cert_file = av_strdup(buf);
- if (!c->cert_file)
- return AVERROR(ENOMEM);
- }
-
- if (!c->key_file && av_find_info_tag(buf, sizeof(buf), "key", p)) {
- c->key_file = av_strdup(buf);
- if (!c->key_file)
- return AVERROR(ENOMEM);
- }
-
- return 0;
-}
-
int ff_tls_open_underlying(TLSShared *c, URLContext *parent, const char *uri, AVDictionary **options)
{
int port;
@@ -77,17 +42,18 @@ int ff_tls_open_underlying(TLSShared *c, URLContext *parent, const char *uri, AV
int use_proxy;
int ret;
- ret = set_options(c, uri);
- if (ret < 0)
- return ret;
+ p = strchr(uri, '?');
+ if (p) {
+ ret = ff_parse_opts_from_query_string(c, p, 1);
+ if (ret < 0)
+ return ret;
+ }
if (c->listen && !c->is_dtls)
snprintf(opts, sizeof(opts), "?listen=1");
av_url_split(NULL, 0, NULL, 0, c->underlying_host, sizeof(c->underlying_host), &port, NULL, 0, uri);
- p = strchr(uri, '?');
-
if (!p) {
p = opts;
} else {
diff --git a/libavformat/tls_mbedtls.c b/libavformat/tls_mbedtls.c
index 2bcd3cca63..8aa142b9d2 100644
--- a/libavformat/tls_mbedtls.c
+++ b/libavformat/tls_mbedtls.c
@@ -174,17 +174,6 @@ static void handle_handshake_error(URLContext *h, int ret)
}
}
-static void parse_options(TLSContext *tls_ctxc, const char *uri)
-{
- char buf[1024];
- const char *p = strchr(uri, '?');
- if (!p)
- return;
-
- if (!tls_ctxc->priv_key_pw && av_find_info_tag(buf, sizeof(buf), "key_password", p))
- tls_ctxc->priv_key_pw = av_strdup(buf);
-}
-
static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **options)
{
TLSContext *tls_ctx = h->priv_data;
@@ -192,9 +181,6 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op
uint32_t verify_res_flags;
int ret;
- // parse additional options
- parse_options(tls_ctx, uri);
-
if ((ret = ff_tls_open_underlying(shr, h, uri, options)) < 0)
goto fail;
--
2.49.1
>From 594ea1b6017eed768a7de8422a068b87bffa6072 Mon Sep 17 00:00:00 2001
From: Marton Balint <cus@passwd.hu>
Date: Wed, 3 Sep 2025 00:55:46 +0200
Subject: [PATCH 10/10] avformat/tls_openssl: initialize underlying protocol
early for dtls_start()
The same way we do with TLS, so all tls URL options will be properly supported.
Signed-off-by: Marton Balint <cus@passwd.hu>
---
doc/protocols.texi | 13 ++++++++++---
libavformat/tls_openssl.c | 15 ++++++++-------
2 files changed, 18 insertions(+), 10 deletions(-)
diff --git a/doc/protocols.texi b/doc/protocols.texi
index 9f88f005b9..b74383122a 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -2052,12 +2052,19 @@ Datagram Transport Layer Security (DTLS)
The required syntax for a DTLS URL is:
@example
-dtls://@var{hostname}:@var{port}
+dtls://@var{hostname}:@var{port}[?@var{options}]
@end example
+@var{options} contains a list of &-separated options of the form
+@var{key}=@var{val}. Standard percent-encoding (and using the plus sign for
+space) can be used to escape keys and values.
+
+Options can also can be specified via command line options (or in code via
+@code{AVOption}s).
+
DTLS shares most options with TLS, but operates over UDP instead of TCP.
-The following parameters can be set via command line options
-(or in code via @code{AVOption}s):
+
+The list of supported options follows.
@table @option
diff --git a/libavformat/tls_openssl.c b/libavformat/tls_openssl.c
index edfd657a3f..f724153da1 100644
--- a/libavformat/tls_openssl.c
+++ b/libavformat/tls_openssl.c
@@ -749,6 +749,13 @@ static int dtls_start(URLContext *h, const char *url, int flags, AVDictionary **
av_assert0(s);
s->is_dtls = 1;
+ if (!c->tls_shared.external_sock) {
+ if ((ret = ff_tls_open_underlying(&c->tls_shared, h, url, options)) < 0) {
+ av_log(c, AV_LOG_ERROR, "Failed to connect %s\n", url);
+ return ret;
+ }
+ }
+
c->ctx = SSL_CTX_new(s->listen ? DTLS_server_method() : DTLS_client_method());
if (!c->ctx) {
ret = AVERROR(ENOMEM);
@@ -801,13 +808,6 @@ static int dtls_start(URLContext *h, const char *url, int flags, AVDictionary **
DTLS_set_link_mtu(c->ssl, s->mtu);
init_bio_method(h);
- if (!c->tls_shared.external_sock) {
- if ((ret = ff_tls_open_underlying(&c->tls_shared, h, url, options)) < 0) {
- av_log(c, AV_LOG_ERROR, "Failed to connect %s\n", url);
- return ret;
- }
- }
-
/* This seems to be necessary despite explicitly setting client/server method above. */
if (s->listen)
SSL_set_accept_state(c->ssl);
@@ -838,6 +838,7 @@ static int dtls_start(URLContext *h, const char *url, int flags, AVDictionary **
ret = 0;
fail:
+ tls_close(h);
return ret;
}
--
2.49.1
_______________________________________________
ffmpeg-devel mailing list -- ffmpeg-devel@ffmpeg.org
To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2025-09-19 19:08 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-09-19 19:07 [FFmpeg-devel] [PATCH] Use AVOption calls to parse URL options (PR #20555) Marton Balint via ffmpeg-devel
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
This inbox may be cloned and mirrored by anyone:
git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git
# If you have public-inbox 1.1+ installed, you may
# initialize and index your mirror using the following commands:
public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
ffmpegdev@gitmailbox.com
public-inbox-index ffmpegdev
Example config snippet for mirrors.
AGPL code for this site: git clone https://public-inbox.org/public-inbox.git