PR #23263 opened by birkedal
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23263
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23263.patch

An initial implementation of MoQ for FFmpeg using fragmented mp4 (fMP4).

### How to test MoQ locally

1. Clone https://github.com/moq-dev/moq
2. run `just relay` to run the moq-relay locally.
3. run `just web` to run the player locally, listening for streams to the local 
relay.
4. Build FFmpeg with support for MoQ `--enable-libmoq`, details in muxers.texi.
5. `ffmpeg -i INPUT -c copy -f moq http://localhost:4443/test/stream`
6. Check that it plays fine in the web player.

### Additional features that could make sense to implement and test

1. Support for MPEG-TS over MoQ
2. Configurable fragment periods, especially for audio
3. Untested with other media types like data tracks (scte35, time codes etc.)



>From d61a2a1a041efd55612c2feab733c2f3fc371849 Mon Sep 17 00:00:00 2001
From: Ole Andre Birkedal <[email protected]>
Date: Thu, 28 May 2026 15:27:15 +0200
Subject: [PATCH] Media over QUIC (MoQ) support for FFmpeg using fragmented mp4

---
 MAINTAINERS               |   1 +
 configure                 |  14 ++
 doc/general_contents.texi |   8 +
 doc/muxers.texi           |  57 +++++
 libavformat/Makefile      |   1 +
 libavformat/allformats.c  |   1 +
 libavformat/moqenc.c      | 455 ++++++++++++++++++++++++++++++++++++++
 7 files changed, 537 insertions(+)
 create mode 100644 libavformat/moqenc.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 3bf41bd8cb..bb6d332356 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -494,6 +494,7 @@ Muxers/Demuxers:
   webm dash (matroskaenc.c)             Vignesh Venkatasubramanian
   webvtt*                               Matthew J Heaney
   westwood.c                            Mike Melanson
+  moqenc.c                          [2] Ole Andre Birkedal
   whip.c                            [2] Jack Lau
   wtv.c                                 Peter Ross
 
diff --git a/configure b/configure
index 1abf53b678..205e4cfc71 100755
--- a/configure
+++ b/configure
@@ -249,6 +249,7 @@ External library support:
   --enable-liblcevc-dec    enable LCEVC decoding via liblcevc-dec [no]
   --enable-liblensfun      enable lensfun lens correction [no]
   --enable-libmodplug      enable ModPlug via libmodplug [no]
+  --enable-libmoq          enable Media over QUIC output via libmoq [no]
   --enable-libmp3lame      enable MP3 encoding via libmp3lame [no]
   --enable-libmpeghdec     enable MPEG-H 3DA decoding via libmpeghdec [no]
   --enable-liboapv         enable APV encoding via liboapv [no]
@@ -2104,6 +2105,7 @@ EXTERNAL_LIBRARY_LIST="
     liblc3
     liblcevc_dec
     libmodplug
+    libmoq
     libmp3lame
     libmysofa
     liboapv
@@ -3970,6 +3972,8 @@ mov_demuxer_select="iso_media riffdec"
 mov_demuxer_suggest="iamfdec zlib"
 mov_muxer_select="cbs_apv_lavf cbs_av1_lavf iso_media iso_writer riffenc 
rtpenc_chain vp9_superframe_bsf aac_adtstoasc_bsf ac3_parser"
 mov_muxer_suggest="iamfenc"
+moq_muxer_deps="libmoq"
+moq_muxer_select="mov_muxer"
 mp3_demuxer_select="mpegaudio_parser"
 mp3_muxer_select="mpegaudioheader"
 mp4_muxer_select="mov_muxer"
@@ -7395,6 +7399,16 @@ if enabled libmfx; then
 fi
 
 enabled libmodplug        && require_pkg_config libmodplug libmodplug 
libmodplug/modplug.h ModPlug_Load
+if enabled libmoq; then
+    case $target_os in
+        darwin*)
+            require libmoq moq.h moq_session_connect -lmoq -framework 
CoreFoundation -framework Security
+            ;;
+        *)
+            require libmoq moq.h moq_session_connect -lmoq -lpthread -ldl -lm
+            ;;
+    esac
+fi
 enabled libmp3lame        && require "libmp3lame >= 3.98.3" lame/lame.h 
