]> git.sesse.net Git - nageru/commitdiff
Implement SRT output.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 6 Aug 2023 20:51:18 +0000 (22:51 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 6 Aug 2023 20:57:12 +0000 (22:57 +0200)
This is useful for push, and for bad networks (e.g. 4G).
You can in theory push to another Nageru instance, but the most
logical would either be to a Cubemap (running FFmpeg to demux,
unfortunately), or to something like YouTube, which is now working
on SRT ingest.

Note that for YouTube SRT ingest to work, someone from YouTube needs to
set a special flag on your account for now.

README
nageru/defs.h
nageru/flags.cpp
nageru/flags.h
nageru/main.cpp
nageru/quicksync_encoder.cpp
nageru/quicksync_encoder.h
nageru/quicksync_encoder_impl.h
nageru/video_encoder.cpp
nageru/video_encoder.h

diff --git a/README b/README
index 095054743ba5978e6e6133aba004e45df823565f..b4690fa759fac6aa1bd6d174a792eb97a5da0af5 100644 (file)
--- a/README
+++ b/README
@@ -91,8 +91,9 @@ Nageru currently needs:
  - Optional: libsrt, for SRT inputs (by default, Nageru will listen on
    port 9710, although you can change this port on the command line,
    turn it off with --srt-port -1, or turn it off live in the UI).
-   If you build with libsrt, make sure it is not linked to OpenSSL,
-   for license reasons.
+   SRT can also be used for output in addition to listening for HTTP
+   (see --srt-destination). If you build with libsrt, make sure it is not
+   linked to OpenSSL, for license reasons.
 
  - Optional: SVT-AV1, for encoding high-quality video suitable for streaming to
    end users (higher quality than using x264, but not nearly as mature).
index a8817f637ebb550c90521626b612e450f1f06c5e..8f320e154f8ad243e595cc0e8e765b04d91b965e 100644 (file)
@@ -23,6 +23,7 @@
 #define DEFAULT_STREAM_MUX_NAME "nut"  // Only for HTTP. Local dump guesses from LOCAL_DUMP_SUFFIX.
 #define DEFAULT_HTTPD_PORT 9095
 #define DEFAULT_SRT_PORT 9710
+#define DEFAULT_SRT_OUTPUT_LATENCY_MS 2000
 
 #include "shared/shared_defs.h"
 
index 3c1575589b6a33169e1f922c9eb726fd55218188..965cdc7402d5aeac372fbadc9e618f7dacb4037e 100644 (file)
@@ -51,6 +51,11 @@ enum LongOption {
        OPTION_HTTP_PORT,
        OPTION_SRT_PORT,
        OPTION_NO_SRT,
+       OPTION_SRT_DESTINATION,
+       OPTION_SRT_STREAMID,
+       OPTION_SRT_PASSPHRASE,
+       OPTION_SRT_YOUTUBE_STREAM_KEY,
+       OPTION_SRT_LATENCY,
        OPTION_NO_TRANSCODE_VIDEO,
        OPTION_NO_TRANSCODE_AUDIO,
        OPTION_DISABLE_AUDIO,
@@ -186,7 +191,7 @@ void usage(Program program)
                fprintf(stderr, "      --x264-separate-disk-param=NAME[,VALUE] set any x264 parameter, for fine tuning\n");
        }
 #ifdef HAVE_AV1
-       fprintf(stderr, "      --http-av1-video            send AV1-compressed video to HTTP clients\n");
+       fprintf(stderr, "      --http-av1-video            send AV1-compressed video to HTTP clients and SRT output\n");
        fprintf(stderr, "      --av1-preset                SVT-AV1 quality preset (default %d, from -2 to 13)\n",
                DEFAULT_AV1_PRESET);
        fprintf(stderr, "      --av1-bitrate               AV1 bitrate (in kilobit/sec, default %d)\n",
@@ -196,9 +201,9 @@ void usage(Program program)
        fprintf(stderr, "      --av1-param=NAME[,VALUE]    set any SVT-AV1 parameter, for fine tuning\n");
 #endif
        fprintf(stderr, "      --http-mux=NAME             mux to use for HTTP streams (default " DEFAULT_STREAM_MUX_NAME ")\n");
-       fprintf(stderr, "      --http-audio-codec=NAME     audio codec to use for HTTP streams\n");
+       fprintf(stderr, "      --http-audio-codec=NAME     audio codec to use for HTTP streams and SRT output\n");
        fprintf(stderr, "                                  (default is to use the same as for the recording)\n");
-       fprintf(stderr, "      --http-audio-bitrate=KBITS  audio codec bit rate to use for HTTP streams\n");
+       fprintf(stderr, "      --http-audio-bitrate=KBITS  audio codec bit rate to use for HTTP streams and SRT output\n");
        fprintf(stderr, "                                  (default is %d, ignored unless --http-audio-codec is set)\n",
                DEFAULT_AUDIO_OUTPUT_BIT_RATE / 1000);
        fprintf(stderr, "      --http-port=PORT            which port to use for the built-in HTTP server\n");
@@ -216,6 +221,15 @@ void usage(Program program)
                fprintf(stderr, "      --disable-audio             do not include any audio in the stream\n");
        }
        if (program == PROGRAM_NAGERU) {
+#ifdef HAVE_SRT
+               fprintf(stderr, "      --srt-destination=HOST:PORT  send HTTP video stream also to given destination\n");
+               fprintf(stderr, "      --srt-streamid=STREAMID     SRT stream identifying ID\n");
+               fprintf(stderr, "      --srt-passphrase=PASSPHRASE  SRT encryption key\n");
+               fprintf(stderr, "      --srt-youtube-stream-key=KEY  shortcut for --srt-destination=a.srt.youtube.com:2010\n");
+               fprintf(stderr, "                                    --srt-streamid=#!::u=<KEY>,copy=0,encoder=Nageru\n");
+               fprintf(stderr, "      --srt-latency=MS            output SRT latency in milliseconds (default is %d)\n", DEFAULT_SRT_OUTPUT_LATENCY_MS);
+               fprintf(stderr, "                                    --srt-streamid=#!::u=<KEY>,copy=0,encoder=Nageru\n");
+#endif
                fprintf(stderr, "      --flat-audio                start with most audio processing turned off\n");
                fprintf(stderr, "                                    (can be overridden by e.g. --enable-limiter)\n");
                fprintf(stderr, "      --gain-staging=DB           set initial gain staging to the given value\n");
@@ -306,6 +320,13 @@ void parse_flags(Program program, int argc, char * const argv[])
                { "srt-port", required_argument, 0, OPTION_SRT_PORT },
 #endif
                { "no-srt", no_argument, 0, OPTION_NO_SRT },  // We silently allow this even without HAVE_SRT.
+#ifdef HAVE_SRT
+               { "srt-destination", required_argument, 0, OPTION_SRT_DESTINATION },
+               { "srt-streamid", required_argument, 0, OPTION_SRT_STREAMID },
+               { "srt-passphrase", required_argument, 0, OPTION_SRT_PASSPHRASE },
+               { "srt-youtube-stream-key", required_argument, 0, OPTION_SRT_YOUTUBE_STREAM_KEY },
+               { "srt-latency", required_argument, 0, OPTION_SRT_LATENCY },
+#endif
                { "no-transcode-video", no_argument, 0, OPTION_NO_TRANSCODE_VIDEO },
                { "no-transcode-audio", no_argument, 0, OPTION_NO_TRANSCODE_AUDIO },
                { "disable-audio", no_argument, 0, OPTION_DISABLE_AUDIO },
@@ -446,6 +467,37 @@ void parse_flags(Program program, int argc, char * const argv[])
                case OPTION_NO_SRT:
                        global_flags.srt_port = -1;
                        break;
+#ifdef HAVE_SRT
+               case OPTION_SRT_DESTINATION: {
+                       const char *ptr = strrchr(optarg, ':');
+                       if (ptr == nullptr || strlen(optarg) < 3) {
+                               fprintf(stderr, "ERROR: --srt-destination must be of the form host:port\n");
+                               exit(1);
+                       }
+                       global_flags.srt_destination_host = string(optarg, ptr - optarg);
+                       if (global_flags.srt_destination_host[0] == '[' &&
+                           global_flags.srt_destination_host.back() == ']') {
+                               // Support [IPv6]:port, in a sort of hackish way.
+                               global_flags.srt_destination_host = global_flags.srt_destination_host.substr(1, global_flags.srt_destination_host.size() - 2);
+                       }
+                       global_flags.srt_destination_port = ptr + 1;
+                       break;
+               }
+               case OPTION_SRT_STREAMID:
+                       global_flags.srt_streamid = optarg;
+                       break;
+               case OPTION_SRT_PASSPHRASE:
+                       global_flags.srt_passphrase = optarg;
+                       break;
+               case OPTION_SRT_YOUTUBE_STREAM_KEY:
+                       global_flags.srt_destination_host = "a.srt.youtube.com";
+                       global_flags.srt_destination_port = "2010";
+                       global_flags.srt_streamid = string("#!::u=") + optarg + ",copy=0,encoder=Nageru " NAGERU_VERSION;
+                       break;
+               case OPTION_SRT_LATENCY:
+                       global_flags.srt_output_latency = atoi(optarg);
+                       break;
+#endif
                case OPTION_NO_TRANSCODE_VIDEO:
                        global_flags.transcode_video = false;
                        break;
@@ -831,4 +883,9 @@ void parse_flags(Program program, int argc, char * const argv[])
                        global_flags.card_to_mjpeg_stream_export[card_idx] = card_idx;
                }
        }
