#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include /* gpac includes */ #include #define GST_TYPE_GPAC_MUX (gst_gpac_mux_get_type()) #define GST_GPAC_MUX(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GPAC_MUX, GstGpacMux)) #define GST_GPAC_MUX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GPAC_MUX, GstGpacMux)) #define GST_IS_GPAC_MUX(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GPAC_MUX)) #define GST_IS_GPAC_MUX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GPAC_MUX)) typedef struct _GstGpacMux GstGpacMux; typedef struct _GstGpacMuxClass GstGpacMuxClass; typedef enum { GST_GPAC_PAD_STATE_CONTROL = 0, GST_GPAC_PAD_STATE_DATA = 1 } GstGpacPadState; typedef struct { GstCollectData collect; /* we extend the CollectData */ gint track_number; guint32 di; /* outDescriptionIndex */ GstBuffer *buffer; GstBuffer *next_buffer; guint32 frame_count; gboolean is_video; gboolean eos; } GstGpacPad; struct _GstGpacMux { GstElement element; GstPad *srcpad; GstCollectPads *collect; gint active_pads; GF_ISOFile *file; }; struct _GstGpacMuxClass { GstElementClass parent_class; }; /* elementfactory information */ static const GstElementDetails gst_gpac_mux_details = GST_ELEMENT_DETAILS ("Gpac muxer", "Codec/Muxer", "mux mp4 streams", "Hallyson Melo finalize = gst_gpac_mux_finalize; gstelement_class->request_new_pad = gst_gpac_mux_request_new_pad; gstelement_class->release_pad = gst_gpac_mux_release_pad; gstelement_class->change_state = gst_gpac_mux_change_state; } static void gst_gpac_mux_init (GstGpacMux * gpac_mux) { GstElementClass *klass = GST_ELEMENT_GET_CLASS (gpac_mux); gpac_mux->srcpad = gst_pad_new_from_template (gst_element_class_get_pad_template (klass, "src"), "src"); gst_pad_set_event_function (gpac_mux->srcpad, gst_gpac_mux_handle_src_event); gst_element_add_pad (GST_ELEMENT (gpac_mux), gpac_mux->srcpad); gpac_mux->collect = gst_collect_pads_new (); gst_collect_pads_set_function (gpac_mux->collect, (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_gpac_mux_collected), gpac_mux); /* Opens gpac library */ /* FIXME */ gpac_mux->file = gf_isom_open("/tmp/gpac.mp4", GF_ISOM_OPEN_WRITE, NULL); gf_isom_set_storage_mode(gpac_mux->file, GF_ISOM_STORE_FLAT /*STREAMABLE*/); //gst_gpac_mux_clear (gpac_mux); } static void gst_gpac_mux_finalize (GObject * object) { GstGpacMux *gpac_mux; gpac_mux = GST_GPAC_MUX (object); if (gpac_mux->collect) { gst_object_unref (gpac_mux->collect); gpac_mux->collect = NULL; } G_OBJECT_CLASS (parent_class)->finalize (object); } static void gst_gpac_mux_gpac_pad_destroy_notify (GstCollectData * data) { GstGpacPad *gpacpad = (GstGpacPad *) data; GstBuffer *buf; } static GstPadLinkReturn gst_gpac_mux_sinkconnect (GstPad * pad, GstPad * peer) { GstGpacMux *gpac_mux; gpac_mux = GST_GPAC_MUX (gst_pad_get_parent (pad)); GST_DEBUG_OBJECT (gpac_mux, "sinkconnect triggered on %s", GST_PAD_NAME (pad)); gst_object_unref (gpac_mux); return GST_PAD_LINK_OK; } static GstPad * gst_gpac_mux_request_new_pad (GstElement * element, GstPadTemplate * templ, const gchar * req_name) { GstGpacMux *gpac_mux; GstPad *newpad; GstElementClass *klass; gchar *padname = NULL; gint serial; gboolean is_video = FALSE; g_return_val_if_fail (templ != NULL, NULL); if (templ->direction != GST_PAD_SINK) goto wrong_direction; g_return_val_if_fail (GST_IS_GPAC_MUX (element), NULL); gpac_mux = GST_GPAC_MUX (element); klass = GST_ELEMENT_GET_CLASS (element); if (req_name == NULL || strlen (req_name) < 6) { /* no name given when requesting the pad, use random serial number */ serial = rand (); } else { /* parse serial number from requested padname */ serial = atoi (&req_name[5]); } if (templ == gst_element_class_get_pad_template (klass, "video_%d")) { is_video = TRUE; padname = g_strdup_printf ("video_%d", serial); } else if (templ != gst_element_class_get_pad_template (klass, "audio_%d")) { goto wrong_template; } else { padname = g_strdup_printf ("audio_%d", serial); } { /* create new pad with the name */ GST_DEBUG_OBJECT (gpac_mux, "Creating new pad for serial %d", serial); newpad = gst_pad_new_from_template (templ, padname); g_free (padname); /* construct our own wrapper data structure for the pad to * keep track of its status */ { GstGpacPad *gpacpad; gpacpad = (GstGpacPad *) gst_collect_pads_add_pad_full (gpac_mux->collect, newpad, sizeof (GstGpacPad), gst_gpac_mux_gpac_pad_destroy_notify); gpac_mux->active_pads++; /* gpac new track */ gpacpad->is_video = is_video; if (gpacpad->is_video) { gpacpad->track_number = gf_isom_new_track(gpac_mux->file, 0, GF_ISOM_MEDIA_VISUAL, 10 * 1000 /* time scale */); } else { gpacpad->track_number = gf_isom_new_track(gpac_mux->file, 0, GF_ISOM_MEDIA_AUDIO, 48000 /*time scale */); } if (gpacpad->track_number == 0) { g_warning ("Error while adding the new gpac track"); } else { gf_isom_set_track_enabled(gpac_mux->file, gpacpad->track_number, 1); if (is_video) { GF_ESD *esd = gf_odf_desc_esd_new (0); esd->ESID = gf_isom_get_track_id(gpac_mux->file, gpacpad->track_number); gf_isom_new_mpeg4_description( gpac_mux->file, gpacpad->track_number, esd, NULL, NULL, &(gpacpad->di)); gf_isom_set_visual_info (gpac_mux->file, gpacpad->track_number, gpacpad->di, 320, 240);//fixme //gf_isom_set_cts_packing (gpac_mux->file, gpacpad->track_number, 0); } else { GF_ESD *esd = gf_odf_desc_esd_new (2); esd->ESID = gf_isom_get_track_id(gpac_mux->file, gpacpad->track_number); gf_isom_new_mpeg4_description(gpac_mux->file, gpacpad->track_number, esd, NULL, NULL, &(gpacpad->di)); gf_isom_set_audio_info(gpac_mux->file, gpacpad->track_number, gpacpad->di, 48000, 2 /*num channels */, 16); } } } } /* setup some pad functions */ gst_pad_set_link_function (newpad, gst_gpac_mux_sinkconnect); /* dd the pad to the element */ gst_element_add_pad (element, newpad); return newpad; /* ERRORS */ wrong_direction: { g_warning ("gpac_mux: request pad that is not a SINK pad\n"); return NULL; } wrong_template: { g_warning ("gpac_mux: this is not our template!\n"); return NULL; } } static void gst_gpac_mux_release_pad (GstElement * element, GstPad * pad) { GstGpacMux *gpac_mux; gpac_mux = GST_GPAC_MUX (gst_pad_get_parent (pad)); gst_collect_pads_remove_pad (gpac_mux->collect, pad); gst_element_remove_pad (element, pad); } /* handle events */ static gboolean gst_gpac_mux_handle_src_event (GstPad * pad, GstEvent * event) { GstEventType type; type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN; switch (type) { case GST_EVENT_SEEK: /* disable seeking for now */ return FALSE; default: break; } return gst_pad_event_default (pad, event); } static GstFlowReturn gst_gpac_mux_push_buffer (GstGpacMux * mux, GstBuffer * buffer) { GstCaps *caps; #if 0 /* fix up OFFSET and OFFSET_END again */ GST_BUFFER_OFFSET (buffer) = mux->offset; mux->offset += GST_BUFFER_SIZE (buffer); GST_BUFFER_OFFSET_END (buffer) = mux->offset; /* Ensure we have monotonically increasing timestamps in the output. */ if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) { if (mux->last_ts != GST_CLOCK_TIME_NONE && GST_BUFFER_TIMESTAMP (buffer) < mux->last_ts) GST_BUFFER_TIMESTAMP (buffer) = mux->last_ts; else mux->last_ts = GST_BUFFER_TIMESTAMP (buffer); } #endif caps = gst_pad_get_negotiated_caps (mux->srcpad); gst_buffer_set_caps (buffer, caps); if (caps != NULL) { gst_caps_unref (caps); } return gst_pad_push (mux->srcpad, buffer); } static GstFlowReturn gst_gpac_mux_collected (GstCollectPads * pads, GstGpacMux * gpac_mux) { GstFlowReturn ret = GST_FLOW_OK; GstGpacPad *pad; gint active_before; GST_LOG_OBJECT (gpac_mux, "collected"); active_before = gpac_mux->active_pads; pad = gst_gpac_mux_queue_pads (gpac_mux); if (!pad) { return GST_FLOW_WRONG_STATE; } if (pad->buffer) { ret = gst_gpac_mux_process_pad (gpac_mux, pad); } if (gpac_mux->active_pads < active_before) { /* If the active pad count went down, this mean at least one pad has gone * EOS. Since CollectPads only calls _collected() once when all pads are * EOS, and our code doesn't _pop() from all pads we need to check that by * peeking on all pads, else we won't be called again and the muxing will * not terminate (push out EOS). */ printf ("XXXX um pad foi desativado %" GST_PTR_FORMAT "\n", pad); /* if all the pads have been removed, flush all pending data */ if ((ret == GST_FLOW_OK) && gst_gpac_mux_all_pads_eos (pads)) { GST_LOG_OBJECT (gpac_mux, "no pads remaining, flushing data"); do { pad = gst_gpac_mux_queue_pads (gpac_mux); if (pad) ret = gst_gpac_mux_process_pad (gpac_mux, pad); } while ((ret == GST_FLOW_OK) && (pad != NULL)); /* gpac file close (eos) */ // Fixme: this should flush all data to src pad // Fixme: where to release gpac_mux->file? printf ("CCCCCCCCCCCCCCCCcclosing the file\n"); gf_isom_close (gpac_mux->file); GST_DEBUG_OBJECT (gpac_mux, "Pushing EOS"); gst_pad_push_event (gpac_mux->srcpad, gst_event_new_eos ()); } } return ret; } static gboolean gst_gpac_mux_all_pads_eos (GstCollectPads * pads) { GSList *walk; gboolean alleos = TRUE; walk = pads->data; while (walk) { GstBuffer *buf; GstCollectData *data = (GstCollectData *) walk->data; buf = gst_collect_pads_peek (pads, data); if (buf) { alleos = FALSE; gst_buffer_unref (buf); break; } walk = walk->next; } return alleos; } static GstFlowReturn gst_gpac_mux_process_pad (GstGpacMux *gpac_mux, GstGpacPad *pad) { GstFlowReturn ret = GST_FLOW_OK; GF_ISOSample *sample; if (pad->buffer == NULL) { printf ("Buffer is null, wrong state\n"); return GST_FLOW_WRONG_STATE; } /* gpac output */ printf ("xxxxxx buffer size: %d\n", GST_BUFFER_SIZE(pad->buffer)); fflush (stdout); printf ("xxxx frames %d\n", pad->frame_count); sample = gf_isom_sample_new(); sample->dataLength = GST_BUFFER_SIZE(pad->buffer); sample->data = GST_BUFFER_DATA(pad->buffer); sample->CTS_Offset = 0; if (pad->is_video) { sample->IsRAP = 0; sample->DTS += 1000*pad->frame_count; } else { sample->IsRAP = 0; sample->DTS = 2048*pad->frame_count; } gf_isom_add_sample(gpac_mux->file, pad->track_number, pad->di, sample); sample->data = NULL; gf_isom_sample_del(&sample); /* gstreamer output (push) */ ret = gst_gpac_mux_push_buffer (gpac_mux, pad->buffer); pad->buffer = NULL; if (ret != GST_FLOW_OK) { } pad->frame_count++; return ret; } /* reset all variables in the gpac pads. */ static void gst_gpac_mux_init_collectpads (GstCollectPads * collect) { GSList *iter; iter = collect->data; while (iter) { GstGpacPad *gpacpad = (GstGpacPad *) iter->data; //gpac_stream_init (&gpacpad->stream, gpacpad->serial); //gpacpad->packetno = 0; //gpacpad->pageno = 0; //gpacpad->eos = FALSE; /* we assume there will be some control data first for this pad */ //gpacpad->state = GST_GPAC_PAD_STATE_CONTROL; //gpacpad->new_page = TRUE; //gpacpad->first_delta = FALSE; //gpacpad->prev_delta = FALSE; //gpacpad->pagebuffers = g_queue_new (); iter = g_slist_next (iter); } } /* Clear all buffers from the collectpads object */ static void gst_gpac_mux_clear_collectpads (GstCollectPads * collect) { GSList *iter; for (iter = collect->data; iter; iter = g_slist_next (iter)) { GstGpacPad *gpacpad = (GstGpacPad *) iter->data; GstBuffer *buf; //gpac_stream_clear (&gpacpad->stream); /* while ((buf = g_queue_pop_head (gpacpad->pagebuffers)) != NULL) { gst_buffer_unref (buf); } g_queue_free (gpacpad->pagebuffers); gpacpad->pagebuffers = NULL;*/ } } static GstStateChangeReturn gst_gpac_mux_change_state (GstElement * element, GstStateChange transition) { GstGpacMux *gpac_mux; GstStateChangeReturn ret; gpac_mux = GST_GPAC_MUX (element); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: break; case GST_STATE_CHANGE_READY_TO_PAUSED: //gst_gpac_mux_clear (gpac_mux); //gst_gpac_mux_init_collectpads (gpac_mux->collect); gst_collect_pads_start (gpac_mux->collect); break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: break; case GST_STATE_CHANGE_PAUSED_TO_READY: gst_collect_pads_stop (gpac_mux->collect); break; default: break; } ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); switch (transition) { case GST_STATE_CHANGE_PLAYING_TO_PAUSED: break; case GST_STATE_CHANGE_PAUSED_TO_READY: gst_gpac_mux_clear_collectpads (gpac_mux->collect); break; case GST_STATE_CHANGE_READY_TO_NULL: break; default: break; } return ret; } static GstGpacPad * gst_gpac_mux_queue_pads (GstGpacMux * gpac_mux) { GstGpacPad *bestpad = NULL;//, *still_hungry = NULL; GSList *iter; /* try to make sure we have a buffer from each usable pad first */ iter = gpac_mux->collect->data; while (iter) { GstGpacPad *pad; GstCollectData *data; data = (GstCollectData *) iter->data; pad = (GstGpacPad *) data; iter = g_slist_next (iter); GST_LOG_OBJECT (data->pad, "looking at pad for buffer"); /* try to get a new buffer for this pad if needed and possible */ if (pad->buffer == NULL) { GstBuffer *buf; buf = gst_collect_pads_pop (gpac_mux->collect, data); GST_LOG_OBJECT (data->pad, "popped buffer %" GST_PTR_FORMAT, buf); /* On EOS we get a NULL buffer */ if (buf == NULL) { printf ("EENENENENENEND OF STREAM EOS\n"); GST_DEBUG_OBJECT (data->pad, "EOS on pad"); if (!pad->eos) { /* it's no longer active */ gpac_mux->active_pads--; pad->eos = TRUE; } } pad->buffer = buf; } /* we should have a buffer now, see if it is the best pad to * pull on */ if (gst_gpac_mux_compare_pads (gpac_mux, bestpad, pad) > 0) { GST_LOG_OBJECT (data->pad, "new best pad, with buffers %" GST_PTR_FORMAT, pad->buffer); bestpad = pad; } } return bestpad; } static gint gst_gpac_mux_compare_pads (GstGpacMux * ogg_mux, GstGpacPad *first, GstGpacPad *second) { guint64 firsttime, secondtime; /* if the first pad doesn't contain anything or is even NULL, return * the second pad as best candidate and vice versa */ if (first == NULL || (first->buffer == NULL)) return 1; if (second == NULL || (second->buffer == NULL)) return -1; /* no timestamp on first buffer, it must go first */ if (first->buffer) firsttime = GST_BUFFER_TIMESTAMP (first->buffer); if (firsttime == GST_CLOCK_TIME_NONE) return -1; /* no timestamp on second buffer, it must go first */ if (second->buffer) secondtime = GST_BUFFER_TIMESTAMP (second->buffer); if (secondtime == GST_CLOCK_TIME_NONE) return 1; /* first buffer has higher timestamp, second one should go first */ if (secondtime < firsttime) return 1; /* second buffer has higher timestamp, first one should go first */ else if (secondtime > firsttime) return -1; /* same priority if all of the above failed */ return 0; } static gboolean gst_gpac_mux_plugin_init (GstPlugin * plugin) { return gst_element_register (plugin, "gpacmux", GST_RANK_NONE, GST_TYPE_GPAC_MUX); } GST_PLUGIN_DEFINE(GST_VERSION_MAJOR, GST_VERSION_MINOR, "gpacmux", "Muxes audio and video", gst_gpac_mux_plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)