diff --git a/examples/meson.build b/examples/meson.build index 2bacd48274..9352f4ea58 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -15,6 +15,7 @@ examples = [ 'test-readme', 'test-record-auth', 'test-record', + 'test-replay-server', 'test-sdp', 'test-uri', 'test-video', diff --git a/examples/test-replay-server.c b/examples/test-replay-server.c new file mode 100644 index 0000000000..493ca7ede5 --- /dev/null +++ b/examples/test-replay-server.c @@ -0,0 +1,931 @@ +/* GStreamer + * Copyright (C) 2019 Mathieu Duponchelle + * Copyright (C) 2020 Seungha Yang + * + * 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. + */ + + +#include + +#include + +#include "test-replay-server.h" + +GST_DEBUG_CATEGORY_STATIC (replay_server_debug); +#define GST_CAT_DEFAULT (replay_server_debug) + +static GstStaticCaps raw_video_caps = GST_STATIC_CAPS ("video/x-raw"); +static GstStaticCaps raw_audio_caps = GST_STATIC_CAPS ("audio/x-raw"); + +static GList + * gst_rtsp_media_factory_replay_get_demuxers (GstRTSPMediaFactoryReplay * + factory); +static GList + * gst_rtsp_media_factory_replay_get_payloaders (GstRTSPMediaFactoryReplay * + factory); +static GList + * gst_rtsp_media_factory_replay_get_decoders (GstRTSPMediaFactoryReplay * + factory); + +typedef struct +{ + GstPad *srcpad; + gulong block_id; +} GstReplayBinPad; + +static void +gst_replay_bin_pad_unblock_and_free (GstReplayBinPad * pad) +{ + if (pad->srcpad && pad->block_id) { + GST_DEBUG_OBJECT (pad->srcpad, "Unblock"); + gst_pad_remove_probe (pad->srcpad, pad->block_id); + pad->block_id = 0; + } + + gst_clear_object (&pad->srcpad); + g_free (pad); +} + +/* NOTE: this bin implementation is almost completely taken from rtsp-media-factory-uri + * but this example doesn't use the GstRTSPMediaFactoryURI object so that + * we can handle events and messages ourselves. + * Specifically, + * - Handle segment-done message for looping given source + * - Drop all incoming seek event because client seek is not implemented + * and do initial segment seeking on no-more-pads signal + */ +struct _GstReplayBin +{ + GstBin parent; + + gint64 num_loops; + + GstCaps *raw_vcaps; + GstCaps *raw_acaps; + + guint pt; + + /* without ref */ + GstElement *uridecodebin; + GstElement *inner_bin; + + /* holds ref */ + GstRTSPMediaFactoryReplay *factory; + + GMutex lock; + + GList *srcpads; +}; + +static void gst_replay_bin_dispose (GObject * object); +static void gst_replay_bin_finalize (GObject * object); +static void gst_replay_bin_handle_message (GstBin * bin, GstMessage * message); + +static gboolean autoplug_continue_cb (GstElement * dbin, GstPad * pad, + GstCaps * caps, GstReplayBin * self); +static void pad_added_cb (GstElement * dbin, GstPad * pad, GstReplayBin * self); +static void no_more_pads_cb (GstElement * uribin, GstReplayBin * self); + +#define gst_replay_bin_parent_class bin_parent_class +G_DEFINE_TYPE (GstReplayBin, gst_replay_bin, GST_TYPE_BIN); + +static void +gst_replay_bin_class_init (GstReplayBinClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstBinClass *bin_class = GST_BIN_CLASS (klass); + + gobject_class->dispose = gst_replay_bin_dispose; + gobject_class->finalize = gst_replay_bin_finalize; + + bin_class->handle_message = GST_DEBUG_FUNCPTR (gst_replay_bin_handle_message); +} + +static void +gst_replay_bin_init (GstReplayBin * self) +{ + self->raw_vcaps = gst_static_caps_get (&raw_video_caps); + self->raw_acaps = gst_static_caps_get (&raw_audio_caps); + + self->uridecodebin = gst_element_factory_make ("uridecodebin", NULL); + if (!self->uridecodebin) { + GST_ERROR_OBJECT (self, "uridecodebin is unavailable"); + return; + } + + /* our bin will dynamically expose payloaded pads */ + self->inner_bin = gst_bin_new ("dynpay0"); + gst_bin_add (GST_BIN_CAST (self), self->inner_bin); + gst_bin_add (GST_BIN_CAST (self->inner_bin), self->uridecodebin); + + g_signal_connect (self->uridecodebin, "autoplug-continue", + G_CALLBACK (autoplug_continue_cb), self); + g_signal_connect (self->uridecodebin, "pad-added", + G_CALLBACK (pad_added_cb), self); + g_signal_connect (self->uridecodebin, "no-more-pads", + G_CALLBACK (no_more_pads_cb), self); + + self->pt = 96; + + g_mutex_init (&self->lock); +} + +static void +gst_replay_bin_dispose (GObject * object) +{ + GstReplayBin *self = GST_REPLAY_BIN (object); + + GST_DEBUG_OBJECT (self, "dispose"); + + gst_clear_caps (&self->raw_vcaps); + gst_clear_caps (&self->raw_acaps); + gst_clear_object (&self->factory); + + if (self->srcpads) { + g_list_free_full (self->srcpads, + (GDestroyNotify) gst_replay_bin_pad_unblock_and_free); + self->srcpads = NULL; + } + + G_OBJECT_CLASS (bin_parent_class)->dispose (object); +} + +static void +gst_replay_bin_finalize (GObject * object) +{ + GstReplayBin *self = GST_REPLAY_BIN (object); + + g_mutex_clear (&self->lock); + + G_OBJECT_CLASS (bin_parent_class)->finalize (object); +} + +static gboolean +send_eos_foreach_srcpad (GstElement * element, GstPad * pad, gpointer user_data) +{ + GST_DEBUG_OBJECT (pad, "Sending EOS to downstream"); + gst_pad_push_event (pad, gst_event_new_eos ()); + + return TRUE; +} + +static void +gst_replay_bin_do_segment_seek (GstElement * element, GstReplayBin * self) +{ + gboolean ret; + + ret = gst_element_seek (element, 1.0, GST_FORMAT_TIME, + GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_SEGMENT, + GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, -1); + + if (!ret) { + GST_WARNING_OBJECT (self, "segment seeking failed"); + gst_element_foreach_src_pad (element, + (GstElementForeachPadFunc) send_eos_foreach_srcpad, NULL); + } +} + +static void +gst_replay_bin_handle_message (GstBin * bin, GstMessage * message) +{ + GstReplayBin *self = GST_REPLAY_BIN (bin); + + if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_SEGMENT_DONE) { + gboolean next_loop = TRUE; + + GST_DEBUG_OBJECT (self, "Have segment done message"); + + g_mutex_lock (&self->lock); + if (self->num_loops != -1) { + self->num_loops--; + + if (self->num_loops < 1) + next_loop = FALSE; + } + + if (next_loop) { + /* Send seek event from non-streaming thread */ + gst_element_call_async (GST_ELEMENT_CAST (self->uridecodebin), + (GstElementCallAsyncFunc) gst_replay_bin_do_segment_seek, self, NULL); + } else { + gst_element_foreach_src_pad (GST_ELEMENT_CAST (self->uridecodebin), + (GstElementForeachPadFunc) send_eos_foreach_srcpad, NULL); + } + + g_mutex_unlock (&self->lock); + } + + GST_BIN_CLASS (bin_parent_class)->handle_message (bin, message); +} + +static GstElementFactory * +find_payloader (GstReplayBin * self, GstCaps * caps) +{ + GList *list; + GstElementFactory *factory = NULL; + gboolean autoplug_more = FALSE; + GList *demuxers = NULL; + GList *payloaders = NULL; + + demuxers = gst_rtsp_media_factory_replay_get_demuxers (self->factory); + + /* first find a demuxer that can link */ + list = gst_element_factory_list_filter (demuxers, caps, GST_PAD_SINK, FALSE); + + if (list) { + GstStructure *structure = gst_caps_get_structure (caps, 0); + gboolean parsed = FALSE; + gint mpegversion = 0; + + if (!gst_structure_get_boolean (structure, "parsed", &parsed) && + gst_structure_has_name (structure, "audio/mpeg") && + gst_structure_get_int (structure, "mpegversion", &mpegversion) && + (mpegversion == 2 || mpegversion == 4)) { + /* for AAC it's framed=true instead of parsed=true */ + gst_structure_get_boolean (structure, "framed", &parsed); + } + + /* Avoid plugging parsers in a loop. This is not 100% correct, as some + * parsers don't set parsed=true in caps. We should do something like + * decodebin does and track decode chains and elements plugged in those + * chains... + */ + if (parsed) { + GList *walk; + const gchar *klass; + + for (walk = list; walk; walk = walk->next) { + factory = GST_ELEMENT_FACTORY (walk->data); + klass = gst_element_factory_get_metadata (factory, + GST_ELEMENT_METADATA_KLASS); + if (strstr (klass, "Parser")) + /* caps have parsed=true, so skip this parser to avoid loops */ + continue; + + autoplug_more = TRUE; + break; + } + } else { + /* caps don't have parsed=true set and we have a demuxer/parser */ + autoplug_more = TRUE; + } + + gst_plugin_feature_list_free (list); + } + + if (autoplug_more) + /* we have a demuxer, try that one first */ + return NULL; + + payloaders = gst_rtsp_media_factory_replay_get_payloaders (self->factory); + + /* no demuxer try a depayloader */ + list = gst_element_factory_list_filter (payloaders, + caps, GST_PAD_SINK, FALSE); + + if (list == NULL) { + GList *decoders = + gst_rtsp_media_factory_replay_get_decoders (self->factory); + /* no depayloader, try a decoder, we'll get to a payloader for a decoded + * video or audio format, worst case. */ + list = gst_element_factory_list_filter (decoders, + caps, GST_PAD_SINK, FALSE); + + if (list != NULL) { + /* we have a decoder, try that one first */ + gst_plugin_feature_list_free (list); + return NULL; + } + } + + if (list != NULL) { + factory = GST_ELEMENT_FACTORY_CAST (list->data); + g_object_ref (factory); + gst_plugin_feature_list_free (list); + } + + return factory; +} + +static gboolean +autoplug_continue_cb (GstElement * dbin, GstPad * pad, GstCaps * caps, + GstReplayBin * self) +{ + GstElementFactory *factory; + + GST_DEBUG_OBJECT (self, "found pad %s:%s of caps %" GST_PTR_FORMAT, + GST_DEBUG_PAD_NAME (pad), caps); + + if (!(factory = find_payloader (self, caps))) + goto no_factory; + + /* we found a payloader, stop autoplugging so we can plug the + * payloader. */ + GST_DEBUG_OBJECT (self, "found factory %s", + gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory))); + gst_object_unref (factory); + + return FALSE; + +no_factory: + { + /* no payloader, continue autoplugging */ + GST_DEBUG_OBJECT (self, "no payloader found for caps %" GST_PTR_FORMAT, + caps); + return TRUE; + } +} + +static GstPadProbeReturn +replay_bin_sink_probe (GstPad * pad, GstPadProbeInfo * info, + GstReplayBin * self) +{ + GstPadProbeReturn ret = GST_PAD_PROBE_OK; + + if (GST_IS_EVENT (GST_PAD_PROBE_INFO_DATA (info))) { + GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + /* Ideally this shouldn't happen because we are responsing + * seeking query with non-seekable */ + GST_DEBUG_OBJECT (pad, "Drop seek event"); + ret = GST_PAD_PROBE_DROP; + break; + default: + break; + } + } else if (GST_IS_QUERY (GST_PAD_PROBE_INFO_DATA (info))) { + GstQuery *query = GST_PAD_PROBE_INFO_QUERY (info); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_SEEKING: + { + /* FIXME: client seek is not implemented */ + gst_query_set_seeking (query, GST_FORMAT_TIME, FALSE, 0, + GST_CLOCK_TIME_NONE); + ret = GST_PAD_PROBE_HANDLED; + break; + } + case GST_QUERY_SEGMENT: + /* client seeking is not considered in here */ + gst_query_set_segment (query, + 1.0, GST_FORMAT_TIME, 0, GST_CLOCK_TIME_NONE); + ret = GST_PAD_PROBE_HANDLED; + break; + default: + break; + } + } + + return ret; +} + +static GstPadProbeReturn +replay_bin_src_block (GstPad * pad, GstPadProbeInfo * info, GstReplayBin * self) +{ + GST_DEBUG_OBJECT (pad, "Block pad"); + + return GST_PAD_PROBE_OK; +} + +static void +pad_added_cb (GstElement * dbin, GstPad * pad, GstReplayBin * self) +{ + GstElementFactory *factory; + GstElement *payloader; + GstCaps *caps; + GstPad *sinkpad, *srcpad, *ghostpad; + GstPad *dpad = pad; + GstElement *convert; + gchar *padname, *payloader_name; + GstElement *inner_bin = self->inner_bin; + GstReplayBinPad *bin_pad; + + GST_DEBUG_OBJECT (self, "added pad %s:%s", GST_DEBUG_PAD_NAME (pad)); + + /* ref to make refcounting easier later */ + gst_object_ref (pad); + padname = gst_pad_get_name (pad); + + /* get pad caps first, then call get_caps, then fail */ + if ((caps = gst_pad_get_current_caps (pad)) == NULL) + if ((caps = gst_pad_query_caps (pad, NULL)) == NULL) + goto no_caps; + + /* check for raw caps */ + if (gst_caps_can_intersect (caps, self->raw_vcaps)) { + /* we have raw video caps, insert converter */ + convert = gst_element_factory_make ("videoconvert", NULL); + } else if (gst_caps_can_intersect (caps, self->raw_acaps)) { + /* we have raw audio caps, insert converter */ + convert = gst_element_factory_make ("audioconvert", NULL); + } else { + convert = NULL; + } + + if (convert) { + gst_bin_add (GST_BIN_CAST (inner_bin), convert); + gst_element_sync_state_with_parent (convert); + + sinkpad = gst_element_get_static_pad (convert, "sink"); + gst_pad_link (pad, sinkpad); + gst_object_unref (sinkpad); + + /* unref old pad, we reffed before */ + gst_object_unref (pad); + + /* continue with new pad and caps */ + pad = gst_element_get_static_pad (convert, "src"); + if ((caps = gst_pad_get_current_caps (pad)) == NULL) + if ((caps = gst_pad_query_caps (pad, NULL)) == NULL) + goto no_caps; + } + + if (!(factory = find_payloader (self, caps))) + goto no_factory; + + gst_caps_unref (caps); + + /* we have a payloader now */ + GST_DEBUG_OBJECT (self, "found payloader factory %s", + gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory))); + + payloader_name = g_strdup_printf ("pay_%s", padname); + payloader = gst_element_factory_create (factory, payloader_name); + g_free (payloader_name); + if (payloader == NULL) + goto no_payloader; + + g_object_set (payloader, "pt", self->pt, NULL); + self->pt++; + + if (g_object_class_find_property (G_OBJECT_GET_CLASS (payloader), + "buffer-list")) + g_object_set (payloader, "buffer-list", TRUE, NULL); + + /* add the payloader to the pipeline */ + gst_bin_add (GST_BIN_CAST (inner_bin), payloader); + gst_element_sync_state_with_parent (payloader); + + /* link the pad to the sinkpad of the payloader */ + sinkpad = gst_element_get_static_pad (payloader, "sink"); + gst_pad_link (pad, sinkpad); + gst_object_unref (pad); + + /* Add pad probe to handle events */ + gst_pad_add_probe (sinkpad, + GST_PAD_PROBE_TYPE_EVENT_UPSTREAM | GST_PAD_PROBE_TYPE_QUERY_UPSTREAM, + (GstPadProbeCallback) replay_bin_sink_probe, self, NULL); + gst_object_unref (sinkpad); + + /* block data for initial segment seeking */ + bin_pad = g_new0 (GstReplayBinPad, 1); + + /* Move owenership of pad to this struct */ + bin_pad->srcpad = gst_object_ref (dpad); + bin_pad->block_id = + gst_pad_add_probe (dpad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, + (GstPadProbeCallback) replay_bin_src_block, self, NULL); + g_mutex_lock (&self->lock); + self->srcpads = g_list_append (self->srcpads, bin_pad); + g_mutex_unlock (&self->lock); + + /* now expose the srcpad of the payloader as a ghostpad with the same name + * as the uridecodebin pad name. */ + srcpad = gst_element_get_static_pad (payloader, "src"); + ghostpad = gst_ghost_pad_new (padname, srcpad); + gst_object_unref (srcpad); + g_free (padname); + + gst_pad_set_active (ghostpad, TRUE); + gst_element_add_pad (inner_bin, ghostpad); + + return; + + /* ERRORS */ +no_caps: + { + GST_WARNING ("could not get caps from pad"); + g_free (padname); + gst_object_unref (pad); + return; + } +no_factory: + { + GST_DEBUG ("no payloader found"); + g_free (padname); + gst_caps_unref (caps); + gst_object_unref (pad); + return; + } +no_payloader: + { + GST_ERROR ("could not create payloader from factory"); + g_free (padname); + gst_caps_unref (caps); + gst_object_unref (pad); + return; + } +} + +static void +gst_replay_bin_do_initial_segment_seek (GstElement * element, + GstReplayBin * self) +{ + gboolean ret; + GstQuery *query; + gboolean seekable; + + query = gst_query_new_seeking (GST_FORMAT_TIME); + ret = gst_element_query (element, query); + + if (!ret) { + GST_WARNING_OBJECT (self, "Cannot query seeking"); + gst_query_unref (query); + goto done; + } + + gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL); + gst_query_unref (query); + + if (!seekable) { + GST_WARNING_OBJECT (self, "Source is not seekable"); + ret = FALSE; + goto done; + } + + ret = gst_element_seek (element, 1.0, GST_FORMAT_TIME, + GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_SEGMENT | GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, -1); + + if (!ret) + GST_WARNING_OBJECT (self, "segment seeking failed"); + +done: + /* Unblock all pads then */ + g_mutex_lock (&self->lock); + if (self->srcpads) { + g_list_free_full (self->srcpads, + (GDestroyNotify) gst_replay_bin_pad_unblock_and_free); + self->srcpads = NULL; + } + g_mutex_unlock (&self->lock); + + if (!ret) { + GST_WARNING_OBJECT (self, "Sending eos to all pads"); + gst_element_foreach_src_pad (element, + (GstElementForeachPadFunc) send_eos_foreach_srcpad, NULL); + } +} + +static void +no_more_pads_cb (GstElement * uribin, GstReplayBin * self) +{ + GST_DEBUG_OBJECT (self, "no-more-pads"); + gst_element_no_more_pads (GST_ELEMENT_CAST (self->inner_bin)); + + /* Flush seeking from streaming thread might not be good idea. + * Do this from another (non-streaming) thread */ + gst_element_call_async (GST_ELEMENT_CAST (self->uridecodebin), + (GstElementCallAsyncFunc) gst_replay_bin_do_initial_segment_seek, + self, NULL); +} + +static GstElement * +gst_replay_bin_new (const gchar * uri, gint64 num_loops, + GstRTSPMediaFactoryReplay * factory, const gchar * name) +{ + GstReplayBin *self; + + g_return_val_if_fail (uri != NULL, NULL); + g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), NULL); + + if (!name) + name = "GstRelayBin"; + + self = GST_REPLAY_BIN (g_object_new (GST_TYPE_REPLAY_BIN, + "name", name, NULL)); + + if (!self->uridecodebin) { + gst_object_unref (self); + return NULL; + } + + g_object_set (self->uridecodebin, "uri", uri, NULL); + self->factory = g_object_ref (factory); + self->num_loops = num_loops; + + return GST_ELEMENT_CAST (self); +} + +struct _GstRTSPMediaFactoryReplay +{ + GstRTSPMediaFactory parent; + + gchar *uri; + + GList *demuxers; + GList *payloaders; + GList *decoders; + + gint64 num_loops; +}; + +enum +{ + PROP_0, + PROP_URI, + PROP_NUM_LOOPS, +}; + +#define DEFAULT_NUM_LOOPS (-1) + +static void gst_rtsp_media_factory_replay_get_property (GObject * object, + guint propid, GValue * value, GParamSpec * pspec); +static void gst_rtsp_media_factory_replay_set_property (GObject * object, + guint propid, const GValue * value, GParamSpec * pspec); +static void gst_rtsp_media_factory_replay_finalize (GObject * object); + +static GstElement + * gst_rtsp_media_factory_replay_create_element (GstRTSPMediaFactory * + factory, const GstRTSPUrl * url); + +typedef struct +{ + GList *demux; + GList *payload; + GList *decode; +} FilterData; + +static gboolean +payloader_filter (GstPluginFeature * feature, FilterData * self); + +#define gst_rtsp_media_factory_replay_parent_class parent_class +G_DEFINE_TYPE (GstRTSPMediaFactoryReplay, + gst_rtsp_media_factory_replay, GST_TYPE_RTSP_MEDIA_FACTORY); + +static void +gst_rtsp_media_factory_replay_class_init (GstRTSPMediaFactoryReplayClass + * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstRTSPMediaFactoryClass *mf_class = GST_RTSP_MEDIA_FACTORY_CLASS (klass); + + gobject_class->get_property = gst_rtsp_media_factory_replay_get_property; + gobject_class->set_property = gst_rtsp_media_factory_replay_set_property; + gobject_class->finalize = gst_rtsp_media_factory_replay_finalize; + + g_object_class_install_property (gobject_class, PROP_URI, + g_param_spec_string ("uri", "URI", + "The URI of the resource to stream", NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_NUM_LOOPS, + g_param_spec_int64 ("num-loops", "Num Loops", + "The number of loops (-1 = infiniate)", -1, G_MAXINT64, + DEFAULT_NUM_LOOPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + mf_class->create_element = + GST_DEBUG_FUNCPTR (gst_rtsp_media_factory_replay_create_element); +} + +static void +gst_rtsp_media_factory_replay_init (GstRTSPMediaFactoryReplay * self) +{ + FilterData data = { NULL, }; + + /* get the feature list using the filter */ + gst_registry_feature_filter (gst_registry_get (), (GstPluginFeatureFilter) + payloader_filter, FALSE, &data); + + /* sort */ + self->demuxers = + g_list_sort (data.demux, gst_plugin_feature_rank_compare_func); + self->payloaders = + g_list_sort (data.payload, gst_plugin_feature_rank_compare_func); + self->decoders = + g_list_sort (data.decode, gst_plugin_feature_rank_compare_func); + + self->num_loops = DEFAULT_NUM_LOOPS; +} + +static void +gst_rtsp_media_factory_replay_get_property (GObject * object, guint propid, + GValue * value, GParamSpec * pspec) +{ + GstRTSPMediaFactoryReplay *self = GST_RTSP_MEDIA_FACTORY_REPLAY (object); + + switch (propid) { + case PROP_URI: + g_value_take_string (value, self->uri); + break; + case PROP_NUM_LOOPS: + g_value_set_int64 (value, self->num_loops); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); + } +} + +static void +gst_rtsp_media_factory_replay_set_property (GObject * object, guint propid, + const GValue * value, GParamSpec * pspec) +{ + GstRTSPMediaFactoryReplay *self = GST_RTSP_MEDIA_FACTORY_REPLAY (object); + + switch (propid) { + case PROP_URI: + g_free (self->uri); + self->uri = g_value_dup_string (value); + break; + case PROP_NUM_LOOPS: + self->num_loops = g_value_get_int64 (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); + } +} + +static void +gst_rtsp_media_factory_replay_finalize (GObject * object) +{ + GstRTSPMediaFactoryReplay *self = GST_RTSP_MEDIA_FACTORY_REPLAY (object); + + g_free (self->uri); + + gst_plugin_feature_list_free (self->demuxers); + gst_plugin_feature_list_free (self->payloaders); + gst_plugin_feature_list_free (self->decoders); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static GstElement * +gst_rtsp_media_factory_replay_create_element (GstRTSPMediaFactory * factory, + const GstRTSPUrl * url) +{ + GstRTSPMediaFactoryReplay *self = GST_RTSP_MEDIA_FACTORY_REPLAY (factory); + + return gst_replay_bin_new (self->uri, self->num_loops, self, + "GstRTSPMediaFactoryReplay"); +} + +static gboolean +payloader_filter (GstPluginFeature * feature, FilterData * data) +{ + const gchar *klass; + GstElementFactory *fact; + GList **list = NULL; + + /* we only care about element factories */ + if (G_UNLIKELY (!GST_IS_ELEMENT_FACTORY (feature))) + return FALSE; + + if (gst_plugin_feature_get_rank (feature) < GST_RANK_MARGINAL) + return FALSE; + + fact = GST_ELEMENT_FACTORY_CAST (feature); + + klass = gst_element_factory_get_metadata (fact, GST_ELEMENT_METADATA_KLASS); + + if (strstr (klass, "Decoder")) + list = &data->decode; + else if (strstr (klass, "Demux")) + list = &data->demux; + else if (strstr (klass, "Parser") && strstr (klass, "Codec")) + list = &data->demux; + else if (strstr (klass, "Payloader") && strstr (klass, "RTP")) + list = &data->payload; + + if (list) { + GST_LOG ("adding %s", GST_OBJECT_NAME (fact)); + *list = g_list_prepend (*list, gst_object_ref (fact)); + } + + return FALSE; +} + +static GList * +gst_rtsp_media_factory_replay_get_demuxers (GstRTSPMediaFactoryReplay * factory) +{ + return factory->demuxers; +} + +static GList * +gst_rtsp_media_factory_replay_get_payloaders (GstRTSPMediaFactoryReplay * + factory) +{ + return factory->payloaders; +} + +static GList * +gst_rtsp_media_factory_replay_get_decoders (GstRTSPMediaFactoryReplay * factory) +{ + return factory->decoders; +} + +static GstRTSPMediaFactory * +gst_rtsp_media_factory_replay_new (const gchar * uri, gint64 num_loops) +{ + GstRTSPMediaFactory *factory; + + factory = + GST_RTSP_MEDIA_FACTORY (g_object_new + (GST_TYPE_RTSP_MEDIA_FACTORY_REPLAY, "uri", uri, "num-loops", num_loops, + NULL)); + + return factory; +} + +int +main (int argc, char *argv[]) +{ + GMainLoop *loop; + GstRTSPServer *server; + GstRTSPMountPoints *mounts; + GstRTSPMediaFactory *factory; + GOptionContext *optctx; + GError *error = NULL; + gchar *service; + gchar *uri = NULL; + gint64 num_loops = -1; + GOptionEntry options[] = { + {"num-loops", 0, 0, G_OPTION_ARG_INT64, &num_loops, + "The number of loops (default = -1, infinate)", NULL}, + {NULL} + }; + + optctx = g_option_context_new ("RTSP Replay Server"); + g_option_context_add_main_entries (optctx, options, NULL); + g_option_context_add_group (optctx, gst_init_get_option_group ()); + if (!g_option_context_parse (optctx, &argc, &argv, &error)) { + g_printerr ("Error parsing options: %s\n", error->message); + g_option_context_free (optctx); + g_clear_error (&error); + return -1; + } + if (argc < 2) { + g_print ("%s\n", g_option_context_get_help (optctx, TRUE, NULL)); + return 1; + } + + g_option_context_free (optctx); + + /* check if URI is valid, otherwise convert filename to URI if it's a file */ + if (gst_uri_is_valid (argv[1])) { + uri = g_strdup (argv[1]); + } else if (g_file_test (argv[1], G_FILE_TEST_EXISTS)) { + uri = gst_filename_to_uri (argv[1], NULL); + } else { + g_printerr ("Unrecognised command line argument '%s'.\n" + "Please pass an URI or file as argument!\n", argv[1]); + return -1; + } + + if (num_loops < -1 || num_loops == 0) { + g_printerr ("num-loop should be non-zero or -1"); + return -1; + } + + GST_DEBUG_CATEGORY_INIT (replay_server_debug, "replay-server", 0, + "RTSP replay server"); + + if (num_loops != -1) + g_print ("Run loop %" G_GINT64_FORMAT " times\n", num_loops); + + loop = g_main_loop_new (NULL, FALSE); + + server = gst_rtsp_server_new (); + + mounts = gst_rtsp_server_get_mount_points (server); + factory = gst_rtsp_media_factory_replay_new (uri, num_loops); + g_free (uri); + + gst_rtsp_mount_points_add_factory (mounts, "/test", factory); + + g_object_unref (mounts); + + gst_rtsp_server_attach (server, NULL); + + service = gst_rtsp_server_get_service (server); + g_print ("stream ready at rtsp://127.0.0.1:%s/test\n", service); + g_free (service); + g_main_loop_run (loop); + + return 0; +} diff --git a/examples/test-replay-server.h b/examples/test-replay-server.h new file mode 100644 index 0000000000..1204775fbe --- /dev/null +++ b/examples/test-replay-server.h @@ -0,0 +1,36 @@ +/* GStreamer + * Copyright (C) 2019 Mathieu Duponchelle + * Copyright (C) 2020 Seungha Yang + * + * 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. + */ + + +#include + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_REPLAY_BIN (gst_replay_bin_get_type ()) +G_DECLARE_FINAL_TYPE (GstReplayBin, gst_replay_bin, GST, REPLAY_BIN, GstBin); + +#define GST_TYPE_RTSP_MEDIA_FACTORY_REPLAY (gst_rtsp_media_factory_replay_get_type ()) +G_DECLARE_FINAL_TYPE (GstRTSPMediaFactoryReplay, + gst_rtsp_media_factory_replay, GST, RTSP_MEDIA_FACTORY_REPLAY, + GstRTSPMediaFactory); + +G_END_DECLS