+
+       if (global_flags.srt_destination_host == "a.srt.youtube.com" && global_flags.srt_passphrase.empty()) {
+               fprintf(stderr, "ERROR: Cannot stream to YouTube without --srt-passphrase (get it from the YouTube streaming console)\n");
+               exit(1);
+       }
 }
index 54e971a6b0ae2f78dc399d125e2d834e1d029b7c..91e8d8568ef9354dcd01d576a328c953f49ea2ae 100644 (file)
@@ -83,6 +83,11 @@ struct Flags {
        bool use_zerocopy = false;  // Not user-settable.
        bool fullscreen = false;
        std::map<unsigned, unsigned> card_to_mjpeg_stream_export;  // If a card is not in the map, it is not exported.
+       std::string srt_destination_host;
+       std::string srt_destination_port;
+       std::string srt_streamid;
+       std::string srt_passphrase;
+       int srt_output_latency = DEFAULT_SRT_OUTPUT_LATENCY_MS;
 };
 extern Flags global_flags;
 
index 37034edd2c823fbb36eb7d3dc262be86f258c7e2..9b26426fb0a9e2e81111d9f62260df95b791b500 100644 (file)
@@ -82,7 +82,7 @@ int main(int argc, char *argv[])
 #endif
 
 #ifdef HAVE_SRT
