dashsink: Add signals for allowing custom playlist/fragment

Instead of always going through the file system API we allow the
application to modify the behaviour. For the playlist itself and
fragments, the application can provide a GOutputStream. In addition the
sink notifies the application whenever a fragment can be deleted.

Following the HLS change:
https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/918

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/2064>
This commit is contained in:
Stéphane Cerveau 2021-03-09 11:40:43 +01:00 committed by GStreamer Marge Bot
parent 078b2699e7
commit 506bd90bf7
2 changed files with 224 additions and 58 deletions

View file

@ -7660,7 +7660,29 @@
"writable": true
}
},
"rank": "none"
"rank": "none",
"signals": {
"get-fragment-stream": {
"args": [
{
"name": "arg0",
"type": "gchararray"
}
],
"return-type": "GOutputStream",
"when": "last"
},
"get-playlist-stream": {
"args": [
{
"name": "arg0",
"type": "gchararray"
}
],
"return-type": "GOutputStream",
"when": "last"
}
}
}
},
"filename": "gstdash",

View file

@ -88,6 +88,7 @@
#include <gst/pbutils/pbutils.h>
#include <gst/video/video.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
#include <memory.h>
@ -173,7 +174,6 @@ enum
PROP_0,
PROP_MPD_FILENAME,
PROP_MPD_ROOT_PATH,
PROP_MAX_FILES,
PROP_TARGET_DURATION,
PROP_SEND_KEYFRAME_REQUESTS,
PROP_USE_SEGMENT_LIST,
@ -185,6 +185,15 @@ enum
PROP_MPD_PERIOD_DURATION,
};
enum
{
SIGNAL_GET_PLAYLIST_STREAM,
SIGNAL_GET_FRAGMENT_STREAM,
SIGNAL_LAST
};
static guint signals[SIGNAL_LAST];
typedef enum
{
DASH_SINK_STREAM_TYPE_VIDEO = 0,
@ -217,22 +226,6 @@ typedef union _GstDashSinkStreamInfo
GstDashSinkStreamSubtitleInfo subtitle;
} GstDashSinkStreamInfo;
typedef struct _GstDashSinkStream
{
GstDashSinkStreamType type;
GstPad *pad;
gint buffer_probe;
GstElement *splitmuxsink;
gint adaptation_set_id;
gchar *representation_id;
gchar *current_segment_location;
gchar *mimetype;
gint bitrate;
gchar *codec;
GstClockTime current_running_time_start;
GstDashSinkStreamInfo info;
} GstDashSinkStream;
struct _GstDashSink
{
GstBin bin;
@ -258,6 +251,26 @@ struct _GstDashSink
gint64 period_duration;
};
typedef struct _GstDashSinkStream
{
GstDashSink *sink;
GstDashSinkStreamType type;
GstPad *pad;
gint buffer_probe;
GstElement *splitmuxsink;
gint adaptation_set_id;
gchar *representation_id;
gchar *current_segment_location;
gint current_segment_id;
gint next_segment_id;
gchar *mimetype;
gint bitrate;
gchar *codec;
GstClockTime current_running_time_start;
GstDashSinkStreamInfo info;
GstElement *giostreamsink;
} GstDashSinkStream;
static GstStaticPadTemplate video_sink_template =
GST_STATIC_PAD_TEMPLATE ("video_%u",
GST_PAD_SINK,
@ -278,7 +291,7 @@ GST_STATIC_PAD_TEMPLATE ("subtitle_%u",
#define gst_dash_sink_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstDashSink, gst_dash_sink, GST_TYPE_BIN,
GST_DEBUG_CATEGORY_INIT (gst_dash_sink_debug, "dashsink", 0, "DashSink"););
GST_DEBUG_CATEGORY_INIT (gst_dash_sink_debug, "dashsink", 0, "DashSink"));
GST_ELEMENT_REGISTER_DEFINE (dashsink, "dashsink", GST_RANK_NONE,
gst_dash_sink_get_type ());
@ -322,9 +335,10 @@ gst_dash_sink_stream_from_splitmuxsink (GList * streams, GstElement * element)
}
static void
gst_dash_sink_stream_dispose (gpointer s)
gst_dash_sink_stream_free (gpointer s)
{
GstDashSinkStream *stream = (GstDashSinkStream *) s;
g_object_unref (stream->sink);
g_free (stream->current_segment_location);
g_free (stream->representation_id);
g_free (stream->mimetype);
@ -353,11 +367,56 @@ gst_dash_sink_finalize (GObject * object)
gst_mpd_client_free (sink->mpd_client);
g_mutex_clear (&sink->mpd_lock);
g_list_free_full (sink->streams, gst_dash_sink_stream_dispose);
g_list_free_full (sink->streams, gst_dash_sink_stream_free);
G_OBJECT_CLASS (parent_class)->finalize ((GObject *) sink);
}
/* Default implementations for the signal handlers */
static GOutputStream *
gst_dash_sink_get_playlist_stream (GstDashSink * sink, const gchar * location)
{
GFile *file = g_file_new_for_path (location);
GOutputStream *ostream;
GError *err = NULL;
ostream =
G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE,
G_FILE_CREATE_REPLACE_DESTINATION, NULL, &err));
if (!ostream) {
GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
(("Got no output stream for playlist '%s': %s."), location,
err->message), (NULL));
g_clear_error (&err);
}
g_object_unref (file);
return ostream;
}
static GOutputStream *
gst_dash_sink_get_fragment_stream (GstDashSink * sink, const gchar * location)
{
GFile *file = g_file_new_for_path (location);
GOutputStream *ostream;
GError *err = NULL;
ostream =
G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE,
G_FILE_CREATE_REPLACE_DESTINATION, NULL, &err));
if (!ostream) {
GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
(("Got no output stream for fragment '%s': %s."), location,
err->message), (NULL));
g_clear_error (&err);
}
g_object_unref (file);
return ostream;
}
static void
gst_dash_sink_class_init (GstDashSinkClass * klass)
{
@ -448,17 +507,92 @@ gst_dash_sink_class_init (GstDashSinkClass * klass)
G_MAXUINT64, DEFAULT_MPD_PERIOD_DURATION,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstDashSink::get-playlist-stream:
* @sink: the #GstDashSink
* @location: Location for the playlist file
*
* Returns: #GOutputStream for writing the playlist file.
*
* Since: 1.20
*/
signals[SIGNAL_GET_PLAYLIST_STREAM] =
g_signal_new_class_handler ("get-playlist-stream",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
G_CALLBACK (gst_dash_sink_get_playlist_stream), NULL, NULL, NULL,
G_TYPE_OUTPUT_STREAM, 1, G_TYPE_STRING);
/**
* GstDashSink::get-fragment-stream:
* @sink: the #GstDashSink
* @location: Location for the fragment file
*
* Returns: #GOutputStream for writing the fragment file.
*
* Since: 1.20
*/
signals[SIGNAL_GET_FRAGMENT_STREAM] =
g_signal_new_class_handler ("get-fragment-stream",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
G_CALLBACK (gst_dash_sink_get_fragment_stream), NULL, NULL, NULL,
G_TYPE_OUTPUT_STREAM, 1, G_TYPE_STRING);
gst_type_mark_as_plugin_api (GST_TYPE_DASH_SINK_MUXER, 0);
}
static gchar *
on_format_location (GstElement * splitmuxsink, guint fragment_id,
GstDashSinkStream * dash_stream)
{
GOutputStream *stream = NULL;
GstDashSink *sink = dash_stream->sink;
gchar *segment_tpl_path;
dash_stream->current_segment_id = dash_stream->next_segment_id;
g_free (dash_stream->current_segment_location);
if (sink->use_segment_list)
dash_stream->current_segment_location =
g_strdup_printf ("%s" DEFAULT_SEGMENT_LIST_TPL ".%s",
dash_stream->representation_id, dash_stream->current_segment_id,
dash_muxer_list[sink->muxer].file_ext);
else {
dash_stream->current_segment_location =
g_strdup_printf ("%s" DEFAULT_SEGMENT_TEMPLATE_TPL ".%s",
dash_stream->representation_id, dash_stream->current_segment_id,
dash_muxer_list[sink->muxer].file_ext);
}
dash_stream->next_segment_id++;
if (sink->mpd_root_path)
segment_tpl_path =
g_build_path (G_DIR_SEPARATOR_S, sink->mpd_root_path,
dash_stream->current_segment_location, NULL);
else
segment_tpl_path = g_strdup (dash_stream->current_segment_location);
g_signal_emit (sink, signals[SIGNAL_GET_FRAGMENT_STREAM], 0, segment_tpl_path,
&stream);
if (!stream)
GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
(("Got no output stream for fragment '%s'."), segment_tpl_path),
(NULL));
else
g_object_set (dash_stream->giostreamsink, "stream", stream, NULL);
if (stream)
g_object_unref (stream);
g_free (segment_tpl_path);
return NULL;
}
static gboolean
gst_dash_sink_add_splitmuxsink (GstDashSink * sink, GstDashSinkStream * stream)
{
GstElement *mux = NULL;
gchar *segment_tpl;
gchar *segment_tpl_path;
guint start_index = 0;
mux =
GstElement *mux =
gst_element_factory_make (dash_muxer_list[sink->muxer].element_name,
NULL);
@ -469,35 +603,33 @@ gst_dash_sink_add_splitmuxsink (GstDashSink * sink, GstDashSinkStream * stream)
g_return_val_if_fail (mux != NULL, FALSE);
stream->splitmuxsink = gst_element_factory_make ("splitmuxsink", NULL);
if (stream->splitmuxsink == NULL) {
if (!stream->splitmuxsink) {
gst_object_unref (mux);
return FALSE;
}
stream->giostreamsink = gst_element_factory_make ("giostreamsink", NULL);
if (!stream->giostreamsink) {
gst_object_unref (stream->splitmuxsink);
gst_object_unref (mux);
return FALSE;
}
gst_bin_add (GST_BIN (sink), stream->splitmuxsink);
if (sink->use_segment_list)
segment_tpl =
g_strconcat (stream->representation_id, DEFAULT_SEGMENT_LIST_TPL,
".", dash_muxer_list[sink->muxer].file_ext, NULL);
else {
segment_tpl =
g_strconcat (stream->representation_id, DEFAULT_SEGMENT_TEMPLATE_TPL,
".", dash_muxer_list[sink->muxer].file_ext, NULL);
start_index = 1;
}
if (sink->mpd_root_path)
segment_tpl_path =
g_build_path ("/", sink->mpd_root_path, segment_tpl, NULL);
else
segment_tpl_path = g_strdup (segment_tpl);
g_object_set (stream->splitmuxsink, "location", segment_tpl_path,
if (!sink->use_segment_list)
stream->current_segment_id = 1;
else
stream->current_segment_id = 0;
stream->next_segment_id = stream->current_segment_id;
g_object_set (stream->splitmuxsink, "location", NULL,
"max-size-time", ((GstClockTime) sink->target_duration * GST_SECOND),
"send-keyframe-requests", TRUE, "muxer", mux, "reset-muxer", FALSE,
"send-keyframe-requests", sink->send_keyframe_requests,
"start-index", start_index, NULL);
g_free (segment_tpl);
g_free (segment_tpl_path);
"send-keyframe-requests", TRUE, "muxer", mux, "sink",
stream->giostreamsink, "reset-muxer", FALSE, "send-keyframe-requests",
sink->send_keyframe_requests, NULL);
g_signal_connect (stream->splitmuxsink, "format-location",
G_CALLBACK (on_format_location), stream);
return TRUE;
}
@ -685,28 +817,44 @@ gst_dash_sink_write_mpd_file (GstDashSink * sink,
gint size;
GError *error = NULL;
gchar *mpd_filepath = NULL;
GOutputStream *file_stream = NULL;
gsize bytes_to_write;
g_mutex_lock (&sink->mpd_lock);
gst_dash_sink_generate_mpd_content (sink, current_stream);
if (!gst_mpd_client_get_xml_content (sink->mpd_client, &mpd_content, &size))
return;
g_mutex_unlock (&sink->mpd_lock);
if (sink->mpd_root_path)
mpd_filepath =
g_build_path ("/", sink->mpd_root_path, sink->mpd_filename, NULL);
g_build_path (G_DIR_SEPARATOR_S, sink->mpd_root_path,
sink->mpd_filename, NULL);
else
mpd_filepath = g_strdup (sink->mpd_filename);
GST_DEBUG_OBJECT (sink, "a new mpd content is available: %s", mpd_content);
GST_DEBUG_OBJECT (sink, "write mpd to %s", mpd_filepath);
if (!mpd_content
|| !g_file_set_contents (mpd_filepath, mpd_content, -1, &error)) {
g_signal_emit (sink, signals[SIGNAL_GET_PLAYLIST_STREAM], 0, mpd_filepath,
&file_stream);
if (!file_stream) {
GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
(("Failed to write mpd '%s'."), error->message), (NULL));
(("Got no output stream for fragment '%s'."), mpd_filepath), (NULL));
}
bytes_to_write = strlen (mpd_content);
if (!g_output_stream_write_all (file_stream, mpd_content, bytes_to_write,
NULL, NULL, &error)) {
GST_ERROR ("Failed to write mpd content: %s", error->message);
GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
(("Failed to write playlist '%s'."), error->message), (NULL));
g_error_free (error);
error = NULL;
}
g_free (mpd_content);
g_free (mpd_filepath);
g_object_unref (file_stream);
}
static void
@ -726,15 +874,10 @@ gst_dash_sink_handle_message (GstBin * bin, GstMessage * message)
if (stream) {
if (gst_structure_has_name (s, "splitmuxsink-fragment-opened")) {
gst_dash_sink_get_stream_metadata (sink, stream);
g_free (stream->current_segment_location);
stream->current_segment_location =
g_strdup (gst_structure_get_string (s, "location"));
gst_structure_get_clock_time (s, "running-time",
&stream->current_running_time_start);
} else if (gst_structure_has_name (s, "splitmuxsink-fragment-closed")) {
GstClockTime running_time;
g_assert (strcmp (stream->current_segment_location,
gst_structure_get_string (s, "location")) == 0);
gst_structure_get_clock_time (s, "running-time", &running_time);
if (sink->running_time < running_time)
sink->running_time = running_time;
@ -780,6 +923,7 @@ gst_dash_sink_request_new_pad (GstElement * element, GstPadTemplate * templ,
const gchar *split_pad_name = pad_name;
stream = g_new0 (GstDashSinkStream, 1);
stream->sink = g_object_ref (sink);
if (g_str_has_prefix (templ->name_template, "video")) {
stream->type = DASH_SINK_STREAM_TYPE_VIDEO;
stream->adaptation_set_id = ADAPTATION_SET_ID_VIDEO;
@ -800,7 +944,7 @@ gst_dash_sink_request_new_pad (GstElement * element, GstPadTemplate * templ,
GST_ERROR_OBJECT (sink,
"Unable to create splitmuxsink element for pad template name %s",
templ->name_template);
gst_dash_sink_stream_dispose (stream);
gst_dash_sink_stream_free (stream);
goto done;
}