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: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3883>
This commit is contained in:
Jan Schmidt 2022-10-13 05:19:57 +11:00 committed by GStreamer Marge Bot
parent 5ca336226e
commit cb27c05ca7
5 changed files with 347 additions and 0 deletions

View file

@ -0,0 +1,228 @@
/* GStreamer
Copyright (C) 2022 Jan Schmidt <jan@centricular.com>
*
* 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);
}

View file

@ -0,0 +1,46 @@
/* GStreamer
Copyright (C) 2022 Jan Schmidt <jan@centricular.com>
*
* 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 <glib.h>
#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__ */

View file

@ -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_typefind_buffer, NULL);
gst_buffer_replace (&hls_stream->pending_segment_data, 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) if (hls_stream->moov)
gst_isoff_moov_box_free (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 static GstFlowReturn
gst_hls_demux_stream_update_media_playlist (GstHLSDemux * demux, gst_hls_demux_stream_update_media_playlist (GstHLSDemux * demux,
GstHLSDemuxStream * stream, gchar ** uri, GError ** err) GstHLSDemuxStream * stream, gchar ** uri, GError ** err)
@ -2429,6 +2489,11 @@ gst_hls_demux_stream_update_media_playlist (GstHLSDemux * demux,
stream->playlist = new_playlist; 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) { if (stream->is_variant) {
/* Update time mappings. We only use the variant stream for collecting /* Update time mappings. We only use the variant stream for collecting
* mappings since it is the reference on which rendition stream timing will * 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, stream->current_position, hlsdemux_stream->in_partial_segments,
&seek_result)) { &seek_result)) {
GST_INFO_OBJECT (stream, "At the end of the current media playlist"); GST_INFO_OBJECT (stream, "At the end of the current media playlist");
gst_hls_demux_stream_update_preloads (hlsdemux_stream);
return GST_FLOW_EOS; 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 */ * hit the live edge and need to wait for a playlist update */
if (file->partial_only) { if (file->partial_only) {
GST_INFO_OBJECT (stream, "At the end of the current media playlist"); GST_INFO_OBJECT (stream, "At the end of the current media playlist");
gst_hls_demux_stream_update_preloads (hlsdemux_stream);
return GST_FLOW_EOS; return GST_FLOW_EOS;
} }

View file

@ -30,6 +30,8 @@
#include "m3u8.h" #include "m3u8.h"
#include "gstisoff.h" #include "gstisoff.h"
#include "gstadaptivedemux.h" #include "gstadaptivedemux.h"
#include "gsthlsdemux-preloader.h"
#if defined(HAVE_OPENSSL) #if defined(HAVE_OPENSSL)
#include <openssl/evp.h> #include <openssl/evp.h>
#elif defined(HAVE_NETTLE) #elif defined(HAVE_NETTLE)
@ -126,6 +128,9 @@ struct _GstHLSDemuxStream
gboolean in_partial_segments; gboolean in_partial_segments;
guint part_idx; guint part_idx;
/* Preload helper, that manages blocking preload downloads */
GstHLSDemuxPreloader *preloader;
/* Whether we need to typefind the next buffer */ /* Whether we need to typefind the next buffer */
gboolean do_typefind; gboolean do_typefind;

View file

@ -1,5 +1,6 @@
hls_sources = [ hls_sources = [
'hls/gsthlsdemux.c', 'hls/gsthlsdemux.c',
'hls/gsthlsdemux-preloader.c',
'hls/gsthlsdemux-util.c', 'hls/gsthlsdemux-util.c',
'hls/gsthlselement.c', 'hls/gsthlselement.c',
'hls/m3u8.c', 'hls/m3u8.c',