-       if (global_flags.srt_port >= 0) {
+       if (global_flags.srt_port >= 0 || !global_flags.srt_destination_host.empty()) {
                srt_startup();
        }
 #endif
index 0d0fbec7d23f42155c2ae5165148976dbd745644..d75dbeb7372ea4a2553a753bce40b94a72e41751 100644 (file)
@@ -1353,6 +1353,7 @@ void QuickSyncEncoderImpl::save_codeddata(GLSurface *surf, storage_task task)
                if (!global_flags.x264_video_to_http &&
                    !global_flags.av1_video_to_http) {
                        http_mux->add_packet(pkt, task.pts + global_delay(), task.dts + global_delay());
+                       srt_mux->add_packet(pkt, task.pts + global_delay(), task.dts + global_delay());
                }
        }
 }
@@ -2050,6 +2051,11 @@ void QuickSyncEncoder::set_http_mux(Mux *mux)
        impl->set_http_mux(mux);
 }
 
+void QuickSyncEncoder::set_srt_mux(Mux *mux)
+{
+       impl->set_srt_mux(mux);
+}
+
 int64_t QuickSyncEncoder::global_delay() const {
        return impl->global_delay();
 }
index 6c27cfbcbae8cac26795fdc2d8490640e0864e5f..c900540d7016cf025e8e4816ce0c68ebc270a3ad 100644 (file)
@@ -67,6 +67,7 @@ public:
         ~QuickSyncEncoder();
 
        void set_http_mux(Mux *mux);  // Does not take ownership. Must be called unless x264 is used for the stream.
