/* Copyright (C) 2016-2017 Sebastian Dröge * * Licensed under the Apache License, Version 2.0 or the MIT license * , at your * option. This file may not be copied, modified, or distributed * except according to those terms. */ #include "demuxer.h" #include #include typedef struct { gchar *long_name; gchar *description; gchar *classification; gchar *author; void *create_instance; GstCaps *input_caps; GstCaps *output_caps; } ElementData; static GHashTable *demuxers; /* Declarations for Rust code */ extern gboolean demuxers_register (void *plugin); extern void *demuxer_new (GstRsDemuxer * demuxer, void *create_instance); extern void demuxer_drop (void *rsdemuxer); extern gboolean demuxer_start (void *rsdemuxer, uint64_t upstream_size, gboolean random_access); extern gboolean demuxer_stop (void *rsdemuxer); extern gboolean demuxer_is_seekable (void *rsdemuxer); extern gboolean demuxer_get_position (void *rsdemuxer, uint64_t * position); extern gboolean demuxer_get_duration (void *rsdemuxer, uint64_t * duration); extern gboolean demuxer_seek (void *rsdemuxer, uint64_t start, uint64_t stop, uint64_t * offset); extern GstFlowReturn demuxer_handle_buffer (void *rsdemuxer, GstBuffer * buffer); extern void demuxer_end_of_stream (void *rsdemuxer); extern void cstring_drop (void *str); GST_DEBUG_CATEGORY_STATIC (gst_rs_demuxer_debug); #define GST_CAT_DEFAULT gst_rs_demuxer_debug static void gst_rs_demuxer_finalize (GObject * object); static gboolean gst_rs_demuxer_sink_activate (GstPad * pad, GstObject * parent); static gboolean gst_rs_demuxer_sink_activate_mode (GstPad * pad, GstObject * parent, GstPadMode mode, gboolean active); static GstFlowReturn gst_rs_demuxer_sink_chain (GstPad * pad, GstObject * parent, GstBuffer * buf); static gboolean gst_rs_demuxer_sink_event (GstPad * pad, GstObject * parent, GstEvent * event); static gboolean gst_rs_demuxer_src_query (GstPad * pad, GstObject * parent, GstQuery * query); static gboolean gst_rs_demuxer_src_event (GstPad * pad, GstObject * parent, GstEvent * event); static GstStateChangeReturn gst_rs_demuxer_change_state (GstElement * element, GstStateChange transition); static void gst_rs_demuxer_loop (GstRsDemuxer * demuxer); static GObjectClass *parent_class; static void gst_rs_demuxer_class_init (GstRsDemuxerClass * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; ElementData *data = g_hash_table_lookup (demuxers, GSIZE_TO_POINTER (G_TYPE_FROM_CLASS (klass))); GstPadTemplate *templ; g_assert (data != NULL); gobject_class = G_OBJECT_CLASS (klass); gstelement_class = GST_ELEMENT_CLASS (klass); gobject_class->finalize = gst_rs_demuxer_finalize; gstelement_class->change_state = gst_rs_demuxer_change_state; gst_element_class_set_static_metadata (gstelement_class, data->long_name, data->classification, data->description, data->author); templ = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, data->input_caps); gst_element_class_add_pad_template (gstelement_class, templ); templ = gst_pad_template_new ("src_%u", GST_PAD_SRC, GST_PAD_SOMETIMES, data->output_caps); gst_element_class_add_pad_template (gstelement_class, templ); } static void gst_rs_demuxer_init (GstRsDemuxer * demuxer, GstRsDemuxerClass * klass) { ElementData *data = g_hash_table_lookup (demuxers, GSIZE_TO_POINTER (G_TYPE_FROM_CLASS (klass))); GstPadTemplate *templ; g_assert (data != NULL); demuxer->instance = demuxer_new (demuxer, data->create_instance); templ = gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), "sink"); demuxer->sinkpad = gst_pad_new_from_template (templ, "sink"); gst_pad_set_activate_function (demuxer->sinkpad, gst_rs_demuxer_sink_activate); gst_pad_set_activatemode_function (demuxer->sinkpad, gst_rs_demuxer_sink_activate_mode); gst_pad_set_chain_function (demuxer->sinkpad, gst_rs_demuxer_sink_chain); gst_pad_set_event_function (demuxer->sinkpad, gst_rs_demuxer_sink_event); gst_element_add_pad (GST_ELEMENT (demuxer), demuxer->sinkpad); demuxer->flow_combiner = gst_flow_combiner_new (); GST_DEBUG_OBJECT (demuxer, "Instantiating"); } static void gst_rs_demuxer_finalize (GObject * object) { GstRsDemuxer *demuxer = GST_RS_DEMUXER (object); GST_DEBUG_OBJECT (demuxer, "Finalizing"); gst_flow_combiner_free (demuxer->flow_combiner); demuxer_drop (demuxer->instance); G_OBJECT_CLASS (parent_class)->finalize (object); } static gboolean gst_rs_demuxer_sink_activate (GstPad * pad, GstObject * parent) { GstRsDemuxer *demuxer = GST_RS_DEMUXER (parent); GstQuery *query; GstPadMode mode = GST_PAD_MODE_PUSH; query = gst_query_new_scheduling (); if (!gst_pad_peer_query (pad, query)) { gst_query_unref (query); return FALSE; } // TODO //if (gst_query_has_scheduling_mode_with_flags (query, GST_PAD_MODE_PULL, GST_SCHEDULING_FLAG_SEEKABLE)) { // GST_DEBUG_OBJECT (demuxer, "Activating in PULL mode"); // mode = GST_PAD_MODE_PULL; //} else { //GST_DEBUG_OBJECT (demuxer, "Activating in PUSH mode"); //} gst_query_unref (query); demuxer->upstream_size = -1; query = gst_query_new_duration (GST_FORMAT_BYTES); if (gst_pad_peer_query (pad, query)) { gst_query_parse_duration (query, NULL, &demuxer->upstream_size); } gst_query_unref (query); return gst_pad_activate_mode (pad, mode, TRUE); } static gboolean gst_rs_demuxer_sink_activate_mode (GstPad * pad, GstObject * parent, GstPadMode mode, gboolean active) { GstRsDemuxer *demuxer = GST_RS_DEMUXER (parent); gboolean res = TRUE; GST_DEBUG_OBJECT (demuxer, "%s pad in %s mode", (active ? "Activating" : "Deactivating"), gst_pad_mode_get_name (mode)); if (active) { GST_DEBUG_OBJECT (demuxer, "Starting"); if (!demuxer_start (demuxer->instance, demuxer->upstream_size, mode == GST_PAD_MODE_PULL ? TRUE : FALSE)) { res = FALSE; goto out; } if (mode == GST_PAD_MODE_PULL) gst_pad_start_task (pad, (GstTaskFunction) gst_rs_demuxer_loop, demuxer, NULL); } else { if (mode == GST_PAD_MODE_PULL) gst_pad_stop_task (pad); } out: return res; } static GstFlowReturn gst_rs_demuxer_sink_chain (GstPad * pad, GstObject * parent, GstBuffer * buf) { GstRsDemuxer *demuxer = GST_RS_DEMUXER (parent); GstFlowReturn res; GST_TRACE_OBJECT (demuxer, "Handling buffer %p", buf); res = demuxer_handle_buffer (demuxer->instance, buf); GST_TRACE_OBJECT (demuxer, "Handling buffer returned %s", gst_flow_get_name (res)); return res; } static gboolean gst_rs_demuxer_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) { GstRsDemuxer *demuxer = GST_RS_DEMUXER (parent); gboolean res = FALSE; switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEGMENT:{ // TODO res = TRUE; gst_event_unref (event); break; } case GST_EVENT_EOS: GST_DEBUG_OBJECT (demuxer, "Got EOS"); demuxer_end_of_stream (demuxer->instance); res = gst_pad_event_default (pad, parent, event); break; default: res = gst_pad_event_default (pad, parent, event); break; } return res; } static gboolean gst_rs_demuxer_src_query (GstPad * pad, GstObject * parent, GstQuery * query) { GstRsDemuxer *demuxer = GST_RS_DEMUXER (parent); gboolean res = FALSE; switch (GST_QUERY_TYPE (query)) { case GST_QUERY_POSITION:{ GstFormat format; gst_query_parse_position (query, &format, NULL); if (format == GST_FORMAT_TIME) { gint64 position; if (demuxer_get_position (demuxer->instance, &position)) { GST_DEBUG_OBJECT (demuxer, "Returning position %" GST_TIME_FORMAT, GST_TIME_ARGS (position)); gst_query_set_position (query, format, position); res = TRUE; } else { res = FALSE; } } break; } case GST_QUERY_DURATION:{ GstFormat format; gst_query_parse_duration (query, &format, NULL); if (format == GST_FORMAT_TIME) { gint64 duration; if (demuxer_get_duration (demuxer->instance, &duration)) { GST_DEBUG_OBJECT (demuxer, "Returning duration %" GST_TIME_FORMAT, GST_TIME_ARGS (duration)); gst_query_set_duration (query, format, duration); res = TRUE; } else { res = FALSE; } } break; } default: res = gst_pad_query_default (pad, parent, query); break; } return res; } static gboolean gst_rs_demuxer_src_event (GstPad * pad, GstObject * parent, GstEvent * event) { GstRsDemuxer *demuxer = GST_RS_DEMUXER (parent); gboolean res = FALSE; switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK:{ // TODO g_assert_not_reached (); gst_event_unref (event); break; } default: res = gst_pad_event_default (pad, parent, event); break; } return res; } static GstStateChangeReturn gst_rs_demuxer_change_state (GstElement * element, GstStateChange transition) { GstRsDemuxer *demuxer = GST_RS_DEMUXER (element); GstStateChangeReturn result; GST_DEBUG_OBJECT (demuxer, "Change state %s to %s", gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); switch (transition) { case GST_STATE_CHANGE_READY_TO_PAUSED: demuxer->offset = 0; gst_segment_init (&demuxer->segment, GST_FORMAT_TIME); demuxer->group_id = gst_util_group_id_next (); demuxer->segment_seqnum = gst_util_seqnum_next (); break; default: break; } if (result == GST_STATE_CHANGE_FAILURE) return result; result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); if (result == GST_STATE_CHANGE_FAILURE) return result; switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY:{ guint i; /* Ignore stop failures */ GST_DEBUG_OBJECT (demuxer, "Stopping"); demuxer_stop (demuxer->instance); gst_flow_combiner_clear (demuxer->flow_combiner); for (i = 0; i < G_N_ELEMENTS (demuxer->srcpads); i++) { if (demuxer->srcpads[i]) gst_element_remove_pad (GST_ELEMENT (demuxer), demuxer->srcpads[i]); } memset (demuxer->srcpads, 0, sizeof (demuxer->srcpads)); break; } default: break; } return result; } static void gst_rs_demuxer_loop (GstRsDemuxer * demuxer) { // TODO g_assert_not_reached (); } void gst_rs_demuxer_add_stream (GstRsDemuxer * demuxer, guint32 index, GstCaps * caps, const gchar * stream_id) { GstPad *pad; GstPadTemplate *templ; GstEvent *event; gchar *name, *full_stream_id; g_assert (demuxer->srcpads[index] == NULL); GST_DEBUG_OBJECT (demuxer, "Adding stream %u with format %" GST_PTR_FORMAT " and stream id %s", index, caps, stream_id); templ = gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (demuxer), "src_%u"); name = g_strdup_printf ("src_%u", index); pad = demuxer->srcpads[index] = gst_pad_new_from_template (templ, name); g_free (name); gst_pad_set_query_function (pad, gst_rs_demuxer_src_query); gst_pad_set_event_function (pad, gst_rs_demuxer_src_event); gst_pad_set_active (pad, TRUE); full_stream_id = gst_pad_create_stream_id (pad, GST_ELEMENT (demuxer), stream_id); event = gst_event_new_stream_start (full_stream_id); gst_event_set_group_id (event, demuxer->group_id); g_free (full_stream_id); gst_pad_push_event (pad, event); event = gst_event_new_caps (caps); gst_pad_push_event (pad, event); event = gst_event_new_segment (&demuxer->segment); gst_event_set_seqnum (event, demuxer->segment_seqnum); gst_pad_push_event (pad, event); gst_flow_combiner_add_pad (demuxer->flow_combiner, pad); gst_element_add_pad (GST_ELEMENT (demuxer), pad); } void gst_rs_demuxer_added_all_streams (GstRsDemuxer * demuxer) { GST_DEBUG_OBJECT (demuxer, "No more pads"); gst_element_no_more_pads (GST_ELEMENT (demuxer)); demuxer->group_id = gst_util_group_id_next (); } void gst_rs_demuxer_stream_format_changed (GstRsDemuxer * demuxer, guint32 index, GstCaps * caps) { GstEvent *event; g_assert (demuxer->srcpads[index] != NULL); GST_DEBUG_OBJECT (demuxer, "Format changed for stream %u: %" GST_PTR_FORMAT, index, caps); event = gst_event_new_caps (caps); gst_pad_push_event (demuxer->srcpads[index], event); } void gst_rs_demuxer_stream_eos (GstRsDemuxer * demuxer, guint32 index) { GstCaps *caps; GstEvent *event; g_assert (index == -1 || demuxer->srcpads[index] != NULL); GST_DEBUG_OBJECT (demuxer, "EOS for stream %u", index); event = gst_event_new_eos (); if (index == -1) { gint i; for (i = 0; i < G_N_ELEMENTS (demuxer->srcpads); i++) { if (demuxer->srcpads[i]) gst_pad_push_event (demuxer->srcpads[i], gst_event_ref (event)); } gst_event_unref (event); } else { gst_pad_push_event (demuxer->srcpads[index], event); } } GstFlowReturn gst_rs_demuxer_stream_push_buffer (GstRsDemuxer * demuxer, guint32 index, GstBuffer * buffer) { GstFlowReturn res = GST_FLOW_OK; g_assert (demuxer->srcpads[index] != NULL); GST_DEBUG_OBJECT (demuxer, "Pushing buffer %p for pad %u", buffer, index); res = gst_pad_push (demuxer->srcpads[index], buffer); GST_DEBUG_OBJECT (demuxer, "Pushed buffer returned: %s", gst_flow_get_name (res)); res = gst_flow_combiner_update_flow (demuxer->flow_combiner, res); GST_DEBUG_OBJECT (demuxer, "Combined return: %s", gst_flow_get_name (res)); return res; } void gst_rs_demuxer_remove_all_streams (GstRsDemuxer * demuxer) { guint i; GST_DEBUG_OBJECT (demuxer, "Removing all streams"); gst_flow_combiner_clear (demuxer->flow_combiner); for (i = 0; i < G_N_ELEMENTS (demuxer->srcpads); i++) { if (demuxer->srcpads[i]) gst_element_remove_pad (GST_ELEMENT (demuxer), demuxer->srcpads[i]); } memset (demuxer->srcpads, 0, sizeof (demuxer->srcpads)); } static gpointer gst_rs_demuxer_init_class (gpointer data) { demuxers = g_hash_table_new (g_direct_hash, g_direct_equal); GST_DEBUG_CATEGORY_INIT (gst_rs_demuxer_debug, "rsdemux", 0, "Rust demuxer base class"); parent_class = g_type_class_ref (GST_TYPE_ELEMENT); } gboolean gst_rs_demuxer_register (GstPlugin * plugin, const gchar * name, const gchar * long_name, const gchar * description, const gchar * classification, const gchar * author, GstRank rank, void *create_instance, GstCaps * input_caps, GstCaps * output_caps) { GOnce gonce = G_ONCE_INIT; GTypeInfo type_info = { sizeof (GstRsDemuxerClass), NULL, NULL, (GClassInitFunc) gst_rs_demuxer_class_init, NULL, NULL, sizeof (GstRsDemuxer), 0, (GInstanceInitFunc) gst_rs_demuxer_init }; GType type; gchar *type_name; ElementData *data; g_once (&gonce, gst_rs_demuxer_init_class, NULL); GST_DEBUG ("Registering for %" GST_PTR_FORMAT ": %s", plugin, name); GST_DEBUG (" long name: %s", long_name); GST_DEBUG (" description: %s", description); GST_DEBUG (" classification: %s", classification); GST_DEBUG (" author: %s", author); GST_DEBUG (" rank: %d", rank); GST_DEBUG (" input caps: %" GST_PTR_FORMAT, input_caps); GST_DEBUG (" output caps: %" GST_PTR_FORMAT, output_caps); data = g_new0 (ElementData, 1); data->long_name = g_strdup (long_name); data->description = g_strdup (description); data->classification = g_strdup (classification); data->author = g_strdup (author); data->create_instance = create_instance; data->input_caps = gst_caps_ref (input_caps); data->output_caps = gst_caps_ref (output_caps); type_name = g_strconcat ("RsDemuxer-", name, NULL); type = g_type_register_static (GST_TYPE_ELEMENT, type_name, &type_info, 0); g_free (type_name); g_hash_table_insert (demuxers, GSIZE_TO_POINTER (type), data); if (!gst_element_register (plugin, name, rank, type)) return FALSE; return TRUE; }