mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-23 00:36:51 +00:00
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:
parent
5ca336226e
commit
cb27c05ca7
5 changed files with 347 additions and 0 deletions
|
@ -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);
|
||||
}
|
|
@ -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__ */
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@
|
|||
#include "m3u8.h"
|
||||
#include "gstisoff.h"
|
||||
#include "gstadaptivedemux.h"
|
||||
#include "gsthlsdemux-preloader.h"
|
||||
|
||||
#if defined(HAVE_OPENSSL)
|
||||
#include <openssl/evp.h>
|
||||
#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;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
hls_sources = [
|
||||
'hls/gsthlsdemux.c',
|
||||
'hls/gsthlsdemux-preloader.c',
|
||||
'hls/gsthlsdemux-util.c',
|
||||
'hls/gsthlselement.c',
|
||||
'hls/m3u8.c',
|
||||
|
|
Loading…
Reference in a new issue