[vlc-commits] [Git][videolan/vlc][master] 18 commits: sout: hls: add plugin basic structure

Steve Lhomme (@robUx4) gitlab at videolan.org
Fri Aug 25 08:32:44 UTC 2023



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
b39a5e05 by Alaric Senat at 2023-08-25T08:07:27+00:00
sout: hls: add plugin basic structure

Introduce the plugin boilerplate and some basic options to make the
future commits lighter and ease the review process.

- - - - -
c6f5ebbc by Alaric Senat at 2023-08-25T08:07:27+00:00
sout: hls: add the playlist architecture

Create the structures and basis for HLS playlist/track management.

This architecture will allow the user to chose which ESes will be muxed
together. An array describing the playlist layout is stored in the HLS
context allowing every ESes to be dispatched to the right playlist in
the `pf_add` callback.

Every ES that does not match the predetermined layout will be assigned
to a new `EXT-X-MEDIA` playlist.

- - - - -
86e6eb66 by Alaric Senat at 2023-08-25T08:07:27+00:00
sout: hls: add a storage abstraction

Handy simple storage abstraction to allow support of in-memory or
filesystem HLS segment/manifest storage.

This will be used to store both segments and playlist manifests.

- - - - -
b4caa235 by Alaric Senat at 2023-08-25T08:07:27+00:00
sout: hls: implement file based storage

- - - - -
fe4f2872 by Alaric Senat at 2023-08-25T08:07:27+00:00
sout: hls: add a self-rotating segment queue

This queue handles the HLS segments lifetime, if the queue is at max
capacity, the first inserted segment will also be popped and destroyed
automatically.

- - - - -
e9fcca62 by Alaric Senat at 2023-08-25T08:07:27+00:00
sout: hls: implement HLS segmentation

To create the HLS segment we refer to the PCR value as a stream time
reference, this allow to expose all the segments at the same time and
handle non-continuous data streams (such as subtitles).

Hence, the muxed output from all tracks is bufferized and split
according to the ideal segment length when a PCR value reaches the
correct timestamp. For now, for the sake of simplicity and the fact that
the main use case of HLS (chromecast renderer) doesn't require it, we
don't ensure video segments starts with an I-Frame. This will
definitely be implemented in a future version as there is already the
mechanism ready for that in the TS muxer.

- - - - -
09704e4b by Alaric Senat at 2023-08-25T08:07:27+00:00
sout: hls: generate playlist manifest

- - - - -
9bc26e18 by Alaric Senat at 2023-08-25T08:07:27+00:00
hxxx_helper: add an avc constraint flag getter

This is needed to generate a descriptive codec identifier in the HLS
stream output.

- - - - -
644a991f by Alaric Senat at 2023-08-25T08:07:27+00:00
sout: hls: add codec format utils

HLS require to expose a very descriptive mime type for every codec
exposed. This will likely grow as we support more codecs so it belong
in its own file.

- - - - -
c12d4e87 by Alaric Senat at 2023-08-25T08:07:27+00:00
sout: hls: generate main manifest

- - - - -
cbaced19 by Alaric Senat at 2023-08-25T08:07:27+00:00
sout: hls: reject unsupported codecs

- - - - -
1841a559 by Alaric Senat at 2023-08-25T08:07:27+00:00
sout: hls: enable input pacing

- - - - -
c4ea8af5 by Alaric Senat at 2023-08-25T08:07:27+00:00
sout: hls: store mime type in storage

This is needed for HTTP sharing segments and manifest correctly.

- - - - -
2ae99f64 by Alaric Senat at 2023-08-25T08:07:27+00:00
sout: hls: share manifests via HTTP

- - - - -
eaedac2d by Alaric Senat at 2023-08-25T08:07:27+00:00
sout: hls: share segments via HTTP

- - - - -
2ed79a2f by Alaric Senat at 2023-08-25T08:07:27+00:00
sout: hls: add playlist related logging

- - - - -
c9bd2581 by Alaric Senat at 2023-08-25T08:07:27+00:00
sout: hls: set maximum memory for segment storage

- - - - -
c67dec41 by Alaric Senat at 2023-08-25T08:07:27+00:00
sout: hls: forbid memory segments when HTTP isn't hosted

- - - - -


13 changed files:

- modules/codec/hxxx_helper.c
- modules/codec/hxxx_helper.h
- modules/stream_out/Makefile.am
- + modules/stream_out/hls/codecs.c
- + modules/stream_out/hls/codecs.h
- + modules/stream_out/hls/hls.c
- + modules/stream_out/hls/hls.h
- + modules/stream_out/hls/segments.c
- + modules/stream_out/hls/segments.h
- + modules/stream_out/hls/storage.c
- + modules/stream_out/hls/storage.h
- + modules/stream_out/hls/variant_maps.c
- + modules/stream_out/hls/variant_maps.h


Changes:

=====================================
modules/codec/hxxx_helper.c
=====================================
@@ -890,6 +890,19 @@ hxxx_helper_get_current_profile_level(const struct hxxx_helper *hh,
     return VLC_EGENERIC;
 }
 
+int h264_helper_get_constraint_flag(const struct hxxx_helper *hh,
+                                    uint8_t *pi_constraints) {
+    
+    assert(hh->i_codec == VLC_CODEC_H264);
+    const struct hxxx_helper_nal *hsps = h264_helper_get_current_sps(hh);
+    if (hsps)
+    {
+        *pi_constraints = hsps->h264_sps->i_constraint_set_flags;
+        return VLC_SUCCESS;
+    }
+    return VLC_EGENERIC;
+}
+
 int
 hxxx_helper_get_chroma_chroma(const struct hxxx_helper *hh, uint8_t *pi_chroma_format,
                               uint8_t *pi_depth_luma, uint8_t *pi_depth_chroma)


