When ffmpeg was streaming, multiple clients were only supported by using a multicast destination address. An alternative was to stream to a server which re-distributes the content. This commit adds ZeroMQ as a protocol, which allows multiple clients to connect to a single ffmpeg instance. Signed-off-by: Marton Balint <cus@passwd.hu>tags/n4.3
| @@ -8,6 +8,7 @@ version <next>: | |||||
| - support for TrueHD in mp4 | - support for TrueHD in mp4 | ||||
| - Supoort AMD AMF encoder on Linux (via Vulkan) | - Supoort AMD AMF encoder on Linux (via Vulkan) | ||||
| - IMM5 video decoder | - IMM5 video decoder | ||||
| - ZeroMQ protocol | |||||
| version 4.2: | version 4.2: | ||||
| @@ -3415,6 +3415,8 @@ libsrt_protocol_deps="libsrt" | |||||
| libsrt_protocol_select="network" | libsrt_protocol_select="network" | ||||
| libssh_protocol_deps="libssh" | libssh_protocol_deps="libssh" | ||||
| libtls_conflict="openssl gnutls mbedtls" | libtls_conflict="openssl gnutls mbedtls" | ||||
| libzmq_protocol_deps="libzmq" | |||||
| libzmq_protocol_select="network" | |||||
| # filters | # filters | ||||
| afftdn_filter_deps="avcodec" | afftdn_filter_deps="avcodec" | ||||
| @@ -6325,7 +6327,7 @@ enabled libxavs && require libxavs "stdint.h xavs.h" xavs_encoder_enco | |||||
| enabled libxavs2 && require_pkg_config libxavs2 "xavs2 >= 1.3.0" "stdint.h xavs2.h" xavs2_api_get | enabled libxavs2 && require_pkg_config libxavs2 "xavs2 >= 1.3.0" "stdint.h xavs2.h" xavs2_api_get | ||||
| enabled libxvid && require libxvid xvid.h xvid_global -lxvidcore | enabled libxvid && require libxvid xvid.h xvid_global -lxvidcore | ||||
| enabled libzimg && require_pkg_config libzimg "zimg >= 2.7.0" zimg.h zimg_get_api_version | enabled libzimg && require_pkg_config libzimg "zimg >= 2.7.0" zimg.h zimg_get_api_version | ||||
| enabled libzmq && require_pkg_config libzmq libzmq zmq.h zmq_ctx_new | |||||
| enabled libzmq && require_pkg_config libzmq "libzmq >= 4.2.1" zmq.h zmq_ctx_new | |||||
| enabled libzvbi && require_pkg_config libzvbi zvbi-0.2 libzvbi.h vbi_decoder_new && | enabled libzvbi && require_pkg_config libzvbi zvbi-0.2 libzvbi.h vbi_decoder_new && | ||||
| { test_cpp_condition libzvbi.h "VBI_VERSION_MAJOR > 0 || VBI_VERSION_MINOR > 2 || VBI_VERSION_MINOR == 2 && VBI_VERSION_MICRO >= 28" || | { test_cpp_condition libzvbi.h "VBI_VERSION_MAJOR > 0 || VBI_VERSION_MINOR > 2 || VBI_VERSION_MINOR == 2 && VBI_VERSION_MICRO >= 28" || | ||||
| enabled gpl || die "ERROR: libzvbi requires version 0.2.28 or --enable-gpl."; } | enabled gpl || die "ERROR: libzvbi requires version 0.2.28 or --enable-gpl."; } | ||||
| @@ -1339,6 +1339,7 @@ performance on systems without hardware floating point support). | |||||
| @item TCP @tab X | @item TCP @tab X | ||||
| @item TLS @tab X | @item TLS @tab X | ||||
| @item UDP @tab X | @item UDP @tab X | ||||
| @item ZMQ @tab E | |||||
| @end multitable | @end multitable | ||||
| @code{X} means that the protocol is supported. | @code{X} means that the protocol is supported. | ||||
| @@ -1728,4 +1728,51 @@ Timeout in ms. | |||||
| Create the Unix socket in listening mode. | Create the Unix socket in listening mode. | ||||
| @end table | @end table | ||||
| @section zmq | |||||
| ZeroMQ asynchronous messaging using the libzmq library. | |||||
| This library supports unicast streaming to multiple clients without relying on | |||||
| an external server. | |||||
| The required syntax for streaming or connecting to a stream is: | |||||
| @example | |||||
| zmq:tcp://ip-address:port | |||||
| @end example | |||||
| Example: | |||||
| Create a localhost stream on port 5555: | |||||
| @example | |||||
| ffmpeg -re -i input -f mpegts zmq:tcp://127.0.0.1:5555 | |||||
| @end example | |||||
| Multiple clients may connect to the stream using: | |||||
| @example | |||||
| ffplay zmq:tcp://127.0.0.1:5555 | |||||
| @end example | |||||
| Streaming to multiple clients is implemented using a ZeroMQ Pub-Sub pattern. | |||||
| The server side binds to a port and publishes data. Clients connect to the | |||||
| server (via IP address/port) and subscribe to the stream. The order in which | |||||
| the server and client start generally does not matter. | |||||
| ffmpeg must be compiled with the --enable-libzmq option to support | |||||
| this protocol. | |||||
| Options can be set on the @command{ffmpeg}/@command{ffplay} command | |||||
| line. The following options are supported: | |||||
| @table @option | |||||
| @item pkt_size | |||||
| Forces the maximum packet size for sending/receiving data. The default value is | |||||
| 32,768 bytes. On the server side, this sets the maximum size of sent packets | |||||
| via ZeroMQ. On the clients, it sets an internal buffer size for receiving | |||||
| packets. Note that pkt_size on the clients should be equal to or greater than | |||||
| pkt_size on the server. Otherwise the received message may be truncated causing | |||||
| decoding errors. | |||||
| @end table | |||||
| @c man end PROTOCOLS | @c man end PROTOCOLS | ||||
| @@ -631,6 +631,7 @@ OBJS-$(CONFIG_LIBRTMPTE_PROTOCOL) += librtmp.o | |||||
| OBJS-$(CONFIG_LIBSMBCLIENT_PROTOCOL) += libsmbclient.o | OBJS-$(CONFIG_LIBSMBCLIENT_PROTOCOL) += libsmbclient.o | ||||
| OBJS-$(CONFIG_LIBSRT_PROTOCOL) += libsrt.o | OBJS-$(CONFIG_LIBSRT_PROTOCOL) += libsrt.o | ||||
| OBJS-$(CONFIG_LIBSSH_PROTOCOL) += libssh.o | OBJS-$(CONFIG_LIBSSH_PROTOCOL) += libssh.o | ||||
| OBJS-$(CONFIG_LIBZMQ_PROTOCOL) += libzmq.o | |||||
| # libavdevice dependencies | # libavdevice dependencies | ||||
| OBJS-$(CONFIG_IEC61883_INDEV) += dv.o | OBJS-$(CONFIG_IEC61883_INDEV) += dv.o | ||||
| @@ -0,0 +1,199 @@ | |||||
| /* | |||||
| * ZeroMQ Protocol | |||||
| * Copyright (c) 2019 Andriy Gelman | |||||
| * | |||||
| * 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 <zmq.h> | |||||
| #include "url.h" | |||||
| #include "network.h" | |||||
| #include "libavutil/avstring.h" | |||||
| #include "libavutil/opt.h" | |||||
| #include "libavutil/time.h" | |||||
| #define ZMQ_STRERROR zmq_strerror(zmq_errno()) | |||||
| typedef struct ZMQContext { | |||||
| const AVClass *class; | |||||
| void *context; | |||||
| void *socket; | |||||
| int pkt_size; | |||||
| int pkt_size_overflow; /*keep track of the largest packet during overflow*/ | |||||
| } ZMQContext; | |||||
| #define OFFSET(x) offsetof(ZMQContext, x) | |||||
| #define D AV_OPT_FLAG_DECODING_PARAM | |||||
| #define E AV_OPT_FLAG_ENCODING_PARAM | |||||
| static const AVOption options[] = { | |||||
| { "pkt_size", "Maximum send/read packet size", OFFSET(pkt_size), AV_OPT_TYPE_INT, { .i64 = 32768 }, -1, INT_MAX, .flags = D | E }, | |||||
| { NULL } | |||||
| }; | |||||
| static int zmq_proto_wait(URLContext *h, void *socket, int write) | |||||
| { | |||||
| int ret; | |||||
| int ev = write ? ZMQ_POLLOUT : ZMQ_POLLIN; | |||||
| zmq_pollitem_t items = { .socket = socket, .fd = 0, .events = ev, .revents = 0 }; | |||||
| ret = zmq_poll(&items, 1, POLLING_TIME); | |||||
| if (ret == -1) { | |||||
| av_log(h, AV_LOG_ERROR, "Error occured during zmq_poll(): %s\n", ZMQ_STRERROR); | |||||
| return AVERROR_EXTERNAL; | |||||
| } | |||||
| return items.revents & ev ? 0 : AVERROR(EAGAIN); | |||||
| } | |||||
| static int zmq_proto_wait_timeout(URLContext *h, void *socket, int write, int64_t timeout, AVIOInterruptCB *int_cb) | |||||
| { | |||||
| int ret; | |||||
| int64_t wait_start = 0; | |||||
| while (1) { | |||||
| if (ff_check_interrupt(int_cb)) | |||||
| return AVERROR_EXIT; | |||||
| ret = zmq_proto_wait(h, socket, write); | |||||
| if (ret != AVERROR(EAGAIN)) | |||||
| return ret; | |||||
| if (timeout > 0) { | |||||
| if (!wait_start) | |||||
| wait_start = av_gettime_relative(); | |||||
| else if (av_gettime_relative() - wait_start > timeout) | |||||
| return AVERROR(ETIMEDOUT); | |||||
| } | |||||
| } | |||||
| } | |||||
| static int zmq_proto_open(URLContext *h, const char *uri, int flags) | |||||
| { | |||||
| int ret; | |||||
| ZMQContext *s = h->priv_data; | |||||
| s->pkt_size_overflow = 0; | |||||
| h->is_streamed = 1; | |||||
| if (s->pkt_size > 0) | |||||
| h->max_packet_size = s->pkt_size; | |||||
| s->context = zmq_ctx_new(); | |||||
| if (!s->context) { | |||||
| /*errno not set on failure during zmq_ctx_new()*/ | |||||
| av_log(h, AV_LOG_ERROR, "Error occured during zmq_ctx_new()\n"); | |||||
| return AVERROR_EXTERNAL; | |||||
| } | |||||
| av_strstart(uri, "zmq:", &uri); | |||||
| /*publish during write*/ | |||||
| if (h->flags & AVIO_FLAG_WRITE) { | |||||
| s->socket = zmq_socket(s->context, ZMQ_PUB); | |||||
| if (!s->socket) { | |||||
| av_log(h, AV_LOG_ERROR, "Error occured during zmq_socket(): %s\n", ZMQ_STRERROR); | |||||
| zmq_ctx_term(s->context); | |||||
| return AVERROR_EXTERNAL; | |||||
| } | |||||
| ret = zmq_bind(s->socket, uri); | |||||
| if (ret == -1) { | |||||
| av_log(h, AV_LOG_ERROR, "Error occured during zmq_bind(): %s\n", ZMQ_STRERROR); | |||||
| zmq_close(s->socket); | |||||
| zmq_ctx_term(s->context); | |||||
| return AVERROR_EXTERNAL; | |||||
| } | |||||
| } | |||||
| /*subscribe for read*/ | |||||
| if (h->flags & AVIO_FLAG_READ) { | |||||
| s->socket = zmq_socket(s->context, ZMQ_SUB); | |||||
| if (!s->socket) { | |||||
| av_log(h, AV_LOG_ERROR, "Error occured during zmq_socket(): %s\n", ZMQ_STRERROR); | |||||
| zmq_ctx_term(s->context); | |||||
| return AVERROR_EXTERNAL; | |||||
| } | |||||
| zmq_setsockopt(s->socket, ZMQ_SUBSCRIBE, "", 0); | |||||
| ret = zmq_connect(s->socket, uri); | |||||
| if (ret == -1) { | |||||
| av_log(h, AV_LOG_ERROR, "Error occured during zmq_connect(): %s\n", ZMQ_STRERROR); | |||||
| zmq_close(s->socket); | |||||
| zmq_ctx_term(s->context); | |||||
| return AVERROR_EXTERNAL; | |||||
| } | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| static int zmq_proto_write(URLContext *h, const unsigned char *buf, int size) | |||||
| { | |||||
| int ret; | |||||
| ZMQContext *s = h->priv_data; | |||||
| ret = zmq_proto_wait_timeout(h, s->socket, 1, h->rw_timeout, &h->interrupt_callback); | |||||
| if (ret) | |||||
| return ret; | |||||
| ret = zmq_send(s->socket, buf, size, 0); | |||||
| if (ret == -1) { | |||||
| av_log(h, AV_LOG_ERROR, "Error occured during zmq_send(): %s\n", ZMQ_STRERROR); | |||||
| return AVERROR_EXTERNAL; | |||||
| } | |||||
| return ret; /*number of bytes sent*/ | |||||
| } | |||||
| static int zmq_proto_read(URLContext *h, unsigned char *buf, int size) | |||||
| { | |||||
| int ret; | |||||
| ZMQContext *s = h->priv_data; | |||||
| ret = zmq_proto_wait_timeout(h, s->socket, 0, h->rw_timeout, &h->interrupt_callback); | |||||
| if (ret) | |||||
| return ret; | |||||
| ret = zmq_recv(s->socket, buf, size, 0); | |||||
| if (ret == -1) { | |||||
| av_log(h, AV_LOG_ERROR, "Error occured during zmq_recv(): %s\n", ZMQ_STRERROR); | |||||
| return AVERROR_EXTERNAL; | |||||
| } | |||||
| if (ret > size) { | |||||
| s->pkt_size_overflow = FFMAX(s->pkt_size_overflow, ret); | |||||
| av_log(h, AV_LOG_WARNING, "Message exceeds available space in the buffer. Message will be truncated. Setting -pkt_size %d may resolve the issue.\n", s->pkt_size_overflow); | |||||
| ret = size; | |||||
| } | |||||
| return ret; /*number of bytes read*/ | |||||
| } | |||||
| static int zmq_proto_close(URLContext *h) | |||||
| { | |||||
| ZMQContext *s = h->priv_data; | |||||
| zmq_close(s->socket); | |||||
| zmq_ctx_term(s->context); | |||||
| return 0; | |||||
| } | |||||
| static const AVClass zmq_context_class = { | |||||
| .class_name = "zmq", | |||||
| .item_name = av_default_item_name, | |||||
| .option = options, | |||||
| .version = LIBAVUTIL_VERSION_INT, | |||||
| }; | |||||
| const URLProtocol ff_libzmq_protocol = { | |||||
| .name = "zmq", | |||||
| .url_close = zmq_proto_close, | |||||
| .url_open = zmq_proto_open, | |||||
| .url_read = zmq_proto_read, | |||||
| .url_write = zmq_proto_write, | |||||
| .priv_data_size = sizeof(ZMQContext), | |||||
| .priv_data_class = &zmq_context_class, | |||||
| .flags = URL_PROTOCOL_FLAG_NETWORK, | |||||
| }; | |||||
| @@ -68,6 +68,7 @@ extern const URLProtocol ff_librtmpte_protocol; | |||||
| extern const URLProtocol ff_libsrt_protocol; | extern const URLProtocol ff_libsrt_protocol; | ||||
| extern const URLProtocol ff_libssh_protocol; | extern const URLProtocol ff_libssh_protocol; | ||||
| extern const URLProtocol ff_libsmbclient_protocol; | extern const URLProtocol ff_libsmbclient_protocol; | ||||
| extern const URLProtocol ff_libzmq_protocol; | |||||
| #include "libavformat/protocol_list.c" | #include "libavformat/protocol_list.c" | ||||
| @@ -32,7 +32,7 @@ | |||||
| // Major bumping may affect Ticket5467, 5421, 5451(compatibility with Chromium) | // Major bumping may affect Ticket5467, 5421, 5451(compatibility with Chromium) | ||||
| // Also please add any ticket numbers that you believe might be affected here | // Also please add any ticket numbers that you believe might be affected here | ||||
| #define LIBAVFORMAT_VERSION_MAJOR 58 | #define LIBAVFORMAT_VERSION_MAJOR 58 | ||||
| #define LIBAVFORMAT_VERSION_MINOR 31 | |||||
| #define LIBAVFORMAT_VERSION_MINOR 32 | |||||
| #define LIBAVFORMAT_VERSION_MICRO 104 | #define LIBAVFORMAT_VERSION_MICRO 104 | ||||
| #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ | #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ | ||||