lame_set_VBR_quality -lmp3lame $libm_extralibs
 enabled libmpeghdec       && require_pkg_config libmpeghdec "mpeghdec >= 
3.0.0" mpeghdec/mpeghdecoder.h mpeghdecoder_init
 enabled libmysofa         && { check_pkg_config libmysofa libmysofa mysofa.h 
mysofa_neighborhood_init_withstepdefine ||
diff --git a/doc/general_contents.texi b/doc/general_contents.texi
index 5fed093642..d9043d84bd 100644
--- a/doc/general_contents.texi
+++ b/doc/general_contents.texi
@@ -199,6 +199,14 @@ Go to @url{http://www.webmproject.org/} and follow the 
instructions for
 installing the library. Then pass @code{--enable-libvpx} to configure to
 enable it.
 
+@section libmoq
+
+FFmpeg can make use of the libmoq library for publishing live media streams
+via Media over QUIC (MoQ).
+
+Go to @url{https://github.com/moq-dev/moq/tree/main/rs/libmoq} and follow the 
instructions for
+building the library. Then pass @code{--enable-libmoq} to configure to enable 
it.
+
 @section ModPlug
 
 FFmpeg can make use of this library, originating in Modplug-XMMS, to read from 
MOD-like music files.
diff --git a/doc/muxers.texi b/doc/muxers.texi
index 26199ad836..3f3fa8502c 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -2992,6 +2992,63 @@ assistants.
 
 This muxer accepts a single @samp{adpcm_yamaha} audio stream.
 
+@section moq
+
+Media over QUIC (MoQ) muxer that publishes live media streams to a MoQ relay
+using the libmoq library. MoQ is a low-latency media delivery protocol built
+on top of QUIC and WebTransport, designed for real-time broadcasting with
+sub-second latency.
+
+Each input stream is independently packaged into fragmented MP4 (fMP4) and
+published as a separate MoQ track. Video streams produce one fragment per 
frame,
+while audio streams are fragmented at 100ms intervals.
+
+To build with support for MoQ you need to configure FFmpeg with
+@code{--enable-libmoq}. To get @{libmoq} you need to build it using
+these instructions @url{https://github.com/moq-dev/moq/tree/main/rs/libmoq}.
+
+You could either use the generated @code{pkg-config} file to find the
+library or set include and library paths explicitly:
+
+@example
+./configure --enable-libmoq --extra-cflags=-I./moq/target/include/
+                            --extra-ldflags=-L./moq/target/release/
+@end example
+
+The output URL specifies the relay address with the broadcast path appended as
+the URL path:
+
+@example
+ffmpeg -i INPUT -f moq https://relay.example.com:4443/live/stream1
+@end example
+
+Here @code{https://relay.example.com:4443} is the relay and
+@code{live/stream1} is the broadcast path that subscribers use to find
+the stream. The broadcast path can alternatively be specified with the
+@option{broadcast_path} option:
+
+@example
+ffmpeg -i INPUT -f moq -broadcast_path live/stream1 
https://relay.example.com:4443
+@end example
+
+Both forms above are equivalent.
+
+The default port is 443 for @code{https} or 4443 for @code{http} if not
+specified explicitly.
+
+@subsection Options
+
+This muxer supports the following options:
+
+@table @option
+
+@item broadcast_path @var{string}
+Set the broadcast path (e.g. @code{live/stream1}). When specified, this
+overrides any path component in the output URL. Subscribers use this path to
+find and consume the stream from the relay.
+
+@end table
+
 @section mp3
 
 The MP3 muxer writes a raw MP3 stream with the following optional features:
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 33525369a4..c7395910e4 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -393,6 +393,7 @@ OBJS-$(CONFIG_MOV_MUXER)                 += movenc.o \
                                             movenchint.o mov_chan.o rtp.o \
                                             movenccenc.o movenc_ttml.o 
rawutils.o \
                                             apv.o dovi_isom.o evc.o
+OBJS-$(CONFIG_MOQ_MUXER)                 += moqenc.o
 OBJS-$(CONFIG_MP2_MUXER)                 += rawenc.o
 OBJS-$(CONFIG_MP3_DEMUXER)               += mp3dec.o replaygain.o
 OBJS-$(CONFIG_MP3_MUXER)                 += mp3enc.o rawenc.o id3v2enc.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index af7eea5e5c..027d2287e3 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -289,6 +289,7 @@ extern const FFInputFormat  ff_mods_demuxer;
 extern const FFInputFormat  ff_moflex_demuxer;
 extern const FFInputFormat  ff_mov_demuxer;
 extern const FFOutputFormat ff_mov_muxer;
+extern const FFOutputFormat ff_moq_muxer;
 extern const FFOutputFormat ff_mp2_muxer;
 extern const FFInputFormat  ff_mp3_demuxer;
 extern const FFOutputFormat ff_mp3_muxer;
diff --git a/libavformat/moqenc.c b/libavformat/moqenc.c
new file mode 100644
index 0000000000..e6433dfb76
--- /dev/null
+++ b/libavformat/moqenc.c
@@ -0,0 +1,455 @@
+/*
+ * Media over QUIC (MoQ) muxer
+ * Copyright (c) 2026 Ole Andre Birkedal
+ *
+ * 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 "config.h"
+#include "config_components.h"
+
+#include <moq.h>
+
+#include "libavutil/avstring.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+
+#include "avformat.h"
+#include "mux.h"
+
+typedef struct MOQOutputStream {
+    AVFormatContext *ctx;
+    int ctx_inited;
+
+    uint32_t track_id;
+
+    uint8_t *init_data;
+    int init_data_size;
+
+    int64_t last_input_dts;
+} MOQOutputStream;
+
+typedef struct MOQContext {
+    const AVClass *class;
+
+    /* libmoq handles */
+    uint32_t session;
+    uint32_t origin;
+    uint32_t broadcast;
+
+    char *broadcast_path;
+
+    /* Parsed from output URL */
+    char parsed_relay_url[1024];
+    char parsed_broadcast_path[1024];
+
+    MOQOutputStream *streams;
+    int tracks_created;
+} MOQContext;
+
+static void moq_session_status_callback(void *user_data, int32_t code)
+{
+    AVFormatContext *s = (AVFormatContext *)user_data;
+    if (code < 0)
+        av_log(s, AV_LOG_ERROR, "MoQ session error: %d\n", code);
+    else
+        av_log(s, AV_LOG_DEBUG, "MoQ session status: %d\n", code);
+}
+
+static int moq_init_stream(AVFormatContext *s, MOQOutputStream *os, int 
stream_idx)
+{
+    AVFormatContext *ctx;
+    AVStream *in_st, *out_st;
+    AVDictionary *opts = NULL;
+    int ret;
+
+    ctx = avformat_alloc_context();
+    if (!ctx)
+        return AVERROR(ENOMEM);
+
+    os->ctx = ctx;
+
+    ctx->oformat = av_guess_format("mp4", NULL, NULL);
+    if (!ctx->oformat) {
+        av_log(s, AV_LOG_ERROR, "Could not find mp4 muxer\n");
+        return AVERROR_MUXER_NOT_FOUND;
+    }
+
+    ctx->interrupt_callback = s->interrupt_callback;
+    ctx->flags = s->flags;
+    ctx->avoid_negative_ts = AVFMT_AVOID_NEG_TS_DISABLED;
+    ctx->max_interleave_delta = 0;
+    ctx->flush_packets = 1;
+
+    in_st = s->streams[stream_idx];
+    out_st = avformat_new_stream(ctx, NULL);
+    if (!out_st)
+        return AVERROR(ENOMEM);
+
+    ret = avcodec_parameters_copy(out_st->codecpar, in_st->codecpar);
+    if (ret < 0)
+        return ret;
+
+    out_st->time_base = in_st->time_base;
+    out_st->sample_aspect_ratio = in_st->sample_aspect_ratio;
+    out_st->avg_frame_rate = in_st->avg_frame_rate;
+    out_st->codecpar->initial_padding = 0;
+
+    ret = avio_open_dyn_buf(&ctx->pb);
+    if (ret < 0)
+        return ret;
+
+    av_dict_set(&opts, "movflags",
+                "empty_moov+default_base_moof+omit_tfhd_offset+skip_trailer",
+                AV_DICT_APPEND);
+    if (in_st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
+        av_dict_set(&opts, "movflags", "+frag_every_frame", AV_DICT_APPEND);
+    } else {
+        av_dict_set_int(&opts, "frag_duration", 100000, 0);
+    }
+    av_dict_set(&opts, "avoid_negative_ts", "disabled", 0);
+    av_dict_set(&opts, "use_editlist", "0", 0);
+    av_dict_set(&opts, "write_tmcd", "0", 0);
+
+    ret = avformat_write_header(ctx, &opts);
+    av_dict_free(&opts);
+    if (ret < 0) {
+        av_log(s, AV_LOG_ERROR, "Stream %d: could not write fMP4 header: %s\n",
+               stream_idx, av_err2str(ret));
+        return ret;
+    }
+
+    os->init_data_size = avio_close_dyn_buf(ctx->pb, &os->init_data);
+    ctx->pb = NULL;
+
+    if (os->init_data_size <= 0) {
+        av_log(s, AV_LOG_ERROR, "Stream %d: failed to extract init segment\n", 
stream_idx);
+        return AVERROR(EINVAL);
+    }
+
+    ret = avio_open_dyn_buf(&ctx->pb);
+    if (ret < 0)
+        return ret;
+
+    os->ctx_inited = 1;
+    return 0;
+}
+
+static int moq_create_track(AVFormatContext *s, MOQOutputStream *os, int 
stream_idx)
+{
+    MOQContext *moq = s->priv_data;
+    static const char format[] = "fmp4";
+    int ret;
+
+    if (!os->init_data)
+        return AVERROR(EINVAL);
+
+    // We currently just support fmp4 here, MPEG-TS could also be supported
+    ret = moq_publish_media_ordered(moq->broadcast, format, sizeof(format) - 
1, os->init_data, os->init_data_size);
+    if (ret < 0) {
+        av_log(s, AV_LOG_ERROR, "Stream %d: failed to create MoQ track: %d\n",
+               stream_idx, ret);
+        return AVERROR_EXTERNAL;
+    }
+
+    os->track_id = (uint32_t)ret;
+
+    return 0;
+}
+
+static int moq_parse_url(AVFormatContext *s)
+{
+    MOQContext *moq = s->priv_data;
+    const char *url = s->url;
+    char proto[10], authorization[256], hostname[256], path[1024];
+    int port;
+
+    if (!url || !*url) {
+        av_log(s, AV_LOG_ERROR, "No URL provided\n");
+        return AVERROR(EINVAL);
+    }
+
+    av_url_split(proto, sizeof(proto),
+                 authorization, sizeof(authorization),
+                 hostname, sizeof(hostname),
+                 &port,
+                 path, sizeof(path),
+                 url);
+
+    if (!*hostname) {
+        av_log(s, AV_LOG_ERROR, "Invalid URL format: %s\n", url);
+        av_log(s, AV_LOG_ERROR, "Expected format: 
http://relay:port/broadcast/path\n";);
+        return AVERROR(EINVAL);
+    }
+
+    if (port > 0) {
+        snprintf(moq->parsed_relay_url, sizeof(moq->parsed_relay_url),
+                 "%s://%s:%d", proto, hostname, port);
+    } else {
+        int default_port = (strcmp(proto, "https") == 0) ? 443 : 4443;
+        snprintf(moq->parsed_relay_url, sizeof(moq->parsed_relay_url),
+                 "%s://%s:%d", proto, hostname, default_port);
+    }
+
+    if (moq->broadcast_path) {
+        av_strlcpy(moq->parsed_broadcast_path, moq->broadcast_path,
+                   sizeof(moq->parsed_broadcast_path));
+    } else if (*path == '/') {
+        av_strlcpy(moq->parsed_broadcast_path, path + 1, 
sizeof(moq->parsed_broadcast_path));
+    } else {
+        av_strlcpy(moq->parsed_broadcast_path, path, 
sizeof(moq->parsed_broadcast_path));
+    }
+
+    if (!*moq->parsed_broadcast_path) {
+        av_log(s, AV_LOG_ERROR, "No broadcast path specified in URL or via 
-broadcast_path\n");
+        av_log(s, AV_LOG_ERROR, "Expected format: 
http://relay:port/broadcast/path\n";);
+        return AVERROR(EINVAL);
+    }
+
+    av_log(s, AV_LOG_INFO, "Relay URL: %s\n", moq->parsed_relay_url);
+    av_log(s, AV_LOG_INFO, "Broadcast path: %s\n", moq->parsed_broadcast_path);
+
+    return 0;
+}
+
+static int moq_connect_relay(AVFormatContext *s)
+{
+    MOQContext *moq = s->priv_data;
+    const char *relay_url = moq->parsed_relay_url;
+    const char *broadcast_path = moq->parsed_broadcast_path;
+    int ret;
+
+    av_log(s, AV_LOG_INFO, "Connecting to MoQ relay: %s\n", relay_url);
+
+    ret = moq_origin_create();
+    if (ret < 0) {
+        av_log(s, AV_LOG_ERROR, "Failed to create MoQ origin: %d\n", ret);
+        return AVERROR_EXTERNAL;
+    }
+    moq->origin = (uint32_t)ret;
+
+    ret = moq_session_connect(relay_url, strlen(relay_url), moq->origin, 0, 
moq_session_status_callback, s);
+    if (ret < 0) {
+        av_log(s, AV_LOG_ERROR, "Failed to connect to MoQ relay: %d\n", ret);
+        moq_origin_close(moq->origin);
+        return AVERROR_EXTERNAL;
+    }
+    moq->session = (uint32_t)ret;
+
+    ret = moq_publish_create();
+    if (ret < 0) {
+        av_log(s, AV_LOG_ERROR, "Failed to create broadcast: %d\n", ret);
+        moq_session_close(moq->session);
+        moq_origin_close(moq->origin);
+        return AVERROR_EXTERNAL;
+    }
+    moq->broadcast = (uint32_t)ret;
+
+    ret = moq_origin_publish(moq->origin, broadcast_path, 
strlen(broadcast_path), moq->broadcast);
+    if (ret < 0) {
+        av_log(s, AV_LOG_ERROR, "Failed to publish broadcast: %d\n", ret);
+        moq_publish_close(moq->broadcast);
+        moq_session_close(moq->session);
+        moq_origin_close(moq->origin);
+        return AVERROR_EXTERNAL;
+    }
+
+    av_log(s, AV_LOG_INFO, "Connected to MoQ relay, broadcasting to: %s\n", 
broadcast_path);
+    return 0;
+}
+
+static int moq_write_header(AVFormatContext *s)
+{
+    MOQContext *moq = s->priv_data;
+    int ret;
+
+    moq->streams = av_calloc(s->nb_streams, sizeof(*moq->streams));
+    if (!moq->streams)
+        return AVERROR(ENOMEM);
+
+    for (unsigned int i = 0; i < s->nb_streams; i++) {
+        MOQOutputStream *os = &moq->streams[i];
+        os->last_input_dts = AV_NOPTS_VALUE;
+    }
+
+    ret = moq_parse_url(s);
+    if (ret < 0)
+        return ret;
+
+    ret = moq_connect_relay(s);
+    if (ret < 0)
+        return ret;
+
+    for (unsigned int i = 0; i < s->nb_streams; i++) {
+        ret = moq_init_stream(s, &moq->streams[i], i);
+        if (ret < 0)
+            return ret;
+    }
+
+    for (unsigned int i = 0; i < s->nb_streams; i++) {
+        ret = moq_create_track(s, &moq->streams[i], i);
+        if (ret < 0)
+            return ret;
+    }
+
+    moq->tracks_created = 1;
+
+    return 0;
+}
+
+static int moq_write_packet(AVFormatContext *s, AVPacket *pkt)
+{
+    MOQContext *moq = s->priv_data;
+    MOQOutputStream *os;
+    uint8_t *buf;
+    int size, ret;
+
+    if (!moq->tracks_created)
+        return AVERROR(EINVAL);
+
+    if (pkt->stream_index >= s->nb_streams)
+        return AVERROR(EINVAL);
+
+    os = &moq->streams[pkt->stream_index];
+    if (!os->ctx_inited)
+        return AVERROR(EINVAL);
+
+    if (!pkt->duration && os->last_input_dts != AV_NOPTS_VALUE)
+        pkt->duration = pkt->dts - os->last_input_dts;
+    os->last_input_dts = pkt->dts;
+
+    ret = ff_write_chained(os->ctx, 0, pkt, s, 0);
+    if (ret < 0) {
+        av_log(s, AV_LOG_ERROR, "Stream %d: ff_write_chained failed: %s\n",
+               pkt->stream_index, av_err2str(ret));
+        return ret;
+    }
+
+    if (avio_tell(os->ctx->pb) > 0) {
+        size = avio_close_dyn_buf(os->ctx->pb, &buf);
+        if (size > 0) {
+            ret = moq_publish_media_frame(os->track_id, buf, size, 0);
+            if (ret < 0)
+                av_log(s, AV_LOG_WARNING, "Stream %d: moq_publish_media_frame 
failed: %d (%d bytes, key=%d)\n",
+                       pkt->stream_index, ret, size,
+                       (pkt->flags & AV_PKT_FLAG_KEY) ? 1 : 0);
+            av_free(buf);
+        }
+
+        ret = avio_open_dyn_buf(&os->ctx->pb);
+        if (ret < 0)
+            return ret;
+    }
+
+    return 0;
+}
+
+static int moq_write_trailer(AVFormatContext *s)
+{
+    MOQContext *moq = s->priv_data;
+    uint8_t *buf;
+    int size;
+
+    av_log(s, AV_LOG_INFO, "MoQ muxer closing\n");
+
+    for (unsigned int i = 0; i < s->nb_streams; i++) {
+        MOQOutputStream *os = &moq->streams[i];
+
+        if (!os->ctx)
+            continue;
+
+        av_write_frame(os->ctx, NULL);
+        av_write_trailer(os->ctx);
+
+        if (os->ctx->pb) {
+            size = avio_close_dyn_buf(os->ctx->pb, &buf);
+            if (size > 0 && os->track_id > 0)
+                moq_publish_media_frame(os->track_id, buf, size, 0);
+            av_free(buf);
+            os->ctx->pb = NULL;
+        }
+
+        avformat_free_context(os->ctx);
+        os->ctx = NULL;
+    }
+
+    for (unsigned int i = 0; i < s->nb_streams; i++) {
+        if (moq->streams[i].track_id > 0)
+            moq_publish_media_close(moq->streams[i].track_id);
+    }
+
+    if (moq->broadcast > 0)
+        moq_publish_close(moq->broadcast);
+    if (moq->session > 0)
+        moq_session_close(moq->session);
+    if (moq->origin > 0)
+        moq_origin_close(moq->origin);
+
+    av_log(s, AV_LOG_INFO, "MoQ muxer closed\n");
+    return 0;
+}
+
+static void moq_deinit(AVFormatContext *s)
+{
+    MOQContext *moq = s->priv_data;
+    uint8_t *buf;
+
+    if (moq->streams) {
+        for (unsigned int i = 0; i < s->nb_streams; i++) {
+            MOQOutputStream *os = &moq->streams[i];
+            if (os->ctx) {
+                if (os->ctx->pb) {
+                    avio_close_dyn_buf(os->ctx->pb, &buf);
+                    av_free(buf);
+                }
+                avformat_free_context(os->ctx);
+            }
+            av_freep(&os->init_data);
+        }
+        av_freep(&moq->streams);
+    }
+}
+
+#define OFFSET(x) offsetof(MOQContext, x)
+#define ENC AV_OPT_FLAG_ENCODING_PARAM
+
+static const AVOption options[] = {
+    { "broadcast_path", "Broadcast path (overrides path parsed from output 
URL)", OFFSET(broadcast_path), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, ENC },
+    { NULL }
+};
+
+static const AVClass moq_muxer_class = {
+    .class_name = "moq muxer",
+    .item_name  = av_default_item_name,
+    .option     = options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+const FFOutputFormat ff_moq_muxer = {
+    .p.name            = "moq",
+    .p.long_name       = NULL_IF_CONFIG_SMALL("Media over QUIC (MoQ)"),
+    .p.extensions      = "",
+    .priv_data_size    = sizeof(MOQContext),
+    .p.audio_codec     = AV_CODEC_ID_AAC,
+    .p.video_codec     = AV_CODEC_ID_H264,
+    .p.flags           = AVFMT_GLOBALHEADER | AVFMT_NOFILE | 
AVFMT_TS_NONSTRICT,
+    .p.priv_class      = &moq_muxer_class,
+    .write_header      = moq_write_header,
+    .write_packet      = moq_write_packet,
+    .write_trailer     = moq_write_trailer,
+    .deinit            = moq_deinit,
+};
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to