=====================================
modules/codec/hxxx_helper.h
=====================================
@@ -122,6 +122,8 @@ int h264_helper_get_current_dpb_values(const struct hxxx_helper *hh,
 
 int hxxx_helper_get_current_profile_level(const struct hxxx_helper *hh,
                                           uint8_t *p_profile, uint8_t *p_level);
+int h264_helper_get_constraint_flag(const struct hxxx_helper *hh,
+                                    uint8_t *pi_constraints);
 
 int
 hxxx_helper_get_chroma_chroma(const struct hxxx_helper *hh, uint8_t *pi_chroma_format,


=====================================
modules/stream_out/Makefile.am
=====================================
@@ -35,6 +35,14 @@ libstream_out_udp_plugin_la_SOURCES = \
 	stream_out/udp.c
 libstream_out_udp_plugin_la_LIBADD = $(SOCKET_LIBS)
 
+libstream_out_hls_plugin_la_SOURCES = \
+	stream_out/hls/hls.h stream_out/hls/hls.c \
+	stream_out/hls/variant_maps.h stream_out/hls/variant_maps.c \
+	stream_out/hls/storage.h stream_out/hls/storage.c \
+	stream_out/hls/segments.h stream_out/hls/segments.c \
+	stream_out/hls/codecs.h stream_out/hls/codecs.c
+libstream_out_hls_plugin_la_LIBADD = libvlc_hxxxhelper.la
+
 sout_LTLIBRARIES = \
 	libstream_out_dummy_plugin.la \
 	libstream_out_cycle_plugin.la \
@@ -53,6 +61,7 @@ sout_LTLIBRARIES = \
 	libstream_out_setid_plugin.la \
 	libstream_out_trace_plugin.la \
 	libstream_out_transcode_plugin.la \
+	libstream_out_hls_plugin.la \
 	libstream_out_udp_plugin.la
 
 if HAVE_DECKLINK


=====================================
modules/stream_out/hls/codecs.c
=====================================
@@ -0,0 +1,74 @@
+/*****************************************************************************
+ * codecs.c:
+ *****************************************************************************
+ * Copyright (C) 2023 VLC authors and VideoLAN
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <vlc_common.h>
+
+#include <vlc_memstream.h>
+
+#include "../codec/hxxx_helper.h"
+#include "codecs.h"
+
+static int FormatAVC1(struct vlc_memstream *ms, const es_format_t *fmt)
+{
+    /* Parse the h264 constraint flag. */
+    uint8_t constraints = 0;
+    uint8_t profile = 0;
+    uint8_t level = 0;
+    struct hxxx_helper hh;
+    hxxx_helper_init(&hh, NULL, fmt->i_codec, 0, 0);
+    if (hxxx_helper_set_extra(&hh, fmt->p_extra, fmt->i_extra) == VLC_SUCCESS)
+    {
+        h264_helper_get_constraint_flag(&hh, &constraints);
+        hxxx_helper_get_current_profile_level(&hh, &profile, &level);
+    }
+    hxxx_helper_clean(&hh);
+
+    const int wrote = vlc_memstream_printf(
+        ms, "avc1.%02X%02X%02X", profile, constraints, level);
+    return (wrote == -1) ? VLC_ENOMEM : VLC_SUCCESS;
+}
+
+static int FormatMP4A(struct vlc_memstream *ms, const es_format_t *fmt)
+{
+    const int wrote = vlc_memstream_printf(
+        ms, "mp4a.40.%02x", (fmt->i_profile == -1) ? 2u : fmt->i_profile + 1u);
+    return (wrote == -1) ? VLC_ENOMEM : VLC_SUCCESS;
+}
+
+int hls_codec_Format(struct vlc_memstream *ms, const es_format_t *fmt)
+{
+    switch (fmt->i_codec)
+    {
+        case VLC_CODEC_H264:
+            return FormatAVC1(ms, fmt);
+        case VLC_CODEC_MP4A:
+            return FormatMP4A(ms, fmt);
+        default:
+            return VLC_ENOTSUP;
+    }
+}
+
+bool hls_codec_IsSupported(const es_format_t *fmt)
+{
+    return fmt->i_codec == VLC_CODEC_H264 || fmt->i_codec == VLC_CODEC_MP4A;
+}


=====================================
modules/stream_out/hls/codecs.h
=====================================
@@ -0,0 +1,27 @@
+/*****************************************************************************
+ * codecs.h
+ *****************************************************************************
+ * Copyright (C) 2023 VLC authors and VideoLAN
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef HLS_CODECS_H
+#define HLS_CODECS_H
+
+int hls_codec_Format(struct vlc_memstream *, const es_format_t *);
+bool hls_codec_IsSupported(const es_format_t *);
+
+#endif
+


=====================================
modules/stream_out/hls/hls.c
=====================================
@@ -0,0 +1,1051 @@
+/*****************************************************************************
+ * hls.c: HLS stream output module
+ *****************************************************************************
+ * Copyright (C) 2023 VLC authors and VideoLAN
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <vlc_common.h>
+
+#include <vlc_block.h>
+#include <vlc_configuration.h>
+#include <vlc_frame.h>
+#include <vlc_httpd.h>
+#include <vlc_iso_lang.h>
+#include <vlc_list.h>
+#include <vlc_memstream.h>
+#include <vlc_messages.h>
+#include <vlc_plugin.h>
+#include <vlc_sout.h>
+#include <vlc_tick.h>
+#include <vlc_vector.h>
+
+#include "codecs.h"
+#include "hls.h"
+#include "segments.h"
+#include "storage.h"
+#include "variant_maps.h"
+
+typedef struct
+{
+    block_t *begin;
+    block_t **end;
+    vlc_tick_t length;
+} hls_block_chain_t;
+
+static inline void hls_block_chain_Reset(hls_block_chain_t *chain)
+{
+    chain->begin = NULL;
+    chain->end = &chain->begin;
+    chain->length = 0;
+}
+
+/**
+ * Represent one HLS playlist as in RFC 8216 section 4.
+ */
+typedef struct hls_playlist
+{
+    unsigned int id;
+
+    const struct hls_config *config;
+    size_t *current_memory_cached_ref;
+
+    sout_access_out_t *access;
+    sout_mux_t *mux;
+    /** Every ES muxed in this playlist. */
+    struct vlc_list tracks;
+
+    hls_block_chain_t muxed_output;
+
+    /**
+     * Completed segments queue.
+     *
+     * The queue is generally max-sized (configurable by the user). Which means
+     * that, when the max size is reached, pushing in the queue erase the first
+     * segment.
+     */
+    hls_segment_queue_t segments;
+
+    char *url;
+    const char *name;
+    struct vlc_logger *logger;
+
+    /**
+     * Current playlist manifest as in RFC 8216 section 4.3.3.
+     */
+    struct hls_storage *manifest;
+    httpd_url_t *http_manifest;
+
+    bool ended;
+
+    struct vlc_list node;
+} hls_playlist_t;
+
+/**
+ * Represent one ES.
+ *
+ * Returned from `pf_add` to have both the sout_input context and the owning
+ * playlist reference.
+ */
+typedef struct
+{
+    sout_input_t *input;
+    const char *es_id;
+    hls_playlist_t *playlist_ref;
+    struct vlc_list node;
+} hls_track_t;
+
+typedef struct
+{
+    /** All the plugin constants. */
+    struct hls_config config;
+
+    hls_variant_stream_maps_t variant_stream_maps;
+
+    httpd_host_t *http_host;
+
+    /**
+     * All the created variant streams "EXT-X-STREAM-INF" (As in RFC 8216
+     * section 4.3.4.2) playlists.
+     */
+    struct vlc_list variant_playlists;
+    /**
+     * All the created alternative renditions "EXT-X-MEDIA" (As in RFC 8216
+     * section 4.3.4.1) playlists.
+     */
+    struct vlc_list media_playlists;
+
+    /**
+     * Total number of playlist created by the plugin.
+     *
+     * Notably used to create unique playlists IDs.
+     */
+    unsigned int playlist_created_count;
+
+    /**
+     * Current "Master" Playlist manifest (As in RFC 8216 4.3.4).
+     */
+    struct hls_storage *manifest;
+    httpd_url_t *http_manifest;
+
+    vlc_tick_t first_pcr;
+    vlc_tick_t last_pcr;
+    vlc_tick_t last_segment;
+
+    size_t current_memory_cached;
+} sout_stream_sys_t;
+
+#define hls_playlists_foreach(it)                                              \
+    for (size_t i_##it = 0; i_##it < 2; ++i_##it)                              \
+        vlc_list_foreach (                                                     \
+            it,                                                                \
+            (i_##it == 0 ? &sys->variant_playlists : &sys->media_playlists),   \
+            node)
+
+static int HTTPCallback(httpd_callback_sys_t *sys,
+                        httpd_client_t *client,
+                        httpd_message_t *answer,
+                        const httpd_message_t *query)
+{
+    if (answer == NULL || query == NULL || client == NULL)
+        return VLC_SUCCESS;
+
+    struct hls_storage *storage = (struct hls_storage *)sys;
+
+    httpd_MsgAdd(answer, "Content-Type", "%s", storage->mime);
+    httpd_MsgAdd(answer, "Cache-Control", "no-cache");
+
+    answer->i_proto = HTTPD_PROTO_HTTP;
+    answer->i_version = 0;
+    answer->i_type = HTTPD_MSG_ANSWER;
+
+    const ssize_t size = storage->get_content(storage, &answer->p_body);
+    if (size != -1)
+    {
+        answer->i_body = size;
+        answer->i_status = 200;
+    }
+    else
+        answer->i_status = 500;
+
+    if (httpd_MsgGet(query, "Connection") != NULL)
+        httpd_MsgAdd(answer, "Connection", "close");
+    httpd_MsgAdd(answer, "Content-Length", "%zu", answer->i_body);
+
+    return VLC_SUCCESS;
+}
+
+typedef struct VLC_VECTOR(const es_format_t *) es_format_vec_t;
+
+static inline bool IsCodecAlreadyDescribed(const es_format_vec_t *vec,
+                                           const es_format_t *fmt)
+{
+    const es_format_t *it;
+    vlc_vector_foreach (it, vec)
+    {
+        if (es_format_IsSimilar(it, (fmt)))
+            return true;
+    }
+    return false;
+}
+
+static inline hls_track_t *MediaGetTrack(const hls_playlist_t *media_playlist)
+{
+    hls_track_t *track = vlc_list_first_entry_or_null(
+        &media_playlist->tracks, hls_track_t, node);
+    assert(track != NULL);
+    return track;
+}
+
+static char *GeneratePlaylistCodecInfo(const struct vlc_list *media_list,
+                                       const hls_playlist_t *playlist)
+{
+    es_format_vec_t already_described = VLC_VECTOR_INITIALIZER;
+
+    bool is_stream_empty = true;
+    struct vlc_memstream out;
+    vlc_memstream_open(&out);
+
+    /* Describe codecs from the playlist. */
+    const hls_track_t *track;
+    vlc_list_foreach (track, &playlist->tracks, node)
+    {
+        if (IsCodecAlreadyDescribed(&already_described, &track->input->fmt))
+            continue;
+
+        if (!is_stream_empty)
+            vlc_memstream_putc(&out, ',');
+        if (hls_codec_Format(&out, &track->input->fmt) != VLC_SUCCESS)
+            goto error;
+        is_stream_empty = false;
+        vlc_vector_push(&already_described, &track->input->fmt);
+    }
+
+    /* Describe codecs from all the EXT-X-MEDIA tracks. */
+    hls_playlist_t *media;
+    vlc_list_foreach (media, media_list, node)
+    {
+        track = MediaGetTrack(media);
+
+        if (IsCodecAlreadyDescribed(&already_described, &track->input->fmt))
+            continue;
+
+        if (!is_stream_empty)
+            vlc_memstream_putc(&out, ',');
+        if (hls_codec_Format(&out, &track->input->fmt) != VLC_SUCCESS)
+            goto error;
+        is_stream_empty = false;
+        vlc_vector_push(&already_described, &track->input->fmt);
+    }
+
+    vlc_vector_destroy(&already_described);
+
+    vlc_memstream_putc(&out, '\0');
+    if (vlc_memstream_close(&out) != 0)
+        return NULL;
+    return out.ptr;
+error:
+    if (vlc_memstream_close(&out) != 0)
+        return NULL;
+    free(out.ptr);
+    vlc_vector_destroy(&already_described);
+    return NULL;
+}
+
+static struct hls_storage *GenerateMainManifest(const sout_stream_sys_t *sys)
+{
+    struct vlc_memstream out;
+    vlc_memstream_open(&out);
+
+#define MANIFEST_START_TAG(tag)                                                \
+    do                                                                         \
+    {                                                                          \
+        bool first_attribute = true;                                           \
+        vlc_memstream_puts(&out, tag);
+
+#define MANIFEST_ADD_ATTRIBUTE(attribute, ...)                                 \
+    do                                                                         \
+    {                                                                          \
+        if (vlc_memstream_printf(&out,                                         \
+                                 "%s" attribute,                               \
+                                 first_attribute ? ":" : ",",                  \
+                                 ##__VA_ARGS__) < 0)                           \
+            goto error;                                                        \
+        first_attribute = false;                                               \
+    } while (0)
+
+#define MANIFEST_END_TAG                                                       \
+    vlc_memstream_putc(&out, '\n');                                            \
+    }                                                                          \
+    while (0)                                                                  \
+        ;
+
+    vlc_memstream_puts(&out, "#EXTM3U\n");
+
+    static const char *const TRACK_TYPES[] = {
+        [VIDEO_ES] = "VIDEO",
+        [AUDIO_ES] = "AUDIO",
+    };
+    static const char *const GROUP_IDS[] = {
+        [VIDEO_ES] = "video",
+        [AUDIO_ES] = "audio",
+    };
+
+    const hls_playlist_t *playlist;
+    vlc_list_foreach (playlist, &sys->media_playlists, node)
+    {
+        const hls_track_t *track = MediaGetTrack(playlist);
+        const es_format_t *fmt = &track->input->fmt;
+        assert(fmt->i_cat == VIDEO_ES || fmt->i_cat == AUDIO_ES);
+
+        MANIFEST_START_TAG("#EXT-X-MEDIA")
+            const char *track_type = TRACK_TYPES[fmt->i_cat];
+            MANIFEST_ADD_ATTRIBUTE("TYPE=%s", track_type);
+
+            const char *group_id = GROUP_IDS[fmt->i_cat];
+            MANIFEST_ADD_ATTRIBUTE("GROUP-ID=\"%s\"", group_id);
+
+            const iso639_lang_t *lang =
+                (fmt->psz_language != NULL)
+                    ? vlc_find_iso639(fmt->psz_language, false)
+                    : NULL;
+
+            if (lang != NULL)
+            {
+                MANIFEST_ADD_ATTRIBUTE("NAME=\"%s\"", lang->psz_eng_name);
+                MANIFEST_ADD_ATTRIBUTE("LANGUAGE=\"%3.3s\"",
+                                       lang->psz_iso639_2T);
+            }
+            else
+            {
+                MANIFEST_ADD_ATTRIBUTE("NAME=\"%s\"", track->es_id);
+            }
+
+            MANIFEST_ADD_ATTRIBUTE("URI=\"%s\"", playlist->url);
+        MANIFEST_END_TAG
+    }
+
+    /* Format EXT-X-STREAM-INF */
+    vlc_list_foreach (playlist, &sys->variant_playlists, node)
+    {
+        MANIFEST_START_TAG("#EXT-X-STREAM-INF")
+            unsigned int bandwidth = 0;
+            const hls_track_t *track;
+            vlc_list_foreach (track, &playlist->tracks, node)
+                bandwidth += track->input->fmt.i_bitrate;
+            MANIFEST_ADD_ATTRIBUTE("BANDWIDTH=%u", bandwidth);
+
+            char *codecs =
+                GeneratePlaylistCodecInfo(&sys->media_playlists, playlist);
+            if (unlikely(codecs == NULL))
+                goto error;
+            MANIFEST_ADD_ATTRIBUTE("CODECS=\"%s\"", codecs);
+            free(codecs);
+
+            MANIFEST_ADD_ATTRIBUTE("VIDEO=\"%s\"", GROUP_IDS[VIDEO_ES]);
+            MANIFEST_ADD_ATTRIBUTE("AUDIO=\"%s\"", GROUP_IDS[AUDIO_ES]);
+        MANIFEST_END_TAG
+
+        if (vlc_memstream_printf(&out, "%s\n", playlist->url) < 0)
+            goto error;
+    }
+
+#undef MANIFEST_START_TAG
+#undef MANIFEST_ADD_ATTRIBUTE
+#undef MANIFEST_END_TAG
+
+    if (vlc_memstream_close(&out) != 0)
+        return NULL;
+
+    const struct hls_storage_config storage_conf = {
+        .name = "index.m3u8",
+        .mime = "application/vnd.apple.mpegurl",
+    };
+    return hls_storage_FromBytes(
+        out.ptr, out.length, &storage_conf, &sys->config);
+error:
+    if (vlc_memstream_close(&out) != 0)
+        return NULL;
+    free(out.ptr);
+    return NULL;
+}
+
+static struct hls_storage *
+GeneratePlaylistManifest(const hls_playlist_t *playlist)
+{
+    struct vlc_memstream out;
+    vlc_memstream_open(&out);
+
+#define MANIFEST_ADD_TAG(fmt, ...)                                             \
+    do                                                                         \
+    {                                                                          \
+        if (vlc_memstream_printf(&out, fmt "\n", ##__VA_ARGS__) < 0)           \
+            goto error;                                                        \
+    } while (0)
+
+    MANIFEST_ADD_TAG("#EXTM3U");
+    const double seg_duration =
+        secf_from_vlc_tick(playlist->config->segment_length);
+    MANIFEST_ADD_TAG("#EXT-X-TARGETDURATION:%.0f", seg_duration);
+    // First version adding CMAF fragments support.
+    MANIFEST_ADD_TAG("#EXT-X-VERSION:7");
+
+    const bool will_destroy_segments = playlist->config->max_segments == 0;
+    if (playlist->ended)
+        MANIFEST_ADD_TAG("#EXT-X-PLAYLIST-TYPE:VOD");
+    else if (!will_destroy_segments)
+        MANIFEST_ADD_TAG("#EXT-X-PLAYLIST-TYPE:EVENT");
+
+    const hls_segment_t *first_seg = hls_segment_GetFirst(&playlist->segments);
+    MANIFEST_ADD_TAG("#EXT-X-MEDIA-SEQUENCE:%u",
+                     (first_seg == NULL) ? 0u : first_seg->id);
+
+    const hls_segment_t *segment;
+    hls_segment_queue_Foreach(&playlist->segments, segment)
+    {
+        MANIFEST_ADD_TAG("#EXTINF:%.2f,", secf_from_vlc_tick(segment->length));
+        MANIFEST_ADD_TAG("%s", segment->url);
+    }
+
+    if (playlist->ended)
+        MANIFEST_ADD_TAG("#EXT-X-ENDLIST");
+
+#undef MANIFEST_ADD_TAG
+
+    if (vlc_memstream_close(&out) != 0)
+        return NULL;
+
+    const struct hls_storage_config storage_config = {
+        .name = playlist->name, .mime = "application/vnd.apple.mpegurl"};
+    return hls_storage_FromBytes(
+        out.ptr, out.length, &storage_config, playlist->config);
+error:
+    if (vlc_memstream_close(&out) != 0)
+        return NULL;
+    free(out.ptr);
+    return NULL;
+}
+
+static int UpdatePlaylistManifest(hls_playlist_t *playlist)
+{
+    struct hls_storage *new_manifest = GeneratePlaylistManifest(playlist);
+    if (unlikely(new_manifest == NULL))
+        return VLC_EGENERIC;
+
+    if (playlist->http_manifest != NULL)
+    {
+        httpd_UrlCatch(playlist->http_manifest,
+                       HTTPD_MSG_GET,
+                       HTTPCallback,
+                       (httpd_callback_sys_t *)new_manifest);
+    }
+
+    if (playlist->manifest != NULL)
+        hls_storage_Destroy(playlist->manifest);
+    playlist->manifest = new_manifest;
+    return VLC_SUCCESS;
+}
+
+static ssize_t AccessOutWrite(sout_access_out_t *access, block_t *block)
+{
+    hls_playlist_t *playlist = access->p_sys;
+
+    size_t size = 0;
+    block_ChainProperties(block, NULL, &size, NULL);
+
+    if (hls_config_IsMemStorageEnabled(playlist->config))
+    {
+        *playlist->current_memory_cached_ref += size;
+        if (*playlist->current_memory_cached_ref >=
+            playlist->config->max_memory)
+        {
+            vlc_error(playlist->logger,
+                      "Maximum memory capacity (%luKb) for segment storage was "
+                      "reached. The HLS server will stop creating segments. "
+                      "Please refer to the max-memory option for more info.",
+                      BYTES_TO_KB(playlist->config->max_memory));
+            block_ChainRelease(block);
+            return -1;
+        }
+    }
+
+    block_ChainLastAppend(&playlist->muxed_output.end, block);
+    return size;
+}
+
+static sout_access_out_t *CreateAccessOut(sout_stream_t *stream,
+                                          hls_playlist_t *sys)
+{
+    sout_access_out_t *access = vlc_object_create(stream, sizeof(*access));
+    if (unlikely(access == NULL))
+        return NULL;
+
+    access->psz_access = strdup("hls");
+    if (unlikely(access->psz_access == NULL))
+    {
+        vlc_object_delete(access);
+        return NULL;
+    }
+
+    access->p_cfg = NULL;
+    access->p_module = NULL;
+    access->p_sys = sys;
+    access->psz_path = NULL;
+
+    access->pf_control = NULL;
+    access->pf_read = NULL;
+    access->pf_seek = NULL;
+    access->pf_write = AccessOutWrite;
+    return access;
+}
+
+static inline char *FormatPlaylistManifestURL(const hls_playlist_t *playlist)
+{
+    char *url;
+    const int status = asprintf(&url,
+                                "%s/playlist-%u-index.m3u8",
+                                playlist->config->base_url,
+                                playlist->id);
+    if (unlikely(status == -1))
+        return NULL;
+    return url;
+}
+
+static hls_playlist_t *CreatePlaylist(sout_stream_t *stream)
+{
+    sout_stream_sys_t *sys = stream->p_sys;
+
+    hls_playlist_t *playlist = malloc(sizeof(*playlist));
+    if (unlikely(playlist == NULL))
+        return NULL;
+
+    playlist->access = CreateAccessOut(stream, playlist);
+    if (unlikely(playlist->access == NULL))
+        goto access_err;
+
+    playlist->mux = sout_MuxNew(playlist->access, "ts");
+    if (unlikely(playlist->mux == NULL))
+        goto mux_err;
+
+    playlist->id = sys->playlist_created_count;
+    playlist->config = &sys->config;
+    playlist->ended = false;
+    playlist->current_memory_cached_ref = &sys->current_memory_cached;
+
+    playlist->url = FormatPlaylistManifestURL(playlist);
+    if (unlikely(playlist->url == NULL))
+        goto url_err;
+
+    playlist->name = playlist->url + strlen(sys->config.base_url) + 1;
+
+    playlist->logger = vlc_LogHeaderCreate(stream->obj.logger, playlist->name);
+    if (unlikely(playlist->logger == NULL))
+        goto log_err;
+
+    struct hls_segment_queue_config config = {
+        .playlist_id = playlist->id,
+        .httpd_ref = sys->http_host,
+        .httpd_callback = HTTPCallback,
+    };
+    hls_segment_queue_Init(&playlist->segments, &config, &sys->config);
+
+    hls_block_chain_Reset(&playlist->muxed_output);
+
+    playlist->manifest = NULL;
+    if (sys->http_host != NULL)
+    {
+        playlist->http_manifest =
+            httpd_UrlNew(sys->http_host, playlist->url, NULL, NULL);
+        if (playlist->http_manifest == NULL)
+            goto manifest_err;
+    }
+    else
+        playlist->http_manifest = NULL;
+
+    if (UpdatePlaylistManifest(playlist) != VLC_SUCCESS)
+        goto error;
+
+    vlc_list_init(&playlist->tracks);
+
+    vlc_info(playlist->logger, "Playlist created");
+
+    return playlist;
+error:
+    if (playlist->http_manifest != NULL)
+        httpd_UrlDelete(playlist->http_manifest);
+manifest_err:
+    hls_segment_queue_Clear(&playlist->segments);
+    vlc_LogDestroy(playlist->logger);
+log_err:
+    free(playlist->url);
+url_err:
+    sout_MuxDelete(playlist->mux);
+mux_err:
+    sout_AccessOutDelete(playlist->access);
+access_err:
+    free(playlist);
+    return NULL;
+}
+
+static void DeletePlaylist(hls_playlist_t *playlist)
+{
+    sout_MuxDelete(playlist->mux);
+
+    sout_AccessOutDelete(playlist->access);
+
+    if (playlist->http_manifest != NULL)
+        httpd_UrlDelete(playlist->http_manifest);
+
+    if (playlist->manifest != NULL)
+        hls_storage_Destroy(playlist->manifest);
+
+    block_ChainRelease(playlist->muxed_output.begin);
+    hls_segment_queue_Clear(&playlist->segments);
+
+    vlc_list_remove(&playlist->node);
+
+    vlc_LogDestroy(playlist->logger);
+    free(playlist->url);
+
+    free(playlist);
+}
+
+static hls_playlist_t *AddPlaylist(sout_stream_t *stream, struct vlc_list *head)
+{
+    hls_playlist_t *variant = CreatePlaylist(stream);
+    if (variant != NULL)
+        vlc_list_append(&variant->node, head);
+    return variant;
+}
+
+static void *
+Add(sout_stream_t *stream, const es_format_t *fmt, const char *es_id)
+{
+    if (!hls_codec_IsSupported(fmt))
+        return NULL;
+
+    sout_stream_sys_t *sys = stream->p_sys;
+
+    // Either retrieve the already created playlist from the map or create it.
+    struct hls_variant_stream_map *map =
+        hls_variant_map_FromESID(&sys->variant_stream_maps, es_id);
+    hls_playlist_t *playlist;
+    if (map != NULL)
+    {
+        if (map->playlist_ref == NULL)
+            map->playlist_ref = AddPlaylist(stream, &sys->variant_playlists);
+        playlist = map->playlist_ref;
+    }
+    else
+        playlist = AddPlaylist(stream, &sys->media_playlists);
+
+    if (playlist == NULL)
+        return NULL;
+
+    ++sys->playlist_created_count;
+
+    sout_input_t *input = sout_MuxAddStream(playlist->mux, fmt);
+    if (input == NULL)
+        goto error;
+
+    hls_track_t *track = malloc(sizeof(*track));
+    if (unlikely(track == NULL))
+        goto error;
+
+    track->input = input;
+    track->es_id = es_id;
+    track->playlist_ref = playlist;
+
+    vlc_list_append(&track->node, &playlist->tracks);
+
+    struct hls_storage *new_manifest = GenerateMainManifest(sys);
+    if (unlikely(new_manifest == NULL))
+    {
+        vlc_list_remove(&track->node);
+        free(track);
+        goto error;
+    }
+
+    if (sys->http_host != NULL)
+    {
+        httpd_UrlCatch(sys->http_manifest,
+                       HTTPD_MSG_GET,
+                       HTTPCallback,
+                       (httpd_callback_sys_t *)new_manifest);
+    }
+
+    if (sys->manifest != NULL)
+        hls_storage_Destroy(sys->manifest);
+    sys->manifest = new_manifest;
+
+    if (map != NULL && map->playlist_ref == NULL)
+        map->playlist_ref = playlist;
+
+    return track;
+error:
+    if (input != NULL)
+        sout_MuxDeleteStream(playlist->mux, input);
+    if (vlc_list_is_empty(&playlist->tracks))
+        DeletePlaylist(playlist);
+    return NULL;
+}
+
+static hls_block_chain_t ExtractSegment(hls_playlist_t *playlist,
+                                        vlc_tick_t max_segment_length)
+{
+    hls_block_chain_t segment = {.begin = playlist->muxed_output.begin};
+
+    block_t *prev = NULL;
+    for (block_t *it = playlist->muxed_output.begin; it != NULL;
+         it = it->p_next)
+    {
+        if (segment.length + it->i_length > max_segment_length)
+        {
+            playlist->muxed_output.begin = it;
+
+            if (prev != NULL)
+                prev->p_next = NULL;
+            return segment;
+        }
+        segment.length += it->i_length;
+        prev = it;
+    }
+
+    hls_block_chain_Reset(&playlist->muxed_output);
+    return segment;
+}
+
+static void ExtractAndAddSegment(hls_playlist_t *playlist,
+                                 vlc_tick_t last_segment_time)
+{
+    hls_block_chain_t segment = ExtractSegment(playlist, last_segment_time);
+
+    if (hls_config_IsMemStorageEnabled(playlist->config) &&
+        hls_segment_queue_IsAtMaxCapacity(&playlist->segments))
+    {
+        const hls_segment_t *to_be_removed =
+            hls_segment_GetFirst(&playlist->segments);
+        *playlist->current_memory_cached_ref -=
+            hls_storage_GetSize(to_be_removed->storage);
+    }
+
+    const int status = hls_segment_queue_NewSegment(
+        &playlist->segments, segment.begin, segment.length);
+    if (unlikely(status != VLC_SUCCESS))
+    {
+        vlc_error(playlist->logger,
+                  "Segment '%u' creation failed",
+                  playlist->segments.total_segments + 1);
+        return;
+    }
+
+    vlc_debug(playlist->logger,
+              "Segment '%u' created",
+              playlist->segments.total_segments);
+
+    UpdatePlaylistManifest(playlist);
+}
+static void Del(sout_stream_t *stream, void *id)
+{
+    sout_stream_sys_t *sys = stream->p_sys;
+    hls_track_t *track = id;
+
+    sout_MuxDeleteStream(track->playlist_ref->mux, track->input);
+    vlc_list_remove(&track->node);
+
+    if (vlc_list_is_empty(&track->playlist_ref->tracks))
+    {
+        struct hls_variant_stream_map *map = hls_variant_map_FromPlaylist(
+            &sys->variant_stream_maps, track->playlist_ref);
+        if (map != NULL)
+            map->playlist_ref = NULL;
+
+        hls_playlist_t *playlist;
+        hls_playlists_foreach (playlist)
+            ExtractAndAddSegment(playlist, sys->config.segment_length);
+
+        playlist->ended = true;
+        UpdatePlaylistManifest(playlist);
+
+        DeletePlaylist(track->playlist_ref);
+    }
+
+    free(track);
+}
+
+static int Send(sout_stream_t *stream, void *id, vlc_frame_t *frame)
+{
+    hls_track_t *track = id;
+    return sout_MuxSendBuffer(track->playlist_ref->mux, track->input, frame);
+    (void)stream;
+}
+
+/**
+ * PCR events are used to have a reliable stream time status. Segmenting is done
+ * after a PCR testifying that we are above the segment limit arrives.
+ */
+static void SetPCR(sout_stream_t *stream, vlc_tick_t pcr)
+{
+    sout_stream_sys_t *sys = stream->p_sys;
+
+    const vlc_tick_t last_pcr = sys->last_pcr;
+    sys->last_pcr = pcr;
+
+    if (sys->first_pcr == VLC_TICK_INVALID)
+    {
+        sys->first_pcr = pcr;
+        return;
+    }
+
+    const vlc_tick_t stream_time = pcr - sys->first_pcr;
+    const vlc_tick_t current_seglen = stream_time - sys->last_segment;
+
+    const vlc_tick_t pcr_gap = pcr - last_pcr;
+    /* PCR and segment length aren't necessarily aligned. Testing segment length
+     * with a **next** PCR  approximation will avoid piling up data:
+     *
+     * |------x#|-----x##|----x###| time
+     * ^ PCR  ^ Segment end     ^ Buffer expanding
+     *
+     * The segments are then a little shorter than they could be.
+     */
+    if (current_seglen + pcr_gap >= sys->config.segment_length)
+    {
+        hls_playlist_t *playlist;
+        hls_playlists_foreach (playlist)
+            ExtractAndAddSegment(playlist, sys->config.segment_length);
+        sys->last_segment = stream_time;
+    }
+}
+
+static int Control(sout_stream_t *stream, int query, va_list args)
+{
+    const sout_stream_sys_t *sys = stream->p_sys;
+    switch (query)
+    {
+        case SOUT_STREAM_IS_SYNCHRONOUS:
+            *va_arg(args, bool *) = sys->config.pace;
+            break;
+
+        default:
+            return VLC_EGENERIC;
+    }
+
+    return VLC_SUCCESS;
+}
+
+static int InitHTTP(sout_stream_t *stream)
+{
+    sout_stream_sys_t *sys = stream->p_sys;
+    sys->http_host = vlc_http_HostNew(VLC_OBJECT(stream));
+    if (sys->http_host == NULL)
+        return VLC_EGENERIC;
+
+    char *mainfest_url;
+    if (asprintf(&mainfest_url, "%s/stream.m3u8", sys->config.base_url) == -1)
+        goto error;
+
+    sys->http_manifest = httpd_UrlNew(sys->http_host, mainfest_url, NULL, NULL);
+    free(mainfest_url);
+    if (sys->http_manifest == NULL)
+        goto error;
+    return VLC_SUCCESS;
+error:
+    httpd_HostDelete(sys->http_host);
+    return VLC_EGENERIC;
+}
+
+#define SOUT_CFG_PREFIX "sout-hls-"
+
+static int Open(vlc_object_t *this)
+{
+    sout_stream_t *stream = (sout_stream_t *)this;
+
+    sout_stream_sys_t *sys = malloc(sizeof(*sys));
+    if (unlikely(sys == NULL))
+        return VLC_ENOMEM;
+    stream->p_sys = sys;
+
+    static const char *const options[] = {"base-url",
+                                          "host-http",
+                                          "max-memory",
+                                          "num-seg",
+                                          "out-dir",
+                                          "pace",
+                                          "seg-len",
+                                          "variants",
+                                          NULL};
+    config_ChainParse(stream, SOUT_CFG_PREFIX, options, stream->p_cfg);
+
+    sys->config.base_url = var_GetString(stream, SOUT_CFG_PREFIX "base-url");
+    sys->config.outdir =
+        var_GetNonEmptyString(stream, SOUT_CFG_PREFIX "out-dir");
+    sys->config.max_segments =
+        var_GetInteger(stream, SOUT_CFG_PREFIX "num-seg");
+    sys->config.pace = var_GetBool(stream, SOUT_CFG_PREFIX "pace");
+    sys->config.segment_length =
+        VLC_TICK_FROM_SEC(var_GetInteger(stream, SOUT_CFG_PREFIX "seg-len"));
+    sys->config.max_memory =
+        BYTES_FROM_KB(var_GetInteger(stream, SOUT_CFG_PREFIX "max-memory"));
+
+    int status = VLC_EINVAL;
+
+    vlc_vector_init(&sys->variant_stream_maps);
+    char *variants = var_GetNonEmptyString(stream, SOUT_CFG_PREFIX "variants");
+    if (variants == NULL)
+    {
+        msg_Err(stream,
+                "At least one variant mapping needs to be specified with the "
+                "\"" SOUT_CFG_PREFIX "variants\" option");
+        goto error;
+    }
+    status = hls_variant_maps_Parse(variants, &sys->variant_stream_maps);
+    free(variants);
+    if (status != VLC_SUCCESS)
+    {
+        if (status == VLC_EINVAL)
+            msg_Err(stream,
+                    "Wrong variant mapping syntax. It should look like: "
+                    "\"{id1,id2},{id3,id4},...\"");
+        goto error;
+    }
+
+    if (var_GetBool(stream, SOUT_CFG_PREFIX "host-http"))
+    {
+        status = InitHTTP(stream);
+        if (status != VLC_SUCCESS)
+            goto error;
+    }
+    else if (sys->config.outdir != NULL)
+    {
+        sys->http_host = NULL;
+        sys->http_manifest = NULL;
+    }
+    else
+    {
+        msg_Err(stream,
+                "No output directory specified."
+                " See \"" SOUT_CFG_PREFIX "out-dir\"");
+        status = VLC_EINVAL;
+        goto error;
+    }
+
+    sys->manifest = NULL;
+
+    sys->playlist_created_count = 0;
+
+    vlc_list_init(&sys->variant_playlists);
+    vlc_list_init(&sys->media_playlists);
+
+    sys->first_pcr = VLC_TICK_INVALID;
+    sys->last_pcr = VLC_TICK_INVALID;
+    sys->last_segment = 0;
+
+    sys->current_memory_cached = 0;
+
+    static const struct sout_stream_operations ops = {
+        .add = Add,
+        .del = Del,
+        .send = Send,
+        .set_pcr = SetPCR,
+        .control = Control,
+    };
+    stream->ops = &ops;
+
+    return VLC_SUCCESS;
+error:
+    hls_variant_maps_Destroy(&sys->variant_stream_maps);
+    hls_config_Clean(&sys->config);
+    free(sys);
+    return status;
+}
+
+static void Close(vlc_object_t *this)
+{
+    sout_stream_t *stream = (sout_stream_t *)this;
+    sout_stream_sys_t *sys = stream->p_sys;
+
+    if (sys->http_host != NULL)
+    {
+        httpd_UrlDelete(sys->http_manifest);
+        httpd_HostDelete(sys->http_host);
+    }
+
+    if (sys->manifest != NULL)
+        hls_storage_Destroy(sys->manifest);
+
+    hls_config_Clean(&sys->config);
+
+    hls_variant_maps_Destroy(&sys->variant_stream_maps);
+
+    free(sys);
+}
+
+#define VARIANTS_LONGTEXT                                                      \
+    N_("String map ES string IDs into variant streams. The syntax is the "     \
+       "following: \"{video/1,audio/2},{video/3,audio/4}\". This example "     \
+       "describes two variant streams that contains different audio and "      \
+       "video based on their string ES ID. ES that aren't described in the "   \
+       "variant stream map will be automatically treated as alternative "      \
+       "renditions")
+#define VARIANTS_TEXT                                                          \
+    N_("Map that group ES string IDs into variant streams (mandatory)")
+#define BASEURL_TEXT N_("Base of the URL")
+#define HOSTHTTP_LONGTEXT                                                      \
+    N_("The internal HTTP server will share the HLS output. This is "          \
+       "unadvised for the common use case where an external HTTP server "      \
+       "implementation will be way more efficient. This can be useful for "    \
+       "quick testing on networks with a small load")
+#define HOSTHTTP_TEXT                                                          \
+    N_("Enable hosting the HLS output on the internal HTTP server")
+#define MAXMEMORY_LONGTEXT                                                     \
+    N_("Maximum allowed memory for segment storage in Kb. This option is "     \
+       "only relevant when segments are stored in internal memory. If the "    \
+       "value is bypassed, the HLS server will stop with an error")
+#define MAXMEMORY_TEXT N_("Maximum allowed memory for segment storage in Kb")
+#define NUMSEG_TEXT N_("Number of maximum segment exposed")
+#define OUTDIR_TEXT N_("Output directory path")
+#define OUTDIR_LONGTEXT                                                        \
+    N_("Output directory path. If not specified and HTTP is enabled, the "     \
+       "segments will be stored in memory")
+#define PACE_LONGTEXT                                                          \
+    N_("Enable input pacing, the media will play at playback rate")
+#define PACE_TEXT N_("Enable pacing")
+#define SEGLEN_LONGTEXT N_("Length of segments in seconds")
+#define SEGLEN_TEXT N_("Segment length (sec)")
+
+vlc_module_begin()
+    set_shortname("HLS")
+    set_description(N_("HLS stream output"))
+    set_capability("sout output", 50)
+    add_shortcut("hls")
+    set_subcategory(SUBCAT_SOUT_STREAM)
+
+    add_string(SOUT_CFG_PREFIX "variants", NULL, VARIANTS_TEXT, VARIANTS_LONGTEXT)
+
+    add_string(SOUT_CFG_PREFIX "base-url", "", BASEURL_TEXT, BASEURL_TEXT)
+    add_bool(SOUT_CFG_PREFIX "host-http", false, HOSTHTTP_TEXT, HOSTHTTP_LONGTEXT)
+    add_integer(SOUT_CFG_PREFIX "max-memory", 20000, MAXMEMORY_TEXT, MAXMEMORY_LONGTEXT)
+    add_integer(SOUT_CFG_PREFIX "num-seg", 0, NUMSEG_TEXT, NUMSEG_TEXT)
+    add_string(SOUT_CFG_PREFIX "out-dir", NULL, OUTDIR_TEXT, OUTDIR_LONGTEXT)
+    add_bool(SOUT_CFG_PREFIX "pace", false, PACE_TEXT, PACE_LONGTEXT)
+    add_integer(SOUT_CFG_PREFIX "seg-len", 4, SEGLEN_TEXT, SEGLEN_LONGTEXT)
+
+    set_callbacks(Open, Close)
+vlc_module_end()


=====================================
modules/stream_out/hls/hls.h
=====================================
@@ -0,0 +1,48 @@
+/*****************************************************************************
+ * hls.h
+ *****************************************************************************
+ * Copyright (C) 2023 VLC authors and VideoLAN
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef HLS_H
+#define HLS_H
+
+struct hls_config
+{
+    char *base_url;
+    char *outdir;
+    unsigned int max_segments;
+    bool pace;
+    vlc_tick_t segment_length;
+    size_t max_memory;
+};
+
+#define BYTES_FROM_KB(x) ((x) * 1000)
+#define BYTES_TO_KB(x) ((x) / 1000)
+
+static inline void hls_config_Clean(struct hls_config *config)
+{
+    free(config->base_url);
+    free(config->outdir);
+}
+
+static inline bool
+hls_config_IsMemStorageEnabled(const struct hls_config *config)
+{
+    return config->outdir == NULL;
+}
+
+#endif


=====================================
modules/stream_out/hls/segments.c
=====================================
@@ -0,0 +1,124 @@
+/*****************************************************************************
+ * segments.c
+ *****************************************************************************
+ * Copyright (C) 2023 VLC authors and VideoLAN
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+
+#include <vlc_common.h>
+
+#include <vlc_httpd.h>
+#include <vlc_list.h>
+#include <vlc_tick.h>
+
+#include "hls.h"
+#include "segments.h"
+#include "storage.h"
+
+static void hls_segment_Destroy(hls_segment_t *segment)
+{
+    if (segment->http_url != NULL)
+        httpd_UrlDelete(segment->http_url);
+    hls_storage_Destroy(segment->storage);
+    free(segment->url);
+    free(segment);
+}
+
+void hls_segment_queue_Init(hls_segment_queue_t *queue,
+                            const struct hls_segment_queue_config *config,
+                            const struct hls_config *hls_config)
+{
+    memcpy(&queue->config, config, sizeof(*config));
+    queue->total_segments = 0;
+    queue->hls_config = hls_config;
+
+    vlc_list_init(&queue->segments);
+}
+
+void hls_segment_queue_Clear(hls_segment_queue_t *queue)
+{
+    hls_segment_t *it;
+    hls_segment_queue_Foreach(queue, it) { hls_segment_Destroy(it); }
+}
+
+int hls_segment_queue_NewSegment(hls_segment_queue_t *queue,
+                                 block_t *content,
+                                 vlc_tick_t length)
+{
+    hls_segment_t *segment = malloc(sizeof(*segment));
+    if (unlikely(segment == NULL))
+        return VLC_ENOMEM;
+
+    segment->id = queue->total_segments;
+    segment->length = length;
+
+    if (asprintf(&segment->url,
+                 "%s/playlist-%u-%u.ts",
+                 queue->hls_config->base_url,
+                 queue->config.playlist_id,
+                 segment->id) == -1)
+    {
+        segment->url = NULL;
+        goto nomem;
+    }
+
+    const struct hls_storage_config storage_conf = {
+        .name = segment->url + strlen(queue->hls_config->base_url) + 1,
+        .mime = "video/MP2T",
+    };
+    segment->storage =
+        hls_storage_FromBlocks(content, &storage_conf, queue->hls_config);
+    if (unlikely(segment->storage == NULL))
+        goto nomem;
+
+    if (queue->config.httpd_ref != NULL)
+    {
+        segment->http_url =
+            httpd_UrlNew(queue->config.httpd_ref, segment->url, NULL, NULL);
+        if (segment->http_url == NULL)
+            goto nomem;
+
+        httpd_UrlCatch(segment->http_url,
+                       HTTPD_MSG_GET,
+                       queue->config.httpd_callback,
+                       (httpd_callback_sys_t *)segment->storage);
+    }
+    else
+        segment->http_url = NULL;
+
+    if (hls_segment_queue_IsAtMaxCapacity(queue))
+    {
+        hls_segment_t *old = hls_segment_GetFirst(queue);
+        assert(old != NULL);
+        vlc_list_remove(&old->priv_node);
+        hls_segment_Destroy(old);
+    }
+
+    ++queue->total_segments;
+    vlc_list_append(&segment->priv_node, &queue->segments);
+    return VLC_SUCCESS;
+nomem:
+    if (segment->storage != NULL)
+        hls_storage_Destroy(segment->storage);
+    free(segment->url);
+    free(segment);
+    return VLC_ENOMEM;
+}


=====================================
modules/stream_out/hls/segments.h
=====================================
@@ -0,0 +1,90 @@
+/*****************************************************************************
+ * segments.h
+ *****************************************************************************
+ * Copyright (C) 2023 VLC authors and VideoLAN
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef HLS_SEGMENTS_H
+#define HLS_SEGMENTS_H
+
+struct hls_storage;
+struct hls_config;
+
+typedef struct hls_segment
+{
+    char *url;
+    unsigned int id;
+    vlc_tick_t length;
+
+    struct hls_storage *storage;
+
+    httpd_url_t *http_url;
+
+    struct vlc_list priv_node;
+} hls_segment_t;
+
+struct hls_segment_queue_config
+{
+    unsigned int playlist_id;
+
+    httpd_host_t *httpd_ref;
+    httpd_callback_t httpd_callback;
+};
+
+typedef struct
+{
+    struct hls_segment_queue_config config;
+    unsigned int total_segments;
+
+    const struct hls_config *hls_config;
+
+    struct vlc_list segments;
+} hls_segment_queue_t;
+
+#define hls_segment_queue_Foreach(queue, it)                                   \
+    vlc_list_foreach (it, &(queue)->segments, priv_node)
+#define hls_segment_GetFirst(queue)                                            \
+    vlc_list_first_entry_or_null(&(queue)->segments, hls_segment_t, priv_node);
+
+void hls_segment_queue_Init(hls_segment_queue_t *,
+                            const struct hls_segment_queue_config *,
+                            const struct hls_config *);
+void hls_segment_queue_Clear(hls_segment_queue_t *);
+
+/**
+ * Add a new segment to the queue.
+ *
+ * If the queue is at max capacity, the first inserted segment will also be
+ * popped and destroyed.
+ *
+ * \param content A chain of block containing segment's data.
+ * \param length The media time size of the segment.
+ *
+ * \retval VLC_SUCCESS on success.
+ * \retval VLC_ENOMEM on internal allocation failure.
+ */
+int hls_segment_queue_NewSegment(hls_segment_queue_t *,
+                                 block_t *content,
+                                 vlc_tick_t length);
+
+static inline bool
+hls_segment_queue_IsAtMaxCapacity(const hls_segment_queue_t *queue)
+{
+    return queue->hls_config->max_segments != 0 &&
+           queue->hls_config->max_segments <= queue->total_segments;
+}
+
+#endif


=====================================
modules/stream_out/hls/storage.c
=====================================
@@ -0,0 +1,320 @@
+/*****************************************************************************
+ * storage.c
+ *****************************************************************************
+ * Copyright (C) 2023 VLC authors and VideoLAN
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <fcntl.h>
+
+#include <vlc_common.h>
+
+#include <vlc_block.h>
+#include <vlc_fs.h>
+
+#include "hls.h"
+#include "storage.h"
+
+struct storage_priv
+{
+    hls_storage_t storage;
+    void (*destroy)(struct storage_priv *storage);
+    size_t size;
+
+    union
+    {
+        struct
+        {
+            block_t *content;
+        } mem;
+
+        struct
+        {
+            char *path;
+        } fs;
+    };
+};
+
+static void mem_storage_Destroy(struct storage_priv *priv)
+{
+    block_ChainRelease(priv->mem.content);
+    free(priv);
+}
+
+static ssize_t mem_storage_GetContent(const hls_storage_t *storage,
+                                      uint8_t **dest)
+{
+    const struct storage_priv *priv =
+        container_of(storage, struct storage_priv, storage);
+
+    *dest = malloc(priv->size);
+    if (unlikely(*dest == NULL))
+        return -1;
+
+    uint8_t *cursor = *dest;
+    for (const block_t *it = priv->mem.content; it != NULL; it = it->p_next)
+    {
+        memcpy(cursor, it->p_buffer, it->i_buffer);
+        cursor += it->i_buffer;
+    }
+    return priv->size;
+}
+
+static hls_storage_t *mem_storage_FromBlock(block_t *content)
+{
+    struct storage_priv *priv = malloc(sizeof(*priv));
+    if (unlikely(priv == NULL))
+        return NULL;
+
+    priv->storage.get_content = mem_storage_GetContent;
+    priv->destroy = mem_storage_Destroy;
+    priv->mem.content = content;
+    block_ChainProperties(content, NULL, &priv->size, NULL);
+    return &priv->storage;
+}
+
+static hls_storage_t *mem_storage_FromBytes(void *bytes, size_t size)
+{
+    struct storage_priv *priv = malloc(sizeof(*priv));
+    if (unlikely(priv == NULL))
+        return NULL;
+
+    block_t *content = block_heap_Alloc(bytes, size);
+    if (unlikely(content == NULL))
+    {
+        free(priv);
+        return NULL;
+    }
+
+    priv->storage.get_content = mem_storage_GetContent;
+    priv->destroy = mem_storage_Destroy;
+    priv->size = size;
+    priv->mem.content = content;
+    return &priv->storage;
+}
+
+static ssize_t fs_storage_Read(int fd, uint8_t buf[], size_t len)
+{
+    size_t total = 0;
+    while (total < len)
+    {
+        const ssize_t n = read(fd, buf + total, len - total);
+        if (n == -1)
+        {
+            if (errno == EINTR || errno == EAGAIN)
+                continue;
+            return -1;
+        }
+        else if (n == 0)
+            break;
+
+        total += n;
+    }
+    return total;
+}
+
+static ssize_t fs_storage_GetContent(const hls_storage_t *storage,
+                                     uint8_t **dest)
+{
+    const struct storage_priv *priv =
+        container_of(storage, struct storage_priv, storage);
+
+    const int fd = vlc_open(priv->fs.path, O_RDONLY);
+
+    if (fd == -1)
+        return -1;
+
+    *dest = malloc(priv->size);
+    if (unlikely(*dest == NULL))
+        goto err;
+
+    const ssize_t read = fs_storage_Read(fd, *dest, priv->size);
+    if (read == -1)
+        goto err;
+
+    close(fd);
+    return read;
+err:
+    free(dest);
+    close(fd);
+    return -1;
+}
+
+static int fs_storage_Write(int fd, const uint8_t *data, size_t len)
+{
+    size_t written = 0;
+    while (written < len)
+    {
+        const ssize_t n = vlc_write(fd, data + written, len - written);
+        if (n == -1)
+        {
+            if (errno == EINTR || errno == EAGAIN)
+                continue;
+            return VLC_EGENERIC;
+        }
+
+        written += n;
+    }
+    return VLC_SUCCESS;
+}
+
+static void fs_storage_Destroy(struct storage_priv *priv)
+{
+    free(priv->fs.path);
+    free(priv);
+}
+
+static inline char *fs_storage_CreatePath(const char *outdir,
+                                          const char *storage_name)
+{
+    char *ret;
+
+    if (asprintf(&ret, "%s/%s", outdir, storage_name) == -1)
+        return NULL;
+    return ret;
+}
+
+static hls_storage_t *
+fs_storage_FromBlock(block_t *content,
+                     const struct hls_storage_config *config,
+                     const struct hls_config *hls_config)
+{
+    struct storage_priv *priv = malloc(sizeof(*priv));
+    if (unlikely(priv == NULL))
+        goto err;
+
+    priv->fs.path = fs_storage_CreatePath(hls_config->outdir, config->name);
+    if (unlikely(priv->fs.path == NULL))
+        goto err;
+
+    const int fd = vlc_open(priv->fs.path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+    if (fd == -1)
+        goto err;
+
+    size_t size = 0;
+    for (const block_t *it = content; it != NULL; it = it->p_next)
+    {
+        const int status = fs_storage_Write(fd, it->p_buffer, it->i_buffer);
+        if (status != VLC_SUCCESS)
+        {
+            close(fd);
+            goto err;
+        }
+        size += it->i_buffer;
+    }
+
+    close(fd);
+    block_ChainRelease(content);
+
+    priv->storage.get_content = fs_storage_GetContent;
+    priv->size = size;
+    priv->destroy = fs_storage_Destroy;
+
+    return &priv->storage;
+err:
+    block_ChainRelease(content);
+    if (priv != NULL)
+        free(priv->fs.path);
+    free(priv);
+    return NULL;
+}
+
+static hls_storage_t *
+fs_storage_FromBytes(void *bytes,
+                     size_t size,
+                     const struct hls_storage_config *config,
+                     const struct hls_config *hls_config)
+{
+    struct storage_priv *priv = malloc(sizeof(*priv));
+    if (unlikely(priv == NULL))
+        return NULL;
+
+    priv->fs.path = fs_storage_CreatePath(hls_config->outdir, config->name);
+    if (unlikely(priv->fs.path == NULL))
+        goto err;
+
+    const int fd = vlc_open(priv->fs.path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+    if (fd == -1)
+        goto err;
+
+    const int status = fs_storage_Write(fd, bytes, size);
+    close(fd);
+
+    if (unlikely(status != VLC_SUCCESS))
+        goto err;
+
+    priv->storage.get_content = fs_storage_GetContent;
+    priv->size = size;
+    priv->destroy = fs_storage_Destroy;
+
+    free(bytes);
+    return &priv->storage;
+err:
+    free(bytes);
+    if (priv != NULL)
+        free(priv->fs.path);
+    free(priv);
+    return NULL;
+}
+
+hls_storage_t *hls_storage_FromBlocks(block_t *content,
+                                      const struct hls_storage_config *config,
+                                      const struct hls_config *hls_config)
+{
+    hls_storage_t *storage;
+    if (hls_config_IsMemStorageEnabled(hls_config))
+        storage = mem_storage_FromBlock(content);
+    else
+        storage = fs_storage_FromBlock(content, config, hls_config);
+
+    if (storage != NULL)
+        storage->mime = config->mime;
+    return storage;
+}
+
+hls_storage_t *hls_storage_FromBytes(void *data,
+                                     size_t size,
+                                     const struct hls_storage_config *config,
+                                     const struct hls_config *hls_config)
+{
+    hls_storage_t *storage;
+    if (hls_config_IsMemStorageEnabled(hls_config))
+        storage = mem_storage_FromBytes(data, size);
+    else
+        storage = fs_storage_FromBytes(data, size, config, hls_config);
+
+    if (storage != NULL)
+        storage->mime = config->mime;
+    return storage;
+}
+
+size_t hls_storage_GetSize(const hls_storage_t *storage)
+{
+    const struct storage_priv *priv =
+        container_of(storage, struct storage_priv, storage);
+    return priv->size;
+}
+
+void hls_storage_Destroy(hls_storage_t *storage)
+{
+    struct storage_priv *priv =
+        container_of(storage, struct storage_priv, storage);
+    priv->destroy(priv);
+}


=====================================
modules/stream_out/hls/storage.h
=====================================
@@ -0,0 +1,85 @@
+/*****************************************************************************
+ * storage.h
+ *****************************************************************************
+ * Copyright (C) 2023 VLC authors and VideoLAN
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef HLS_STORAGE_H
+#define HLS_STORAGE_H
+
+/**
+ * Handy simple storage abstraction to allow seamless support of in-memory or
+ * filesystem HLS segment/manifest storage.
+ */
+
+struct hls_storage_config
+{
+    const char *name;
+    const char *mime;
+};
+
+typedef struct hls_storage
+{
+    const char *mime;
+    /**
+     * Get a copy of the whole storage content.
+     *
+     * \param[out] dest Pointer on a byte buffer, will be freshly allocated by
+     * the function call. \return Byte count of the byte buffer. \retval -1 On
+     * allocation error.
+     */
+    ssize_t (*get_content)(const struct hls_storage *, uint8_t **dest);
+} hls_storage_t;
+
+/**
+ * Create an HLS opaque storage from a chain of blocks.
+ *
+ * \note The returned storage must be destroyed with \ref hls_storage_Destroy.
+ *
+ * \param content The block chain.
+ * \param hls_storage_config The storage specific config.
+ * \param hls_config The global hls config.
+ *
+ * \return An opaque pointer on the HLS storage.
+ * \retval NULL on allocation error.
+ */
+hls_storage_t *hls_storage_FromBlocks(block_t *content,
+                                      const struct hls_storage_config *,
+                                      const struct hls_config *) VLC_USED;
+
+/**
+ * Create an HLS opaque storage from a byte buffer.
+ *
+ * \note The returned storage must be destroyed with \ref hls_storage_Destroy.
+ *
+ * \param data Pointer on the buffer.
+ * \param size Byte size of the buffer.
+ * \param hls_storage_config The storage specific config.
+ * \param hls_config The global hls config.
+ *
+ * \return An opaque pointer on the HLS storage.
+ * \retval NULL on allocation error.
+ */
+hls_storage_t *hls_storage_FromBytes(void *data,
+                                     size_t size,
+                                     const struct hls_storage_config *,
+                                     const struct hls_config *) VLC_USED;
+
+size_t hls_storage_GetSize(const hls_storage_t *);
+
+void hls_storage_Destroy(hls_storage_t *);
+
+#endif


=====================================
modules/stream_out/hls/variant_maps.c
=====================================
@@ -0,0 +1,197 @@
+/*****************************************************************************
+ * variant_maps.c
+ *****************************************************************************
+ * Copyright (C) 2023 VLC authors and VideoLAN
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <ctype.h>
+
+#include <vlc_common.h>
+
+#include <vlc_memstream.h>
+#include <vlc_vector.h>
+
+#include "variant_maps.h"
+
+enum hls_map_parser_state
+{
+    STATE_MAP_BEGIN,
+    STATE_NEW_ESID,
+    STATE_BUILD_ESID,
+    STATE_MAP_END,
+};
+
+static int parser_NewESID(struct vlc_memstream *esid,
+                          struct hls_variant_stream_map *map)
+{
+    vlc_memstream_putc(esid, '\0');
+    if (vlc_memstream_close(esid))
+        return VLC_ENOMEM;
+
+    if (!vlc_vector_push(&map->es_list, esid->ptr))
+    {
+        free(esid->ptr);
+        return VLC_ENOMEM;
+    }
+
+    return VLC_SUCCESS;
+}
+
+static inline bool is_valid_char(char c) { return isalnum(c) || c == '/'; }
+
+static void hls_variant_map_Destroy(struct hls_variant_stream_map *map)
+{
+    char *esid;
+    vlc_vector_foreach (esid, &map->es_list)
+    {
+        free(esid);
+    }
+    vlc_vector_destroy(&map->es_list);
+    free(map);
+}
+
+int hls_variant_maps_Parse(const char *s, hls_variant_stream_maps_t *out)
+{
+    int error = VLC_ENOMEM;
+
+    struct vlc_memstream current_esid;
+    struct hls_variant_stream_map *current_map = NULL;
+    enum hls_map_parser_state state = STATE_MAP_BEGIN;
+
+    for (char c = *s; c != '\0'; c = *++s)
+    {
+        if (c == ' ' || c == '\t')
+            continue;
+
+        switch (state)
+        {
+            case STATE_MAP_BEGIN:
+                if (c != '{')
+                    goto einval;
+
+                current_map = malloc(sizeof(*current_map));
+                if (unlikely(current_map == NULL))
+                    goto enomem;
+
+                vlc_vector_init(&current_map->es_list);
+                current_map->playlist_ref = NULL;
+
+                state = STATE_NEW_ESID;
+                break;
+            case STATE_NEW_ESID:
+                if (!is_valid_char(c))
+                    goto einval;
+
+                vlc_memstream_open(&current_esid);
+                vlc_memstream_putc(&current_esid, c);
+                state = STATE_BUILD_ESID;
+                break;
+            case STATE_BUILD_ESID:
+                if (c == ',')
+                {
+                    if (parser_NewESID(&current_esid, current_map) !=
+                        VLC_SUCCESS)
+                        goto enomem;
+                    state = STATE_NEW_ESID;
+                }
+                else if (c == '}')
+                {
+                    if (parser_NewESID(&current_esid, current_map) !=
+                        VLC_SUCCESS)
+                        goto enomem;
+
+                    if (!vlc_vector_push(out, current_map))
+                        goto enomem;
+                    current_map = NULL;
+
+                    state = STATE_MAP_END;
+                }
+                else if (is_valid_char(c))
+                    vlc_memstream_putc(&current_esid, c);
+                else
+                {
+                    if (!vlc_memstream_close(&current_esid))
+                        free(current_esid.ptr);
+                    goto einval;
+                }
+                break;
+            case STATE_MAP_END:
+                if (c == ',')
+                    state = STATE_MAP_BEGIN;
+                else
+                    goto einval;
+                break;
+        }
+    }
+
+    if (state == STATE_MAP_END)
+        return VLC_SUCCESS;
+
+einval:
+    error = VLC_EINVAL;
+enomem:
+    hls_variant_maps_Destroy(out);
+    if (current_map != NULL)
+        hls_variant_map_Destroy(current_map);
+    return error;
+}
+
+void hls_variant_maps_Destroy(hls_variant_stream_maps_t *maps)
+{
+    struct hls_variant_stream_map *map;
+    vlc_vector_foreach (map, maps)
+    {
+        char *esid;
+        vlc_vector_foreach (esid, &map->es_list)
+            free(esid);
+        vlc_vector_destroy(&map->es_list);
+        free(map);
+    }
+    vlc_vector_destroy(maps);
+}
+
+struct hls_variant_stream_map *
+hls_variant_map_FromESID(hls_variant_stream_maps_t *maps, const char *es_id)
+{
+    struct hls_variant_stream_map *it;
+    vlc_vector_foreach (it, maps)
+    {
+        const char *es_id_it;
+        vlc_vector_foreach (es_id_it, &it->es_list)
+        {
+            if (!strcmp(es_id_it, es_id))
+                return it;
+        }
+    }
+    return NULL;
+}
+
+struct hls_variant_stream_map *
+hls_variant_map_FromPlaylist(hls_variant_stream_maps_t *maps,
+                             struct hls_playlist *playlist)
+{
+    struct hls_variant_stream_map *it;
+    vlc_vector_foreach (it, maps)
+    {
+        if (it->playlist_ref == playlist)
+            return it;
+    }
+    return NULL;
+}


=====================================
modules/stream_out/hls/variant_maps.h
=====================================
@@ -0,0 +1,51 @@
+/*****************************************************************************
+ * variant_maps.h
+ *****************************************************************************
+ * Copyright (C) 2023 VLC authors and VideoLAN
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef HLS_VARIANT_MAPS_H
+#define HLS_VARIANT_MAPS_H
+
+/**
+ * Variant stream represented by a group of ES string ID. This structure
+ * represents the parsed user input and is used to add the incomming ES to the
+ * correct playlists.
+ */
+struct hls_variant_stream_map
+{
+    struct VLC_VECTOR(char *) es_list;
+    /**
+     * Reference on the playlist associated with this list of ES. Having a
+     * reference here after the playlist creation ease subsequent searches of
+     * the playlist.
+     */
+    struct hls_playlist *playlist_ref;
+};
+
+typedef struct VLC_VECTOR(struct hls_variant_stream_map *)
+    hls_variant_stream_maps_t;
+
+int hls_variant_maps_Parse(const char *in, hls_variant_stream_maps_t *out);
+void hls_variant_maps_Destroy(hls_variant_stream_maps_t *);
+
+struct hls_variant_stream_map *
+hls_variant_map_FromESID(hls_variant_stream_maps_t *, const char *);
+struct hls_variant_stream_map *
+hls_variant_map_FromPlaylist(hls_variant_stream_maps_t *,
+                             struct hls_playlist *);
+
+#endif



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/87f265b0dbdb7e08e0afcf7e8a7e22143724d7fe...c67dec41df363393c84120563cc1faedd7799c8f

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/87f265b0dbdb7e08e0afcf7e8a7e22143724d7fe...c67dec41df363393c84120563cc1faedd7799c8f
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list