From cb27c05ca79bf59e194efe1289c11d5a6f656eb4 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Thu, 13 Oct 2022 05:19:57 +1100 Subject: [PATCH] hlsdemux2: Add preloader helper. Add a helper that submits and handles blocking preload requests for future PART/MAP data from live playlists. Add handling in the hlsdemux stream to submit preload requests when hitting the end of the available segments in a live playlist. Part-of: --- .../hls/gsthlsdemux-preloader.c | 228 ++++++++++++++++++ .../hls/gsthlsdemux-preloader.h | 46 ++++ .../ext/adaptivedemux2/hls/gsthlsdemux.c | 67 +++++ .../ext/adaptivedemux2/hls/gsthlsdemux.h | 5 + .../ext/adaptivedemux2/hls/meson.build | 1 + 5 files changed, 347 insertions(+) create mode 100644 subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-preloader.c create mode 100644 subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-preloader.h diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-preloader.c b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-preloader.c new file mode 100644 index 0000000000..4c0fca871c --- /dev/null +++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-preloader.c @@ -0,0 +1,228 @@ +/* GStreamer + Copyright (C) 2022 Jan Schmidt + * + * gsthlsdemux-preloader.c: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gsthlsdemux-preloader.h" + +GST_DEBUG_CATEGORY_EXTERN (gst_hls_demux2_debug); +#define GST_CAT_DEFAULT gst_hls_demux2_debug + +typedef struct _GstHLSDemuxPreloadRequest GstHLSDemuxPreloadRequest; +struct _GstHLSDemuxPreloadRequest +{ + GstHLSDemuxPreloader *preloader; /* Parent preloader */ + GstM3U8PreloadHint *hint; + DownloadRequest *download_request; +}; + +static GstHLSDemuxPreloadRequest * +gst_hls_demux_preload_request_new (GstHLSDemuxPreloader * preloader, + GstM3U8PreloadHint * hint) +{ + GstHLSDemuxPreloadRequest *req = g_new0 (GstHLSDemuxPreloadRequest, 1); + req->preloader = preloader; + req->hint = gst_m3u8_preload_hint_ref (hint); + + return req; +}; + +static void +gst_hls_demux_preload_request_free (GstHLSDemuxPreloadRequest * req) +{ + gst_m3u8_preload_hint_unref (req->hint); + + /* The download request must have been cancelled and removed by the preload helper */ + g_assert (req->download_request == NULL); + g_free (req); +}; + +static gboolean +gst_hls_demux_preloader_submit (GstHLSDemuxPreloader * preloader, + GstHLSDemuxPreloadRequest * preload_req, const gchar * referrer_uri); +static void gst_hls_demux_preloader_cancel_request (GstHLSDemuxPreloader * + preloader, GstHLSDemuxPreloadRequest * req); + +GstHLSDemuxPreloader * +gst_hls_demux_preloader_new (DownloadHelper * download_helper) +{ + GstHLSDemuxPreloader *preloader = g_new0 (GstHLSDemuxPreloader, 1); + + preloader->download_helper = download_helper; + preloader->active_preloads = g_ptr_array_new (); + + return preloader; +} + +void +gst_hls_demux_preloader_free (GstHLSDemuxPreloader * preloader) +{ + gst_hls_demux_preloader_cancel (preloader, M3U8_PRELOAD_HINT_ALL); + g_ptr_array_free (preloader->active_preloads, TRUE); + g_free (preloader); +} + +void +gst_hls_demux_preloader_load (GstHLSDemuxPreloader * preloader, + GstM3U8PreloadHint * hint, const gchar * referrer_uri) +{ + /* Check if we have an active preload already for this hint */ + guint idx; + for (idx = 0; idx < preloader->active_preloads->len; idx++) { + GstHLSDemuxPreloadRequest *req = + g_ptr_array_index (preloader->active_preloads, idx); + if (hint->hint_type == req->hint->hint_type) { + /* We already have an active hint of this type. If this new one is different, cancel + * the active preload before starting this one */ + if (gst_m3u8_preload_hint_equal (hint, req->hint)) { + GST_LOG ("Ignoring pre-existing preload of type %d uri: %s, range:%" + G_GINT64_FORMAT " size %" G_GINT64_FORMAT, hint->hint_type, + hint->uri, hint->offset, hint->size); + return; /* Nothing to do */ + } + + gst_hls_demux_preloader_cancel_request (preloader, req); + g_ptr_array_remove_index_fast (preloader->active_preloads, idx); + break; + } + } + + /* If we get here, then there's no preload of this type. Create one */ + GstHLSDemuxPreloadRequest *req = + gst_hls_demux_preload_request_new (preloader, hint); + /* Submit the request */ + + if (gst_hls_demux_preloader_submit (preloader, req, referrer_uri)) { + g_ptr_array_add (preloader->active_preloads, req); + } else { + /* Discard failed request */ + gst_hls_demux_preloader_cancel_request (preloader, req); + } +} + +void +gst_hls_demux_preloader_cancel (GstHLSDemuxPreloader * preloader, + GstM3U8PreloadHintType hint_types) +{ + /* Go through the active downloads and remove/cancel any with the matching type */ + guint idx; + for (idx = 0; idx < preloader->active_preloads->len;) { + GstHLSDemuxPreloadRequest *req = + g_ptr_array_index (preloader->active_preloads, idx); + if (hint_types & req->hint->hint_type) { + gst_hls_demux_preloader_cancel_request (preloader, req); + g_ptr_array_remove_index_fast (preloader->active_preloads, idx); + continue; /* Don't increment idx++, as we just removed an item */ + } + + idx++; + } +} + +static void +on_download_cancellation (DownloadRequest * request, DownloadRequestState state, + GstHLSDemuxPreloadRequest * preload_req) +{ +} + +static void +on_download_error (DownloadRequest * request, DownloadRequestState state, + GstHLSDemuxPreloadRequest * preload_req) +{ + GstM3U8PreloadHint *hint = preload_req->hint; + GST_DEBUG ("preload type %d uri: %s download error", hint->hint_type, + hint->uri); + GST_FIXME ("How to handle failed preload request?"); +} + +static void +on_download_progress (DownloadRequest * request, DownloadRequestState state, + GstHLSDemuxPreloadRequest * preload_req) +{ + GstM3U8PreloadHint *hint = preload_req->hint; + GST_DEBUG ("preload type %d uri: %s download progress", hint->hint_type, + hint->uri); +} + +static void +on_download_complete (DownloadRequest * request, DownloadRequestState state, + GstHLSDemuxPreloadRequest * preload_req) +{ + GstM3U8PreloadHint *hint = preload_req->hint; + GST_DEBUG ("preload type %d uri: %s download complete", hint->hint_type, + hint->uri); +} + +static gboolean +gst_hls_demux_preloader_submit (GstHLSDemuxPreloader * preloader, + GstHLSDemuxPreloadRequest * preload_req, const gchar * referrer_uri) +{ + g_assert (preload_req->download_request == NULL); + + DownloadRequest *download_req = download_request_new (); + GstM3U8PreloadHint *hint = preload_req->hint; + + /* Configure our download request */ + gint64 end = RFC8673_LAST_BYTE_POS; + if (hint->size > 0) { + end = hint->offset + hint->size - 1; + } + + download_request_set_uri (download_req, hint->uri, hint->offset, end); + download_request_set_callbacks (download_req, + (DownloadRequestEventCallback) on_download_complete, + (DownloadRequestEventCallback) on_download_error, + (DownloadRequestEventCallback) on_download_cancellation, + (DownloadRequestEventCallback) on_download_progress, preload_req); + + GST_DEBUG ("Submitting preload type %d uri: %s, range:%" G_GINT64_FORMAT + " - %" G_GINT64_FORMAT, hint->hint_type, hint->uri, hint->offset, end); + + if (!downloadhelper_submit_request (preloader->download_helper, + referrer_uri, DOWNLOAD_FLAG_NONE, download_req, NULL)) { + /* Abandon the request */ + download_request_unref (download_req); + return FALSE; + } + + preload_req->download_request = download_req; + return TRUE; +} + +static void +gst_hls_demux_preloader_cancel_request (GstHLSDemuxPreloader * preloader, + GstHLSDemuxPreloadRequest * preload_req) +{ + if (preload_req->download_request) { + GstM3U8PreloadHint *hint = preload_req->hint; + GST_DEBUG ("Cancelling preload type %d uri: %s, range start:%" + G_GINT64_FORMAT " size %" G_GINT64_FORMAT, hint->hint_type, hint->uri, + hint->offset, hint->size); + + downloadhelper_cancel_request (preloader->download_helper, + preload_req->download_request); + download_request_unref (preload_req->download_request); + preload_req->download_request = NULL; + } + gst_hls_demux_preload_request_free (preload_req); +} diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-preloader.h b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-preloader.h new file mode 100644 index 0000000000..6bf4b40aab --- /dev/null +++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-preloader.h @@ -0,0 +1,46 @@ +/* GStreamer + Copyright (C) 2022 Jan Schmidt + * + * gsthlsdemux-preloader.h: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef __GST_HLS_DEMUX_PRELOADER_H__ +#define __GST_HLS_DEMUX_PRELOADER_H__ + +#include + +#include "m3u8.h" + +#include "downloadrequest.h" +#include "downloadhelper.h" + +G_BEGIN_DECLS + +typedef struct _GstHLSDemuxPreloader GstHLSDemuxPreloader; + +struct _GstHLSDemuxPreloader { + DownloadHelper *download_helper; /* Owned by the demuxer */ + GPtrArray *active_preloads; +}; + +GstHLSDemuxPreloader *gst_hls_demux_preloader_new (DownloadHelper *download_helper); +void gst_hls_demux_preloader_free (GstHLSDemuxPreloader *preloader); +void gst_hls_demux_preloader_load (GstHLSDemuxPreloader *preloader, GstM3U8PreloadHint *hint, const gchar *referrer_uri); +void gst_hls_demux_preloader_cancel (GstHLSDemuxPreloader *preloader, GstM3U8PreloadHintType hint_types); + +G_END_DECLS +#endif /* __GST_HLS_DEMUX_PRELOADER_H__ */ diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.c b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.c index 9f7790bc50..f4721cb558 100644 --- a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.c +++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.c @@ -1844,6 +1844,11 @@ gst_hls_demux_stream_finalize (GObject * object) gst_buffer_replace (&hls_stream->pending_typefind_buffer, NULL); gst_buffer_replace (&hls_stream->pending_segment_data, NULL); + if (hls_stream->preloader) { + gst_hls_demux_preloader_free (hls_stream->preloader); + hls_stream->preloader = NULL; + } + if (hls_stream->moov) gst_isoff_moov_box_free (hls_stream->moov); @@ -2298,6 +2303,61 @@ gst_hls_demux_reset_for_lost_sync (GstHLSDemux * hlsdemux) } } +static void +gst_hls_demux_stream_update_preloads (GstHLSDemuxStream * hlsdemux_stream) +{ + GstHLSMediaPlaylist *playlist = hlsdemux_stream->playlist; + GstAdaptiveDemux *demux = + GST_ADAPTIVE_DEMUX2_STREAM_CAST (hlsdemux_stream)->demux; + GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); + + gboolean preloads_allowed = hlsdemux->llhls_enabled + && GST_HLS_MEDIA_PLAYLIST_IS_LIVE (playlist); + + if (playlist->preload_hints == NULL || !preloads_allowed) { + if (hlsdemux_stream->preloader != NULL) { + /* Cancel any preloads, the new playlist doesn't have them */ + gst_hls_demux_preloader_cancel (hlsdemux_stream->preloader, + M3U8_PRELOAD_HINT_ALL); + } + /* Nothing to preload */ + return; + } + + if (hlsdemux_stream->preloader == NULL) { + GstAdaptiveDemux *demux = + GST_ADAPTIVE_DEMUX2_STREAM (hlsdemux_stream)->demux; + hlsdemux_stream->preloader = + gst_hls_demux_preloader_new (demux->download_helper); + if (hlsdemux_stream->preloader == NULL) { + GST_WARNING_OBJECT (hlsdemux_stream, "Failed to create preload handler"); + return; + } + } + + /* The HLS spec says any extra preload hint of each type should be ignored */ + GstM3U8PreloadHintType seen_types = 0; + guint idx; + for (idx = 0; idx < playlist->preload_hints->len; idx++) { + GstM3U8PreloadHint *hint = g_ptr_array_index (playlist->preload_hints, idx); + switch (hint->hint_type) { + case M3U8_PRELOAD_HINT_MAP: + case M3U8_PRELOAD_HINT_PART: + if (seen_types & hint->hint_type) { + continue; /* Ignore preload hint type we've already seen */ + } + seen_types |= hint->hint_type; + break; + default: + GST_FIXME_OBJECT (hlsdemux_stream, "Ignoring unknown preload type %d", + hint->hint_type); + continue; /* Unknown hint type, ignore it */ + } + gst_hls_demux_preloader_load (hlsdemux_stream->preloader, hint, + playlist->uri); + } +} + static GstFlowReturn gst_hls_demux_stream_update_media_playlist (GstHLSDemux * demux, GstHLSDemuxStream * stream, gchar ** uri, GError ** err) @@ -2429,6 +2489,11 @@ gst_hls_demux_stream_update_media_playlist (GstHLSDemux * demux, stream->playlist = new_playlist; } + if (!GST_HLS_MEDIA_PLAYLIST_IS_LIVE (stream->playlist)) { + /* Make sure to cancel any preloads if a playlist isn't live after reload */ + gst_hls_demux_stream_update_preloads (stream); + } + if (stream->is_variant) { /* Update time mappings. We only use the variant stream for collecting * mappings since it is the reference on which rendition stream timing will @@ -2581,6 +2646,7 @@ gst_hls_demux_stream_update_fragment_info (GstAdaptiveDemux2Stream * stream) stream->current_position, hlsdemux_stream->in_partial_segments, &seek_result)) { GST_INFO_OBJECT (stream, "At the end of the current media playlist"); + gst_hls_demux_stream_update_preloads (hlsdemux_stream); return GST_FLOW_EOS; } @@ -2620,6 +2686,7 @@ gst_hls_demux_stream_update_fragment_info (GstAdaptiveDemux2Stream * stream) * hit the live edge and need to wait for a playlist update */ if (file->partial_only) { GST_INFO_OBJECT (stream, "At the end of the current media playlist"); + gst_hls_demux_stream_update_preloads (hlsdemux_stream); return GST_FLOW_EOS; } diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.h b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.h index e5021f96fc..8b3fb87722 100644 --- a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.h +++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.h @@ -30,6 +30,8 @@ #include "m3u8.h" #include "gstisoff.h" #include "gstadaptivedemux.h" +#include "gsthlsdemux-preloader.h" + #if defined(HAVE_OPENSSL) #include #elif defined(HAVE_NETTLE) @@ -126,6 +128,9 @@ struct _GstHLSDemuxStream gboolean in_partial_segments; guint part_idx; + /* Preload helper, that manages blocking preload downloads */ + GstHLSDemuxPreloader *preloader; + /* Whether we need to typefind the next buffer */ gboolean do_typefind; diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/meson.build b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/meson.build index c4cc3ad435..23630286c6 100644 --- a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/meson.build +++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/meson.build @@ -1,5 +1,6 @@ hls_sources = [ 'hls/gsthlsdemux.c', + 'hls/gsthlsdemux-preloader.c', 'hls/gsthlsdemux-util.c', 'hls/gsthlselement.c', 'hls/m3u8.c',