#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "gmencoder.h" #define G_MENCODER_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), G_TYPE_MENCODER, GMencoderPrivate)) typedef struct _GMencoderPrivate GMencoderPrivate; struct _GMencoderPrivate { GstElement *pipe; GstElement *abin; GstElement *vbin; GstElement *sink; gboolean ready; }; enum { READY, PAUSED, PLAYING, STOPED, EOS, ERROR, LAST_SIGNAL }; static void g_mencoder_class_init (GMencoderClass *klass); static void g_mencoder_init (GMencoder *object); static void g_mencoder_dispose (GObject *object); static void g_mencoder_finalize (GObject *object); static GstElement* _create_audio_bin (GMencoder *self, const gchar* encode, gchar** encode_prop, gint rate); static GstElement* _create_video_bin (GMencoder* self, const gchar* encode, gchar** encode_prop, gdouble fps, gint rate, guint width, guint height); static gboolean _pipeline_bus_cb (GstBus *bus, GstMessage *msg, gpointer user_data); static void _decodebin_new_pad_cb (GstElement* object, GstPad* pad, gboolean flag, gpointer user_data); static void _decodebin_unknown_type_cb (GstElement* object, GstPad* pad, GstCaps* caps, gpointer user_data); static guint g_mencoder_signals[LAST_SIGNAL] = { 0 }; G_DEFINE_TYPE(GMencoder, g_mencoder, G_TYPE_OBJECT) static void g_mencoder_class_init (GMencoderClass *klass) { GObjectClass *object_class; object_class = (GObjectClass *) klass; g_type_class_add_private (klass, sizeof (GMencoderPrivate)); object_class->dispose = g_mencoder_dispose; object_class->finalize = g_mencoder_finalize; g_mencoder_signals[READY] = g_signal_new ("ready", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); g_mencoder_signals[PAUSED] = g_signal_new ("paused", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); g_mencoder_signals[PLAYING] = g_signal_new ("playing", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); g_mencoder_signals[STOPED] = g_signal_new ("stoped", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); g_mencoder_signals[EOS] = g_signal_new ("eos", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); g_mencoder_signals[ERROR] = g_signal_new ("error", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); } static void g_mencoder_init (GMencoder *self) { } static void g_mencoder_dispose (GObject *object) { } static void g_mencoder_finalize (GObject *object) { g_mencoder_close_stream (G_MENCODER (object)); } GMencoder* g_mencoder_new (void) { return g_object_new (G_TYPE_MENCODER, NULL); } static void _obj_set_prop (GObject *obj, const gchar *prop_name, const gchar *prop_val) { GValue p = {0}; GValue v = {0}; GParamSpec *s = NULL; GObjectClass *k = G_OBJECT_GET_CLASS (obj); g_value_init (&v, G_TYPE_STRING); g_value_set_string (&v, prop_val); g_debug ("PROP [%s] VAL [%s]", prop_name, prop_val); s = g_object_class_find_property (k, prop_name); if (s == NULL) { g_print ("Invalid property name: %s\n", prop_name); return; } g_value_init (&p, s->value_type); switch (s->value_type) { case G_TYPE_INT: g_value_set_int (&p, atoi (prop_val)); break; case G_TYPE_STRING: g_value_set_string (&p, prop_val); break; default: return; } g_object_set_property (obj, prop_name, &p); g_value_unset (&v); g_value_unset (&p); } static GstElement* _create_element_with_prop (const gchar* factory_name, const gchar* element_name, gchar** prop) { GstElement *ret; int i; g_debug ("SET OBJ [%s]", factory_name); ret = gst_element_factory_make (factory_name, element_name); if (ret == NULL) return NULL; if (prop != NULL) { for (i=0; i < g_strv_length (prop); i++) { char** v = g_strsplit(prop[i], "=", 2); if (g_strv_length (v) == 2) { _obj_set_prop (G_OBJECT (ret), v[0], v[1]); } g_strfreev (v); } } return ret; } static GstElement* _create_audio_bin (GMencoder* self, const gchar* encode, gchar** encode_prop, gint rate) { GstElement *abin = NULL; GstElement *aqueue = NULL; GstElement *aconvert = NULL; GstElement *aencode = NULL; GstElement *aqueue_src = NULL; GstPad *apad = NULL; //audio/x-raw-int ! queue ! audioconvert ! faac ! rtpmp4gpay ! udpsink name=upd_audio host=224.0.0.1 port=5002 abin = gst_bin_new ("abin"); aqueue = gst_element_factory_make ("queue", "aqueue"); aconvert= gst_element_factory_make ("audioconvert", "aconvert"); aencode = _create_element_with_prop ((encode ? encode : "lame"), "aencode", encode_prop); aqueue_src= gst_element_factory_make ("queue", "aqueue_src"); if ((abin == NULL) || (aqueue == NULL) || (aconvert == NULL) || (aencode == NULL) || (aqueue_src == NULL)) { g_warning ("Audio elements not found"); goto error; } gst_bin_add_many (GST_BIN (abin), aqueue, aconvert, aencode, aqueue_src, NULL); gst_element_link_many (aqueue, aconvert, aencode, aqueue_src, NULL); //TODO: apply audio rate // ghost pad the audio bin apad = gst_element_get_pad (aqueue, "sink"); gst_element_add_pad (abin, gst_ghost_pad_new("sink", apad)); gst_object_unref (apad); apad = gst_element_get_pad (aqueue_src, "src"); gst_element_add_pad (abin, gst_ghost_pad_new("src", apad)); gst_object_unref (apad); return abin; error: if (abin != NULL) gst_object_unref (abin); if (aqueue != NULL) gst_object_unref (aqueue); if (aconvert != NULL) gst_object_unref (aconvert); if (aencode != NULL) gst_object_unref (aencode); if (aqueue_src != NULL) gst_object_unref (aqueue_src); if (apad != NULL) gst_object_unref (apad); return NULL; } //queue ! videoscale ! video/x-raw-yuv,width=240,height=144 ! colorspace ! rate ! encode ! queue static GstElement* _create_video_bin (GMencoder* self, const gchar* encode, gchar** encode_prop, gdouble fps, gint rate, guint width, guint height) { GstElement *vbin = NULL; GstElement *vqueue = NULL; GstElement* vqueue_src = NULL; GstElement *vcolorspace = NULL; GstElement *vencode = NULL; GstElement *vrate = NULL; GstPad *vpad = NULL; vbin = gst_bin_new ("vbin"); vqueue = gst_element_factory_make ("queue", "vqueue"); vcolorspace = gst_element_factory_make ("ffmpegcolorspace", "colorspace"); vencode = _create_element_with_prop ((encode ? encode : "ffenc_mpeg1video"), "vencode", encode_prop); vrate = gst_element_factory_make ("videorate", "vrate"); vqueue_src = gst_element_factory_make ("queue", "queue_src"); if ((vbin == NULL) || (vqueue == NULL) || (vcolorspace == NULL) || (vencode == NULL) || (vqueue_src == NULL)) { g_warning ("Video elements not found"); goto error; } gst_bin_add_many (GST_BIN (vbin), vqueue, vcolorspace, vencode, vqueue_src, NULL); if ((width > 0) && (height > 0)) { //Scalling video GstCaps *vcaps; GstElement *vscale = gst_element_factory_make ("videoscale", "vscale"); gst_bin_add (GST_BIN (vbin), vscale); vcaps = gst_caps_new_simple ("video/x-raw-yuv", "width", G_TYPE_INT, width, "height", G_TYPE_INT, height, NULL); gst_element_link (vqueue, vscale); if (gst_element_link_filtered (vscale, vcolorspace, vcaps) == FALSE) { g_warning ("Fail to resize video"); gst_object_unref (vcaps); gst_object_unref (vscale); goto error; } gst_caps_unref (vcaps); } else { gst_element_link (vqueue, vcolorspace); } if (fps > 0) { //Changing the video fps GstCaps *vcaps; gst_bin_add (GST_BIN (vbin), vrate); if (gst_element_link (vcolorspace, vrate) == FALSE) { g_warning ("Fail to link video elements"); goto error; } vcaps = gst_caps_new_simple ("video/x-raw-yuv", "framerate", GST_TYPE_FRACTION, (int) (fps * 1000), 1000, NULL); if (gst_element_link_filtered (vrate, vencode, vcaps) == FALSE) { g_warning ("Fail to link vrate with vencode."); goto error; } gst_caps_unref (vcaps); } else { if (gst_element_link (vcolorspace, vencode) == FALSE) { g_warning ("Fail to link colorspace and video encode element."); goto error; } } gst_element_link (vencode, vqueue_src); // ghost pad the video bin vpad = gst_element_get_pad (vqueue, "sink"); gst_element_add_pad (vbin, gst_ghost_pad_new ("sink", vpad)); gst_object_unref (vpad); vpad = gst_element_get_pad (vqueue_src, "src"); gst_element_add_pad (vbin, gst_ghost_pad_new ("src", vpad)); gst_object_unref (vpad); return vbin; error: if (vpad != NULL) gst_object_unref (vpad); if (vbin != NULL) gst_object_unref (vbin); if (vqueue != NULL) gst_object_unref (vqueue); if (vencode != NULL) gst_object_unref (vencode); if (vqueue_src != NULL) gst_object_unref (vqueue_src); if (vcolorspace != NULL) gst_object_unref (vcolorspace); return NULL; } gboolean g_mencoder_setup_stream (GMencoder *self, const gchar* uri, const gchar* video_encode, gchar** video_encode_prop, gdouble video_fps, gdouble video_rate, guint video_width, guint video_height, const gchar* audio_encode, gchar** audio_encode_prop, guint audio_rate, const gchar* sink_name, gchar** sink_prop) { GstBus *bus = NULL; GstElement *pipe = NULL; GstElement *fdsink = NULL; GstElement *mux = NULL; GstElement *decode = NULL; GstElement *src = NULL; GstElement *abin = NULL; GstElement *vbin = NULL; GstPad *aux_pad = NULL; GstPad *mux_pad = NULL; GMencoderPrivate *priv = G_MENCODER_GET_PRIVATE (self); pipe = gst_pipeline_new ("pipe"); src = gst_element_make_from_uri (GST_URI_SRC, uri, "src"); if (src == NULL) goto error; decode = gst_element_factory_make ("decodebin2", "decode"); if (decode == NULL) goto error; mux = gst_element_factory_make ("ffmux_mpeg", "mux"); if (mux == NULL) goto error; fdsink = _create_element_with_prop (sink_name, "sink", sink_prop); if (fdsink == NULL) goto error; abin = _create_audio_bin (self, audio_encode, audio_encode_prop, audio_rate); if (abin == NULL) goto error; vbin = _create_video_bin (self, video_encode, video_encode_prop, video_fps, video_rate, video_width, video_height); if (vbin == NULL) goto error; // Finish Pipe gst_bin_add_many (GST_BIN (pipe), src, decode, abin, vbin, mux, fdsink, NULL); gst_element_link (src, decode); //Link bins with mux aux_pad = gst_element_get_pad (abin, "src"); mux_pad = gst_element_get_pad (mux, "audio_0"); GstPadLinkReturn ret = gst_pad_link (aux_pad, mux_pad); if (ret != GST_PAD_LINK_OK) { g_warning ("Fail link audio and mux: %d", ret); goto error; } gst_object_unref (aux_pad); gst_object_unref (mux_pad); aux_pad = gst_element_get_pad (vbin, "src"); mux_pad = gst_element_get_pad (mux, "video_0"); ret = gst_pad_link (aux_pad, mux_pad); if (ret != GST_PAD_LINK_OK) { g_warning ("Fail link video and mux: %d", ret); goto error; } gst_object_unref (aux_pad); gst_object_unref (mux_pad); aux_pad = NULL; mux_pad = NULL; //Link mux with sink gst_element_link (mux, fdsink); g_signal_connect (G_OBJECT (decode), "new-decoded-pad", G_CALLBACK (_decodebin_new_pad_cb), self); g_signal_connect (G_OBJECT (decode), "unknown-type", G_CALLBACK (_decodebin_unknown_type_cb), self); bus = gst_pipeline_get_bus (GST_PIPELINE (pipe)); gst_bus_add_watch (bus, _pipeline_bus_cb, self); gst_object_unref (bus); priv->pipe = pipe; priv->abin = abin; priv->vbin = vbin; priv->sink = fdsink; priv->ready = FALSE; gst_element_set_state (pipe, GST_STATE_PAUSED); return TRUE; error: g_warning ("Invalid uri"); if (pipe != NULL) { gst_object_unref (pipe); } if (src != NULL) { gst_object_unref (src); } if (mux != NULL) { gst_object_unref (mux); } if (mux_pad != NULL) { gst_object_unref (mux_pad); } if (aux_pad != NULL) { gst_object_unref (mux_pad); } if (fdsink != NULL) { gst_object_unref (fdsink); } if (abin != NULL) { gst_object_unref (abin); } if (vbin != NULL) { gst_object_unref (vbin); } return FALSE; } gboolean g_mencoder_play_stream (GMencoder *self) { GMencoderPrivate *priv = G_MENCODER_GET_PRIVATE (self); g_return_val_if_fail (priv->ready == TRUE, FALSE); if (gst_element_set_state (priv->pipe, GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE) { g_debug ("PLAYING"); return TRUE; } return FALSE; } gboolean g_mencoder_pause_stream (GMencoder *self) { GMencoderPrivate *priv = G_MENCODER_GET_PRIVATE (self); g_return_val_if_fail (priv->ready == TRUE, FALSE); if (gst_element_set_state (priv->pipe, GST_STATE_PAUSED) != GST_STATE_CHANGE_FAILURE) { g_debug ("PAUSED"); return TRUE; } return FALSE; } void g_mencoder_close_stream (GMencoder *self) { GMencoderPrivate *priv = G_MENCODER_GET_PRIVATE (self); g_return_if_fail (priv->ready == TRUE); gst_element_set_state (priv->pipe, GST_STATE_NULL); gst_object_unref (priv->pipe); priv->pipe = NULL; priv->abin = NULL; priv->vbin = NULL; priv->sink = NULL; priv->ready = FALSE; } static gboolean _pipeline_bus_cb (GstBus *bus, GstMessage *msg, gpointer user_data) { switch (GST_MESSAGE_TYPE (msg)) { case GST_MESSAGE_STATE_CHANGED: { GstState oldstate; GstState newstate; GstState pendingstate; GMencoderPrivate *priv = G_MENCODER_GET_PRIVATE (user_data); gst_message_parse_state_changed (msg, &oldstate, &newstate, &pendingstate); if (pendingstate != GST_STATE_VOID_PENDING) break; if ((oldstate == GST_STATE_READY) && (newstate == GST_STATE_PAUSED)) { if (priv->ready) g_signal_emit (user_data, g_mencoder_signals[PAUSED], 0); else { priv->ready = TRUE; g_signal_emit (user_data, g_mencoder_signals[READY], 0); } } else if ((oldstate == GST_STATE_PAUSED) && (newstate == GST_STATE_PLAYING)) { g_signal_emit (user_data, g_mencoder_signals[PLAYING], 0); } else if ((oldstate == GST_STATE_READY) && (newstate == GST_STATE_NULL)) { g_signal_emit (user_data, g_mencoder_signals[STOPED], 0); } break; } case GST_MESSAGE_ERROR: { GError *error; gchar *debug; gchar *err_str; gst_message_parse_error (msg, &error, &debug); err_str = g_strdup_printf ("Error [%d] %s (%s)", error->code, error->message, debug); g_signal_emit (user_data, g_mencoder_signals[ERROR], 0, err_str); g_free (err_str); g_clear_error (&error); g_free (debug); break; } case GST_MESSAGE_EOS: g_signal_emit (user_data, g_mencoder_signals[EOS], 0); break; default: break; } return TRUE; } static void _decodebin_new_pad_cb (GstElement* object, GstPad* pad, gboolean flag, gpointer user_data) { GstCaps *caps; gchar *str_caps = NULL; GMencoderPrivate *priv = G_MENCODER_GET_PRIVATE (user_data); caps = gst_pad_get_caps (pad); str_caps = gst_caps_to_string (caps); g_debug ("CAPS : %s", str_caps); if (strstr (str_caps, "audio") != NULL) { GstPad *apad = gst_element_get_pad (priv->abin, "sink"); gst_pad_link (pad, apad); gst_object_unref (apad); } else if (strstr (str_caps, "video") != NULL) { GstPad *vpad = gst_element_get_pad (priv->vbin, "sink"); gst_pad_link (pad, vpad); gst_object_unref (vpad); } else { g_warning ("invalid caps %s", str_caps); } g_free (str_caps); gst_caps_unref (caps); } static void _decodebin_unknown_type_cb (GstElement* object, GstPad* pad, GstCaps* caps, gpointer user_data) { g_warning ("Unknown Type"); }