#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 */ guint32 frame_count; gboolean is_video; } GstGpacPad; struct _GstGpacMux { GstElement element; GstPad *srcpad; GstCollectPads *collect; 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; /* if (gpacpad->pagebuffers) { while ((buf = g_queue_pop_head (gpacpad->pagebuffers)) != NULL) { gst_buffer_unref (buf); } g_queue_free (gpacpad->pagebuffers); gpacpad->pagebuffers = NULL; }*/ } 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); printf ("XXXX new pad from template\n"); 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 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 gboolean all_pads_eos (GstCollectPads * pads) { GSList *iter; gboolean alleos = TRUE; iter = pads->data; while (iter) { GstBuffer *buf; GstCollectData *data = (GstCollectData *) iter->data; buf = gst_collect_pads_peek (pads, data); if (buf) { alleos = FALSE; gst_buffer_unref (buf); goto beach; } iter = iter->next; } beach: return alleos; } static GstFlowReturn gst_gpac_mux_collected (GstCollectPads * pads, GstGpacMux * gpac_mux) { GstFlowReturn ret = GST_FLOW_OK; GSList *iter; GstBuffer *buf; gint i = 0; GST_LOG_OBJECT (gpac_mux, "collected"); iter = gpac_mux->collect->data; while (iter) { GstCollectData *data = (GstCollectData *) iter->data; GstGpacPad *pad = (GstGpacPad *) data; buf = gst_collect_pads_pop (gpac_mux->collect, data); if (buf == NULL) { iter = g_slist_next (iter); continue; } GST_LOG_OBJECT (data->pad, "popped buffer %" GST_PTR_FORMAT, buf); /* gpac output */ printf ("xxxxxx buffer size: %d\n", GST_BUFFER_SIZE(buf)); fflush (stdout); if (pad->frame_count < 300) { GF_ISOSample *sample = gf_isom_sample_new(); sample->dataLength = GST_BUFFER_SIZE(buf); sample->data = GST_BUFFER_DATA(buf); 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); printf ("xxxx frames %d\n", pad->frame_count); } else if (pad->frame_count == 300) { printf ("XXX closing gpac output file\n"); fflush (stdout); gf_isom_close (gpac_mux->file); } /* gstreamer output (push) */ if (gst_gpac_mux_push_buffer (gpac_mux, buf) != GST_FLOW_OK) { printf ("EEEEEEEE push failed\n"); } iter = g_slist_next (iter); pad->frame_count++; i++; } /* fixme */ return ret; } /* reset all variables in the gpac pads. */ static void gst_gpac_mux_init_collectpads (GstCollectPads * collect) { GSList *walk; walk = collect->data; while (walk) { GstGpacPad *gpacpad = (GstGpacPad *) walk->data; //ogg_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 (); walk = g_slist_next (walk); } } /* Clear all buffers from the collectpads object */ static void gst_gpac_mux_clear_collectpads (GstCollectPads * collect) { GSList *walk; for (walk = collect->data; walk; walk = g_slist_next (walk)) { GstGpacPad *gpacpad = (GstGpacPad *) walk->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 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)