+       void set_srt_mux(Mux *mux);  // Does not take ownership. Must be called if SRT is to be used, unless x264 is used for the stream.
        void add_audio(int64_t pts, std::vector<float> audio);  // Thread-safe.
        bool is_zerocopy() const;  // Thread-safe.
 
index ee8ec4789dec6926dee6c81b1e7463aec8de81d6..94c62518d4422d02a99a113fa29d2525dce9e372 100644 (file)
@@ -56,6 +56,10 @@ public:
        {
                http_mux = mux;
        }
+       void set_srt_mux(Mux *mux)
+       {
+               srt_mux = mux;
+       }
 
        // So we never get negative dts.
        int64_t global_delay() const {
@@ -175,6 +179,7 @@ private:
        std::unique_ptr<V4LOutput> v4l_output;  // nullptr if not using V4L2 output.
 
        Mux* http_mux = nullptr;  // To the HTTP server.
+       Mux* srt_mux = nullptr;  // To the remote SRT endpoint, if any.
        std::unique_ptr<Mux> file_mux;  // To local disk.
 
        // Encoder parameters
index ca5ed01b6569f4fc60807070336ec2a904c8678f..29f31050e084a4e9527407cbdfc6823c0a7330d4 100644 (file)
@@ -4,6 +4,9 @@
 #include <stdio.h>
 #include <time.h>
 #include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
 #include <string>
 #include <thread>
 
@@ -54,6 +57,10 @@ string generate_local_dump_filename(int frame)
 VideoEncoder::VideoEncoder(ResourcePool *resource_pool, QSurface *surface, const std::string &va_display, int width, int height, HTTPD *httpd, DiskSpaceEstimator *disk_space_estimator)
        : resource_pool(resource_pool), surface(surface), va_display(va_display), width(width), height(height), httpd(httpd), disk_space_estimator(disk_space_estimator)
 {
+       // TODO: If we're outputting AV1, we can't use MPEG-TS currently.
+       srt_oformat = av_guess_format("mpegts", nullptr, nullptr);
+       assert(srt_oformat != nullptr);
+
        oformat = av_guess_format(global_flags.stream_mux_name.c_str(), nullptr, nullptr);
        assert(oformat != nullptr);
        if (global_flags.stream_audio_codec_name.empty()) {
@@ -80,15 +87,19 @@ VideoEncoder::VideoEncoder(ResourcePool *resource_pool, QSurface *surface, const
        string filename = generate_local_dump_filename(/*frame=*/0);
        quicksync_encoder.reset(new QuickSyncEncoder(filename, resource_pool, surface, va_display, width, height, oformat, http_encoder, disk_encoder, disk_space_estimator));
 
-       open_output_stream();
+       open_output_streams();
        stream_audio_encoder->add_mux(http_mux.get());
+       stream_audio_encoder->add_mux(srt_mux.get());
        quicksync_encoder->set_http_mux(http_mux.get());
+       quicksync_encoder->set_srt_mux(srt_mux.get());
        if (global_flags.x264_video_to_http) {
                x264_encoder->add_mux(http_mux.get());
+               x264_encoder->add_mux(srt_mux.get());
        }
 #ifdef HAVE_AV1
        if (global_flags.av1_video_to_http) {
                av1_encoder->add_mux(http_mux.get());
+               av1_encoder->add_mux(srt_mux.get());
        }
 #endif
 }
@@ -202,38 +213,54 @@ RefCountedGLsync VideoEncoder::end_frame()
        return quicksync_encoder->end_frame();
 }
 
-void VideoEncoder::open_output_stream()
+void VideoEncoder::open_output_streams()
 {
-       AVFormatContext *avctx = avformat_alloc_context();
-       avctx->oformat = oformat;
+       for (bool is_srt : {false, true}) {
+               if (is_srt && global_flags.srt_destination_host.empty()) {
+                       continue;
+               }
 
-       uint8_t *buf = (uint8_t *)av_malloc(MUX_BUFFER_SIZE);
-       avctx->pb = avio_alloc_context(buf, MUX_BUFFER_SIZE, 1, this, nullptr, nullptr, nullptr);
-       avctx->pb->write_data_type = &VideoEncoder::write_packet2_thunk;
-       avctx->pb->ignore_boundary_point = 1;
+               AVFormatContext *avctx = avformat_alloc_context();
+               avctx->oformat = is_srt ? srt_oformat : oformat;
 
-       Mux::Codec video_codec;
-       if (global_flags.av1_video_to_http) {
-               video_codec = Mux::CODEC_AV1;
-       } else {
-               video_codec = Mux::CODEC_H264;
-       }
+               uint8_t *buf = (uint8_t *)av_malloc(MUX_BUFFER_SIZE);
+               avctx->pb = avio_alloc_context(buf, MUX_BUFFER_SIZE, 1, this, nullptr, nullptr, nullptr);
+               if (is_srt) {
+                       avctx->pb->write_packet = &VideoEncoder::write_srt_packet_thunk;
+               } else {
+                       avctx->pb->write_data_type = &VideoEncoder::write_packet2_thunk;
+                       avctx->pb->ignore_boundary_point = 1;
+               }
+
+               Mux::Codec video_codec;
+               if (global_flags.av1_video_to_http) {
+                       video_codec = Mux::CODEC_AV1;
+               } else {
+                       video_codec = Mux::CODEC_H264;
+               }
 
-       avctx->flags = AVFMT_FLAG_CUSTOM_IO;
+               avctx->flags = AVFMT_FLAG_CUSTOM_IO;
 
-       string video_extradata;
-       if (global_flags.x264_video_to_http) {
-               video_extradata = x264_encoder->get_global_headers();
+               string video_extradata;
+               if (global_flags.x264_video_to_http) {
+                       video_extradata = x264_encoder->get_global_headers();
 #ifdef HAVE_AV1
-       } else if (global_flags.av1_video_to_http) {
-               video_extradata = av1_encoder->get_global_headers();
+               } else if (global_flags.av1_video_to_http) {
+                       video_extradata = av1_encoder->get_global_headers();
 #endif
-       }
+               }
 
-       http_mux.reset(new Mux(avctx, width, height, video_codec, video_extradata, stream_audio_encoder->get_codec_parameters().get(),
-               get_color_space(global_flags.ycbcr_rec709_coefficients), COARSE_TIMEBASE,
-               /*write_callback=*/nullptr, Mux::WRITE_FOREGROUND, { &http_mux_metrics }));
-       http_mux_metrics.init({{ "destination", "http" }});
+               Mux *mux = new Mux(avctx, width, height, video_codec, video_extradata, stream_audio_encoder->get_codec_parameters().get(),
+                       get_color_space(global_flags.ycbcr_rec709_coefficients), COARSE_TIMEBASE,
+                       /*write_callback=*/nullptr, is_srt ? Mux::WRITE_BACKGROUND : Mux::WRITE_FOREGROUND, { is_srt ? &srt_mux_metrics : &http_mux_metrics });
+               if (is_srt) {
+                       srt_mux.reset(mux);
+                       srt_mux_metrics.init({{ "destination", "srt" }});
+               } else {
+                       http_mux.reset(mux);
+                       http_mux_metrics.init({{ "destination", "http" }});
+               }
+       }
 }
 
 int VideoEncoder::write_packet2_thunk(void *opaque, uint8_t *buf, int buf_size, AVIODataMarkerType type, int64_t time)
@@ -261,3 +288,127 @@ int VideoEncoder::write_packet2(uint8_t *buf, int buf_size, AVIODataMarkerType t
        return buf_size;
 }
 
+int VideoEncoder::write_srt_packet_thunk(void *opaque, uint8_t *buf, int buf_size)
+{
+       VideoEncoder *video_encoder = (VideoEncoder *)opaque;
+       return video_encoder->write_srt_packet(buf, buf_size);
+}
+
+static string print_addrinfo(const addrinfo *ai)
+{
+       char hoststr[NI_MAXHOST], portstr[NI_MAXSERV];
+       if (getnameinfo(ai->ai_addr, ai->ai_addrlen, hoststr, sizeof(hoststr), portstr, sizeof(portstr), NI_DGRAM | NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
+               return "<unknown address>";  // Should basically never happen, since we're not doing DNS lookups.
+       }
+
+       if (ai->ai_family == AF_INET6) {
+               return string("[") + hoststr + "]:" + portstr;
+       } else {
+               return string(hoststr) + ":" + portstr;
+       }
+}
+
+int VideoEncoder::open_srt_socket()
+{
+       int sock = srt_create_socket();
+       if (sock == -1) {
+               fprintf(stderr, "srt_create_socket(): %s\n", srt_getlasterror_str());
+               return -1;
+       }
+
+       SRT_TRANSTYPE live = SRTT_LIVE;
+       if (srt_setsockopt(sock, 0, SRTO_TRANSTYPE, &live, sizeof(live)) < 0) {
+               fprintf(stderr, "srt_setsockopt(SRTO_TRANSTYPE): %s\n", srt_getlasterror_str());
+               srt_close(sock);
+               return -1;
+       }
+
+       if (srt_setsockopt(sock, 0, SRTO_LATENCY, &global_flags.srt_output_latency, sizeof(global_flags.srt_output_latency)) < 0) {
+               fprintf(stderr, "srt_setsockopt(SRTO_LATENCY): %s\n", srt_getlasterror_str());
+               srt_close(sock);
+               return -1;
+       }
+
+       if (!global_flags.srt_streamid.empty()) {
+               if (srt_setsockopt(sock, 0, SRTO_STREAMID, global_flags.srt_streamid.data(), global_flags.srt_streamid.size()) < 0) {
+                       fprintf(stderr, "srt_setsockopt(SRTO_STREAMID): %s\n", srt_getlasterror_str());
+                       srt_close(sock);
+                       return -1;
+               }
+       }
+
+       if (!global_flags.srt_passphrase.empty()) {
+               if (srt_setsockopt(sock, 0, SRTO_PASSPHRASE, global_flags.srt_passphrase.data(), global_flags.srt_passphrase.size()) < 0) {
+                       fprintf(stderr, "srt_setsockopt(SRTO_PASSPHRASE): %s\n", srt_getlasterror_str());
+                       srt_close(sock);
+                       return -1;
+               }
+       }
+
+       return sock;
+}
+
+int VideoEncoder::connect_to_srt()
+{
+       // We need to specify SOCK_DGRAM as a hint, or we'll get all addresses
+       // three times (for each of TCP, UDP, raw).
+       addrinfo hints;
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_flags = AI_ADDRCONFIG;
+       hints.ai_socktype = SOCK_DGRAM;
+
+       addrinfo *ai;
+       int ret = getaddrinfo(global_flags.srt_destination_host.c_str(), global_flags.srt_destination_port.c_str(), &hints, &ai);
+       if (ret != 0) {
+               fprintf(stderr, "getaddrinfo(%s:%s): %s\n", global_flags.srt_destination_host.c_str(), global_flags.srt_destination_port.c_str(), gai_strerror(ret));
+               return -1;
+       }
+
+       for (const addrinfo *cur = ai; cur != nullptr; cur = cur->ai_next) {
+               // Seemingly, srt_create_socket() isn't universal; once we try to connect,
+               // it gets locked to either IPv4 or IPv6. So we need to create a new one
+               // for every address we try.
+               int sock = open_srt_socket();
+               if (sock == -1) {
+                       // Die immediately.
+                       return sock;
+               }
+               if (srt_connect(sock, cur->ai_addr, cur->ai_addrlen) < 0) {
+                       fprintf(stderr, "srt_connect(%s): %s\n", print_addrinfo(cur).c_str(), srt_getlasterror_str());
+                       srt_close(sock);
+                       continue;
+               }
+               fprintf(stderr, "Connected to destination SRT endpoint at %s.\n", print_addrinfo(cur).c_str());
+               freeaddrinfo(ai);
+               return sock;
+       }
+
+       // Out of candidates, so give up.
+       freeaddrinfo(ai);
+       return -1;
+}
+
+int VideoEncoder::write_srt_packet(uint8_t *buf, int buf_size)
+{
+       while (buf_size > 0) {
+               if (srt_sock == -1) {
+                       srt_sock = connect_to_srt();
+                       if (srt_sock == -1) {
+                               usleep(100000);
+                               continue;
+                       }
+               }
+               int to_send = min(buf_size, SRT_LIVE_DEF_PLSIZE);
+               int ret = srt_send(srt_sock, (char *)buf, to_send);
+               if (ret < 0)  {
+                       fprintf(stderr, "srt_send(): %s\n", srt_getlasterror_str());
+                       srt_close(srt_sock);
+                       srt_sock = connect_to_srt();
+                       continue;
+               }
+               buf += ret;
+               buf_size -= ret;
+       }
+       return buf_size;
+}
+
index 61b16f763d3cdb3211ca864fc311b79c9c47cb22..86badf2a9a189fa708789045273f576336225e5e 100644 (file)
@@ -20,6 +20,8 @@ extern "C" {
 #include <libavformat/avio.h>
 }
 
+#include <srt/srt.h>
+
 #include "shared/mux.h"
 #include "shared/ref_counted_gl_sync.h"
 
@@ -75,11 +77,16 @@ public:
        void change_x264_bitrate(unsigned rate_kbit);
 
 private:
-       void open_output_stream();
+       void open_output_streams();
        static int write_packet2_thunk(void *opaque, uint8_t *buf, int buf_size, AVIODataMarkerType type, int64_t time);
        int write_packet2(uint8_t *buf, int buf_size, AVIODataMarkerType type, int64_t time);
 
-       const AVOutputFormat *oformat;
+       static int write_srt_packet_thunk(void *opaque, uint8_t *buf, int buf_size);
+       int write_srt_packet(uint8_t *buf, int buf_size);
+       int open_srt_socket();  // Returns -1 on error.
+       int connect_to_srt();  // Returns -1 on error.
+
+       const AVOutputFormat *oformat, *srt_oformat;
        mutable std::mutex qs_mu, qs_audio_mu;
        std::unique_ptr<QuickSyncEncoder> quicksync_encoder;  // Under <qs_mu> _and_ <qs_audio_mu>.
        movit::ResourcePool *resource_pool;
@@ -92,6 +99,7 @@ private:
        bool seen_sync_markers = false;
 
        std::unique_ptr<Mux> http_mux;  // To the HTTP server.
+       std::unique_ptr<Mux> srt_mux;  // To the SRT endpoint (if any).
        std::unique_ptr<AudioEncoder> stream_audio_encoder;
        std::unique_ptr<X264Encoder> x264_encoder;  // nullptr if not using x264.
        std::unique_ptr<X264Encoder> x264_disk_encoder;  // nullptr if not using x264, or if not having separate disk encodes.
@@ -99,8 +107,11 @@ private:
        std::unique_ptr<AV1Encoder> av1_encoder;  // nullptr if not using SVT-AV1.
 #endif
 
+       SRTSOCKET srt_sock = -1;
+
        std::string http_mux_header;
        MuxMetrics http_mux_metrics;
+       MuxMetrics srt_mux_metrics;
 
        std::atomic<int> quicksync_encoders_in_shutdown{0};
        std::atomic<int> overriding_bitrate{0};